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