* elmo-util.el (elmo-file-field-primitive-condition-match): Fixed
[elisp/wanderlust.git] / elmo / elmo-pop3.el
index bdf4e52..29ad744 100644 (file)
@@ -1,8 +1,10 @@
 ;;; elmo-pop3.el -- POP3 Interface for ELMO.
 
-;; Copyright 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
+;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
+;; Copyright (C) 1999,2000      Kenichi OKADA <okada@opaopa.org>
 
 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
+;;     Kenichi OKADA <okada@opaopa.org>
 ;; Keywords: mail, net news
 
 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
 ;; 
 
 (require 'elmo-msgdb)
+(require 'elmo-net)
+
 (eval-when-compile
-  (require 'elmo-util)
-  (condition-case nil
-      (progn
-       (require 'starttls)
-       (require 'sasl))
-    (error))
-  (defun-maybe md5 (a))
-  (defun-maybe sasl-digest-md5-digest-response 
-    (digest-challenge username passwd serv-type host &optional realm))  
-  (defun-maybe sasl-scram-md5-client-msg-1
-    (authenticate-id &optional authorize-id))
-  (defun-maybe sasl-scram-md5-client-msg-2
-    (server-msg-1 client-msg-1 salted-pass))
-  (defun-maybe sasl-scram-md5-make-salted-pass
-    (server-msg-1 passphrase))
-  (defun-maybe sasl-scram-md5-authenticate-server
-    (server-msg-1 server-msg-2 client-msg-1 salted-pass))
-  (defun-maybe starttls-negotiate (a)))
-(condition-case nil
-    (progn
-      (require 'sasl))
-  (error))
+  (require 'elmo-util))
+
+(eval-and-compile
+  (autoload 'md5 "md5"))
 
 (defvar elmo-pop3-exists-exactly t)
-(defvar elmo-pop3-read-point nil)
-(defvar elmo-pop3-connection-cache nil
-  "Cache of pop3 connection.")
+(defvar sasl-mechanism-alist)
 
-(defun elmo-pop3-close-connection (connection &optional process buffer)
-  (save-excursion
-    (let* ((buffer  (or buffer (nth 0 connection)))
-          (process (or process (nth 1 connection))))
-      (elmo-pop3-send-command buffer process "quit")
-      (when (null (elmo-pop3-read-response buffer process t))
-       (error "POP error: QUIT failed")))))
-
-(defun elmo-pop3-flush-connection ()
-  (interactive)
-  (let ((cache elmo-pop3-connection-cache)
-       buffer process proc-stat)
-    (while cache
-      (setq buffer (car (cdr (car cache))))
-      (setq process (car (cdr (cdr (car cache)))))
-      (if (and process
-              (not (or (eq (setq proc-stat 
-                                 (process-status process)) 
-                           'closed)
-                       (eq proc-stat 'exit))))
-         (condition-case ()
-             (elmo-pop3-close-connection nil process buffer)
-           (error)))
-      (if buffer (kill-buffer buffer))
-      ;;(setq process (car (cdr (cdr (car cache)))))
-      (if process (delete-process process))
-      (setq cache (cdr cache)))
-    (setq elmo-pop3-connection-cache nil)))
-
-(defun elmo-pop3-get-connection (spec)
-  (let* ((user   (elmo-pop3-spec-username spec))
-        (server (elmo-pop3-spec-hostname spec))
-        (port   (elmo-pop3-spec-port spec))
-        (auth   (elmo-pop3-spec-auth spec))
-        (ssl    (elmo-pop3-spec-ssl spec))
-        (user-at-host (format "%s@%s" user server))
-        ret-val result buffer process errmsg proc-stat
-        user-at-host-on-port)
-    (if (not (elmo-plugged-p server port))
-       (error "Unplugged"))
-    (setq user-at-host-on-port 
-         (concat user-at-host ":" (int-to-string port)
-                 (if (eq ssl 'starttls) "!!" (if ssl "!"))))
-    (setq ret-val (assoc user-at-host-on-port elmo-pop3-connection-cache))
-    (if (and ret-val 
-            (or (eq (setq proc-stat 
-                          (process-status (cadr (cdr ret-val)))) 
-                    'closed)
-                (eq proc-stat 'exit)))
-       ;; connection is closed...
-       (progn
-         (kill-buffer (car (cdr ret-val)))
-         (setq elmo-pop3-connection-cache 
-               (delete ret-val elmo-pop3-connection-cache))
-         (setq ret-val nil)
-         ))
-    (if ret-val
-       (cdr ret-val)
-      (setq result
-           (elmo-pop3-open-connection 
-            server user port auth
-            (elmo-get-passwd user-at-host) ssl))
-      (if (null result)
-         (error "Connection failed"))
-      (setq buffer (car result))
-      (setq process (cdr result))
-      (when (and process (null buffer))
-       (elmo-remove-passwd user-at-host)
-       (delete-process process)
-       (error "Login failed")
-       )
-      (setq elmo-pop3-connection-cache 
-           (append elmo-pop3-connection-cache 
-                   (list 
-                    (cons user-at-host-on-port
-                          (setq ret-val (list buffer process))))))
-      ret-val)))
-
-(defun elmo-pop3-send-command (buffer process command)
-  (save-excursion
-    (set-buffer buffer)
-    (erase-buffer)
-    (goto-char (point-min))
-    (setq elmo-pop3-read-point (point))
-    (process-send-string process command)
-    (process-send-string process "\r\n")))
+(defvar elmo-pop3-total-size nil)
 
-(defun elmo-pop3-send-command-no-erase (buffer process command)
-  (save-excursion
-    (set-buffer buffer)
-    ;(erase-buffer)
+;; For debugging.
+(defvar elmo-pop3-debug nil
+  "Non-nil forces POP3 folder as debug mode.
+Debug information is inserted in the buffer \"*POP3 DEBUG*\"")
+
+(defvar elmo-pop3-debug-inhibit-logging nil)
+
+;;; Debug
+(defsubst elmo-pop3-debug (message &rest args)
+  (if elmo-pop3-debug
+      (with-current-buffer (get-buffer-create "*POP3 DEBUG*")
+       (goto-char (point-max))
+       (if elmo-pop3-debug-inhibit-logging
+           (insert "NO LOGGING\n")
+         (insert (apply 'format message args) "\n")))))
+
+(luna-define-class elmo-pop3-session (elmo-network-session))
+
+;; buffer-local
+(defvar elmo-pop3-read-point nil)
+(defvar elmo-pop3-number-uidl-hash nil) ; number -> uidl
+(defvar elmo-pop3-uidl-number-hash nil) ; uidl -> number
+(defvar elmo-pop3-size-hash nil) ; number -> size
+(defvar elmo-pop3-uidl-done nil)
+(defvar elmo-pop3-list-done nil)
+
+(defvar elmo-pop3-local-variables '(elmo-pop3-read-point
+                                   elmo-pop3-uidl-number-hash
+                                   elmo-pop3-number-uidl-hash
+                                   elmo-pop3-uidl-done
+                                   elmo-pop3-size-hash
+                                   elmo-pop3-list-done))
+
+(luna-define-method elmo-network-close-session ((session elmo-pop3-session))
+  (when (elmo-network-session-process-internal session)
+    (when (memq (process-status
+                (elmo-network-session-process-internal session))
+               '(open run))
+      (elmo-pop3-send-command (elmo-network-session-process-internal session)
+                             "quit")
+      (or (elmo-pop3-read-response
+          (elmo-network-session-process-internal session) t)
+         (error "POP error: QUIT failed")))
+    (kill-buffer (process-buffer
+                 (elmo-network-session-process-internal session)))
+    (delete-process (elmo-network-session-process-internal session))))
+
+(defun elmo-pop3-get-session (spec &optional if-exists)
+  (elmo-network-get-session
+   'elmo-pop3-session
+   "POP3"
+   (elmo-pop3-spec-hostname spec)
+   (elmo-pop3-spec-port spec)
+   (elmo-pop3-spec-username spec)
+   (elmo-pop3-spec-auth spec)
+   (elmo-pop3-spec-stream-type spec)
+   if-exists))
+
+(defun elmo-pop3-send-command (process command &optional no-erase)
+  (with-current-buffer (process-buffer process)
+    (unless no-erase
+      (erase-buffer))
     (goto-char (point-min))
     (setq elmo-pop3-read-point (point))
+    (elmo-pop3-debug "SEND: %s\n" command)
     (process-send-string process command)
     (process-send-string process "\r\n")))
 
-(defun elmo-pop3-read-response (buffer process &optional not-command)
-  (save-excursion
-    (set-buffer buffer)
+(defun elmo-pop3-read-response (process &optional not-command)
+  (with-current-buffer (process-buffer process)
     (let ((case-fold-search nil)
          (response-string nil)
          (response-continue t)
              (buffer-substring elmo-pop3-read-point (- match-end 2)))
        (goto-char elmo-pop3-read-point)
        (if (looking-at "\\+.*$")
-           (progn 
+           (progn
              (setq response-continue nil)
              (setq elmo-pop3-read-point match-end)
-             (setq return-value 
-                   (if return-value 
+             (setq return-value
+                   (if return-value
                        (concat return-value "\n" response-string)
-                     response-string
-                     )))
+                     response-string)))
          (if (looking-at "\\-.*$")
-             (progn 
+             (progn
                (setq response-continue nil)
                (setq elmo-pop3-read-point match-end)
                (setq return-value nil))
            (setq elmo-pop3-read-point match-end)
            (if not-command
-               (setq response-continue nil))
-           (setq return-value 
-                 (if return-value 
+               (setq response-continue nil))
+           (setq return-value
+                 (if return-value
                      (concat return-value "\n" response-string)
                    response-string)))
          (setq elmo-pop3-read-point match-end)))
   (save-excursion
     (set-buffer (process-buffer process))
     (goto-char (point-max))
-    (insert output)))
-
-(defun elmo-pop3-open-connection (server user port auth passphrase ssl)
-  (let ((process nil)
-       (host server)
-       process-buffer ret-val response capability)
-    (catch 'done
-      (as-binary-process
-       (setq process-buffer
-            (get-buffer-create (format " *POP session to %s:%d" host port)))
-       (save-excursion
-        (set-buffer process-buffer)
-        (elmo-set-buffer-multibyte nil)         
-        (erase-buffer))
-       (setq process
-            (elmo-open-network-stream "POP" process-buffer host port ssl))
-       (and (null process) (throw 'done nil))
-       (set-process-filter process 'elmo-pop3-process-filter)
-       ;; flush connections when exiting...
-       (save-excursion
-        (set-buffer process-buffer)
-        (make-local-variable 'elmo-pop3-read-point)
-        (setq elmo-pop3-read-point (point-min))
-        (when (null (setq response
-                          (elmo-pop3-read-response process-buffer process t)))
-          (setq ret-val (cons nil process))
-          (throw 'done nil))
-        (when (eq ssl 'starttls)
-          (elmo-pop3-send-command process-buffer process "stls")
-          (string-match "^\+OK" 
-                        (elmo-pop3-read-response 
-                         process-buffer process))
-          (starttls-negotiate process))
-        (cond ((string= auth "apop")
-               ;; try only APOP
-               (if (string-match "^\+OK .*\\(<[^\>]+>\\)" response)
-                   ;; good, APOP ready server
-                   (progn
-                     (require 'md5)
-                     (elmo-pop3-send-command  
-                      process-buffer process 
-                      (format "apop %s %s" 
-                              user
-                              (md5 
-                               (concat (match-string 1 response)
-                                           passphrase)))))
-                 ;; otherwise, fail (only APOP authentication)
-                 (setq ret-val (cons nil process))
-                 (throw 'done nil)))
-              ((string= auth "cram-md5")
-               (elmo-pop3-send-command  
-                process-buffer process "auth cram-md5")
-               (when (null (setq response
-                                 (elmo-pop3-read-response
-                                  process-buffer process t)))
-                 (setq ret-val (cons nil process))
-                 (throw 'done nil))
-               (elmo-pop3-send-command
-                process-buffer process
-                (elmo-base64-encode-string
-                 (sasl-cram-md5 user passphrase 
-                                (elmo-base64-decode-string
-                                 (cadr (split-string response " ")))))))
-              ((string= auth "digest-md5")
-               (elmo-pop3-send-command  
-                process-buffer process "auth digest-md5")
-               (when (null (setq response
-                                 (elmo-pop3-read-response
-                                  process-buffer process t)))
-                 (setq ret-val (cons nil process))
-                 (throw 'done nil))
-               (elmo-pop3-send-command
-                process-buffer process
-                (elmo-base64-encode-string
-                 (sasl-digest-md5-digest-response
-                  (elmo-base64-decode-string
-                   (cadr (split-string response " ")))
-                  user passphrase "pop" host)
-                 'no-line-break))
-               (when (null (setq response
-                                 (elmo-pop3-read-response
-                                  process-buffer process t)))
-                 (setq ret-val (cons nil process))
-                 (throw 'done nil))
-               (elmo-pop3-send-command process-buffer process ""))
-              ((string= auth "scram-md5")
-               (let (server-msg-1 server-msg-2 client-msg-1 client-msg-2
-                                  salted-pass)
-                 (elmo-pop3-send-command
-                  process-buffer process
-                  (format "auth scram-md5 %s"
+    (insert output)
+    (elmo-pop3-debug "RECEIVED: %s\n" output)
+    (if elmo-pop3-total-size
+       (message "Retrieving...(%d/%d bytes)." 
+                (buffer-size) elmo-pop3-total-size))))
+
+(defun elmo-pop3-auth-user (session)
+  (let ((process (elmo-network-session-process-internal session)))
+    ;; try USER/PASS
+    (elmo-pop3-send-command
+     process
+     (format "user %s" (elmo-network-session-user-internal session)))
+    (or (elmo-pop3-read-response process t)
+       (signal 'elmo-authenticate-error
+               '(elmo-pop-auth-user)))
+    (elmo-pop3-send-command  process
+                            (format
+                             "pass %s"
+                             (elmo-get-passwd
+                              (elmo-network-session-password-key session))))
+    (or (elmo-pop3-read-response process t)
+       (signal 'elmo-authenticate-error
+               '(elmo-pop-auth-user)))))
+
+(defun elmo-pop3-auth-apop (session)
+  (if (string-match "^\+OK .*\\(<[^\>]+>\\)"
+                   (elmo-network-session-greeting-internal session))
+      ;; good, APOP ready server
+      (progn
+       (elmo-pop3-send-command
+        (elmo-network-session-process-internal session)
+        (format "apop %s %s"
+                (elmo-network-session-user-internal session)
+                (md5
+                 (concat (match-string
+                          1
+                          (elmo-network-session-greeting-internal session))
+                         (elmo-get-passwd
+                          (elmo-network-session-password-key session))))))
+       (or (elmo-pop3-read-response
+            (elmo-network-session-process-internal session)
+            t)
+           (signal 'elmo-authenticate-error
+                   '(elmo-pop3-auth-apop))))
+    (signal 'elmo-open-error '(elmo-pop3-auth-apop))))
+    
+(luna-define-method elmo-network-initialize-session-buffer :after
+  ((session elmo-pop3-session) buffer)
+  (with-current-buffer buffer
+    (mapcar 'make-variable-buffer-local elmo-pop3-local-variables)))
+
+(luna-define-method elmo-network-initialize-session ((session
+                                                     elmo-pop3-session))
+  (let ((process (elmo-network-session-process-internal session))
+       response mechanism)
+    (with-current-buffer (process-buffer process)
+      (set-process-filter process 'elmo-pop3-process-filter)
+      (setq elmo-pop3-read-point (point-min))
+      ;; Skip garbage output from process before greeting.
+      (while (and (memq (process-status process) '(open run))
+                 (goto-char (point-max))
+                 (forward-line -1)
+                 (not (looking-at "+OK")))
+       (accept-process-output process 1))
+      (setq elmo-pop3-read-point (point))
+      (or (elmo-network-session-set-greeting-internal
+          session
+          (elmo-pop3-read-response process t))
+         (signal 'elmo-open-error
+                 '(elmo-network-intialize-session)))
+      (when (eq (elmo-network-stream-type-symbol
+                (elmo-network-session-stream-type-internal session))
+               'starttls)
+       (elmo-pop3-send-command process "stls")
+       (if (string-match "^\+OK"
+                         (elmo-pop3-read-response process))
+           (starttls-negotiate process)
+         (signal 'elmo-open-error
+                 '(elmo-pop3-starttls-error)))))))
+
+(luna-define-method elmo-network-authenticate-session ((session
+                                                       elmo-pop3-session))
+  (with-current-buffer (process-buffer 
+                       (elmo-network-session-process-internal session))
+    (let* ((process (elmo-network-session-process-internal session))
+          (elmo-pop3-debug-inhibit-logging t)
+          (auth (elmo-network-session-auth-internal session))
+          (auth (mapcar '(lambda (mechanism) (upcase (symbol-name mechanism)))
+                        (if (listp auth) auth (list auth))))
+          sasl-mechanisms
+          client name step response mechanism
+          sasl-read-passphrase)
+      (or (and (string= "USER" (car auth))
+              (elmo-pop3-auth-user session))
+         (and (string= "APOP" (car auth))
+              (elmo-pop3-auth-apop session))
+         (progn
+           (require 'sasl)
+           (setq sasl-mechanisms (mapcar 'car sasl-mechanism-alist))
+           (setq mechanism (sasl-find-mechanism auth))
+           (unless mechanism
+             (signal 'elmo-authenticate-error '(elmo-pop3-auth-no-mechanisms)))
+           (setq client
+                 (sasl-make-client
+                  mechanism
+                  (elmo-network-session-user-internal session)
+                  "pop"
+                  (elmo-network-session-host-internal session)))
+;;;        (if elmo-pop3-auth-user-realm
+;;;            (sasl-client-set-property client 'realm elmo-pop3-auth-user-realm))
+           (setq name (sasl-mechanism-name mechanism))
+           (elmo-network-session-set-auth-internal session
+                                                   (intern (downcase name)))
+           (setq sasl-read-passphrase
+                 (function
+                  (lambda (prompt)
+                    (elmo-get-passwd
+                     (elmo-network-session-password-key session)))))
+           (setq step (sasl-next-step client nil))
+           (elmo-pop3-send-command
+            process
+            (concat "AUTH " name
+                    (and (sasl-step-data step)
+                         (concat
+                          " "
                           (elmo-base64-encode-string
-                           (setq client-msg-1
-                                 (sasl-scram-md5-client-msg-1 user)))))
-                 (when (null (setq response
-                                   (elmo-pop3-read-response
-                                    process-buffer process t)))
-                   (setq ret-val (cons nil process))
-                   (throw 'done nil))
-                 (setq server-msg-1
-                       (elmo-base64-decode-string
-                        (cadr (split-string response " "))))
-                 (elmo-pop3-send-command
-                  process-buffer process
-                  (elmo-base64-encode-string
-                   (sasl-scram-md5-client-msg-2
-                    server-msg-1
-                    client-msg-1
-                    (setq salted-pass
-                          (sasl-scram-md5-make-salted-pass 
-                           server-msg-1 passphrase)))))
-                 (when (null (setq response
-                                   (elmo-pop3-read-response
-                                    process-buffer process t)))
-                   (setq ret-val (cons nil process))
-                   (throw 'done nil))
-                 (setq server-msg-2
-                       (elmo-base64-decode-string
-                        (cadr (split-string response " "))))
-                 (if (null (sasl-scram-md5-authenticate-server
-                            server-msg-1
-                            server-msg-2
-                            client-msg-1
-                            salted-pass))
-                     (throw 'done nil))
-                 (elmo-pop3-send-command
-                  process-buffer process "") ))
-              (t
-               ;; try USER/PASS
-               (elmo-pop3-send-command  process-buffer process 
-                                        (format "user %s" user))
-               (when (null (elmo-pop3-read-response process-buffer process t))
-                 (setq ret-val (cons nil process))
-                 (throw 'done nil))
-               (elmo-pop3-send-command  process-buffer process 
-                                        (format "pass %s" passphrase))))
-        ;; read PASS or APOP response
-        (when (null (elmo-pop3-read-response process-buffer process t))
-          (setq ret-val (cons nil process))
-          (throw 'done nil))
-        (setq ret-val (cons process-buffer process)))))
-    ret-val))
+                           (sasl-step-data step) 'no-line-break))))) ;)
+           (catch 'done
+             (while t
+               (unless (setq response (elmo-pop3-read-response process t))
+                 ;; response is NO or BAD.
+                 (signal 'elmo-authenticate-error
+                         (list (intern
+                                (concat "elmo-pop3-auth-"
+                                        (downcase name))))))
+               (if (string-match "^\+OK" response)
+                   (if (sasl-next-step client step)
+                       ;; Bogus server?
+                       (signal 'elmo-authenticate-error
+                               (list (intern
+                                      (concat "elmo-pop3-auth-"
+                                              (downcase name)))))
+                     ;; The authentication process is finished.
+                     (throw 'done nil)))
+               (sasl-step-set-data
+                step
+                (elmo-base64-decode-string 
+                 (cadr (split-string response " "))))
+               (setq step (sasl-next-step client step))
+               (elmo-pop3-send-command
+                process
+                (if (sasl-step-data step)
+                    (elmo-base64-encode-string (sasl-step-data step)
+                                               'no-line-break)
+                  "")))))))))
+
+(luna-define-method elmo-network-setup-session ((session
+                                                elmo-pop3-session))
+  (let ((process (elmo-network-session-process-internal session))
+       count response)
+    (with-current-buffer (process-buffer process)
+      (setq elmo-pop3-size-hash (elmo-make-hash 31))
+      ;; To get obarray of uidl and size
+      (elmo-pop3-send-command process "list")
+      (if (null (elmo-pop3-read-response process))
+         (error "POP LIST command failed"))
+      (if (null (setq response
+                     (elmo-pop3-read-contents
+                      (current-buffer) process)))
+         (error "POP LIST command failed"))
+      ;; POP server always returns a sequence of serial numbers.
+      (setq count (elmo-pop3-parse-list-response response))
+      ;; UIDL
+      (when elmo-pop3-use-uidl
+       (setq elmo-pop3-uidl-number-hash (elmo-make-hash (* count 2)))
+       (setq elmo-pop3-number-uidl-hash (elmo-make-hash (* count 2)))
+       ;; UIDL
+       (elmo-pop3-send-command process "uidl")
+       (unless (elmo-pop3-read-response process)
+         (error "POP UIDL failed"))
+       (unless (setq response (elmo-pop3-read-contents
+                               (current-buffer) process))
+         (error "POP UIDL failed"))
+       (elmo-pop3-parse-uidl-response response)))))
 
 (defun elmo-pop3-read-contents (buffer process)
-  (save-excursion
-    (set-buffer buffer)
+  (with-current-buffer buffer
     (let ((case-fold-search nil)
          match-end)
       (goto-char elmo-pop3-read-point)
        (goto-char elmo-pop3-read-point))
       (setq match-end (point))
       (elmo-delete-cr
-       (buffer-substring elmo-pop3-read-point 
+       (buffer-substring elmo-pop3-read-point
                         (- match-end 3))))))
 
 ;; dummy functions
   (if (and elmo-pop3-exists-exactly
           (elmo-pop3-plugged-p spec))
       (save-excursion
-       (let (elmo-auto-change-plugged) ;;don't change plug status.
-         (condition-case nil
-             (prog1
-                 (elmo-pop3-get-connection spec)
-               (elmo-pop3-flush-connection))
-           (error nil))))
+       (let (elmo-auto-change-plugged ; don't change plug status.
+             elmo-pop3-use-uidl       ; No need to use uidl.
+             session)
+         (prog1
+             (setq session (elmo-pop3-get-session spec))
+           (if session
+               (elmo-network-close-session session)))))
     t))
 
-(defun elmo-pop3-parse-list-response (string)
-  (save-excursion
-    (let ((tmp-buffer (get-buffer-create " *ELMO PARSE TMP*"))
-         ret-val)
-      (set-buffer tmp-buffer)
-      (let ((case-fold-search t))
-       (erase-buffer)
+(defun elmo-pop3-parse-uidl-response (string)
+  (let ((buffer (current-buffer))
+       number list size)
+    (with-temp-buffer
+      (let (number uid list)
        (insert string)
        (goto-char (point-min))
-       (while (re-search-forward "^\\([0-9]*\\)[\t ].*$" nil t)
-         (setq ret-val
-               (cons
-                (string-to-int
-                 (elmo-match-buffer 1))
-                ret-val)))
-       (kill-buffer tmp-buffer)
-       (nreverse ret-val)))))
-
-(defun elmo-pop3-list-folder (spec)
-  (save-excursion
-    (elmo-pop3-flush-connection)
-    (let* ((connection (elmo-pop3-get-connection spec))
-          (buffer  (nth 0 connection))
-          (process (nth 1 connection))
-          response errmsg ret-val)
-      (elmo-pop3-send-command buffer process "list")
-      (if (null (elmo-pop3-read-response buffer process))
-         (error "POP List folder failed"))
-      (if (null (setq response (elmo-pop3-read-contents buffer process)))
-         (error "POP List folder failed"))
-      ;; POP server always returns a sequence of serial numbers.
-      (elmo-pop3-parse-list-response response))))
+       (while (re-search-forward "^\\([0-9]+\\)[\t ]+\\([^ \n]+\\)$" nil t)
+         (setq number  (elmo-match-buffer 1))
+         (setq uid (elmo-match-buffer 2))
+         (with-current-buffer buffer
+           (elmo-set-hash-val uid number elmo-pop3-uidl-number-hash)
+           (elmo-set-hash-val (concat "#" number) uid
+                              elmo-pop3-number-uidl-hash))
+         (setq list (cons uid list)))
+       (with-current-buffer buffer (setq elmo-pop3-uidl-done t))
+       (nreverse list)))))
+
+(defun elmo-pop3-parse-list-response (string)
+  (let ((buffer (current-buffer))
+       (count 0)
+       alist)
+    (with-temp-buffer
+      (insert string)
+      (goto-char (point-min))
+      (while (re-search-forward "^\\([0-9]+\\)[\t ]+\\([0-9]+\\)$" nil t)
+       (setq alist
+             (cons
+              (cons (elmo-match-buffer 1)
+                    (elmo-match-buffer 2))
+              alist))
+       (setq count (1+ count)))
+      (with-current-buffer buffer
+       (setq elmo-pop3-size-hash (elmo-make-hash (* (length alist) 2)))
+       (while alist
+         (elmo-set-hash-val (concat "#" (car (car alist)))
+                            (cdr (car alist))
+                            elmo-pop3-size-hash)
+         (setq alist (cdr alist)))
+       (setq elmo-pop3-list-done t))
+      count)))
+
+(defun elmo-pop3-list-location (spec)
+  (with-current-buffer (process-buffer
+                       (elmo-network-session-process-internal
+                        (elmo-pop3-get-session spec)))
+    (let (list)
+      (if elmo-pop3-uidl-done
+         (progn
+           (mapatoms
+            (lambda (atom)
+              (setq list (cons (symbol-name atom) list)))
+            elmo-pop3-uidl-number-hash)
+           (nreverse list))
+       (error "POP3: Error in UIDL")))))
+
+(defun elmo-pop3-list-by-uidl-subr (spec &optional nonsort)
+  (let ((flist (elmo-list-folder-by-location
+               spec
+               (elmo-pop3-list-location spec))))
+    (if nonsort
+       (cons (elmo-max-of-list flist) (length flist))
+      (sort flist '<))))
+
+(defun elmo-pop3-list-by-list (spec)
+  (with-current-buffer (process-buffer
+                       (elmo-network-session-process-internal
+                        (elmo-pop3-get-session spec)))
+    (let (list)
+      (if elmo-pop3-list-done
+         (progn
+           (mapatoms (lambda (atom)
+                       (setq list (cons (string-to-int
+                                         (substring (symbol-name atom) 1))
+                                        list)))
+                     elmo-pop3-size-hash)
+           (sort list '<))
+       (error "POP3: Error in list")))))
+
+(defun elmo-pop3-list-folder (spec &optional nohide)
+  (let ((killed (and elmo-use-killed-list
+                    (elmo-msgdb-killed-list-load
+                     (elmo-msgdb-expand-path spec))))
+       numbers)
+    (elmo-pop3-commit spec)
+    (setq numbers (if elmo-pop3-use-uidl
+                     (progn
+                       (elmo-pop3-list-by-uidl-subr spec))
+                   (elmo-pop3-list-by-list spec)))
+    (elmo-living-messages numbers killed)))
 
 (defun elmo-pop3-max-of-folder (spec)
-  (save-excursion
-    (elmo-pop3-flush-connection)
-    (let* ((connection (elmo-pop3-get-connection spec))
-          (buffer  (nth 0 connection))
-          (process (nth 1 connection))
+  (elmo-pop3-commit spec)
+  (if elmo-pop3-use-uidl
+      (prog1
+         (elmo-pop3-list-by-uidl-subr spec 'nonsort)
+       (elmo-pop3-commit spec))
+    (let* ((process
+           (elmo-network-session-process-internal
+            (elmo-pop3-get-session spec)))
           (total 0)
           response)
-      (elmo-pop3-send-command buffer process "STAT")
-      (setq response (elmo-pop3-read-response buffer process))
-      ;; response: "^\+OK 2 7570$"
-      (if (not (string-match "^\+OK[ \t]*\\([0-9]*\\)" response))
-         (error "POP STAT command failed")
-       (setq total
-             (string-to-int
-              (substring response (match-beginning 1)(match-end 1 ))))
-       (cons total total)))))
+      (with-current-buffer (process-buffer process)
+       (elmo-pop3-send-command process "STAT")
+       (setq response (elmo-pop3-read-response process))
+       ;; response: "^\+OK 2 7570$"
+       (if (not (string-match "^\+OK[ \t]*\\([0-9]*\\)" response))
+           (error "POP STAT command failed")
+         (setq total
+               (string-to-int
+                (substring response (match-beginning 1)(match-end 1 ))))
+         (elmo-pop3-commit spec)
+         (cons total total))))))
 
 (defvar elmo-pop3-header-fetch-chop-length 200)
 
       nil))
    (t
     nil)))
-     
+
 (defun elmo-pop3-retrieve-headers (buffer tobuffer process articles)
   (save-excursion
     (set-buffer buffer)
          (last-point (point-min)))
       ;; Send HEAD commands.
       (while articles
-       (elmo-pop3-send-command-no-erase
-        buffer
-        process
-        (format "top %s 0" (car articles))
-        )
-       ; (accept-process-output process 1)
+       (elmo-pop3-send-command process (format
+                                        "top %s 0" (car articles))
+                               'no-erase)
+;;;    (accept-process-output process 1)
        (setq articles (cdr articles))
        (setq count (1+ count))
        ;; Every 200 requests we have to read the stream in
                     (setq last-point (point))
                     (setq received (1+ received)))
                   (< received count))
-           (and (zerop (% received 20))
-                (elmo-display-progress
-                 'elmo-pop3-retrieve-headers "Getting headers..."
-                 (/ (* received 100) number)))
+           (when (> number elmo-display-progress-threshold)
+             (if (or (zerop (% received 5)) (= received number))
+                 (elmo-display-progress
+                  'elmo-pop3-retrieve-headers "Getting headers..."
+                  (/ (* received 100) number))))
            (accept-process-output process 1)
-           ;(accept-process-output process)
-           (discard-input)
-           )))
-      (elmo-display-progress
-       'elmo-pop3-retrieve-headers "Getting headers..." 100)
+;;;        (accept-process-output process)
+           (discard-input))))
       ;; Remove all "\r"'s.
       (goto-char (point-min))
       (while (search-forward "\r\n" nil t)
        (replace-match "\n"))
-      (copy-to-buffer tobuffer (point-min) (point-max))
-      ;(elmo-pop3-close-connection nil process buffer) ; close connection
-      )))
+      (copy-to-buffer tobuffer (point-min) (point-max)))))
 
 (defalias 'elmo-pop3-msgdb-create 'elmo-pop3-msgdb-create-as-numlist)
+
+(defun elmo-pop3-sort-overview-by-original-number (overview loc-alist)
+  (if loc-alist
+      (sort overview
+           (lambda (ent1 ent2)
+             (< (elmo-pop3-uidl-to-number
+                 (cdr (assq (elmo-msgdb-overview-entity-get-number ent1)
+                            loc-alist)))
+                (elmo-pop3-uidl-to-number
+                 (cdr (assq (elmo-msgdb-overview-entity-get-number ent2)
+                            loc-alist))))))
+    overview))
+
+(defun elmo-pop3-sort-msgdb-by-original-number (msgdb)
+  (message "Sorting...")
+  (let ((overview (elmo-msgdb-get-overview msgdb)))
+    (setq overview (elmo-pop3-sort-overview-by-original-number
+                   overview
+                   (elmo-msgdb-get-location msgdb)))
+    (message "Sorting...done")
+    (list overview (nth 1 msgdb)(nth 2 msgdb)(nth 3 msgdb)(nth 4 msgdb))))
+
 (defun elmo-pop3-msgdb-create-as-numlist (spec numlist new-mark
                                               already-mark seen-mark
-                                              important-mark seen-list)
+                                              important-mark seen-list
+                                              &optional msgdb)
   (when numlist
-    (let* ((connection (elmo-pop3-get-connection spec))
-          (buffer (nth 0 connection))
-          (process (nth 1 connection))
-          response errmsg ret-val)
-      (elmo-pop3-msgdb-create-by-header buffer process numlist
-                                       new-mark already-mark 
-                                       seen-mark seen-list))))
-
-(defun elmo-pop3-msgdb-create-by-header (buffer process numlist
-                                               new-mark already-mark 
-                                               seen-mark
-                                               seen-list)
-  (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*"))
-       ret-val)
-    (elmo-pop3-retrieve-headers
-     buffer tmp-buffer process numlist)
-    (setq ret-val
+    (let ((process (elmo-network-session-process-internal
+                   (elmo-pop3-get-session spec)))
+         loc-alist)
+      (if elmo-pop3-use-uidl
+         (setq loc-alist (if msgdb (elmo-msgdb-get-location msgdb)
+                           (elmo-msgdb-location-load
+                            (elmo-msgdb-expand-path spec)))))
+      (with-current-buffer (process-buffer process)
+       (elmo-pop3-sort-msgdb-by-original-number
+        (elmo-pop3-msgdb-create-by-header process numlist
+                                          new-mark already-mark
+                                          seen-mark seen-list
+                                          loc-alist))))))
+
+(defun elmo-pop3-uidl-to-number (uidl)
+  (string-to-number (elmo-get-hash-val uidl
+                                      elmo-pop3-uidl-number-hash)))
+
+(defun elmo-pop3-number-to-uidl (number)
+  (elmo-get-hash-val (format "#%d" number)
+                    elmo-pop3-number-uidl-hash))
+
+(defun elmo-pop3-number-to-size (number)
+  (elmo-get-hash-val (format "#%d" number)
+                    elmo-pop3-size-hash))
+
+(defun elmo-pop3-msgdb-create-by-header (process numlist
+                                                new-mark already-mark
+                                                seen-mark
+                                                seen-list
+                                                loc-alist)
+  (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*")))
+    (with-current-buffer (process-buffer process)
+      (if loc-alist ; use uidl.
+         (setq numlist
+               (delq
+                nil
+                (mapcar
+                 (lambda (number)
+                   (elmo-pop3-uidl-to-number (cdr (assq number loc-alist))))
+                 numlist))))
+      (elmo-pop3-retrieve-headers (process-buffer process)
+                                 tmp-buffer process numlist)
+      (prog1
          (elmo-pop3-msgdb-create-message
-          tmp-buffer 
+          tmp-buffer
+          process
           (length numlist)
           numlist
-          new-mark already-mark seen-mark seen-list))
-    (kill-buffer tmp-buffer)
-    ret-val))
+          new-mark already-mark seen-mark seen-list loc-alist)
+       (kill-buffer tmp-buffer)))))
 
-(defun elmo-pop3-msgdb-create-message (buffer 
-                                      num numlist new-mark already-mark 
+(defun elmo-pop3-msgdb-create-message (buffer
+                                      process
+                                      num
+                                      numlist new-mark already-mark
                                       seen-mark
-                                      seen-list)
+                                      seen-list
+                                      loc-alist)
   (save-excursion
-    (let (beg
-         overview number-alist mark-alist
-         entity i number message-id gmark seen)
+    (let (beg overview number-alist mark-alist
+             entity i number message-id gmark seen size)
       (set-buffer buffer)
       (elmo-set-buffer-multibyte default-enable-multibyte-characters)
       (goto-char (point-min))
          (save-restriction
            (narrow-to-region beg (point))
            (setq entity
-                 (elmo-msgdb-create-overview-from-buffer 
+                 (elmo-msgdb-create-overview-from-buffer
                   (car numlist)))
            (setq numlist (cdr numlist))
            (when entity
-             (setq overview 
+             (setq overview
                    (elmo-msgdb-append-element
                     overview entity))
+             (with-current-buffer (process-buffer process)
+               (elmo-msgdb-overview-entity-set-size
+                entity
+                (string-to-number
+                 (elmo-pop3-number-to-size
+                  (elmo-msgdb-overview-entity-get-number entity))))
+               (if (setq number
+                         (car
+                          (rassoc
+                           (elmo-pop3-number-to-uidl
+                            (elmo-msgdb-overview-entity-get-number entity))
+                           loc-alist)))
+                   (elmo-msgdb-overview-entity-set-number entity number)))
              (setq number-alist
-                   (elmo-msgdb-number-add number-alist
-                                          (elmo-msgdb-overview-entity-get-number entity)
-                                          (car entity)))
+                   (elmo-msgdb-number-add
+                    number-alist
+                    (elmo-msgdb-overview-entity-get-number entity)
+                    (car entity)))
              (setq message-id (car entity))
              (setq seen (member message-id seen-list))
              (if (setq gmark (or (elmo-msgdb-global-mark-get message-id)
-                                 (if (elmo-cache-exists-p 
+                                 (if (elmo-cache-exists-p
                                       message-id) ; XXX
                                      (if seen
                                          nil
                                            seen-mark)
                                      new-mark))))
                  (setq mark-alist
-                       (elmo-msgdb-mark-append 
+                       (elmo-msgdb-mark-append
                         mark-alist
                         (elmo-msgdb-overview-entity-get-number entity)
-                        gmark)))
-             )))
-       (setq i (1+ i))
-       (and (zerop (% i 20))
-            (elmo-display-progress
-             'elmo-pop3-msgdb-create-message "Creating msgdb..."
-             (/ (* i 100) num)))
-       )
-      (elmo-display-progress
-       'elmo-pop3-msgdb-create-message "Creating msgdb..." 100)
-      (list overview number-alist mark-alist))))
-
-(defun elmo-pop3-read-body (buffer process outbuf)
-  (with-current-buffer buffer
+                        gmark))))))
+       (when (> num elmo-display-progress-threshold)
+         (setq i (1+ i))
+         (if (or (zerop (% i 5)) (= i num))
+             (elmo-display-progress
+              'elmo-pop3-msgdb-create-message "Creating msgdb..."
+              (/ (* i 100) num)))))
+      (list overview number-alist mark-alist loc-alist))))
+
+(defun elmo-pop3-read-body (process outbuf)
+  (with-current-buffer (process-buffer process)
     (let ((start elmo-pop3-read-point)
          end)
       (goto-char start)
       (setq end (point))
       (with-current-buffer outbuf
        (erase-buffer)
-       (insert-buffer-substring buffer start (- end 3))
+       (insert-buffer-substring (process-buffer process) start (- end 3))
        (elmo-delete-cr-get-content-type)))))
 
-(defun elmo-pop3-read-msg (spec number outbuf)
-  (save-excursion
-    (let* ((connection (elmo-pop3-get-connection spec))
-          (buffer  (car connection))
-          (process (cadr connection))
-          (cwf     (caddr connection))  
-          response errmsg msg)
-      (elmo-pop3-send-command buffer process 
-                             (format "retr %s" number))
-      (when (null (setq response (elmo-pop3-read-response
-                                 buffer process t)))
-       (error "Fetching message failed"))
-      (setq response (elmo-pop3-read-body buffer process outbuf))
-      (set-buffer outbuf)
-      (goto-char (point-min))
-      (while (re-search-forward "^\\." nil t)
-       (replace-match "")
-       (forward-line))
-      response)))
-
-(defun elmo-pop3-delete-msg (buffer process number)
-  (let (response errmsg msg)
-    (elmo-pop3-send-command buffer process 
-                           (format "dele %s" number))
-    (when (null (setq response (elmo-pop3-read-response
-                               buffer process t)))
-      (error "Deleting message failed"))))
-
-(defun elmo-pop3-delete-msgs (spec msgs)
-  (save-excursion
-    (let* ((connection (elmo-pop3-get-connection spec))
-          (buffer  (car connection))
-          (process (cadr connection)))
-      (mapcar '(lambda (msg) (elmo-pop3-delete-msg 
-                             buffer process msg))
-             msgs))))
+(defun elmo-pop3-read-msg (spec number outbuf &optional msgdb unread)
+  (let* ((loc-alist (if elmo-pop3-use-uidl
+                       (if msgdb
+                           (elmo-msgdb-get-location msgdb)
+                         (elmo-msgdb-location-load
+                          (elmo-msgdb-expand-path spec)))))
+        (process (elmo-network-session-process-internal
+                  (elmo-pop3-get-session spec)))
+        size response errmsg msg)
+    (with-current-buffer (process-buffer process)
+      (if loc-alist
+         (setq number (elmo-pop3-uidl-to-number
+                       (cdr (assq number loc-alist)))))
+      (setq size (string-to-number
+                 (elmo-pop3-number-to-size number)))
+      (when number
+       (elmo-pop3-send-command process
+                               (format "retr %s" number))
+       (setq elmo-pop3-total-size size)
+       (unless elmo-inhibit-display-retrieval-progress
+         (setq elmo-pop3-total-size size)
+         (elmo-display-progress
+          'elmo-pop3-display-retrieval-progress
+          (format "Retrieving (0/%d bytes)..." elmo-pop3-total-size)
+          0))
+       (unwind-protect
+           (progn
+             (when (null (setq response (elmo-pop3-read-response
+                                         process t)))
+               (error "Fetching message failed"))
+             (setq response (elmo-pop3-read-body process outbuf)))
+         (setq elmo-pop3-total-size nil))
+       (unless elmo-inhibit-display-retrieval-progress
+         (elmo-display-progress
+          'elmo-display-retrieval-progress "" 100)  ; remove progress bar.
+         (message "Retrieving...done."))
+       (set-buffer outbuf)
+       (goto-char (point-min))
+       (while (re-search-forward "^\\." nil t)
+         (replace-match "")
+         (forward-line))
+       response))))
+
+(defun elmo-pop3-delete-msg (process number loc-alist)
+  (with-current-buffer (process-buffer process)
+    (let (response errmsg msg)
+      (if loc-alist
+         (setq number (elmo-pop3-uidl-to-number
+                       (cdr (assq number loc-alist)))))
+      (if number
+         (progn
+           (elmo-pop3-send-command process
+                                   (format "dele %s" number))
+           (when (null (setq response (elmo-pop3-read-response
+                                       process t)))
+             (error "Deleting message failed")))
+       (error "Deleting message failed")))))
+
+(defun elmo-pop3-delete-msgs (spec msgs &optional msgdb)
+  (let ((loc-alist (if elmo-pop3-use-uidl
+                      (if msgdb
+                          (elmo-msgdb-get-location msgdb)
+                        (elmo-msgdb-location-load
+                         (elmo-msgdb-expand-path spec)))))
+       (process (elmo-network-session-process-internal
+                 (elmo-pop3-get-session spec))))
+    (mapcar '(lambda (msg) (elmo-pop3-delete-msg
+                           process msg loc-alist))
+           msgs)))
 
 (defun elmo-pop3-search (spec condition &optional numlist)
   (error "Searching in pop3 folder is not implemented yet"))
 
 (defun elmo-pop3-port-label (spec)
   (concat "pop3"
-         (if (elmo-pop3-spec-ssl spec) "!ssl" "")))
+         (if (elmo-pop3-spec-stream-type spec)
+             (concat "!" (symbol-name
+                          (elmo-network-stream-type-symbol
+                           (elmo-pop3-spec-stream-type spec)))))))
 
 (defsubst elmo-pop3-portinfo (spec)
-  (list (elmo-pop3-spec-hostname spec) 
+  (list (elmo-pop3-spec-hostname spec)
        (elmo-pop3-spec-port spec)))
 
 (defun elmo-pop3-plugged-p (spec)
         (append (elmo-pop3-portinfo spec)
                 (list nil nil (quote (elmo-pop3-port-label spec)) add))))
 
-(defalias 'elmo-pop3-sync-number-alist 
+(defalias 'elmo-pop3-sync-number-alist
   'elmo-generic-sync-number-alist)
-(defalias 'elmo-pop3-list-folder-unread 
+(defalias 'elmo-pop3-list-folder-unread
   'elmo-generic-list-folder-unread)
 (defalias 'elmo-pop3-list-folder-important
   'elmo-generic-list-folder-important)
-(defalias 'elmo-pop3-commit 'elmo-generic-commit)
+(defalias 'elmo-pop3-folder-diff 'elmo-generic-folder-diff)
+
+(defun elmo-pop3-commit (spec)
+  (if (elmo-pop3-plugged-p spec)
+      (let ((session (elmo-pop3-get-session spec 'if-exists)))
+       (when session
+         (elmo-network-close-session session)))))
 
-(provide 'elmo-pop3)
+(require 'product)
+(product-provide (provide 'elmo-pop3) (require 'elmo-version))
 
 ;;; elmo-pop3.el ends here