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