8888fc31dbf7170ae7b634d3c9971213235c54ea
[elisp/gnus.git-] / lisp / nnheader.el
1 ;;; nnheader.el --- header access macros for Semi-gnus and its backends
2
3 ;; Copyright (C) 1987, 1988, 1989, 1990, 1993, 1994,
4 ;;   1995, 1996, 1997, 1998, 2000, 2001, 2002, 2003,
5 ;;   2004, 2005 Free Software Foundation, Inc.
6
7 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
8 ;;      Lars Magne Ingebrigtsen <larsi@gnus.org>
9 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
10 ;;      Katsumi Yamaoka <yamaoka@jpl.org>
11 ;; Keywords: mail, news, MIME
12
13 ;; This file is part of GNU Emacs.
14
15 ;; GNU Emacs is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; any later version.
19
20 ;; GNU Emacs is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 ;; GNU General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
27 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 ;; Boston, MA 02110-1301, USA.
29
30 ;;; Commentary:
31
32 ;;; Code:
33
34 (eval-when-compile (require 'cl))
35 (eval-when-compile (require 'static))
36
37 (defvar nnmail-extra-headers)
38
39 ;; Requiring `gnus-util' at compile time creates a circular
40 ;; dependency between nnheader.el and gnus-util.el.
41 ;;(eval-when-compile (require 'gnus-util))
42
43 (require 'mail-utils)
44 (require 'gnus-util)
45
46 ;; Reduce the required value of `recursive-load-depth-limit' for Emacs 21.
47 (require 'pces)
48 (require 'poem)
49 (require 'std11)
50
51 (require 'mime)
52 (eval-and-compile
53   (autoload 'gnus-sorted-intersection "gnus-range")
54   (autoload 'gnus-intersection "gnus-range")
55   (autoload 'gnus-sorted-complement "gnus-range")
56   (autoload 'gnus-sorted-difference "gnus-range"))
57
58 (defcustom gnus-verbose-backends 7
59   "Integer that says how verbose the Gnus backends should be.
60 The higher the number, the more messages the Gnus backends will flash
61 to say what it's doing.  At zero, the Gnus backends will be totally
62 mute; at five, they will display most important messages; and at ten,
63 they will keep on jabbering all the time."
64   :group 'gnus-start
65   :type 'integer)
66
67 (defcustom gnus-nov-is-evil nil
68   "If non-nil, Gnus backends will never output headers in the NOV format."
69   :group 'gnus-server
70   :type 'boolean)
71
72 (defvar nnheader-max-head-length 8192
73   "*Max length of the head of articles.
74
75 Value is an integer, nil, or t.  nil means read in chunks of a file
76 indefinitely until a complete head is found\; t means always read the
77 entire file immediately, disregarding `nnheader-head-chop-length'.
78
79 Integer values will in effect be rounded up to the nearest multiple of
80 `nnheader-head-chop-length'.")
81
82 (defvar nnheader-head-chop-length 2048
83   "*Length of each read operation when trying to fetch HEAD headers.")
84
85 (defvar nnheader-read-timeout
86   (if (string-match "windows-nt\\|os/2\\|emx\\|cygwin"
87                     (symbol-name system-type))
88       ;; http://thread.gmane.org/v9655t3pjo.fsf@marauder.physik.uni-ulm.de
89       ;;
90       ;; IIRC, values lower than 1.0 didn't/don't work on Windows/DOS.
91       ;;
92       ;; There should probably be a runtime test to determine the timing
93       ;; resolution, or a primitive to report it.  I don't know off-hand
94       ;; what's possible.  Perhaps better, maybe the Windows/DOS primitive
95       ;; could round up non-zero timeouts to a minimum of 1.0?
96       1.0
97     0.1)
98   "How long nntp should wait between checking for the end of output.
99 Shorter values mean quicker response, but are more CPU intensive.")
100
101 (defvar nnheader-file-name-translation-alist
102   (let ((case-fold-search t))
103     (cond
104      ((string-match "windows-nt\\|os/2\\|emx\\|cygwin"
105                     (symbol-name system-type))
106       (append (mapcar (lambda (c) (cons c ?_))
107                       '(?: ?* ?\" ?< ?> ??))
108               (if (string-match "windows-nt\\|cygwin"
109                                 (symbol-name system-type))
110                   nil
111                 '((?+ . ?-)))))
112      (t nil)))
113   "*Alist that says how to translate characters in file names.
114 For instance, if \":\" is invalid as a file character in file names
115 on your system, you could say something like:
116
117 \(setq nnheader-file-name-translation-alist '((?: . ?_)))")
118
119 (defvar nnheader-text-coding-system
120   (if (memq system-type '(windows-nt ms-dos ms-windows))
121       'raw-text-dos
122     'raw-text)
123   "Text-safe coding system (For removing ^M).
124 This variable is a substitute for `mm-text-coding-system'.")
125
126 (defvar nnheader-text-coding-system-for-write nil
127   "Text coding system for write.
128 This variable is a substitute for `mm-text-coding-system-for-write'.")
129
130 (defvar nnheader-auto-save-coding-system
131   (cond
132    ((not (fboundp 'find-coding-system)) nil)
133    ((find-coding-system 'emacs-mule)
134     (if (memq system-type '(windows-nt ms-dos ms-windows))
135         'emacs-mule-dos 'emacs-mule))
136    ((find-coding-system 'escape-quoted) 'escape-quoted)
137    ((find-coding-system 'no-conversion) 'no-conversion)
138    (t nil))
139   "Coding system of auto save file.")
140
141 (defvar nnheader-directory-separator-character
142   (string-to-char (substring (file-name-as-directory ".") -1))
143   "*A character used to a directory separator.")
144
145 (eval-and-compile
146   (autoload 'nnmail-message-id "nnmail")
147   (autoload 'mail-position-on-field "sendmail")
148   (autoload 'message-remove-header "message")
149   (autoload 'gnus-buffer-live-p "gnus-util"))
150
151 ;; mm-util stuff.
152 (defvar mm-emacs-mule t "True in Emacs with Mule.")
153
154 (unless (featurep 'mm-util)
155   ;; Should keep track of `mm-image-load-path' in mm-util.el.
156   (defun nnheader-image-load-path (&optional package)
157     (let (dir result)
158       (dolist (path load-path (nreverse result))
159         (if (file-directory-p
160              (setq dir (concat (file-name-directory
161                                 (directory-file-name path))
162                                "etc/" (or package "gnus/"))))
163             (push dir result))
164         (push path result))))
165   (defalias 'mm-image-load-path 'nnheader-image-load-path)
166
167   ;; Should keep track of `mm-read-coding-system' in mm-util.el.
168   (defalias 'mm-read-coding-system 'read-coding-system)
169
170   ;; Should keep track of `mm-%s' in mm-util.el.
171   (defalias 'mm-multibyte-string-p
172     (if (fboundp 'multibyte-string-p)
173         'multibyte-string-p
174       'ignore))
175   (defalias 'mm-encode-coding-string 'encode-coding-string)
176   (defalias 'mm-decode-coding-string 'decode-coding-string)
177   (defalias 'mm-encode-coding-region 'encode-coding-region)
178   (defalias 'mm-decode-coding-region 'decode-coding-region)
179   (defalias 'mm-set-buffer-file-coding-system 'set-buffer-file-coding-system)
180
181   ;; Should keep track of `mm-detect-coding-region' in mm-util.el.
182   (defun nnheader-detect-coding-region (start end)
183     "Like 'detect-coding-region' except returning the best one."
184     (let ((coding-systems (detect-coding-region (point) (point-max))))
185       (or (car-safe coding-systems)
186           coding-systems)))
187   (defalias 'mm-detect-coding-region 'nnheader-detect-coding-region)
188
189   ;; Should keep track of `mm-detect-mime-charset-region' in mm-util.el.
190   (defun nnheader-detect-mime-charset-region (start end)
191     "Detect MIME charset of the text in the region between START and END."
192     (coding-system-to-mime-charset
193      (nnheader-detect-coding-region start end)))
194   (defalias 'mm-detect-mime-charset-region
195     'nnheader-detect-mime-charset-region)
196
197   ;; Should keep track of `mm-with-unibyte-buffer' in mm-util.el.
198   (defmacro nnheader-with-unibyte-buffer (&rest forms)
199     "Create a temporary buffer, and evaluate FORMS there like `progn'.
200 Use unibyte mode for this."
201     `(let (default-enable-multibyte-characters)
202        (with-temp-buffer ,@forms)))
203   (put 'nnheader-with-unibyte-buffer 'lisp-indent-function 0)
204   (put 'nnheader-with-unibyte-buffer 'edebug-form-spec '(body))
205   (put 'mm-with-unibyte-buffer 'lisp-indent-function 0)
206   (put 'mm-with-unibyte-buffer 'edebug-form-spec '(body))
207   (defalias 'mm-with-unibyte-buffer 'nnheader-with-unibyte-buffer)
208
209   ;; Should keep track of `mm-with-multibyte-buffer' in mm-util.el.
210   (defmacro nnheader-with-multibyte-buffer (&rest forms)
211     "Create a temporary buffer, and evaluate FORMS there like `progn'.
212 Use multibyte mode for this."
213     `(let ((default-enable-multibyte-characters t))
214        (with-temp-buffer ,@forms)))
215   (put 'nnheader-with-multibyte-buffer 'lisp-indent-function 0)
216   (put 'nnheader-with-multibyte-buffer 'edebug-form-spec '(body))
217   (put 'mm-with-multibyte-buffer 'lisp-indent-function 0)
218   (put 'mm-with-multibyte-buffer 'edebug-form-spec '(body))
219   (defalias 'mm-with-multibyte-buffer 'nnheader-with-multibyte-buffer)
220
221   ;; Should keep track of `mm-with-unibyte-current-buffer' in mm-util.el.
222   (defmacro nnheader-with-unibyte-current-buffer (&rest forms)
223     "Evaluate FORMS with current current buffer temporarily made unibyte.
224 Also bind `default-enable-multibyte-characters' to nil.
225 Equivalent to `progn' in XEmacs"
226     (let ((multibyte (make-symbol "multibyte"))
227           (buffer (make-symbol "buffer")))
228       (cond ((featurep 'xemacs)
229              `(let (default-enable-multibyte-characters)
230                 ,@forms))
231             (t
232              `(let ((,multibyte enable-multibyte-characters)
233                     (,buffer (current-buffer)))
234                 (unwind-protect
235                     (let (default-enable-multibyte-characters)
236                       (set-buffer-multibyte nil)
237                       ,@forms)
238                   (set-buffer ,buffer)
239                   (set-buffer-multibyte ,multibyte)))))))
240   (put 'nnheader-with-unibyte-current-buffer 'lisp-indent-function 0)
241   (put 'nnheader-with-unibyte-current-buffer 'edebug-form-spec '(body))
242   (put 'mm-with-unibyte-current-buffer 'lisp-indent-function 0)
243   (put 'mm-with-unibyte-current-buffer 'edebug-form-spec '(body))
244   (defalias 'mm-with-unibyte-current-buffer
245     'nnheader-with-unibyte-current-buffer)
246
247   ;; Should keep track of `mm-with-unibyte' in mm-util.el.
248   (defmacro nnheader-with-unibyte (&rest forms)
249     "Eval the FORMS with the default value of `enable-multibyte-characters'
250 nil, ."
251     `(let (default-enable-multibyte-characters)
252        ,@forms))
253   (put 'nnheader-with-unibyte 'lisp-indent-function 0)
254   (put 'nnheader-with-unibyte 'edebug-form-spec '(body))
255   (put 'mm-with-unibyte 'lisp-indent-function 0)
256   (put 'mm-with-unibyte 'edebug-form-spec '(body))
257   (defalias 'mm-with-unibyte 'nnheader-with-unibyte)
258
259   ;; Should keep track of `mm-guess-mime-charset' in mm-util.el.
260   (defun nnheader-guess-mime-charset ()
261     "Guess the default MIME charset from the language environment."
262     (let ((language-info
263            (and (boundp 'current-language-environment)
264                 (assoc current-language-environment
265                        language-info-alist)))
266           item)
267       (cond
268        ((null language-info)
269         'iso-8859-1)
270        ((setq item
271               (cadr
272                (or (assq 'coding-priority language-info)
273                    (assq 'coding-system language-info))))
274         (if (fboundp 'coding-system-get)
275             (or (coding-system-get item 'mime-charset)
276                 item)
277           item))
278        ((setq item (car (last (assq 'charset language-info))))
279         (if (eq item 'ascii)
280             'iso-8859-1
281           (charsets-to-mime-charset (list item))))
282        (t
283         'iso-8859-1))))
284   (defalias 'mm-guess-mime-charset 'nnheader-guess-mime-charset)
285
286   (defalias 'mm-char-int 'char-int)
287
288   ;; Should keep track of the same alias in mm-util.el.
289   (defalias 'mm-multibyte-p
290     (static-cond ((and (featurep 'xemacs) (featurep 'mule))
291                   (lambda nil t))
292                  ((featurep 'xemacs)
293                   (lambda nil nil))
294                  (t
295                   (lambda nil enable-multibyte-characters))))
296
297   ;; Should keep track of the same alias in mm-util.el.
298   (defalias 'mm-make-temp-file
299     (if (fboundp 'make-temp-file)
300         'make-temp-file
301       (lambda (prefix &optional dir-flag)
302         (let ((file (expand-file-name
303                      (make-temp-name prefix)
304                      (if (fboundp 'temp-directory)
305                          (temp-directory)
306                        temporary-file-directory))))
307           (if dir-flag
308               (make-directory file))
309           file))))
310
311   ;; Should keep track of `mm-coding-system-p' in mm-util.el.
312   (defun nnheader-coding-system-p (sym)
313     "Return non-nil if SYM is a coding system."
314     (or (and (fboundp 'find-coding-system) (find-coding-system sym))
315         (and (fboundp 'coding-system-p) (coding-system-p sym))))
316   (defalias 'mm-coding-system-p 'nnheader-coding-system-p)
317
318   (defalias 'mm-disable-multibyte
319     (static-if (featurep 'xemacs)
320         'ignore
321       (lambda nil (set-buffer-multibyte nil))))
322   (defalias 'mm-enable-multibyte
323     (static-if (featurep 'xemacs)
324         'ignore
325       ;; Why isn't it t but `to'?  See mm-util.el.
326       (lambda nil (set-buffer-multibyte 'to))))
327
328   (defalias 'mm-encode-coding-region 'encode-coding-region)
329
330   (defalias 'mm-string-make-unibyte
331     (if (fboundp 'string-make-unibyte)
332         'string-make-unibyte
333       'identity))
334
335   (defalias 'mm-char-or-char-int-p
336     (cond
337      ((fboundp 'char-or-char-int-p) 'char-or-char-int-p)
338      ((fboundp 'char-valid-p) 'char-valid-p)
339      (t 'identity))))
340
341 ;; mail-parse stuff.
342 (unless (featurep 'mail-parse)
343   (unless (fboundp 'std11-narrow-to-field)
344     (defalias 'std11-narrow-to-field
345       ;; Should keep track of `rfc2047-narrow-to-field' in rfc2047.el.
346       (lambda ()
347         "Narrow the buffer to the header on the current line."
348         (forward-line 0)
349         (narrow-to-region (point)
350                           (progn
351                             (std11-field-end)
352                             (when (eolp) (forward-line 1))
353                             (point)))
354         (goto-char (point-min)))))
355   (defalias 'mail-header-narrow-to-field 'std11-narrow-to-field)
356
357   ;; Should keep track of `ietf-drums-narrow-to-header' in ietf-drums.el.
358   (defun mail-narrow-to-head ()
359     "Narrow to the header section in the current buffer."
360     (narrow-to-region
361      (goto-char (point-min))
362      (if (re-search-forward "^\r?$" nil 1)
363          (match-beginning 0)
364        (point-max)))
365     (goto-char (point-min)))
366
367   (unless (fboundp 'std11-fold-region)
368     (defalias 'std11-fold-region
369       ;; Should keep track of `rfc2047-fold-region' in rfc2047.el.
370       (lambda (b e)
371         "Fold long lines in region B to E."
372         (save-restriction
373           (narrow-to-region b e)
374           (goto-char (point-min))
375           (let ((break nil)
376                 (qword-break nil)
377                 (first t)
378                 (bol (save-restriction
379                        (widen)
380                        (point-at-bol))))
381             (while (not (eobp))
382               (when (and (or break qword-break)
383                          (> (- (point) bol) 76))
384                 (goto-char (or break qword-break))
385                 (setq break nil
386                       qword-break nil)
387                 (if (looking-at "[ \t]")
388                     (insert "\n")
389                   (insert "\n "))
390                 (setq bol (1- (point)))
391                 ;; Don't break before the first non-LWSP characters.
392                 (skip-chars-forward " \t")
393                 (unless (eobp)
394                   (forward-char 1)))
395               (cond
396                ((eq (char-after) ?\n)
397                 (forward-char 1)
398                 (setq bol (point)
399                       break nil
400                       qword-break nil)
401                 (skip-chars-forward " \t")
402                 (unless (or (eobp) (eq (char-after) ?\n))
403                   (forward-char 1)))
404                ((eq (char-after) ?\r)
405                 (forward-char 1))
406                ((memq (char-after) '(?  ?\t))
407                 (skip-chars-forward " \t")
408                 (if first
409                     ;; Don't break just after the header name.
410                     (setq first nil)
411                   (setq break (1- (point)))))
412                ((not break)
413                 (if (not (looking-at "=\\?[^=]"))
414                     (if (eq (char-after) ?=)
415                         (forward-char 1)
416                       (skip-chars-forward "^ \t\n\r="))
417                   (setq qword-break (point))
418                   (skip-chars-forward "^ \t\n\r")))
419                (t
420                 (skip-chars-forward "^ \t\n\r"))))
421             (when (and (or break qword-break)
422                        (> (- (point) bol) 76))
423               (goto-char (or break qword-break))
424               (setq break nil
425                     qword-break nil)
426               (if (looking-at "[ \t]")
427                   (insert "\n")
428                 (insert "\n "))
429               (setq bol (1- (point)))
430               ;; Don't break before the first non-LWSP characters.
431               (skip-chars-forward " \t")
432               (unless (eobp)
433                 (forward-char 1))))))))
434
435   (unless (fboundp 'std11-fold-field)
436     (defalias 'std11-fold-field
437       ;; Should keep track of `rfc2047-fold-field' in rfc2047.el.
438       (lambda ()
439         "Fold the current line."
440         (save-excursion
441           (save-restriction
442             (std11-narrow-to-field)
443             (std11-fold-region (point-min) (point-max)))))))
444   (defalias 'mail-header-fold-field 'std11-fold-field)
445
446   (unless (fboundp 'std11-unfold-region)
447     (defalias 'std11-unfold-region
448       ;; Should keep track of `rfc2047-unfold-region' in rfc2047.el.
449       (lambda (b e)
450         "Unfold lines in region B to E."
451         (save-restriction
452           (narrow-to-region b e)
453           (goto-char (point-min))
454           (let ((bol (save-restriction
455                        (widen)
456                        (point-at-bol)))
457                 (eol (point-at-eol)))
458             (forward-line 1)
459             (while (not (eobp))
460               (if (and (looking-at "[ \t]")
461                        (< (- (point-at-eol) bol) 76))
462                   (delete-region eol (progn
463                                        (goto-char eol)
464                                        (skip-chars-forward "\r\n")
465                                        (point)))
466                 (setq bol (point-at-bol)))
467               (setq eol (point-at-eol))
468               (forward-line 1)))))))
469
470   (unless (fboundp 'std11-unfold-field)
471     (defalias 'std11-unfold-field
472       ;; Should keep track of `rfc2047-unfold-field' in rfc2047.el.
473       (lambda ()
474         "Fold the current line."
475         (save-excursion
476           (save-restriction
477             (std11-narrow-to-field)
478             (std11-unfold-region (point-min) (point-max)))))))
479   (defalias 'mail-header-unfold-field 'std11-unfold-field)
480
481   (unless (fboundp 'std11-extract-addresses-components)
482     (defalias 'std11-extract-addresses-components
483       ;; This is the original function in T-gnus.
484       (lambda (string)
485         "Extract a list of full name and canonical address from STRING.  Each
486 element looks like a list of the form (FULL-NAME CANONICAL-ADDRESS).
487 If no name can be extracted, FULL-NAME will be nil."
488         (when string
489           (let (addresses)
490             (dolist (structure (std11-parse-addresses-string
491                                 (std11-unfold-string string))
492                                addresses)
493               (push (list (std11-full-name-string structure)
494                           (std11-address-string structure))
495                     addresses))
496             (nreverse addresses))))))
497
498   ;; Should keep track of `ietf-drums-parse-addresses' in ietf-drums.el.
499   (defun mail-header-parse-addresses (string)
500     "Parse STRING and return a list of MAILBOX / DISPLAY-NAME pairs."
501     (mapcar (function
502              (lambda (components)
503                (cons (nth 1 components) (car components))))
504             (std11-extract-addresses-components string)))
505
506   ;; Should keep track of `rfc2047-field-value' in rfc2047.el.
507   (defun std11-field-value (&optional dont-include-last-newline)
508     "Return the value of the field at point.  If the optional argument is
509 given, the return value will not contain the last newline."
510     (let ((begin (point))
511           (inhibit-point-motion-hooks t)
512           start value)
513       (beginning-of-line)
514       (unless (eobp)
515         (while (and (memq (char-after) '(?\t ?\ ))
516                     (zerop (forward-line -1))))
517         (when (looking-at "[^\t\n ]+:[\t\n ]+")
518           (goto-char (setq start (match-end 0)))
519           (forward-line 1)
520           (while (and (memq (char-after) '(?\t ?\ ))
521                       (zerop (forward-line 1))))
522           (when dont-include-last-newline
523             (skip-chars-backward "\t\n " start))
524           (setq value (buffer-substring start (point)))))
525       (goto-char begin)
526       value))
527   (defalias 'mail-header-field-value 'std11-field-value))
528
529 ;; ietf-drums stuff.
530 (unless (featurep 'ietf-drums)
531   ;; Should keep track of `ietf-drums-unfold-fws' in ietf-drums.el.
532   (defun nnheader-unfold-fws ()
533     "Unfold folding white space in the current buffer."
534     (goto-char (point-min))
535     (while (re-search-forward "[ \t]*\n[ \t]+" nil t)
536       (replace-match " " t t))
537     (goto-char (point-min)))
538
539   (defalias 'ietf-drums-unfold-fws 'nnheader-unfold-fws))
540
541 ;;; Header access macros.
542
543 ;; These macros may look very much like the ones in GNUS 4.1.  They
544 ;; are, in a way, but you should note that the indices they use have
545 ;; been changed from the internal GNUS format to the NOV format.  The
546 ;; makes it possible to read headers from XOVER much faster.
547 ;;
548 ;; The format of a header is now:
549 ;; [number subject from date id references chars lines xref extra]
550 ;;
551 ;; (That next-to-last entry is defined as "misc" in the NOV format,
552 ;; but Gnus uses it for xrefs.)
553
554 (require 'mmgnus)
555
556 (defmacro mail-header-number (header)
557   "Return article number in HEADER."
558   `(mime-entity-location-internal ,header))
559
560 (defmacro mail-header-set-number (header number)
561   "Set article number of HEADER to NUMBER."
562   `(mime-entity-set-location-internal ,header ,number))
563
564 (defalias 'mail-header-subject 'mime-gnus-entity-subject-internal)
565 (defalias 'mail-header-set-subject 'mime-gnus-entity-set-subject-internal)
566
567 (defalias 'mail-header-from 'mime-gnus-entity-from-internal)
568 (defalias 'mail-header-set-from 'mime-gnus-entity-set-from-internal)
569
570 (defalias 'mail-header-date 'mime-gnus-entity-date-internal)
571 (defalias 'mail-header-set-date 'mime-gnus-entity-set-date-internal)
572
573 (defalias 'mail-header-message-id 'mime-gnus-entity-id-internal)
574 (defalias 'mail-header-id 'mime-gnus-entity-id-internal)
575 (defalias 'mail-header-set-message-id 'mime-gnus-entity-set-id-internal)
576 (defalias 'mail-header-set-id 'mime-gnus-entity-set-id-internal)
577
578 (defalias 'mail-header-references 'mime-gnus-entity-references-internal)
579 (defalias 'mail-header-set-references
580   'mime-gnus-entity-set-references-internal)
581
582 (defalias 'mail-header-chars 'mime-gnus-entity-chars-internal)
583 (defalias 'mail-header-set-chars 'mime-gnus-entity-set-chars-internal)
584
585 (defalias 'mail-header-lines 'mime-gnus-entity-lines-internal)
586 (defalias 'mail-header-set-lines 'mime-gnus-entity-set-lines-internal)
587
588 (defalias 'mail-header-xref 'mime-gnus-entity-xref-internal)
589 (defalias 'mail-header-set-xref 'mime-gnus-entity-set-xref-internal)
590
591 (defalias 'nnheader-decode-subject
592   (mime-find-field-decoder 'Subject 'nov))
593 (defalias 'nnheader-decode-from
594   (mime-find-field-decoder 'From 'nov))
595
596 (defalias 'mail-header-extra 'mime-gnus-entity-extra-internal)
597
598 (defun mail-header-set-extra (header extra)
599   "Set the extra headers in HEADER to EXTRA."
600   (mime-gnus-entity-set-extra-internal header extra))
601
602 (defun nnheader-decode-field-body (field-body field-name
603                                               &optional mode max-column)
604   (mime-decode-field-body field-body
605                           (if (stringp field-name)
606                               (intern (capitalize field-name))
607                             field-name)
608                           mode max-column))
609
610 (defsubst make-full-mail-header (&optional number subject from date id
611                                            references chars lines xref
612                                            extra)
613   "Create a new mail header structure initialized with the parameters given."
614   (luna-make-entity (mm-expand-class-name 'gnus)
615                     :location number
616                     :subject (if subject
617                                  (nnheader-decode-subject subject))
618                     :from (if from
619                               (nnheader-decode-from from))
620                     :date date
621                     :id id
622                     :references references
623                     :chars chars
624                     :lines lines
625                     :xref xref
626                     :original-header (list (cons 'Subject subject)
627                                            (cons 'From from))
628                     :extra extra))
629
630 (defsubst make-full-mail-header-from-decoded-header
631   (&optional number subject from date id references chars lines xref extra)
632   "Create a new mail header structure initialized with the parameters given."
633   (luna-make-entity (mm-expand-class-name 'gnus)
634                     :location number
635                     :subject subject
636                     :from from
637                     :date date
638                     :id id
639                     :references references
640                     :chars chars
641                     :lines lines
642                     :xref xref
643                     :extra extra))
644
645 (defsubst make-mail-header (&optional init)
646   "Create a new mail header structure initialized with INIT."
647   (make-full-mail-header init init init init init
648                          init init init init init))
649
650 ;; fake message-ids: generation and detection
651
652 (defvar nnheader-fake-message-id 1)
653
654 (defsubst nnheader-generate-fake-message-id (&optional number)
655   (if (numberp number)
656       (format "fake+none+%s+%d" gnus-newsgroup-name number)
657     (format "fake+none+%s+%s"
658             gnus-newsgroup-name
659             (int-to-string (incf nnheader-fake-message-id)))))
660
661 (defsubst nnheader-fake-message-id-p (id)
662   (save-match-data                      ; regular message-id's are <.*>
663     (string-match "\\`fake\\+none\\+.*\\+[0-9]+\\'" id)))
664
665 ;; Parsing headers and NOV lines.
666
667 (defsubst nnheader-remove-cr-followed-by-lf ()
668   (goto-char (point-max))
669   (while (search-backward "\r\n" nil t)
670     (delete-char 1)))
671
672 (defsubst nnheader-header-value ()
673   (let ((pt (point)))
674     (prog2
675         (skip-chars-forward " \t")
676         (buffer-substring (point) (std11-field-end))
677       (goto-char pt))))
678
679 (defun nnheader-parse-naked-head (&optional number)
680   ;; This function unfolds continuation lines in this buffer
681   ;; destructively.  When this side effect is unwanted, use
682   ;; `nnheader-parse-head' instead of this function.
683   (let ((case-fold-search t)
684         (buffer-read-only nil)
685         (cur (current-buffer))
686         (p (point-min))
687         in-reply-to lines ref)
688     (nnheader-remove-cr-followed-by-lf)
689     (ietf-drums-unfold-fws)
690     (subst-char-in-region (point-min) (point-max) ?\t ? )
691     (goto-char p)
692     (insert "\n")
693     (prog1
694         ;; This implementation of this function, with nine
695         ;; search-forwards instead of the one re-search-forward and a
696         ;; case (which basically was the old function) is actually
697         ;; about twice as fast, even though it looks messier.  You
698         ;; can't have everything, I guess.  Speed and elegance don't
699         ;; always go hand in hand.
700         (make-full-mail-header
701          ;; Number.
702          (or number 0)
703          ;; Subject.
704          (progn
705            (goto-char p)
706            (if (search-forward "\nsubject:" nil t)
707                (nnheader-header-value) "(none)"))
708          ;; From.
709          (progn
710            (goto-char p)
711            (if (search-forward "\nfrom:" nil t)
712                (nnheader-header-value) "(nobody)"))
713          ;; Date.
714          (progn
715            (goto-char p)
716            (if (search-forward "\ndate:" nil t)
717                (nnheader-header-value) ""))
718          ;; Message-ID.
719          (progn
720            (goto-char p)
721            (if (search-forward "\nmessage-id:" nil t)
722                (buffer-substring
723                 (1- (or (search-forward "<" (point-at-eol) t)
724                         (point)))
725                 (or (search-forward ">" (point-at-eol) t) (point)))
726              ;; If there was no message-id, we just fake one to make
727              ;; subsequent routines simpler.
728              (nnheader-generate-fake-message-id number)))
729          ;; References.
730          (progn
731            (goto-char p)
732            (if (search-forward "\nreferences:" nil t)
733                (nnheader-header-value)
734              ;; Get the references from the in-reply-to header if
735              ;; there were no references and the in-reply-to header
736              ;; looks promising.
737              (if (and (search-forward "\nin-reply-to:" nil t)
738                       (setq in-reply-to (nnheader-header-value))
739                       (string-match "<[^\n>]+>" in-reply-to))
740                  (let (ref2)
741                    (setq ref (substring in-reply-to (match-beginning 0)
742                                         (match-end 0)))
743                    (while (string-match "<[^\n>]+>"
744                                         in-reply-to (match-end 0))
745                      (setq ref2 (substring in-reply-to (match-beginning 0)
746                                            (match-end 0)))
747                      (when (> (length ref2) (length ref))
748                        (setq ref ref2)))
749                    ref)
750                nil)))
751          ;; Chars.
752          0
753          ;; Lines.
754          (progn
755            (goto-char p)
756            (if (search-forward "\nlines: " nil t)
757                (if (numberp (setq lines (read cur)))
758                    lines 0)
759              0))
760          ;; Xref.
761          (progn
762            (goto-char p)
763            (and (search-forward "\nxref:" nil t)
764                 (nnheader-header-value)))
765          ;; Extra.
766          (when nnmail-extra-headers
767            (let ((extra nnmail-extra-headers)
768                  out)
769              (while extra
770                (goto-char p)
771                (when (search-forward
772                       (concat "\n" (symbol-name (car extra)) ":") nil t)
773                  (push (cons (car extra) (nnheader-header-value))
774                        out))
775                (pop extra))
776              out)))
777       (goto-char p)
778       (delete-char 1))))
779
780 (defun nnheader-parse-head (&optional naked)
781   (let ((cur (current-buffer)) num beg end)
782     (when (if naked
783               (setq num 0
784                     beg (point-min)
785                     end (point-max))
786             (goto-char (point-min))
787             ;; Search to the beginning of the next header.  Error
788             ;; messages do not begin with 2 or 3.
789             (when (re-search-forward "^[23][0-9]+ " nil t)
790               (end-of-line)
791               (setq num (read cur)
792                     beg (point)
793                     end (if (search-forward "\n.\n" nil t)
794                             (- (point) 2)
795                           (point)))))
796       (with-temp-buffer
797         (insert-buffer-substring cur beg end)
798         (nnheader-parse-naked-head num)))))
799
800 (defmacro nnheader-nov-skip-field ()
801   '(search-forward "\t" eol 'move))
802
803 (defmacro nnheader-nov-field ()
804   '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
805
806 (defmacro nnheader-nov-read-integer ()
807   '(prog1
808        (if (eq (char-after) ?\t)
809            0
810          (let ((num (condition-case nil
811                         (read (current-buffer))
812                       (error nil))))
813            (if (numberp num) num 0)))
814      (unless (eobp)
815        (search-forward "\t" eol 'move))))
816
817 (defmacro nnheader-nov-parse-extra ()
818   '(let (out string)
819      (while (not (memq (char-after) '(?\n nil)))
820        (setq string (nnheader-nov-field))
821        (when (string-match "^\\([^ :]+\\): " string)
822          (push (cons (intern (match-string 1 string))
823                      (substring string (match-end 0)))
824                out)))
825      out))
826
827 (defvar nnheader-uniquify-message-id nil)
828
829 (defmacro nnheader-nov-read-message-id (&optional number)
830   `(let ((id (nnheader-nov-field)))
831      (if (string-match "^<[^>]+>$" id)
832          ,(if nnheader-uniquify-message-id
833               `(if (string-match "__[^@]+@" id)
834                    (concat (substring id 0 (match-beginning 0))
835                            (substring id (1- (match-end 0))))
836                  id)
837             'id)
838        (nnheader-generate-fake-message-id ,number))))
839
840 (defun nnheader-parse-nov ()
841   (let* ((eol (point-at-eol))
842          (number (nnheader-nov-read-integer)))
843     (make-full-mail-header
844      number                             ; number
845      (nnheader-nov-field)               ; subject
846      (nnheader-nov-field)               ; from
847      (nnheader-nov-field)               ; date
848      (nnheader-nov-read-message-id number) ; id
849      (nnheader-nov-field)               ; refs
850      (nnheader-nov-read-integer)        ; chars
851      (nnheader-nov-read-integer)        ; lines
852      (if (eq (char-after) ?\n)
853          nil
854        (if (looking-at "Xref: ")
855            (goto-char (match-end 0)))
856        (nnheader-nov-field))            ; Xref
857      (nnheader-nov-parse-extra))))      ; extra
858
859 (defun nnheader-insert-nov (header)
860   (princ (mail-header-number header) (current-buffer))
861   (let ((p (point)))
862     (insert
863      "\t"
864      (or (mime-entity-fetch-field header 'Subject) "(none)") "\t"
865      (or (mime-entity-fetch-field header 'From) "(nobody)") "\t"
866      (or (mail-header-date header) "") "\t"
867      (or (mail-header-id header)
868          (nnmail-message-id))
869      "\t"
870      (or (mail-header-references header) "") "\t")
871     (princ (or (mail-header-chars header) 0) (current-buffer))
872     (insert "\t")
873     (princ (or (mail-header-lines header) 0) (current-buffer))
874     (insert "\t")
875     (when (mail-header-xref header)
876       (insert "Xref: " (mail-header-xref header)))
877     (when (or (mail-header-xref header)
878               (mail-header-extra header))
879       (insert "\t"))
880     (when (mail-header-extra header)
881       (let ((extra (mail-header-extra header)))
882         (while extra
883           (insert (symbol-name (caar extra))
884                   ": " (cdar extra) "\t")
885           (pop extra))))
886     (insert "\n")
887     (backward-char 1)
888     (while (search-backward "\n" p t)
889       (delete-char 1))
890     (forward-line 1)))
891
892 (defun nnheader-parse-overview-file (file)
893   "Parse FILE and return a list of headers."
894   (mm-with-unibyte-buffer
895     (nnheader-insert-file-contents file)
896     (goto-char (point-min))
897     (let (headers)
898       (while (not (eobp))
899         (push (nnheader-parse-nov) headers)
900         (forward-line 1))
901       (nreverse headers))))
902
903 (defun nnheader-write-overview-file (file headers)
904   "Write HEADERS to FILE."
905   (with-temp-file file
906     (mapcar 'nnheader-insert-nov headers)))
907
908 (defun nnheader-insert-header (header)
909   (insert
910    "Subject: " (or (mail-header-subject header) "(none)") "\n"
911    "From: " (or (mail-header-from header) "(nobody)") "\n"
912    "Date: " (or (mail-header-date header) "") "\n"
913    "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
914    "References: " (or (mail-header-references header) "") "\n"
915    "Lines: ")
916   (princ (or (mail-header-lines header) 0) (current-buffer))
917   (insert "\n\n"))
918
919 (defun nnheader-insert-article-line (article)
920   (goto-char (point-min))
921   (insert "220 ")
922   (princ article (current-buffer))
923   (insert " Article retrieved.\n")
924   (search-forward "\n\n" nil 'move)
925   (delete-region (point) (point-max))
926   (forward-char -1)
927   (insert "."))
928
929 (defun nnheader-nov-delete-outside-range (beg end)
930   "Delete all NOV lines that lie outside the BEG to END range."
931   ;; First we find the first wanted line.
932   (nnheader-find-nov-line beg)
933   (delete-region (point-min) (point))
934   ;; Then we find the last wanted line.
935   (when (nnheader-find-nov-line end)
936     (forward-line 1))
937   (delete-region (point) (point-max)))
938
939 (defun nnheader-find-nov-line (article)
940   "Put point at the NOV line that start with ARTICLE.
941 If ARTICLE doesn't exist, put point where that line
942 would have been.  The function will return non-nil if
943 the line could be found."
944   ;; This function basically does a binary search.
945   (let ((max (point-max))
946         (min (goto-char (point-min)))
947         (cur (current-buffer))
948         (prev (point-min))
949         num found)
950     (while (not found)
951       (goto-char (+ min (/ (- max min) 2)))
952       (beginning-of-line)
953       (if (or (= (point) prev)
954               (eobp))
955           (setq found t)
956         (setq prev (point))
957         (while (and (not (numberp (setq num (read cur))))
958                     (not (eobp)))
959           (gnus-delete-line))
960         (cond ((> num article)
961                (setq max (point)))
962               ((< num article)
963                (setq min (point)))
964               (t
965                (setq found 'yes)))))
966     ;; We may be at the first line.
967     (when (and (not num)
968                (not (eobp)))
969       (setq num (read cur)))
970     ;; Now we may have found the article we're looking for, or we
971     ;; may be somewhere near it.
972     (when (and (not (eq found 'yes))
973                (not (eq num article)))
974       (setq found (point))
975       (while (and (< (point) max)
976                   (or (not (numberp num))
977                       (< num article)))
978         (forward-line 1)
979         (setq found (point))
980         (or (eobp)
981             (= (setq num (read cur)) article)))
982       (unless (eq num article)
983         (goto-char found)))
984     (beginning-of-line)
985     (eq num article)))
986
987 (defun nnheader-retrieve-headers-from-directory* (articles
988                                                   directory dependencies
989                                                   &optional
990                                                   fetch-old force-new large
991                                                   backend)
992   (with-temp-buffer
993     (let* ((file nil)
994            (number (length articles))
995            (count 0)
996            (file-name-coding-system 'binary)
997            (case-fold-search t)
998            (cur (current-buffer))
999            article
1000            headers header id end ref in-reply-to lines chars ctype)
1001       ;; We don't support fetching by Message-ID.
1002       (if (stringp (car articles))
1003           'headers
1004         (while articles
1005           (when (and (file-exists-p
1006                       (setq file (expand-file-name
1007                                   (int-to-string
1008                                    (setq article (pop articles)))
1009                                   directory)))
1010                      (not (file-directory-p file)))
1011             (erase-buffer)
1012             (nnheader-insert-head file)
1013             (save-restriction
1014               (std11-narrow-to-header)
1015               (setq
1016                header
1017                (make-full-mail-header
1018                 ;; Number.
1019                 article
1020                 ;; Subject.
1021                 (or (std11-fetch-field "Subject")
1022                     "(none)")
1023                 ;; From.
1024                 (or (std11-fetch-field "From")
1025                     "(nobody)")
1026                 ;; Date.
1027                 (or (std11-fetch-field "Date")
1028                     "")
1029                 ;; Message-ID.
1030                 (progn
1031                   (goto-char (point-min))
1032                   (setq id (if (re-search-forward
1033                                 "^Message-ID: *\\(<[^\n\t> ]+>\\)" nil t)
1034                                ;; We do it this way to make sure the Message-ID
1035                                ;; is (somewhat) syntactically valid.
1036                                (buffer-substring (match-beginning 1)
1037                                                  (match-end 1))
1038                              ;; If there was no message-id, we just fake one
1039                              ;; to make subsequent routines simpler.
1040                              (nnheader-generate-fake-message-id))))
1041                 ;; References.
1042                 (progn
1043                   (goto-char (point-min))
1044                   (if (search-forward "\nReferences: " nil t)
1045                       (progn
1046                         (setq end (point))
1047                         (prog1
1048                             (buffer-substring (match-end 0) (std11-field-end))
1049                           (setq ref
1050                                 (buffer-substring
1051                                  (progn
1052                                    ;; (end-of-line)
1053                                    (search-backward ">" end t)
1054                                    (1+ (point)))
1055                                  (progn
1056                                    (search-backward "<" end t)
1057                                    (point))))))
1058                     ;; Get the references from the in-reply-to header if there
1059                     ;; were no references and the in-reply-to header looks
1060                     ;; promising.
1061                     (if (and (search-forward "\nIn-Reply-To: " nil t)
1062                              (setq in-reply-to
1063                                    (buffer-substring (match-end 0)
1064                                                      (std11-field-end)))
1065                              (string-match "<[^>]+>" in-reply-to))
1066                         (let (ref2)
1067                           (setq ref (substring in-reply-to (match-beginning 0)
1068                                                (match-end 0)))
1069                           (while (string-match "<[^>]+>"
1070                                                in-reply-to (match-end 0))
1071                             (setq ref2
1072                                   (substring in-reply-to (match-beginning 0)
1073                                              (match-end 0)))
1074                             (when (> (length ref2) (length ref))
1075                               (setq ref ref2)))
1076                           ref)
1077                       (setq ref nil))))
1078                 ;; Chars.
1079                 (progn
1080                   (goto-char (point-min))
1081                   (if (search-forward "\nChars: " nil t)
1082                       (if (numberp (setq chars (ignore-errors (read cur))))
1083                           chars 0)
1084                     0))
1085                 ;; Lines.
1086                 (progn
1087                   (goto-char (point-min))
1088                   (if (search-forward "\nLines: " nil t)
1089                       (if (numberp (setq lines (ignore-errors (read cur))))
1090                           lines 0)
1091                     0))
1092                 ;; Xref.
1093                 (std11-fetch-field "Xref")
1094                 ))
1095               (goto-char (point-min))
1096               (if (setq ctype (std11-fetch-field "Content-Type"))
1097                   (mime-entity-set-content-type-internal
1098                    header (mime-parse-Content-Type ctype)))
1099               )
1100             (when (setq header
1101                         (gnus-dependencies-add-header
1102                          header dependencies force-new))
1103               (push header headers))
1104             )
1105           (setq count (1+ count))
1106
1107           (and large
1108                (zerop (% count 20))
1109                (nnheader-message 5 "%s: Receiving headers... %d%%"
1110                                  backend
1111                                  (/ (* count 100) number))))
1112
1113         (when large
1114           (nnheader-message 5 "%s: Receiving headers...done" backend))
1115
1116         headers))))
1117
1118 (defun nnheader-retrieve-headers-from-directory (articles
1119                                                  directory dependencies
1120                                                  &optional
1121                                                  fetch-old force-new large
1122                                                  backend)
1123   (cons 'header
1124         (nreverse (nnheader-retrieve-headers-from-directory*
1125                    articles directory dependencies
1126                    fetch-old force-new large backend))))
1127
1128 (defun nnheader-get-newsgroup-headers-xover* (sequence
1129                                               &optional
1130                                               force-new dependencies
1131                                               group)
1132   "Parse the news overview data in the server buffer, and return a
1133 list of headers that match SEQUENCE (see `nntp-retrieve-headers')."
1134   ;; Get the Xref when the users reads the articles since most/some
1135   ;; NNTP servers do not include Xrefs when using XOVER.
1136   ;; (setq gnus-article-internal-prepare-hook '(gnus-article-get-xrefs))
1137   (let ((cur nntp-server-buffer)
1138         number headers header)
1139     (save-excursion
1140       (set-buffer nntp-server-buffer)
1141       ;; Allow the user to mangle the headers before parsing them.
1142       (gnus-run-hooks 'gnus-parse-headers-hook)
1143       (goto-char (point-min))
1144       (while (not (eobp))
1145         (condition-case ()
1146             (while (and sequence (not (eobp)))
1147               (setq number (read cur))
1148               (while (and sequence
1149                           (< (car sequence) number))
1150                 (setq sequence (cdr sequence)))
1151               (and sequence
1152                    (eq number (car sequence))
1153                    (progn
1154                      (setq sequence (cdr sequence))
1155                      (setq header (inline
1156                                     (gnus-nov-parse-line
1157                                      number dependencies force-new))))
1158                    (push header headers))
1159               (forward-line 1))
1160           (error
1161            (gnus-error 4 "Strange nov line (%d)"
1162                        (count-lines (point-min) (point)))))
1163         (forward-line 1))
1164       ;; A common bug in inn is that if you have posted an article and
1165       ;; then retrieves the active file, it will answer correctly --
1166       ;; the new article is included.  However, a NOV entry for the
1167       ;; article may not have been generated yet, so this may fail.
1168       ;; We work around this problem by retrieving the last few
1169       ;; headers using HEAD.
1170       headers)))
1171
1172 ;; Various cruft the backends and Gnus need to communicate.
1173
1174 (defvar nntp-server-buffer nil)
1175 (defvar nntp-process-response nil)
1176 (defvar news-reply-yank-from nil)
1177 (defvar news-reply-yank-message-id nil)
1178
1179 (defvar nnheader-callback-function nil)
1180
1181 (defun nnheader-init-server-buffer ()
1182   "Initialize the Gnus-backend communication buffer."
1183   (save-excursion
1184     (unless (gnus-buffer-live-p nntp-server-buffer)
1185       (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
1186     (set-buffer nntp-server-buffer)
1187     (erase-buffer)
1188     (kill-all-local-variables)
1189     (setq case-fold-search t)           ;Should ignore case.
1190     (set (make-local-variable 'nntp-process-response) nil)
1191     t))
1192
1193 ;;; Various functions the backends use.
1194
1195 (defun nnheader-file-error (file)
1196   "Return a string that says what is wrong with FILE."
1197   (format
1198    (cond
1199     ((not (file-exists-p file))
1200      "%s does not exist")
1201     ((file-directory-p file)
1202      "%s is a directory")
1203     ((not (file-readable-p file))
1204      "%s is not readable"))
1205    file))
1206
1207 (defun nnheader-insert-head (file)
1208   "Insert the head of the article."
1209   (when (file-exists-p file)
1210     (if (eq nnheader-max-head-length t)
1211         ;; Just read the entire file.
1212         (nnheader-insert-file-contents file)
1213       ;; Read 1K blocks until we find a separator.
1214       (let ((beg 0)
1215             format-alist)
1216         (while (and (eq nnheader-head-chop-length
1217                         (nth 1 (nnheader-insert-file-contents
1218                                 file nil beg
1219                                 (incf beg nnheader-head-chop-length))))
1220                     (prog1 (not (search-forward "\n\n" nil t))
1221                       (goto-char (point-max)))
1222                     (or (null nnheader-max-head-length)
1223                         (< beg nnheader-max-head-length))))))
1224     t))
1225
1226 (defun nnheader-article-p ()
1227   "Say whether the current buffer looks like an article."
1228   (goto-char (point-min))
1229   (if (not (search-forward "\n\n" nil t))
1230       nil
1231     (narrow-to-region (point-min) (1- (point)))
1232     (goto-char (point-min))
1233     (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
1234       (goto-char (match-end 0)))
1235     (prog1
1236         (eobp)
1237       (widen))))
1238
1239 (defun nnheader-insert-references (references message-id)
1240   "Insert a References header based on REFERENCES and MESSAGE-ID."
1241   (if (and (not references) (not message-id))
1242       ;; This is invalid, but not all articles have Message-IDs.
1243       ()
1244     (mail-position-on-field "References")
1245     (let ((begin (point-at-bol))
1246           (fill-column 78)
1247           (fill-prefix "\t"))
1248       (when references
1249         (insert references))
1250       (when (and references message-id)
1251         (insert " "))
1252       (when message-id
1253         (insert message-id))
1254       ;; Fold long References lines to conform to RFC1036 (sort of).
1255       ;; The region must end with a newline to fill the region
1256       ;; without inserting extra newline.
1257       (fill-region-as-paragraph begin (1+ (point))))))
1258
1259 (defun nnheader-replace-header (header new-value)
1260   "Remove HEADER and insert the NEW-VALUE."
1261   (save-excursion
1262     (save-restriction
1263       (nnheader-narrow-to-headers)
1264       (prog1
1265           (message-remove-header header)
1266         (goto-char (point-max))
1267         (insert header ": " new-value "\n")))))
1268
1269 (defun nnheader-narrow-to-headers ()
1270   "Narrow to the head of an article."
1271   (widen)
1272   (narrow-to-region
1273    (goto-char (point-min))
1274    (if (search-forward "\n\n" nil t)
1275        (1- (point))
1276      (point-max)))
1277   (goto-char (point-min)))
1278
1279 (defun nnheader-get-lines-and-char ()
1280   "Return the number of lines and chars in the article body."
1281   (goto-char (point-min))
1282   (if (not (re-search-forward "\n\r?\n" nil t))
1283       (list 0 0)
1284     (list (count-lines (point) (point-max))
1285           (- (point-max) (point)))))
1286
1287 (defun nnheader-remove-body ()
1288   "Remove the body from an article in this current buffer."
1289   (goto-char (point-min))
1290   (when (re-search-forward "\n\r?\n" nil t)
1291     (delete-region (point) (point-max))))
1292
1293 (defun nnheader-set-temp-buffer (name &optional noerase)
1294   "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
1295   (set-buffer (get-buffer-create name))
1296   (buffer-disable-undo)
1297   (unless noerase
1298     (erase-buffer))
1299   (current-buffer))
1300
1301 (eval-when-compile (defvar jka-compr-compression-info-list))
1302 (defvar nnheader-numerical-files
1303   (if (boundp 'jka-compr-compression-info-list)
1304       (concat "\\([0-9]+\\)\\("
1305               (mapconcat (lambda (i) (aref i 0))
1306                          jka-compr-compression-info-list "\\|")
1307               "\\)?")
1308     "[0-9]+$")
1309   "Regexp that match numerical files.")
1310
1311 (defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
1312   "Regexp that matches numerical file names.")
1313
1314 (defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
1315   "Regexp that matches numerical full file names.")
1316
1317 (defsubst nnheader-file-to-number (file)
1318   "Take a FILE name and return the article number."
1319   (if (string= nnheader-numerical-short-files "^[0-9]+$")
1320       (string-to-number file)
1321     (string-match nnheader-numerical-short-files file)
1322     (string-to-number (match-string 0 file))))
1323
1324 (defvar nnheader-directory-files-is-safe
1325   (or (eq system-type 'windows-nt)
1326       (not (featurep 'xemacs)))
1327   "If non-nil, Gnus believes `directory-files' is safe.
1328 It has been reported numerous times that `directory-files' fails with
1329 an alarming frequency on NFS mounted file systems. If it is nil,
1330 `nnheader-directory-files-safe' is used.")
1331
1332 (defun nnheader-directory-files-safe (&rest args)
1333   "Execute `directory-files' twice and returns the longer result."
1334   (let ((first (apply 'directory-files args))
1335         (second (apply 'directory-files args)))
1336     (if (> (length first) (length second))
1337         first
1338       second)))
1339
1340 (defun nnheader-directory-articles (dir)
1341   "Return a list of all article files in directory DIR."
1342   (mapcar 'nnheader-file-to-number
1343           (if nnheader-directory-files-is-safe
1344               (directory-files
1345                dir nil nnheader-numerical-short-files t)
1346             (nnheader-directory-files-safe
1347              dir nil nnheader-numerical-short-files t))))
1348
1349 (defun nnheader-article-to-file-alist (dir)
1350   "Return an alist of article/file pairs in DIR."
1351   (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
1352           (if nnheader-directory-files-is-safe
1353               (directory-files
1354                dir nil nnheader-numerical-short-files t)
1355             (nnheader-directory-files-safe
1356              dir nil nnheader-numerical-short-files t))))
1357
1358 (defun nnheader-fold-continuation-lines ()
1359   "Fold continuation lines in the current buffer."
1360   (nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
1361
1362 (defun nnheader-translate-file-chars (file &optional full)
1363   "Translate FILE into something that can be a file name.
1364 If FULL, translate everything."
1365   (if (null nnheader-file-name-translation-alist)
1366       ;; No translation is necessary.
1367       file
1368     (let* ((i 0)
1369            trans leaf path len)
1370       (if full
1371           ;; Do complete translation.
1372           (setq leaf (copy-sequence file)
1373                 path ""
1374                 i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1)))
1375                       2 0))
1376         ;; We translate -- but only the file name.  We leave the directory
1377         ;; alone.
1378         (if (and (featurep 'xemacs)
1379                  (memq system-type '(cygwin32 win32 w32 mswindows windows-nt
1380                                               cygwin)))
1381             ;; This is needed on NT and stuff, because
1382             ;; file-name-nondirectory is not enough to split
1383             ;; file names, containing ':', e.g.
1384             ;; "d:\\Work\\News\\nntp+news.fido7.ru:fido7.ru.gnu.SCORE"
1385             ;;
1386             ;; we are trying to correctly split such names:
1387             ;; "d:file.name" -> "a:" "file.name"
1388             ;; "aaa:bbb.ccc" -> "" "aaa:bbb.ccc"
1389             ;; "d:aaa\\bbb:ccc"   -> "d:aaa\\" "bbb:ccc"
1390             ;; etc.
1391             ;; to translate then only the file name part.
1392             (progn
1393               (setq leaf file
1394                     path "")
1395               (if (string-match "\\(^\\w:\\|[/\\]\\)\\([^/\\]+\\)$" file)
1396                   (setq leaf (substring file (match-beginning 2))
1397                         path (substring file 0 (match-beginning 2)))))
1398           ;; Emacs DTRT, says andrewi.
1399           (setq leaf (file-name-nondirectory file)
1400                 path (file-name-directory file))))
1401       (setq len (length leaf))
1402       (while (< i len)
1403         (when (setq trans (cdr (assq (aref leaf i)
1404                                      nnheader-file-name-translation-alist)))
1405           (aset leaf i trans))
1406         (incf i))
1407       (concat path leaf))))
1408
1409 (defun nnheader-report (backend &rest args)
1410   "Report an error from the BACKEND.
1411 The first string in ARGS can be a format string."
1412   (set (intern (format "%s-status-string" backend))
1413        (if (< (length args) 2)
1414            (car args)
1415          (apply 'format args)))
1416   nil)
1417
1418 (defun nnheader-get-report (backend)
1419   "Get the most recent report from BACKEND."
1420   (condition-case ()
1421       (nnheader-message 5 "%s" (symbol-value (intern (format "%s-status-string"
1422                                                              backend))))
1423     (error (nnheader-message 5 ""))))
1424
1425 (defun nnheader-insert (format &rest args)
1426   "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
1427 If FORMAT isn't a format string, it and all ARGS will be inserted
1428 without formatting."
1429   (save-excursion
1430     (set-buffer nntp-server-buffer)
1431     (erase-buffer)
1432     (if (string-match "%" format)
1433         (insert (apply 'format format args))
1434       (apply 'insert format args))
1435     t))
1436
1437 (static-if (fboundp 'subst-char-in-string)
1438     (defsubst nnheader-replace-chars-in-string (string from to)
1439       (subst-char-in-string from to string))
1440   (defun nnheader-replace-chars-in-string (string from to)
1441     "Replace characters in STRING from FROM to TO."
1442     (let ((string (substring string 0)) ;Copy string.
1443           (len (length string))
1444           (idx 0))
1445       ;; Replace all occurrences of FROM with TO.
1446       (while (< idx len)
1447         (when (= (aref string idx) from)
1448           (aset string idx to))
1449         (setq idx (1+ idx)))
1450       string)))
1451
1452 (defun nnheader-replace-duplicate-chars-in-string (string from to)
1453   "Replace characters in STRING from FROM to TO."
1454   (let ((string (substring string 0))   ;Copy string.
1455         (len (length string))
1456         (idx 0) prev i)
1457     ;; Replace all occurrences of FROM with TO.
1458     (while (< idx len)
1459       (setq i (aref string idx))
1460       (when (and (eq prev from) (= i from))
1461         (aset string (1- idx) to)
1462         (aset string idx to))
1463       (setq prev i)
1464       (setq idx (1+ idx)))
1465     string))
1466
1467 (defun nnheader-file-to-group (file &optional top)
1468   "Return a group name based on FILE and TOP."
1469   (nnheader-replace-chars-in-string
1470    (if (not top)
1471        file
1472      (condition-case ()
1473          (substring (expand-file-name file)
1474                     (length
1475                      (expand-file-name
1476                       (file-name-as-directory top))))
1477        (error "")))
1478    nnheader-directory-separator-character ?.))
1479
1480 (defun nnheader-message (level &rest args)
1481   "Message if the Gnus backends are talkative."
1482   (if (or (not (numberp gnus-verbose-backends))
1483           (<= level gnus-verbose-backends))
1484       (apply 'message args)
1485     (apply 'format args)))
1486
1487 (defun nnheader-be-verbose (level)
1488   "Return whether the backends should be verbose on LEVEL."
1489   (or (not (numberp gnus-verbose-backends))
1490       (<= level gnus-verbose-backends)))
1491
1492 (defvar nnheader-pathname-coding-system 'binary
1493   "*Coding system for file name.")
1494
1495 (defun nnheader-group-pathname (group dir &optional file)
1496   "Make file name for GROUP."
1497   (concat
1498    (let ((dir (file-name-as-directory (expand-file-name dir))))
1499      ;; If this directory exists, we use it directly.
1500      (file-name-as-directory
1501       (if (file-directory-p (concat dir group))
1502           (expand-file-name group dir)
1503         ;; If not, we translate dots into slashes.
1504         (expand-file-name (encode-coding-string
1505                            (nnheader-replace-chars-in-string group ?. ?/)
1506                            nnheader-pathname-coding-system)
1507                           dir))))
1508    (cond ((null file) "")
1509          ((numberp file) (int-to-string file))
1510          (t file))))
1511
1512 (defun nnheader-concat (dir &rest files)
1513   "Concat DIR as directory to FILES."
1514   (apply 'concat (file-name-as-directory dir) files))
1515
1516 (defun nnheader-ms-strip-cr ()
1517   "Strip ^M from the end of all lines."
1518   (save-excursion
1519     (nnheader-remove-cr-followed-by-lf)))
1520
1521 (defun nnheader-file-size (file)
1522   "Return the file size of FILE or 0."
1523   (or (nth 7 (file-attributes file)) 0))
1524
1525 (defun nnheader-find-etc-directory (package &optional file first)
1526   "Go through `load-path' and find the \"../etc/PACKAGE\" directory.
1527 This function will look in the parent directory of each `load-path'
1528 entry, and look for the \"etc\" directory there.
1529 If FILE, find the \".../etc/PACKAGE\" file instead.
1530 If FIRST is non-nil, return the directory or the file found at the
1531 first.  Otherwise, find the newest one, though it may take a time."
1532   (let ((path load-path)
1533         dir results)
1534     ;; We try to find the dir by looking at the load path,
1535     ;; stripping away the last component and adding "etc/".
1536     (while path
1537       (if (and (car path)
1538                (file-exists-p
1539                 (setq dir (concat
1540                            (file-name-directory
1541                             (directory-file-name (car path)))
1542                            "etc/" package
1543                            (if file "" "/"))))
1544                (or file (file-directory-p dir)))
1545           (progn
1546             (or (member dir results)
1547                 (push dir results))
1548             (setq path (if first nil (cdr path))))
1549         (setq path (cdr path))))
1550     (if (or first (not (cdr results)))
1551         (car results)
1552       (car (sort results 'file-newer-than-file-p)))))
1553
1554 (eval-when-compile
1555   (defvar ange-ftp-path-format)
1556   (defvar efs-path-regexp))
1557 (defun nnheader-re-read-dir (path)
1558   "Re-read directory PATH if PATH is on a remote system."
1559   (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
1560       (when (string-match efs-path-regexp path)
1561         (efs-re-read-dir path))
1562     (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
1563       (when (string-match (car ange-ftp-path-format) path)
1564         (ange-ftp-re-read-dir path)))))
1565
1566 (defvar nnheader-file-coding-system 'raw-text
1567   "Coding system used in file backends of Gnus.")
1568
1569 (defun nnheader-insert-file-contents (filename &optional visit beg end replace)
1570   "Like `insert-file-contents', q.v., but only reads in the file.
1571 A buffer may be modified in several ways after reading into the buffer due
1572 to advanced Emacs features, such as file-name-handlers, format decoding,
1573 find-file-hooks, etc.
1574   This function ensures that none of these modifications will take place."
1575   (let* ((format-alist nil)
1576          (auto-mode-alist (nnheader-auto-mode-alist))
1577          (default-major-mode 'fundamental-mode)
1578          (enable-local-variables nil)
1579          (after-insert-file-functions nil)
1580          (enable-local-eval nil)
1581          (ffh (if (boundp 'find-file-hook)
1582                   'find-file-hook
1583                 'find-file-hooks))
1584          (val (symbol-value ffh)))
1585     (set ffh nil)
1586     (unwind-protect
1587         (insert-file-contents-as-coding-system
1588          nnheader-file-coding-system filename visit beg end replace)
1589       (set ffh val))))
1590
1591 (defun nnheader-insert-nov-file (file first)
1592   (let ((size (nth 7 (file-attributes file)))
1593         (cutoff (* 32 1024)))
1594     (when size
1595       (if (< size cutoff)
1596           ;; If the file is small, we just load it.
1597           (nnheader-insert-file-contents file)
1598         ;; We start on the assumption that FIRST is pretty recent.  If
1599         ;; not, we just insert the rest of the file as well.
1600         (let (current)
1601           (nnheader-insert-file-contents file nil (- size cutoff) size)
1602           (goto-char (point-min))
1603           (delete-region (point) (or (search-forward "\n" nil 'move) (point)))
1604           (setq current (ignore-errors (read (current-buffer))))
1605           (if (and (numberp current)
1606                    (< current first))
1607               t
1608             (delete-region (point-min) (point-max))
1609             (nnheader-insert-file-contents file)))))))
1610
1611 (defun nnheader-find-file-noselect (&rest args)
1612   "Open a file with some variables bound.
1613 See `find-file-noselect' for the arguments."
1614   (let* ((format-alist nil)
1615          (auto-mode-alist (nnheader-auto-mode-alist))
1616          (default-major-mode 'fundamental-mode)
1617          (enable-local-variables nil)
1618          (after-insert-file-functions nil)
1619          (enable-local-eval nil)
1620          (ffh (if (boundp 'find-file-hook)
1621                   'find-file-hook
1622                 'find-file-hooks))
1623          (val (symbol-value ffh)))
1624     (set ffh nil)
1625     (unwind-protect
1626         (apply 'find-file-noselect-as-coding-system
1627                nnheader-file-coding-system args)
1628       (set ffh val))))
1629
1630 (defun nnheader-auto-mode-alist ()
1631   "Return an `auto-mode-alist' with only the .gz (etc) thingies."
1632   (let ((alist auto-mode-alist)
1633         out)
1634     (while alist
1635       (when (listp (cdar alist))
1636         (push (car alist) out))
1637       (pop alist))
1638     (nreverse out)))
1639
1640 (defun nnheader-directory-regular-files (dir)
1641   "Return a list of all regular files in DIR."
1642   (let ((files (directory-files dir t))
1643         out)
1644     (while files
1645       (when (file-regular-p (car files))
1646         (push (car files) out))
1647       (pop files))
1648     (nreverse out)))
1649
1650 (defun nnheader-directory-files (&rest args)
1651   "Same as `directory-files', but prune \".\" and \"..\"."
1652   (let ((files (apply 'directory-files args))
1653         out)
1654     (while files
1655       (unless (member (file-name-nondirectory (car files)) '("." ".."))
1656         (push (car files) out))
1657       (pop files))
1658     (nreverse out)))
1659
1660 (defmacro nnheader-skeleton-replace (from &optional to regexp)
1661   `(let ((new (generate-new-buffer " *nnheader replace*"))
1662          (cur (current-buffer))
1663          (start (point-min)))
1664      (set-buffer cur)
1665      (goto-char (point-min))
1666      (while (,(if regexp 're-search-forward 'search-forward)
1667              ,from nil t)
1668        (insert-buffer-substring
1669         cur start (prog1 (match-beginning 0) (set-buffer new)))
1670        (goto-char (point-max))
1671        ,(when to `(insert ,to))
1672        (set-buffer cur)
1673        (setq start (point)))
1674      (insert-buffer-substring
1675       cur start (prog1 (point-max) (set-buffer new)))
1676      (copy-to-buffer cur (point-min) (point-max))
1677      (kill-buffer (current-buffer))
1678      (set-buffer cur)))
1679
1680 (defun nnheader-replace-string (from to)
1681   "Do a fast replacement of FROM to TO from point to `point-max'."
1682   (nnheader-skeleton-replace from to))
1683
1684 (defun nnheader-replace-regexp (from to)
1685   "Do a fast regexp replacement of FROM to TO from point to `point-max'."
1686   (nnheader-skeleton-replace from to t))
1687
1688 (defun nnheader-strip-cr ()
1689   "Strip all \r's from the current buffer."
1690   (nnheader-skeleton-replace "\r"))
1691
1692 (defalias 'nnheader-cancel-timer 'cancel-timer)
1693 (defalias 'nnheader-cancel-function-timers 'cancel-function-timers)
1694 (defalias 'nnheader-string-as-multibyte 'string-as-multibyte)
1695
1696 (defun nnheader-Y-or-n-p (prompt)
1697   "Ask user a \"Y/n\" question. Return t if answer is neither \"n\", \"N\" nor \"C-g\"."
1698   (let ((cursor-in-echo-area t)
1699         (echo-keystrokes 0)
1700         (inhibit-quit t)
1701         ans)
1702     (let (message-log-max)
1703       (while (not (memq ans '(?\  ?N ?Y ?\C-g ?\e ?\n ?\r ?n ?y)))
1704         (message "%s(Y/n) " prompt)
1705         (setq ans (read-char-exclusive))))
1706     (if (memq ans '(?\C-g ?N ?n))
1707         (progn
1708           (message "%s(Y/n) No" prompt)
1709           nil)
1710       (message "%s(Y/n) Yes" prompt)
1711       t)))
1712
1713 (defun-maybe shell-command-to-string (command)
1714   "Execute shell command COMMAND and return its output as a string."
1715   (with-output-to-string
1716     (with-current-buffer
1717         standard-output
1718       (call-process shell-file-name nil t nil shell-command-switch command))))
1719
1720 (defun nnheader-accept-process-output (process)
1721   (accept-process-output
1722    process
1723    (truncate nnheader-read-timeout)
1724    (truncate (* (- nnheader-read-timeout
1725                    (truncate nnheader-read-timeout))
1726                 1000))))
1727
1728 (when (featurep 'xemacs)
1729   (require 'nnheaderxm))
1730
1731 (run-hooks 'nnheader-load-hook)
1732
1733 (provide 'nnheader)
1734
1735 ;;; nnheader.el ends here