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