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