Synch to No Gnus 200502242023.
[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, 1995, 1996,
4 ;;        1997, 1998, 2000, 2001, 2002, 2003, 2004
5 ;;        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., 59 Temple Place - Suite 330,
28 ;; Boston, MA 02111-1307, 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 (defalias 'mail-header-set-extra 'mime-gnus-entity-set-extra-internal)
596
597 (defun nnheader-decode-field-body (field-body field-name
598                                               &optional mode max-column)
599   (mime-decode-field-body field-body
600                           (if (stringp field-name)
601                               (intern (capitalize field-name))
602                             field-name)
603                           mode max-column))
604
605 (defsubst make-full-mail-header (&optional number subject from date id
606                                            references chars lines xref
607                                            extra)
608   "Create a new mail header structure initialized with the parameters given."
609   (luna-make-entity (mm-expand-class-name 'gnus)
610                     :location number
611                     :subject (if subject
612                                  (nnheader-decode-subject subject))
613                     :from (if from
614                               (nnheader-decode-from from))
615                     :date date
616                     :id id
617                     :references references
618                     :chars chars
619                     :lines lines
620                     :xref xref
621                     :original-header (list (cons 'Subject subject)
622                                            (cons 'From from))
623                     :extra extra))
624
625 (defsubst make-full-mail-header-from-decoded-header
626   (&optional number subject from date id references chars lines xref extra)
627   "Create a new mail header structure initialized with the parameters given."
628   (luna-make-entity (mm-expand-class-name 'gnus)
629                     :location number
630                     :subject subject
631                     :from from
632                     :date date
633                     :id id
634                     :references references
635                     :chars chars
636                     :lines lines
637                     :xref xref
638                     :extra extra))
639
640 (defsubst make-mail-header (&optional init)
641   "Create a new mail header structure initialized with INIT."
642   (make-full-mail-header init init init init init
643                          init init init init init))
644
645 ;; fake message-ids: generation and detection
646
647 (defvar nnheader-fake-message-id 1)
648
649 (defsubst nnheader-generate-fake-message-id (&optional number)
650   (if (numberp number)
651       (format "fake+none+%s+%d" gnus-newsgroup-name number)
652     (format "fake+none+%s+%s"
653             gnus-newsgroup-name
654             (int-to-string (incf nnheader-fake-message-id)))))
655
656 (defsubst nnheader-fake-message-id-p (id)
657   (save-match-data                      ; regular message-id's are <.*>
658     (string-match "\\`fake\\+none\\+.*\\+[0-9]+\\'" id)))
659
660 ;; Parsing headers and NOV lines.
661
662 (defsubst nnheader-remove-cr-followed-by-lf ()
663   (goto-char (point-max))
664   (while (search-backward "\r\n" nil t)
665     (delete-char 1)))
666
667 (defsubst nnheader-header-value ()
668   (let ((pt (point)))
669     (prog2
670         (skip-chars-forward " \t")
671         (buffer-substring (point) (std11-field-end))
672       (goto-char pt))))
673
674 (defun nnheader-parse-naked-head (&optional number)
675   ;; This function unfolds continuation lines in this buffer
676   ;; destructively.  When this side effect is unwanted, use
677   ;; `nnheader-parse-head' instead of this function.
678   (let ((case-fold-search t)
679         (buffer-read-only nil)
680         (cur (current-buffer))
681         (p (point-min))
682         in-reply-to lines ref)
683     (nnheader-remove-cr-followed-by-lf)
684     (ietf-drums-unfold-fws)
685     (subst-char-in-region (point-min) (point-max) ?\t ? )
686     (goto-char p)
687     (insert "\n")
688     (prog1
689         ;; This implementation of this function, with nine
690         ;; search-forwards instead of the one re-search-forward and a
691         ;; case (which basically was the old function) is actually
692         ;; about twice as fast, even though it looks messier.  You
693         ;; can't have everything, I guess.  Speed and elegance don't
694         ;; always go hand in hand.
695         (make-full-mail-header
696          ;; Number.
697          (or number 0)
698          ;; Subject.
699          (progn
700            (goto-char p)
701            (if (search-forward "\nsubject:" nil t)
702                (nnheader-header-value) "(none)"))
703          ;; From.
704          (progn
705            (goto-char p)
706            (if (search-forward "\nfrom:" nil t)
707                (nnheader-header-value) "(nobody)"))
708          ;; Date.
709          (progn
710            (goto-char p)
711            (if (search-forward "\ndate:" nil t)
712                (nnheader-header-value) ""))
713          ;; Message-ID.
714          (progn
715            (goto-char p)
716            (if (search-forward "\nmessage-id:" nil t)
717                (buffer-substring
718                 (1- (or (search-forward "<" (point-at-eol) t)
719                         (point)))
720                 (or (search-forward ">" (point-at-eol) t) (point)))
721              ;; If there was no message-id, we just fake one to make
722              ;; subsequent routines simpler.
723              (nnheader-generate-fake-message-id number)))
724          ;; References.
725          (progn
726            (goto-char p)
727            (if (search-forward "\nreferences:" nil t)
728                (nnheader-header-value)
729              ;; Get the references from the in-reply-to header if
730              ;; there were no references and the in-reply-to header
731              ;; looks promising.
732              (if (and (search-forward "\nin-reply-to:" nil t)
733                       (setq in-reply-to (nnheader-header-value))
734                       (string-match "<[^\n>]+>" in-reply-to))
735                  (let (ref2)
736                    (setq ref (substring in-reply-to (match-beginning 0)
737                                         (match-end 0)))
738                    (while (string-match "<[^\n>]+>"
739                                         in-reply-to (match-end 0))
740                      (setq ref2 (substring in-reply-to (match-beginning 0)
741                                            (match-end 0)))
742                      (when (> (length ref2) (length ref))
743                        (setq ref ref2)))
744                    ref)
745                nil)))
746          ;; Chars.
747          0
748          ;; Lines.
749          (progn
750            (goto-char p)
751            (if (search-forward "\nlines: " nil t)
752                (if (numberp (setq lines (read cur)))
753                    lines 0)
754              0))
755          ;; Xref.
756          (progn
757            (goto-char p)
758            (and (search-forward "\nxref:" nil t)
759                 (nnheader-header-value)))
760          ;; Extra.
761          (when nnmail-extra-headers
762            (let ((extra nnmail-extra-headers)
763                  out)
764              (while extra
765                (goto-char p)
766                (when (search-forward
767                       (concat "\n" (symbol-name (car extra)) ":") nil t)
768                  (push (cons (car extra) (nnheader-header-value))
769                        out))
770                (pop extra))
771              out)))
772       (goto-char p)
773       (delete-char 1))))
774
775 (defun nnheader-parse-head (&optional naked)
776   (let ((cur (current-buffer)) num beg end)
777     (when (if naked
778               (setq num 0
779                     beg (point-min)
780                     end (point-max))
781             (goto-char (point-min))
782             ;; Search to the beginning of the next header.  Error
783             ;; messages do not begin with 2 or 3.
784             (when (re-search-forward "^[23][0-9]+ " nil t)
785               (end-of-line)
786               (setq num (read cur)
787                     beg (point)
788                     end (if (search-forward "\n.\n" nil t)
789                             (- (point) 2)
790                           (point)))))
791       (with-temp-buffer
792         (insert-buffer-substring cur beg end)
793         (nnheader-parse-naked-head num)))))
794
795 (defmacro nnheader-nov-skip-field ()
796   '(search-forward "\t" eol 'move))
797
798 (defmacro nnheader-nov-field ()
799   '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
800
801 (defmacro nnheader-nov-read-integer ()
802   '(prog1
803        (if (eq (char-after) ?\t)
804            0
805          (let ((num (condition-case nil
806                         (read (current-buffer))
807                       (error nil))))
808            (if (numberp num) num 0)))
809      (unless (eobp)
810        (search-forward "\t" eol 'move))))
811
812 (defmacro nnheader-nov-parse-extra ()
813   '(let (out string)
814      (while (not (memq (char-after) '(?\n nil)))
815        (setq string (nnheader-nov-field))
816        (when (string-match "^\\([^ :]+\\): " string)
817          (push (cons (intern (match-string 1 string))
818                      (substring string (match-end 0)))
819                out)))
820      out))
821
822 (defvar nnheader-uniquify-message-id nil)
823
824 (defmacro nnheader-nov-read-message-id (&optional number)
825   `(let ((id (nnheader-nov-field)))
826      (if (string-match "^<[^>]+>$" id)
827          ,(if nnheader-uniquify-message-id
828               `(if (string-match "__[^@]+@" id)
829                    (concat (substring id 0 (match-beginning 0))
830                            (substring id (1- (match-end 0))))
831                  id)
832             'id)
833        (nnheader-generate-fake-message-id ,number))))
834
835 (defun nnheader-parse-nov ()
836   (let* ((eol (point-at-eol))
837          (number (nnheader-nov-read-integer)))
838     (make-full-mail-header
839      number                             ; number
840      (nnheader-nov-field)               ; subject
841      (nnheader-nov-field)               ; from
842      (nnheader-nov-field)               ; date
843      (nnheader-nov-read-message-id number) ; id
844      (nnheader-nov-field)               ; refs
845      (nnheader-nov-read-integer)        ; chars
846      (nnheader-nov-read-integer)        ; lines
847      (if (eq (char-after) ?\n)
848          nil
849        (if (looking-at "Xref: ")
850            (goto-char (match-end 0)))
851        (nnheader-nov-field))            ; Xref
852      (nnheader-nov-parse-extra))))      ; extra
853
854 (defun nnheader-insert-nov (header)
855   (princ (mail-header-number header) (current-buffer))
856   (let ((p (point)))
857     (insert
858      "\t"
859      (or (mime-entity-fetch-field header 'Subject) "(none)") "\t"
860      (or (mime-entity-fetch-field header 'From) "(nobody)") "\t"
861      (or (mail-header-date header) "") "\t"
862      (or (mail-header-id header)
863          (nnmail-message-id))
864      "\t"
865      (or (mail-header-references header) "") "\t")
866     (princ (or (mail-header-chars header) 0) (current-buffer))
867     (insert "\t")
868     (princ (or (mail-header-lines header) 0) (current-buffer))
869     (insert "\t")
870     (when (mail-header-xref header)
871       (insert "Xref: " (mail-header-xref header)))
872     (when (or (mail-header-xref header)
873               (mail-header-extra header))
874       (insert "\t"))
875     (when (mail-header-extra header)
876       (let ((extra (mail-header-extra header)))
877         (while extra
878           (insert (symbol-name (caar extra))
879                   ": " (cdar extra) "\t")
880           (pop extra))))
881     (insert "\n")
882     (backward-char 1)
883     (while (search-backward "\n" p t)
884       (delete-char 1))
885     (forward-line 1)))
886
887 (defun nnheader-parse-overview-file (file)
888   "Parse FILE and return a list of headers."
889   (mm-with-unibyte-buffer
890     (nnheader-insert-file-contents file)
891     (goto-char (point-min))
892     (let (headers)
893       (while (not (eobp))
894         (push (nnheader-parse-nov) headers)
895         (forward-line 1))
896       (nreverse headers))))
897
898 (defun nnheader-write-overview-file (file headers)
899   "Write HEADERS to FILE."
900   (with-temp-file file
901     (mapcar 'nnheader-insert-nov headers)))
902
903 (defun nnheader-insert-header (header)
904   (insert
905    "Subject: " (or (mail-header-subject header) "(none)") "\n"
906    "From: " (or (mail-header-from header) "(nobody)") "\n"
907    "Date: " (or (mail-header-date header) "") "\n"
908    "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
909    "References: " (or (mail-header-references header) "") "\n"
910    "Lines: ")
911   (princ (or (mail-header-lines header) 0) (current-buffer))
912   (insert "\n\n"))
913
914 (defun nnheader-insert-article-line (article)
915   (goto-char (point-min))
916   (insert "220 ")
917   (princ article (current-buffer))
918   (insert " Article retrieved.\n")
919   (search-forward "\n\n" nil 'move)
920   (delete-region (point) (point-max))
921   (forward-char -1)
922   (insert "."))
923
924 (defun nnheader-nov-delete-outside-range (beg end)
925   "Delete all NOV lines that lie outside the BEG to END range."
926   ;; First we find the first wanted line.
927   (nnheader-find-nov-line beg)
928   (delete-region (point-min) (point))
929   ;; Then we find the last wanted line.
930   (when (nnheader-find-nov-line end)
931     (forward-line 1))
932   (delete-region (point) (point-max)))
933
934 (defun nnheader-find-nov-line (article)
935   "Put point at the NOV line that start with ARTICLE.
936 If ARTICLE doesn't exist, put point where that line
937 would have been.  The function will return non-nil if
938 the line could be found."
939   ;; This function basically does a binary search.
940   (let ((max (point-max))
941         (min (goto-char (point-min)))
942         (cur (current-buffer))
943         (prev (point-min))
944         num found)
945     (while (not found)
946       (goto-char (+ min (/ (- max min) 2)))
947       (beginning-of-line)
948       (if (or (= (point) prev)
949               (eobp))
950           (setq found t)
951         (setq prev (point))
952         (while (and (not (numberp (setq num (read cur))))
953                     (not (eobp)))
954           (gnus-delete-line))
955         (cond ((> num article)
956                (setq max (point)))
957               ((< num article)
958                (setq min (point)))
959               (t
960                (setq found 'yes)))))
961     ;; We may be at the first line.
962     (when (and (not num)
963                (not (eobp)))
964       (setq num (read cur)))
965     ;; Now we may have found the article we're looking for, or we
966     ;; may be somewhere near it.
967     (when (and (not (eq found 'yes))
968                (not (eq num article)))
969       (setq found (point))
970       (while (and (< (point) max)
971                   (or (not (numberp num))
972                       (< num article)))
973         (forward-line 1)
974         (setq found (point))
975         (or (eobp)
976             (= (setq num (read cur)) article)))
977       (unless (eq num article)
978         (goto-char found)))
979     (beginning-of-line)
980     (eq num article)))
981
982 (defun nnheader-retrieve-headers-from-directory* (articles
983                                                   directory dependencies
984                                                   &optional
985                                                   fetch-old force-new large
986                                                   backend)
987   (with-temp-buffer
988     (let* ((file nil)
989            (number (length articles))
990            (count 0)
991            (file-name-coding-system 'binary)
992            (case-fold-search t)
993            (cur (current-buffer))
994            article
995            headers header id end ref in-reply-to lines chars ctype)
996       ;; We don't support fetching by Message-ID.
997       (if (stringp (car articles))
998           'headers
999         (while articles
1000           (when (and (file-exists-p
1001                       (setq file (expand-file-name
1002                                   (int-to-string
1003                                    (setq article (pop articles)))
1004                                   directory)))
1005                      (not (file-directory-p file)))
1006             (erase-buffer)
1007             (nnheader-insert-head file)
1008             (save-restriction
1009               (std11-narrow-to-header)
1010               (setq
1011                header
1012                (make-full-mail-header
1013                 ;; Number.
1014                 article
1015                 ;; Subject.
1016                 (or (std11-fetch-field "Subject")
1017                     "(none)")
1018                 ;; From.
1019                 (or (std11-fetch-field "From")
1020                     "(nobody)")
1021                 ;; Date.
1022                 (or (std11-fetch-field "Date")
1023                     "")
1024                 ;; Message-ID.
1025                 (progn
1026                   (goto-char (point-min))
1027                   (setq id (if (re-search-forward
1028                                 "^Message-ID: *\\(<[^\n\t> ]+>\\)" nil t)
1029                                ;; We do it this way to make sure the Message-ID
1030                                ;; is (somewhat) syntactically valid.
1031                                (buffer-substring (match-beginning 1)
1032                                                  (match-end 1))
1033                              ;; If there was no message-id, we just fake one
1034                              ;; to make subsequent routines simpler.
1035                              (nnheader-generate-fake-message-id))))
1036                 ;; References.
1037                 (progn
1038                   (goto-char (point-min))
1039                   (if (search-forward "\nReferences: " nil t)
1040                       (progn
1041                         (setq end (point))
1042                         (prog1
1043                             (buffer-substring (match-end 0) (std11-field-end))
1044                           (setq ref
1045                                 (buffer-substring
1046                                  (progn
1047                                    ;; (end-of-line)
1048                                    (search-backward ">" end t)
1049                                    (1+ (point)))
1050                                  (progn
1051                                    (search-backward "<" end t)
1052                                    (point))))))
1053                     ;; Get the references from the in-reply-to header if there
1054                     ;; were no references and the in-reply-to header looks
1055                     ;; promising.
1056                     (if (and (search-forward "\nIn-Reply-To: " nil t)
1057                              (setq in-reply-to
1058                                    (buffer-substring (match-end 0)
1059                                                      (std11-field-end)))
1060                              (string-match "<[^>]+>" in-reply-to))
1061                         (let (ref2)
1062                           (setq ref (substring in-reply-to (match-beginning 0)
1063                                                (match-end 0)))
1064                           (while (string-match "<[^>]+>"
1065                                                in-reply-to (match-end 0))
1066                             (setq ref2
1067                                   (substring in-reply-to (match-beginning 0)
1068                                              (match-end 0)))
1069                             (when (> (length ref2) (length ref))
1070                               (setq ref ref2)))
1071                           ref)
1072                       (setq ref nil))))
1073                 ;; Chars.
1074                 (progn
1075                   (goto-char (point-min))
1076                   (if (search-forward "\nChars: " nil t)
1077                       (if (numberp (setq chars (ignore-errors (read cur))))
1078                           chars 0)
1079                     0))
1080                 ;; Lines.
1081                 (progn
1082                   (goto-char (point-min))
1083                   (if (search-forward "\nLines: " nil t)
1084                       (if (numberp (setq lines (ignore-errors (read cur))))
1085                           lines 0)
1086                     0))
1087                 ;; Xref.
1088                 (std11-fetch-field "Xref")
1089                 ))
1090               (goto-char (point-min))
1091               (if (setq ctype (std11-fetch-field "Content-Type"))
1092                   (mime-entity-set-content-type-internal
1093                    header (mime-parse-Content-Type ctype)))
1094               )
1095             (when (setq header
1096                         (gnus-dependencies-add-header
1097                          header dependencies force-new))
1098               (push header headers))
1099             )
1100           (setq count (1+ count))
1101
1102           (and large
1103                (zerop (% count 20))
1104                (nnheader-message 5 "%s: Receiving headers... %d%%"
1105                                  backend
1106                                  (/ (* count 100) number))))
1107
1108         (when large
1109           (nnheader-message 5 "%s: Receiving headers...done" backend))
1110
1111         headers))))
1112
1113 (defun nnheader-retrieve-headers-from-directory (articles
1114                                                  directory dependencies
1115                                                  &optional
1116                                                  fetch-old force-new large
1117                                                  backend)
1118   (cons 'header
1119         (nreverse (nnheader-retrieve-headers-from-directory*
1120                    articles directory dependencies
1121                    fetch-old force-new large backend))))
1122
1123 (defun nnheader-get-newsgroup-headers-xover* (sequence
1124                                               &optional
1125                                               force-new dependencies
1126                                               group)
1127   "Parse the news overview data in the server buffer, and return a
1128 list of headers that match SEQUENCE (see `nntp-retrieve-headers')."
1129   ;; Get the Xref when the users reads the articles since most/some
1130   ;; NNTP servers do not include Xrefs when using XOVER.
1131   ;; (setq gnus-article-internal-prepare-hook '(gnus-article-get-xrefs))
1132   (let ((cur nntp-server-buffer)
1133         number headers header)
1134     (save-excursion
1135       (set-buffer nntp-server-buffer)
1136       ;; Allow the user to mangle the headers before parsing them.
1137       (gnus-run-hooks 'gnus-parse-headers-hook)
1138       (goto-char (point-min))
1139       (while (not (eobp))
1140         (condition-case ()
1141             (while (and sequence (not (eobp)))
1142               (setq number (read cur))
1143               (while (and sequence
1144                           (< (car sequence) number))
1145                 (setq sequence (cdr sequence)))
1146               (and sequence
1147                    (eq number (car sequence))
1148                    (progn
1149                      (setq sequence (cdr sequence))
1150                      (setq header (inline
1151                                     (gnus-nov-parse-line
1152                                      number dependencies force-new))))
1153                    (push header headers))
1154               (forward-line 1))
1155           (error
1156            (gnus-error 4 "Strange nov line (%d)"
1157                        (count-lines (point-min) (point)))))
1158         (forward-line 1))
1159       ;; A common bug in inn is that if you have posted an article and
1160       ;; then retrieves the active file, it will answer correctly --
1161       ;; the new article is included.  However, a NOV entry for the
1162       ;; article may not have been generated yet, so this may fail.
1163       ;; We work around this problem by retrieving the last few
1164       ;; headers using HEAD.
1165       headers)))
1166
1167 ;; Various cruft the backends and Gnus need to communicate.
1168
1169 (defvar nntp-server-buffer nil)
1170 (defvar nntp-process-response nil)
1171 (defvar news-reply-yank-from nil)
1172 (defvar news-reply-yank-message-id nil)
1173
1174 (defvar nnheader-callback-function nil)
1175
1176 (defun nnheader-init-server-buffer ()
1177   "Initialize the Gnus-backend communication buffer."
1178   (save-excursion
1179     (unless (gnus-buffer-live-p nntp-server-buffer)
1180       (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
1181     (set-buffer nntp-server-buffer)
1182     (erase-buffer)
1183     (kill-all-local-variables)
1184     (setq case-fold-search t)           ;Should ignore case.
1185     (set (make-local-variable 'nntp-process-response) nil)
1186     t))
1187
1188 ;;; Various functions the backends use.
1189
1190 (defun nnheader-file-error (file)
1191   "Return a string that says what is wrong with FILE."
1192   (format
1193    (cond
1194     ((not (file-exists-p file))
1195      "%s does not exist")
1196     ((file-directory-p file)
1197      "%s is a directory")
1198     ((not (file-readable-p file))
1199      "%s is not readable"))
1200    file))
1201
1202 (defun nnheader-insert-head (file)
1203   "Insert the head of the article."
1204   (when (file-exists-p file)
1205     (if (eq nnheader-max-head-length t)
1206         ;; Just read the entire file.
1207         (nnheader-insert-file-contents file)
1208       ;; Read 1K blocks until we find a separator.
1209       (let ((beg 0)
1210             format-alist)
1211         (while (and (eq nnheader-head-chop-length
1212                         (nth 1 (nnheader-insert-file-contents
1213                                 file nil beg
1214                                 (incf beg nnheader-head-chop-length))))
1215                     (prog1 (not (search-forward "\n\n" nil t))
1216                       (goto-char (point-max)))
1217                     (or (null nnheader-max-head-length)
1218                         (< beg nnheader-max-head-length))))))
1219     t))
1220
1221 (defun nnheader-article-p ()
1222   "Say whether the current buffer looks like an article."
1223   (goto-char (point-min))
1224   (if (not (search-forward "\n\n" nil t))
1225       nil
1226     (narrow-to-region (point-min) (1- (point)))
1227     (goto-char (point-min))
1228     (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
1229       (goto-char (match-end 0)))
1230     (prog1
1231         (eobp)
1232       (widen))))
1233
1234 (defun nnheader-insert-references (references message-id)
1235   "Insert a References header based on REFERENCES and MESSAGE-ID."
1236   (if (and (not references) (not message-id))
1237       ;; This is invalid, but not all articles have Message-IDs.
1238       ()
1239     (mail-position-on-field "References")
1240     (let ((begin (point-at-bol))
1241           (fill-column 78)
1242           (fill-prefix "\t"))
1243       (when references
1244         (insert references))
1245       (when (and references message-id)
1246         (insert " "))
1247       (when message-id
1248         (insert message-id))
1249       ;; Fold long References lines to conform to RFC1036 (sort of).
1250       ;; The region must end with a newline to fill the region
1251       ;; without inserting extra newline.
1252       (fill-region-as-paragraph begin (1+ (point))))))
1253
1254 (defun nnheader-replace-header (header new-value)
1255   "Remove HEADER and insert the NEW-VALUE."
1256   (save-excursion
1257     (save-restriction
1258       (nnheader-narrow-to-headers)
1259       (prog1
1260           (message-remove-header header)
1261         (goto-char (point-max))
1262         (insert header ": " new-value "\n")))))
1263
1264 (defun nnheader-narrow-to-headers ()
1265   "Narrow to the head of an article."
1266   (widen)
1267   (narrow-to-region
1268    (goto-char (point-min))
1269    (if (search-forward "\n\n" nil t)
1270        (1- (point))
1271      (point-max)))
1272   (goto-char (point-min)))
1273
1274 (defun nnheader-get-lines-and-char ()
1275   "Return the number of lines and chars in the article body."
1276   (goto-char (point-min))
1277   (if (not (re-search-forward "\n\r?\n" nil t))
1278       (list 0 0)
1279     (list (count-lines (point) (point-max))
1280           (- (point-max) (point)))))
1281
1282 (defun nnheader-remove-body ()
1283   "Remove the body from an article in this current buffer."
1284   (goto-char (point-min))
1285   (when (re-search-forward "\n\r?\n" nil t)
1286     (delete-region (point) (point-max))))
1287
1288 (defun nnheader-set-temp-buffer (name &optional noerase)
1289   "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
1290   (set-buffer (get-buffer-create name))
1291   (buffer-disable-undo)
1292   (unless noerase
1293     (erase-buffer))
1294   (current-buffer))
1295
1296 (eval-when-compile (defvar jka-compr-compression-info-list))
1297 (defvar nnheader-numerical-files
1298   (if (boundp 'jka-compr-compression-info-list)
1299       (concat "\\([0-9]+\\)\\("
1300               (mapconcat (lambda (i) (aref i 0))
1301                          jka-compr-compression-info-list "\\|")
1302               "\\)?")
1303     "[0-9]+$")
1304   "Regexp that match numerical files.")
1305
1306 (defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
1307   "Regexp that matches numerical file names.")
1308
1309 (defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
1310   "Regexp that matches numerical full file names.")
1311
1312 (defsubst nnheader-file-to-number (file)
1313   "Take a FILE name and return the article number."
1314   (if (string= nnheader-numerical-short-files "^[0-9]+$")
1315       (string-to-int file)
1316     (string-match nnheader-numerical-short-files file)
1317     (string-to-int (match-string 0 file))))
1318
1319 (defvar nnheader-directory-files-is-safe
1320   (or (eq system-type 'windows-nt)
1321       (not (featurep 'xemacs)))
1322   "If non-nil, Gnus believes `directory-files' is safe.
1323 It has been reported numerous times that `directory-files' fails with
1324 an alarming frequency on NFS mounted file systems. If it is nil,
1325 `nnheader-directory-files-safe' is used.")
1326
1327 (defun nnheader-directory-files-safe (&rest args)
1328   "Execute `directory-files' twice and returns the longer result."
1329   (let ((first (apply 'directory-files args))
1330         (second (apply 'directory-files args)))
1331     (if (> (length first) (length second))
1332         first
1333       second)))
1334
1335 (defun nnheader-directory-articles (dir)
1336   "Return a list of all article files in directory DIR."
1337   (mapcar 'nnheader-file-to-number
1338           (if nnheader-directory-files-is-safe
1339               (directory-files
1340                dir nil nnheader-numerical-short-files t)
1341             (nnheader-directory-files-safe
1342              dir nil nnheader-numerical-short-files t))))
1343
1344 (defun nnheader-article-to-file-alist (dir)
1345   "Return an alist of article/file pairs in DIR."
1346   (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
1347           (if nnheader-directory-files-is-safe
1348               (directory-files
1349                dir nil nnheader-numerical-short-files t)
1350             (nnheader-directory-files-safe
1351              dir nil nnheader-numerical-short-files t))))
1352
1353 (defun nnheader-fold-continuation-lines ()
1354   "Fold continuation lines in the current buffer."
1355   (nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
1356
1357 (defun nnheader-translate-file-chars (file &optional full)
1358   "Translate FILE into something that can be a file name.
1359 If FULL, translate everything."
1360   (if (null nnheader-file-name-translation-alist)
1361       ;; No translation is necessary.
1362       file
1363     (let* ((i 0)
1364            trans leaf path len)
1365       (if full
1366           ;; Do complete translation.
1367           (setq leaf (copy-sequence file)
1368                 path ""
1369                 i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1)))
1370                       2 0))
1371         ;; We translate -- but only the file name.  We leave the directory
1372         ;; alone.
1373         (if (and (featurep 'xemacs)
1374                  (memq system-type '(cygwin32 win32 w32 mswindows windows-nt
1375                                               cygwin)))
1376             ;; This is needed on NT and stuff, because
1377             ;; file-name-nondirectory is not enough to split
1378             ;; file names, containing ':', e.g.
1379             ;; "d:\\Work\\News\\nntp+news.fido7.ru:fido7.ru.gnu.SCORE"
1380             ;;
1381             ;; we are trying to correctly split such names:
1382             ;; "d:file.name" -> "a:" "file.name"
1383             ;; "aaa:bbb.ccc" -> "" "aaa:bbb.ccc"
1384             ;; "d:aaa\\bbb:ccc"   -> "d:aaa\\" "bbb:ccc"
1385             ;; etc.
1386             ;; to translate then only the file name part.
1387             (progn
1388               (setq leaf file
1389                     path "")
1390               (if (string-match "\\(^\\w:\\|[/\\]\\)\\([^/\\]+\\)$" file)
1391                   (setq leaf (substring file (match-beginning 2))
1392                         path (substring file 0 (match-beginning 2)))))
1393           ;; Emacs DTRT, says andrewi.
1394           (setq leaf (file-name-nondirectory file)
1395                 path (file-name-directory file))))
1396       (setq len (length leaf))
1397       (while (< i len)
1398         (when (setq trans (cdr (assq (aref leaf i)
1399                                      nnheader-file-name-translation-alist)))
1400           (aset leaf i trans))
1401         (incf i))
1402       (concat path leaf))))
1403
1404 (defun nnheader-report (backend &rest args)
1405   "Report an error from the BACKEND.
1406 The first string in ARGS can be a format string."
1407   (set (intern (format "%s-status-string" backend))
1408        (if (< (length args) 2)
1409            (car args)
1410          (apply 'format args)))
1411   nil)
1412
1413 (defun nnheader-get-report (backend)
1414   "Get the most recent report from BACKEND."
1415   (condition-case ()
1416       (nnheader-message 5 "%s" (symbol-value (intern (format "%s-status-string"
1417                                                              backend))))
1418     (error (nnheader-message 5 ""))))
1419
1420 (defun nnheader-insert (format &rest args)
1421   "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
1422 If FORMAT isn't a format string, it and all ARGS will be inserted
1423 without formatting."
1424   (save-excursion
1425     (set-buffer nntp-server-buffer)
1426     (erase-buffer)
1427     (if (string-match "%" format)
1428         (insert (apply 'format format args))
1429       (apply 'insert format args))
1430     t))
1431
1432 (static-if (fboundp 'subst-char-in-string)
1433     (defsubst nnheader-replace-chars-in-string (string from to)
1434       (subst-char-in-string from to string))
1435   (defun nnheader-replace-chars-in-string (string from to)
1436     "Replace characters in STRING from FROM to TO."
1437     (let ((string (substring string 0)) ;Copy string.
1438           (len (length string))
1439           (idx 0))
1440       ;; Replace all occurrences of FROM with TO.
1441       (while (< idx len)
1442         (when (= (aref string idx) from)
1443           (aset string idx to))
1444         (setq idx (1+ idx)))
1445       string)))
1446
1447 (defun nnheader-replace-duplicate-chars-in-string (string from to)
1448   "Replace characters in STRING from FROM to TO."
1449   (let ((string (substring string 0))   ;Copy string.
1450         (len (length string))
1451         (idx 0) prev i)
1452     ;; Replace all occurrences of FROM with TO.
1453     (while (< idx len)
1454       (setq i (aref string idx))
1455       (when (and (eq prev from) (= i from))
1456         (aset string (1- idx) to)
1457         (aset string idx to))
1458       (setq prev i)
1459       (setq idx (1+ idx)))
1460     string))
1461
1462 (defun nnheader-file-to-group (file &optional top)
1463   "Return a group name based on FILE and TOP."
1464   (nnheader-replace-chars-in-string
1465    (if (not top)
1466        file
1467      (condition-case ()
1468          (substring (expand-file-name file)
1469                     (length
1470                      (expand-file-name
1471                       (file-name-as-directory top))))
1472        (error "")))
1473    nnheader-directory-separator-character ?.))
1474
1475 (defun nnheader-message (level &rest args)
1476   "Message if the Gnus backends are talkative."
1477   (if (or (not (numberp gnus-verbose-backends))
1478           (<= level gnus-verbose-backends))
1479       (apply 'message args)
1480     (apply 'format args)))
1481
1482 (defun nnheader-be-verbose (level)
1483   "Return whether the backends should be verbose on LEVEL."
1484   (or (not (numberp gnus-verbose-backends))
1485       (<= level gnus-verbose-backends)))
1486
1487 (defvar nnheader-pathname-coding-system 'binary
1488   "*Coding system for file name.")
1489
1490 (defun nnheader-group-pathname (group dir &optional file)
1491   "Make file name for GROUP."
1492   (concat
1493    (let ((dir (file-name-as-directory (expand-file-name dir))))
1494      ;; If this directory exists, we use it directly.
1495      (file-name-as-directory
1496       (if (file-directory-p (concat dir group))
1497           (expand-file-name group dir)
1498         ;; If not, we translate dots into slashes.
1499         (expand-file-name (encode-coding-string
1500                            (nnheader-replace-chars-in-string group ?. ?/)
1501                            nnheader-pathname-coding-system)
1502                           dir))))
1503    (cond ((null file) "")
1504          ((numberp file) (int-to-string file))
1505          (t file))))
1506
1507 (defun nnheader-concat (dir &rest files)
1508   "Concat DIR as directory to FILES."
1509   (apply 'concat (file-name-as-directory dir) files))
1510
1511 (defun nnheader-ms-strip-cr ()
1512   "Strip ^M from the end of all lines."
1513   (save-excursion
1514     (nnheader-remove-cr-followed-by-lf)))
1515
1516 (defun nnheader-file-size (file)
1517   "Return the file size of FILE or 0."
1518   (or (nth 7 (file-attributes file)) 0))
1519
1520 (defun nnheader-find-etc-directory (package &optional file first)
1521   "Go through `load-path' and find the \"../etc/PACKAGE\" directory.
1522 This function will look in the parent directory of each `load-path'
1523 entry, and look for the \"etc\" directory there.
1524 If FILE, find the \".../etc/PACKAGE\" file instead.
1525 If FIRST is non-nil, return the directory or the file found at the
1526 first.  Otherwise, find the newest one, though it may take a time."
1527   (let ((path load-path)
1528         dir results)
1529     ;; We try to find the dir by looking at the load path,
1530     ;; stripping away the last component and adding "etc/".
1531     (while path
1532       (if (and (car path)
1533                (file-exists-p
1534                 (setq dir (concat
1535                            (file-name-directory
1536                             (directory-file-name (car path)))
1537                            "etc/" package
1538                            (if file "" "/"))))
1539                (or file (file-directory-p dir)))
1540           (progn
1541             (or (member dir results)
1542                 (push dir results))
1543             (setq path (if first nil (cdr path))))
1544         (setq path (cdr path))))
1545     (if (or first (not (cdr results)))
1546         (car results)
1547       (car (sort results 'file-newer-than-file-p)))))
1548
1549 (eval-when-compile
1550   (defvar ange-ftp-path-format)
1551   (defvar efs-path-regexp))
1552 (defun nnheader-re-read-dir (path)
1553   "Re-read directory PATH if PATH is on a remote system."
1554   (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
1555       (when (string-match efs-path-regexp path)
1556         (efs-re-read-dir path))
1557     (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
1558       (when (string-match (car ange-ftp-path-format) path)
1559         (ange-ftp-re-read-dir path)))))
1560
1561 (defvar nnheader-file-coding-system 'raw-text
1562   "Coding system used in file backends of Gnus.")
1563
1564 (defun nnheader-insert-file-contents (filename &optional visit beg end replace)
1565   "Like `insert-file-contents', q.v., but only reads in the file.
1566 A buffer may be modified in several ways after reading into the buffer due
1567 to advanced Emacs features, such as file-name-handlers, format decoding,
1568 find-file-hooks, etc.
1569   This function ensures that none of these modifications will take place."
1570   (let ((format-alist nil)
1571         (auto-mode-alist (nnheader-auto-mode-alist))
1572         (default-major-mode 'fundamental-mode)
1573         (enable-local-variables nil)
1574         (after-insert-file-functions nil)
1575         (enable-local-eval nil)
1576         (find-file-hooks nil))
1577     (insert-file-contents-as-coding-system
1578      nnheader-file-coding-system filename visit beg end replace)))
1579
1580 (defun nnheader-insert-nov-file (file first)
1581   (let ((size (nth 7 (file-attributes file)))
1582         (cutoff (* 32 1024)))
1583     (when size
1584       (if (< size cutoff)
1585           ;; If the file is small, we just load it.
1586           (nnheader-insert-file-contents file)
1587         ;; We start on the assumption that FIRST is pretty recent.  If
1588         ;; not, we just insert the rest of the file as well.
1589         (let (current)
1590           (nnheader-insert-file-contents file nil (- size cutoff) size)
1591           (goto-char (point-min))
1592           (delete-region (point) (or (search-forward "\n" nil 'move) (point)))
1593           (setq current (ignore-errors (read (current-buffer))))
1594           (if (and (numberp current)
1595                    (< current first))
1596               t
1597             (delete-region (point-min) (point-max))
1598             (nnheader-insert-file-contents file)))))))
1599
1600 (defun nnheader-find-file-noselect (&rest args)
1601   "Open a file with some variables bound.
1602 See `find-file-noselect' for the arguments."
1603   (let ((format-alist nil)
1604         (auto-mode-alist (nnheader-auto-mode-alist))
1605         (default-major-mode 'fundamental-mode)
1606         (enable-local-variables nil)
1607         (after-insert-file-functions nil)
1608         (enable-local-eval nil)
1609         (find-file-hooks nil))
1610     (apply 'find-file-noselect-as-coding-system
1611            nnheader-file-coding-system args)))
1612
1613 (defun nnheader-auto-mode-alist ()
1614   "Return an `auto-mode-alist' with only the .gz (etc) thingies."
1615   (let ((alist auto-mode-alist)
1616         out)
1617     (while alist
1618       (when (listp (cdar alist))
1619         (push (car alist) out))
1620       (pop alist))
1621     (nreverse out)))
1622
1623 (defun nnheader-directory-regular-files (dir)
1624   "Return a list of all regular files in DIR."
1625   (let ((files (directory-files dir t))
1626         out)
1627     (while files
1628       (when (file-regular-p (car files))
1629         (push (car files) out))
1630       (pop files))
1631     (nreverse out)))
1632
1633 (defun nnheader-directory-files (&rest args)
1634   "Same as `directory-files', but prune \".\" and \"..\"."
1635   (let ((files (apply 'directory-files args))
1636         out)
1637     (while files
1638       (unless (member (file-name-nondirectory (car files)) '("." ".."))
1639         (push (car files) out))
1640       (pop files))
1641     (nreverse out)))
1642
1643 (defmacro nnheader-skeleton-replace (from &optional to regexp)
1644   `(let ((new (generate-new-buffer " *nnheader replace*"))
1645          (cur (current-buffer))
1646          (start (point-min)))
1647      (set-buffer cur)
1648      (goto-char (point-min))
1649      (while (,(if regexp 're-search-forward 'search-forward)
1650              ,from nil t)
1651        (insert-buffer-substring
1652         cur start (prog1 (match-beginning 0) (set-buffer new)))
1653        (goto-char (point-max))
1654        ,(when to `(insert ,to))
1655        (set-buffer cur)
1656        (setq start (point)))
1657      (insert-buffer-substring
1658       cur start (prog1 (point-max) (set-buffer new)))
1659      (copy-to-buffer cur (point-min) (point-max))
1660      (kill-buffer (current-buffer))
1661      (set-buffer cur)))
1662
1663 (defun nnheader-replace-string (from to)
1664   "Do a fast replacement of FROM to TO from point to `point-max'."
1665   (nnheader-skeleton-replace from to))
1666
1667 (defun nnheader-replace-regexp (from to)
1668   "Do a fast regexp replacement of FROM to TO from point to `point-max'."
1669   (nnheader-skeleton-replace from to t))
1670
1671 (defun nnheader-strip-cr ()
1672   "Strip all \r's from the current buffer."
1673   (nnheader-skeleton-replace "\r"))
1674
1675 (defalias 'nnheader-cancel-timer 'cancel-timer)
1676 (defalias 'nnheader-cancel-function-timers 'cancel-function-timers)
1677 (defalias 'nnheader-string-as-multibyte 'string-as-multibyte)
1678
1679 (defun nnheader-Y-or-n-p (prompt)
1680   "Ask user a \"Y/n\" question. Return t if answer is neither \"n\", \"N\" nor \"C-g\"."
1681   (let ((cursor-in-echo-area t)
1682         (echo-keystrokes 0)
1683         (inhibit-quit t)
1684         ans)
1685     (let (message-log-max)
1686       (while (not (memq ans '(?\  ?N ?Y ?\C-g ?\e ?\n ?\r ?n ?y)))
1687         (message "%s(Y/n) " prompt)
1688         (setq ans (read-char-exclusive))))
1689     (if (memq ans '(?\C-g ?N ?n))
1690         (progn
1691           (message "%s(Y/n) No" prompt)
1692           nil)
1693       (message "%s(Y/n) Yes" prompt)
1694       t)))
1695
1696 (defun-maybe shell-command-to-string (command)
1697   "Execute shell command COMMAND and return its output as a string."
1698   (with-output-to-string
1699     (with-current-buffer
1700         standard-output
1701       (call-process shell-file-name nil t nil shell-command-switch command))))
1702
1703 (defun nnheader-accept-process-output (process)
1704   (accept-process-output
1705    process
1706    (truncate nnheader-read-timeout)
1707    (truncate (* (- nnheader-read-timeout
1708                    (truncate nnheader-read-timeout))
1709                 1000))))
1710
1711 (when (featurep 'xemacs)
1712   (require 'nnheaderxm))
1713
1714 (run-hooks 'nnheader-load-hook)
1715
1716 (provide 'nnheader)
1717
1718 ;;; nnheader.el ends here