Move lisp/emacs-lisp/authors.el to admin/
[bpt/emacs.git] / lisp / gnus / nnheader.el
CommitLineData
eec82323 1;;; nnheader.el --- header access macros for Gnus and its backends
16409b0b 2
ba318903 3;; Copyright (C) 1987-1990, 1993-1998, 2000-2014 Free Software
ab422c4d 4;; Foundation, Inc.
eec82323
LMI
5
6;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
23f87bed 7;; Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
8;; Keywords: news
9
10;; This file is part of GNU Emacs.
11
5e809f55 12;; GNU Emacs is free software: you can redistribute it and/or modify
eec82323 13;; it under the terms of the GNU General Public License as published by
5e809f55
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
eec82323
LMI
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
5e809f55 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
24
25;;; Commentary:
26
eec82323
LMI
27;;; Code:
28
7df7482d 29(eval-when-compile (require 'cl))
6f3b74c6 30
03da5d08 31(defvar nnmail-extra-headers)
9efa445f
DN
32(defvar gnus-newsgroup-name)
33(defvar nnheader-file-coding-system)
34(defvar jka-compr-compression-info-list)
03da5d08 35
6f3b74c6
GM
36;; Requiring `gnus-util' at compile time creates a circular
37;; dependency between nnheader.el and gnus-util.el.
23f87bed 38;;(eval-when-compile (require 'gnus-util))
7df7482d 39
eec82323 40(require 'mail-utils)
16409b0b 41(require 'mm-util)
23f87bed 42(require 'gnus-util)
5a6a61f7
GM
43(autoload 'gnus-range-add "gnus-range")
44(autoload 'gnus-remove-from-range "gnus-range")
8abf1b22
GM
45;; FIXME none of these are used explicitly in this file.
46(autoload 'gnus-sorted-intersection "gnus-range")
47(autoload 'gnus-intersection "gnus-range")
48(autoload 'gnus-sorted-complement "gnus-range")
49(autoload 'gnus-sorted-difference "gnus-range")
23f87bed
MB
50
51(defcustom gnus-verbose-backends 7
52 "Integer that says how verbose the Gnus backends should be.
53The higher the number, the more messages the Gnus backends will flash
54to say what it's doing. At zero, the Gnus backends will be totally
55mute; at five, they will display most important messages; and at ten,
56they will keep on jabbering all the time."
57 :group 'gnus-start
58 :type 'integer)
59
60(defcustom gnus-nov-is-evil nil
61 "If non-nil, Gnus backends will never output headers in the NOV format."
62 :group 'gnus-server
63 :type 'boolean)
eec82323 64
f4dd4ae8 65(defvar nnheader-max-head-length 8192
23f87bed
MB
66 "*Max length of the head of articles.
67
68Value is an integer, nil, or t. nil means read in chunks of a file
69indefinitely until a complete head is found\; t means always read the
70entire file immediately, disregarding `nnheader-head-chop-length'.
71
72Integer values will in effect be rounded up to the nearest multiple of
73`nnheader-head-chop-length'.")
eec82323
LMI
74
75(defvar nnheader-head-chop-length 2048
76 "*Length of each read operation when trying to fetch HEAD headers.")
77
23f87bed 78(defvar nnheader-read-timeout
f5ec697d 79 (if (string-match "windows-nt\\|os/2\\|cygwin"
23f87bed 80 (symbol-name system-type))
e62e7654
MB
81 ;; http://thread.gmane.org/v9655t3pjo.fsf@marauder.physik.uni-ulm.de
82 ;;
83 ;; IIRC, values lower than 1.0 didn't/don't work on Windows/DOS.
84 ;;
85 ;; There should probably be a runtime test to determine the timing
86 ;; resolution, or a primitive to report it. I don't know off-hand
87 ;; what's possible. Perhaps better, maybe the Windows/DOS primitive
88 ;; could round up non-zero timeouts to a minimum of 1.0?
89 1.0
e3e955fe
MB
90 ;; 2008-05-19 change by Larsi:
91 ;; Change the default timeout from 0.1 seconds to 0.01 seconds. This will
92 ;; make nntp and pop3 article retrieval faster in some cases, but might
93 ;; make CPU usage larger. If this has any bad side effects, we might
94 ;; revert this change.
a368801c 95 0.01)
e3e955fe
MB
96 ;; When changing this variable, consider changing `pop3-read-timeout' as
97 ;; well.
23f87bed
MB
98 "How long nntp should wait between checking for the end of output.
99Shorter values mean quicker response, but are more CPU intensive.")
100
c60ee5e7 101(defvar nnheader-file-name-translation-alist
3e7b2fa7
SZ
102 (let ((case-fold-search t))
103 (cond
f5ec697d 104 ((string-match "windows-nt\\|os/2\\|cygwin"
3e7b2fa7
SZ
105 (symbol-name system-type))
106 (append (mapcar (lambda (c) (cons c ?_))
107 '(?: ?* ?\" ?< ?> ??))
23f87bed 108 (if (string-match "windows-nt\\|cygwin"
3e7b2fa7
SZ
109 (symbol-name system-type))
110 nil
111 '((?+ . ?-)))))
112 (t nil)))
eec82323 113 "*Alist that says how to translate characters in file names.
16409b0b 114For instance, if \":\" is invalid as a file character in file names
eec82323
LMI
115on your system, you could say something like:
116
117\(setq nnheader-file-name-translation-alist '((?: . ?_)))")
118
23f87bed
MB
119(defvar nnheader-directory-separator-character
120 (string-to-char (substring (file-name-as-directory ".") -1))
121 "*A character used to a directory separator.")
122
8abf1b22
GM
123(autoload 'nnmail-message-id "nnmail")
124(autoload 'mail-position-on-field "sendmail")
8abf1b22 125(autoload 'gnus-buffer-live-p "gnus-util")
eec82323
LMI
126
127;;; Header access macros.
128
16409b0b
GM
129;; These macros may look very much like the ones in GNUS 4.1. They
130;; are, in a way, but you should note that the indices they use have
131;; been changed from the internal GNUS format to the NOV format. The
132;; makes it possible to read headers from XOVER much faster.
133;;
134;; The format of a header is now:
135;; [number subject from date id references chars lines xref extra]
136;;
137;; (That next-to-last entry is defined as "misc" in the NOV format,
138;; but Gnus uses it for xrefs.)
139
eec82323
LMI
140(defmacro mail-header-number (header)
141 "Return article number in HEADER."
142 `(aref ,header 0))
143
144(defmacro mail-header-set-number (header number)
145 "Set article number of HEADER to NUMBER."
146 `(aset ,header 0 ,number))
147
148(defmacro mail-header-subject (header)
149 "Return subject string in HEADER."
150 `(aref ,header 1))
151
152(defmacro mail-header-set-subject (header subject)
153 "Set article subject of HEADER to SUBJECT."
154 `(aset ,header 1 ,subject))
155
156(defmacro mail-header-from (header)
157 "Return author string in HEADER."
158 `(aref ,header 2))
159
160(defmacro mail-header-set-from (header from)
161 "Set article author of HEADER to FROM."
162 `(aset ,header 2 ,from))
163
164(defmacro mail-header-date (header)
165 "Return date in HEADER."
166 `(aref ,header 3))
167
168(defmacro mail-header-set-date (header date)
169 "Set article date of HEADER to DATE."
170 `(aset ,header 3 ,date))
171
172(defalias 'mail-header-message-id 'mail-header-id)
173(defmacro mail-header-id (header)
174 "Return Id in HEADER."
175 `(aref ,header 4))
176
177(defalias 'mail-header-set-message-id 'mail-header-set-id)
178(defmacro mail-header-set-id (header id)
179 "Set article Id of HEADER to ID."
180 `(aset ,header 4 ,id))
181
182(defmacro mail-header-references (header)
183 "Return references in HEADER."
184 `(aref ,header 5))
185
186(defmacro mail-header-set-references (header ref)
187 "Set article references of HEADER to REF."
188 `(aset ,header 5 ,ref))
189
190(defmacro mail-header-chars (header)
191 "Return number of chars of article in HEADER."
192 `(aref ,header 6))
193
194(defmacro mail-header-set-chars (header chars)
195 "Set number of chars in article of HEADER to CHARS."
196 `(aset ,header 6 ,chars))
197
198(defmacro mail-header-lines (header)
199 "Return lines in HEADER."
200 `(aref ,header 7))
201
202(defmacro mail-header-set-lines (header lines)
203 "Set article lines of HEADER to LINES."
204 `(aset ,header 7 ,lines))
205
206(defmacro mail-header-xref (header)
207 "Return xref string in HEADER."
208 `(aref ,header 8))
209
210(defmacro mail-header-set-xref (header xref)
16409b0b 211 "Set article XREF of HEADER to xref."
eec82323
LMI
212 `(aset ,header 8 ,xref))
213
16409b0b
GM
214(defmacro mail-header-extra (header)
215 "Return the extra headers in HEADER."
216 `(aref ,header 9))
217
01c52d31 218(defun mail-header-set-extra (header extra)
16409b0b 219 "Set the extra headers in HEADER to EXTRA."
01c52d31 220 (aset header 9 extra))
16409b0b
GM
221
222(defsubst make-mail-header (&optional init)
eec82323 223 "Create a new mail header structure initialized with INIT."
16409b0b 224 (make-vector 10 init))
eec82323 225
16409b0b
GM
226(defsubst make-full-mail-header (&optional number subject from date id
227 references chars lines xref
228 extra)
eec82323 229 "Create a new mail header structure initialized with the parameters given."
16409b0b 230 (vector number subject from date id references chars lines xref extra))
eec82323
LMI
231
232;; fake message-ids: generation and detection
233
234(defvar nnheader-fake-message-id 1)
235
01c52d31
MB
236(defsubst nnheader-generate-fake-message-id (&optional number)
237 (if (numberp number)
238 (format "fake+none+%s+%d" gnus-newsgroup-name number)
239 (format "fake+none+%s+%s"
240 gnus-newsgroup-name
241 (int-to-string (incf nnheader-fake-message-id)))))
eec82323
LMI
242
243(defsubst nnheader-fake-message-id-p (id)
23f87bed 244 (save-match-data ; regular message-id's are <.*>
01c52d31 245 (string-match "\\`fake\\+none\\+.*\\+[0-9]+\\'" id)))
eec82323
LMI
246
247;; Parsing headers and NOV lines.
248
23f87bed
MB
249(defsubst nnheader-remove-cr-followed-by-lf ()
250 (goto-char (point-max))
251 (while (search-backward "\r\n" nil t)
252 (delete-char 1)))
253
eec82323 254(defsubst nnheader-header-value ()
23f87bed 255 (skip-chars-forward " \t")
01c52d31 256 (buffer-substring (point) (point-at-eol)))
eec82323 257
8867ac54
GM
258(autoload 'ietf-drums-unfold-fws "ietf-drums")
259
23f87bed
MB
260(defun nnheader-parse-naked-head (&optional number)
261 ;; This function unfolds continuation lines in this buffer
262 ;; destructively. When this side effect is unwanted, use
263 ;; `nnheader-parse-head' instead of this function.
eec82323 264 (let ((case-fold-search t)
eec82323 265 (buffer-read-only nil)
23f87bed
MB
266 (cur (current-buffer))
267 (p (point-min))
268 in-reply-to lines ref)
269 (nnheader-remove-cr-followed-by-lf)
270 (ietf-drums-unfold-fws)
271 (subst-char-in-region (point-min) (point-max) ?\t ? )
272 (goto-char p)
273 (insert "\n")
eec82323 274 (prog1
23f87bed
MB
275 ;; This implementation of this function, with nine
276 ;; search-forwards instead of the one re-search-forward and a
277 ;; case (which basically was the old function) is actually
278 ;; about twice as fast, even though it looks messier. You
279 ;; can't have everything, I guess. Speed and elegance don't
280 ;; always go hand in hand.
281 (vector
282 ;; Number.
283 (or number 0)
284 ;; Subject.
285 (progn
286 (goto-char p)
287 (if (search-forward "\nsubject:" nil t)
288 (nnheader-header-value) "(none)"))
289 ;; From.
290 (progn
291 (goto-char p)
292 (if (search-forward "\nfrom:" nil t)
293 (nnheader-header-value) "(nobody)"))
294 ;; Date.
295 (progn
296 (goto-char p)
297 (if (search-forward "\ndate:" nil t)
298 (nnheader-header-value) ""))
299 ;; Message-ID.
300 (progn
301 (goto-char p)
302 (if (search-forward "\nmessage-id:" nil t)
303 (buffer-substring
01c52d31 304 (1- (or (search-forward "<" (point-at-eol) t)
23f87bed 305 (point)))
01c52d31 306 (or (search-forward ">" (point-at-eol) t) (point)))
23f87bed
MB
307 ;; If there was no message-id, we just fake one to make
308 ;; subsequent routines simpler.
01c52d31 309 (nnheader-generate-fake-message-id number)))
23f87bed
MB
310 ;; References.
311 (progn
312 (goto-char p)
313 (if (search-forward "\nreferences:" nil t)
314 (nnheader-header-value)
315 ;; Get the references from the in-reply-to header if
316 ;; there were no references and the in-reply-to header
317 ;; looks promising.
318 (if (and (search-forward "\nin-reply-to:" nil t)
319 (setq in-reply-to (nnheader-header-value))
320 (string-match "<[^\n>]+>" in-reply-to))
321 (let (ref2)
322 (setq ref (substring in-reply-to (match-beginning 0)
323 (match-end 0)))
324 (while (string-match "<[^\n>]+>"
325 in-reply-to (match-end 0))
326 (setq ref2 (substring in-reply-to (match-beginning 0)
327 (match-end 0)))
328 (when (> (length ref2) (length ref))
329 (setq ref ref2)))
330 ref)
331 nil)))
332 ;; Chars.
333 0
334 ;; Lines.
335 (progn
336 (goto-char p)
337 (if (search-forward "\nlines: " nil t)
338 (if (numberp (setq lines (read cur)))
339 lines 0)
340 0))
341 ;; Xref.
342 (progn
343 (goto-char p)
344 (and (search-forward "\nxref:" nil t)
345 (nnheader-header-value)))
346 ;; Extra.
347 (when nnmail-extra-headers
348 (let ((extra nnmail-extra-headers)
349 out)
350 (while extra
351 (goto-char p)
352 (when (search-forward
353 (concat "\n" (symbol-name (car extra)) ":") nil t)
354 (push (cons (car extra) (nnheader-header-value))
355 out))
356 (pop extra))
357 out)))
358 (goto-char p)
359 (delete-char 1))))
360
361(defun nnheader-parse-head (&optional naked)
362 (let ((cur (current-buffer)) num beg end)
363 (when (if naked
364 (setq num 0
365 beg (point-min)
366 end (point-max))
23f87bed
MB
367 ;; Search to the beginning of the next header. Error
368 ;; messages do not begin with 2 or 3.
369 (when (re-search-forward "^[23][0-9]+ " nil t)
23f87bed
MB
370 (setq num (read cur)
371 beg (point)
372 end (if (search-forward "\n.\n" nil t)
094ae2ab 373 (goto-char (- (point) 2))
23f87bed
MB
374 (point)))))
375 (with-temp-buffer
376 (insert-buffer-substring cur beg end)
377 (nnheader-parse-naked-head num)))))
eec82323
LMI
378
379(defmacro nnheader-nov-skip-field ()
380 '(search-forward "\t" eol 'move))
381
382(defmacro nnheader-nov-field ()
383 '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
384
385(defmacro nnheader-nov-read-integer ()
386 '(prog1
16409b0b 387 (if (eq (char-after) ?\t)
eec82323 388 0
16409b0b
GM
389 (let ((num (condition-case nil
390 (read (current-buffer))
391 (error nil))))
eec82323
LMI
392 (if (numberp num) num 0)))
393 (or (eobp) (forward-char 1))))
394
16409b0b
GM
395(defmacro nnheader-nov-parse-extra ()
396 '(let (out string)
397 (while (not (memq (char-after) '(?\n nil)))
398 (setq string (nnheader-nov-field))
399 (when (string-match "^\\([^ :]+\\): " string)
400 (push (cons (intern (match-string 1 string))
401 (substring string (match-end 0)))
402 out)))
403 out))
404
01c52d31
MB
405(eval-and-compile
406 (defvar nnheader-uniquify-message-id nil))
407
408(defmacro nnheader-nov-read-message-id (&optional number)
409 `(let ((id (nnheader-nov-field)))
16409b0b 410 (if (string-match "^<[^>]+>$" id)
01c52d31
MB
411 ,(if nnheader-uniquify-message-id
412 `(if (string-match "__[^@]+@" id)
413 (concat (substring id 0 (match-beginning 0))
414 (substring id (1- (match-end 0))))
415 id)
416 'id)
417 (nnheader-generate-fake-message-id ,number))))
eec82323
LMI
418
419(defun nnheader-parse-nov ()
01c52d31
MB
420 (let ((eol (point-at-eol))
421 (number (nnheader-nov-read-integer)))
eec82323 422 (vector
01c52d31 423 number ; number
eec82323
LMI
424 (nnheader-nov-field) ; subject
425 (nnheader-nov-field) ; from
426 (nnheader-nov-field) ; date
01c52d31 427 (nnheader-nov-read-message-id number) ; id
eec82323
LMI
428 (nnheader-nov-field) ; refs
429 (nnheader-nov-read-integer) ; chars
430 (nnheader-nov-read-integer) ; lines
16409b0b 431 (if (eq (char-after) ?\n)
eec82323 432 nil
8b93df01
DL
433 (if (looking-at "Xref: ")
434 (goto-char (match-end 0)))
435 (nnheader-nov-field)) ; Xref
16409b0b 436 (nnheader-nov-parse-extra)))) ; extra
eec82323
LMI
437
438(defun nnheader-insert-nov (header)
439 (princ (mail-header-number header) (current-buffer))
16409b0b
GM
440 (let ((p (point)))
441 (insert
442 "\t"
443 (or (mail-header-subject header) "(none)") "\t"
444 (or (mail-header-from header) "(nobody)") "\t"
445 (or (mail-header-date header) "") "\t"
446 (or (mail-header-id header)
447 (nnmail-message-id))
448 "\t"
449 (or (mail-header-references header) "") "\t")
450 (princ (or (mail-header-chars header) 0) (current-buffer))
451 (insert "\t")
452 (princ (or (mail-header-lines header) 0) (current-buffer))
453 (insert "\t")
454 (when (mail-header-xref header)
455 (insert "Xref: " (mail-header-xref header)))
456 (when (or (mail-header-xref header)
457 (mail-header-extra header))
458 (insert "\t"))
459 (when (mail-header-extra header)
460 (let ((extra (mail-header-extra header)))
461 (while extra
462 (insert (symbol-name (caar extra))
bdaa75c7 463 ": " (if (stringp (cdar extra)) (cdar extra) "") "\t")
16409b0b
GM
464 (pop extra))))
465 (insert "\n")
466 (backward-char 1)
467 (while (search-backward "\n" p t)
468 (delete-char 1))
469 (forward-line 1)))
470
23f87bed
MB
471(defun nnheader-parse-overview-file (file)
472 "Parse FILE and return a list of headers."
473 (mm-with-unibyte-buffer
474 (nnheader-insert-file-contents file)
475 (goto-char (point-min))
476 (let (headers)
477 (while (not (eobp))
478 (push (nnheader-parse-nov) headers)
479 (forward-line 1))
480 (nreverse headers))))
481
482(defun nnheader-write-overview-file (file headers)
483 "Write HEADERS to FILE."
484 (with-temp-file file
485 (mapcar 'nnheader-insert-nov headers)))
486
16409b0b 487(defun nnheader-insert-header (header)
eec82323 488 (insert
16409b0b
GM
489 "Subject: " (or (mail-header-subject header) "(none)") "\n"
490 "From: " (or (mail-header-from header) "(nobody)") "\n"
491 "Date: " (or (mail-header-date header) "") "\n"
492 "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
493 "References: " (or (mail-header-references header) "") "\n"
494 "Lines: ")
eec82323 495 (princ (or (mail-header-lines header) 0) (current-buffer))
16409b0b 496 (insert "\n\n"))
eec82323
LMI
497
498(defun nnheader-insert-article-line (article)
499 (goto-char (point-min))
500 (insert "220 ")
501 (princ article (current-buffer))
502 (insert " Article retrieved.\n")
503 (search-forward "\n\n" nil 'move)
504 (delete-region (point) (point-max))
505 (forward-char -1)
506 (insert "."))
507
508(defun nnheader-nov-delete-outside-range (beg end)
509 "Delete all NOV lines that lie outside the BEG to END range."
510 ;; First we find the first wanted line.
511 (nnheader-find-nov-line beg)
512 (delete-region (point-min) (point))
513 ;; Then we find the last wanted line.
514 (when (nnheader-find-nov-line end)
515 (forward-line 1))
516 (delete-region (point) (point-max)))
517
518(defun nnheader-find-nov-line (article)
519 "Put point at the NOV line that start with ARTICLE.
520If ARTICLE doesn't exist, put point where that line
521would have been. The function will return non-nil if
522the line could be found."
523 ;; This function basically does a binary search.
524 (let ((max (point-max))
525 (min (goto-char (point-min)))
526 (cur (current-buffer))
527 (prev (point-min))
528 num found)
529 (while (not found)
23f87bed 530 (goto-char (+ min (/ (- max min) 2)))
eec82323
LMI
531 (beginning-of-line)
532 (if (or (= (point) prev)
533 (eobp))
534 (setq found t)
535 (setq prev (point))
6748645f
LMI
536 (while (and (not (numberp (setq num (read cur))))
537 (not (eobp)))
538 (gnus-delete-line))
539 (cond ((> num article)
eec82323
LMI
540 (setq max (point)))
541 ((< num article)
542 (setq min (point)))
543 (t
544 (setq found 'yes)))))
545 ;; We may be at the first line.
546 (when (and (not num)
547 (not (eobp)))
548 (setq num (read cur)))
549 ;; Now we may have found the article we're looking for, or we
550 ;; may be somewhere near it.
551 (when (and (not (eq found 'yes))
552 (not (eq num article)))
553 (setq found (point))
554 (while (and (< (point) max)
555 (or (not (numberp num))
556 (< num article)))
557 (forward-line 1)
558 (setq found (point))
559 (or (eobp)
560 (= (setq num (read cur)) article)))
561 (unless (eq num article)
562 (goto-char found)))
563 (beginning-of-line)
564 (eq num article)))
565
566;; Various cruft the backends and Gnus need to communicate.
567
568(defvar nntp-server-buffer nil)
23f87bed 569(defvar nntp-process-response nil)
eec82323
LMI
570
571(defvar nnheader-callback-function nil)
572
573(defun nnheader-init-server-buffer ()
574 "Initialize the Gnus-backend communication buffer."
2ec9cf15
SM
575 (unless (gnus-buffer-live-p nntp-server-buffer)
576 (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
577 (with-current-buffer nntp-server-buffer
eec82323 578 (erase-buffer)
33e2f72c 579 (mm-enable-multibyte)
eec82323
LMI
580 (kill-all-local-variables)
581 (setq case-fold-search t) ;Should ignore case.
23f87bed 582 (set (make-local-variable 'nntp-process-response) nil)
eec82323
LMI
583 t))
584
585;;; Various functions the backends use.
586
587(defun nnheader-file-error (file)
588 "Return a string that says what is wrong with FILE."
589 (format
590 (cond
591 ((not (file-exists-p file))
592 "%s does not exist")
593 ((file-directory-p file)
594 "%s is a directory")
595 ((not (file-readable-p file))
596 "%s is not readable"))
597 file))
598
599(defun nnheader-insert-head (file)
600 "Insert the head of the article."
601 (when (file-exists-p file)
602 (if (eq nnheader-max-head-length t)
603 ;; Just read the entire file.
604 (nnheader-insert-file-contents file)
ebf693f3
MB
605 ;; Read blocks of the size specified by `nnheader-head-chop-length'
606 ;; until we find a separator.
eec82323 607 (let ((beg 0)
ebf693f3
MB
608 (start (point))
609 ;; Use `binary' to prevent the contents from being decoded,
610 ;; or it will change the number of characters that
611 ;; `insert-file-contents' returns.
612 (coding-system-for-read 'binary))
eec82323 613 (while (and (eq nnheader-head-chop-length
ebf693f3 614 (nth 1 (mm-insert-file-contents
eec82323
LMI
615 file nil beg
616 (incf beg nnheader-head-chop-length))))
5ebdc299 617 ;; CRLF or CR might be used for the line-break code.
ebf693f3 618 (prog1 (not (re-search-forward "\n\r?\n\\|\r\r" nil t))
eec82323
LMI
619 (goto-char (point-max)))
620 (or (null nnheader-max-head-length)
ebf693f3
MB
621 (< beg nnheader-max-head-length))))
622 ;; Finally decode the contents.
623 (when (mm-coding-system-p nnheader-file-coding-system)
624 (mm-decode-coding-region start (point-max)
625 nnheader-file-coding-system))))
eec82323
LMI
626 t))
627
628(defun nnheader-article-p ()
629 "Say whether the current buffer looks like an article."
630 (goto-char (point-min))
631 (if (not (search-forward "\n\n" nil t))
632 nil
633 (narrow-to-region (point-min) (1- (point)))
634 (goto-char (point-min))
16409b0b 635 (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
eec82323
LMI
636 (goto-char (match-end 0)))
637 (prog1
638 (eobp)
639 (widen))))
640
641(defun nnheader-insert-references (references message-id)
642 "Insert a References header based on REFERENCES and MESSAGE-ID."
643 (if (and (not references) (not message-id))
16409b0b
GM
644 ;; This is invalid, but not all articles have Message-IDs.
645 ()
eec82323 646 (mail-position-on-field "References")
01c52d31 647 (let ((begin (point-at-bol))
eec82323
LMI
648 (fill-column 78)
649 (fill-prefix "\t"))
650 (when references
651 (insert references))
652 (when (and references message-id)
653 (insert " "))
654 (when message-id
655 (insert message-id))
656 ;; Fold long References lines to conform to RFC1036 (sort of).
657 ;; The region must end with a newline to fill the region
658 ;; without inserting extra newline.
659 (fill-region-as-paragraph begin (1+ (point))))))
660
aa8f8277
GM
661(declare-function message-remove-header "message"
662 (header &optional is-regexp first reverse))
663
eec82323
LMI
664(defun nnheader-replace-header (header new-value)
665 "Remove HEADER and insert the NEW-VALUE."
aa8f8277 666 (require 'message)
eec82323
LMI
667 (save-excursion
668 (save-restriction
669 (nnheader-narrow-to-headers)
670 (prog1
671 (message-remove-header header)
672 (goto-char (point-max))
673 (insert header ": " new-value "\n")))))
674
675(defun nnheader-narrow-to-headers ()
676 "Narrow to the head of an article."
677 (widen)
678 (narrow-to-region
679 (goto-char (point-min))
680 (if (search-forward "\n\n" nil t)
681 (1- (point))
682 (point-max)))
683 (goto-char (point-min)))
684
01c52d31
MB
685(defun nnheader-get-lines-and-char ()
686 "Return the number of lines and chars in the article body."
687 (goto-char (point-min))
688 (if (not (re-search-forward "\n\r?\n" nil t))
689 (list 0 0)
690 (list (count-lines (point) (point-max))
691 (- (point-max) (point)))))
692
23f87bed
MB
693(defun nnheader-remove-body ()
694 "Remove the body from an article in this current buffer."
695 (goto-char (point-min))
696 (when (re-search-forward "\n\r?\n" nil t)
697 (delete-region (point) (point-max))))
698
eec82323
LMI
699(defun nnheader-set-temp-buffer (name &optional noerase)
700 "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
701 (set-buffer (get-buffer-create name))
16409b0b 702 (buffer-disable-undo)
eec82323
LMI
703 (unless noerase
704 (erase-buffer))
705 (current-buffer))
706
eec82323
LMI
707(defvar nnheader-numerical-files
708 (if (boundp 'jka-compr-compression-info-list)
709 (concat "\\([0-9]+\\)\\("
710 (mapconcat (lambda (i) (aref i 0))
711 jka-compr-compression-info-list "\\|")
712 "\\)?")
713 "[0-9]+$")
714 "Regexp that match numerical files.")
715
716(defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
717 "Regexp that matches numerical file names.")
718
719(defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
35ef97a5 720 "Regexp that matches numerical full file names.")
eec82323
LMI
721
722(defsubst nnheader-file-to-number (file)
16409b0b 723 "Take a FILE name and return the article number."
6748645f 724 (if (string= nnheader-numerical-short-files "^[0-9]+$")
e9bd5782 725 (string-to-number file)
eec82323 726 (string-match nnheader-numerical-short-files file)
e9bd5782 727 (string-to-number (match-string 0 file))))
eec82323 728
23f87bed
MB
729(defvar nnheader-directory-files-is-safe
730 (or (eq system-type 'windows-nt)
01c52d31 731 (not (featurep 'xemacs)))
23f87bed
MB
732 "If non-nil, Gnus believes `directory-files' is safe.
733It has been reported numerous times that `directory-files' fails with
734an alarming frequency on NFS mounted file systems. If it is nil,
735`nnheader-directory-files-safe' is used.")
736
eec82323 737(defun nnheader-directory-files-safe (&rest args)
23f87bed 738 "Execute `directory-files' twice and returns the longer result."
eec82323
LMI
739 (let ((first (apply 'directory-files args))
740 (second (apply 'directory-files args)))
741 (if (> (length first) (length second))
742 first
743 second)))
744
745(defun nnheader-directory-articles (dir)
16409b0b 746 "Return a list of all article files in directory DIR."
eec82323 747 (mapcar 'nnheader-file-to-number
23f87bed
MB
748 (if nnheader-directory-files-is-safe
749 (directory-files
750 dir nil nnheader-numerical-short-files t)
751 (nnheader-directory-files-safe
752 dir nil nnheader-numerical-short-files t))))
eec82323
LMI
753
754(defun nnheader-article-to-file-alist (dir)
755 "Return an alist of article/file pairs in DIR."
756 (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
23f87bed
MB
757 (if nnheader-directory-files-is-safe
758 (directory-files
759 dir nil nnheader-numerical-short-files t)
760 (nnheader-directory-files-safe
761 dir nil nnheader-numerical-short-files t))))
eec82323
LMI
762
763(defun nnheader-fold-continuation-lines ()
764 "Fold continuation lines in the current buffer."
765 (nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
766
6748645f
LMI
767(defun nnheader-translate-file-chars (file &optional full)
768 "Translate FILE into something that can be a file name.
769If FULL, translate everything."
eec82323
LMI
770 (if (null nnheader-file-name-translation-alist)
771 ;; No translation is necessary.
772 file
eec82323
LMI
773 (let* ((i 0)
774 trans leaf path len)
6748645f
LMI
775 (if full
776 ;; Do complete translation.
777 (setq leaf (copy-sequence file)
16409b0b
GM
778 path ""
779 i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1)))
780 2 0))
6748645f
LMI
781 ;; We translate -- but only the file name. We leave the directory
782 ;; alone.
df359f6c 783 (if (and (featurep 'xemacs)
f5ec697d 784 (memq system-type '(windows-nt cygwin)))
df359f6c
DL
785 ;; This is needed on NT and stuff, because
786 ;; file-name-nondirectory is not enough to split
787 ;; file names, containing ':', e.g.
788 ;; "d:\\Work\\News\\nntp+news.fido7.ru:fido7.ru.gnu.SCORE"
c60ee5e7 789 ;;
df359f6c
DL
790 ;; we are trying to correctly split such names:
791 ;; "d:file.name" -> "a:" "file.name"
792 ;; "aaa:bbb.ccc" -> "" "aaa:bbb.ccc"
793 ;; "d:aaa\\bbb:ccc" -> "d:aaa\\" "bbb:ccc"
794 ;; etc.
795 ;; to translate then only the file name part.
796 (progn
797 (setq leaf file
798 path "")
799 (if (string-match "\\(^\\w:\\|[/\\]\\)\\([^/\\]+\\)$" file)
800 (setq leaf (substring file (match-beginning 2))
801 path (substring file 0 (match-beginning 2)))))
802 ;; Emacs DTRT, says andrewi.
6748645f
LMI
803 (setq leaf (file-name-nondirectory file)
804 path (file-name-directory file))))
eec82323
LMI
805 (setq len (length leaf))
806 (while (< i len)
807 (when (setq trans (cdr (assq (aref leaf i)
808 nnheader-file-name-translation-alist)))
809 (aset leaf i trans))
810 (incf i))
811 (concat path leaf))))
812
813(defun nnheader-report (backend &rest args)
814 "Report an error from the BACKEND.
815The first string in ARGS can be a format string."
816 (set (intern (format "%s-status-string" backend))
817 (if (< (length args) 2)
818 (car args)
819 (apply 'format args)))
820 nil)
821
8ccbef23 822(defun nnheader-get-report-string (backend)
eec82323
LMI
823 "Get the most recent report from BACKEND."
824 (condition-case ()
8ccbef23
G
825 (format "%s" (symbol-value (intern (format "%s-status-string"
826 backend))))
827 (error "")))
828
829(defun nnheader-get-report (backend)
830 "Get the most recent report from BACKEND."
831 (nnheader-message 5 (nnheader-get-report-string backend)))
eec82323
LMI
832
833(defun nnheader-insert (format &rest args)
834 "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
835If FORMAT isn't a format string, it and all ARGS will be inserted
836without formatting."
20a673b2 837 (with-current-buffer nntp-server-buffer
eec82323
LMI
838 (erase-buffer)
839 (if (string-match "%" format)
840 (insert (apply 'format format args))
841 (apply 'insert format args))
842 t))
843
23f87bed
MB
844(defsubst nnheader-replace-chars-in-string (string from to)
845 (mm-subst-char-in-string from to string))
16409b0b
GM
846
847(defun nnheader-replace-duplicate-chars-in-string (string from to)
eec82323
LMI
848 "Replace characters in STRING from FROM to TO."
849 (let ((string (substring string 0)) ;Copy string.
850 (len (length string))
16409b0b 851 (idx 0) prev i)
eec82323
LMI
852 ;; Replace all occurrences of FROM with TO.
853 (while (< idx len)
16409b0b
GM
854 (setq i (aref string idx))
855 (when (and (eq prev from) (= i from))
856 (aset string (1- idx) to)
eec82323 857 (aset string idx to))
16409b0b 858 (setq prev i)
eec82323
LMI
859 (setq idx (1+ idx)))
860 string))
861
862(defun nnheader-file-to-group (file &optional top)
863 "Return a group name based on FILE and TOP."
864 (nnheader-replace-chars-in-string
865 (if (not top)
866 file
867 (condition-case ()
868 (substring (expand-file-name file)
869 (length
870 (expand-file-name
871 (file-name-as-directory top))))
872 (error "")))
23f87bed 873 nnheader-directory-separator-character ?.))
eec82323
LMI
874
875(defun nnheader-message (level &rest args)
876 "Message if the Gnus backends are talkative."
877 (if (or (not (numberp gnus-verbose-backends))
878 (<= level gnus-verbose-backends))
01c52d31
MB
879 (if gnus-add-timestamp-to-message
880 (apply 'gnus-message-with-timestamp args)
881 (apply 'message args))
eec82323
LMI
882 (apply 'format args)))
883
884(defun nnheader-be-verbose (level)
885 "Return whether the backends should be verbose on LEVEL."
886 (or (not (numberp gnus-verbose-backends))
887 (<= level gnus-verbose-backends)))
888
23f87bed
MB
889(defvar nnheader-pathname-coding-system 'iso-8859-1
890 "*Coding system for file name.")
e0b8fdf5 891
eec82323 892(defun nnheader-group-pathname (group dir &optional file)
35ef97a5 893 "Make file name for GROUP."
eec82323
LMI
894 (concat
895 (let ((dir (file-name-as-directory (expand-file-name dir))))
896 ;; If this directory exists, we use it directly.
16409b0b
GM
897 (file-name-as-directory
898 (if (file-directory-p (concat dir group))
899 (expand-file-name group dir)
900 ;; If not, we translate dots into slashes.
901 (expand-file-name (mm-encode-coding-string
902 (nnheader-replace-chars-in-string group ?. ?/)
23f87bed 903 nnheader-pathname-coding-system)
16409b0b 904 dir))))
eec82323
LMI
905 (cond ((null file) "")
906 ((numberp file) (int-to-string file))
907 (t file))))
908
eec82323 909(defun nnheader-concat (dir &rest files)
16409b0b 910 "Concat DIR as directory to FILES."
eec82323
LMI
911 (apply 'concat (file-name-as-directory dir) files))
912
913(defun nnheader-ms-strip-cr ()
914 "Strip ^M from the end of all lines."
915 (save-excursion
23f87bed 916 (nnheader-remove-cr-followed-by-lf)))
eec82323
LMI
917
918(defun nnheader-file-size (file)
919 "Return the file size of FILE or 0."
920 (or (nth 7 (file-attributes file)) 0))
921
23f87bed
MB
922(defun nnheader-find-etc-directory (package &optional file first)
923 "Go through `load-path' and find the \"../etc/PACKAGE\" directory.
924This function will look in the parent directory of each `load-path'
925entry, and look for the \"etc\" directory there.
926If FILE, find the \".../etc/PACKAGE\" file instead.
927If FIRST is non-nil, return the directory or the file found at the
928first. Otherwise, find the newest one, though it may take a time."
eec82323 929 (let ((path load-path)
23f87bed 930 dir results)
eec82323
LMI
931 ;; We try to find the dir by looking at the load path,
932 ;; stripping away the last component and adding "etc/".
933 (while path
934 (if (and (car path)
935 (file-exists-p
936 (setq dir (concat
937 (file-name-directory
938 (directory-file-name (car path)))
939 "etc/" package
940 (if file "" "/"))))
941 (or file (file-directory-p dir)))
23f87bed
MB
942 (progn
943 (or (member dir results)
944 (push dir results))
945 (setq path (if first nil (cdr path))))
eec82323 946 (setq path (cdr path))))
23f87bed
MB
947 (if (or first (not (cdr results)))
948 (car results)
949 (car (sort results 'file-newer-than-file-p)))))
eec82323 950
9efa445f
DN
951(defvar ange-ftp-path-format)
952(defvar efs-path-regexp)
eec82323
LMI
953(defun nnheader-re-read-dir (path)
954 "Re-read directory PATH if PATH is on a remote system."
955 (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
956 (when (string-match efs-path-regexp path)
957 (efs-re-read-dir path))
958 (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
959 (when (string-match (car ange-ftp-path-format) path)
960 (ange-ftp-re-read-dir path)))))
961
6748645f
LMI
962(defvar nnheader-file-coding-system 'raw-text
963 "Coding system used in file backends of Gnus.")
964
eec82323
LMI
965(defun nnheader-insert-file-contents (filename &optional visit beg end replace)
966 "Like `insert-file-contents', q.v., but only reads in the file.
967A buffer may be modified in several ways after reading into the buffer due
968to advanced Emacs features, such as file-name-handlers, format decoding,
969find-file-hooks, etc.
970 This function ensures that none of these modifications will take place."
16409b0b
GM
971 (let ((coding-system-for-read nnheader-file-coding-system))
972 (mm-insert-file-contents filename visit beg end replace)))
eec82323 973
23f87bed
MB
974(defun nnheader-insert-nov-file (file first)
975 (let ((size (nth 7 (file-attributes file)))
976 (cutoff (* 32 1024)))
977 (when size
978 (if (< size cutoff)
979 ;; If the file is small, we just load it.
980 (nnheader-insert-file-contents file)
981 ;; We start on the assumption that FIRST is pretty recent. If
982 ;; not, we just insert the rest of the file as well.
983 (let (current)
984 (nnheader-insert-file-contents file nil (- size cutoff) size)
985 (goto-char (point-min))
986 (delete-region (point) (or (search-forward "\n" nil 'move) (point)))
987 (setq current (ignore-errors (read (current-buffer))))
988 (if (and (numberp current)
989 (< current first))
990 t
991 (delete-region (point-min) (point-max))
992 (nnheader-insert-file-contents file)))))))
993
eec82323 994(defun nnheader-find-file-noselect (&rest args)
ff4d3926
MB
995 "Open a file with some variables bound.
996See `find-file-noselect' for the arguments."
14acf2f5
SM
997 (letf* ((format-alist nil)
998 (auto-mode-alist (mm-auto-mode-alist))
999 ((default-value 'major-mode) 'fundamental-mode)
1000 (enable-local-variables nil)
1001 (after-insert-file-functions nil)
1002 (enable-local-eval nil)
1003 (coding-system-for-read nnheader-file-coding-system)
1004 (version-control 'never)
1005 (ffh (if (boundp 'find-file-hook)
1006 'find-file-hook
1007 'find-file-hooks))
1008 (val (symbol-value ffh)))
4a43ee9b
MB
1009 (set ffh nil)
1010 (unwind-protect
1011 (apply 'find-file-noselect args)
1012 (set ffh val))))
eec82323 1013
eec82323
LMI
1014(defun nnheader-directory-regular-files (dir)
1015 "Return a list of all regular files in DIR."
1016 (let ((files (directory-files dir t))
1017 out)
1018 (while files
1019 (when (file-regular-p (car files))
1020 (push (car files) out))
1021 (pop files))
1022 (nreverse out)))
1023
6748645f
LMI
1024(defun nnheader-directory-files (&rest args)
1025 "Same as `directory-files', but prune \".\" and \"..\"."
1026 (let ((files (apply 'directory-files args))
1027 out)
1028 (while files
1029 (unless (member (file-name-nondirectory (car files)) '("." ".."))
1030 (push (car files) out))
1031 (pop files))
1032 (nreverse out)))
1033
eec82323
LMI
1034(defmacro nnheader-skeleton-replace (from &optional to regexp)
1035 `(let ((new (generate-new-buffer " *nnheader replace*"))
1036 (cur (current-buffer))
1037 (start (point-min)))
eec82323
LMI
1038 (set-buffer cur)
1039 (goto-char (point-min))
1040 (while (,(if regexp 're-search-forward 'search-forward)
1041 ,from nil t)
1042 (insert-buffer-substring
1043 cur start (prog1 (match-beginning 0) (set-buffer new)))
1044 (goto-char (point-max))
1045 ,(when to `(insert ,to))
1046 (set-buffer cur)
1047 (setq start (point)))
1048 (insert-buffer-substring
1049 cur start (prog1 (point-max) (set-buffer new)))
1050 (copy-to-buffer cur (point-min) (point-max))
1051 (kill-buffer (current-buffer))
1052 (set-buffer cur)))
1053
1054(defun nnheader-replace-string (from to)
16409b0b 1055 "Do a fast replacement of FROM to TO from point to `point-max'."
eec82323
LMI
1056 (nnheader-skeleton-replace from to))
1057
1058(defun nnheader-replace-regexp (from to)
16409b0b 1059 "Do a fast regexp replacement of FROM to TO from point to `point-max'."
eec82323
LMI
1060 (nnheader-skeleton-replace from to t))
1061
1062(defun nnheader-strip-cr ()
1063 "Strip all \r's from the current buffer."
1064 (nnheader-skeleton-replace "\r"))
1065
16409b0b
GM
1066(defalias 'nnheader-cancel-timer 'cancel-timer)
1067(defalias 'nnheader-cancel-function-timers 'cancel-function-timers)
23f87bed 1068
e3e955fe
MB
1069;; When changing this function, consider changing `pop3-accept-process-output'
1070;; as well.
23f87bed
MB
1071(defun nnheader-accept-process-output (process)
1072 (accept-process-output
1073 process
1074 (truncate nnheader-read-timeout)
1075 (truncate (* (- nnheader-read-timeout
1076 (truncate nnheader-read-timeout))
1077 1000))))
eec82323 1078
5f285722
LMI
1079(defun nnheader-update-marks-actions (backend-marks actions)
1080 (dolist (action actions)
1081 (let ((range (nth 0 action))
1082 (what (nth 1 action))
1083 (marks (nth 2 action)))
1084 (dolist (mark marks)
1085 (setq backend-marks
1086 (gnus-update-alist-soft
1087 mark
1088 (cond
1089 ((eq what 'add)
1e91d0eb
LMI
1090 (gnus-range-add (cdr (assoc mark backend-marks)) range))
1091 ((eq what 'del)
1092 (gnus-remove-from-range
1093 (cdr (assoc mark backend-marks)) range))
1094 ((eq what 'set)
1095 range))
5f285722
LMI
1096 backend-marks)))))
1097 backend-marks)
1098
9f5e78f7
LMI
1099(defmacro nnheader-insert-buffer-substring (buffer &optional start end)
1100 "Copy string from unibyte buffer to multibyte current buffer."
1101 (if (featurep 'xemacs)
1102 `(insert-buffer-substring ,buffer ,start ,end)
1103 `(if enable-multibyte-characters
1104 (insert (with-current-buffer ,buffer
1105 (mm-string-to-multibyte
1106 ,(if (or start end)
1107 `(buffer-substring (or ,start (point-min))
1108 (or ,end (point-max)))
1109 '(buffer-string)))))
1110 (insert-buffer-substring ,buffer ,start ,end))))
1111
2146e256
LMI
1112(defvar nnheader-last-message-time '(0 0))
1113(defun nnheader-message-maybe (&rest args)
1114 (let ((now (current-time)))
1115 (when (> (float-time (time-subtract now nnheader-last-message-time)) 1)
1116 (setq nnheader-last-message-time now)
1117 (apply 'nnheader-message args))))
1118
df359f6c 1119(when (featurep 'xemacs)
eec82323
LMI
1120 (require 'nnheaderxm))
1121
1122(run-hooks 'nnheader-load-hook)
1123
1124(provide 'nnheader)
1125
1126;;; nnheader.el ends here