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