* nnheader.el (nnheader-remove-body): Fix an error of detecting boundary
[gnus] / lisp / nnheader.el
index ed74363..84da0da 100644 (file)
@@ -1,11 +1,11 @@
 ;;; nnheader.el --- header access macros for Gnus and its backends
 
 ;; Copyright (C) 1987, 1988, 1989, 1990, 1993, 1994, 1995, 1996,
-;;        1997, 1998, 2000, 2001
+;;        1997, 1998, 2000, 2001, 2002
 ;;        Free Software Foundation, Inc.
 
 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
-;;     Lars Magne Ingebrigtsen <larsi@gnus.org>
+;;     Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
 
 ;; This file is part of GNU Emacs.
@@ -40,7 +40,8 @@
 (eval-and-compile
   (autoload 'gnus-sorted-intersection "gnus-range")
   (autoload 'gnus-intersection "gnus-range")
-  (autoload 'gnus-sorted-complement "gnus-range"))
+  (autoload 'gnus-sorted-complement "gnus-range")
+  (autoload 'gnus-sorted-difference "gnus-range"))
 
 (defcustom gnus-verbose-backends 7
   "Integer that says how verbose the Gnus backends should be.
@@ -59,7 +60,7 @@ they will keep on jabbering all the time."
 (defvar nnheader-max-head-length 4096
   "*Max length of the head of articles.
 
-Value is an integer, nil, or t.  Nil means read in chunks of a file
+Value is an integer, nil, or t.  nil means read in chunks of a file
 indefinitely until a complete head is found\; t means always read the
 entire file immediately, disregarding `nnheader-head-chop-length'.
 
@@ -69,7 +70,18 @@ Integer values will in effect be rounded up to the nearest multiple of
 (defvar nnheader-head-chop-length 2048
   "*Length of each read operation when trying to fetch HEAD headers.")
 
-(defvar nnheader-file-name-translation-alist nil
+(defvar nnheader-file-name-translation-alist
+  (let ((case-fold-search t))
+    (cond
+     ((string-match "windows-nt\\|os/2\\|emx\\|cygwin"
+                   (symbol-name system-type))
+      (append (mapcar (lambda (c) (cons c ?_))
+                     '(?: ?* ?\" ?< ?> ??))
+             (if (string-match "windows-nt\\|cygwin"
+                               (symbol-name system-type))
+                 nil
+               '((?+ . ?-)))))
+     (t nil)))
   "*Alist that says how to translate characters in file names.
 For instance, if \":\" is invalid as a file character in file names
 on your system, you could say something like:
@@ -81,7 +93,6 @@ on your system, you could say something like:
   (autoload 'mail-position-on-field "sendmail")
   (autoload 'message-remove-header "message")
   (autoload 'gnus-point-at-eol "gnus-util")
-  (autoload 'gnus-delete-line "gnus-util" nil nil 'macro)
   (autoload 'gnus-buffer-live-p "gnus-util"))
 
 ;;; Header access macros.
@@ -202,120 +213,135 @@ on your system, you could say something like:
 
 ;; Parsing headers and NOV lines.
 
+(defsubst nnheader-remove-cr-followed-by-lf ()
+  (goto-char (point-max))
+  (while (search-backward "\r\n" nil t)
+    (delete-char 1)))
+
 (defsubst nnheader-header-value ()
   (skip-chars-forward " \t")
   (buffer-substring (point) (gnus-point-at-eol)))
 
-(defun nnheader-parse-head (&optional naked)
+(defun nnheader-parse-naked-head (&optional number)
+  ;; This function unfolds continuation lines in this buffer
+  ;; destructively.  When this side effect is unwanted, use
+  ;; `nnheader-parse-head' instead of this function.
   (let ((case-fold-search t)
-       (cur (current-buffer))
        (buffer-read-only nil)
-       in-reply-to lines p ref)
-    (goto-char (point-min))
-    (when naked
-      (insert "\n"))
-    ;; Search to the beginning of the next header.  Error messages
-    ;; do not begin with 2 or 3.
+       (cur (current-buffer))
+       (p (point-min))
+       in-reply-to lines ref)
+    (nnheader-remove-cr-followed-by-lf)
+    (ietf-drums-unfold-fws)
+    (subst-char-in-region (point-min) (point-max) ?\t ? )
+    (goto-char p)
+    (insert "\n")
     (prog1
-       (when (or naked (re-search-forward "^[23][0-9]+ " nil t))
-         ;; This implementation of this function, with nine
-         ;; search-forwards instead of the one re-search-forward and
-         ;; a case (which basically was the old function) is actually
-         ;; about twice as fast, even though it looks messier.  You
-         ;; can't have everything, I guess.  Speed and elegance
-         ;; don't always go hand in hand.
-         (vector
-          ;; Number.
-          (if naked
-              (progn
-                (setq p (point-min))
-                0)
-            (prog1
-                (read cur)
-              (end-of-line)
-              (setq p (point))
-              (narrow-to-region (point)
-                                (or (and (search-forward "\n.\n" nil t)
-                                         (- (point) 2))
-                                    (point)))))
-          ;; Subject.
-          (progn
-            (goto-char p)
-            (if (search-forward "\nsubject:" nil t)
-                (nnheader-header-value) "(none)"))
-          ;; From.
-          (progn
-            (goto-char p)
-            (if (search-forward "\nfrom:" nil t)
-                (nnheader-header-value) "(nobody)"))
-          ;; Date.
-          (progn
-            (goto-char p)
-            (if (search-forward "\ndate:" nil t)
-                (nnheader-header-value) ""))
-          ;; Message-ID.
-          (progn
-            (goto-char p)
-            (if (search-forward "\nmessage-id:" nil t)
-                (buffer-substring
-                 (1- (or (search-forward "<" (gnus-point-at-eol) t)
-                         (point)))
-                 (or (search-forward ">" (gnus-point-at-eol) t) (point)))
-              ;; If there was no message-id, we just fake one to make
-              ;; subsequent routines simpler.
-              (nnheader-generate-fake-message-id)))
-          ;; References.
-          (progn
-            (goto-char p)
-            (if (search-forward "\nreferences:" nil t)
-                (nnheader-header-value)
-           ;; Get the references from the in-reply-to header if there
-              ;; were no references and the in-reply-to header looks
-              ;; promising.
-              (if (and (search-forward "\nin-reply-to:" nil t)
-                       (setq in-reply-to (nnheader-header-value))
-                       (string-match "<[^\n>]+>" in-reply-to))
-                  (let (ref2)
-                    (setq ref (substring in-reply-to (match-beginning 0)
-                                         (match-end 0)))
-                    (while (string-match "<[^\n>]+>"
-                                         in-reply-to (match-end 0))
-                      (setq ref2 (substring in-reply-to (match-beginning 0)
-                                            (match-end 0)))
-                      (when (> (length ref2) (length ref))
-                        (setq ref ref2)))
-                    ref)
-                nil)))
-          ;; Chars.
-          0
-          ;; Lines.
-          (progn
-            (goto-char p)
-            (if (search-forward "\nlines: " nil t)
-                (if (numberp (setq lines (read cur)))
-                    lines 0)
-              0))
-          ;; Xref.
-          (progn
-            (goto-char p)
-            (and (search-forward "\nxref:" nil t)
-                 (nnheader-header-value)))
-
-          ;; Extra.
-          (when nnmail-extra-headers
-            (let ((extra nnmail-extra-headers)
-                  out)
-              (while extra
-                (goto-char p)
-                (when (search-forward
-                       (concat "\n" (symbol-name (car extra)) ":") nil t)
-                  (push (cons (car extra) (nnheader-header-value))
-                        out))
-                (pop extra))
-              out))))
-      (when naked
-       (goto-char (point-min))
-       (delete-char 1)))))
+       ;; This implementation of this function, with nine
+       ;; search-forwards instead of the one re-search-forward and a
+       ;; case (which basically was the old function) is actually
+       ;; about twice as fast, even though it looks messier.  You
+       ;; can't have everything, I guess.  Speed and elegance don't
+       ;; always go hand in hand.
+       (vector
+        ;; Number.
+        (or number 0)
+        ;; Subject.
+        (progn
+          (goto-char p)
+          (if (search-forward "\nsubject:" nil t)
+              (nnheader-header-value) "(none)"))
+        ;; From.
+        (progn
+          (goto-char p)
+          (if (search-forward "\nfrom:" nil t)
+              (nnheader-header-value) "(nobody)"))
+        ;; Date.
+        (progn
+          (goto-char p)
+          (if (search-forward "\ndate:" nil t)
+              (nnheader-header-value) ""))
+        ;; Message-ID.
+        (progn
+          (goto-char p)
+          (if (search-forward "\nmessage-id:" nil t)
+              (buffer-substring
+               (1- (or (search-forward "<" (gnus-point-at-eol) t)
+                       (point)))
+               (or (search-forward ">" (gnus-point-at-eol) t) (point)))
+            ;; If there was no message-id, we just fake one to make
+            ;; subsequent routines simpler.
+            (nnheader-generate-fake-message-id)))
+        ;; References.
+        (progn
+          (goto-char p)
+          (if (search-forward "\nreferences:" nil t)
+              (nnheader-header-value)
+            ;; Get the references from the in-reply-to header if
+            ;; there were no references and the in-reply-to header
+            ;; looks promising.
+            (if (and (search-forward "\nin-reply-to:" nil t)
+                     (setq in-reply-to (nnheader-header-value))
+                     (string-match "<[^\n>]+>" in-reply-to))
+                (let (ref2)
+                  (setq ref (substring in-reply-to (match-beginning 0)
+                                       (match-end 0)))
+                  (while (string-match "<[^\n>]+>"
+                                       in-reply-to (match-end 0))
+                    (setq ref2 (substring in-reply-to (match-beginning 0)
+                                          (match-end 0)))
+                    (when (> (length ref2) (length ref))
+                      (setq ref ref2)))
+                  ref)
+              nil)))
+        ;; Chars.
+        0
+        ;; Lines.
+        (progn
+          (goto-char p)
+          (if (search-forward "\nlines: " nil t)
+              (if (numberp (setq lines (read cur)))
+                  lines 0)
+            0))
+        ;; Xref.
+        (progn
+          (goto-char p)
+          (and (search-forward "\nxref:" nil t)
+               (nnheader-header-value)))
+        ;; Extra.
+        (when nnmail-extra-headers
+          (let ((extra nnmail-extra-headers)
+                out)
+            (while extra
+              (goto-char p)
+              (when (search-forward
+                     (concat "\n" (symbol-name (car extra)) ":") nil t)
+                (push (cons (car extra) (nnheader-header-value))
+                      out))
+              (pop extra))
+            out)))
+      (goto-char p)
+      (delete-char 1))))
+
+(defun nnheader-parse-head (&optional naked)
+  (let ((cur (current-buffer)) num beg end)
+    (when (if naked
+             (setq num 0
+                   beg (point-min)
+                   end (point-max))
+           (goto-char (point-min))
+           ;; Search to the beginning of the next header.  Error
+           ;; messages do not begin with 2 or 3.
+           (when (re-search-forward "^[23][0-9]+ " nil t)
+             (end-of-line)
+             (setq num (read cur)
+                   beg (point)
+                   end (if (search-forward "\n.\n" nil t)
+                           (- (point) 2)
+                         (point)))))
+      (with-temp-buffer
+       (insert-buffer-substring cur beg end)
+       (nnheader-parse-naked-head num)))))
 
 (defmacro nnheader-nov-skip-field ()
   '(search-forward "\t" eol 'move))
@@ -400,6 +426,22 @@ on your system, you could say something like:
       (delete-char 1))
     (forward-line 1)))
 
