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