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