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