+(defun nnheader-parse-overview-file (file)
+  "Parse FILE and return a list of headers."
+  (mm-with-unibyte-buffer
+    (nnheader-insert-file-contents file)
+    (goto-char (point-min))
+    (let (headers)
+      (while (not (eobp))
+       (push (nnheader-parse-nov) headers)
+       (forward-line 1))
+      (nreverse headers))))
+
+(defun nnheader-write-overview-file (file headers)
+  "Write HEADERS to FILE."
+  (with-temp-file file
+    (mapcar 'nnheader-insert-nov headers)))
+
 (defun nnheader-insert-header (header)
   (insert
    "Subject: " (or (mail-header-subject header) "(none)") "\n"
@@ -451,7 +493,8 @@ the line could be found."
        (setq prev (point))
        (while (and (not (numberp (setq num (read cur))))
                    (not (eobp)))
-         (gnus-delete-line))
+         (delete-region (progn (beginning-of-line) (point))
+                        (progn (forward-line 1) (point))))
        (cond ((> num article)
               (setq max (point)))
              ((< num article)
@@ -587,6 +630,12 @@ the line could be found."
      (point-max)))
   (goto-char (point-min)))
 
+(defun nnheader-remove-body ()
+  "Remove the body from an article in this current buffer."
+  (goto-char (point-min))
+  (when (re-search-forward "\n\r?\n" nil t)
+    (delete-region (point) (point-max))))
+
 (defun nnheader-set-temp-buffer (name &optional noerase)
   "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
   (set-buffer (get-buffer-create name))
@@ -671,7 +720,7 @@ If FULL, translate everything."
                path ""
                i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1)))
                      2 0))
