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