4f28d31c6714762ebdcf014fa395ba853e749f7e
[gnus] / lisp / imap.el
1 ;;; imap.el --- imap library
2
3 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 ;;   2005, 2006 Free Software Foundation, Inc.
5
6 ;; Author: Simon Josefsson <jas@pdc.kth.se>
7 ;; Keywords: mail
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;; imap.el is a elisp library providing an interface for talking to
29 ;; IMAP servers.
30 ;;
31 ;; imap.el is roughly divided in two parts, one that parses IMAP
32 ;; responses from the server and storing data into buffer-local
33 ;; variables, and one for utility functions which send commands to
34 ;; server, waits for an answer, and return information.  The latter
35 ;; part is layered on top of the previous.
36 ;;
37 ;; The imap.el API consist of the following functions, other functions
38 ;; in this file should not be called directly and the result of doing
39 ;; so are at best undefined.
40 ;;
41 ;; Global commands:
42 ;;
43 ;; imap-open,       imap-opened,    imap-authenticate, imap-close,
44 ;; imap-capability, imap-namespace, imap-error-text
45 ;;
46 ;; Mailbox commands:
47 ;;
48 ;; imap-mailbox-get,       imap-mailbox-map,         imap-current-mailbox,
49 ;; imap-current-mailbox-p, imap-search,              imap-mailbox-select,
50 ;; imap-mailbox-examine,   imap-mailbox-unselect,    imap-mailbox-expunge
51 ;; imap-mailbox-close,     imap-mailbox-create,      imap-mailbox-delete
52 ;; imap-mailbox-rename,    imap-mailbox-lsub,        imap-mailbox-list
53 ;; imap-mailbox-subscribe, imap-mailbox-unsubscribe, imap-mailbox-status
54 ;; imap-mailbox-acl-get,   imap-mailbox-acl-set,     imap-mailbox-acl-delete
55 ;;
56 ;; Message commands:
57 ;;
58 ;; imap-fetch-asynch,                 imap-fetch,
59 ;; imap-current-message,              imap-list-to-message-set,
60 ;; imap-message-get,                  imap-message-map
61 ;; imap-message-envelope-date,        imap-message-envelope-subject,
62 ;; imap-message-envelope-from,        imap-message-envelope-sender,
63 ;; imap-message-envelope-reply-to,    imap-message-envelope-to,
64 ;; imap-message-envelope-cc,          imap-message-envelope-bcc
65 ;; imap-message-envelope-in-reply-to, imap-message-envelope-message-id
66 ;; imap-message-body,                 imap-message-flag-permanent-p
67 ;; imap-message-flags-set,            imap-message-flags-del
68 ;; imap-message-flags-add,            imap-message-copyuid
69 ;; imap-message-copy,                 imap-message-appenduid
70 ;; imap-message-append,               imap-envelope-from
71 ;; imap-body-lines
72 ;;
73 ;; It is my hope that these commands should be pretty self
74 ;; explanatory for someone that know IMAP.  All functions have
75 ;; additional documentation on how to invoke them.
76 ;;
77 ;; imap.el support RFC1730/2060/RFC3501 (IMAP4/IMAP4rev1), implemented
78 ;; IMAP extensions are RFC2195 (CRAM-MD5), RFC2086 (ACL), RFC2342
79 ;; (NAMESPACE), RFC2359 (UIDPLUS), the IMAP-part of RFC2595 (STARTTLS,
80 ;; LOGINDISABLED) (with use of external library starttls.el and
81 ;; program starttls), and the GSSAPI / kerberos V4 sections of RFC1731
82 ;; (with use of external program `imtest'), RFC2971 (ID).  It also
83 ;; takes advantage of the UNSELECT extension in Cyrus IMAPD.
84 ;;
85 ;; Without the work of John McClary Prevost and Jim Radford this library
86 ;; would not have seen the light of day.  Many thanks.
87 ;;
88 ;; This is a transcript of short interactive session for demonstration
89 ;; purposes.
90 ;;
91 ;; (imap-open "my.mail.server")
92 ;; => " *imap* my.mail.server:0"
93 ;;
94 ;; The rest are invoked with current buffer as the buffer returned by
95 ;; `imap-open'.  It is possible to do all without this, but it would
96 ;; look ugly here since `buffer' is always the last argument for all
97 ;; imap.el API functions.
98 ;;
99 ;; (imap-authenticate "myusername" "mypassword")
100 ;; => auth
101 ;;
102 ;; (imap-mailbox-lsub "*")
103 ;; => ("INBOX.sentmail" "INBOX.private" "INBOX.draft" "INBOX.spam")
104 ;;
105 ;; (imap-mailbox-list "INBOX.n%")
106 ;; => ("INBOX.namedroppers" "INBOX.nnimap" "INBOX.ntbugtraq")
107 ;;
108 ;; (imap-mailbox-select "INBOX.nnimap")
109 ;; => "INBOX.nnimap"
110 ;;
111 ;; (imap-mailbox-get 'exists)
112 ;; => 166
113 ;;
114 ;; (imap-mailbox-get 'uidvalidity)
115 ;; => "908992622"
116 ;;
117 ;; (imap-search "FLAGGED SINCE 18-DEC-98")
118 ;; => (235 236)
119 ;;
120 ;; (imap-fetch 235 "RFC822.PEEK" 'RFC822)
121 ;; => "X-Sieve: cmu-sieve 1.3^M\nX-Username: <jas@pdc.kth.se>^M\r...."
122 ;;
123 ;; Todo:
124 ;;
125 ;; o Parse UIDs as strings? We need to overcome the 28 bit limit somehow.
126 ;; o Don't use `read' at all (important places already fixed)
127 ;; o Accept list of articles instead of message set string in most
128 ;;   imap-message-* functions.
129 ;; o Send strings as literal if they contain, e.g., ".
130 ;;
131 ;; Revision history:
132 ;;
133 ;;  - 19991218 added starttls/digest-md5 patch,
134 ;;             by Daiki Ueno <ueno@ueda.info.waseda.ac.jp>
135 ;;             NB! you need SLIM for starttls.el and digest-md5.el
136 ;;  - 19991023 commited to pgnus
137 ;;
138
139 ;;; Code:
140
141 (eval-when-compile (require 'cl))
142 (eval-and-compile
143   (autoload 'starttls-open-stream "starttls")
144   (autoload 'starttls-negotiate "starttls")
145   (autoload 'sasl-find-mechanism "sasl")
146   (autoload 'digest-md5-parse-digest-challenge "digest-md5")
147   (autoload 'digest-md5-digest-response "digest-md5")
148   (autoload 'digest-md5-digest-uri "digest-md5")
149   (autoload 'digest-md5-challenge "digest-md5")
150   (autoload 'rfc2104-hash "rfc2104")
151   (autoload 'utf7-encode "utf7")
152   (autoload 'utf7-decode "utf7")
153   (autoload 'format-spec "format-spec")
154   (autoload 'format-spec-make "format-spec")
155   (autoload 'open-tls-stream "tls"))
156
157 ;; User variables.
158
159 (defgroup imap nil
160   "Low-level IMAP issues."
161   :version "21.1"
162   :group 'mail)
163
164 (defcustom imap-kerberos4-program '("imtest -m kerberos_v4 -u %l -p %p %s"
165                                     "imtest -kp %s %p")
166   "List of strings containing commands for Kerberos 4 authentication.
167 %s is replaced with server hostname, %p with port to connect to, and
168 %l with the value of `imap-default-user'.  The program should accept
169 IMAP commands on stdin and return responses to stdout.  Each entry in
170 the list is tried until a successful connection is made."
171   :group 'imap
172   :type '(repeat string))
173
174 (defcustom imap-gssapi-program (list
175                                 (concat "gsasl %s %p "
176                                         "--mechanism GSSAPI "
177                                         "--authentication-id %l")
178                                 "imtest -m gssapi -u %l -p %p %s")
179   "List of strings containing commands for GSSAPI (krb5) authentication.
180 %s is replaced with server hostname, %p with port to connect to, and
181 %l with the value of `imap-default-user'.  The program should accept
182 IMAP commands on stdin and return responses to stdout.  Each entry in
183 the list is tried until a successful connection is made."
184   :group 'imap
185   :type '(repeat string))
186
187 (defcustom imap-ssl-program '("openssl s_client -quiet -ssl3 -connect %s:%p"
188                               "openssl s_client -quiet -ssl2 -connect %s:%p"
189                               "s_client -quiet -ssl3 -connect %s:%p"
190                               "s_client -quiet -ssl2 -connect %s:%p")
191   "A string, or list of strings, containing commands for SSL connections.
192 Within a string, %s is replaced with the server address and %p with
193 port number on server.  The program should accept IMAP commands on
194 stdin and return responses to stdout.  Each entry in the list is tried
195 until a successful connection is made."
196   :group 'imap
197   :type '(choice string
198                  (repeat string)))
199
200 (defcustom imap-shell-program '("ssh %s imapd"
201                                 "rsh %s imapd"
202                                 "ssh %g ssh %s imapd"
203                                 "rsh %g rsh %s imapd")
204   "A list of strings, containing commands for IMAP connection.
205 Within a string, %s is replaced with the server address, %p with port
206 number on server, %g with `imap-shell-host', and %l with
207 `imap-default-user'.  The program should read IMAP commands from stdin
208 and write IMAP response to stdout. Each entry in the list is tried
209 until a successful connection is made."
210   :group 'imap
211   :type '(repeat string))
212
213 (defcustom imap-process-connection-type nil
214   "*Value for `process-connection-type' to use for Kerberos4, GSSAPI and SSL.
215 The `process-connection-type' variable control type of device
216 used to communicate with subprocesses.  Values are nil to use a
217 pipe, or t or `pty' to use a pty.  The value has no effect if the
218 system has no ptys or if all ptys are busy: then a pipe is used
219 in any case.  The value takes effect when a IMAP server is
220 opened, changing it after that has no effect."
221   :version "22.1"
222   :group 'imap
223   :type 'boolean)
224
225 (defcustom imap-use-utf7 t
226   "If non-nil, do utf7 encoding/decoding of mailbox names.
227 Since the UTF7 decoding currently only decodes into ISO-8859-1
228 characters, you may disable this decoding if you need to access UTF7
229 encoded mailboxes which doesn't translate into ISO-8859-1."
230   :group 'imap
231   :type 'boolean)
232
233 (defcustom imap-log nil
234   "If non-nil, a imap session trace is placed in *imap-log* buffer.
235 Note that username, passwords and other privacy sensitive
236 information (such as e-mail) may be stored in the *imap-log*
237 buffer.  It is not written to disk, however.  Do not enable this
238 variable unless you are comfortable with that."
239   :group 'imap
240   :type 'boolean)
241
242 (defcustom imap-debug nil
243   "If non-nil, random debug spews are placed in *imap-debug* buffer.
244 Note that username, passwords and other privacy sensitive
245 information (such as e-mail) may be stored in the *imap-debug*
246 buffer.  It is not written to disk, however.  Do not enable this
247 variable unless you are comfortable with that."
248   :group 'imap
249   :type 'boolean)
250
251 (defcustom imap-shell-host "gateway"
252   "Hostname of rlogin proxy."
253   :group 'imap
254   :type 'string)
255
256 (defcustom imap-default-user (user-login-name)
257   "Default username to use."
258   :group 'imap
259   :type 'string)
260
261 (defcustom imap-read-timeout (if (string-match
262                                   "windows-nt\\|os/2\\|emx\\|cygwin"
263                                   (symbol-name system-type))
264                                  1.0
265                                0.1)
266   "*How long to wait between checking for the end of output.
267 Shorter values mean quicker response, but is more CPU intensive."
268   :type 'number
269   :group 'imap)
270
271 (defcustom imap-store-password nil
272   "If non-nil, store session password without promting."
273   :group 'imap
274   :type 'boolean)
275
276 ;; Various variables.
277
278 (defvar imap-fetch-data-hook nil
279   "Hooks called after receiving each FETCH response.")
280
281 (defvar imap-streams '(gssapi kerberos4 starttls tls ssl network shell)
282   "Priority of streams to consider when opening connection to server.")
283
284 (defvar imap-stream-alist
285   '((gssapi    imap-gssapi-stream-p    imap-gssapi-open)
286     (kerberos4 imap-kerberos4-stream-p imap-kerberos4-open)
287     (tls       imap-tls-p              imap-tls-open)
288     (ssl       imap-ssl-p              imap-ssl-open)
289     (network   imap-network-p          imap-network-open)
290     (shell     imap-shell-p            imap-shell-open)
291     (starttls  imap-starttls-p         imap-starttls-open))
292   "Definition of network streams.
293
294 \(NAME CHECK OPEN)
295
296 NAME names the stream, CHECK is a function returning non-nil if the
297 server support the stream and OPEN is a function for opening the
298 stream.")
299
300 (defvar imap-authenticators '(gssapi
301                               kerberos4
302                               digest-md5
303                               cram-md5
304                               ;;sasl
305                               login
306                               anonymous)
307   "Priority of authenticators to consider when authenticating to server.")
308
309 (defvar imap-authenticator-alist
310   '((gssapi     imap-gssapi-auth-p    imap-gssapi-auth)
311     (kerberos4  imap-kerberos4-auth-p imap-kerberos4-auth)
312     (sasl       imap-sasl-auth-p      imap-sasl-auth)
313     (cram-md5   imap-cram-md5-p       imap-cram-md5-auth)
314     (login      imap-login-p          imap-login-auth)
315     (anonymous  imap-anonymous-p      imap-anonymous-auth)
316     (digest-md5 imap-digest-md5-p     imap-digest-md5-auth))
317   "Definition of authenticators.
318
319 \(NAME CHECK AUTHENTICATE)
320
321 NAME names the authenticator.  CHECK is a function returning non-nil if
322 the server support the authenticator and AUTHENTICATE is a function
323 for doing the actual authentication.")
324
325 (defvar imap-error nil
326   "Error codes from the last command.")
327
328 ;; Internal constants.  Change these and die.
329
330 (defconst imap-default-port 143)
331 (defconst imap-default-ssl-port 993)
332 (defconst imap-default-tls-port 993)
333 (defconst imap-default-stream 'network)
334 (defconst imap-coding-system-for-read 'binary)
335 (defconst imap-coding-system-for-write 'binary)
336 (defconst imap-local-variables '(imap-server
337                                  imap-port
338                                  imap-client-eol
339                                  imap-server-eol
340                                  imap-auth
341                                  imap-stream
342                                  imap-username
343                                  imap-password
344                                  imap-current-mailbox
345                                  imap-current-target-mailbox
346                                  imap-message-data
347                                  imap-capability
348                                  imap-id
349                                  imap-namespace
350                                  imap-state
351                                  imap-reached-tag
352                                  imap-failed-tags
353                                  imap-tag
354                                  imap-process
355                                  imap-calculate-literal-size-first
356                                  imap-mailbox-data))
357 (defconst imap-log-buffer "*imap-log*")
358 (defconst imap-debug-buffer "*imap-debug*")
359
360 ;; Internal variables.
361
362 (defvar imap-stream nil)
363 (defvar imap-auth nil)
364 (defvar imap-server nil)
365 (defvar imap-port nil)
366 (defvar imap-username nil)
367 (defvar imap-password nil)
368 (defvar imap-calculate-literal-size-first nil)
369 (defvar imap-state 'closed
370   "IMAP state.
371 Valid states are `closed', `initial', `nonauth', `auth', `selected'
372 and `examine'.")
373
374 (defvar imap-server-eol "\r\n"
375   "The EOL string sent from the server.")
376
377 (defvar imap-client-eol "\r\n"
378   "The EOL string we send to the server.")
379
380 (defvar imap-current-mailbox nil
381   "Current mailbox name.")
382
383 (defvar imap-current-target-mailbox nil
384   "Current target mailbox for COPY and APPEND commands.")
385
386 (defvar imap-mailbox-data nil
387   "Obarray with mailbox data.")
388
389 (defvar imap-mailbox-prime 997
390   "Length of imap-mailbox-data.")
391
392 (defvar imap-current-message nil
393   "Current message number.")
394
395 (defvar imap-message-data nil
396   "Obarray with message data.")
397
398 (defvar imap-message-prime 997
399   "Length of imap-message-data.")
400
401 (defvar imap-capability nil
402   "Capability for server.")
403
404 (defvar imap-id nil
405   "Identity of server.
406 See RFC 2971.")
407
408 (defvar imap-namespace nil
409   "Namespace for current server.")
410
411 (defvar imap-reached-tag 0
412   "Lower limit on command tags that have been parsed.")
413
414 (defvar imap-failed-tags nil
415   "Alist of tags that failed.
416 Each element is a list with four elements; tag (a integer), response
417 state (a symbol, `OK', `NO' or `BAD'), response code (a string), and
418 human readable response text (a string).")
419
420 (defvar imap-tag 0
421   "Command tag number.")
422
423 (defvar imap-process nil
424   "Process.")
425
426 (defvar imap-continuation nil
427   "Non-nil indicates that the server emitted a continuation request.
428 The actual value is really the text on the continuation line.")
429
430 (defvar imap-callbacks nil
431   "List of response tags and callbacks, on the form `(number . function)'.
432 The function should take two arguments, the first the IMAP tag and the
433 second the status (OK, NO, BAD etc) of the command.")
434
435 \f
436 ;; Utility functions:
437
438 (defun imap-remassoc (key alist)
439   "Delete by side effect any elements of LIST whose car is `equal' to KEY.
440 The modified LIST is returned.  If the first member
441 of LIST has a car that is `equal' to KEY, there is no way to remove it
442 by side effect; therefore, write `(setq foo (remassoc key foo))' to be
443 sure of changing the value of `foo'."
444   (when alist
445     (if (equal key (caar alist))
446         (cdr alist)
447       (setcdr alist (imap-remassoc key (cdr alist)))
448       alist)))
449
450 (defsubst imap-disable-multibyte ()
451   "Enable multibyte in the current buffer."
452   (when (fboundp 'set-buffer-multibyte)
453     (set-buffer-multibyte nil)))
454
455 (defsubst imap-utf7-encode (string)
456   (if imap-use-utf7
457       (and string
458            (condition-case ()
459                (utf7-encode string t)
460              (error (message
461                      "imap: Could not UTF7 encode `%s', using it unencoded..."
462                      string)
463                     string)))
464     string))
465
466 (defsubst imap-utf7-decode (string)
467   (if imap-use-utf7
468       (and string
469            (condition-case ()
470                (utf7-decode string t)
471              (error (message
472                      "imap: Could not UTF7 decode `%s', using it undecoded..."
473                      string)
474                     string)))
475     string))
476
477 (defsubst imap-ok-p (status)
478   (if (eq status 'OK)
479       t
480     (setq imap-error status)
481     nil))
482
483 (defun imap-error-text (&optional buffer)
484   (with-current-buffer (or buffer (current-buffer))
485     (nth 3 (car imap-failed-tags))))
486
487 \f
488 ;; Server functions; stream stuff:
489
490 (defun imap-kerberos4-stream-p (buffer)
491   (imap-capability 'AUTH=KERBEROS_V4 buffer))
492
493 (defun imap-kerberos4-open (name buffer server port)
494   (let ((cmds imap-kerberos4-program)
495         cmd done)
496     (while (and (not done) (setq cmd (pop cmds)))
497       (message "Opening Kerberos 4 IMAP connection with `%s'..." cmd)
498       (erase-buffer)
499       (let* ((port (or port imap-default-port))
500              (coding-system-for-read imap-coding-system-for-read)
501              (coding-system-for-write imap-coding-system-for-write)
502              (process-connection-type imap-process-connection-type)
503              (process (start-process
504                        name buffer shell-file-name shell-command-switch
505                        (format-spec
506                         cmd
507                         (format-spec-make
508                          ?s server
509                          ?p (number-to-string port)
510                          ?l imap-default-user))))
511              response)
512         (when process
513           (with-current-buffer buffer
514             (setq imap-client-eol "\n"
515                   imap-calculate-literal-size-first t)
516             (while (and (memq (process-status process) '(open run))
517                         (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
518                         (goto-char (point-min))
519                         ;; Athena IMTEST can output SSL verify errors
520                         (or (while (looking-at "^verify error:num=")
521                               (forward-line))
522                             t)
523                         (or (while (looking-at "^TLS connection established")
524                               (forward-line))
525                             t)
526                         ;; cyrus 1.6.x (13? < x <= 22) queries capabilities
527                         (or (while (looking-at "^C:")
528                               (forward-line))
529                             t)
530                         ;; cyrus 1.6 imtest print "S: " before server greeting
531                         (or (not (looking-at "S: "))
532                             (forward-char 3)
533                             t)
534                         (not (and (imap-parse-greeting)
535                                   ;; success in imtest < 1.6:
536                                   (or (re-search-forward
537                                        "^__\\(.*\\)__\n" nil t)
538                                       ;; success in imtest 1.6:
539                                       (re-search-forward
540                                        "^\\(Authenticat.*\\)" nil t))
541                                   (setq response (match-string 1)))))
542               (accept-process-output process 1)
543               (sit-for 1))
544             (and imap-log
545                  (with-current-buffer (get-buffer-create imap-log-buffer)
546                    (imap-disable-multibyte)
547                    (buffer-disable-undo)
548                    (goto-char (point-max))
549                    (insert-buffer-substring buffer)))
550             (erase-buffer)
551             (message "Opening Kerberos 4 IMAP connection with `%s'...%s" cmd
552                      (if response (concat "done, " response) "failed"))
553             (if (and response (let ((case-fold-search nil))
554                                 (not (string-match "failed" response))))
555                 (setq done process)
556               (if (memq (process-status process) '(open run))
557                   (imap-send-command "LOGOUT"))
558               (delete-process process)
559               nil)))))
560     done))
561
562 (defun imap-gssapi-stream-p (buffer)
563   (imap-capability 'AUTH=GSSAPI buffer))
564
565 (defun imap-gssapi-open (name buffer server port)
566   (let ((cmds imap-gssapi-program)
567         cmd done)
568     (while (and (not done) (setq cmd (pop cmds)))
569       (message "Opening GSSAPI IMAP connection with `%s'..." cmd)
570       (erase-buffer)
571       (let* ((port (or port imap-default-port))
572              (coding-system-for-read imap-coding-system-for-read)
573              (coding-system-for-write imap-coding-system-for-write)
574              (process-connection-type imap-process-connection-type)
575              (process (start-process
576                        name buffer shell-file-name shell-command-switch
577                        (format-spec
578                         cmd
579                         (format-spec-make
580                          ?s server
581                          ?p (number-to-string port)
582                          ?l imap-default-user))))
583              response)
584         (when process
585           (with-current-buffer buffer
586             (setq imap-client-eol "\n"
587                   imap-calculate-literal-size-first t)
588             (while (and (memq (process-status process) '(open run))
589                         (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
590                         (goto-char (point-min))
591                         ;; Athena IMTEST can output SSL verify errors
592                         (or (while (looking-at "^verify error:num=")
593                               (forward-line))
594                             t)
595                         (or (while (looking-at "^TLS connection established")
596                               (forward-line))
597                             t)
598                         ;; cyrus 1.6.x (13? < x <= 22) queries capabilities
599                         (or (while (looking-at "^C:")
600                               (forward-line))
601                             t)
602                         ;; cyrus 1.6 imtest print "S: " before server greeting
603                         (or (not (looking-at "S: "))
604                             (forward-char 3)
605                             t)
606                         ;; GNU SASL may print 'Trying ...' first.
607                         (or (not (looking-at "Trying "))
608                             (forward-line)
609                             t)
610                         (not (and (imap-parse-greeting)
611                                   ;; success in imtest 1.6:
612                                   (re-search-forward
613                                    (concat "^\\(\\(Authenticat.*\\)\\|\\("
614                                            "Client authentication "
615                                            "finished.*\\)\\)")
616                                    nil t)
617                                   (setq response (match-string 1)))))
618               (accept-process-output process 1)
619               (sit-for 1))
620             (and imap-log
621                  (with-current-buffer (get-buffer-create imap-log-buffer)
622                    (imap-disable-multibyte)
623                    (buffer-disable-undo)
624                    (goto-char (point-max))
625                    (insert-buffer-substring buffer)))
626             (erase-buffer)
627             (message "GSSAPI IMAP connection: %s" (or response "failed"))
628             (if (and response (let ((case-fold-search nil))
629                                 (not (string-match "failed" response))))
630                 (setq done process)
631               (if (memq (process-status process) '(open run))
632                   (imap-send-command "LOGOUT"))
633               (delete-process process)
634               nil)))))
635     done))
636
637 (defun imap-ssl-p (buffer)
638   nil)
639
640 (defun imap-ssl-open (name buffer server port)
641   "Open a SSL connection to server."
642   (let ((cmds (if (listp imap-ssl-program) imap-ssl-program
643                 (list imap-ssl-program)))
644         cmd done)
645     (while (and (not done) (setq cmd (pop cmds)))
646       (message "imap: Opening SSL connection with `%s'..." cmd)
647       (erase-buffer)
648       (let* ((port (or port imap-default-ssl-port))
649              (coding-system-for-read imap-coding-system-for-read)
650              (coding-system-for-write imap-coding-system-for-write)
651              (process-connection-type imap-process-connection-type)
652              (set-process-query-on-exit-flag
653               (if (fboundp 'set-process-query-on-exit-flag)
654                   'set-process-query-on-exit-flag
655                 'process-kill-without-query))
656              process)
657         (when (progn
658                 (setq process (start-process
659                                name buffer shell-file-name
660                                shell-command-switch
661                                (format-spec cmd
662                                             (format-spec-make
663                                              ?s server
664                                              ?p (number-to-string port)))))
665                 (funcall set-process-query-on-exit-flag process nil)
666                 process)
667           (with-current-buffer buffer
668             (goto-char (point-min))
669             (while (and (memq (process-status process) '(open run))
670                         (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
671                         (goto-char (point-max))
672                         (forward-line -1)
673                         (not (imap-parse-greeting)))
674               (accept-process-output process 1)
675               (sit-for 1))
676             (and imap-log
677                  (with-current-buffer (get-buffer-create imap-log-buffer)
678                    (imap-disable-multibyte)
679                    (buffer-disable-undo)
680                    (goto-char (point-max))
681                    (insert-buffer-substring buffer)))
682             (erase-buffer)
683             (when (memq (process-status process) '(open run))
684               (setq done process))))))
685     (if done
686         (progn
687           (message "imap: Opening SSL connection with `%s'...done" cmd)
688           done)
689       (message "imap: Opening SSL connection with `%s'...failed" cmd)
690       nil)))
691
692 (defun imap-tls-p (buffer)
693   nil)
694
695 (defun imap-tls-open (name buffer server port)
696   (let* ((port (or port imap-default-tls-port))
697          (coding-system-for-read imap-coding-system-for-read)
698          (coding-system-for-write imap-coding-system-for-write)
699          (process (open-tls-stream name buffer server port)))
700     (when process
701       (while (and (memq (process-status process) '(open run))
702                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
703                   (goto-char (point-max))
704                   (forward-line -1)
705                   (not (imap-parse-greeting)))
706         (accept-process-output process 1)
707         (sit-for 1))
708       (and imap-log
709            (with-current-buffer (get-buffer-create imap-log-buffer)
710              (imap-disable-multibyte)
711              (buffer-disable-undo)
712              (goto-char (point-max))
713              (insert-buffer-substring buffer)))
714       (when (memq (process-status process) '(open run))
715         process))))
716
717 (defun imap-network-p (buffer)
718   t)
719
720 (defun imap-network-open (name buffer server port)
721   (let* ((port (or port imap-default-port))
722          (coding-system-for-read imap-coding-system-for-read)
723          (coding-system-for-write imap-coding-system-for-write)
724          (process (open-network-stream name buffer server port)))
725     (when process
726       (while (and (memq (process-status process) '(open run))
727                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
728                   (goto-char (point-min))
729                   (not (imap-parse-greeting)))
730         (accept-process-output process 1)
731         (sit-for 1))
732       (and imap-log
733            (with-current-buffer (get-buffer-create imap-log-buffer)
734              (imap-disable-multibyte)
735              (buffer-disable-undo)
736              (goto-char (point-max))
737              (insert-buffer-substring buffer)))
738       (when (memq (process-status process) '(open run))
739         process))))
740
741 (defun imap-shell-p (buffer)
742   nil)
743
744 (defun imap-shell-open (name buffer server port)
745   (let ((cmds (if (listp imap-shell-program) imap-shell-program
746                 (list imap-shell-program)))
747         cmd done)
748     (while (and (not done) (setq cmd (pop cmds)))
749       (message "imap: Opening IMAP connection with `%s'..." cmd)
750       (setq imap-client-eol "\n")
751       (let* ((port (or port imap-default-port))
752              (coding-system-for-read imap-coding-system-for-read)
753              (coding-system-for-write imap-coding-system-for-write)
754              (process (start-process
755                        name buffer shell-file-name shell-command-switch
756                        (format-spec
757                         cmd
758                         (format-spec-make
759                          ?s server
760                          ?g imap-shell-host
761                          ?p (number-to-string port)
762                          ?l imap-default-user)))))
763         (when process
764           (while (and (memq (process-status process) '(open run))
765                       (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
766                       (goto-char (point-max))
767                       (forward-line -1)
768                       (not (imap-parse-greeting)))
769             (accept-process-output process 1)
770             (sit-for 1))
771           (and imap-log
772                (with-current-buffer (get-buffer-create imap-log-buffer)
773                  (imap-disable-multibyte)
774                  (buffer-disable-undo)
775                  (goto-char (point-max))
776                  (insert-buffer-substring buffer)))
777           (erase-buffer)
778           (when (memq (process-status process) '(open run))
779             (setq done process)))))
780     (if done
781         (progn
782           (message "imap: Opening IMAP connection with `%s'...done" cmd)
783           done)
784       (message "imap: Opening IMAP connection with `%s'...failed" cmd)
785       nil)))
786
787 (defun imap-starttls-p (buffer)
788   (imap-capability 'STARTTLS buffer))
789
790 (defun imap-starttls-open (name buffer server port)
791   (let* ((port (or port imap-default-port))
792          (coding-system-for-read imap-coding-system-for-read)
793          (coding-system-for-write imap-coding-system-for-write)
794          (process (starttls-open-stream name buffer server port))
795          done tls-info)
796     (message "imap: Connecting with STARTTLS...")
797     (when process
798       (while (and (memq (process-status process) '(open run))
799                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
800                   (goto-char (point-max))
801                   (forward-line -1)
802                   (not (imap-parse-greeting)))
803         (accept-process-output process 1)
804         (sit-for 1))
805       (imap-send-command "STARTTLS")
806       (while (and (memq (process-status process) '(open run))
807                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
808                   (goto-char (point-max))
809                   (forward-line -1)
810                   (not (re-search-forward "[0-9]+ OK.*\r?\n" nil t)))
811         (accept-process-output process 1)
812         (sit-for 1))
813       (and imap-log
814            (with-current-buffer (get-buffer-create imap-log-buffer)
815              (buffer-disable-undo)
816              (goto-char (point-max))
817              (insert-buffer-substring buffer)))
818       (when (and (setq tls-info (starttls-negotiate process))
819                  (memq (process-status process) '(open run)))
820         (setq done process)))
821     (if (stringp tls-info)
822         (message "imap: STARTTLS info: %s" tls-info))
823     (message "imap: Connecting with STARTTLS...%s" (if done "done" "failed"))
824     done))
825
826 ;; Server functions; authenticator stuff:
827
828 (defun imap-interactive-login (buffer loginfunc)
829   "Login to server in BUFFER.
830 LOGINFUNC is passed a username and a password, it should return t if
831 it where successful authenticating itself to the server, nil otherwise.
832 Returns t if login was successful, nil otherwise."
833   (with-current-buffer buffer
834     (make-local-variable 'imap-username)
835     (make-local-variable 'imap-password)
836     (let (user passwd ret)
837       ;;      (condition-case ()
838       (while (or (not user) (not passwd))
839         (setq user (or imap-username
840                        (read-from-minibuffer
841                         (concat "IMAP username for " imap-server
842                                 " (using stream `" (symbol-name imap-stream)
843                                 "'): ")
844                         (or user imap-default-user))))
845         (setq passwd (or imap-password
846                          (read-passwd
847                           (concat "IMAP password for " user "@"
848                                   imap-server " (using authenticator `"
849                                   (symbol-name imap-auth) "'): "))))
850         (when (and user passwd)
851           (if (funcall loginfunc user passwd)
852               (progn
853                 (setq ret t
854                       imap-username user)
855                 (when (and (not imap-password)
856                            (or imap-store-password
857                                (y-or-n-p "Store password for this session? ")))
858                   (setq imap-password passwd)))
859             (message "Login failed...")
860             (setq passwd nil)
861             (setq imap-password nil)
862             (sit-for 1))))
863       ;;        (quit (with-current-buffer buffer
864       ;;                (setq user nil
865       ;;                      passwd nil)))
866       ;;        (error (with-current-buffer buffer
867       ;;                 (setq user nil
868       ;;                       passwd nil))))
869       ret)))
870
871 (defun imap-gssapi-auth-p (buffer)
872   (eq imap-stream 'gssapi))
873
874 (defun imap-gssapi-auth (buffer)
875   (message "imap: Authenticating using GSSAPI...%s"
876            (if (eq imap-stream 'gssapi) "done" "failed"))
877   (eq imap-stream 'gssapi))
878
879 (defun imap-kerberos4-auth-p (buffer)
880   (and (imap-capability 'AUTH=KERBEROS_V4 buffer)
881        (eq imap-stream 'kerberos4)))
882
883 (defun imap-kerberos4-auth (buffer)
884   (message "imap: Authenticating using Kerberos 4...%s"
885            (if (eq imap-stream 'kerberos4) "done" "failed"))
886   (eq imap-stream 'kerberos4))
887
888 (defun imap-cram-md5-p (buffer)
889   (imap-capability 'AUTH=CRAM-MD5 buffer))
890
891 (defun imap-cram-md5-auth (buffer)
892   "Login to server using the AUTH CRAM-MD5 method."
893   (message "imap: Authenticating using CRAM-MD5...")
894   (let ((done (imap-interactive-login
895                buffer
896                (lambda (user passwd)
897                  (imap-ok-p
898                   (imap-send-command-wait
899                    (list
900                     "AUTHENTICATE CRAM-MD5"
901                     (lambda (challenge)
902                       (let* ((decoded (base64-decode-string challenge))
903                              (hash (rfc2104-hash 'md5 64 16 passwd decoded))
904                              (response (concat user " " hash))
905                              (encoded (base64-encode-string response)))
906                         encoded)))))))))
907     (if done
908         (message "imap: Authenticating using CRAM-MD5...done")
909       (message "imap: Authenticating using CRAM-MD5...failed"))))
910
911 (defun imap-login-p (buffer)
912   (and (not (imap-capability 'LOGINDISABLED buffer))
913        (not (imap-capability 'X-LOGIN-CMD-DISABLED buffer))))
914
915 (defun imap-quote-specials (string)
916   (with-temp-buffer
917     (insert string)
918     (goto-char (point-min))
919     (while (re-search-forward "[\\\"]" nil t)
920       (forward-char -1)
921       (insert "\\")
922       (forward-char 1))
923     (buffer-string)))
924
925 (defun imap-login-auth (buffer)
926   "Login to server using the LOGIN command."
927   (message "imap: Plaintext authentication...")
928   (imap-interactive-login buffer
929                           (lambda (user passwd)
930                             (imap-ok-p (imap-send-command-wait
931                                         (concat "LOGIN \""
932                                                 (imap-quote-specials user)
933                                                 "\" \""
934                                                 (imap-quote-specials passwd)
935                                                 "\""))))))
936
937 (defun imap-anonymous-p (buffer)
938   t)
939
940 (defun imap-anonymous-auth (buffer)
941   (message "imap: Logging in anonymously...")
942   (with-current-buffer buffer
943     (imap-ok-p (imap-send-command-wait
944                 (concat "LOGIN anonymous \"" (concat (user-login-name) "@"
945                                                      (system-name)) "\"")))))
946
947 ;;; Compiler directives.
948
949 (defvar imap-sasl-client)
950 (defvar imap-sasl-step)
951
952 (defun imap-sasl-make-mechanisms (buffer)
953   (let ((mecs '()))
954     (mapc (lambda (sym)
955             (let ((name (symbol-name sym)))
956               (if (and (> (length name) 5)
957                        (string-equal "AUTH=" (substring name 0 5 )))
958                   (setq mecs (cons (substring name 5) mecs)))))
959           (imap-capability nil buffer))
960     mecs))
961
962 (defun imap-sasl-auth-p (buffer)
963   (and (condition-case ()
964            (require 'sasl)
965          (error nil))
966        (sasl-find-mechanism (imap-sasl-make-mechanisms buffer))))
967
968 (defun imap-sasl-auth (buffer)
969   "Login to server using the SASL method."
970   (message "imap: Authenticating using SASL...")
971   (with-current-buffer buffer
972     (make-local-variable 'imap-username)
973     (make-local-variable 'imap-sasl-client)
974     (make-local-variable 'imap-sasl-step)
975     (let ((mechanism (sasl-find-mechanism (imap-sasl-make-mechanisms buffer)))
976           logged user)
977       (while (not logged)
978         (setq user (or imap-username
979                        (read-from-minibuffer
980                         (concat "IMAP username for " imap-server " using SASL "
981                                 (sasl-mechanism-name mechanism) ": ")
982                         (or user imap-default-user))))
983         (when user
984           (setq imap-sasl-client (sasl-make-client mechanism user "imap2" imap-server)
985                 imap-sasl-step (sasl-next-step imap-sasl-client nil))
986           (let ((tag (imap-send-command
987                       (if (sasl-step-data imap-sasl-step)
988                           (format "AUTHENTICATE %s %s"
989                                   (sasl-mechanism-name mechanism)
990                                   (sasl-step-data imap-sasl-step))
991                         (format "AUTHENTICATE %s" (sasl-mechanism-name mechanism)))
992                       buffer)))
993             (while (eq (imap-wait-for-tag tag) 'INCOMPLETE)
994               (sasl-step-set-data imap-sasl-step (base64-decode-string imap-continuation))
995               (setq imap-continuation nil
996                     imap-sasl-step (sasl-next-step imap-sasl-client imap-sasl-step))
997               (imap-send-command-1 (if (sasl-step-data imap-sasl-step)
998                                        (base64-encode-string (sasl-step-data imap-sasl-step) t)
999                                      "")))
1000             (if (imap-ok-p (imap-wait-for-tag tag))
1001                 (setq imap-username user
1002                       logged t)
1003               (message "Login failed...")
1004               (sit-for 1)))))
1005       logged)))
1006
1007 (defun imap-digest-md5-p (buffer)
1008   (and (imap-capability 'AUTH=DIGEST-MD5 buffer)
1009        (condition-case ()
1010            (require 'digest-md5)
1011          (error nil))))
1012
1013 (defun imap-digest-md5-auth (buffer)
1014   "Login to server using the AUTH DIGEST-MD5 method."
1015   (message "imap: Authenticating using DIGEST-MD5...")
1016   (imap-interactive-login
1017    buffer
1018    (lambda (user passwd)
1019      (let ((tag
1020             (imap-send-command
1021              (list
1022               "AUTHENTICATE DIGEST-MD5"
1023               (lambda (challenge)
1024                 (digest-md5-parse-digest-challenge
1025                  (base64-decode-string challenge))
1026                 (let* ((digest-uri
1027                         (digest-md5-digest-uri
1028                          "imap" (digest-md5-challenge 'realm)))
1029                        (response
1030                         (digest-md5-digest-response
1031                          user passwd digest-uri)))
1032                   (base64-encode-string response 'no-line-break))))
1033              )))
1034        (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE))
1035            nil
1036          (setq imap-continuation nil)
1037          (imap-send-command-1 "")
1038          (imap-ok-p (imap-wait-for-tag tag)))))))
1039
1040 ;; Server functions:
1041
1042 (defun imap-open-1 (buffer)
1043   (with-current-buffer buffer
1044     (erase-buffer)
1045     (setq imap-current-mailbox nil
1046           imap-current-message nil
1047           imap-state 'initial
1048           imap-process (condition-case ()
1049                            (funcall (nth 2 (assq imap-stream
1050                                                  imap-stream-alist))
1051                                     "imap" buffer imap-server imap-port)
1052                          ((error quit) nil)))
1053     (when imap-process
1054       (set-process-filter imap-process 'imap-arrival-filter)
1055       (set-process-sentinel imap-process 'imap-sentinel)
1056       (while (and (eq imap-state 'initial)
1057                   (memq (process-status imap-process) '(open run)))
1058         (message "Waiting for response from %s..." imap-server)
1059         (accept-process-output imap-process 1))
1060       (message "Waiting for response from %s...done" imap-server)
1061       (and (memq (process-status imap-process) '(open run))
1062            imap-process))))
1063
1064 (defun imap-open (server &optional port stream auth buffer)
1065   "Open a IMAP connection to host SERVER at PORT returning a buffer.
1066 If PORT is unspecified, a default value is used (143 except
1067 for SSL which use 993).
1068 STREAM indicates the stream to use, see `imap-streams' for available
1069 streams.  If nil, it choices the best stream the server is capable of.
1070 AUTH indicates authenticator to use, see `imap-authenticators' for
1071 available authenticators.  If nil, it choices the best stream the
1072 server is capable of.
1073 BUFFER can be a buffer or a name of a buffer, which is created if
1074 necessary.  If nil, the buffer name is generated."
1075   (setq buffer (or buffer (format " *imap* %s:%d" server (or port 0))))
1076   (with-current-buffer (get-buffer-create buffer)
1077     (if (imap-opened buffer)
1078         (imap-close buffer))
1079     (mapcar 'make-local-variable imap-local-variables)
1080     (imap-disable-multibyte)
1081     (buffer-disable-undo)
1082     (setq imap-server (or server imap-server))
1083     (setq imap-port (or port imap-port))
1084     (setq imap-auth (or auth imap-auth))
1085     (setq imap-stream (or stream imap-stream))
1086     (message "imap: Connecting to %s..." imap-server)
1087     (if (null (let ((imap-stream (or imap-stream imap-default-stream)))
1088                 (imap-open-1 buffer)))
1089         (progn
1090           (message "imap: Connecting to %s...failed" imap-server)
1091           nil)
1092       (when (null imap-stream)
1093         ;; Need to choose stream.
1094         (let ((streams imap-streams))
1095           (while (setq stream (pop streams))
1096             ;; OK to use this stream?
1097             (when (funcall (nth 1 (assq stream imap-stream-alist)) buffer)
1098               ;; Stream changed?
1099               (if (not (eq imap-default-stream stream))
1100                   (with-current-buffer (get-buffer-create
1101                                         (generate-new-buffer-name " *temp*"))
1102                     (mapcar 'make-local-variable imap-local-variables)
1103                     (imap-disable-multibyte)
1104                     (buffer-disable-undo)
1105                     (setq imap-server (or server imap-server))
1106                     (setq imap-port (or port imap-port))
1107                     (setq imap-auth (or auth imap-auth))
1108                     (message "imap: Reconnecting with stream `%s'..." stream)
1109                     (if (null (let ((imap-stream stream))
1110                                 (imap-open-1 (current-buffer))))
1111                         (progn
1112                           (kill-buffer (current-buffer))
1113                           (message
1114                            "imap: Reconnecting with stream `%s'...failed"
1115                            stream))
1116                       ;; We're done, kill the first connection
1117                       (imap-close buffer)
1118                       (let ((name (if (stringp buffer)
1119                                       buffer
1120                                     (buffer-name buffer))))
1121                         (kill-buffer buffer)
1122                         (rename-buffer name))
1123                       (message "imap: Reconnecting with stream `%s'...done"
1124                                stream)
1125                       (setq imap-stream stream)
1126                       (setq imap-capability nil)
1127                       (setq streams nil)))
1128                 ;; We're done
1129                 (message "imap: Connecting to %s...done" imap-server)
1130                 (setq imap-stream stream)
1131                 (setq imap-capability nil)
1132                 (setq streams nil))))))
1133       (when (imap-opened buffer)
1134         (setq imap-mailbox-data (make-vector imap-mailbox-prime 0)))
1135       (when imap-stream
1136         buffer))))
1137
1138 (defun imap-opened (&optional buffer)
1139   "Return non-nil if connection to imap server in BUFFER is open.
1140 If BUFFER is nil then the current buffer is used."
1141   (and (setq buffer (get-buffer (or buffer (current-buffer))))
1142        (buffer-live-p buffer)
1143        (with-current-buffer buffer
1144          (and imap-process
1145               (memq (process-status imap-process) '(open run))))))
1146
1147 (defun imap-authenticate (&optional user passwd buffer)
1148   "Authenticate to server in BUFFER, using current buffer if nil.
1149 It uses the authenticator specified when opening the server.  If the
1150 authenticator requires username/passwords, they are queried from the
1151 user and optionally stored in the buffer.  If USER and/or PASSWD is
1152 specified, the user will not be questioned and the username and/or
1153 password is remembered in the buffer."
1154   (with-current-buffer (or buffer (current-buffer))
1155     (if (not (eq imap-state 'nonauth))
1156         (or (eq imap-state 'auth)
1157             (eq imap-state 'selected)
1158             (eq imap-state 'examine))
1159       (make-local-variable 'imap-username)
1160       (make-local-variable 'imap-password)
1161       (if user (setq imap-username user))
1162       (if passwd (setq imap-password passwd))
1163       (if imap-auth
1164           (and (funcall (nth 2 (assq imap-auth
1165                                      imap-authenticator-alist)) buffer)
1166                (setq imap-state 'auth))
1167         ;; Choose authenticator.
1168         (let ((auths imap-authenticators)
1169               auth)
1170           (while (setq auth (pop auths))
1171             ;; OK to use authenticator?
1172             (when (funcall (nth 1 (assq auth imap-authenticator-alist)) buffer)
1173               (message "imap: Authenticating to `%s' using `%s'..."
1174                        imap-server auth)
1175               (setq imap-auth auth)
1176               (if (funcall (nth 2 (assq auth imap-authenticator-alist)) buffer)
1177                   (progn
1178                     (message "imap: Authenticating to `%s' using `%s'...done"
1179                              imap-server auth)
1180                     (setq auths nil))
1181                 (message "imap: Authenticating to `%s' using `%s'...failed"
1182                          imap-server auth)))))
1183         imap-state))))
1184
1185 (defun imap-close (&optional buffer)
1186   "Close connection to server in BUFFER.
1187 If BUFFER is nil, the current buffer is used."
1188   (with-current-buffer (or buffer (current-buffer))
1189     (when (imap-opened)
1190       (condition-case nil
1191           (imap-send-command-wait "LOGOUT")
1192         (quit nil)))
1193     (when (and imap-process
1194                (memq (process-status imap-process) '(open run)))
1195       (delete-process imap-process))
1196     (setq imap-current-mailbox nil
1197           imap-current-message nil
1198           imap-process nil)
1199     (erase-buffer)
1200     t))
1201
1202 (defun imap-capability (&optional identifier buffer)
1203   "Return a list of identifiers which server in BUFFER support.
1204 If IDENTIFIER, return non-nil if it's among the servers capabilities.
1205 If BUFFER is nil, the current buffer is assumed."
1206   (with-current-buffer (or buffer (current-buffer))
1207     (unless imap-capability
1208       (unless (imap-ok-p (imap-send-command-wait "CAPABILITY"))
1209         (setq imap-capability '(IMAP2))))
1210     (if identifier
1211         (memq (intern (upcase (symbol-name identifier))) imap-capability)
1212       imap-capability)))
1213
1214 (defun imap-id (&optional list-of-values buffer)
1215   "Identify client to server in BUFFER, and return server identity.
1216 LIST-OF-VALUES is nil, or a plist with identifier and value
1217 strings to send to the server to identify the client.
1218
1219 Return a list of identifiers which server in BUFFER support, or
1220 nil if it doesn't support ID or returns no information.
1221
1222 If BUFFER is nil, the current buffer is assumed."
1223   (with-current-buffer (or buffer (current-buffer))
1224     (when (and (imap-capability 'ID)
1225                (imap-ok-p (imap-send-command-wait
1226                            (if (null list-of-values)
1227                                "ID NIL"
1228                              (concat "ID (" (mapconcat (lambda (el)
1229                                                          (concat "\"" el "\""))
1230                                                        list-of-values
1231                                                        " ") ")")))))
1232       imap-id)))
1233
1234 (defun imap-namespace (&optional buffer)
1235   "Return a namespace hierarchy at server in BUFFER.
1236 If BUFFER is nil, the current buffer is assumed."
1237   (with-current-buffer (or buffer (current-buffer))
1238     (unless imap-namespace
1239       (when (imap-capability 'NAMESPACE)
1240         (imap-send-command-wait "NAMESPACE")))
1241     imap-namespace))
1242
1243 (defun imap-send-command-wait (command &optional buffer)
1244   (imap-wait-for-tag (imap-send-command command buffer) buffer))
1245
1246 \f
1247 ;; Mailbox functions:
1248
1249 (defun imap-mailbox-put (propname value &optional mailbox buffer)
1250   (with-current-buffer (or buffer (current-buffer))
1251     (if imap-mailbox-data
1252         (put (intern (or mailbox imap-current-mailbox) imap-mailbox-data)
1253              propname value)
1254       (error "Imap-mailbox-data is nil, prop %s value %s mailbox %s buffer %s"
1255              propname value mailbox (current-buffer)))
1256     t))
1257
1258 (defsubst imap-mailbox-get-1 (propname &optional mailbox)
1259   (get (intern-soft (or mailbox imap-current-mailbox) imap-mailbox-data)
1260        propname))
1261
1262 (defun imap-mailbox-get (propname &optional mailbox buffer)
1263   (let ((mailbox (imap-utf7-encode mailbox)))
1264     (with-current-buffer (or buffer (current-buffer))
1265       (imap-mailbox-get-1 propname (or mailbox imap-current-mailbox)))))
1266
1267 (defun imap-mailbox-map-1 (func &optional mailbox-decoder buffer)
1268   (with-current-buffer (or buffer (current-buffer))
1269     (let (result)
1270       (mapatoms
1271        (lambda (s)
1272          (push (funcall func (if mailbox-decoder
1273                                  (funcall mailbox-decoder (symbol-name s))
1274                                (symbol-name s))) result))
1275        imap-mailbox-data)
1276       result)))
1277
1278 (defun imap-mailbox-map (func &optional buffer)
1279   "Map a function across each mailbox in `imap-mailbox-data', returning a list.
1280 Function should take a mailbox name (a string) as
1281 the only argument."
1282   (imap-mailbox-map-1 func 'imap-utf7-decode buffer))
1283
1284 (defun imap-current-mailbox (&optional buffer)
1285   (with-current-buffer (or buffer (current-buffer))
1286     (imap-utf7-decode imap-current-mailbox)))
1287
1288 (defun imap-current-mailbox-p-1 (mailbox &optional examine)
1289   (and (string= mailbox imap-current-mailbox)
1290        (or (and examine
1291                 (eq imap-state 'examine))
1292            (and (not examine)
1293                 (eq imap-state 'selected)))))
1294
1295 (defun imap-current-mailbox-p (mailbox &optional examine buffer)
1296   (with-current-buffer (or buffer (current-buffer))
1297     (imap-current-mailbox-p-1 (imap-utf7-encode mailbox) examine)))
1298
1299 (defun imap-mailbox-select-1 (mailbox &optional examine)
1300   "Select MAILBOX on server in BUFFER.
1301 If EXAMINE is non-nil, do a read-only select."
1302   (if (imap-current-mailbox-p-1 mailbox examine)
1303       imap-current-mailbox
1304     (setq imap-current-mailbox mailbox)
1305     (if (imap-ok-p (imap-send-command-wait
1306                     (concat (if examine "EXAMINE" "SELECT") " \""
1307                             mailbox "\"")))
1308         (progn
1309           (setq imap-message-data (make-vector imap-message-prime 0)
1310                 imap-state (if examine 'examine 'selected))
1311           imap-current-mailbox)
1312       ;; Failed SELECT/EXAMINE unselects current mailbox
1313       (setq imap-current-mailbox nil))))
1314
1315 (defun imap-mailbox-select (mailbox &optional examine buffer)
1316   (with-current-buffer (or buffer (current-buffer))
1317     (imap-utf7-decode
1318      (imap-mailbox-select-1 (imap-utf7-encode mailbox) examine))))
1319
1320 (defun imap-mailbox-examine-1 (mailbox &optional buffer)
1321   (with-current-buffer (or buffer (current-buffer))
1322     (imap-mailbox-select-1 mailbox 'examine)))
1323
1324 (defun imap-mailbox-examine (mailbox &optional buffer)
1325   "Examine MAILBOX on server in BUFFER."
1326   (imap-mailbox-select mailbox 'examine buffer))
1327
1328 (defun imap-mailbox-unselect (&optional buffer)
1329   "Close current folder in BUFFER, without expunging articles."
1330   (with-current-buffer (or buffer (current-buffer))
1331     (when (or (eq imap-state 'auth)
1332               (and (imap-capability 'UNSELECT)
1333                    (imap-ok-p (imap-send-command-wait "UNSELECT")))
1334               (and (imap-ok-p
1335                     (imap-send-command-wait (concat "EXAMINE \""
1336                                                     imap-current-mailbox
1337                                                     "\"")))
1338                    (imap-ok-p (imap-send-command-wait "CLOSE"))))
1339       (setq imap-current-mailbox nil
1340             imap-message-data nil
1341             imap-state 'auth)
1342       t)))
1343
1344 (defun imap-mailbox-expunge (&optional asynch buffer)
1345   "Expunge articles in current folder in BUFFER.
1346 If ASYNCH, do not wait for succesful completion of the command.
1347 If BUFFER is nil the current buffer is assumed."
1348   (with-current-buffer (or buffer (current-buffer))
1349     (when (and imap-current-mailbox (not (eq imap-state 'examine)))
1350       (if asynch
1351           (imap-send-command "EXPUNGE")
1352       (imap-ok-p (imap-send-command-wait "EXPUNGE"))))))
1353
1354 (defun imap-mailbox-close (&optional asynch buffer)
1355   "Expunge articles and close current folder in BUFFER.
1356 If ASYNCH, do not wait for succesful completion of the command.
1357 If BUFFER is nil the current buffer is assumed."
1358   (with-current-buffer (or buffer (current-buffer))
1359     (when imap-current-mailbox
1360       (if asynch
1361           (imap-add-callback (imap-send-command "CLOSE")
1362                              `(lambda (tag status)
1363                                 (message "IMAP mailbox `%s' closed... %s"
1364                                          imap-current-mailbox status)
1365                                 (when (eq ,imap-current-mailbox
1366                                           imap-current-mailbox)
1367                                   ;; Don't wipe out data if another mailbox
1368                                   ;; was selected...
1369                                   (setq imap-current-mailbox nil
1370                                         imap-message-data nil
1371                                         imap-state 'auth))))
1372         (when (imap-ok-p (imap-send-command-wait "CLOSE"))
1373           (setq imap-current-mailbox nil
1374                 imap-message-data nil
1375                 imap-state 'auth)))
1376       t)))
1377
1378 (defun imap-mailbox-create-1 (mailbox)
1379   (imap-ok-p (imap-send-command-wait (list "CREATE \"" mailbox "\""))))
1380
1381 (defun imap-mailbox-create (mailbox &optional buffer)
1382   "Create MAILBOX on server in BUFFER.
1383 If BUFFER is nil the current buffer is assumed."
1384   (with-current-buffer (or buffer (current-buffer))
1385     (imap-mailbox-create-1 (imap-utf7-encode mailbox))))
1386
1387 (defun imap-mailbox-delete (mailbox &optional buffer)
1388   "Delete MAILBOX on server in BUFFER.
1389 If BUFFER is nil the current buffer is assumed."
1390   (let ((mailbox (imap-utf7-encode mailbox)))
1391     (with-current-buffer (or buffer (current-buffer))
1392       (imap-ok-p
1393        (imap-send-command-wait (list "DELETE \"" mailbox "\""))))))
1394
1395 (defun imap-mailbox-rename (oldname newname &optional buffer)
1396   "Rename mailbox OLDNAME to NEWNAME on server in BUFFER.
1397 If BUFFER is nil the current buffer is assumed."
1398   (let ((oldname (imap-utf7-encode oldname))
1399         (newname (imap-utf7-encode newname)))
1400     (with-current-buffer (or buffer (current-buffer))
1401       (imap-ok-p
1402        (imap-send-command-wait (list "RENAME \"" oldname "\" "
1403                                      "\"" newname "\""))))))
1404
1405 (defun imap-mailbox-lsub (&optional root reference add-delimiter buffer)
1406   "Return a list of subscribed mailboxes on server in BUFFER.
1407 If ROOT is non-nil, only list matching mailboxes.  If ADD-DELIMITER is
1408 non-nil, a hierarchy delimiter is added to root.  REFERENCE is a
1409 implementation-specific string that has to be passed to lsub command."
1410   (with-current-buffer (or buffer (current-buffer))
1411     ;; Make sure we know the hierarchy separator for root's hierarchy
1412     (when (and add-delimiter (null (imap-mailbox-get-1 'delimiter root)))
1413       (imap-send-command-wait (concat "LIST \"" reference "\" \""
1414                                       (imap-utf7-encode root) "\"")))
1415     ;; clear list data (NB not delimiter and other stuff)
1416     (imap-mailbox-map-1 (lambda (mailbox)
1417                           (imap-mailbox-put 'lsub nil mailbox)))
1418     (when (imap-ok-p
1419            (imap-send-command-wait
1420             (concat "LSUB \"" reference "\" \"" (imap-utf7-encode root)
1421                     (and add-delimiter (imap-mailbox-get-1 'delimiter root))
1422                     "%\"")))
1423       (let (out)
1424         (imap-mailbox-map-1 (lambda (mailbox)
1425                               (when (imap-mailbox-get-1 'lsub mailbox)
1426                                 (push (imap-utf7-decode mailbox) out))))
1427         (nreverse out)))))
1428
1429 (defun imap-mailbox-list (root &optional reference add-delimiter buffer)
1430   "Return a list of mailboxes matching ROOT on server in BUFFER.
1431 If ADD-DELIMITER is non-nil, a hierarchy delimiter is added to
1432 root.  REFERENCE is a implementation-specific string that has to be
1433 passed to list command."
1434   (with-current-buffer (or buffer (current-buffer))
1435     ;; Make sure we know the hierarchy separator for root's hierarchy
1436     (when (and add-delimiter (null (imap-mailbox-get-1 'delimiter root)))
1437       (imap-send-command-wait (concat "LIST \"" reference "\" \""
1438                                       (imap-utf7-encode root) "\"")))
1439     ;; clear list data (NB not delimiter and other stuff)
1440     (imap-mailbox-map-1 (lambda (mailbox)
1441                           (imap-mailbox-put 'list nil mailbox)))
1442     (when (imap-ok-p
1443            (imap-send-command-wait
1444             (concat "LIST \"" reference "\" \"" (imap-utf7-encode root)
1445                     (and add-delimiter (imap-mailbox-get-1 'delimiter root))
1446                     "%\"")))
1447       (let (out)
1448         (imap-mailbox-map-1 (lambda (mailbox)
1449                               (when (imap-mailbox-get-1 'list mailbox)
1450                                 (push (imap-utf7-decode mailbox) out))))
1451         (nreverse out)))))
1452
1453 (defun imap-mailbox-subscribe (mailbox &optional buffer)
1454   "Send the SUBSCRIBE command on the mailbox to server in BUFFER.
1455 Returns non-nil if successful."
1456   (with-current-buffer (or buffer (current-buffer))
1457     (imap-ok-p (imap-send-command-wait (concat "SUBSCRIBE \""
1458                                                (imap-utf7-encode mailbox)
1459                                                "\"")))))
1460
1461 (defun imap-mailbox-unsubscribe (mailbox &optional buffer)
1462   "Send the SUBSCRIBE command on the mailbox to server in BUFFER.
1463 Returns non-nil if successful."
1464   (with-current-buffer (or buffer (current-buffer))
1465     (imap-ok-p (imap-send-command-wait (concat "UNSUBSCRIBE "
1466                                                (imap-utf7-encode mailbox)
1467                                                "\"")))))
1468
1469 (defun imap-mailbox-status (mailbox items &optional buffer)
1470   "Get status items ITEM in MAILBOX from server in BUFFER.
1471 ITEMS can be a symbol or a list of symbols, valid symbols are one of
1472 the STATUS data items -- ie 'messages, 'recent, 'uidnext, 'uidvalidity
1473 or 'unseen.  If ITEMS is a list of symbols, a list of values is
1474 returned, if ITEMS is a symbol only its value is returned."
1475   (with-current-buffer (or buffer (current-buffer))
1476     (when (imap-ok-p
1477            (imap-send-command-wait (list "STATUS \""
1478                                          (imap-utf7-encode mailbox)
1479                                          "\" "
1480                                          (upcase
1481                                           (format "%s"
1482                                                   (if (listp items)
1483                                                       items
1484                                                     (list items)))))))
1485       (if (listp items)
1486           (mapcar (lambda (item)
1487                     (imap-mailbox-get item mailbox))
1488                   items)
1489         (imap-mailbox-get items mailbox)))))
1490
1491 (defun imap-mailbox-status-asynch (mailbox items &optional buffer)
1492   "Send status item request ITEM on MAILBOX to server in BUFFER.
1493 ITEMS can be a symbol or a list of symbols, valid symbols are one of
1494 the STATUS data items -- ie 'messages, 'recent, 'uidnext, 'uidvalidity
1495 or 'unseen.  The IMAP command tag is returned."
1496   (with-current-buffer (or buffer (current-buffer))
1497     (imap-send-command (list "STATUS \""
1498                              (imap-utf7-encode mailbox)
1499                              "\" "
1500                              (format "%s"
1501                                      (if (listp items)
1502                                          items
1503                                        (list items)))))))
1504
1505 (defun imap-mailbox-acl-get (&optional mailbox buffer)
1506   "Get ACL on mailbox from server in BUFFER."
1507   (let ((mailbox (imap-utf7-encode mailbox)))
1508     (with-current-buffer (or buffer (current-buffer))
1509       (when (imap-ok-p
1510              (imap-send-command-wait (list "GETACL \""
1511                                            (or mailbox imap-current-mailbox)
1512                                            "\"")))
1513         (imap-mailbox-get-1 'acl (or mailbox imap-current-mailbox))))))
1514
1515 (defun imap-mailbox-acl-set (identifier rights &optional mailbox buffer)
1516   "Change/set ACL for IDENTIFIER to RIGHTS in MAILBOX from server in BUFFER."
1517   (let ((mailbox (imap-utf7-encode mailbox)))
1518     (with-current-buffer (or buffer (current-buffer))
1519       (imap-ok-p
1520        (imap-send-command-wait (list "SETACL \""
1521                                      (or mailbox imap-current-mailbox)
1522                                      "\" "
1523                                      identifier
1524                                      " "
1525                                      rights))))))
1526
1527 (defun imap-mailbox-acl-delete (identifier &optional mailbox buffer)
1528   "Removes any <identifier,rights> pair for IDENTIFIER in MAILBOX from server in BUFFER."
1529   (let ((mailbox (imap-utf7-encode mailbox)))
1530     (with-current-buffer (or buffer (current-buffer))
1531       (imap-ok-p
1532        (imap-send-command-wait (list "DELETEACL \""
1533                                      (or mailbox imap-current-mailbox)
1534                                      "\" "
1535                                      identifier))))))
1536
1537 \f
1538 ;; Message functions:
1539
1540 (defun imap-current-message (&optional buffer)
1541   (with-current-buffer (or buffer (current-buffer))
1542     imap-current-message))
1543
1544 (defun imap-list-to-message-set (list)
1545   (mapconcat (lambda (item)
1546                (number-to-string item))
1547              (if (listp list)
1548                  list
1549                (list list))
1550              ","))
1551
1552 (defun imap-range-to-message-set (range)
1553   (mapconcat
1554    (lambda (item)
1555      (if (consp item)
1556          (format "%d:%d"
1557                  (car item) (cdr item))
1558        (format "%d" item)))
1559    (if (and (listp range) (not (listp (cdr range))))
1560        (list range) ;; make (1 . 2) into ((1 . 2))
1561      range)
1562    ","))
1563
1564 (defun imap-fetch-asynch (uids props &optional nouidfetch buffer)
1565   (with-current-buffer (or buffer (current-buffer))
1566     (imap-send-command (format "%sFETCH %s %s" (if nouidfetch "" "UID ")
1567                                (if (listp uids)
1568                                    (imap-list-to-message-set uids)
1569                                  uids)
1570                                props))))
1571
1572 (defun imap-fetch (uids props &optional receive nouidfetch buffer)
1573   "Fetch properties PROPS from message set UIDS from server in BUFFER.
1574 UIDS can be a string, number or a list of numbers.  If RECEIVE
1575 is non-nil return these properties."
1576   (with-current-buffer (or buffer (current-buffer))
1577     (when (imap-ok-p (imap-send-command-wait
1578                       (format "%sFETCH %s %s" (if nouidfetch "" "UID ")
1579                               (if (listp uids)
1580                                   (imap-list-to-message-set uids)
1581                                 uids)
1582                               props)))
1583       (if (or (null receive) (stringp uids))
1584           t
1585         (if (listp uids)
1586             (mapcar (lambda (uid)
1587                       (if (listp receive)
1588                           (mapcar (lambda (prop)
1589                                     (imap-message-get uid prop))
1590                                   receive)
1591                         (imap-message-get uid receive)))
1592                     uids)
1593           (imap-message-get uids receive))))))
1594
1595 (defun imap-message-put (uid propname value &optional buffer)
1596   (with-current-buffer (or buffer (current-buffer))
1597     (if imap-message-data
1598         (put (intern (number-to-string uid) imap-message-data)
1599              propname value)
1600       (error "Imap-message-data is nil, uid %s prop %s value %s buffer %s"
1601              uid propname value (current-buffer)))
1602     t))
1603
1604 (defun imap-message-get (uid propname &optional buffer)
1605   (with-current-buffer (or buffer (current-buffer))
1606     (get (intern-soft (number-to-string uid) imap-message-data)
1607          propname)))
1608
1609 (defun imap-message-map (func propname &optional buffer)
1610   "Map a function across each mailbox in `imap-message-data', returning a list."
1611   (with-current-buffer (or buffer (current-buffer))
1612     (let (result)
1613       (mapatoms
1614        (lambda (s)
1615          (push (funcall func (get s 'UID) (get s propname)) result))
1616        imap-message-data)
1617       result)))
1618
1619 (defmacro imap-message-envelope-date (uid &optional buffer)
1620   `(with-current-buffer (or ,buffer (current-buffer))
1621      (elt (imap-message-get ,uid 'ENVELOPE) 0)))
1622
1623 (defmacro imap-message-envelope-subject (uid &optional buffer)
1624   `(with-current-buffer (or ,buffer (current-buffer))
1625      (elt (imap-message-get ,uid 'ENVELOPE) 1)))
1626
1627 (defmacro imap-message-envelope-from (uid &optional buffer)
1628   `(with-current-buffer (or ,buffer (current-buffer))
1629      (elt (imap-message-get ,uid 'ENVELOPE) 2)))
1630
1631 (defmacro imap-message-envelope-sender (uid &optional buffer)
1632   `(with-current-buffer (or ,buffer (current-buffer))
1633      (elt (imap-message-get ,uid 'ENVELOPE) 3)))
1634
1635 (defmacro imap-message-envelope-reply-to (uid &optional buffer)
1636   `(with-current-buffer (or ,buffer (current-buffer))
1637      (elt (imap-message-get ,uid 'ENVELOPE) 4)))
1638
1639 (defmacro imap-message-envelope-to (uid &optional buffer)
1640   `(with-current-buffer (or ,buffer (current-buffer))
1641      (elt (imap-message-get ,uid 'ENVELOPE) 5)))
1642
1643 (defmacro imap-message-envelope-cc (uid &optional buffer)
1644   `(with-current-buffer (or ,buffer (current-buffer))
1645      (elt (imap-message-get ,uid 'ENVELOPE) 6)))
1646
1647 (defmacro imap-message-envelope-bcc (uid &optional buffer)
1648   `(with-current-buffer (or ,buffer (current-buffer))
1649      (elt (imap-message-get ,uid 'ENVELOPE) 7)))
1650
1651 (defmacro imap-message-envelope-in-reply-to (uid &optional buffer)
1652   `(with-current-buffer (or ,buffer (current-buffer))
1653      (elt (imap-message-get ,uid 'ENVELOPE) 8)))
1654
1655 (defmacro imap-message-envelope-message-id (uid &optional buffer)
1656   `(with-current-buffer (or ,buffer (current-buffer))
1657      (elt (imap-message-get ,uid 'ENVELOPE) 9)))
1658
1659 (defmacro imap-message-body (uid &optional buffer)
1660   `(with-current-buffer (or ,buffer (current-buffer))
1661      (imap-message-get ,uid 'BODY)))
1662
1663 (defun imap-search (predicate &optional buffer)
1664   (with-current-buffer (or buffer (current-buffer))
1665     (imap-mailbox-put 'search 'dummy)
1666     (when (imap-ok-p (imap-send-command-wait (concat "UID SEARCH " predicate)))
1667       (if (eq (imap-mailbox-get-1 'search imap-current-mailbox) 'dummy)
1668           (progn
1669             (message "Missing SEARCH response to a SEARCH command (server not RFC compliant)...")
1670             nil)
1671         (imap-mailbox-get-1 'search imap-current-mailbox)))))
1672
1673 (defun imap-message-flag-permanent-p (flag &optional mailbox buffer)
1674   "Return t iff FLAG can be permanently (between IMAP sessions) saved on articles, in MAILBOX on server in BUFFER."
1675   (with-current-buffer (or buffer (current-buffer))
1676     (or (member "\\*" (imap-mailbox-get 'permanentflags mailbox))
1677         (member flag (imap-mailbox-get 'permanentflags mailbox)))))
1678
1679 (defun imap-message-flags-set (articles flags &optional silent buffer)
1680   (when (and articles flags)
1681     (with-current-buffer (or buffer (current-buffer))
1682       (imap-ok-p (imap-send-command-wait
1683                   (concat "UID STORE " articles
1684                           " FLAGS" (if silent ".SILENT") " (" flags ")"))))))
1685
1686 (defun imap-message-flags-del (articles flags &optional silent buffer)
1687   (when (and articles flags)
1688     (with-current-buffer (or buffer (current-buffer))
1689       (imap-ok-p (imap-send-command-wait
1690                   (concat "UID STORE " articles
1691                           " -FLAGS" (if silent ".SILENT") " (" flags ")"))))))
1692
1693 (defun imap-message-flags-add (articles flags &optional silent buffer)
1694   (when (and articles flags)
1695     (with-current-buffer (or buffer (current-buffer))
1696       (imap-ok-p (imap-send-command-wait
1697                   (concat "UID STORE " articles
1698                           " +FLAGS" (if silent ".SILENT") " (" flags ")"))))))
1699
1700 (defun imap-message-copyuid-1 (mailbox)
1701   (if (imap-capability 'UIDPLUS)
1702       (list (nth 0 (imap-mailbox-get-1 'copyuid mailbox))
1703             (string-to-number (nth 2 (imap-mailbox-get-1 'copyuid mailbox))))
1704     (let ((old-mailbox imap-current-mailbox)
1705           (state imap-state)
1706           (imap-message-data (make-vector 2 0)))
1707       (when (imap-mailbox-examine-1 mailbox)
1708         (prog1
1709             (and (imap-fetch "*" "UID")
1710                  (list (imap-mailbox-get-1 'uidvalidity mailbox)
1711                        (apply 'max (imap-message-map
1712                                     (lambda (uid prop) uid) 'UID))))
1713           (if old-mailbox
1714               (imap-mailbox-select old-mailbox (eq state 'examine))
1715             (imap-mailbox-unselect)))))))
1716
1717 (defun imap-message-copyuid (mailbox &optional buffer)
1718   (with-current-buffer (or buffer (current-buffer))
1719     (imap-message-copyuid-1 (imap-utf7-decode mailbox))))
1720
1721 (defun imap-message-copy (articles mailbox
1722                                    &optional dont-create no-copyuid buffer)
1723   "Copy ARTICLES (a string message set) to MAILBOX on server in
1724 BUFFER, creating mailbox if it doesn't exist.  If dont-create is
1725 non-nil, it will not create a mailbox.  On success, return a list with
1726 the UIDVALIDITY of the mailbox the article(s) was copied to as the
1727 first element, rest of list contain the saved articles' UIDs."
1728   (when articles
1729     (with-current-buffer (or buffer (current-buffer))
1730       (let ((mailbox (imap-utf7-encode mailbox)))
1731         (if (let ((cmd (concat "UID COPY " articles " \"" mailbox "\""))
1732                   (imap-current-target-mailbox mailbox))
1733               (if (imap-ok-p (imap-send-command-wait cmd))
1734                   t
1735                 (when (and (not dont-create)
1736                            ;; removed because of buggy Oracle server
1737                            ;; that doesn't send TRYCREATE tags (which
1738                            ;; is a MUST according to specifications):
1739                            ;;(imap-mailbox-get-1 'trycreate mailbox)
1740                            (imap-mailbox-create-1 mailbox))
1741                   (imap-ok-p (imap-send-command-wait cmd)))))
1742             (or no-copyuid
1743                 (imap-message-copyuid-1 mailbox)))))))
1744
1745 (defun imap-message-appenduid-1 (mailbox)
1746   (if (imap-capability 'UIDPLUS)
1747       (imap-mailbox-get-1 'appenduid mailbox)
1748     (let ((old-mailbox imap-current-mailbox)
1749           (state imap-state)
1750           (imap-message-data (make-vector 2 0)))
1751       (when (imap-mailbox-examine-1 mailbox)
1752         (prog1
1753             (and (imap-fetch "*" "UID")
1754                  (list (imap-mailbox-get-1 'uidvalidity mailbox)
1755                        (apply 'max (imap-message-map
1756                                     (lambda (uid prop) uid) 'UID))))
1757           (if old-mailbox
1758               (imap-mailbox-select old-mailbox (eq state 'examine))
1759             (imap-mailbox-unselect)))))))
1760
1761 (defun imap-message-appenduid (mailbox &optional buffer)
1762   (with-current-buffer (or buffer (current-buffer))
1763     (imap-message-appenduid-1 (imap-utf7-encode mailbox))))
1764
1765 (defun imap-message-append (mailbox article &optional flags date-time buffer)
1766   "Append ARTICLE (a buffer) to MAILBOX on server in BUFFER.
1767 FLAGS and DATE-TIME is currently not used.  Return a cons holding
1768 uidvalidity of MAILBOX and UID the newly created article got, or nil
1769 on failure."
1770   (let ((mailbox (imap-utf7-encode mailbox)))
1771     (with-current-buffer (or buffer (current-buffer))
1772       (and (let ((imap-current-target-mailbox mailbox))
1773              (imap-ok-p
1774               (imap-send-command-wait
1775                (list "APPEND \"" mailbox "\" "  article))))
1776            (imap-message-appenduid-1 mailbox)))))
1777
1778 (defun imap-body-lines (body)
1779   "Return number of lines in article by looking at the mime bodystructure BODY."
1780   (if (listp body)
1781       (if (stringp (car body))
1782           (cond ((and (string= (upcase (car body)) "TEXT")
1783                       (numberp (nth 7 body)))
1784                  (nth 7 body))
1785                 ((and (string= (upcase (car body)) "MESSAGE")
1786                       (numberp (nth 9 body)))
1787                  (nth 9 body))
1788                 (t 0))
1789         (apply '+ (mapcar 'imap-body-lines body)))
1790     0))
1791
1792 (defun imap-envelope-from (from)
1793   "Return a from string line."
1794   (and from
1795        (concat (aref from 0)
1796                (if (aref from 0) " <")
1797                (aref from 2)
1798                "@"
1799                (aref from 3)
1800                (if (aref from 0) ">"))))
1801
1802 \f
1803 ;; Internal functions.
1804
1805 (defun imap-add-callback (tag func)
1806   (setq imap-callbacks (append (list (cons tag func)) imap-callbacks)))
1807
1808 (defun imap-send-command-1 (cmdstr)
1809   (setq cmdstr (concat cmdstr imap-client-eol))
1810   (and imap-log
1811        (with-current-buffer (get-buffer-create imap-log-buffer)
1812          (imap-disable-multibyte)
1813          (buffer-disable-undo)
1814          (goto-char (point-max))
1815          (insert cmdstr)))
1816   (process-send-string imap-process cmdstr))
1817
1818 (defun imap-send-command (command &optional buffer)
1819   (with-current-buffer (or buffer (current-buffer))
1820     (if (not (listp command)) (setq command (list command)))
1821     (let ((tag (setq imap-tag (1+ imap-tag)))
1822           cmd cmdstr)
1823       (setq cmdstr (concat (number-to-string imap-tag) " "))
1824       (while (setq cmd (pop command))
1825         (cond ((stringp cmd)
1826                (setq cmdstr (concat cmdstr cmd)))
1827               ((bufferp cmd)
1828                (let ((eol imap-client-eol)
1829                      (calcfirst imap-calculate-literal-size-first)
1830                      size)
1831                  (with-current-buffer cmd
1832                    (if calcfirst
1833                        (setq size (buffer-size)))
1834                    (when (not (equal eol "\r\n"))
1835                      ;; XXX modifies buffer!
1836                      (goto-char (point-min))
1837                      (while (search-forward "\r\n" nil t)
1838                        (replace-match eol)))
1839                    (if (not calcfirst)
1840                        (setq size (buffer-size))))
1841                  (setq cmdstr
1842                        (concat cmdstr (format "{%d}" size))))
1843                (unwind-protect
1844                    (progn
1845                      (imap-send-command-1 cmdstr)
1846                      (setq cmdstr nil)
1847                      (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE))
1848                          (setq command nil) ;; abort command if no cont-req
1849                        (let ((process imap-process)
1850                              (stream imap-stream)
1851                              (eol imap-client-eol))
1852                          (with-current-buffer cmd
1853                            (and imap-log
1854                                 (with-current-buffer (get-buffer-create
1855                                                       imap-log-buffer)
1856                                   (imap-disable-multibyte)
1857                                   (buffer-disable-undo)
1858                                   (goto-char (point-max))
1859                                   (insert-buffer-substring cmd)))
1860                            (process-send-region process (point-min)
1861                                                 (point-max)))
1862                          (process-send-string process imap-client-eol))))
1863                  (setq imap-continuation nil)))
1864               ((functionp cmd)
1865                (imap-send-command-1 cmdstr)
1866                (setq cmdstr nil)
1867                (unwind-protect
1868                    (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE))
1869                        (setq command nil) ;; abort command if no cont-req
1870                      (setq command (cons (funcall cmd imap-continuation)
1871                                          command)))
1872                  (setq imap-continuation nil)))
1873               (t
1874                (error "Unknown command type"))))
1875       (if cmdstr
1876           (imap-send-command-1 cmdstr))
1877       tag)))
1878
1879 (defun imap-wait-for-tag (tag &optional buffer)
1880   (with-current-buffer (or buffer (current-buffer))
1881     (let (imap-have-messaged)
1882       (while (and (null imap-continuation)
1883                   (memq (process-status imap-process) '(open run))
1884                   (< imap-reached-tag tag))
1885         (let ((len (/ (point-max) 1024))
1886               message-log-max)
1887           (unless (< len 10)
1888             (setq imap-have-messaged t)
1889             (message "imap read: %dk" len))
1890           (accept-process-output imap-process
1891                                  (truncate imap-read-timeout)
1892                                  (truncate (* (- imap-read-timeout
1893                                                  (truncate imap-read-timeout))
1894                                               1000)))))
1895       ;; A process can die _before_ we have processed everything it
1896       ;; has to say.  Moreover, this can happen in between the call to
1897       ;; accept-process-output and the call to process-status in an
1898       ;; iteration of the loop above.
1899       (when (and (null imap-continuation)
1900                  (< imap-reached-tag tag))
1901         (accept-process-output imap-process 0 0))
1902       (when imap-have-messaged
1903         (message ""))
1904       (and (memq (process-status imap-process) '(open run))
1905            (or (assq tag imap-failed-tags)
1906                (if imap-continuation
1907                    'INCOMPLETE
1908                  'OK))))))
1909
1910 (defun imap-sentinel (process string)
1911   (delete-process process))
1912
1913 (defun imap-find-next-line ()
1914   "Return point at end of current line, taking into account literals.
1915 Return nil if no complete line has arrived."
1916   (when (re-search-forward (concat imap-server-eol "\\|{\\([0-9]+\\)}"
1917                                    imap-server-eol)
1918                            nil t)
1919     (if (match-string 1)
1920         (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
1921             nil
1922           (goto-char (+ (point) (string-to-number (match-string 1))))
1923           (imap-find-next-line))
1924       (point))))
1925
1926 (defun imap-arrival-filter (proc string)
1927   "IMAP process filter."
1928   ;; Sometimes, we are called even though the process has died.
1929   ;; Better abstain from doing stuff in that case.
1930   (when (buffer-name (process-buffer proc))
1931     (with-current-buffer (process-buffer proc)
1932       (goto-char (point-max))
1933       (insert string)
1934       (and imap-log
1935            (with-current-buffer (get-buffer-create imap-log-buffer)
1936              (imap-disable-multibyte)
1937              (buffer-disable-undo)
1938              (goto-char (point-max))
1939              (insert string)))
1940       (let (end)
1941         (goto-char (point-min))
1942         (while (setq end (imap-find-next-line))
1943           (save-restriction
1944             (narrow-to-region (point-min) end)
1945             (delete-backward-char (length imap-server-eol))
1946             (goto-char (point-min))
1947             (unwind-protect
1948                 (cond ((eq imap-state 'initial)
1949                        (imap-parse-greeting))
1950                       ((or (eq imap-state 'auth)
1951                            (eq imap-state 'nonauth)
1952                            (eq imap-state 'selected)
1953                            (eq imap-state 'examine))
1954                        (imap-parse-response))
1955                       (t
1956                        (message "Unknown state %s in arrival filter"
1957                                 imap-state)))
1958               (delete-region (point-min) (point-max)))))))))
1959
1960 \f
1961 ;; Imap parser.
1962
1963 (defsubst imap-forward ()
1964   (or (eobp) (forward-char)))
1965
1966 ;;   number          = 1*DIGIT
1967 ;;                       ; Unsigned 32-bit integer
1968 ;;                       ; (0 <= n < 4,294,967,296)
1969
1970 (defsubst imap-parse-number ()
1971   (when (looking-at "[0-9]+")
1972     (prog1
1973         (string-to-number (match-string 0))
1974       (goto-char (match-end 0)))))
1975
1976 ;;   literal         = "{" number "}" CRLF *CHAR8
1977 ;;                       ; Number represents the number of CHAR8s
1978
1979 (defsubst imap-parse-literal ()
1980   (when (looking-at "{\\([0-9]+\\)}\r\n")
1981     (let ((pos (match-end 0))
1982           (len (string-to-number (match-string 1))))
1983       (if (< (point-max) (+ pos len))
1984           nil
1985         (goto-char (+ pos len))
1986         (buffer-substring pos (+ pos len))))))
1987
1988 ;;   string          = quoted / literal
1989 ;;
1990 ;;   quoted          = DQUOTE *QUOTED-CHAR DQUOTE
1991 ;;
1992 ;;   QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> /
1993 ;;                     "\" quoted-specials
1994 ;;
1995 ;;   quoted-specials = DQUOTE / "\"
1996 ;;
1997 ;;   TEXT-CHAR       = <any CHAR except CR and LF>
1998
1999 (defsubst imap-parse-string ()
2000   (cond ((eq (char-after) ?\")
2001          (forward-char 1)
2002          (let ((p (point)) (name ""))
2003            (skip-chars-forward "^\"\\\\")
2004            (setq name (buffer-substring p (point)))
2005            (while (eq (char-after) ?\\)
2006              (setq p (1+ (point)))
2007              (forward-char 2)
2008              (skip-chars-forward "^\"\\\\")
2009              (setq name (concat name (buffer-substring p (point)))))
2010            (forward-char 1)
2011            name))
2012         ((eq (char-after) ?{)
2013          (imap-parse-literal))))
2014
2015 ;;   nil             = "NIL"
2016
2017 (defsubst imap-parse-nil ()
2018   (if (looking-at "NIL")
2019       (goto-char (match-end 0))))
2020
2021 ;;   nstring         = string / nil
2022
2023 (defsubst imap-parse-nstring ()
2024   (or (imap-parse-string)
2025       (and (imap-parse-nil)
2026            nil)))
2027
2028 ;;   astring         = atom / string
2029 ;;
2030 ;;   atom            = 1*ATOM-CHAR
2031 ;;
2032 ;;   ATOM-CHAR       = <any CHAR except atom-specials>
2033 ;;
2034 ;;   atom-specials   = "(" / ")" / "{" / SP / CTL / list-wildcards /
2035 ;;                     quoted-specials
2036 ;;
2037 ;;   list-wildcards  = "%" / "*"
2038 ;;
2039 ;;   quoted-specials = DQUOTE / "\"
2040
2041 (defsubst imap-parse-astring ()
2042   (or (imap-parse-string)
2043       (buffer-substring (point)
2044                         (if (re-search-forward "[(){ \r\n%*\"\\]" nil t)
2045                             (goto-char (1- (match-end 0)))
2046                           (end-of-line)
2047                           (point)))))
2048
2049 ;;   address         = "(" addr-name SP addr-adl SP addr-mailbox SP
2050 ;;                      addr-host ")"
2051 ;;
2052 ;;   addr-adl        = nstring
2053 ;;                       ; Holds route from [RFC-822] route-addr if
2054 ;;                       ; non-nil
2055 ;;
2056 ;;   addr-host       = nstring
2057 ;;                       ; nil indicates [RFC-822] group syntax.
2058 ;;                       ; Otherwise, holds [RFC-822] domain name
2059 ;;
2060 ;;   addr-mailbox    = nstring
2061 ;;                       ; nil indicates end of [RFC-822] group; if
2062 ;;                       ; non-nil and addr-host is nil, holds
2063 ;;                       ; [RFC-822] group name.
2064 ;;                       ; Otherwise, holds [RFC-822] local-part
2065 ;;                       ; after removing [RFC-822] quoting
2066 ;;
2067 ;;   addr-name       = nstring
2068 ;;                       ; If non-nil, holds phrase from [RFC-822]
2069 ;;                       ; mailbox after removing [RFC-822] quoting
2070 ;;
2071
2072 (defsubst imap-parse-address ()
2073   (let (address)
2074     (when (eq (char-after) ?\()
2075       (imap-forward)
2076       (setq address (vector (prog1 (imap-parse-nstring)
2077                               (imap-forward))
2078                             (prog1 (imap-parse-nstring)
2079                               (imap-forward))
2080                             (prog1 (imap-parse-nstring)
2081                               (imap-forward))
2082                             (imap-parse-nstring)))
2083       (when (eq (char-after) ?\))
2084         (imap-forward)
2085         address))))
2086
2087 ;;   address-list    = "(" 1*address ")" / nil
2088 ;;
2089 ;;   nil             = "NIL"
2090
2091 (defsubst imap-parse-address-list ()
2092   (if (eq (char-after) ?\()
2093       (let (address addresses)
2094         (imap-forward)
2095         (while (and (not (eq (char-after) ?\)))
2096                     ;; next line for MS Exchange bug
2097                     (progn (and (eq (char-after) ? ) (imap-forward)) t)
2098                     (setq address (imap-parse-address)))
2099           (setq addresses (cons address addresses)))
2100         (when (eq (char-after) ?\))
2101           (imap-forward)
2102           (nreverse addresses)))
2103     ;; With assert, the code might not be eval'd.
2104     ;; (assert (imap-parse-nil) t "In imap-parse-address-list")
2105     (imap-parse-nil)))
2106
2107 ;;   mailbox         = "INBOX" / astring
2108 ;;                       ; INBOX is case-insensitive.  All case variants of
2109 ;;                       ; INBOX (e.g. "iNbOx") MUST be interpreted as INBOX
2110 ;;                       ; not as an astring.  An astring which consists of
2111 ;;                       ; the case-insensitive sequence "I" "N" "B" "O" "X"
2112 ;;                       ; is considered to be INBOX and not an astring.
2113 ;;                       ;  Refer to section 5.1 for further
2114 ;;                       ; semantic details of mailbox names.
2115
2116 (defsubst imap-parse-mailbox ()
2117   (let ((mailbox (imap-parse-astring)))
2118     (if (string-equal "INBOX" (upcase mailbox))
2119         "INBOX"
2120       mailbox)))
2121
2122 ;;   greeting        = "*" SP (resp-cond-auth / resp-cond-bye) CRLF
2123 ;;
2124 ;;   resp-cond-auth  = ("OK" / "PREAUTH") SP resp-text
2125 ;;                       ; Authentication condition
2126 ;;
2127 ;;   resp-cond-bye   = "BYE" SP resp-text
2128
2129 (defun imap-parse-greeting ()
2130   "Parse a IMAP greeting."
2131   (cond ((looking-at "\\* OK ")
2132          (setq imap-state 'nonauth))
2133         ((looking-at "\\* PREAUTH ")
2134          (setq imap-state 'auth))
2135         ((looking-at "\\* BYE ")
2136          (setq imap-state 'closed))))
2137
2138 ;;   response        = *(continue-req / response-data) response-done
2139 ;;
2140 ;;   continue-req    = "+" SP (resp-text / base64) CRLF
2141 ;;
2142 ;;   response-data   = "*" SP (resp-cond-state / resp-cond-bye /
2143 ;;                     mailbox-data / message-data / capability-data) CRLF
2144 ;;
2145 ;;   response-done   = response-tagged / response-fatal
2146 ;;
2147 ;;   response-fatal  = "*" SP resp-cond-bye CRLF
2148 ;;                       ; Server closes connection immediately
2149 ;;
2150 ;;   response-tagged = tag SP resp-cond-state CRLF
2151 ;;
2152 ;;   resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
2153 ;;                       ; Status condition
2154 ;;
2155 ;;   resp-cond-bye   = "BYE" SP resp-text
2156 ;;
2157 ;;   mailbox-data    =  "FLAGS" SP flag-list /
2158 ;;                      "LIST" SP mailbox-list /
2159 ;;                      "LSUB" SP mailbox-list /
2160 ;;                      "SEARCH" *(SP nz-number) /
2161 ;;                      "STATUS" SP mailbox SP "("
2162 ;;                            [status-att SP number *(SP status-att SP number)] ")" /
2163 ;;                      number SP "EXISTS" /
2164 ;;                      number SP "RECENT"
2165 ;;
2166 ;;   message-data    = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att))
2167 ;;
2168 ;;   capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1"
2169 ;;                     *(SP capability)
2170 ;;                       ; IMAP4rev1 servers which offer RFC 1730
2171 ;;                       ; compatibility MUST list "IMAP4" as the first
2172 ;;                       ; capability.
2173
2174 (defun imap-parse-response ()
2175   "Parse a IMAP command response."
2176   (let (token)
2177     (case (setq token (read (current-buffer)))
2178       (+ (setq imap-continuation
2179                (or (buffer-substring (min (point-max) (1+ (point)))
2180                                      (point-max))
2181                    t)))
2182       (* (case (prog1 (setq token (read (current-buffer)))
2183                  (imap-forward))
2184            (OK         (imap-parse-resp-text))
2185            (NO         (imap-parse-resp-text))
2186            (BAD        (imap-parse-resp-text))
2187            (BYE        (imap-parse-resp-text))
2188            (FLAGS      (imap-mailbox-put 'flags (imap-parse-flag-list)))
2189            (LIST       (imap-parse-data-list 'list))
2190            (LSUB       (imap-parse-data-list 'lsub))
2191            (SEARCH     (imap-mailbox-put
2192                         'search
2193                         (read (concat "(" (buffer-substring (point) (point-max)) ")"))))
2194            (STATUS     (imap-parse-status))
2195            (CAPABILITY (setq imap-capability
2196                                (read (concat "(" (upcase (buffer-substring
2197                                                           (point) (point-max)))
2198                                              ")"))))
2199            (ID         (setq imap-id (read (buffer-substring (point)
2200                                                              (point-max)))))
2201            (ACL        (imap-parse-acl))
2202            (t       (case (prog1 (read (current-buffer))
2203                             (imap-forward))
2204                       (EXISTS  (imap-mailbox-put 'exists token))
2205                       (RECENT  (imap-mailbox-put 'recent token))
2206                       (EXPUNGE t)
2207                       (FETCH   (imap-parse-fetch token))
2208                       (t       (message "Garbage: %s" (buffer-string)))))))
2209       (t (let (status)
2210            (if (not (integerp token))
2211                (message "Garbage: %s" (buffer-string))
2212              (case (prog1 (setq status (read (current-buffer)))
2213                      (imap-forward))
2214                (OK  (progn
2215                       (setq imap-reached-tag (max imap-reached-tag token))
2216                       (imap-parse-resp-text)))
2217                (NO  (progn
2218                       (setq imap-reached-tag (max imap-reached-tag token))
2219                       (save-excursion
2220                         (imap-parse-resp-text))
2221                       (let (code text)
2222                         (when (eq (char-after) ?\[)
2223                           (setq code (buffer-substring (point)
2224                                                        (search-forward "]")))
2225                           (imap-forward))
2226                         (setq text (buffer-substring (point) (point-max)))
2227                         (push (list token status code text)
2228                               imap-failed-tags))))
2229                (BAD (progn
2230                       (setq imap-reached-tag (max imap-reached-tag token))
2231                       (save-excursion
2232                         (imap-parse-resp-text))
2233                       (let (code text)
2234                         (when (eq (char-after) ?\[)
2235                           (setq code (buffer-substring (point)
2236                                                        (search-forward "]")))
2237                           (imap-forward))
2238                         (setq text (buffer-substring (point) (point-max)))
2239                         (push (list token status code text) imap-failed-tags)
2240                         (error "Internal error, tag %s status %s code %s text %s"
2241                                token status code text))))
2242                (t   (message "Garbage: %s" (buffer-string))))
2243              (when (assq token imap-callbacks)
2244                (funcall (cdr (assq token imap-callbacks)) token status)
2245                (setq imap-callbacks
2246                      (imap-remassoc token imap-callbacks)))))))))
2247
2248 ;;   resp-text       = ["[" resp-text-code "]" SP] text
2249 ;;
2250 ;;   text            = 1*TEXT-CHAR
2251 ;;
2252 ;;   TEXT-CHAR       = <any CHAR except CR and LF>
2253
2254 (defun imap-parse-resp-text ()
2255   (imap-parse-resp-text-code))
2256
2257 ;;   resp-text-code  = "ALERT" /
2258 ;;                     "BADCHARSET [SP "(" astring *(SP astring) ")" ] /
2259 ;;                     "NEWNAME" SP string SP string /
2260 ;;                     "PARSE" /
2261 ;;                     "PERMANENTFLAGS" SP "("
2262 ;;                               [flag-perm *(SP flag-perm)] ")" /
2263 ;;                     "READ-ONLY" /
2264 ;;                     "READ-WRITE" /
2265 ;;                     "TRYCREATE" /
2266 ;;                     "UIDNEXT" SP nz-number /
2267 ;;                     "UIDVALIDITY" SP nz-number /
2268 ;;                     "UNSEEN" SP nz-number /
2269 ;;                     resp-text-atom [SP 1*<any TEXT-CHAR except "]">]
2270 ;;
2271 ;;   resp_code_apnd  = "APPENDUID" SPACE nz_number SPACE uniqueid
2272 ;;
2273 ;;   resp_code_copy  = "COPYUID" SPACE nz_number SPACE set SPACE set
2274 ;;
2275 ;;   set             = sequence-num / (sequence-num ":" sequence-num) /
2276 ;;                        (set "," set)
2277 ;;                          ; Identifies a set of messages.  For message
2278 ;;                          ; sequence numbers, these are consecutive
2279 ;;                          ; numbers from 1 to the number of messages in
2280 ;;                          ; the mailbox
2281 ;;                          ; Comma delimits individual numbers, colon
2282 ;;                          ; delimits between two numbers inclusive.
2283 ;;                          ; Example: 2,4:7,9,12:* is 2,4,5,6,7,9,12,13,
2284 ;;                          ; 14,15 for a mailbox with 15 messages.
2285 ;;
2286 ;;   sequence-num    = nz-number / "*"
2287 ;;                          ; * is the largest number in use.  For message
2288 ;;                          ; sequence numbers, it is the number of messages
2289 ;;                          ; in the mailbox.  For unique identifiers, it is
2290 ;;                          ; the unique identifier of the last message in
2291 ;;                          ; the mailbox.
2292 ;;
2293 ;;   flag-perm       = flag / "\*"
2294 ;;
2295 ;;   flag            = "\Answered" / "\Flagged" / "\Deleted" /
2296 ;;                     "\Seen" / "\Draft" / flag-keyword / flag-extension
2297 ;;                       ; Does not include "\Recent"
2298 ;;
2299 ;;   flag-extension  = "\" atom
2300 ;;                       ; Future expansion.  Client implementations
2301 ;;                       ; MUST accept flag-extension flags.  Server
2302 ;;                       ; implementations MUST NOT generate
2303 ;;                       ; flag-extension flags except as defined by
2304 ;;                       ; future standard or standards-track
2305 ;;                       ; revisions of this specification.
2306 ;;
2307 ;;   flag-keyword    = atom
2308 ;;
2309 ;;   resp-text-atom  = 1*<any ATOM-CHAR except "]">
2310
2311 (defun imap-parse-resp-text-code ()
2312   ;; xxx next line for stalker communigate pro 3.3.1 bug
2313   (when (looking-at " \\[")
2314     (imap-forward))
2315   (when (eq (char-after) ?\[)
2316     (imap-forward)
2317     (cond ((search-forward "PERMANENTFLAGS " nil t)
2318            (imap-mailbox-put 'permanentflags (imap-parse-flag-list)))
2319           ((search-forward "UIDNEXT \\([0-9]+\\)" nil t)
2320            (imap-mailbox-put 'uidnext (match-string 1)))
2321           ((search-forward "UNSEEN " nil t)
2322            (imap-mailbox-put 'first-unseen (read (current-buffer))))
2323           ((looking-at "UIDVALIDITY \\([0-9]+\\)")
2324            (imap-mailbox-put 'uidvalidity (match-string 1)))
2325           ((search-forward "READ-ONLY" nil t)
2326            (imap-mailbox-put 'read-only t))
2327           ((search-forward "NEWNAME " nil t)
2328            (let (oldname newname)
2329              (setq oldname (imap-parse-string))
2330              (imap-forward)
2331              (setq newname (imap-parse-string))
2332              (imap-mailbox-put 'newname newname oldname)))
2333           ((search-forward "TRYCREATE" nil t)
2334            (imap-mailbox-put 'trycreate t imap-current-target-mailbox))
2335           ((looking-at "APPENDUID \\([0-9]+\\) \\([0-9]+\\)")
2336            (imap-mailbox-put 'appenduid
2337                              (list (match-string 1)
2338                                    (string-to-number (match-string 2)))
2339                              imap-current-target-mailbox))
2340           ((looking-at "COPYUID \\([0-9]+\\) \\([0-9,:]+\\) \\([0-9,:]+\\)")
2341            (imap-mailbox-put 'copyuid (list (match-string 1)
2342                                             (match-string 2)
2343                                             (match-string 3))
2344                              imap-current-target-mailbox))
2345           ((search-forward "ALERT] " nil t)
2346            (message "Imap server %s information: %s" imap-server
2347                     (buffer-substring (point) (point-max)))))))
2348
2349 ;;   mailbox-list    = "(" [mbx-list-flags] ")" SP
2350 ;;                      (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox
2351 ;;
2352 ;;   mbx-list-flags  = *(mbx-list-oflag SP) mbx-list-sflag
2353 ;;                     *(SP mbx-list-oflag) /
2354 ;;                     mbx-list-oflag *(SP mbx-list-oflag)
2355 ;;
2356 ;;   mbx-list-oflag  = "\Noinferiors" / flag-extension
2357 ;;                       ; Other flags; multiple possible per LIST response
2358 ;;
2359 ;;   mbx-list-sflag  = "\Noselect" / "\Marked" / "\Unmarked"
2360 ;;                       ; Selectability flags; only one per LIST response
2361 ;;
2362 ;;   QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> /
2363 ;;                     "\" quoted-specials
2364 ;;
2365 ;;   quoted-specials = DQUOTE / "\"
2366
2367 (defun imap-parse-data-list (type)
2368   (let (flags delimiter mailbox)
2369     (setq flags (imap-parse-flag-list))
2370     (when (looking-at " NIL\\| \"\\\\?\\(.\\)\"")
2371       (setq delimiter (match-string 1))
2372       (goto-char (1+ (match-end 0)))
2373       (when (setq mailbox (imap-parse-mailbox))
2374         (imap-mailbox-put type t mailbox)
2375         (imap-mailbox-put 'list-flags flags mailbox)
2376         (imap-mailbox-put 'delimiter delimiter mailbox)))))
2377
2378 ;;  msg_att         ::= "(" 1#("ENVELOPE" SPACE envelope /
2379 ;;                      "FLAGS" SPACE "(" #(flag / "\Recent") ")" /
2380 ;;                      "INTERNALDATE" SPACE date_time /
2381 ;;                      "RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
2382 ;;                      "RFC822.SIZE" SPACE number /
2383 ;;                      "BODY" ["STRUCTURE"] SPACE body /
2384 ;;                      "BODY" section ["<" number ">"] SPACE nstring /
2385 ;;                      "UID" SPACE uniqueid) ")"
2386 ;;
2387 ;;  date_time       ::= <"> date_day_fixed "-" date_month "-" date_year
2388 ;;                      SPACE time SPACE zone <">
2389 ;;
2390 ;;  section         ::= "[" [section_text / (nz_number *["." nz_number]
2391 ;;                      ["." (section_text / "MIME")])] "]"
2392 ;;
2393 ;;  section_text    ::= "HEADER" / "HEADER.FIELDS" [".NOT"]
2394 ;;                      SPACE header_list / "TEXT"
2395 ;;
2396 ;;  header_fld_name ::= astring
2397 ;;
2398 ;;  header_list     ::= "(" 1#header_fld_name ")"
2399
2400 (defsubst imap-parse-header-list ()
2401   (when (eq (char-after) ?\()
2402     (let (strlist)
2403       (while (not (eq (char-after) ?\)))
2404         (imap-forward)
2405         (push (imap-parse-astring) strlist))
2406       (imap-forward)
2407       (nreverse strlist))))
2408
2409 (defsubst imap-parse-fetch-body-section ()
2410   (let ((section
2411          (buffer-substring (point) (1- (re-search-forward "[] ]" nil t)))))
2412     (if (eq (char-before) ? )
2413         (prog1
2414             (mapconcat 'identity (cons section (imap-parse-header-list)) " ")
2415           (search-forward "]" nil t))
2416       section)))
2417
2418 (defun imap-parse-fetch (response)
2419   (when (eq (char-after) ?\()
2420     (let (uid flags envelope internaldate rfc822 rfc822header rfc822text
2421               rfc822size body bodydetail bodystructure flags-empty)
2422       (while (not (eq (char-after) ?\)))
2423         (imap-forward)
2424         (let ((token (read (current-buffer))))
2425           (imap-forward)
2426           (cond ((eq token 'UID)
2427                  (setq uid (condition-case ()
2428                                (read (current-buffer))
2429                              (error))))
2430                 ((eq token 'FLAGS)
2431                  (setq flags (imap-parse-flag-list))
2432                  (if (not flags)
2433                      (setq flags-empty 't)))
2434                 ((eq token 'ENVELOPE)
2435                  (setq envelope (imap-parse-envelope)))
2436                 ((eq token 'INTERNALDATE)
2437                  (setq internaldate (imap-parse-string)))
2438                 ((eq token 'RFC822)
2439                  (setq rfc822 (imap-parse-nstring)))
2440                 ((eq token 'RFC822.HEADER)
2441                  (setq rfc822header (imap-parse-nstring)))
2442                 ((eq token 'RFC822.TEXT)
2443                  (setq rfc822text (imap-parse-nstring)))
2444                 ((eq token 'RFC822.SIZE)
2445                  (setq rfc822size (read (current-buffer))))
2446                 ((eq token 'BODY)
2447                  (if (eq (char-before) ?\[)
2448                      (push (list
2449                             (upcase (imap-parse-fetch-body-section))
2450                             (and (eq (char-after) ?<)
2451                                  (buffer-substring (1+ (point))
2452                                                    (search-forward ">" nil t)))
2453                             (progn (imap-forward)
2454                                    (imap-parse-nstring)))
2455                            bodydetail)
2456                    (setq body (imap-parse-body))))
2457                 ((eq token 'BODYSTRUCTURE)
2458                  (setq bodystructure (imap-parse-body))))))
2459       (when uid
2460         (setq imap-current-message uid)
2461         (imap-message-put uid 'UID uid)
2462         (and (or flags flags-empty) (imap-message-put uid 'FLAGS flags))
2463         (and envelope (imap-message-put uid 'ENVELOPE envelope))
2464         (and internaldate (imap-message-put uid 'INTERNALDATE internaldate))
2465         (and rfc822 (imap-message-put uid 'RFC822 rfc822))
2466         (and rfc822header (imap-message-put uid 'RFC822.HEADER rfc822header))
2467         (and rfc822text (imap-message-put uid 'RFC822.TEXT rfc822text))
2468         (and rfc822size (imap-message-put uid 'RFC822.SIZE rfc822size))
2469         (and body (imap-message-put uid 'BODY body))
2470         (and bodydetail (imap-message-put uid 'BODYDETAIL bodydetail))
2471         (and bodystructure (imap-message-put uid 'BODYSTRUCTURE bodystructure))
2472         (run-hooks 'imap-fetch-data-hook)))))
2473
2474 ;;   mailbox-data    =  ...
2475 ;;                      "STATUS" SP mailbox SP "("
2476 ;;                            [status-att SP number
2477 ;;                            *(SP status-att SP number)] ")"
2478 ;;                      ...
2479 ;;
2480 ;;   status-att      = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" /
2481 ;;                     "UNSEEN"
2482
2483 (defun imap-parse-status ()
2484   (let ((mailbox (imap-parse-mailbox)))
2485     (if (eq (char-after) ? )
2486         (forward-char))
2487     (when (and mailbox (eq (char-after) ?\())
2488       (while (and (not (eq (char-after) ?\)))
2489                   (or (forward-char) t)
2490                   (looking-at "\\([A-Za-z]+\\) "))
2491         (let ((token (match-string 1)))
2492           (goto-char (match-end 0))
2493           (cond ((string= token "MESSAGES")
2494                  (imap-mailbox-put 'messages (read (current-buffer)) mailbox))
2495                 ((string= token "RECENT")
2496                  (imap-mailbox-put 'recent (read (current-buffer)) mailbox))
2497                 ((string= token "UIDNEXT")
2498                  (and (looking-at "[0-9]+")
2499                       (imap-mailbox-put 'uidnext (match-string 0) mailbox)
2500                       (goto-char (match-end 0))))
2501                 ((string= token "UIDVALIDITY")
2502                  (and (looking-at "[0-9]+")
2503                       (imap-mailbox-put 'uidvalidity (match-string 0) mailbox)
2504                       (goto-char (match-end 0))))
2505                 ((string= token "UNSEEN")
2506                  (imap-mailbox-put 'unseen (read (current-buffer)) mailbox))
2507                 (t
2508                  (message "Unknown status data %s in mailbox %s ignored"
2509                           token mailbox)
2510                  (read (current-buffer)))))))))
2511
2512 ;;   acl_data        ::= "ACL" SPACE mailbox *(SPACE identifier SPACE
2513 ;;                        rights)
2514 ;;
2515 ;;   identifier      ::= astring
2516 ;;
2517 ;;   rights          ::= astring
2518
2519 (defun imap-parse-acl ()
2520   (let ((mailbox (imap-parse-mailbox))
2521         identifier rights acl)
2522     (while (eq (char-after) ?\ )
2523       (imap-forward)
2524       (setq identifier (imap-parse-astring))
2525       (imap-forward)
2526       (setq rights (imap-parse-astring))
2527       (setq acl (append acl (list (cons identifier rights)))))
2528     (imap-mailbox-put 'acl acl mailbox)))
2529
2530 ;;   flag-list       = "(" [flag *(SP flag)] ")"
2531 ;;
2532 ;;   flag            = "\Answered" / "\Flagged" / "\Deleted" /
2533 ;;                     "\Seen" / "\Draft" / flag-keyword / flag-extension
2534 ;;                       ; Does not include "\Recent"
2535 ;;
2536 ;;   flag-keyword    = atom
2537 ;;
2538 ;;   flag-extension  = "\" atom
2539 ;;                       ; Future expansion.  Client implementations
2540 ;;                       ; MUST accept flag-extension flags.  Server
2541 ;;                       ; implementations MUST NOT generate
2542 ;;                       ; flag-extension flags except as defined by
2543 ;;                       ; future standard or standards-track
2544 ;;                       ; revisions of this specification.
2545
2546 (defun imap-parse-flag-list ()
2547   (let (flag-list start)
2548     (assert (eq (char-after) ?\() nil "In imap-parse-flag-list")
2549     (while (and (not (eq (char-after) ?\)))
2550                 (setq start (progn
2551                               (imap-forward)
2552                               ;; next line for Courier IMAP bug.
2553                               (skip-chars-forward " ")
2554                               (point)))
2555                 (> (skip-chars-forward "^ )" (point-at-eol)) 0))
2556       (push (buffer-substring start (point)) flag-list))
2557     (assert (eq (char-after) ?\)) nil "In imap-parse-flag-list")
2558     (imap-forward)
2559     (nreverse flag-list)))
2560
2561 ;;   envelope        = "(" env-date SP env-subject SP env-from SP env-sender SP
2562 ;;                     env-reply-to SP env-to SP env-cc SP env-bcc SP
2563 ;;                     env-in-reply-to SP env-message-id ")"
2564 ;;
2565 ;;   env-bcc         = "(" 1*address ")" / nil
2566 ;;
2567 ;;   env-cc          = "(" 1*address ")" / nil
2568 ;;
2569 ;;   env-date        = nstring
2570 ;;
2571 ;;   env-from        = "(" 1*address ")" / nil
2572 ;;
2573 ;;   env-in-reply-to = nstring
2574 ;;
2575 ;;   env-message-id  = nstring
2576 ;;
2577 ;;   env-reply-to    = "(" 1*address ")" / nil
2578 ;;
2579 ;;   env-sender      = "(" 1*address ")" / nil
2580 ;;
2581 ;;   env-subject     = nstring
2582 ;;
2583 ;;   env-to          = "(" 1*address ")" / nil
2584
2585 (defun imap-parse-envelope ()
2586   (when (eq (char-after) ?\()
2587     (imap-forward)
2588     (vector (prog1 (imap-parse-nstring) ;; date
2589               (imap-forward))
2590             (prog1 (imap-parse-nstring) ;; subject
2591               (imap-forward))
2592             (prog1 (imap-parse-address-list) ;; from
2593               (imap-forward))
2594             (prog1 (imap-parse-address-list) ;; sender
2595               (imap-forward))
2596             (prog1 (imap-parse-address-list) ;; reply-to
2597               (imap-forward))
2598             (prog1 (imap-parse-address-list) ;; to
2599               (imap-forward))
2600             (prog1 (imap-parse-address-list) ;; cc
2601               (imap-forward))
2602             (prog1 (imap-parse-address-list) ;; bcc
2603               (imap-forward))
2604             (prog1 (imap-parse-nstring) ;; in-reply-to
2605               (imap-forward))
2606             (prog1 (imap-parse-nstring) ;; message-id
2607               (imap-forward)))))
2608
2609 ;;   body-fld-param  = "(" string SP string *(SP string SP string) ")" / nil
2610
2611 (defsubst imap-parse-string-list ()
2612   (cond ((eq (char-after) ?\() ;; body-fld-param
2613          (let (strlist str)
2614            (imap-forward)
2615            (while (setq str (imap-parse-string))
2616              (push str strlist)
2617              ;; buggy stalker communigate pro 3.0 doesn't print SPC
2618              ;; between body-fld-param's sometimes
2619              (or (eq (char-after) ?\")
2620                  (imap-forward)))
2621            (nreverse strlist)))
2622         ((imap-parse-nil)
2623          nil)))
2624
2625 ;;   body-extension  = nstring / number /
2626 ;;                      "(" body-extension *(SP body-extension) ")"
2627 ;;                       ; Future expansion.  Client implementations
2628 ;;                       ; MUST accept body-extension fields.  Server
2629 ;;                       ; implementations MUST NOT generate
2630 ;;                       ; body-extension fields except as defined by
2631 ;;                       ; future standard or standards-track
2632 ;;                       ; revisions of this specification.
2633
2634 (defun imap-parse-body-extension ()
2635   (if (eq (char-after) ?\()
2636       (let (b-e)
2637         (imap-forward)
2638         (push (imap-parse-body-extension) b-e)
2639         (while (eq (char-after) ?\ )
2640           (imap-forward)
2641           (push (imap-parse-body-extension) b-e))
2642         (assert (eq (char-after) ?\)) nil "In imap-parse-body-extension")
2643         (imap-forward)
2644         (nreverse b-e))
2645     (or (imap-parse-number)
2646         (imap-parse-nstring))))
2647
2648 ;;   body-ext-1part  = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang
2649 ;;                     *(SP body-extension)]]
2650 ;;                       ; MUST NOT be returned on non-extensible
2651 ;;                       ; "BODY" fetch
2652 ;;
2653 ;;   body-ext-mpart  = body-fld-param [SP body-fld-dsp [SP body-fld-lang
2654 ;;                     *(SP body-extension)]]
2655 ;;                       ; MUST NOT be returned on non-extensible
2656 ;;                       ; "BODY" fetch
2657
2658 (defsubst imap-parse-body-ext ()
2659   (let (ext)
2660     (when (eq (char-after) ?\ ) ;; body-fld-dsp
2661       (imap-forward)
2662       (let (dsp)
2663         (if (eq (char-after) ?\()
2664             (progn
2665               (imap-forward)
2666               (push (imap-parse-string) dsp)
2667               (imap-forward)
2668               (push (imap-parse-string-list) dsp)
2669               (imap-forward))
2670           ;; With assert, the code might not be eval'd.
2671           ;; (assert (imap-parse-nil) t "In imap-parse-body-ext")
2672           (imap-parse-nil))
2673         (push (nreverse dsp) ext))
2674       (when (eq (char-after) ?\ ) ;; body-fld-lang
2675         (imap-forward)
2676         (if (eq (char-after) ?\()
2677             (push (imap-parse-string-list) ext)
2678           (push (imap-parse-nstring) ext))
2679         (while (eq (char-after) ?\ ) ;; body-extension
2680           (imap-forward)
2681           (setq ext (append (imap-parse-body-extension) ext)))))
2682     ext))
2683
2684 ;;   body            = "(" body-type-1part / body-type-mpart ")"
2685 ;;
2686 ;;   body-ext-1part  = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang
2687 ;;                     *(SP body-extension)]]
2688 ;;                       ; MUST NOT be returned on non-extensible
2689 ;;                       ; "BODY" fetch
2690 ;;
2691 ;;   body-ext-mpart  = body-fld-param [SP body-fld-dsp [SP body-fld-lang
2692 ;;                     *(SP body-extension)]]
2693 ;;                       ; MUST NOT be returned on non-extensible
2694 ;;                       ; "BODY" fetch
2695 ;;
2696 ;;   body-fields     = body-fld-param SP body-fld-id SP body-fld-desc SP
2697 ;;                     body-fld-enc SP body-fld-octets
2698 ;;
2699 ;;   body-fld-desc   = nstring
2700 ;;
2701 ;;   body-fld-dsp    = "(" string SP body-fld-param ")" / nil
2702 ;;
2703 ;;   body-fld-enc    = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
2704 ;;                     "QUOTED-PRINTABLE") DQUOTE) / string
2705 ;;
2706 ;;   body-fld-id     = nstring
2707 ;;
2708 ;;   body-fld-lang   = nstring / "(" string *(SP string) ")"
2709 ;;
2710 ;;   body-fld-lines  = number
2711 ;;
2712 ;;   body-fld-md5    = nstring
2713 ;;
2714 ;;   body-fld-octets = number
2715 ;;
2716 ;;   body-fld-param  = "(" string SP string *(SP string SP string) ")" / nil
2717 ;;
2718 ;;   body-type-1part = (body-type-basic / body-type-msg / body-type-text)
2719 ;;                     [SP body-ext-1part]
2720 ;;
2721 ;;   body-type-basic = media-basic SP body-fields
2722 ;;                       ; MESSAGE subtype MUST NOT be "RFC822"
2723 ;;
2724 ;;   body-type-msg   = media-message SP body-fields SP envelope
2725 ;;                     SP body SP body-fld-lines
2726 ;;
2727 ;;   body-type-text  = media-text SP body-fields SP body-fld-lines
2728 ;;
2729 ;;   body-type-mpart = 1*body SP media-subtype
2730 ;;                     [SP body-ext-mpart]
2731 ;;
2732 ;;   media-basic     = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" /
2733 ;;                     "MESSAGE" / "VIDEO") DQUOTE) / string) SP media-subtype
2734 ;;                       ; Defined in [MIME-IMT]
2735 ;;
2736 ;;   media-message   = DQUOTE "MESSAGE" DQUOTE SP DQUOTE "RFC822" DQUOTE
2737 ;;                      ; Defined in [MIME-IMT]
2738 ;;
2739 ;;   media-subtype   = string
2740 ;;                       ; Defined in [MIME-IMT]
2741 ;;
2742 ;;   media-text      = DQUOTE "TEXT" DQUOTE SP media-subtype
2743 ;;                       ; Defined in [MIME-IMT]
2744
2745 (defun imap-parse-body ()
2746   (let (body)
2747     (when (eq (char-after) ?\()
2748       (imap-forward)
2749       (if (eq (char-after) ?\()
2750           (let (subbody)
2751             (while (and (eq (char-after) ?\()
2752                         (setq subbody (imap-parse-body)))
2753              ;; buggy stalker communigate pro 3.0 insert a SPC between
2754               ;; parts in multiparts
2755               (when (and (eq (char-after) ?\ )
2756                          (eq (char-after (1+ (point))) ?\())
2757                 (imap-forward))
2758               (push subbody body))
2759             (imap-forward)
2760             (push (imap-parse-string) body) ;; media-subtype
2761             (when (eq (char-after) ?\ ) ;; body-ext-mpart:
2762               (imap-forward)
2763               (if (eq (char-after) ?\() ;; body-fld-param
2764                   (push (imap-parse-string-list) body)
2765                 (push (and (imap-parse-nil) nil) body))
2766               (setq body
2767                     (append (imap-parse-body-ext) body))) ;; body-ext-...
2768             (assert (eq (char-after) ?\)) nil "In imap-parse-body")
2769             (imap-forward)
2770             (nreverse body))
2771
2772         (push (imap-parse-string) body) ;; media-type
2773         (imap-forward)
2774         (push (imap-parse-string) body) ;; media-subtype
2775         (imap-forward)
2776         ;; next line for Sun SIMS bug
2777         (and (eq (char-after) ? ) (imap-forward))
2778         (if (eq (char-after) ?\() ;; body-fld-param
2779             (push (imap-parse-string-list) body)
2780           (push (and (imap-parse-nil) nil) body))
2781         (imap-forward)
2782         (push (imap-parse-nstring) body) ;; body-fld-id
2783         (imap-forward)
2784         (push (imap-parse-nstring) body) ;; body-fld-desc
2785         (imap-forward)
2786         ;; next `or' for Sun SIMS bug, it regard body-fld-enc as a
2787         ;; nstring and return nil instead of defaulting back to 7BIT
2788         ;; as the standard says.
2789         (push (or (imap-parse-nstring) "7BIT") body) ;; body-fld-enc
2790         (imap-forward)
2791         (push (imap-parse-number) body) ;; body-fld-octets
2792
2793    ;; ok, we're done parsing the required parts, what comes now is one
2794         ;; of three things:
2795         ;;
2796         ;; envelope       (then we're parsing body-type-msg)
2797         ;; body-fld-lines (then we're parsing body-type-text)
2798         ;; body-ext-1part (then we're parsing body-type-basic)
2799         ;;
2800   ;; the problem is that the two first are in turn optionally followed
2801 ;; by the third.  So we parse the first two here (if there are any)...
2802
2803         (when (eq (char-after) ?\ )
2804           (imap-forward)
2805           (let (lines)
2806             (cond ((eq (char-after) ?\() ;; body-type-msg:
2807                    (push (imap-parse-envelope) body) ;; envelope
2808                    (imap-forward)
2809                    (push (imap-parse-body) body) ;; body
2810                    ;; buggy stalker communigate pro 3.0 doesn't print
2811                    ;; number of lines in message/rfc822 attachment
2812                    (if (eq (char-after) ?\))
2813                        (push 0 body)
2814                      (imap-forward)
2815                      (push (imap-parse-number) body))) ;; body-fld-lines
2816                   ((setq lines (imap-parse-number)) ;; body-type-text:
2817                    (push lines body)) ;; body-fld-lines
2818                   (t
2819                    (backward-char))))) ;; no match...
2820
2821         ;; ...and then parse the third one here...
2822
2823         (when (eq (char-after) ?\ ) ;; body-ext-1part:
2824           (imap-forward)
2825           (push (imap-parse-nstring) body) ;; body-fld-md5
2826           (setq body (append (imap-parse-body-ext) body))) ;; body-ext-1part..
2827
2828         (assert (eq (char-after) ?\)) nil "In imap-parse-body 2")
2829         (imap-forward)
2830         (nreverse body)))))
2831
2832 (when imap-debug                        ; (untrace-all)
2833   (require 'trace)
2834   (buffer-disable-undo (get-buffer-create imap-debug-buffer))
2835   (mapcar (lambda (f) (trace-function-background f imap-debug-buffer))
2836           '(
2837             imap-utf7-encode
2838             imap-utf7-decode
2839             imap-error-text
2840             imap-kerberos4s-p
2841             imap-kerberos4-open
2842             imap-ssl-p
2843             imap-ssl-open
2844             imap-network-p
2845             imap-network-open
2846             imap-interactive-login
2847             imap-kerberos4a-p
2848             imap-kerberos4-auth
2849             imap-cram-md5-p
2850             imap-cram-md5-auth
2851             imap-login-p
2852             imap-login-auth
2853             imap-anonymous-p
2854             imap-anonymous-auth
2855             imap-open-1
2856             imap-open
2857             imap-opened
2858             imap-authenticate
2859             imap-close
2860             imap-capability
2861             imap-namespace
2862             imap-send-command-wait
2863             imap-mailbox-put
2864             imap-mailbox-get
2865             imap-mailbox-map-1
2866             imap-mailbox-map
2867             imap-current-mailbox
2868             imap-current-mailbox-p-1
2869             imap-current-mailbox-p
2870             imap-mailbox-select-1
2871             imap-mailbox-select
2872             imap-mailbox-examine-1
2873             imap-mailbox-examine
2874             imap-mailbox-unselect
2875             imap-mailbox-expunge
2876             imap-mailbox-close
2877             imap-mailbox-create-1
2878             imap-mailbox-create
2879             imap-mailbox-delete
2880             imap-mailbox-rename
2881             imap-mailbox-lsub
2882             imap-mailbox-list
2883             imap-mailbox-subscribe
2884             imap-mailbox-unsubscribe
2885             imap-mailbox-status
2886             imap-mailbox-acl-get
2887             imap-mailbox-acl-set
2888             imap-mailbox-acl-delete
2889             imap-current-message
2890             imap-list-to-message-set
2891             imap-fetch-asynch
2892             imap-fetch
2893             imap-message-put
2894             imap-message-get
2895             imap-message-map
2896             imap-search
2897             imap-message-flag-permanent-p
2898             imap-message-flags-set
2899             imap-message-flags-del
2900             imap-message-flags-add
2901             imap-message-copyuid-1
2902             imap-message-copyuid
2903             imap-message-copy
2904             imap-message-appenduid-1
2905             imap-message-appenduid
2906             imap-message-append
2907             imap-body-lines
2908             imap-envelope-from
2909             imap-send-command-1
2910             imap-send-command
2911             imap-wait-for-tag
2912             imap-sentinel
2913             imap-find-next-line
2914             imap-arrival-filter
2915             imap-parse-greeting
2916             imap-parse-response
2917             imap-parse-resp-text
2918             imap-parse-resp-text-code
2919             imap-parse-data-list
2920             imap-parse-fetch
2921             imap-parse-status
2922             imap-parse-acl
2923             imap-parse-flag-list
2924             imap-parse-envelope
2925             imap-parse-body-extension
2926             imap-parse-body
2927             )))
2928
2929 (provide 'imap)
2930
2931 ;;; arch-tag: 27369ed6-33e4-482f-96f1-8bb906ba70f7
2932 ;;; imap.el ends here