-    ;; We translate -- but only the file name.  We leave the directory
+       ;; We translate -- but only the file name.  We leave the directory
        ;; alone.
        (if (and (featurep 'xemacs)
                 (memq system-type '(cygwin32 win32 w32 mswindows windows-nt)))
@@ -806,9 +855,7 @@ without formatting."
 (defun nnheader-ms-strip-cr ()
   "Strip ^M from the end of all lines."
   (save-excursion
-    (goto-char (point-min))
-    (while (re-search-forward "\r$" nil t)
-      (delete-backward-char 1))))
+    (nnheader-remove-cr-followed-by-lf)))
 
 (defun nnheader-file-size (file)
   "Return the file size of FILE or 0."
@@ -859,6 +906,25 @@ find-file-hooks, etc.
   (let ((coding-system-for-read nnheader-file-coding-system))
     (mm-insert-file-contents filename visit beg end replace)))
 
+(defun nnheader-insert-nov-file (file first)
+  (let ((size (nth 7 (file-attributes file)))
+       (cutoff (* 32 1024)))
+    (if (< size cutoff)
+       ;; If the file is small, we just load it.
+       (nnheader-insert-file-contents file)
+      ;; We start on the assumption that FIRST is pretty recent.  If
+      ;; not, we just insert the rest of the file as well.
+      (let (current)
+       (nnheader-insert-file-contents file nil (- size cutoff) size)
+       (goto-char (point-min))
+       (delete-region (point) (or (search-forward "\n" nil 'move) (point)))
+       (setq current (ignore-errors (read (current-buffer))))
+       (if (and (numberp current)
+                (< current first))
+           t
+         (delete-region (point-min) (point-max))
+         (nnheader-insert-file-contents file))))))
+
 (defun nnheader-find-file-noselect (&rest args)
   (let ((format-alist nil)
        (auto-mode-alist (mm-auto-mode-alist))