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