;;; pop3.el --- Post Office Protocol (RFC 1460) interface
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
-;; 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+;; Copyright (C) 1996-2012 Free Software Foundation, Inc.
;; Author: Richard L. Pieri <ratinox@peorth.gweep.net>
;; Maintainer: FSF
;;; Code:
+(eval-when-compile (require 'cl))
+
+(eval-and-compile
+ ;; In Emacs 24, `open-protocol-stream' is an autoloaded alias for
+ ;; `make-network-stream'.
+ (unless (fboundp 'open-protocol-stream)
+ (require 'proto-stream)))
+
(require 'mail-utils)
(defvar parse-time-months)
:version "22.1" ;; Oort Gnus
:group 'pop3)
+(defcustom pop3-stream-length 100
+ "How many messages should be requested at one time.
+The lower the number, the more latency-sensitive the fetching
+will be. If your pop3 server doesn't support streaming at all,
+set this to 1."
+ :type 'number
+ :version "24.1"
+ :group 'pop3)
+
(defcustom pop3-leave-mail-on-server nil
"*Non-nil if the mail is to be left on the POP server after fetching.
(defvar pop3-read-point nil)
(defvar pop3-debug nil)
-;; Borrowed from nnheader-accept-process-output in nnheader.el.
-(defvar pop3-read-timeout
- (if (string-match "windows-nt\\|os/2\\|emx\\|cygwin"
- (symbol-name system-type))
- ;; http://thread.gmane.org/v9655t3pjo.fsf@marauder.physik.uni-ulm.de
- ;;
- ;; IIRC, values lower than 1.0 didn't/don't work on Windows/DOS.
- ;;
- ;; There should probably be a runtime test to determine the timing
- ;; resolution, or a primitive to report it. I don't know off-hand
- ;; what's possible. Perhaps better, maybe the Windows/DOS primitive
- ;; could round up non-zero timeouts to a minimum of 1.0?
- 1.0
- 0.1)
- "How long pop3 should wait between checking for the end of output.
+;; Borrowed from nnheader-accept-process-output in nnheader.el. See the
+;; comments there for explanations about the values.
+
+(eval-and-compile
+ (if (and (fboundp 'nnheader-accept-process-output)
+ (boundp 'nnheader-read-timeout))
+ (defalias 'pop3-accept-process-output 'nnheader-accept-process-output)
+ ;; Borrowed from `nnheader.el':
+ (defvar pop3-read-timeout
+ (if (string-match "windows-nt\\|os/2\\|cygwin"
+ (symbol-name system-type))
+ 1.0
+ 0.01)
+ "How long pop3 should wait between checking for the end of output.
Shorter values mean quicker response, but are more CPU intensive.")
-
-;; Borrowed from nnheader-accept-process-output in nnheader.el.
-(defun pop3-accept-process-output (process)
- (accept-process-output
- process
- (truncate pop3-read-timeout)
- (truncate (* (- pop3-read-timeout
- (truncate pop3-read-timeout))
- 1000))))
-
-(autoload 'nnheader-accept-process-output "nnheader")
-
-(defun pop3-movemail (&optional crashbox)
- "Transfer contents of a maildrop to the specified CRASHBOX."
- (or crashbox (setq crashbox (expand-file-name "~/.crashbox")))
+ (defun pop3-accept-process-output (process)
+ (accept-process-output
+ process
+ (truncate pop3-read-timeout)
+ (truncate (* (- pop3-read-timeout
+ (truncate pop3-read-timeout))
+ 1000))))))
+
+;;;###autoload
+(defun pop3-movemail (file)
+ "Transfer contents of a maildrop to the specified FILE.
+Use streaming commands."
(let* ((process (pop3-open-server pop3-mailhost pop3-port))
- (crashbuf (get-buffer-create " *pop3-retr*"))
- (n 1)
- message-count
- (pop3-password pop3-password))
+ message-count message-total-size)
+ (pop3-logon process)
+ (with-current-buffer (process-buffer process)
+ (let ((size (pop3-stat process)))
+ (setq message-count (car size)
+ message-total-size (cadr size)))
+ (when (> message-count 0)
+ (pop3-send-streaming-command
+ process "RETR" message-count message-total-size)
+ (pop3-write-to-file file)
+ (unless pop3-leave-mail-on-server
+ (pop3-send-streaming-command
+ process "DELE" message-count nil))))
+ (pop3-quit process)
+ t))
+
+(defun pop3-send-streaming-command (process command count total-size)
+ (erase-buffer)
+ (let ((i 1)
+ (start-point (point-min))
+ (waited-for 0))
+ (while (>= count i)
+ (process-send-string process (format "%s %d\r\n" command i))
+ ;; Only do 100 messages at a time to avoid pipe stalls.
+ (when (zerop (% i pop3-stream-length))
+ (setq start-point
+ (pop3-wait-for-messages process pop3-stream-length
+ total-size start-point))
+ (incf waited-for pop3-stream-length))
+ (incf i))
+ (pop3-wait-for-messages process (- count waited-for)
+ total-size start-point)))
+
+(defun pop3-wait-for-messages (process count total-size start-point)
+ (while (> count 0)
+ (goto-char start-point)
+ (while (or (and (re-search-forward "^\\+OK" nil t)
+ (or (not total-size)
+ (re-search-forward "^\\.\r?\n" nil t)))
+ (re-search-forward "^-ERR " nil t))
+ (decf count)
+ (setq start-point (point)))
+ (unless (memq (process-status process) '(open run))
+ (error "pop3 process died"))
+ (when total-size
+ (message "pop3 retrieved %dKB (%d%%)"
+ (truncate (/ (buffer-size) 1000))
+ (truncate (* (/ (* (buffer-size) 1.0)
+ total-size) 100))))
+ (pop3-accept-process-output process))
+ start-point)
+
+(defun pop3-write-to-file (file)
+ (let ((pop-buffer (current-buffer))
+ (start (point-min))
+ beg end
+ temp-buffer)
+ (with-temp-buffer
+ (setq temp-buffer (current-buffer))
+ (with-current-buffer pop-buffer
+ (goto-char (point-min))
+ (while (re-search-forward "^\\+OK" nil t)
+ (forward-line 1)
+ (setq beg (point))
+ (when (re-search-forward "^\\.\r?\n" nil t)
+ (setq start (point))
+ (forward-line -1)
+ (setq end (point)))
+ (with-current-buffer temp-buffer
+ (goto-char (point-max))
+ (let ((hstart (point)))
+ (insert-buffer-substring pop-buffer beg end)
+ (pop3-clean-region hstart (point))
+ (goto-char (point-max))
+ (pop3-munge-message-separator hstart (point))
+ (goto-char (point-max))))))
+ (let ((coding-system-for-write 'binary))
+ (goto-char (point-min))
+ ;; Check whether something inserted a newline at the start and
+ ;; delete it.
+ (when (eolp)
+ (delete-char 1))
+ (write-region (point-min) (point-max) file nil 'nomesg)))))
+
+(defun pop3-logon (process)
+ (let ((pop3-password pop3-password))
;; for debugging only
(if pop3-debug (switch-to-buffer (process-buffer process)))
;; query for password
((equal 'pass pop3-authentication-scheme)
(pop3-user process pop3-maildrop)
(pop3-pass process))
- (t (error "Invalid POP3 authentication scheme")))
- (setq message-count (car (pop3-stat process)))
- (unwind-protect
- (while (<= n message-count)
- (message "Retrieving message %d of %d from %s..."
- n message-count pop3-mailhost)
- (pop3-retr process n crashbuf)
- (save-excursion
- (set-buffer crashbuf)
- (let ((coding-system-for-write 'binary))
- (write-region (point-min) (point-max) crashbox t 'nomesg))
- (set-buffer (process-buffer process))
- (while (> (buffer-size) 5000)
- (goto-char (point-min))
- (forward-line 50)
- (delete-region (point-min) (point))))
- (unless pop3-leave-mail-on-server
- (pop3-dele process n))
- (setq n (+ 1 n))
- (nnheader-accept-process-output process))
- (when (and pop3-leave-mail-on-server
- (> n 1))
- (message "pop3.el doesn't support UIDL. Setting `pop3-leave-mail-on-server'
-to %s might not give the result you'd expect." pop3-leave-mail-on-server)
- (sit-for 1))
- (pop3-quit process))
- (kill-buffer crashbuf))
- t)
+ (t (error "Invalid POP3 authentication scheme")))))
(defun pop3-get-message-count ()
"Return the number of messages in the maildrop."
(pop3-quit process)
message-count))
-(autoload 'open-tls-stream "tls")
-(autoload 'starttls-open-stream "starttls")
-(autoload 'starttls-negotiate "starttls") ; avoid warning
-
(defcustom pop3-stream-type nil
- "*Transport security type for POP3 connexions.
-This may be either nil (plain connexion), `ssl' (use an
+ "*Transport security type for POP3 connections.
+This may be either nil (plain connection), `ssl' (use an
SSL/TSL-secured stream) or `starttls' (use the starttls mechanism
to turn on TLS security after opening the stream). However, if
-this is nil, `ssl' is assumed for connexions to port
+this is nil, `ssl' is assumed for connections to port
995 (pop3s)."
:version "23.1" ;; No Gnus
:group 'pop3
(const :tag "SSL/TLS" ssl)
(const starttls)))
+(eval-and-compile
+ (if (fboundp 'set-process-query-on-exit-flag)
+ (defalias 'pop3-set-process-query-on-exit-flag
+ 'set-process-query-on-exit-flag)
+ (defalias 'pop3-set-process-query-on-exit-flag
+ 'process-kill-without-query)))
+
(defun pop3-open-server (mailhost port)
"Open TCP connection to MAILHOST on PORT.
Returns the process associated with the connection."
(let ((coding-system-for-read 'binary)
(coding-system-for-write 'binary)
- process)
- (save-excursion
- (set-buffer (get-buffer-create (concat " trace of POP session to "
- mailhost)))
+ result)
+ (with-current-buffer
+ (get-buffer-create (concat " trace of POP session to "
+ mailhost))
(erase-buffer)
(setq pop3-read-point (point-min))
- (setq process
- (cond
- ((or (eq pop3-stream-type 'ssl)
- (and (not pop3-stream-type) (member port '(995 "pop3s"))))
- ;; gnutls-cli, openssl don't accept service names
- (if (or (equal port "pop3s")
- (null port))
- (setq port 995))
- (let ((process (open-tls-stream "POP" (current-buffer)
- mailhost port)))
- (when process
- ;; There's a load of info printed that needs deleting.
- (let ((again 't))
- ;; repeat until
- ;; - either we received the +OK line
- ;; - or accept-process-output timed out without getting
- ;; anything
- (while (and again
- (setq again (memq (process-status process)
- '(open run))))
- (setq again (pop3-accept-process-output process))
- (goto-char (point-max))
- (forward-line -1)
- (cond ((looking-at "\\+OK")