* elmo-util.el (elmo-quoted-specials-list): New constant.
[elisp/wanderlust.git] / elmo / elmo-pop3.el
1 ;;; elmo-pop3.el --- POP3 Interface for ELMO.
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Copyright (C) 1999,2000      Kenichi OKADA <okada@opaopa.org>
5
6 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
7 ;;      Kenichi OKADA <okada@opaopa.org>
8 ;; Keywords: mail, net news
9
10 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
11
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16 ;;
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21 ;;
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26 ;;
27
28 ;;; Commentary:
29 ;;
30
31 ;;; Code:
32 ;;
33
34 (require 'elmo-msgdb)
35 (require 'elmo-net)
36
37 (eval-when-compile
38   (require 'elmo-util))
39
40 (eval-and-compile
41   (autoload 'md5 "md5"))
42
43 (defcustom elmo-pop3-default-use-uidl t
44   "If non-nil, use UIDL on POP3."
45   :type 'boolean
46   :group 'elmo)
47
48 (defvar elmo-pop3-use-uidl-internal t
49   "(Internal switch for using UIDL on POP3).")
50
51 (defvar elmo-pop3-use-cache t
52   "Use cache in pop3 folder.")
53
54 (defvar elmo-pop3-send-command-synchronously nil
55   "If non-nil, commands are send synchronously.
56 If server doesn't accept asynchronous commands, this variable should be
57 set as non-nil.")
58
59 (defcustom elmo-pop3-exists-exactly nil
60   "If non-nil, POP3 folder existence is checked everytime before the session."
61   :type 'boolean
62   :group 'elmo)
63
64 (defvar sasl-mechanism-alist)
65
66 (defvar elmo-pop3-total-size nil)
67
68 ;; For debugging.
69 (defvar elmo-pop3-debug nil
70   "Non-nil forces POP3 folder as debug mode.
71 Debug information is inserted in the buffer \"*POP3 DEBUG*\"")
72
73 ;;; Debug
74 (defsubst elmo-pop3-debug (message &rest args)
75   (if elmo-pop3-debug
76       (let ((biff (string-match "BIFF-" (buffer-name)))
77             pos)
78         (with-current-buffer (get-buffer-create (concat "*POP3 DEBUG*"
79                                                         (if biff "BIFF")))
80           (goto-char (point-max))
81           (setq pos (point))
82           (insert (apply 'format message args) "\n")))))
83
84 ;;; ELMO POP3 folder
85 (eval-and-compile
86   (luna-define-class elmo-pop3-folder (elmo-net-folder)
87                      (use-uidl location-alist))
88   (luna-define-internal-accessors 'elmo-pop3-folder))
89
90 (luna-define-method elmo-folder-initialize ((folder elmo-pop3-folder) name)
91   (let ((elmo-network-stream-type-alist
92          (if elmo-pop3-stream-type-alist
93              (append elmo-pop3-stream-type-alist
94                      elmo-network-stream-type-alist)
95            elmo-network-stream-type-alist))
96         parse)
97     ;; user
98     (setq parse (elmo-parse-token name "/:@"))
99     (elmo-net-folder-set-user-internal folder
100                                        (if (eq (length (car parse)) 0)
101                                            elmo-pop3-default-user
102                                          (car parse)))
103     ;; auth
104     (setq parse (elmo-parse-prefixed-element ?/ (cdr parse) ":@"))
105     (elmo-net-folder-set-auth-internal folder
106                                        (if (eq (length (car parse)) 0)
107                                            elmo-pop3-default-authenticate-type
108                                          (intern (downcase (car parse)))))
109     ;; uidl
110     (setq parse (elmo-parse-prefixed-element ?: (cdr parse) "@"))
111     (elmo-pop3-folder-set-use-uidl-internal folder
112                                             (if (eq (length (car parse)) 0)
113                                                 elmo-pop3-default-use-uidl
114                                               (string= (car parse) "uidl")))
115     ;; network
116     (elmo-net-parse-network folder (cdr parse))
117     (unless (elmo-net-folder-server-internal folder)
118       (elmo-net-folder-set-server-internal folder
119                                            elmo-pop3-default-server))
120     (unless (elmo-net-folder-port-internal folder)
121       (elmo-net-folder-set-port-internal folder
122                                          elmo-pop3-default-port))
123     (unless (elmo-net-folder-stream-type-internal folder)
124       (elmo-net-folder-set-stream-type-internal
125        folder
126        (elmo-get-network-stream-type
127         elmo-pop3-default-stream-type)))
128     folder))
129
130 ;;; POP3 session
131 (luna-define-class elmo-pop3-session (elmo-network-session) ())
132
133 ;; buffer-local
134 (defvar elmo-pop3-read-point nil)
135 (defvar elmo-pop3-number-uidl-hash nil) ; number -> uidl
136 (defvar elmo-pop3-uidl-number-hash nil) ; uidl -> number
137 (defvar elmo-pop3-size-hash nil) ; number -> size
138 (defvar elmo-pop3-uidl-done nil)
139 (defvar elmo-pop3-list-done nil)
140 (defvar elmo-pop3-lock nil)
141
142 (defvar elmo-pop3-local-variables '(elmo-pop3-read-point
143                                     elmo-pop3-uidl-number-hash
144                                     elmo-pop3-number-uidl-hash
145                                     elmo-pop3-uidl-done
146                                     elmo-pop3-size-hash
147                                     elmo-pop3-list-done
148                                     elmo-pop3-lock))
149
150 (luna-define-method elmo-network-close-session ((session elmo-pop3-session))
151   (when (elmo-network-session-process-internal session)
152     (when (memq (process-status
153                  (elmo-network-session-process-internal session))
154                 '(open run))
155       (elmo-pop3-send-command (elmo-network-session-process-internal session)
156                               "quit")
157       ;; process is dead.
158       (or (cdr (elmo-pop3-read-response
159                 (elmo-network-session-process-internal session)
160                 t))
161           (error "POP error: QUIT failed")))
162     (kill-buffer (process-buffer
163                   (elmo-network-session-process-internal session)))
164     (delete-process (elmo-network-session-process-internal session))))
165
166 (defun elmo-pop3-get-session (folder &optional if-exists)
167   "Get POP3 session for FOLDER.
168 If IF-EXISTS is non-nil, don't get new session.
169 If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
170   (let ((elmo-pop3-use-uidl-internal (if elmo-inhibit-number-mapping
171                                          nil
172                                        (elmo-pop3-folder-use-uidl-internal
173                                         folder))))
174     (prog1
175         (if (eq if-exists 'any-exists)
176             (or (elmo-network-get-session 'elmo-pop3-session
177                                           "POP3"
178                                           folder if-exists)
179                 (elmo-network-get-session 'elmo-pop3-session
180                                           "BIFF-POP3"
181                                           folder if-exists))
182           (elmo-network-get-session 'elmo-pop3-session
183                                     (concat
184                                      (if (elmo-folder-biff-internal folder)
185                                          "BIFF-")
186                                      "POP3")
187                                     folder if-exists))
188       ;; For saving existency.
189       (unless (file-exists-p (elmo-folder-msgdb-path folder))
190         (elmo-make-directory (elmo-folder-msgdb-path folder))))))
191
192 (defun elmo-pop3-send-command (process command &optional no-erase no-log)
193   (with-current-buffer (process-buffer process)
194     (unless no-erase
195       (erase-buffer))
196     (goto-char (point-min))
197     (setq elmo-pop3-read-point (point))
198     (elmo-pop3-debug "SEND: %s\n" (if no-log "<NO LOGGING>" command))
199     (process-send-string process (concat command "\r\n"))))
200
201 (defun elmo-pop3-read-response (process &optional not-command)
202   "Read response and return a cons cell of \(CODE . BODY\).
203 PROCESS is the process to read response from.
204 If optional NOT-COMMAND is non-nil, read only the first line.
205 CODE is one of the following:
206 'ok          ... response is OK.
207 'err         ... response is ERROR.
208 'login-delay ... user is not allowed to login until the login delay
209                  period has expired.
210 'in-use      ... authentication was successful but the mailbox is in use."
211   ;; buffer is in case for process is dead.
212   (with-current-buffer (process-buffer process)
213     (let ((case-fold-search nil)
214           (response-string nil)
215           (response-continue t)
216           (return-value nil)
217           (err nil)
218           match-end)
219       (while response-continue
220         (goto-char elmo-pop3-read-point)
221         (while (not (re-search-forward "\r?\n" nil t))
222           (accept-process-output process 1)
223           (goto-char elmo-pop3-read-point))
224         (setq match-end (point))
225         (setq response-string
226               (buffer-substring elmo-pop3-read-point (- match-end 2)))
227         (goto-char elmo-pop3-read-point)
228         (if (looking-at "\\+.*$")
229             (progn
230               (setq response-continue nil)
231               (setq elmo-pop3-read-point match-end)
232               (setq return-value
233                     (if return-value
234                         (concat return-value "\n" response-string)
235                       response-string)))
236           (if (looking-at "\\-.*$")
237               (progn
238                 (when (looking-at "[^ ]+ \\[\\([^]]+\\)\\]")
239                   (setq return-value
240                         (intern
241                          (downcase
242                           (buffer-substring (match-beginning 1)
243                                             (match-end 1))))))
244                 (setq err t
245                       response-continue nil
246                       elmo-pop3-read-point match-end
247                       return-value (cons (or return-value 'err) nil)))
248             (setq elmo-pop3-read-point match-end)
249             (if not-command
250                 (setq response-continue nil))
251             (setq return-value
252                   (if return-value
253                       (concat return-value "\n" response-string)
254                     response-string)))
255           (setq elmo-pop3-read-point match-end)))
256       (if err
257           return-value
258         (cons 'ok return-value)))))
259
260 (defun elmo-pop3-process-filter (process output)
261   (when (buffer-live-p (process-buffer process))
262     (with-current-buffer (process-buffer process)
263       (goto-char (point-max))
264       (insert output)
265       (elmo-pop3-debug "RECEIVED: %s\n" output)
266       (if (and elmo-pop3-total-size
267                (> elmo-pop3-total-size
268                   (min elmo-display-retrieval-progress-threshold 100)))
269           (elmo-display-progress
270            'elmo-display-retrieval-progress
271            (format "Retrieving (%d/%d bytes)..."
272                    (buffer-size)
273                    elmo-pop3-total-size)
274            (/ (buffer-size) (/ elmo-pop3-total-size 100)))))))
275
276 (defun elmo-pop3-auth-user (session)
277   (let ((process (elmo-network-session-process-internal session))
278         response)
279     ;; try USER/PASS
280     (elmo-pop3-send-command
281      process
282      (format "user %s" (elmo-network-session-user-internal session))
283      nil 'no-log)
284     (setq response (elmo-pop3-read-response process t))
285     (unless (eq (car response) 'ok)
286       (delete-process process)
287       (signal 'elmo-open-error '(elmo-pop-auth-user)))
288     (elmo-pop3-send-command  process
289                              (format
290                               "pass %s"
291                               (elmo-get-passwd
292                                (elmo-network-session-password-key session)))
293                              nil 'no-log)
294     (setq response (elmo-pop3-read-response process t))
295     (unless (eq (car response) 'ok)
296       (delete-process process)
297       (unless (eq 'ok (car response))
298         (if (or (eq (car response) 'in-use)
299                 (eq (car response) 'login-delay))
300             (error (cond ((eq (car response) 'in-use)
301                           "Maildrop is currently in use")
302                          ((eq (car response) 'login-delay)
303                           "Not allowed to login \
304 until the login delay period has expired")))
305           (signal 'elmo-authenticate-error
306                   '(elmo-pop-auth-user)))))
307     (car response)))
308
309 (defun elmo-pop3-auth-apop (session)
310   (if (string-match "^\+OK .*\\(<[^\>]+>\\)"
311                     (elmo-network-session-greeting-internal session))
312       ;; good, APOP ready server
313       (progn
314         (elmo-pop3-send-command
315          (elmo-network-session-process-internal session)
316          (format "apop %s %s"
317                  (elmo-network-session-user-internal session)
318                  (md5
319                   (concat (match-string
320                            1
321                            (elmo-network-session-greeting-internal session))
322                           (elmo-get-passwd
323                            (elmo-network-session-password-key session)))))
324          nil 'no-log)
325         (let ((response (elmo-pop3-read-response
326                          (elmo-network-session-process-internal session)
327                          t)))
328           (unless (eq (car response) 'ok)
329             (delete-process (elmo-network-session-process-internal session))
330             (unless (eq 'ok (car response))
331               (if (or (eq (car response) 'in-use)
332                       (eq (car response) 'login-delay))
333                   (error (cond ((eq (car response) 'in-use)
334                                 "Maildrop is currently in use")
335                                ((eq (car response) 'login-delay)
336                                 "Not allowed to login \
337 until the login delay period has expired")))
338                 (signal 'elmo-authenticate-error
339                         '(elmo-pop-auth-apop)))))
340           (car response)))
341     (signal 'elmo-open-error '(elmo-pop3-auth-apop))))
342
343 (luna-define-method elmo-network-initialize-session-buffer :after
344   ((session elmo-pop3-session) buffer)
345   (with-current-buffer buffer
346     (mapcar 'make-variable-buffer-local elmo-pop3-local-variables)))
347
348 (luna-define-method elmo-network-initialize-session ((session
349                                                       elmo-pop3-session))
350   (let ((process (elmo-network-session-process-internal session))
351         response mechanism)
352     (with-current-buffer (process-buffer process)
353       (set-process-filter process 'elmo-pop3-process-filter)
354       (setq elmo-pop3-read-point (point-min))
355       ;; Skip garbage output from process before greeting.
356       (while (and (memq (process-status process) '(open run))
357                   (goto-char (point-max))
358                   (forward-line -1)
359                   (not (looking-at "+OK")))
360         (accept-process-output process 1))
361       (setq elmo-pop3-read-point (point))
362       (or (elmo-network-session-set-greeting-internal
363            session
364            (cdr (elmo-pop3-read-response process t))) ; if ok, cdr is non-nil.
365           (signal 'elmo-open-error
366                   '(elmo-network-intialize-session)))
367       (when (eq (elmo-network-stream-type-symbol
368                  (elmo-network-session-stream-type-internal session))
369                 'starttls)
370         (elmo-pop3-send-command process "stls")
371         (if (eq 'ok (car (elmo-pop3-read-response process)))
372             (starttls-negotiate process)
373           (signal 'elmo-open-error '(elmo-pop3-starttls-error)))))))
374
375 (luna-define-method elmo-network-authenticate-session ((session
376                                                         elmo-pop3-session))
377   (with-current-buffer (process-buffer
378                         (elmo-network-session-process-internal session))
379     (let* ((process (elmo-network-session-process-internal session))
380            (auth (elmo-network-session-auth-internal session))
381            (auth (mapcar '(lambda (mechanism) (upcase (symbol-name mechanism)))
382                          (if (listp auth) auth (list auth))))
383            sasl-mechanisms
384            client name step response mechanism
385            sasl-read-passphrase)
386       (or (and (string= "USER" (car auth))
387                (elmo-pop3-auth-user session))
388           (and (string= "APOP" (car auth))
389                (elmo-pop3-auth-apop session))
390           (progn
391             (require 'sasl)
392             (setq sasl-mechanisms (mapcar 'car sasl-mechanism-alist))
393             (setq mechanism (sasl-find-mechanism auth))
394             (unless mechanism
395               (signal 'elmo-authenticate-error '(elmo-pop3-auth-no-mechanisms)))
396             (setq client
397                   (sasl-make-client
398                    mechanism
399                    (elmo-network-session-user-internal session)
400                    "pop"
401                    (elmo-network-session-server-internal session)))
402 ;;;         (if elmo-pop3-auth-user-realm
403 ;;;             (sasl-client-set-property client 'realm elmo-pop3-auth-user-realm))
404             (setq name (sasl-mechanism-name mechanism))
405             (elmo-network-session-set-auth-internal session
406                                                     (intern (downcase name)))
407             (setq sasl-read-passphrase
408                   (function
409                    (lambda (prompt)
410                      (elmo-get-passwd
411                       (elmo-network-session-password-key session)))))
412             (setq step (sasl-next-step client nil))
413             (elmo-pop3-send-command
414              process
415              (concat "AUTH " name
416                      (and (sasl-step-data step)
417                           (concat
418                            " "
419                            (elmo-base64-encode-string
420                             (sasl-step-data step) 'no-line-break))))
421              nil 'no-log)
422             (catch 'done
423               (while t
424                 (setq response (elmo-pop3-read-response process t))
425                 (unless (eq 'ok (car response))
426                   (if (or (eq (car response) 'in-use)
427                           (eq (car response) 'login-delay))
428                       (error (cond ((eq (car response) 'in-use)
429                                     "Maildrop is currently in use")
430                                    ((eq (car response) 'login-delay)
431                                     "Not allowed to login \
432 until the login delay period has expired")))
433                     (signal 'elmo-authenticate-error
434                             (list (intern (concat "elmo-pop3-auth-"
435                                                   (downcase name)))))))
436                 (if (sasl-next-step client step)
437                     ;; Bogus server?
438                     (signal 'elmo-authenticate-error
439                             (list (intern
440                                    (concat "elmo-pop3-auth-"
441                                            (downcase name)))))
442                   ;; The authentication process is finished.
443                   (throw 'done nil))
444                 (sasl-step-set-data
445                  step
446                  (elmo-base64-decode-string
447                   (cadr (split-string response " "))))
448                 (setq step (sasl-next-step client step))
449                 (elmo-pop3-send-command
450                  process
451                  (if (sasl-step-data step)
452                      (elmo-base64-encode-string (sasl-step-data step)
453                                                 'no-line-break)
454                    "") nil 'no-log))))))))
455
456 (luna-define-method elmo-network-setup-session ((session
457                                                  elmo-pop3-session))
458   (let ((process (elmo-network-session-process-internal session))
459         count response)
460     (with-current-buffer (process-buffer process)
461       (setq elmo-pop3-size-hash (elmo-make-hash 31))
462       ;; To get obarray of uidl and size
463       (elmo-pop3-send-command process "list")
464       (if (null (cdr (elmo-pop3-read-response process)))
465           (error "POP LIST command failed"))
466       (if (null (setq response
467                       (elmo-pop3-read-contents process)))
468           (error "POP LIST command failed"))
469       ;; POP server always returns a sequence of serial numbers.
470       (setq count (elmo-pop3-parse-list-response response))
471       ;; UIDL
472       (when elmo-pop3-use-uidl-internal
473         (setq elmo-pop3-uidl-number-hash (elmo-make-hash (* count 2)))
474         (setq elmo-pop3-number-uidl-hash (elmo-make-hash (* count 2)))
475         ;; UIDL
476         (elmo-pop3-send-command process "uidl")
477         (unless (cdr (elmo-pop3-read-response process))
478           (error "POP UIDL failed"))
479         (unless (setq response (elmo-pop3-read-contents process))
480           (error "POP UIDL failed"))
481         (elmo-pop3-parse-uidl-response response)))))
482
483 (defun elmo-pop3-read-contents (process)
484   (with-current-buffer (process-buffer process)
485     (let ((case-fold-search nil)
486           match-end)
487       (goto-char elmo-pop3-read-point)
488       (while (not (re-search-forward "^\\.\r\n" nil t))
489         (accept-process-output process 1)
490         (goto-char elmo-pop3-read-point))
491       (setq match-end (point))
492       (elmo-delete-cr
493        (buffer-substring elmo-pop3-read-point
494                          (- match-end 3))))))
495
496 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-pop3-folder))
497   (convert-standard-filename
498    (expand-file-name
499     (elmo-safe-filename (elmo-net-folder-user-internal folder))
500     (expand-file-name (elmo-net-folder-server-internal folder)
501                       (expand-file-name
502                        "pop"
503                        elmo-msgdb-directory)))))
504
505 (luna-define-method elmo-folder-exists-p ((folder elmo-pop3-folder))
506   (if (and elmo-pop3-exists-exactly
507            (elmo-folder-plugged-p folder))
508       (save-excursion
509         (let (elmo-auto-change-plugged  ; don't change plug status.
510               (elmo-inhibit-number-mapping t) ; No need to use uidl.
511               session)
512           (prog1
513               (setq session (elmo-pop3-get-session folder))
514             (if session
515                 (elmo-network-close-session session)))))
516     (or (file-directory-p (elmo-folder-msgdb-path folder))
517         ;; First time.
518         (when (elmo-folder-plugged-p folder)
519           (let ((elmo-pop3-exists-exactly t))
520             (elmo-folder-exists-p folder))))))
521
522 (defun elmo-pop3-parse-uidl-response (string)
523   (let ((buffer (current-buffer))
524         number list size)
525     (with-temp-buffer
526       (let (number uid list)
527         (insert string)
528         (goto-char (point-min))
529         (while (re-search-forward "^\\([0-9]+\\)[\t ]+\\([^ \n]+\\)$" nil t)
530           (setq number  (elmo-match-buffer 1))
531           (setq uid (elmo-match-buffer 2))
532           (with-current-buffer buffer
533             (elmo-set-hash-val uid number elmo-pop3-uidl-number-hash)
534             (elmo-set-hash-val (concat "#" number) uid
535                                elmo-pop3-number-uidl-hash))
536           (setq list (cons uid list)))
537         (with-current-buffer buffer (setq elmo-pop3-uidl-done t))
538         (nreverse list)))))
539
540 (defun elmo-pop3-parse-list-response (string)
541   (let ((buffer (current-buffer))
542         (count 0)
543         alist)
544     (with-temp-buffer
545       (insert string)
546       (goto-char (point-min))
547       (while (re-search-forward "^\\([0-9]+\\)[\t ]+\\([0-9]+\\)$" nil t)
548         (setq alist
549               (cons
550                (cons (elmo-match-buffer 1)
551                      (elmo-match-buffer 2))
552                alist))
553         (setq count (1+ count)))
554       (with-current-buffer buffer
555         (setq elmo-pop3-size-hash (elmo-make-hash (* (length alist) 2)))
556         (while alist
557           (elmo-set-hash-val (concat "#" (car (car alist)))
558                              (cdr (car alist))
559                              elmo-pop3-size-hash)
560           (setq alist (cdr alist)))
561         (setq elmo-pop3-list-done t))
562       count)))
563
564 (defun elmo-pop3-list-location (folder)
565   (with-current-buffer (process-buffer
566                         (elmo-network-session-process-internal
567                          (elmo-pop3-get-session folder)))
568     (let (locations)
569       (if elmo-pop3-uidl-done
570           (progn
571             (mapatoms
572              (lambda (atom)
573                (setq locations (cons (symbol-name atom) locations)))
574              elmo-pop3-uidl-number-hash)
575             (sort locations
576                   (lambda (loc1 loc2)
577                     (< (elmo-pop3-uidl-to-number loc1)
578                        (elmo-pop3-uidl-to-number loc2)))))
579         (error "POP3: Error in UIDL")))))
580
581 (defun elmo-pop3-list-folder-by-location (folder locations)
582   (let* ((location-alist (elmo-pop3-folder-location-alist-internal folder))
583          (locations-in-db (mapcar 'cdr location-alist))
584          result new-locs new-alist deleted-locs i)
585     (setq new-locs
586           (elmo-delete-if (function
587                            (lambda (x) (member x locations-in-db)))
588                           locations))
589     (setq deleted-locs
590           (elmo-delete-if (function
591                            (lambda (x) (member x locations)))
592                           locations-in-db))
593     (setq i (or (elmo-max-of-list (mapcar 'car location-alist)) 0))
594     (mapcar
595      (function
596       (lambda (x)
597         (setq location-alist
598               (delq (rassoc x location-alist) location-alist))))
599      deleted-locs)
600     (while new-locs
601       (setq i (1+ i))
602       (setq new-alist (cons (cons i (car new-locs)) new-alist))
603       (setq new-locs (cdr new-locs)))
604     (setq result (nconc location-alist new-alist))
605     (setq result (sort result (lambda (x y) (< (car x)(car y)))))
606     (elmo-pop3-folder-set-location-alist-internal folder result)
607     (mapcar 'car result)))
608
609 (defun elmo-pop3-list-by-uidl-subr (folder &optional nonsort)
610   (let ((flist (elmo-pop3-list-folder-by-location
611                 folder
612                 (elmo-pop3-list-location folder))))
613     (if nonsort
614         (cons (elmo-max-of-list flist) (length flist))
615       (sort flist '<))))
616
617 (defun elmo-pop3-list-by-list (folder)
618   (with-current-buffer (process-buffer
619                         (elmo-network-session-process-internal
620                          (elmo-pop3-get-session folder)))
621     (let (list)
622       (if elmo-pop3-list-done
623           (progn
624             (mapatoms (lambda (atom)
625                         (setq list (cons (string-to-int
626                                           (substring (symbol-name atom) 1))
627                                          list)))
628                       elmo-pop3-size-hash)
629             (sort list '<))
630         (error "POP3: Error in list")))))
631
632 (defsubst elmo-pop3-folder-list-messages (folder)
633   (if (and (not elmo-inhibit-number-mapping)
634            (elmo-pop3-folder-use-uidl-internal folder))
635       (elmo-pop3-list-by-uidl-subr folder)
636     (elmo-pop3-list-by-list folder)))
637
638 (luna-define-method elmo-folder-list-messages-plugged
639   ((folder elmo-pop3-folder) &optional nohide)
640   (elmo-pop3-folder-list-messages folder))
641
642 (luna-define-method elmo-folder-status ((folder elmo-pop3-folder))
643   (elmo-folder-open-internal folder)
644   (elmo-folder-check folder)
645   (if (elmo-pop3-folder-use-uidl-internal folder)
646       (prog1
647           (elmo-pop3-list-by-uidl-subr folder 'nonsort)
648         (elmo-folder-close-internal folder))
649     (let* ((process
650             (elmo-network-session-process-internal
651              (elmo-pop3-get-session folder)))
652            (total 0)
653            response)
654       (with-current-buffer (process-buffer process)
655         (elmo-pop3-send-command process "STAT")
656         (setq response (cdr (elmo-pop3-read-response process)))
657         ;; response: "^\+OK 2 7570$"
658         (if (not (string-match "^\+OK[ \t]*\\([0-9]*\\)" response))
659             (error "POP STAT command failed")
660           (setq total
661                 (string-to-int
662                  (substring response (match-beginning 1)(match-end 1 ))))
663           (elmo-folder-close-internal folder)
664           (cons total total))))))
665
666 (defvar elmo-pop3-header-fetch-chop-length 200)
667
668 (defsubst elmo-pop3-next-result-arrived-p ()
669   (cond
670    ((eq (following-char) ?+)
671     (if (re-search-forward "\n\\.\r?\n" nil t)
672         t
673       nil))
674    ((looking-at "-")
675     (if (search-forward "\n" nil t)
676         t
677       nil))
678    (t
679     nil)))
680
681 (defun elmo-pop3-retrieve-headers (process tobuffer articles)
682   (save-excursion
683     (set-buffer (process-buffer process))
684     (erase-buffer)
685     (let ((number (length articles))
686           (count 0)
687           (received 0)
688           (last-point (point-min)))
689       ;; Send HEAD commands.
690       (while articles
691         (elmo-pop3-send-command process (format
692                                          "top %s 0" (car articles))
693                                 'no-erase)
694 ;;;     (accept-process-output process 1)
695         (setq articles (cdr articles))
696         (setq count (1+ count))
697         ;; Every 200 requests we have to read the stream in
698         ;; order to avoid deadlocks.
699         (when (or elmo-pop3-send-command-synchronously
700                   (null articles)       ;All requests have been sent.
701                   (zerop (% count elmo-pop3-header-fetch-chop-length)))
702           (unless elmo-pop3-send-command-synchronously
703             (accept-process-output process 1))
704           (discard-input)
705           (while (progn
706                    (goto-char last-point)
707                    ;; Count replies.
708                    (while (elmo-pop3-next-result-arrived-p)
709                      (setq last-point (point))
710                      (setq received (1+ received)))
711                    (< received count))
712             (when (> number elmo-display-progress-threshold)
713               (if (or (zerop (% received 5)) (= received number))
714                   (elmo-display-progress
715                    'elmo-pop3-retrieve-headers "Getting headers..."
716                    (/ (* received 100) number))))
717             (accept-process-output process 1)
718 ;;;         (accept-process-output process)
719             (discard-input))))
720       ;; Replace all CRLF with LF.
721       (elmo-delete-cr-buffer)
722       (copy-to-buffer tobuffer (point-min) (point-max)))))
723
724 (luna-define-method elmo-folder-msgdb-create ((folder elmo-pop3-folder)
725                                               numlist flag-table)
726   (let ((process (elmo-network-session-process-internal
727                   (elmo-pop3-get-session folder))))
728     (with-current-buffer (process-buffer process)
729       (elmo-pop3-sort-msgdb-by-original-number
730        folder
731        (elmo-pop3-msgdb-create-by-header
732         folder
733         process
734         numlist
735         flag-table
736         (if (elmo-pop3-folder-use-uidl-internal folder)
737             (elmo-pop3-folder-location-alist-internal folder)))))))
738
739 (defun elmo-pop3-sort-msgdb-by-original-number (folder msgdb)
740   (let ((location-alist (elmo-pop3-folder-location-alist-internal folder)))
741     (when location-alist
742       (elmo-msgdb-sort-entities
743        msgdb
744        (lambda (ent1 ent2 loc-alist)
745          (< (elmo-pop3-uidl-to-number
746              (cdr (assq (elmo-message-entity-number ent1)
747                         loc-alist)))
748             (elmo-pop3-uidl-to-number
749              (cdr (assq (elmo-message-entity-number ent2)
750                         loc-alist)))))
751        location-alist))
752     msgdb))
753
754 (defun elmo-pop3-uidl-to-number (uidl)
755   (string-to-number (elmo-get-hash-val uidl
756                                        elmo-pop3-uidl-number-hash)))
757
758 (defun elmo-pop3-number-to-uidl (number)
759   (elmo-get-hash-val (format "#%d" number)
760                      elmo-pop3-number-uidl-hash))
761
762 (defun elmo-pop3-number-to-size (number)
763   (elmo-get-hash-val (format "#%d" number)
764                      elmo-pop3-size-hash))
765
766 (defun elmo-pop3-msgdb-create-by-header (folder process numlist
767                                                 flag-table
768                                                 loc-alist)
769   (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*")))
770     (with-current-buffer (process-buffer process)
771       (if loc-alist ; use uidl.
772           (setq numlist
773                 (delq
774                  nil
775                  (mapcar
776                   (lambda (number)
777                     (elmo-pop3-uidl-to-number (cdr (assq number loc-alist))))
778                   numlist))))
779       (elmo-pop3-retrieve-headers process tmp-buffer numlist)
780       (prog1
781           (elmo-pop3-msgdb-create-message
782            folder
783            tmp-buffer
784            process
785            (length numlist)
786            numlist
787            flag-table loc-alist)
788         (kill-buffer tmp-buffer)))))
789
790 (defun elmo-pop3-msgdb-create-message (folder
791                                        buffer
792                                        process
793                                        num
794                                        numlist
795                                        flag-table
796                                        loc-alist)
797   (save-excursion
798     (let ((new-msgdb (elmo-make-msgdb))
799           beg entity i number message-id flags)
800       (set-buffer buffer)
801       (set-buffer-multibyte default-enable-multibyte-characters)
802       (goto-char (point-min))
803       (setq i 0)
804       (message "Creating msgdb...")
805       (while (not (eobp))
806         (setq beg (save-excursion (forward-line 1) (point)))
807         (elmo-pop3-next-result-arrived-p)
808         (save-excursion
809           (forward-line -1)
810           (save-restriction
811             (narrow-to-region beg (point))
812             (setq entity
813                   (elmo-msgdb-create-message-entity-from-buffer
814                    (elmo-msgdb-message-entity-handler new-msgdb)
815                    (car numlist)))
816             (setq numlist (cdr numlist))
817             (when entity
818               (with-current-buffer (process-buffer process)
819                 (elmo-message-entity-set-field
820                  entity
821                  'size
822                  (string-to-number
823                   (elmo-pop3-number-to-size
824                    (elmo-message-entity-number entity))))
825                 (if (setq number
826                           (car
827                            (rassoc
828                             (elmo-pop3-number-to-uidl
829                              (elmo-message-entity-number entity))
830                             loc-alist)))
831                     (elmo-message-entity-set-number entity number)))
832               (setq message-id (elmo-message-entity-field entity 'message-id)
833                     flags (elmo-flag-table-get flag-table message-id))
834               (elmo-global-flags-set flags folder number message-id)
835               (elmo-msgdb-append-entity new-msgdb entity flags))))
836         (when (> num elmo-display-progress-threshold)
837           (setq i (1+ i))
838           (if (or (zerop (% i 5)) (= i num))
839               (elmo-display-progress
840                'elmo-pop3-msgdb-create-message "Creating msgdb..."
841                (/ (* i 100) num)))))
842       new-msgdb)))
843
844 (defun elmo-pop3-read-body (process outbuf)
845   (with-current-buffer (process-buffer process)
846     (let ((start elmo-pop3-read-point)
847           end)
848       (goto-char start)
849       (while (not (re-search-forward "^\\.\r?\n" nil t))
850         (accept-process-output process 1)
851         (goto-char start))
852       (setq end (point))
853       (with-current-buffer outbuf
854         (erase-buffer)
855         (insert-buffer-substring (process-buffer process) start (- end 3)))
856       t)))
857
858 (luna-define-method elmo-folder-open-internal ((folder elmo-pop3-folder))
859   (if (and (not elmo-inhibit-number-mapping)
860            (elmo-pop3-folder-use-uidl-internal folder))
861       (elmo-pop3-folder-set-location-alist-internal
862        folder (elmo-msgdb-location-load (elmo-folder-msgdb-path folder)))))
863
864 (luna-define-method elmo-folder-commit :after ((folder elmo-pop3-folder))
865   (when (elmo-folder-persistent-p folder)
866     (elmo-msgdb-location-save (elmo-folder-msgdb-path folder)
867                               (elmo-pop3-folder-location-alist-internal
868                                folder))))
869
870 (luna-define-method elmo-folder-close-internal ((folder elmo-pop3-folder))
871   (elmo-pop3-folder-set-location-alist-internal folder nil)
872   ;; Just close connection
873   (elmo-folder-check folder))
874
875 (luna-define-method elmo-message-fetch-plugged ((folder elmo-pop3-folder)
876                                                 number strategy
877                                                 &optional section
878                                                 outbuf unseen)
879   (let* ((loc-alist (elmo-pop3-folder-location-alist-internal folder))
880          (process (elmo-network-session-process-internal
881                    (elmo-pop3-get-session folder)))
882         size  response errmsg msg)
883     (with-current-buffer (process-buffer process)
884       (if loc-alist
885           (setq number (elmo-pop3-uidl-to-number
886                         (cdr (assq number loc-alist)))))
887       (setq size (string-to-number
888                   (elmo-pop3-number-to-size number)))
889       (when number
890         (elmo-pop3-send-command process
891                                 (format "retr %s" number))
892         (unless elmo-inhibit-display-retrieval-progress
893           (setq elmo-pop3-total-size size)
894           (elmo-display-progress
895            'elmo-display-retrieval-progress
896            (format "Retrieving (0/%d bytes)..." elmo-pop3-total-size)
897            0))
898         (unwind-protect
899             (progn
900               (when (null (setq response (cdr (elmo-pop3-read-response
901                                                process t))))
902                 (error "Fetching message failed"))
903               (setq response  (elmo-pop3-read-body process outbuf)))
904           (setq elmo-pop3-total-size nil))
905         (unless elmo-inhibit-display-retrieval-progress
906           (elmo-display-progress
907            'elmo-display-retrieval-progress
908            "Retrieving..." 100)  ; remove progress bar.
909           (message "Retrieving...done"))
910         (set-buffer outbuf)
911         (goto-char (point-min))
912         (while (re-search-forward "^\\." nil t)
913           (replace-match "")
914           (forward-line))
915         (elmo-delete-cr-buffer)
916         response))))
917
918 (defun elmo-pop3-delete-msg (process number loc-alist)
919   (with-current-buffer (process-buffer process)
920     (let (response errmsg msg)
921       (if loc-alist
922           (setq number (elmo-pop3-uidl-to-number
923                         (cdr (assq number loc-alist)))))
924       (if number
925           (progn
926             (elmo-pop3-send-command process
927                                     (format "dele %s" number))
928             (when (null (setq response (cdr (elmo-pop3-read-response
929                                              process t))))
930               (error "Deleting message failed")))
931         (error "Deleting message failed")))))
932
933 (luna-define-method elmo-folder-delete-messages-plugged ((folder
934                                                           elmo-pop3-folder)
935                                                          msgs)
936   (let ((loc-alist (elmo-pop3-folder-location-alist-internal folder))
937         (process (elmo-network-session-process-internal
938                   (elmo-pop3-get-session folder))))
939     (mapcar '(lambda (msg) (elmo-pop3-delete-msg process msg loc-alist))
940             msgs)))
941
942 (luna-define-method elmo-message-use-cache-p ((folder elmo-pop3-folder) number)
943   elmo-pop3-use-cache)
944
945 (luna-define-method elmo-folder-persistent-p ((folder elmo-pop3-folder))
946   (and (elmo-folder-persistent-internal folder)
947        (elmo-pop3-folder-use-uidl-internal folder)))
948
949 (luna-define-method elmo-folder-clear :around ((folder elmo-pop3-folder)
950                                                &optional keep-killed)
951   (unless keep-killed
952     (elmo-pop3-folder-set-location-alist-internal folder nil))
953   (luna-call-next-method))
954
955 (luna-define-method elmo-folder-check ((folder elmo-pop3-folder))
956   (if (elmo-folder-plugged-p folder)
957       (let ((session (elmo-pop3-get-session folder 'if-exists)))
958         (when session
959           (elmo-network-close-session session)))))
960
961 (require 'product)
962 (product-provide (provide 'elmo-pop3) (require 'elmo-version))
963
964 ;;; elmo-pop3.el ends here