Merge from gnus--devo--0
[bpt/emacs.git] / lisp / gnus / nnheader.el
CommitLineData
eec82323 1;;; nnheader.el --- header access macros for Gnus and its backends
16409b0b 2
e84b4b86
TTN
3;; Copyright (C) 1987, 1988, 1989, 1990, 1993, 1994,
4;; 1995, 1996, 1997, 1998, 2000, 2001, 2002, 2003,
ae940284 5;; 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
eec82323
LMI
6
7;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
23f87bed 8;; Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
9;; Keywords: news
10
11;; This file is part of GNU Emacs.
12
5e809f55 13;; GNU Emacs is free software: you can redistribute it and/or modify
eec82323 14;; it under the terms of the GNU General Public License as published by
5e809f55
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
eec82323
LMI
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
5e809f55 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
25
26;;; Commentary:
27
eec82323
LMI
28;;; Code:
29
7df7482d 30(eval-when-compile (require 'cl))
6f3b74c6 31
03da5d08 32(defvar nnmail-extra-headers)
9efa445f
DN
33(defvar gnus-newsgroup-name)
34(defvar nnheader-file-coding-system)
35(defvar jka-compr-compression-info-list)
03da5d08 36
6f3b74c6
GM
37;; Requiring `gnus-util' at compile time creates a circular
38;; dependency between nnheader.el and gnus-util.el.
23f87bed 39;;(eval-when-compile (require 'gnus-util))
7df7482d 40
eec82323 41(require 'mail-utils)
16409b0b 42(require 'mm-util)
23f87bed 43(require 'gnus-util)
8abf1b22
GM
44;; FIXME none of these are used explicitly in this file.
45(autoload 'gnus-sorted-intersection "gnus-range")
46(autoload 'gnus-intersection "gnus-range")
47(autoload 'gnus-sorted-complement "gnus-range")
48(autoload 'gnus-sorted-difference "gnus-range")
23f87bed
MB
49
50(defcustom gnus-verbose-backends 7
51 "Integer that says how verbose the Gnus backends should be.
52The higher the number, the more messages the Gnus backends will flash
53to say what it's doing. At zero, the Gnus backends will be totally
54mute; at five, they will display most important messages; and at ten,
55they will keep on jabbering all the time."
56 :group 'gnus-start
57 :type 'integer)
58
59(defcustom gnus-nov-is-evil nil
60 "If non-nil, Gnus backends will never output headers in the NOV format."
61 :group 'gnus-server
62 :type 'boolean)
eec82323 63
f4dd4ae8 64(defvar nnheader-max-head-length 8192
23f87bed
MB
65 "*Max length of the head of articles.
66
67Value is an integer, nil, or t. nil means read in chunks of a file
68indefinitely until a complete head is found\; t means always read the
69entire file immediately, disregarding `nnheader-head-chop-length'.
70
71Integer values will in effect be rounded up to the nearest multiple of
72`nnheader-head-chop-length'.")
eec82323
LMI
73
74(defvar nnheader-head-chop-length 2048
75 "*Length of each read operation when trying to fetch HEAD headers.")
76
23f87bed
MB
77(defvar nnheader-read-timeout
78 (if (string-match "windows-nt\\|os/2\\|emx\\|cygwin"
79 (symbol-name system-type))
e62e7654
MB
80 ;; http://thread.gmane.org/v9655t3pjo.fsf@marauder.physik.uni-ulm.de
81 ;;
82 ;; IIRC, values lower than 1.0 didn't/don't work on Windows/DOS.
83 ;;
84 ;; There should probably be a runtime test to determine the timing
85 ;; resolution, or a primitive to report it. I don't know off-hand
86 ;; what's possible. Perhaps better, maybe the Windows/DOS primitive
87 ;; could round up non-zero timeouts to a minimum of 1.0?
88 1.0
e3e955fe
MB
89 ;; 2008-05-19 change by Larsi:
90 ;; Change the default timeout from 0.1 seconds to 0.01 seconds. This will
91 ;; make nntp and pop3 article retrieval faster in some cases, but might
92 ;; make CPU usage larger. If this has any bad side effects, we might
93 ;; revert this change.
a368801c 94 0.01)
e3e955fe
MB
95 ;; When changing this variable, consider changing `pop3-read-timeout' as
96 ;; well.
23f87bed
MB
97 "How long nntp should wait between checking for the end of output.
98Shorter values mean quicker response, but are more CPU intensive.")
99
c60ee5e7 100(defvar nnheader-file-name-translation-alist
3e7b2fa7
SZ
101 (let ((case-fold-search t))
102 (cond
23f87bed 103 ((string-match "windows-nt\\|os/2\\|emx\\|cygwin"
3e7b2fa7
SZ
104 (symbol-name system-type))
105 (append (mapcar (lambda (c) (cons c ?_))
106 '(?: ?* ?\" ?< ?> ??))
23f87bed 107 (if (string-match "windows-nt\\|cygwin"
3e7b2fa7
SZ
108 (symbol-name system-type))
109 nil
110 '((?+ . ?-)))))
111 (t nil)))
eec82323 112 "*Alist that says how to translate characters in file names.
16409b0b 113For instance, if \":\" is invalid as a file character in file names
eec82323
LMI
114on your system, you could say something like:
115
116\(setq nnheader-file-name-translation-alist '((?: . ?_)))")
117
23f87bed
MB
118(defvar nnheader-directory-separator-character
119 (string-to-char (substring (file-name-as-directory ".") -1))
120 "*A character used to a directory separator.")
121
8abf1b22
GM
122(autoload 'nnmail-message-id "nnmail")
123(autoload 'mail-position-on-field "sendmail")
124(autoload 'message-remove-header "message")
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))
367 (goto-char (point-min))
368 ;; Search to the beginning of the next header. Error
369 ;; messages do not begin with 2 or 3.
370 (when (re-search-forward "^[23][0-9]+ " nil t)
371 (end-of-line)
372 (setq num (read cur)
373 beg (point)
374 end (if (search-forward "\n.\n" nil t)
375 (- (point) 2)
376 (point)))))
377 (with-temp-buffer
378 (insert-buffer-substring cur beg end)
379 (nnheader-parse-naked-head num)))))
eec82323
LMI
380
381(defmacro nnheader-nov-skip-field ()
382 '(search-forward "\t" eol 'move))
383
384(defmacro nnheader-nov-field ()
385 '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
386
387(defmacro nnheader-nov-read-integer ()
388 '(prog1
16409b0b 389 (if (eq (char-after) ?\t)
eec82323 390 0
16409b0b
GM
391 (let ((num (condition-case nil
392 (read (current-buffer))
393 (error nil))))
eec82323
LMI
394 (if (numberp num) num 0)))
395 (or (eobp) (forward-char 1))))
396
16409b0b
GM
397(defmacro nnheader-nov-parse-extra ()
398 '(let (out string)
399 (while (not (memq (char-after) '(?\n nil)))
400 (setq string (nnheader-nov-field))
401 (when (string-match "^\\([^ :]+\\): " string)
402 (push (cons (intern (match-string 1 string))
403 (substring string (match-end 0)))
404 out)))
405 out))
406
01c52d31
MB
407(eval-and-compile
408 (defvar nnheader-uniquify-message-id nil))
409
410(defmacro nnheader-nov-read-message-id (&optional number)
411 `(let ((id (nnheader-nov-field)))
16409b0b 412 (if (string-match "^<[^>]+>$" id)
01c52d31
MB
413 ,(if nnheader-uniquify-message-id
414 `(if (string-match "__[^@]+@" id)
415 (concat (substring id 0 (match-beginning 0))
416 (substring id (1- (match-end 0))))
417 id)
418 'id)
419 (nnheader-generate-fake-message-id ,number))))
eec82323
LMI
420
421(defun nnheader-parse-nov ()
01c52d31
MB
422 (let ((eol (point-at-eol))
423 (number (nnheader-nov-read-integer)))
eec82323 424 (vector
01c52d31 425 number ; number
eec82323
LMI
426 (nnheader-nov-field) ; subject
427 (nnheader-nov-field) ; from
428 (nnheader-nov-field) ; date
01c52d31 429 (nnheader-nov-read-message-id number) ; id
eec82323
LMI
430 (nnheader-nov-field) ; refs
431 (nnheader-nov-read-integer) ; chars
432 (nnheader-nov-read-integer) ; lines
16409b0b 433 (if (eq (char-after) ?\n)
eec82323 434 nil
8b93df01
DL
435 (if (looking-at "Xref: ")
436 (goto-char (match-end 0)))
437 (nnheader-nov-field)) ; Xref
16409b0b 438 (nnheader-nov-parse-extra)))) ; extra
eec82323
LMI
439
440(defun nnheader-insert-nov (header)
441 (princ (mail-header-number header) (current-buffer))
16409b0b
GM
442 (let ((p (point)))
443 (insert
444 "\t"
445 (or (mail-header-subject header) "(none)") "\t"
446 (or (mail-header-from header) "(nobody)") "\t"
447 (or (mail-header-date header) "") "\t"
448 (or (mail-header-id header)
449 (nnmail-message-id))
450 "\t"
451 (or (mail-header-references header) "") "\t")
452 (princ (or (mail-header-chars header) 0) (current-buffer))
453 (insert "\t")
454 (princ (or (mail-header-lines header) 0) (current-buffer))
455 (insert "\t")
456 (when (mail-header-xref header)
457 (insert "Xref: " (mail-header-xref header)))
458 (when (or (mail-header-xref header)
459 (mail-header-extra header))
460 (insert "\t"))
461 (when (mail-header-extra header)
462 (let ((extra (mail-header-extra header)))
463 (while extra
464 (insert (symbol-name (caar extra))
465 ": " (cdar extra) "\t")
466 (pop extra))))
467 (insert "\n")
468 (backward-char 1)
469 (while (search-backward "\n" p t)
470 (delete-char 1))
471 (forward-line 1)))
472
23f87bed
MB
473(defun nnheader-parse-overview-file (file)
474 "Parse FILE and return a list of headers."
475 (mm-with-unibyte-buffer
476 (nnheader-insert-file-contents file)
477 (goto-char (point-min))
478 (let (headers)
479 (while (not (eobp))
480 (push (nnheader-parse-nov) headers)
481 (forward-line 1))
482 (nreverse headers))))
483
484(defun nnheader-write-overview-file (file headers)
485 "Write HEADERS to FILE."
486 (with-temp-file file
487 (mapcar 'nnheader-insert-nov headers)))
488
16409b0b 489(defun nnheader-insert-header (header)
eec82323 490 (insert
16409b0b
GM
491 "Subject: " (or (mail-header-subject header) "(none)") "\n"
492 "From: " (or (mail-header-from header) "(nobody)") "\n"
493 "Date: " (or (mail-header-date header) "") "\n"
494 "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
495 "References: " (or (mail-header-references header) "") "\n"
496 "Lines: ")
eec82323 497 (princ (or (mail-header-lines header) 0) (current-buffer))
16409b0b 498 (insert "\n\n"))
eec82323
LMI
499
500(defun nnheader-insert-article-line (article)
501 (goto-char (point-min))
502 (insert "220 ")
503 (princ article (current-buffer))
504 (insert " Article retrieved.\n")
505 (search-forward "\n\n" nil 'move)
506 (delete-region (point) (point-max))
507 (forward-char -1)
508 (insert "."))
509
510(defun nnheader-nov-delete-outside-range (beg end)
511 "Delete all NOV lines that lie outside the BEG to END range."
512 ;; First we find the first wanted line.
513 (nnheader-find-nov-line beg)
514 (delete-region (point-min) (point))
515 ;; Then we find the last wanted line.
516 (when (nnheader-find-nov-line end)
517 (forward-line 1))
518 (delete-region (point) (point-max)))
519
520(defun nnheader-find-nov-line (article)
521 "Put point at the NOV line that start with ARTICLE.
522If ARTICLE doesn't exist, put point where that line
523would have been. The function will return non-nil if
524the line could be found."
525 ;; This function basically does a binary search.
526 (let ((max (point-max))
527 (min (goto-char (point-min)))
528 (cur (current-buffer))
529 (prev (point-min))
530 num found)
531 (while (not found)
23f87bed 532 (goto-char (+ min (/ (- max min) 2)))
eec82323
LMI
533 (beginning-of-line)
534 (if (or (= (point) prev)
535 (eobp))
536 (setq found t)
537 (setq prev (point))
6748645f
LMI
538 (while (and (not (numberp (setq num (read cur))))
539 (not (eobp)))
540 (gnus-delete-line))
541 (cond ((> num article)
eec82323
LMI
542 (setq max (point)))
543 ((< num article)
544 (setq min (point)))
545 (t
546 (setq found 'yes)))))
547 ;; We may be at the first line.
548 (when (and (not num)
549 (not (eobp)))
550 (setq num (read cur)))
551 ;; Now we may have found the article we're looking for, or we
552 ;; may be somewhere near it.
553 (when (and (not (eq found 'yes))
554 (not (eq num article)))
555 (setq found (point))
556 (while (and (< (point) max)
557 (or (not (numberp num))
558 (< num article)))
559 (forward-line 1)
560 (setq found (point))
561 (or (eobp)
562 (= (setq num (read cur)) article)))
563 (unless (eq num article)
564 (goto-char found)))
565 (beginning-of-line)
566 (eq num article)))
567
568;; Various cruft the backends and Gnus need to communicate.
569
570(defvar nntp-server-buffer nil)
23f87bed 571(defvar nntp-process-response nil)
eec82323
LMI
572(defvar news-reply-yank-from nil)
573(defvar news-reply-yank-message-id nil)
574
575(defvar nnheader-callback-function nil)
576
577(defun nnheader-init-server-buffer ()
578 "Initialize the Gnus-backend communication buffer."
2ec9cf15
SM
579 (unless (gnus-buffer-live-p nntp-server-buffer)
580 (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
581 (with-current-buffer nntp-server-buffer
eec82323 582 (erase-buffer)
33e2f72c 583 (mm-enable-multibyte)
eec82323
LMI
584 (kill-all-local-variables)
585 (setq case-fold-search t) ;Should ignore case.
23f87bed 586 (set (make-local-variable 'nntp-process-response) nil)
eec82323
LMI
587 t))
588
589;;; Various functions the backends use.
590
591(defun nnheader-file-error (file)
592 "Return a string that says what is wrong with FILE."
593 (format
594 (cond
595 ((not (file-exists-p file))
596 "%s does not exist")
597 ((file-directory-p file)
598 "%s is a directory")
599 ((not (file-readable-p file))
600 "%s is not readable"))
601 file))
602
603(defun nnheader-insert-head (file)
604 "Insert the head of the article."
605 (when (file-exists-p file)
606 (if (eq nnheader-max-head-length t)
607 ;; Just read the entire file.
608 (nnheader-insert-file-contents file)
ebf693f3
MB
609 ;; Read blocks of the size specified by `nnheader-head-chop-length'
610 ;; until we find a separator.
eec82323 611 (let ((beg 0)
ebf693f3
MB
612 (start (point))
613 ;; Use `binary' to prevent the contents from being decoded,
614 ;; or it will change the number of characters that
615 ;; `insert-file-contents' returns.
616 (coding-system-for-read 'binary))
eec82323 617 (while (and (eq nnheader-head-chop-length
ebf693f3 618 (nth 1 (mm-insert-file-contents
eec82323
LMI
619 file nil beg
620 (incf beg nnheader-head-chop-length))))
5ebdc299 621 ;; CRLF or CR might be used for the line-break code.
ebf693f3 622 (prog1 (not (re-search-forward "\n\r?\n\\|\r\r" nil t))
eec82323
LMI
623 (goto-char (point-max)))
624 (or (null nnheader-max-head-length)
ebf693f3
MB
625 (< beg nnheader-max-head-length))))
626 ;; Finally decode the contents.
627 (when (mm-coding-system-p nnheader-file-coding-system)
628 (mm-decode-coding-region start (point-max)
629 nnheader-file-coding-system))))
eec82323
LMI
630 t))
631
632(defun nnheader-article-p ()
633 "Say whether the current buffer looks like an article."
634 (goto-char (point-min))
635 (if (not (search-forward "\n\n" nil t))
636 nil
637 (narrow-to-region (point-min) (1- (point)))
638 (goto-char (point-min))
16409b0b 639 (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
eec82323
LMI
640 (goto-char (match-end 0)))
641 (prog1
642 (eobp)
643 (widen))))
644
645(defun nnheader-insert-references (references message-id)
646 "Insert a References header based on REFERENCES and MESSAGE-ID."
647 (if (and (not references) (not message-id))
16409b0b
GM
648 ;; This is invalid, but not all articles have Message-IDs.
649 ()
eec82323 650 (mail-position-on-field "References")
01c52d31 651 (let ((begin (point-at-bol))
eec82323
LMI
652 (fill-column 78)
653 (fill-prefix "\t"))
654 (when references
655 (insert references))
656 (when (and references message-id)
657 (insert " "))
658 (when message-id
659 (insert message-id))
660 ;; Fold long References lines to conform to RFC1036 (sort of).
661 ;; The region must end with a newline to fill the region
662 ;; without inserting extra newline.
663 (fill-region-as-paragraph begin (1+ (point))))))
664
665(defun nnheader-replace-header (header new-value)
666 "Remove HEADER and insert the NEW-VALUE."
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)
23f87bed
MB
784 (memq system-type '(cygwin32 win32 w32 mswindows windows-nt
785 cygwin)))
df359f6c
DL
786 ;; This is needed on NT and stuff, because
787 ;; file-name-nondirectory is not enough to split
788 ;; file names, containing ':', e.g.
789 ;; "d:\\Work\\News\\nntp+news.fido7.ru:fido7.ru.gnu.SCORE"
c60ee5e7 790 ;;
df359f6c
DL
791 ;; we are trying to correctly split such names:
792 ;; "d:file.name" -> "a:" "file.name"
793 ;; "aaa:bbb.ccc" -> "" "aaa:bbb.ccc"
794 ;; "d:aaa\\bbb:ccc" -> "d:aaa\\" "bbb:ccc"
795 ;; etc.
796 ;; to translate then only the file name part.
797 (progn
798 (setq leaf file
799 path "")
800 (if (string-match "\\(^\\w:\\|[/\\]\\)\\([^/\\]+\\)$" file)
801 (setq leaf (substring file (match-beginning 2))
802 path (substring file 0 (match-beginning 2)))))
803 ;; Emacs DTRT, says andrewi.
6748645f
LMI
804 (setq leaf (file-name-nondirectory file)
805 path (file-name-directory file))))
eec82323
LMI
806 (setq len (length leaf))
807 (while (< i len)
808 (when (setq trans (cdr (assq (aref leaf i)
809 nnheader-file-name-translation-alist)))
810 (aset leaf i trans))
811 (incf i))
812 (concat path leaf))))
813
814(defun nnheader-report (backend &rest args)
815 "Report an error from the BACKEND.
816The first string in ARGS can be a format string."
817 (set (intern (format "%s-status-string" backend))
818 (if (< (length args) 2)
819 (car args)
820 (apply 'format args)))
821 nil)
822
823(defun nnheader-get-report (backend)
824 "Get the most recent report from BACKEND."
825 (condition-case ()
6748645f 826 (nnheader-message 5 "%s" (symbol-value (intern (format "%s-status-string"
16409b0b 827 backend))))
6748645f 828 (error (nnheader-message 5 ""))))
eec82323
LMI
829
830(defun nnheader-insert (format &rest args)
831 "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
832If FORMAT isn't a format string, it and all ARGS will be inserted
833without formatting."
834 (save-excursion
835 (set-buffer nntp-server-buffer)
836 (erase-buffer)
837 (if (string-match "%" format)
838 (insert (apply 'format format args))
839 (apply 'insert format args))
840 t))
841
23f87bed
MB
842(defsubst nnheader-replace-chars-in-string (string from to)
843 (mm-subst-char-in-string from to string))
16409b0b
GM
844
845(defun nnheader-replace-duplicate-chars-in-string (string from to)
eec82323
LMI
846 "Replace characters in STRING from FROM to TO."
847 (let ((string (substring string 0)) ;Copy string.
848 (len (length string))
16409b0b 849 (idx 0) prev i)
eec82323
LMI
850 ;; Replace all occurrences of FROM with TO.
851 (while (< idx len)
16409b0b
GM
852 (setq i (aref string idx))
853 (when (and (eq prev from) (= i from))
854 (aset string (1- idx) to)
eec82323 855 (aset string idx to))
16409b0b 856 (setq prev i)
eec82323
LMI
857 (setq idx (1+ idx)))
858 string))
859
860(defun nnheader-file-to-group (file &optional top)
861 "Return a group name based on FILE and TOP."
862 (nnheader-replace-chars-in-string
863 (if (not top)
864 file
865 (condition-case ()
866 (substring (expand-file-name file)
867 (length
868 (expand-file-name
869 (file-name-as-directory top))))
870 (error "")))
23f87bed 871 nnheader-directory-separator-character ?.))
eec82323
LMI
872
873(defun nnheader-message (level &rest args)
874 "Message if the Gnus backends are talkative."
875 (if (or (not (numberp gnus-verbose-backends))
876 (<= level gnus-verbose-backends))
01c52d31
MB
877 (if gnus-add-timestamp-to-message
878 (apply 'gnus-message-with-timestamp args)
879 (apply 'message args))
eec82323
LMI
880 (apply 'format args)))
881
882(defun nnheader-be-verbose (level)
883 "Return whether the backends should be verbose on LEVEL."
884 (or (not (numberp gnus-verbose-backends))
885 (<= level gnus-verbose-backends)))
886
23f87bed
MB
887(defvar nnheader-pathname-coding-system 'iso-8859-1
888 "*Coding system for file name.")
e0b8fdf5 889
eec82323 890(defun nnheader-group-pathname (group dir &optional file)
35ef97a5 891 "Make file name for GROUP."
eec82323
LMI
892 (concat
893 (let ((dir (file-name-as-directory (expand-file-name dir))))
894 ;; If this directory exists, we use it directly.
16409b0b
GM
895 (file-name-as-directory
896 (if (file-directory-p (concat dir group))
897 (expand-file-name group dir)
898 ;; If not, we translate dots into slashes.
899 (expand-file-name (mm-encode-coding-string
900 (nnheader-replace-chars-in-string group ?. ?/)
23f87bed 901 nnheader-pathname-coding-system)
16409b0b 902 dir))))
eec82323
LMI
903 (cond ((null file) "")
904 ((numberp file) (int-to-string file))
905 (t file))))
906
eec82323 907(defun nnheader-concat (dir &rest files)
16409b0b 908 "Concat DIR as directory to FILES."
eec82323
LMI
909 (apply 'concat (file-name-as-directory dir) files))
910
911(defun nnheader-ms-strip-cr ()
912 "Strip ^M from the end of all lines."
913 (save-excursion
23f87bed 914 (nnheader-remove-cr-followed-by-lf)))
eec82323
LMI
915
916(defun nnheader-file-size (file)
917 "Return the file size of FILE or 0."
918 (or (nth 7 (file-attributes file)) 0))
919
23f87bed
MB
920(defun nnheader-find-etc-directory (package &optional file first)
921 "Go through `load-path' and find the \"../etc/PACKAGE\" directory.
922This function will look in the parent directory of each `load-path'
923entry, and look for the \"etc\" directory there.
924If FILE, find the \".../etc/PACKAGE\" file instead.
925If FIRST is non-nil, return the directory or the file found at the
926first. Otherwise, find the newest one, though it may take a time."
eec82323 927 (let ((path load-path)
23f87bed 928 dir results)
eec82323
LMI
929 ;; We try to find the dir by looking at the load path,
930 ;; stripping away the last component and adding "etc/".
931 (while path
932 (if (and (car path)
933 (file-exists-p
934 (setq dir (concat
935 (file-name-directory
936 (directory-file-name (car path)))
937 "etc/" package
938 (if file "" "/"))))
939 (or file (file-directory-p dir)))
23f87bed
MB
940 (progn
941 (or (member dir results)
942 (push dir results))
943 (setq path (if first nil (cdr path))))
eec82323 944 (setq path (cdr path))))
23f87bed
MB
945 (if (or first (not (cdr results)))
946 (car results)
947 (car (sort results 'file-newer-than-file-p)))))
eec82323 948
9efa445f
DN
949(defvar ange-ftp-path-format)
950(defvar efs-path-regexp)
eec82323
LMI
951(defun nnheader-re-read-dir (path)
952 "Re-read directory PATH if PATH is on a remote system."
953 (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
954 (when (string-match efs-path-regexp path)
955 (efs-re-read-dir path))
956 (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
957 (when (string-match (car ange-ftp-path-format) path)
958 (ange-ftp-re-read-dir path)))))
959
6748645f
LMI
960(defvar nnheader-file-coding-system 'raw-text
961 "Coding system used in file backends of Gnus.")
962
eec82323
LMI
963(defun nnheader-insert-file-contents (filename &optional visit beg end replace)
964 "Like `insert-file-contents', q.v., but only reads in the file.
965A buffer may be modified in several ways after reading into the buffer due
966to advanced Emacs features, such as file-name-handlers, format decoding,
967find-file-hooks, etc.
968 This function ensures that none of these modifications will take place."
16409b0b
GM
969 (let ((coding-system-for-read nnheader-file-coding-system))
970 (mm-insert-file-contents filename visit beg end replace)))
eec82323 971
23f87bed
MB
972(defun nnheader-insert-nov-file (file first)
973 (let ((size (nth 7 (file-attributes file)))
974 (cutoff (* 32 1024)))
975 (when size
976 (if (< size cutoff)
977 ;; If the file is small, we just load it.
978 (nnheader-insert-file-contents file)
979 ;; We start on the assumption that FIRST is pretty recent. If
980 ;; not, we just insert the rest of the file as well.
981 (let (current)
982 (nnheader-insert-file-contents file nil (- size cutoff) size)
983 (goto-char (point-min))
984 (delete-region (point) (or (search-forward "\n" nil 'move) (point)))
985 (setq current (ignore-errors (read (current-buffer))))
986 (if (and (numberp current)
987 (< current first))
988 t
989 (delete-region (point-min) (point-max))
990 (nnheader-insert-file-contents file)))))))
991
eec82323 992(defun nnheader-find-file-noselect (&rest args)
ff4d3926
MB
993 "Open a file with some variables bound.
994See `find-file-noselect' for the arguments."
4a43ee9b
MB
995 (let* ((format-alist nil)
996 (auto-mode-alist (mm-auto-mode-alist))
997 (default-major-mode 'fundamental-mode)
998 (enable-local-variables nil)
999 (after-insert-file-functions nil)
1000 (enable-local-eval nil)
1001 (coding-system-for-read nnheader-file-coding-system)
01c52d31 1002 (version-control 'never)
4a43ee9b
MB
1003 (ffh (if (boundp 'find-file-hook)
1004 'find-file-hook
1005 'find-file-hooks))
1006 (val (symbol-value ffh)))
1007 (set ffh nil)
1008 (unwind-protect
1009 (apply 'find-file-noselect args)
1010 (set ffh val))))
eec82323 1011
eec82323
LMI
1012(defun nnheader-directory-regular-files (dir)
1013 "Return a list of all regular files in DIR."
1014 (let ((files (directory-files dir t))
1015 out)
1016 (while files
1017 (when (file-regular-p (car files))
1018 (push (car files) out))
1019 (pop files))
1020 (nreverse out)))
1021
6748645f
LMI
1022(defun nnheader-directory-files (&rest args)
1023 "Same as `directory-files', but prune \".\" and \"..\"."
1024 (let ((files (apply 'directory-files args))
1025 out)
1026 (while files
1027 (unless (member (file-name-nondirectory (car files)) '("." ".."))
1028 (push (car files) out))
1029 (pop files))
1030 (nreverse out)))
1031
eec82323
LMI
1032(defmacro nnheader-skeleton-replace (from &optional to regexp)
1033 `(let ((new (generate-new-buffer " *nnheader replace*"))
1034 (cur (current-buffer))
1035 (start (point-min)))
eec82323
LMI
1036 (set-buffer cur)
1037 (goto-char (point-min))
1038 (while (,(if regexp 're-search-forward 'search-forward)
1039 ,from nil t)
1040 (insert-buffer-substring
1041 cur start (prog1 (match-beginning 0) (set-buffer new)))
1042 (goto-char (point-max))
1043 ,(when to `(insert ,to))
1044 (set-buffer cur)
1045 (setq start (point)))
1046 (insert-buffer-substring
1047 cur start (prog1 (point-max) (set-buffer new)))
1048 (copy-to-buffer cur (point-min) (point-max))
1049 (kill-buffer (current-buffer))
1050 (set-buffer cur)))
1051
1052(defun nnheader-replace-string (from to)
16409b0b 1053 "Do a fast replacement of FROM to TO from point to `point-max'."
eec82323
LMI
1054 (nnheader-skeleton-replace from to))
1055
1056(defun nnheader-replace-regexp (from to)
16409b0b 1057 "Do a fast regexp replacement of FROM to TO from point to `point-max'."
eec82323
LMI
1058 (nnheader-skeleton-replace from to t))
1059
1060(defun nnheader-strip-cr ()
1061 "Strip all \r's from the current buffer."
1062 (nnheader-skeleton-replace "\r"))
1063
16409b0b
GM
1064(defalias 'nnheader-cancel-timer 'cancel-timer)
1065(defalias 'nnheader-cancel-function-timers 'cancel-function-timers)
23f87bed 1066
e3e955fe
MB
1067;; When changing this function, consider changing `pop3-accept-process-output'
1068;; as well.
23f87bed
MB
1069(defun nnheader-accept-process-output (process)
1070 (accept-process-output
1071 process
1072 (truncate nnheader-read-timeout)
1073 (truncate (* (- nnheader-read-timeout
1074 (truncate nnheader-read-timeout))
1075 1000))))
eec82323 1076
df359f6c 1077(when (featurep 'xemacs)
eec82323
LMI
1078 (require 'nnheaderxm))
1079
1080(run-hooks 'nnheader-load-hook)
1081
1082(provide 'nnheader)
1083
2ec9cf15 1084;; arch-tag: a9c4b7d9-52ae-4ec9-b196-dfd93124d202
eec82323 1085;;; nnheader.el ends here