(eval-when-compile
(require 'cl))
+(require 'nnheader)
+(require 'gnus-util)
+(require 'gnus)
+(require 'nnoo)
(require 'netrc)
(nnoo-declare nnimap)
This is always done if the server supports UID EXPUNGE, but it's
not done by default on servers that doesn't support that command.")
+(defvoo nnimap-authenticator nil
+ "How nnimap authenticate itself to the server.
+Possible choices are nil (use default methods) or `anonymous'.")
+
+(defvoo nnimap-fetch-partial-articles nil
+ "If non-nil, nnimap will fetch partial articles.
+If t, nnimap will fetch only the first part. If a string, it
+will fetch all parts that have types that match that string. A
+likely value would be \"text/\" to automatically fetch all
+textual parts.")
+
(defvoo nnimap-connection-alist nil)
(defvoo nnimap-current-infos nil)
"Internal variable with default value for `nnimap-split-download-body'.")
(defstruct nnimap
- group process commands capabilities select-result)
+ group process commands capabilities select-result newlinep)
(defvar nnimap-object nil)
(delete-region (line-beginning-position) (line-end-position))
(insert (format "211 %s Article retrieved." article))
(forward-line 1)
- (insert (format "Bytes: %d\n" bytes))
+ (insert (format "Chars: %d\n" bytes))
(when lines
(insert (format "Lines: %s\n" lines)))
(re-search-forward "^\r$")
(current-buffer)))
(defun nnimap-open-shell-stream (name buffer host port)
- (let ((process (start-process name buffer shell-file-name
- shell-command-switch
- (format-spec
- nnimap-shell-program
- (format-spec-make
- ?s host
- ?p port)))))
- process))
+ (let ((process-connection-type nil))
+ (start-process name buffer shell-file-name
+ shell-command-switch
+ (format-spec
+ nnimap-shell-program
+ (format-spec-make
+ ?s host
+ ?p port)))))
(defun nnimap-credentials (address ports)
(let (port credentials)
(when (setq connection-result (nnimap-wait-for-connection))
(unless (equal connection-result "PREAUTH")
(if (not (setq credentials
- (nnimap-credentials nnimap-address ports)))
+ (if (eq nnimap-authenticator 'anonymous)
+ (list "anonymous"
+ (message-make-address))
+ (nnimap-credentials
+ nnimap-address
+ (if nnimap-server-port
+ (cons (format "%s" nnimap-server-port) ports)
+ ports)))))
(setq nnimap-object nil)
(setq login-result (nnimap-command "LOGIN %S %S"
(car credentials)
(deffoo nnimap-request-article (article &optional group server to-buffer)
(with-current-buffer nntp-server-buffer
- (let ((result (nnimap-possibly-change-group group server)))
+ (let ((result (nnimap-possibly-change-group group server))
+ parts)
(when (stringp article)
(setq article (nnimap-find-article-by-message-id group article)))
(when (and result
(erase-buffer)
(with-current-buffer (nnimap-buffer)
(erase-buffer)
+ (when nnimap-fetch-partial-articles
+ (if (eq nnimap-fetch-partial-articles t)
+ (setq parts '(1))
+ (nnimap-command "UID FETCH %d (BODYSTRUCTURE)" article)
+ (goto-char (point-min))
+ (when (re-search-forward "FETCH.*BODYSTRUCTURE" nil t)
+ (let ((structure (ignore-errors (read (current-buffer)))))
+ (setq parts (nnimap-find-wanted-parts structure))))))
(setq result
(nnimap-command
(if (member "IMAP4REV1" (nnimap-capabilities nnimap-object))
"UID FETCH %d BODY.PEEK[]"
"UID FETCH %d RFC822.PEEK")
- article)))
+ article))
+ ;; Check that we really got an article.
+ (goto-char (point-min))
+ (unless (looking-at "\\* [0-9]+ FETCH")
+ (setq result nil)))
(let ((buffer (nnimap-find-process-buffer (current-buffer))))
(when (car result)
- (with-current-buffer to-buffer
+ (with-current-buffer (or to-buffer nntp-server-buffer)
(insert-buffer-substring buffer)
(goto-char (point-min))
(let ((bytes (nnimap-get-length)))
(goto-char (+ (point) bytes))
(delete-region (point) (point-max))
(nnheader-ms-strip-cr))
- t)))))))
+ (cons group article))))))))
+
+(defun nnimap-find-wanted-parts (structure)
+ (message-flatten-list (nnimap-find-wanted-parts-1 structure "")))
+
+(defun nnimap-find-wanted-parts-1 (structure prefix)
+ (let ((num 1)
+ parts)
+ (while (consp (car structure))
+ (let ((sub (pop structure)))
+ (if (consp (car sub))
+ (push (nnimap-find-wanted-parts-1
+ sub (if (string= prefix "")
+ (number-to-string num)
+ (format "%s.%s" prefix num)))
+ parts)
+ (let ((type (format "%s/%s" (nth 0 sub) (nth 1 sub))))
+ (when (string-match nnimap-fetch-partial-articles type)
+ (push (if (string= prefix "")
+ (number-to-string num)
+ (format "%s.%s" prefix num))
+ parts)))
+ (incf num))))
+ (nreverse parts)))
(deffoo nnimap-request-group (group &optional server dont-check info)
(with-current-buffer nntp-server-buffer
"APPEND %S {%d}" (utf7-encode group t)
(length message)))
(process-send-string (get-buffer-process (current-buffer)) message)
- (process-send-string (get-buffer-process (current-buffer)) "\r\n")
+ (process-send-string (get-buffer-process (current-buffer))
+ (if (nnimap-newlinep nnimap-object)
+ "\n"
+ "\r\n"))
(let ((result (nnimap-get-response sequence)))
(when result
(cons group
(nnimap-update-infos (nnimap-flags-to-marks
(nnimap-parse-flags
(nreverse sequences)))
- infos))))
+ infos)
+ ;; Finally, just return something resembling an active file in
+ ;; the nntp buffer, so that the agent can save the info, too.
+ (with-current-buffer nntp-server-buffer
+ (erase-buffer)
+ (dolist (info infos)
+ (let* ((group (gnus-info-group info))
+ (active (gnus-active group)))
+ (when active
+ (insert (format "%S %d %d y\n"
+ (gnus-group-real-name group)
+ (cdr active)
+ (car active))))))))))
(defun nnimap-update-infos (flags infos)
(dolist (info infos)
(process-send-string
(get-buffer-process (current-buffer))
(nnimap-log-command
- (format "%d %s\r\n"
+ (format "%d %s%s\n"
(incf nnimap-sequence)
- (apply #'format args))))
+ (apply #'format args)
+ (if (nnimap-newlinep nnimap-object)
+ ""
+ "\r"))))
nnimap-sequence)
(defun nnimap-log-command (command)
(goto-char (point-min))
(while (and (memq (process-status process)
'(open run))
- (not (re-search-forward "^\\* " nil t)))
+ (not (re-search-forward "^\\* .*\n" nil t)))
(nnheader-accept-process-output process)
(goto-char (point-min)))
- (and (looking-at "[A-Z0-9]+")
- (match-string 0))))
+ (forward-line -1)
+ (and (looking-at "\\* \\([A-Z0-9]+\\)")
+ (match-string 1))))
(defun nnimap-wait-for-response (sequence &optional messagep)
- (goto-char (point-max))
- (while (or (bobp)
- (progn
- (forward-line -1)
- (not (looking-at (format "^%d .*\n" sequence)))))
- (when messagep
- (message "Read %dKB" (/ (buffer-size) 1000)))
- (nnheader-accept-process-output (get-buffer-process (current-buffer)))
- (goto-char (point-max))))
+ (let ((process (get-buffer-process (current-buffer))))
+ (goto-char (point-max))
+ (while (and (memq (process-status process)
+ '(open run))
+ (not (re-search-backward (format "^%d .*\n" sequence)
+ (max (point-min) (- (point) 500))
+ t)))
+ (when messagep
+ (message "Read %dKB" (/ (buffer-size) 1000)))
+ (nnheader-accept-process-output process)
+ (goto-char (point-max)))))
(defun nnimap-parse-response ()
(let ((lines (split-string (nnimap-last-response-string) "\r\n" t))
"BODY.PEEK[HEADER] BODY.PEEK"
"RFC822.PEEK"))
(if nnimap-split-download-body-default
- ""
+ "[]"
"[1]")))
t))