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