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