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