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