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