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