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