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