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