Sync with chaos-1_13.
[elisp/gnus.git-] / lisp / nnheader.el
1 ;;; nnheader.el --- header access macros for Semi-gnus and its backends
2 ;; Copyright (C) 1987-1990,1993-1999 Free Software Foundation, Inc.
3
4 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
5 ;;      Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
7 ;;      Katsumi Yamaoka <yamaoka@jpl.org>
8 ;; Keywords: mail, news, MIME
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28
29 ;;; Code:
30
31 (eval-when-compile (require 'cl))
32
33 (require 'mail-utils)
34 (require 'mime)
35
36 (defvar nnheader-max-head-length 4096
37   "*Max length of the head of articles.")
38
39 (defvar nnheader-head-chop-length 2048
40   "*Length of each read operation when trying to fetch HEAD headers.")
41
42 (defvar nnheader-file-name-translation-alist nil
43   "*Alist that says how to translate characters in file names.
44 For instance, if \":\" is invalid as a file character in file names
45 on your system, you could say something like:
46
47 \(setq nnheader-file-name-translation-alist '((?: . ?_)))")
48
49 (eval-and-compile
50   (autoload 'nnmail-message-id "nnmail")
51   (autoload 'mail-position-on-field "sendmail")
52   (autoload 'message-remove-header "message")
53   (autoload 'cancel-function-timers "timers")
54   (autoload 'gnus-point-at-eol "gnus-util")
55   (autoload 'gnus-delete-line "gnus-util")
56   (autoload 'gnus-buffer-live-p "gnus-util"))
57
58 ;;; Header access macros.
59
60 ;; These macros may look very much like the ones in GNUS 4.1.  They
61 ;; are, in a way, but you should note that the indices they use have
62 ;; been changed from the internal GNUS format to the NOV format.  The
63 ;; makes it possible to read headers from XOVER much faster.
64 ;;
65 ;; The format of a header is now:
66 ;; [number subject from date id references chars lines xref extra]
67 ;;
68 ;; (That next-to-last entry is defined as "misc" in the NOV format,
69 ;; but Gnus uses it for xrefs.)
70
71 (require 'mmgnus)
72
73 (defmacro mail-header-number (header)
74   "Return article number in HEADER."
75   `(mime-entity-location-internal ,header))
76
77 (defmacro mail-header-set-number (header number)
78   "Set article number of HEADER to NUMBER."
79   `(mime-entity-set-location-internal ,header ,number))
80
81 (defalias 'mail-header-subject 'mime-gnus-entity-subject-internal)
82 (defalias 'mail-header-set-subject 'mime-gnus-entity-set-subject-internal)
83
84 (defalias 'mail-header-from 'mime-gnus-entity-from-internal)
85 (defalias 'mail-header-set-from 'mime-gnus-entity-set-from-internal)
86
87 (defalias 'mail-header-date 'mime-gnus-entity-date-internal)
88 (defalias 'mail-header-set-date 'mime-gnus-entity-set-date-internal)
89
90 (defalias 'mail-header-message-id 'mime-gnus-entity-id-internal)
91 (defalias 'mail-header-id 'mime-gnus-entity-id-internal)
92 (defalias 'mail-header-set-message-id 'mime-gnus-entity-set-id-internal)
93 (defalias 'mail-header-set-id 'mime-gnus-entity-set-id-internal)
94
95 (defalias 'mail-header-references 'mime-gnus-entity-references-internal)
96 (defalias 'mail-header-set-references
97   'mime-gnus-entity-set-references-internal)
98
99 (defalias 'mail-header-chars 'mime-gnus-entity-chars-internal)
100 (defalias 'mail-header-set-chars 'mime-gnus-entity-set-chars-internal)
101
102 (defalias 'mail-header-lines 'mime-gnus-entity-lines-internal)
103 (defalias 'mail-header-set-lines 'mime-gnus-entity-set-lines-internal)
104
105 (defalias 'mail-header-xref 'mime-gnus-entity-xref-internal)
106 (defalias 'mail-header-set-xref 'mime-gnus-entity-set-xref-internal)
107
108 (defalias 'nnheader-decode-subject
109   (mime-find-field-decoder 'Subject 'nov))
110 (defalias 'nnheader-decode-from
111   (mime-find-field-decoder 'From 'nov))
112
113 (defalias 'mail-header-extra 'mime-gnus-entity-extra-internal)
114 (defalias 'mail-header-set-extra 'mime-gnus-entity-set-extra-internal)
115
116 (defun nnheader-decode-field-body (field-body field-name
117                                               &optional mode max-column)
118   (mime-decode-field-body field-body
119                           (if (stringp field-name)
120                               (intern (capitalize field-name))
121                             field-name)
122                           mode max-column))
123
124 (defsubst make-full-mail-header (&optional number subject from date id
125                                            references chars lines xref
126                                            extra)
127   "Create a new mail header structure initialized with the parameters given."
128   (luna-make-entity (mm-expand-class-name 'gnus)
129                     :location number
130                     :subject (if subject
131                                  (nnheader-decode-subject subject))
132                     :from (if from
133                               (nnheader-decode-from from))
134                     :date date
135                     :id id
136                     :references references
137                     :chars chars
138                     :lines lines
139                     :xref xref
140                     :original-header (list (cons 'Subject subject)
141                                            (cons 'From from))
142                     :extra extra))
143
144 (defsubst make-full-mail-header-from-decoded-header
145   (&optional number subject from date id references chars lines xref extra)
146   "Create a new mail header structure initialized with the parameters given."
147   (luna-make-entity (mm-expand-class-name 'gnus)
148                     :location number
149                     :subject subject
150                     :from from
151                     :date date
152                     :id id
153                     :references references
154                     :chars chars
155                     :lines lines
156                     :xref xref
157                     :extra extra))
158
159 (defsubst make-mail-header (&optional init)
160   "Create a new mail header structure initialized with INIT."
161   (make-full-mail-header init init init init init
162                          init init init init init))
163
164 ;; fake message-ids: generation and detection
165
166 (defvar nnheader-fake-message-id 1)
167
168 (defsubst nnheader-generate-fake-message-id ()
169   (concat "fake+none+" (int-to-string (incf nnheader-fake-message-id))))
170
171 (defsubst nnheader-fake-message-id-p (id)
172   (save-match-data                      ; regular message-id's are <.*>
173     (string-match "\\`fake\\+none\\+[0-9]+\\'" id)))
174
175 ;; Parsing headers and NOV lines.
176
177 (defsubst nnheader-header-value ()
178   (buffer-substring (match-end 0) (gnus-point-at-eol)))
179
180 (defun nnheader-parse-head (&optional naked)
181   (let ((case-fold-search t)
182         (cur (current-buffer))
183         (buffer-read-only nil)
184         in-reply-to lines p ref)
185     (goto-char (point-min))
186     (when naked
187       (insert "\n"))
188     ;; Search to the beginning of the next header.  Error messages
189     ;; do not begin with 2 or 3.
190     (prog1
191         (when (or naked (re-search-forward "^[23][0-9]+ " nil t))
192           ;; This implementation of this function, with nine
193           ;; search-forwards instead of the one re-search-forward and
194           ;; a case (which basically was the old function) is actually
195           ;; about twice as fast, even though it looks messier.  You
196           ;; can't have everything, I guess.  Speed and elegance
197           ;; don't always go hand in hand.
198           (make-full-mail-header
199            ;; Number.
200            (if naked
201                (progn
202                  (setq p (point-min))
203                  0)
204              (prog1
205                  (read cur)
206                (end-of-line)
207                (setq p (point))
208                (narrow-to-region (point)
209                                  (or (and (search-forward "\n.\n" nil t)
210                                           (- (point) 2))
211                                      (point)))))
212            ;; Subject.
213            (progn
214              (goto-char p)
215              (if (search-forward "\nsubject: " nil t)
216                  (nnheader-header-value) "(none)"))
217            ;; From.
218            (progn
219              (goto-char p)
220              (if (search-forward "\nfrom: " nil t)
221                  (nnheader-header-value) "(nobody)"))
222            ;; Date.
223            (progn
224              (goto-char p)
225              (if (search-forward "\ndate: " nil t)
226                  (nnheader-header-value) ""))
227            ;; Message-ID.
228            (progn
229              (goto-char p)
230              (if (search-forward "\nmessage-id:" nil t)
231                  (buffer-substring
232                   (1- (or (search-forward "<" (gnus-point-at-eol) t)
233                           (point)))
234                   (or (search-forward ">" (gnus-point-at-eol) t) (point)))
235                ;; If there was no message-id, we just fake one to make
236                ;; subsequent routines simpler.
237                (nnheader-generate-fake-message-id)))
238            ;; References.
239            (progn
240              (goto-char p)
241              (if (search-forward "\nreferences: " nil t)
242                  (nnheader-header-value)
243                ;; Get the references from the in-reply-to header if there
244                ;; were no references and the in-reply-to header looks
245                ;; promising.
246                (if (and (search-forward "\nin-reply-to: " nil t)
247                         (setq in-reply-to (nnheader-header-value))
248                         (string-match "<[^\n>]+>" in-reply-to))
249                    (let (ref2)
250                      (setq ref (substring in-reply-to (match-beginning 0)
251                                           (match-end 0)))
252                      (while (string-match "<[^\n>]+>"
253                                           in-reply-to (match-end 0))
254                        (setq ref2 (substring in-reply-to (match-beginning 0)
255                                              (match-end 0)))
256                        (when (> (length ref2) (length ref))
257                          (setq ref ref2)))
258                      ref)
259                  nil)))
260            ;; Chars.
261            0
262            ;; Lines.
263            (progn
264              (goto-char p)
265              (if (search-forward "\nlines: " nil t)
266                  (if (numberp (setq lines (read cur)))
267                      lines 0)
268                0))
269            ;; Xref.
270            (progn
271              (goto-char p)
272              (and (search-forward "\nxref: " nil t)
273                   (nnheader-header-value)))
274
275            ;; Extra.
276            (when nnmail-extra-headers
277              (let ((extra nnmail-extra-headers)
278                    out)
279                (while extra
280                  (goto-char p)
281                  (when (search-forward
282                         (concat "\n" (symbol-name (car extra)) ": ") nil t)
283                    (push (cons (car extra) (nnheader-header-value))
284                          out))
285                  (pop extra))
286                out))))
287       (when naked
288         (goto-char (point-min))
289         (delete-char 1)))))
290
291 (defmacro nnheader-nov-skip-field ()
292   '(search-forward "\t" eol 'move))
293
294 (defmacro nnheader-nov-field ()
295   '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
296
297 (defmacro nnheader-nov-read-integer ()
298   '(prog1
299        (if (eq (char-after) ?\t)
300            0
301          (let ((num (condition-case nil
302                         (read (current-buffer))
303                       (error nil))))
304            (if (numberp num) num 0)))
305      (unless (eobp)
306        (search-forward "\t" eol 'move))))
307
308 (defmacro nnheader-nov-parse-extra ()
309   '(let (out string)
310      (while (not (memq (char-after) '(?\n nil)))
311        (setq string (nnheader-nov-field))
312        (when (string-match "^\\([^ :]+\\): " string)
313          (push (cons (intern (match-string 1 string))
314                      (substring string (match-end 0)))
315                out)))
316      out))
317
318 (defmacro nnheader-nov-read-message-id ()
319   '(let ((id (nnheader-nov-field)))
320      (if (string-match "^<[^>]+>$" id)
321          id
322        (nnheader-generate-fake-message-id))))
323
324 (defun nnheader-parse-nov ()
325   (let ((eol (gnus-point-at-eol)))
326     (make-full-mail-header
327      (nnheader-nov-read-integer)        ; number
328      (nnheader-nov-field)               ; subject
329      (nnheader-nov-field)               ; from
330      (nnheader-nov-field)               ; date
331      (nnheader-nov-read-message-id)     ; id
332      (nnheader-nov-field)               ; refs
333      (nnheader-nov-read-integer)        ; chars
334      (nnheader-nov-read-integer)        ; lines
335      (if (eq (char-after) ?\n)
336          nil
337        (nnheader-nov-field))            ; misc
338      (nnheader-nov-parse-extra))))      ; extra
339
340 (defun nnheader-insert-nov (header)
341   (princ (mail-header-number header) (current-buffer))
342   (insert
343    "\t"
344    (or (mime-entity-fetch-field header 'Subject) "(none)") "\t"
345    (or (mime-entity-fetch-field header 'From) "(nobody)") "\t"
346    (or (mail-header-date header) "") "\t"
347    (or (mail-header-id header)
348        (nnmail-message-id))
349    "\t"
350    (or (mail-header-references header) "") "\t")
351   (princ (or (mail-header-chars header) 0) (current-buffer))
352   (insert "\t")
353   (princ (or (mail-header-lines header) 0) (current-buffer))
354   (insert "\t")
355   (when (mail-header-xref header)
356     (insert "Xref: " (mail-header-xref header)))
357   (when (or (mail-header-xref header)
358             (mail-header-extra header))
359     (insert "\t"))
360   (when (mail-header-extra header)
361     (let ((extra (mail-header-extra header)))
362       (while extra
363         (insert (symbol-name (caar extra))
364                 ": " (cdar extra) "\t")
365         (pop extra))))
366   (insert "\n"))
367
368 (defun nnheader-insert-header (header)
369   (insert
370    "Subject: " (or (mail-header-subject header) "(none)") "\n"
371    "From: " (or (mail-header-from header) "(nobody)") "\n"
372    "Date: " (or (mail-header-date header) "") "\n"
373    "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
374    "References: " (or (mail-header-references header) "") "\n"
375    "Lines: ")
376   (princ (or (mail-header-lines header) 0) (current-buffer))
377   (insert "\n\n"))
378
379 (defun nnheader-insert-article-line (article)
380   (goto-char (point-min))
381   (insert "220 ")
382   (princ article (current-buffer))
383   (insert " Article retrieved.\n")
384   (search-forward "\n\n" nil 'move)
385   (delete-region (point) (point-max))
386   (forward-char -1)
387   (insert "."))
388
389 (defun nnheader-nov-delete-outside-range (beg end)
390   "Delete all NOV lines that lie outside the BEG to END range."
391   ;; First we find the first wanted line.
392   (nnheader-find-nov-line beg)
393   (delete-region (point-min) (point))
394   ;; Then we find the last wanted line.
395   (when (nnheader-find-nov-line end)
396     (forward-line 1))
397   (delete-region (point) (point-max)))
398
399 (defun nnheader-find-nov-line (article)
400   "Put point at the NOV line that start with ARTICLE.
401 If ARTICLE doesn't exist, put point where that line
402 would have been.  The function will return non-nil if
403 the line could be found."
404   ;; This function basically does a binary search.
405   (let ((max (point-max))
406         (min (goto-char (point-min)))
407         (cur (current-buffer))
408         (prev (point-min))
409         num found)
410     (while (not found)
411       (goto-char (/ (+ max min) 2))
412       (beginning-of-line)
413       (if (or (= (point) prev)
414               (eobp))
415           (setq found t)
416         (setq prev (point))
417         (while (and (not (numberp (setq num (read cur))))
418                     (not (eobp)))
419           (gnus-delete-line))
420         (cond ((> num article)
421                (setq max (point)))
422               ((< num article)
423                (setq min (point)))
424               (t
425                (setq found 'yes)))))
426     ;; We may be at the first line.
427     (when (and (not num)
428                (not (eobp)))
429       (setq num (read cur)))
430     ;; Now we may have found the article we're looking for, or we
431     ;; may be somewhere near it.
432     (when (and (not (eq found 'yes))
433                (not (eq num article)))
434       (setq found (point))
435       (while (and (< (point) max)
436                   (or (not (numberp num))
437                       (< num article)))
438         (forward-line 1)
439         (setq found (point))
440         (or (eobp)
441             (= (setq num (read cur)) article)))
442       (unless (eq num article)
443         (goto-char found)))
444     (beginning-of-line)
445     (eq num article)))
446
447 (defun nnheader-retrieve-headers-from-directory* (articles
448                                                   directory dependencies
449                                                   &optional
450                                                   fetch-old force-new large
451                                                   backend)
452   (with-temp-buffer
453     (let* ((file nil)
454            (number (length articles))
455            (count 0)
456            (pathname-coding-system 'binary)
457            (case-fold-search t)
458            (cur (current-buffer))
459            article
460            headers header id end ref in-reply-to lines chars ctype)
461       ;; We don't support fetching by Message-ID.
462       (if (stringp (car articles))
463           'headers
464         (while articles
465           (when (and (file-exists-p
466                       (setq file (expand-file-name
467                                   (int-to-string
468                                    (setq article (pop articles)))
469                                   directory)))
470                      (not (file-directory-p file)))
471             (erase-buffer)
472             (nnheader-insert-head file)
473             (save-restriction
474               (std11-narrow-to-header)
475               (setq
476                header
477                (make-full-mail-header
478                 ;; Number.
479                 article
480                 ;; Subject.
481                 (or (std11-fetch-field "Subject")
482                     "(none)")
483                 ;; From.
484                 (or (std11-fetch-field "From")
485                     "(nobody)")
486                 ;; Date.
487                 (or (std11-fetch-field "Date")
488                     "")
489                 ;; Message-ID.
490                 (progn
491                   (goto-char (point-min))
492                   (setq id (if (re-search-forward
493                                 "^Message-ID: *\\(<[^\n\t> ]+>\\)" nil t)
494                                ;; We do it this way to make sure the Message-ID
495                                ;; is (somewhat) syntactically valid.
496                                (buffer-substring (match-beginning 1)
497                                                  (match-end 1))
498                              ;; If there was no message-id, we just fake one
499                              ;; to make subsequent routines simpler.
500                              (nnheader-generate-fake-message-id))))
501                 ;; References.
502                 (progn
503                   (goto-char (point-min))
504                   (if (search-forward "\nReferences: " nil t)
505                       (progn
506                         (setq end (point))
507                         (prog1
508                             (buffer-substring (match-end 0) (std11-field-end))
509                           (setq ref
510                                 (buffer-substring
511                                  (progn
512                                    ;; (end-of-line)
513                                    (search-backward ">" end t)
514                                    (1+ (point)))
515                                  (progn
516                                    (search-backward "<" end t)
517                                    (point))))))
518                     ;; Get the references from the in-reply-to header if there
519                     ;; were no references and the in-reply-to header looks
520                     ;; promising.
521                     (if (and (search-forward "\nIn-Reply-To: " nil t)
522                              (setq in-reply-to
523                                    (buffer-substring (match-end 0)
524                                                      (std11-field-end)))
525                              (string-match "<[^>]+>" in-reply-to))
526                         (let (ref2)
527                           (setq ref (substring in-reply-to (match-beginning 0)
528                                                (match-end 0)))
529                           (while (string-match "<[^>]+>"
530                                                in-reply-to (match-end 0))
531                             (setq ref2
532                                   (substring in-reply-to (match-beginning 0)
533                                              (match-end 0)))
534                             (when (> (length ref2) (length ref))
535                               (setq ref ref2)))
536                           ref)
537                       (setq ref nil))))
538                 ;; Chars.
539                 (progn
540                   (goto-char (point-min))
541                   (if (search-forward "\nChars: " nil t)
542                       (if (numberp (setq chars (ignore-errors (read cur))))
543                           chars 0)
544                     0))
545                 ;; Lines.
546                 (progn
547                   (goto-char (point-min))
548                   (if (search-forward "\nLines: " nil t)
549                       (if (numberp (setq lines (ignore-errors (read cur))))
550                           lines 0)
551                     0))
552                 ;; Xref.
553                 (std11-fetch-field "Xref")
554                 ))
555               (goto-char (point-min))
556               (if (setq ctype (std11-fetch-field "Content-Type"))
557                   (mime-entity-set-content-type-internal
558                    header (mime-parse-Content-Type ctype)))
559               )
560             (when (setq header
561                         (gnus-dependencies-add-header
562                          header dependencies force-new))
563               (push header headers))
564             )
565           (setq count (1+ count))
566
567           (and large
568                (zerop (% count 20))
569                (nnheader-message 5 "%s: Receiving headers... %d%%"
570                                  backend
571                                  (/ (* count 100) number))))
572
573         (when large
574           (nnheader-message 5 "%s: Receiving headers...done" backend))
575
576         headers))))
577
578 (defun nnheader-retrieve-headers-from-directory (articles
579                                                  directory dependencies
580                                                  &optional
581                                                  fetch-old force-new large
582                                                  backend)
583   (cons 'header
584         (nreverse (nnheader-retrieve-headers-from-directory*
585                    articles directory dependencies
586                    fetch-old force-new large backend))))
587
588 (defun nnheader-get-newsgroup-headers-xover* (sequence
589                                               &optional
590                                               force-new dependencies
591                                               group)
592   "Parse the news overview data in the server buffer, and return a
593 list of headers that match SEQUENCE (see `nntp-retrieve-headers')."
594   ;; Get the Xref when the users reads the articles since most/some
595   ;; NNTP servers do not include Xrefs when using XOVER.
596   ;; (setq gnus-article-internal-prepare-hook '(gnus-article-get-xrefs))
597   (let ((cur nntp-server-buffer)
598         number headers header)
599     (save-excursion
600       (set-buffer nntp-server-buffer)
601       ;; Allow the user to mangle the headers before parsing them.
602       (gnus-run-hooks 'gnus-parse-headers-hook)
603       (goto-char (point-min))
604       (while (not (eobp))
605         (condition-case ()
606             (while (and sequence (not (eobp)))
607               (setq number (read cur))
608               (while (and sequence
609                           (< (car sequence) number))
610                 (setq sequence (cdr sequence)))
611               (and sequence
612                    (eq number (car sequence))
613                    (progn
614                      (setq sequence (cdr sequence))
615                      (setq header (inline
616                                     (gnus-nov-parse-line
617                                      number dependencies force-new))))
618                    (push header headers))
619               (forward-line 1))
620           (error
621            (gnus-error 4 "Strange nov line (%d)"
622                        (count-lines (point-min) (point)))))
623         (forward-line 1))
624       ;; A common bug in inn is that if you have posted an article and
625       ;; then retrieves the active file, it will answer correctly --
626       ;; the new article is included.  However, a NOV entry for the
627       ;; article may not have been generated yet, so this may fail.
628       ;; We work around this problem by retrieving the last few
629       ;; headers using HEAD.
630       headers)))
631
632 ;; Various cruft the backends and Gnus need to communicate.
633
634 (defvar nntp-server-buffer nil)
635 (defvar nntp-process-response nil)
636 (defvar gnus-verbose-backends 7
637   "*A number that says how talkative the Gnus backends should be.")
638 (defvar gnus-nov-is-evil nil
639   "If non-nil, Gnus backends will never output headers in the NOV format.")
640 (defvar news-reply-yank-from nil)
641 (defvar news-reply-yank-message-id nil)
642
643 (defvar nnheader-callback-function nil)
644
645 (defun nnheader-init-server-buffer ()
646   "Initialize the Gnus-backend communication buffer."
647   (save-excursion
648     (unless (gnus-buffer-live-p nntp-server-buffer)
649       (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
650     (set-buffer nntp-server-buffer)
651     (erase-buffer)
652     (kill-all-local-variables)
653     (setq case-fold-search t)           ;Should ignore case.
654     (set (make-local-variable 'nntp-process-response) nil)
655     t))
656
657 ;;; Various functions the backends use.
658
659 (defun nnheader-file-error (file)
660   "Return a string that says what is wrong with FILE."
661   (format
662    (cond
663     ((not (file-exists-p file))
664      "%s does not exist")
665     ((file-directory-p file)
666      "%s is a directory")
667     ((not (file-readable-p file))
668      "%s is not readable"))
669    file))
670
671 (defun nnheader-insert-head (file)
672   "Insert the head of the article."
673   (when (file-exists-p file)
674     (if (eq nnheader-max-head-length t)
675         ;; Just read the entire file.
676         (nnheader-insert-file-contents file)
677       ;; Read 1K blocks until we find a separator.
678       (let ((beg 0)
679             format-alist)
680         (while (and (eq nnheader-head-chop-length
681                         (nth 1 (nnheader-insert-file-contents
682                                 file nil beg
683                                 (incf beg nnheader-head-chop-length))))
684                     (prog1 (not (search-forward "\n\n" nil t))
685                       (goto-char (point-max)))
686                     (or (null nnheader-max-head-length)
687                         (< beg nnheader-max-head-length))))))
688     t))
689
690 (defun nnheader-article-p ()
691   "Say whether the current buffer looks like an article."
692   (goto-char (point-min))
693   (if (not (search-forward "\n\n" nil t))
694       nil
695     (narrow-to-region (point-min) (1- (point)))
696     (goto-char (point-min))
697     (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
698       (goto-char (match-end 0)))
699     (prog1
700         (eobp)
701       (widen))))
702
703 (defun nnheader-insert-references (references message-id)
704   "Insert a References header based on REFERENCES and MESSAGE-ID."
705   (if (and (not references) (not message-id))
706       ;; This is invalid, but not all articles have Message-IDs.
707       ()
708     (mail-position-on-field "References")
709     (let ((begin (save-excursion (beginning-of-line) (point)))
710           (fill-column 78)
711           (fill-prefix "\t"))
712       (when references
713         (insert references))
714       (when (and references message-id)
715         (insert " "))
716       (when message-id
717         (insert message-id))
718       ;; Fold long References lines to conform to RFC1036 (sort of).
719       ;; The region must end with a newline to fill the region
720       ;; without inserting extra newline.
721       (fill-region-as-paragraph begin (1+ (point))))))
722
723 (defun nnheader-replace-header (header new-value)
724   "Remove HEADER and insert the NEW-VALUE."
725   (save-excursion
726     (save-restriction
727       (nnheader-narrow-to-headers)
728       (prog1
729           (message-remove-header header)
730         (goto-char (point-max))
731         (insert header ": " new-value "\n")))))
732
733 (defun nnheader-narrow-to-headers ()
734   "Narrow to the head of an article."
735   (widen)
736   (narrow-to-region
737    (goto-char (point-min))
738    (if (search-forward "\n\n" nil t)
739        (1- (point))
740      (point-max)))
741   (goto-char (point-min)))
742
743 (defun nnheader-set-temp-buffer (name &optional noerase)
744   "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
745   (set-buffer (get-buffer-create name))
746   (buffer-disable-undo)
747   (unless noerase
748     (erase-buffer))
749   (current-buffer))
750
751 (defvar jka-compr-compression-info-list)
752 (defvar nnheader-numerical-files
753   (if (boundp 'jka-compr-compression-info-list)
754       (concat "\\([0-9]+\\)\\("
755               (mapconcat (lambda (i) (aref i 0))
756                          jka-compr-compression-info-list "\\|")
757               "\\)?")
758     "[0-9]+$")
759   "Regexp that match numerical files.")
760
761 (defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
762   "Regexp that matches numerical file names.")
763
764 (defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
765   "Regexp that matches numerical full file paths.")
766
767 (defsubst nnheader-file-to-number (file)
768   "Take a file name and return the article number."
769   (if (string= nnheader-numerical-short-files "^[0-9]+$")
770       (string-to-int file)
771     (string-match nnheader-numerical-short-files file)
772     (string-to-int (match-string 0 file))))
773
774 (defun nnheader-directory-files-safe (&rest args)
775   ;; It has been reported numerous times that `directory-files'
776   ;; fails with an alarming frequency on NFS mounted file systems.
777   ;; This function executes that function twice and returns
778   ;; the longest result.
779   (let ((first (apply 'directory-files args))
780         (second (apply 'directory-files args)))
781     (if (> (length first) (length second))
782         first
783       second)))
784
785 (defun nnheader-directory-articles (dir)
786   "Return a list of all article files in a directory."
787   (mapcar 'nnheader-file-to-number
788           (nnheader-directory-files-safe
789            dir nil nnheader-numerical-short-files t)))
790
791 (defun nnheader-article-to-file-alist (dir)
792   "Return an alist of article/file pairs in DIR."
793   (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
794           (nnheader-directory-files-safe
795            dir nil nnheader-numerical-short-files t)))
796
797 (defun nnheader-fold-continuation-lines ()
798   "Fold continuation lines in the current buffer."
799   (nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
800
801 (defun nnheader-translate-file-chars (file &optional full)
802   "Translate FILE into something that can be a file name.
803 If FULL, translate everything."
804   (if (null nnheader-file-name-translation-alist)
805       ;; No translation is necessary.
806       file
807     (let* ((i 0)
808            trans leaf path len)
809       (if full
810           ;; Do complete translation.
811           (setq leaf (copy-sequence file)
812                 path ""
813                 i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1))) 
814                       2 0))
815         ;; We translate -- but only the file name.  We leave the directory
816         ;; alone.
817         (if (string-match "/[^/]+\\'" file)
818             ;; This is needed on NT's and stuff.
819             (setq leaf (substring file (1+ (match-beginning 0)))
820                   path (substring file 0 (1+ (match-beginning 0))))
821           ;; Fall back on this.
822           (setq leaf (file-name-nondirectory file)
823                 path (file-name-directory file))))
824       (setq len (length leaf))
825       (while (< i len)
826         (when (setq trans (cdr (assq (aref leaf i)
827                                      nnheader-file-name-translation-alist)))
828           (aset leaf i trans))
829         (incf i))
830       (concat path leaf))))
831
832 (defun nnheader-report (backend &rest args)
833   "Report an error from the BACKEND.
834 The first string in ARGS can be a format string."
835   (set (intern (format "%s-status-string" backend))
836        (if (< (length args) 2)
837            (car args)
838          (apply 'format args)))
839   nil)
840
841 (defun nnheader-get-report (backend)
842   "Get the most recent report from BACKEND."
843   (condition-case ()
844       (nnheader-message 5 "%s" (symbol-value (intern (format "%s-status-string"
845                                                              backend))))
846     (error (nnheader-message 5 ""))))
847
848 (defun nnheader-insert (format &rest args)
849   "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
850 If FORMAT isn't a format string, it and all ARGS will be inserted
851 without formatting."
852   (save-excursion
853     (set-buffer nntp-server-buffer)
854     (erase-buffer)
855     (if (string-match "%" format)
856         (insert (apply 'format format args))
857       (apply 'insert format args))
858     t))
859
860 (defun nnheader-replace-chars-in-string (string from to)
861   "Replace characters in STRING from FROM to TO."
862   (let ((string (substring string 0))   ;Copy string.
863         (len (length string))
864         (idx 0))
865     ;; Replace all occurrences of FROM with TO.
866     (while (< idx len)
867       (when (= (aref string idx) from)
868         (aset string idx to))
869       (setq idx (1+ idx)))
870     string))
871
872 (defun nnheader-replace-duplicate-chars-in-string (string from to)
873   "Replace characters in STRING from FROM to TO."
874   (let ((string (substring string 0))   ;Copy string.
875         (len (length string))
876         (idx 0) prev i)
877     ;; Replace all occurrences of FROM with TO.
878     (while (< idx len)
879       (setq i (aref string idx))
880       (when (and (eq prev from) (= i from))
881         (aset string (1- idx) to)
882         (aset string idx to))
883       (setq prev i)
884       (setq idx (1+ idx)))
885     string))
886
887 (defun nnheader-file-to-group (file &optional top)
888   "Return a group name based on FILE and TOP."
889   (nnheader-replace-chars-in-string
890    (if (not top)
891        file
892      (condition-case ()
893          (substring (expand-file-name file)
894                     (length
895                      (expand-file-name
896                       (file-name-as-directory top))))
897        (error "")))
898    ?/ ?.))
899
900 (defun nnheader-message (level &rest args)
901   "Message if the Gnus backends are talkative."
902   (if (or (not (numberp gnus-verbose-backends))
903           (<= level gnus-verbose-backends))
904       (apply 'message args)
905     (apply 'format args)))
906
907 (defun nnheader-be-verbose (level)
908   "Return whether the backends should be verbose on LEVEL."
909   (or (not (numberp gnus-verbose-backends))
910       (<= level gnus-verbose-backends)))
911
912 (defvar nnheader-pathname-coding-system 'binary
913   "*Coding system for pathname.")
914
915 (defun nnheader-group-pathname (group dir &optional file)
916   "Make pathname for GROUP."
917   (concat
918    (let ((dir (file-name-as-directory (expand-file-name dir))))
919      ;; If this directory exists, we use it directly.
920      (if (file-directory-p (concat dir group))
921          (concat dir group "/")
922        ;; If not, we translate dots into slashes.
923        (concat dir
924                (encode-coding-string
925                 (nnheader-replace-chars-in-string group ?. ?/)
926                 nnheader-pathname-coding-system)
927                "/")))
928    (cond ((null file) "")
929          ((numberp file) (int-to-string file))
930          (t file))))
931
932 (defun nnheader-functionp (form)
933   "Return non-nil if FORM is funcallable."
934   (or (and (symbolp form) (fboundp form))
935       (and (listp form) (eq (car form) 'lambda))))
936
937 (defun nnheader-concat (dir &rest files)
938   "Concat DIR as directory to FILE."
939   (apply 'concat (file-name-as-directory dir) files))
940
941 (defun nnheader-ms-strip-cr ()
942   "Strip ^M from the end of all lines."
943   (save-excursion
944     (goto-char (point-min))
945     (while (re-search-forward "\r$" nil t)
946       (delete-backward-char 1))))
947
948 (defun nnheader-file-size (file)
949   "Return the file size of FILE or 0."
950   (or (nth 7 (file-attributes file)) 0))
951
952 (defun nnheader-find-etc-directory (package &optional file)
953   "Go through the path and find the \".../etc/PACKAGE\" directory.
954 If FILE, find the \".../etc/PACKAGE\" file instead."
955   (let ((path load-path)
956         dir result)
957     ;; We try to find the dir by looking at the load path,
958     ;; stripping away the last component and adding "etc/".
959     (while path
960       (if (and (car path)
961                (file-exists-p
962                 (setq dir (concat
963                            (file-name-directory
964                             (directory-file-name (car path)))
965                            "etc/" package
966                            (if file "" "/"))))
967                (or file (file-directory-p dir)))
968           (setq result dir
969                 path nil)
970         (setq path (cdr path))))
971     result))
972
973 (defvar ange-ftp-path-format)
974 (defvar efs-path-regexp)
975 (defun nnheader-re-read-dir (path)
976   "Re-read directory PATH if PATH is on a remote system."
977   (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
978       (when (string-match efs-path-regexp path)
979         (efs-re-read-dir path))
980     (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
981       (when (string-match (car ange-ftp-path-format) path)
982         (ange-ftp-re-read-dir path)))))
983
984 (defvar nnheader-file-coding-system 'raw-text
985   "Coding system used in file backends of Gnus.")
986
987 (defun nnheader-insert-file-contents (filename &optional visit beg end replace)
988   "Like `insert-file-contents', q.v., but only reads in the file.
989 A buffer may be modified in several ways after reading into the buffer due
990 to advanced Emacs features, such as file-name-handlers, format decoding,
991 find-file-hooks, etc.
992   This function ensures that none of these modifications will take place."
993   (let ((format-alist nil)
994         (auto-mode-alist (nnheader-auto-mode-alist))
995         (default-major-mode 'fundamental-mode)
996         (enable-local-variables nil)
997         (after-insert-file-functions nil)
998         (enable-local-eval nil)
999         (find-file-hooks nil))
1000     (insert-file-contents-as-coding-system
1001      nnheader-file-coding-system filename visit beg end replace)))
1002
1003 (defun nnheader-find-file-noselect (&rest args)
1004   (let ((format-alist nil)
1005         (auto-mode-alist (nnheader-auto-mode-alist))
1006         (default-major-mode 'fundamental-mode)
1007         (enable-local-variables nil)
1008         (after-insert-file-functions nil)
1009         (enable-local-eval nil)
1010         (find-file-hooks nil))
1011     (apply 'find-file-noselect-as-coding-system
1012            nnheader-file-coding-system args)))
1013
1014 (defun nnheader-auto-mode-alist ()
1015   "Return an `auto-mode-alist' with only the .gz (etc) thingies."
1016   (let ((alist auto-mode-alist)
1017         out)
1018     (while alist
1019       (when (listp (cdar alist))
1020         (push (car alist) out))
1021       (pop alist))
1022     (nreverse out)))
1023
1024 (defun nnheader-directory-regular-files (dir)
1025   "Return a list of all regular files in DIR."
1026   (let ((files (directory-files dir t))
1027         out)
1028     (while files
1029       (when (file-regular-p (car files))
1030         (push (car files) out))
1031       (pop files))
1032     (nreverse out)))
1033
1034 (defun nnheader-directory-files (&rest args)
1035   "Same as `directory-files', but prune \".\" and \"..\"."
1036   (let ((files (apply 'directory-files args))
1037         out)
1038     (while files
1039       (unless (member (file-name-nondirectory (car files)) '("." ".."))
1040         (push (car files) out))
1041       (pop files))
1042     (nreverse out)))
1043
1044 (defmacro nnheader-skeleton-replace (from &optional to regexp)
1045   `(let ((new (generate-new-buffer " *nnheader replace*"))
1046          (cur (current-buffer))
1047          (start (point-min)))
1048      (set-buffer cur)
1049      (goto-char (point-min))
1050      (while (,(if regexp 're-search-forward 'search-forward)
1051              ,from nil t)
1052        (insert-buffer-substring
1053         cur start (prog1 (match-beginning 0) (set-buffer new)))
1054        (goto-char (point-max))
1055        ,(when to `(insert ,to))
1056        (set-buffer cur)
1057        (setq start (point)))
1058      (insert-buffer-substring
1059       cur start (prog1 (point-max) (set-buffer new)))
1060      (copy-to-buffer cur (point-min) (point-max))
1061      (kill-buffer (current-buffer))
1062      (set-buffer cur)))
1063
1064 (defun nnheader-replace-string (from to)
1065   "Do a fast replacement of FROM to TO from point to point-max."
1066   (nnheader-skeleton-replace from to))
1067
1068 (defun nnheader-replace-regexp (from to)
1069   "Do a fast regexp replacement of FROM to TO from point to point-max."
1070   (nnheader-skeleton-replace from to t))
1071
1072 (defun nnheader-strip-cr ()
1073   "Strip all \r's from the current buffer."
1074   (nnheader-skeleton-replace "\r"))
1075
1076 (fset 'nnheader-run-at-time 'run-at-time)
1077 (fset 'nnheader-cancel-timer 'cancel-timer)
1078 (fset 'nnheader-cancel-function-timers 'cancel-function-timers)
1079
1080 (defun nnheader-Y-or-n-p (prompt)
1081   "Ask user a \"Y/n\" question. Return t if answer is neither \"n\", \"N\" nor \"C-g\"."
1082   (let ((cursor-in-echo-area t)
1083         (echo-keystrokes 0)
1084         (inhibit-quit t)
1085         ans)
1086     (let (message-log-max)
1087       (while (not (memq ans '(?\  ?N ?Y ?\C-g ?\e ?\n ?\r ?n ?y)))
1088         (message "%s(Y/n) " prompt)
1089         (setq ans (read-char-exclusive))))
1090     (if (memq ans '(?\C-g ?N ?n))
1091         (progn
1092           (message "%s(Y/n) No" prompt)
1093           nil)
1094       (message "%s(Y/n) Yes" prompt)
1095       t)))
1096
1097 (when (string-match "XEmacs\\|Lucid" emacs-version)
1098   (require 'nnheaderxm))
1099
1100 (run-hooks 'nnheader-load-hook)
1101
1102 (provide 'nnheader)
1103
1104 ;;; nnheader.el ends here