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