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