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