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