(elmo-flatten): Use `append' and `listp' instead of
[elisp/wanderlust.git] / elmo / elmo-pop3.el
index 19db3b0..96a18ec 100644 (file)
@@ -61,6 +61,11 @@ set as non-nil.")
   :type 'boolean
   :group 'elmo)
 
+(defconst elmo-pop3-folder-name-syntax `(([user ".+"])
+                                        (?/ [auth ".+"])
+                                        (?: [uidl "^[A-Za-z]+$"])
+                                        ,@elmo-net-folder-name-syntax))
+
 (defvar sasl-mechanism-alist)
 
 (defvar elmo-pop3-total-size nil)
@@ -87,45 +92,40 @@ Debug information is inserted in the buffer \"*POP3 DEBUG*\"")
                     (use-uidl location-alist))
   (luna-define-internal-accessors 'elmo-pop3-folder))
 
-(luna-define-method elmo-folder-initialize :around ((folder
-                                                    elmo-pop3-folder)
-                                                   name)
+(luna-define-method elmo-folder-initialize ((folder elmo-pop3-folder) name)
   (let ((elmo-network-stream-type-alist
         (if elmo-pop3-stream-type-alist
             (append elmo-pop3-stream-type-alist
                     elmo-network-stream-type-alist)
           elmo-network-stream-type-alist))
-       parse)
-    (setq name (luna-call-next-method))
+       tokens auth uidl)
+    (setq tokens (car (elmo-parse-separated-tokens
+                      name
+                      elmo-pop3-folder-name-syntax)))
     ;; user
-    (setq parse (elmo-parse-token name "/:"))
     (elmo-net-folder-set-user-internal folder
-                                      (if (eq (length (car parse)) 0)
-                                          elmo-pop3-default-user
-                                        (car parse)))
+                                      (or (cdr (assq 'user tokens))
+                                          elmo-pop3-default-user))
     ;; auth
-    (setq parse (elmo-parse-prefixed-element ?/ (cdr parse) ":"))
+    (setq auth (cdr (assq 'auth tokens)))
     (elmo-net-folder-set-auth-internal folder
-                                      (if (eq (length (car parse)) 0)
-                                          elmo-pop3-default-authenticate-type
-                                        (intern (downcase (car parse)))))
+                                      (if auth
+                                          (intern (downcase auth))
+                                        elmo-pop3-default-authenticate-type))
     ;; uidl
-    (setq parse (elmo-parse-prefixed-element ?: (cdr parse)))
+    (setq uidl (cdr (assq 'uidl tokens)))
     (elmo-pop3-folder-set-use-uidl-internal folder
-                                           (if (eq (length (car parse)) 0)
-                                               elmo-pop3-default-use-uidl
-                                             (string= (car parse) "uidl")))
-    (unless (elmo-net-folder-server-internal folder)
-      (elmo-net-folder-set-server-internal folder
-                                          elmo-pop3-default-server))
-    (unless (elmo-net-folder-port-internal folder)
-      (elmo-net-folder-set-port-internal folder
-                                        elmo-pop3-default-port))
-    (unless (elmo-net-folder-stream-type-internal folder)
-      (elmo-net-folder-set-stream-type-internal
-       folder
-       (elmo-get-network-stream-type
-       elmo-pop3-default-stream-type)))
+                                           (if uidl
+                                               (string= uidl "uidl")
+                                             elmo-pop3-default-use-uidl))
+    ;; network
+    (elmo-net-folder-set-parameters
+     folder
+     tokens
+     (list :server     elmo-pop3-default-server
+          :port        elmo-pop3-default-port
+          :stream-type
+          (elmo-get-network-stream-type elmo-pop3-default-stream-type)))
     folder))
 
 ;;; POP3 session
@@ -156,9 +156,9 @@ Debug information is inserted in the buffer \"*POP3 DEBUG*\"")
       (elmo-pop3-send-command (elmo-network-session-process-internal session)
                              "quit")
       ;; process is dead.
-      (or (elmo-pop3-read-response
-          (elmo-network-session-process-internal session)
-          t)
+      (or (cdr (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)))
@@ -197,16 +197,25 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
     (goto-char (point-min))
     (setq elmo-pop3-read-point (point))
     (elmo-pop3-debug "SEND: %s\n" (if no-log "<NO LOGGING>" command))
-    (process-send-string process command)
-    (process-send-string process "\r\n")))
+    (process-send-string process (concat command "\r\n"))))
 
 (defun elmo-pop3-read-response (process &optional not-command)
+  "Read response and return a cons cell of \(CODE . BODY\).
+PROCESS is the process to read response from.
+If optional NOT-COMMAND is non-nil, read only the first line.
+CODE is one of the following:
+'ok          ... response is OK.
+'err         ... response is ERROR.
+'login-delay ... user is not allowed to login until the login delay
+                 period has expired.
+'in-use      ... authentication was successful but the mailbox is in use."
   ;; buffer is in case for process is dead.
   (with-current-buffer (process-buffer process)
     (let ((case-fold-search nil)
          (response-string nil)
          (response-continue t)
          (return-value nil)
+         (err nil)
          match-end)
       (while response-continue
        (goto-char elmo-pop3-read-point)
@@ -227,9 +236,16 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
                      response-string)))
          (if (looking-at "\\-.*$")
              (progn
-               (setq response-continue nil)
-               (setq elmo-pop3-read-point match-end)
-               (setq return-value nil))
+               (when (looking-at "[^ ]+ \\[\\([^]]+\\)\\]")
+                 (setq return-value
+                       (intern
+                        (downcase
+                         (buffer-substring (match-beginning 1)
+                                           (match-end 1))))))
+               (setq err t
+                     response-continue nil
+                     elmo-pop3-read-point match-end
+                     return-value (cons (or return-value 'err) nil)))
            (setq elmo-pop3-read-point match-end)
            (if not-command
                (setq response-continue nil))
@@ -238,7 +254,9 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
                      (concat return-value "\n" response-string)
                    response-string)))
          (setq elmo-pop3-read-point match-end)))
-      return-value)))
+      (if err
+         return-value
+       (cons 'ok return-value)))))
 
 (defun elmo-pop3-process-filter (process output)
   (when (buffer-live-p (process-buffer process))
@@ -257,53 +275,61 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
           (/ (buffer-size) (/ elmo-pop3-total-size 100)))))))
 
 (defun elmo-pop3-auth-user (session)
-  (let ((process (elmo-network-session-process-internal session)))
+  (let ((process (elmo-network-session-process-internal session))
+       response)
     ;; try USER/PASS
     (elmo-pop3-send-command
      process
      (format "user %s" (elmo-network-session-user-internal session))
      nil 'no-log)
-    (or (elmo-pop3-read-response process t)
-       (progn
-         (delete-process process)
-         (signal 'elmo-authenticate-error
-                 '(elmo-pop-auth-user))))
+    (setq response (elmo-pop3-read-response process t))
+    (unless (eq (car response) 'ok)
+      (signal 'elmo-open-error '(elmo-pop-auth-user)))
     (elmo-pop3-send-command  process
                             (format
                              "pass %s"
                              (elmo-get-passwd
                               (elmo-network-session-password-key session)))
                             nil 'no-log)
-    (or (elmo-pop3-read-response process t)
-       (progn
-         (delete-process process)
-         (signal 'elmo-authenticate-error
-                 '(elmo-pop-auth-user))))))
+    (setq response (elmo-pop3-read-response process t))
+    (case (car response)
+      (ok)
+      (in-use
+       (error "Maildrop is currently in use"))
+      (login-delay
+       (error "Not allowed to login until the login delay period has expired"))
+      (t
+       (signal 'elmo-authenticate-error '(elmo-pop-auth-user))))
+    (car response)))
 
 (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)))))
-        nil 'no-log)
-       (or (elmo-pop3-read-response
-            (elmo-network-session-process-internal session)
-            t)
-           (progn
-             (delete-process (elmo-network-session-process-internal session))
-             (signal 'elmo-authenticate-error
-                     '(elmo-pop3-auth-apop)))))
-    (signal 'elmo-open-error '(elmo-pop3-auth-apop))))
+  (unless (string-match "^\+OK .*\\(<[^\>]+>\\)"
+                       (elmo-network-session-greeting-internal session))
+    (signal 'elmo-open-error '(elmo-pop3-auth-apop)))
+  ;; good, APOP ready server
+  (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)))))
+   nil 'no-log)
+  (let ((response (elmo-pop3-read-response
+                  (elmo-network-session-process-internal session)
+                  t)))
+    (case (car response)
+      (ok)
+      (in-use
+       (error "Maildrop is currently in use"))
+      (login-delay
+       (error "Not allowed to login until the login delay period has expired"))
+      (t
+       (signal 'elmo-authenticate-error '(elmo-pop-auth-apop))))
+    (car response)))
 
 (luna-define-method elmo-network-initialize-session-buffer :after
   ((session elmo-pop3-session) buffer)
@@ -326,18 +352,16 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
       (setq elmo-pop3-read-point (point))
       (or (elmo-network-session-set-greeting-internal
           session
-          (elmo-pop3-read-response process t))
+          (cdr (elmo-pop3-read-response process t))) ; if ok, cdr is non-nil.
          (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))
+       (if (eq 'ok (car (elmo-pop3-read-response process)))
            (starttls-negotiate process)
-         (signal 'elmo-open-error
-                 '(elmo-pop3-starttls-error)))))))
+         (signal 'elmo-open-error '(elmo-pop3-starttls-error)))))))
 
 (luna-define-method elmo-network-authenticate-session ((session
                                                        elmo-pop3-session))
@@ -345,16 +369,15 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
                        (elmo-network-session-process-internal session))
     (let* ((process (elmo-network-session-process-internal session))
           (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)
+          (auth (mapcar (lambda (mechanism) (upcase (symbol-name mechanism)))
+                        (if (listp auth) auth (list auth)))))
       (or (and (string= "USER" (car auth))
               (elmo-pop3-auth-user session))
          (and (string= "APOP" (car auth))
               (elmo-pop3-auth-apop session))
-         (progn
+         (let (sasl-mechanisms
+               client name step response mechanism
+               sasl-read-passphrase)
            (require 'sasl)
            (setq sasl-mechanisms (mapcar 'car sasl-mechanism-alist))
            (setq mechanism (sasl-find-mechanism auth))
@@ -372,10 +395,9 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
            (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)))))
+                 (lambda (prompt)
+                   (elmo-get-passwd
+                    (elmo-network-session-password-key session))))
            (setq step (sasl-next-step client nil))
            (elmo-pop3-send-command
             process
@@ -388,21 +410,26 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
             nil 'no-log)
            (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)))
+               (setq response (elmo-pop3-read-response process t))
+               (case (car response)
+                 (ok)
+                 (in-use
+                  (error "Maildrop is currently in use"))
+                 (login-delay
+                  (error "Not allowed to login \
+until the login delay period has expired"))
+                 (t
+                  (signal 'elmo-authenticate-error
+                          (list (intern (concat "elmo-pop3-auth-"
+                                                (downcase name)))))))
+               (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
@@ -423,11 +450,10 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
       (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))
+      (if (null (cdr (elmo-pop3-read-response process)))
          (error "POP LIST command failed"))
       (if (null (setq response
-                     (elmo-pop3-read-contents
-                      (current-buffer) process)))
+                     (elmo-pop3-read-contents process)))
          (error "POP LIST command failed"))
       ;; POP server always returns a sequence of serial numbers.
       (setq count (elmo-pop3-parse-list-response response))
@@ -437,15 +463,14 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
        (setq elmo-pop3-number-uidl-hash (elmo-make-hash (* count 2)))
        ;; UIDL
        (elmo-pop3-send-command process "uidl")
-       (unless (elmo-pop3-read-response process)
+       (unless (cdr (elmo-pop3-read-response process))
          (error "POP UIDL failed"))
-       (unless (setq response (elmo-pop3-read-contents
-                               (current-buffer) process))
+       (unless (setq response (elmo-pop3-read-contents process))
          (error "POP UIDL failed"))
        (elmo-pop3-parse-uidl-response response)))))
 
-(defun elmo-pop3-read-contents (buffer process)
-  (with-current-buffer buffer
+(defun elmo-pop3-read-contents (process)
+  (with-current-buffer (process-buffer process)
     (let ((case-fold-search nil)
          match-end)
       (goto-char elmo-pop3-read-point)
@@ -617,7 +642,7 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
           response)
       (with-current-buffer (process-buffer process)
        (elmo-pop3-send-command process "STAT")
-       (setq response (elmo-pop3-read-response process))
+       (setq response (cdr (elmo-pop3-read-response process)))
        ;; response: "^\+OK 2 7570$"
        (if (not (string-match "^\+OK[ \t]*\\([0-9]*\\)" response))
            (error "POP STAT command failed")
@@ -642,9 +667,9 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
    (t
     nil)))
 
-(defun elmo-pop3-retrieve-headers (buffer tobuffer process articles)
+(defun elmo-pop3-retrieve-headers (process tobuffer articles)
   (save-excursion
-    (set-buffer buffer)
+    (set-buffer (process-buffer process))
     (erase-buffer)
     (let ((number (length articles))
          (count 0)
@@ -667,7 +692,6 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
            (accept-process-output process 1))
          (discard-input)
          (while (progn
-                  (set-buffer buffer)
                   (goto-char last-point)
                   ;; Count replies.
                   (while (elmo-pop3-next-result-arrived-p)
@@ -682,10 +706,8 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
            (accept-process-output process 1)
 ;;;        (accept-process-output process)
            (discard-input))))
-      ;; Remove all "\r"'s.
-      (goto-char (point-min))
-      (while (search-forward "\r\n" nil t)
-       (replace-match "\n"))
+      ;; Replace all CRLF with LF.
+      (elmo-delete-cr-buffer)
       (copy-to-buffer tobuffer (point-min) (point-max)))))
 
 (luna-define-method elmo-folder-msgdb-create ((folder elmo-pop3-folder)
@@ -743,8 +765,7 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
                  (lambda (number)
                    (elmo-pop3-uidl-to-number (cdr (assq number loc-alist))))
                  numlist))))
-      (elmo-pop3-retrieve-headers (process-buffer process)
-                                 tmp-buffer process numlist)
+      (elmo-pop3-retrieve-headers process tmp-buffer numlist)
       (prog1
          (elmo-pop3-msgdb-create-message
           folder
@@ -766,7 +787,7 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
     (let ((new-msgdb (elmo-make-msgdb))
          beg entity i number message-id flags)
       (set-buffer buffer)
-      (elmo-set-buffer-multibyte default-enable-multibyte-characters)
+      (set-buffer-multibyte default-enable-multibyte-characters)
       (goto-char (point-min))
       (setq i 0)
       (message "Creating msgdb...")
@@ -779,7 +800,8 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
            (narrow-to-region beg (point))
            (setq entity
                  (elmo-msgdb-create-message-entity-from-buffer
-                  new-msgdb (car numlist)))
+                  (elmo-msgdb-message-entity-handler new-msgdb)
+                  (car numlist)))
            (setq numlist (cdr numlist))
            (when entity
              (with-current-buffer (process-buffer process)
@@ -864,8 +886,8 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
           0))
        (unwind-protect
            (progn
-             (when (null (setq response (elmo-pop3-read-response
-                                         process t)))
+             (when (null (setq response (cdr (elmo-pop3-read-response
+                                              process t))))
                (error "Fetching message failed"))
              (setq response  (elmo-pop3-read-body process outbuf)))
          (setq elmo-pop3-total-size nil))
@@ -879,6 +901,7 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
        (while (re-search-forward "^\\." nil t)
          (replace-match "")
          (forward-line))
+       (elmo-delete-cr-buffer)
        response))))
 
 (defun elmo-pop3-delete-msg (process number loc-alist)
@@ -891,8 +914,8 @@ If IF-EXISTS is `any-exists', get BIFF session or normal session if exists."
          (progn
            (elmo-pop3-send-command process
                                    (format "dele %s" number))
-           (when (null (setq response (elmo-pop3-read-response
-                                       process t)))
+           (when (null (setq response (cdr (elmo-pop3-read-response
+                                            process t))))
              (error "Deleting message failed")))
        (error "Deleting message failed")))))