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