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