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