* nndoc.el (nndoc-type-alist): Add mail-in-mail type.
[gnus] / lisp / nndoc.el
1 ;;; nndoc.el --- single file access for Gnus
2 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
7 ;; Keywords: news
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Commentary:
27
28 ;; For Outlook mail boxes format, see http://mbx2mbox.sourceforge.net/
29
30 ;;; Code:
31
32 (require 'nnheader)
33 (require 'message)
34 (require 'nnmail)
35 (require 'nnoo)
36 (require 'gnus-util)
37 (require 'mm-util)
38 (eval-when-compile (require 'cl))
39
40 (nnoo-declare nndoc)
41
42 (defvoo nndoc-article-type 'guess
43   "*Type of the file.
44 One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
45 `rfc934', `rfc822-forward', `mime-parts', `standard-digest',
46 `slack-digest', `clari-briefs', `nsmail', `outlook', `oe-dbx',
47 `mailman', `exim-bounce', or `guess'.")
48
49 (defvoo nndoc-post-type 'mail
50   "*Whether the nndoc group is `mail' or `post'.")
51
52 (defvoo nndoc-open-document-hook 'nnheader-ms-strip-cr
53   "Hook run after opening a document.
54 The default function removes all trailing carriage returns
55 from the document.")
56
57 (defvar nndoc-type-alist
58   `((mmdf
59      (article-begin .  "^\^A\^A\^A\^A\n")
60      (body-end .  "^\^A\^A\^A\^A\n"))
61     (exim-bounce
62      (article-begin . "^------ This is a copy of the message, including all the headers. ------\n\n")
63      (body-end-function . nndoc-exim-bounce-body-end-function))
64     (nsmail
65      (article-begin .  "^From - "))
66     (news
67      (article-begin . "^Path:"))
68     (rnews
69      (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
70      (body-end-function . nndoc-rnews-body-end))
71     (mbox
72      (article-begin-function . nndoc-mbox-article-begin)
73      (body-end-function . nndoc-mbox-body-end))
74     (babyl
75      (article-begin . "\^_\^L *\n")
76      (body-end . "\^_")
77      (body-begin-function . nndoc-babyl-body-begin)
78      (head-begin-function . nndoc-babyl-head-begin))
79     (rfc934
80      (article-begin . "^--.*\n+")
81      (body-end . "^--.*$")
82      (prepare-body-function . nndoc-unquote-dashes))
83     (mailman
84      (article-begin . "^--__--__--\n\nMessage:")
85      (body-end . "^--__--__--$")
86      (prepare-body-function . nndoc-unquote-dashes))
87     (clari-briefs
88      (article-begin . "^ \\*")
89      (body-end . "^\t------*[ \t]^*\n^ \\*")
90      (body-begin . "^\t")
91      (head-end . "^\t")
92      (generate-head-function . nndoc-generate-clari-briefs-head)
93      (article-transform-function . nndoc-transform-clari-briefs))
94     (mime-digest
95      (article-begin . "")
96      (head-begin . "^ ?\n")
97      (head-end . "^ ?$")
98      (body-end . "")
99      (file-end . "")
100      (subtype digest guess))
101     (mime-parts
102      (generate-head-function . nndoc-generate-mime-parts-head)
103      (article-transform-function . nndoc-transform-mime-parts))
104     (standard-digest
105      (first-article . ,(concat "^" (make-string 70 ?-) "\n *\n+"))
106      (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n *\n+"))
107      (prepare-body-function . nndoc-unquote-dashes)
108      (body-end-function . nndoc-digest-body-end)
109      (head-end . "^ *$")
110      (body-begin . "^ *\n")
111      (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
112      (subtype digest guess))
113     (slack-digest
114      (article-begin . "^------------------------------*[\n \t]+")
115      (head-end . "^ ?$")
116      (body-end-function . nndoc-digest-body-end)
117      (body-begin . "^ ?$")
118      (file-end . "^End of")
119      (prepare-body-function . nndoc-unquote-dashes)
120      (subtype digest guess))
121     (lanl-gov-announce
122      (article-begin . "^\\\\\\\\\n")
123      (head-begin . "^Paper.*:")
124      (head-end   . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
125      (body-begin . "")
126      (body-end   . "-------------------------------------------------")
127      (file-end   . "^Title: Recent Seminal")
128      (generate-head-function . nndoc-generate-lanl-gov-head)
129      (article-transform-function . nndoc-transform-lanl-gov-announce)
130      (subtype preprints guess))
131     (rfc822-forward
132      (article-begin . "^\n")
133      (body-end-function . nndoc-rfc822-forward-body-end-function))
134     (outlook
135      (article-begin-function . nndoc-outlook-article-begin)
136      (body-end .  "\0"))
137     (oe-dbx  ;; Outlook Express DBX format
138      (dissection-function . nndoc-oe-dbx-dissection)
139      (generate-head-function . nndoc-oe-dbx-generate-head)
140      (generate-article-function . nndoc-oe-dbx-generate-article))
141     (forward
142      (article-begin . "^-+ \\(Start of \\)?forwarded message.*\n+")
143      (body-end . "^-+ End \\(of \\)?forwarded message.*$")
144      (prepare-body-function . nndoc-unquote-dashes))
145     (mail-in-mail ;; Wild guess on mailer daemon's messages or others
146      (article-begin-function . nndoc-mail-in-mail-article-begin))
147     (guess
148      (guess . t)
149      (subtype nil))
150     (digest
151      (guess . t)
152      (subtype nil))
153     (preprints
154      (guess . t)
155      (subtype nil))))
156
157 (defvar nndoc-binary-file-names ".[Dd][Bb][Xx]$"
158   "Regexp for binary nndoc file names.")
159
160 \f
161 (defvoo nndoc-file-begin nil)
162 (defvoo nndoc-first-article nil)
163 (defvoo nndoc-article-begin nil)
164 (defvoo nndoc-head-begin nil)
165 (defvoo nndoc-head-end nil)
166 (defvoo nndoc-file-end nil)
167 (defvoo nndoc-body-begin nil)
168 (defvoo nndoc-body-end-function nil)
169 (defvoo nndoc-body-begin-function nil)
170 (defvoo nndoc-head-begin-function nil)
171 (defvoo nndoc-body-end nil)
172 ;; nndoc-dissection-alist is a list of sublists.  Each sublist holds the
173 ;; following items.  ARTICLE acts as the association key and is an ordinal
174 ;; starting at 1.  HEAD-BEGIN [0], HEAD-END [1], BODY-BEGIN [2] and BODY-END
175 ;; [3] are positions in the `nndoc' buffer.  LINE-COUNT [4] is a count of
176 ;; lines in the body.  For MIME dissections only, ARTICLE-INSERT [5] and
177 ;; SUMMARY-INSERT [6] give headers to insert for full article or summary line
178 ;; generation, respectively.  Other headers usually follow directly from the
179 ;; buffer.  Value `nil' means no insert.
180 (defvoo nndoc-dissection-alist nil)
181 (defvoo nndoc-prepare-body-function nil)
182 (defvoo nndoc-generate-head-function nil)
183 (defvoo nndoc-article-transform-function nil)
184 (defvoo nndoc-article-begin-function nil)
185 (defvoo nndoc-generate-article-function nil)
186 (defvoo nndoc-dissection-function nil)
187
188 (defvoo nndoc-status-string "")
189 (defvoo nndoc-group-alist nil)
190 (defvoo nndoc-current-buffer nil
191   "Current nndoc news buffer.")
192 (defvoo nndoc-address nil)
193
194 (defconst nndoc-version "nndoc 1.0"
195   "nndoc version.")
196
197 \f
198
199 ;;; Interface functions
200
201 (nnoo-define-basics nndoc)
202
203 (deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
204   (when (nndoc-possibly-change-buffer newsgroup server)
205     (save-excursion
206       (set-buffer nntp-server-buffer)
207       (erase-buffer)
208       (let (article entry)
209         (if (stringp (car articles))
210             'headers
211           (while articles
212             (when (setq entry (cdr (assq (setq article (pop articles))
213                                          nndoc-dissection-alist)))
214               (insert (format "221 %d Article retrieved.\n" article))
215               (if nndoc-generate-head-function
216                   (funcall nndoc-generate-head-function article)
217                 (insert-buffer-substring
218                  nndoc-current-buffer (car entry) (nth 1 entry)))
219               (goto-char (point-max))
220               (unless (eq (char-after (1- (point))) ?\n)
221                 (insert "\n"))
222               (insert (format "Lines: %d\n" (nth 4 entry)))
223               (insert ".\n")))
224
225           (nnheader-fold-continuation-lines)
226           'headers)))))
227
228 (deffoo nndoc-request-article (article &optional newsgroup server buffer)
229   (nndoc-possibly-change-buffer newsgroup server)
230   (save-excursion
231     (let ((buffer (or buffer nntp-server-buffer))
232           (entry (cdr (assq article nndoc-dissection-alist)))
233           beg)
234       (set-buffer buffer)
235       (erase-buffer)
236       (when entry
237         (cond 
238          ((stringp article) nil)
239          (nndoc-generate-article-function
240           (funcall nndoc-generate-article-function article))
241          (t
242           (insert-buffer-substring
243            nndoc-current-buffer (car entry) (nth 1 entry))
244           (insert "\n")
245           (setq beg (point))
246           (insert-buffer-substring
247            nndoc-current-buffer (nth 2 entry) (nth 3 entry))
248           (goto-char beg)
249           (when nndoc-prepare-body-function
250             (funcall nndoc-prepare-body-function))
251           (when nndoc-article-transform-function
252             (funcall nndoc-article-transform-function article))
253           t))))))
254
255 (deffoo nndoc-request-group (group &optional server dont-check)
256   "Select news GROUP."
257   (let (number)
258     (cond
259      ((not (nndoc-possibly-change-buffer group server))
260       (nnheader-report 'nndoc "No such file or buffer: %s"
261                        nndoc-address))
262      (dont-check
263       (nnheader-report 'nndoc "Selected group %s" group)
264       t)
265      ((zerop (setq number (length nndoc-dissection-alist)))
266       (nndoc-close-group group)
267       (nnheader-report 'nndoc "No articles in group %s" group))
268      (t
269       (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
270
271 (deffoo nndoc-request-type (group &optional article)
272   (cond ((not article) 'unknown)
273         (nndoc-post-type nndoc-post-type)
274         (t 'unknown)))
275
276 (deffoo nndoc-close-group (group &optional server)
277   (nndoc-possibly-change-buffer group server)
278   (and nndoc-current-buffer
279        (buffer-name nndoc-current-buffer)
280        (kill-buffer nndoc-current-buffer))
281   (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
282                                 nndoc-group-alist))
283   (setq nndoc-current-buffer nil)
284   (nnoo-close-server 'nndoc server)
285   (setq nndoc-dissection-alist nil)
286   t)
287
288 (deffoo nndoc-request-list (&optional server)
289   nil)
290
291 (deffoo nndoc-request-newgroups (date &optional server)
292   nil)
293
294 (deffoo nndoc-request-list-newsgroups (&optional server)
295   nil)
296
297 \f
298 ;;; Internal functions.
299
300 (defun nndoc-possibly-change-buffer (group source)
301   (let (buf)
302     (cond
303      ;; The current buffer is this group's buffer.
304      ((and nndoc-current-buffer
305            (buffer-name nndoc-current-buffer)
306            (eq nndoc-current-buffer
307                (setq buf (cdr (assoc group nndoc-group-alist))))))
308      ;; We change buffers by taking an old from the group alist.
309      ;; `source' is either a string (a file name) or a buffer object.
310      (buf
311       (setq nndoc-current-buffer buf))
312      ;; It's a totally new group.
313      ((or (and (bufferp nndoc-address)
314                (buffer-name nndoc-address))
315           (and (stringp nndoc-address)
316                (file-exists-p nndoc-address)
317                (not (file-directory-p nndoc-address))))
318       (push (cons group (setq nndoc-current-buffer
319                               (get-buffer-create
320                                (concat " *nndoc " group "*"))))
321             nndoc-group-alist)
322       (setq nndoc-dissection-alist nil)
323       (save-excursion
324         (set-buffer nndoc-current-buffer)
325         (erase-buffer)
326         (if (and (stringp nndoc-address)
327                  (string-match nndoc-binary-file-names nndoc-address))
328             (let ((coding-system-for-read 'binary))
329               (mm-insert-file-contents nndoc-address))
330           (if (stringp nndoc-address)
331               (nnheader-insert-file-contents nndoc-address)
332             (insert-buffer-substring nndoc-address))
333           (run-hooks 'nndoc-open-document-hook)))))
334     ;; Initialize the nndoc structures according to this new document.
335     (when (and nndoc-current-buffer
336                (not nndoc-dissection-alist))
337       (save-excursion
338         (set-buffer nndoc-current-buffer)
339         (nndoc-set-delims)
340         (if (eq nndoc-article-type 'mime-parts)
341             (nndoc-dissect-mime-parts)
342           (nndoc-dissect-buffer))))
343     (unless nndoc-current-buffer
344       (nndoc-close-server))
345     ;; Return whether we managed to select a file.
346     nndoc-current-buffer))
347
348 ;;;
349 ;;; Deciding what document type we have
350 ;;;
351
352 (defun nndoc-set-delims ()
353   "Set the nndoc delimiter variables according to the type of the document."
354   (let ((vars '(nndoc-file-begin
355                 nndoc-first-article
356                 nndoc-article-begin-function
357                 nndoc-head-begin nndoc-head-end
358                 nndoc-file-end nndoc-article-begin
359                 nndoc-body-begin nndoc-body-end-function nndoc-body-end
360                 nndoc-prepare-body-function nndoc-article-transform-function
361                 nndoc-generate-head-function nndoc-body-begin-function
362                 nndoc-head-begin-function
363                 nndoc-generate-article-function
364                 nndoc-dissection-function)))
365     (while vars
366       (set (pop vars) nil)))
367   (let (defs)
368     ;; Guess away until we find the real file type.
369     (while (assq 'guess (setq defs (cdr (assq nndoc-article-type
370                                               nndoc-type-alist))))
371       (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
372     ;; Set the nndoc variables.
373     (while defs
374       (set (intern (format "nndoc-%s" (caar defs)))
375            (cdr (pop defs))))))
376
377 (defun nndoc-guess-type (subtype)
378   (let ((alist nndoc-type-alist)
379         results result entry)
380     (while (and (not result)
381                 (setq entry (pop alist)))
382       (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
383         (goto-char (point-min))
384         ;; Remove blank lines.
385         (while (eq (following-char) ?\n)
386           (delete-char 1))
387         (when (numberp (setq result (funcall (intern
388                                               (format "nndoc-%s-type-p"
389                                                       (car entry))))))
390           (push (cons result entry) results)
391           (setq result nil))))
392     (unless (or result results)
393       (error "Document is not of any recognized type"))
394     (if result
395         (car entry)
396       (cadar (sort results 'car-less-than-car)))))
397
398 ;;;
399 ;;; Built-in type predicates and functions
400 ;;;
401
402 (defun nndoc-mbox-type-p ()
403   (when (looking-at message-unix-mail-delimiter)
404     t))
405
406 (defun nndoc-mbox-article-begin ()
407   (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
408     (goto-char (match-beginning 0))))
409
410 (defun nndoc-mbox-body-end ()
411   (let ((beg (point))
412         len end)
413     (when
414         (save-excursion
415           (and (re-search-backward
416                 (concat "^" message-unix-mail-delimiter) nil t)
417                (setq end (point))
418                (search-forward "\n\n" beg t)
419                (re-search-backward
420                 "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
421                (setq len (string-to-int (match-string 1)))
422                (search-forward "\n\n" beg t)
423                (unless (= (setq len (+ (point) len)) (point-max))
424                  (and (< len (point-max))
425                       (goto-char len)
426                       (looking-at message-unix-mail-delimiter)))))
427       (goto-char len))))
428
429 (defun nndoc-mmdf-type-p ()
430   (when (looking-at "\^A\^A\^A\^A$")
431     t))
432
433 (defun nndoc-news-type-p ()
434   (when (looking-at "^Path:.*\n")
435     t))
436
437 (defun nndoc-rnews-type-p ()
438   (when (looking-at "#! *rnews")
439     t))
440
441 (defun nndoc-rnews-body-end ()
442   (and (re-search-backward nndoc-article-begin nil t)
443        (forward-line 1)
444        (goto-char (+ (point) (string-to-int (match-string 1))))))
445
446 (defun nndoc-babyl-type-p ()
447   (when (re-search-forward "\^_\^L *\n" nil t)
448     t))
449
450 (defun nndoc-babyl-body-begin ()
451   (re-search-forward "^\n" nil t)
452   (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
453     (let ((next (or (save-excursion
454                       (re-search-forward nndoc-article-begin nil t))
455                     (point-max))))
456       (unless (re-search-forward "^\n" next t)
457         (goto-char next)
458         (forward-line -1)
459         (insert "\n")
460         (forward-line -1)))))
461
462 (defun nndoc-babyl-head-begin ()
463   (when (re-search-forward "^[0-9].*\n" nil t)
464     (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
465       (forward-line 1))
466     t))
467
468 (defun nndoc-forward-type-p ()
469   (when (and (re-search-forward "^-+ \\(Start of \\)?forwarded message.*\n+"
470                                 nil t)
471              (looking-at "[\r\n]*[a-zA-Z][a-zA-Z0-9-]*:"))
472     t))
473
474 (defun nndoc-rfc934-type-p ()
475   (when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
476              (not (re-search-forward "^Subject:.*digest" nil t))
477              (not (re-search-backward "^From:" nil t 2))
478              (not (re-search-forward "^From:" nil t 2)))
479     t))
480
481 (defun nndoc-mailman-type-p ()
482   (when (re-search-forward "^--__--__--\n+" nil t)
483     t))
484
485 (defun nndoc-rfc822-forward-type-p ()
486   (save-restriction
487     (message-narrow-to-head)
488     (when (re-search-forward "^Content-Type: *message/rfc822" nil t)
489       t)))
490
491 (defun nndoc-rfc822-forward-body-end-function ()
492   (goto-char (point-max)))
493
494 (defun nndoc-mime-parts-type-p ()
495   (let ((case-fold-search t)
496         (limit (search-forward "\n\n" nil t)))
497     (goto-char (point-min))
498     (when (and limit
499                (re-search-forward
500                 (concat "\
501 ^Content-Type:[ \t]*multipart/[a-z]+ *; *\\(\\(\n[ \t]\\)?.*;\\)*"
502                         "\\(\n[ \t]\\)?[ \t]*boundary=\"?[^\"\n]*[^\" \t\n]")
503                 limit t))
504       t)))
505
506 (defun nndoc-transform-mime-parts (article)
507   (let* ((entry (cdr (assq article nndoc-dissection-alist)))
508          (headers (nth 5 entry)))
509     (when headers
510       (goto-char (point-min))
511       (insert headers))))
512
513 (defun nndoc-generate-mime-parts-head (article)
514   (let* ((entry (cdr (assq article nndoc-dissection-alist)))
515          (headers (nth 6 entry)))
516     (save-restriction
517       (narrow-to-region (point) (point))
518       (insert-buffer-substring
519        nndoc-current-buffer (car entry) (nth 1 entry))
520       (goto-char (point-max)))
521     (when headers
522       (insert headers))))
523
524 (defun nndoc-clari-briefs-type-p ()
525   (when (let ((case-fold-search nil))
526           (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
527     t))
528
529 (defun nndoc-transform-clari-briefs (article)
530   (goto-char (point-min))
531   (when (looking-at " *\\*\\(.*\\)\n")
532     (replace-match "" t t))
533   (nndoc-generate-clari-briefs-head article))
534
535 (defun nndoc-generate-clari-briefs-head (article)
536   (let ((entry (cdr (assq article nndoc-dissection-alist)))
537         subject from)
538     (save-excursion
539       (set-buffer nndoc-current-buffer)
540       (save-restriction
541         (narrow-to-region (car entry) (nth 3 entry))
542         (goto-char (point-min))
543         (when (looking-at " *\\*\\(.*\\)$")
544           (setq subject (match-string 1))
545           (when (string-match "[ \t]+$" subject)
546             (setq subject (substring subject 0 (match-beginning 0)))))
547         (when
548             (let ((case-fold-search nil))
549               (re-search-forward
550                "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
551           (setq from (match-string 1)))))
552     (insert "From: " "clari@clari.net (" (or from "unknown") ")"
553             "\nSubject: " (or subject "(no subject)") "\n")))
554
555 (defun nndoc-exim-bounce-type-p ()
556   (and (re-search-forward "^------ This is a copy of the message, including all the headers. ------" nil t)
557        t))
558
559 (defun nndoc-exim-bounce-body-end-function ()
560   (goto-char (point-max)))
561
562
563 (defun nndoc-mime-digest-type-p ()
564   (let ((case-fold-search t)
565         boundary-id b-delimiter entry)
566     (when (and
567            (re-search-forward
568             (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
569                     "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)")
570             nil t)
571            (match-beginning 1))
572       (setq boundary-id (match-string 1)
573             b-delimiter (concat "\n--" boundary-id "[ \t]*$"))
574       (setq entry (assq 'mime-digest nndoc-type-alist))
575       (setcdr entry
576               (list
577                (cons 'head-begin "^ ?\n")
578                (cons 'head-end "^ ?$")
579                (cons 'body-begin "^ ?\n")
580                (cons 'article-begin b-delimiter)
581                (cons 'body-end-function 'nndoc-digest-body-end)
582                (cons 'file-end (concat "\n--" boundary-id "--[ \t]*$"))))
583       t)))
584
585 (defun nndoc-standard-digest-type-p ()
586   (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
587              (re-search-forward
588               (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
589     t))
590
591 (defun nndoc-digest-body-end ()
592   (and (re-search-forward nndoc-article-begin nil t)
593        (goto-char (match-beginning 0))))
594
595 (defun nndoc-slack-digest-type-p ()
596   0)
597
598 (defun nndoc-lanl-gov-announce-type-p ()
599   (when (let ((case-fold-search nil))
600           (re-search-forward "^\\\\\\\\\nPaper: [a-z-]+/[0-9]+" nil t))
601     t))
602
603 (defun nndoc-transform-lanl-gov-announce (article)
604   (goto-char (point-max))
605   (when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
606     (replace-match "\n\nGet it at \\1 (\\2)" t nil)))
607
608 (defun nndoc-generate-lanl-gov-head (article)
609   (let ((entry (cdr (assq article nndoc-dissection-alist)))
610         (e-mail "no address given")
611         subject from)
612     (save-excursion
613       (set-buffer nndoc-current-buffer)
614       (save-restriction
615         (narrow-to-region (car entry) (nth 1 entry))
616         (goto-char (point-min))
617         (when (looking-at "^Paper.*: \\([a-z-]+/[0-9]+\\)")
618           (setq subject (concat " (" (match-string 1) ")"))
619           (when (re-search-forward "^From: \\([^ ]+\\)" nil t)
620             (setq e-mail (match-string 1)))
621           (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
622                                    nil t)
623             (setq subject (concat (match-string 1) subject))
624             (setq from (concat (match-string 2) " <" e-mail ">"))))))
625     (while (and from (string-match "(\[^)\]*)" from))
626       (setq from (replace-match "" t t from)))
627     (insert "From: "  (or from "unknown")
628             "\nSubject: " (or subject "(no subject)") "\n")))
629
630 (defun nndoc-nsmail-type-p ()
631   (when (looking-at "From - ")
632     t))
633
634 (defun nndoc-outlook-article-begin ()
635   (prog1 (re-search-forward "From:\\|Received:" nil t)
636     (goto-char (match-beginning 0))))
637
638 (defun nndoc-outlook-type-p ()
639   ;; FIXME: Is JMF the magic of outlook mailbox? -- ShengHuo.
640   (looking-at "JMF"))
641
642 (defun nndoc-oe-dbx-type-p ()
643   (looking-at (mm-string-as-multibyte "\317\255\022\376")))
644
645 (defun nndoc-read-little-endian ()
646   (+ (prog1 (char-after) (forward-char 1))
647      (lsh (prog1 (char-after) (forward-char 1)) 8)
648      (lsh (prog1 (char-after) (forward-char 1)) 16)
649      (lsh (prog1 (char-after) (forward-char 1)) 24)))
650
651 (defun nndoc-oe-dbx-decode-block ()
652   (list
653    (nndoc-read-little-endian)   ;; this address
654    (nndoc-read-little-endian)   ;; next address offset
655    (nndoc-read-little-endian)   ;; blocksize
656    (nndoc-read-little-endian))) ;; next address
657
658 (defun nndoc-oe-dbx-dissection ()
659   (let ((i 0) blk p tp)
660     (goto-char 60117) ;; 0x0000EAD4+1
661     (setq p (point))
662     (unless (eobp)
663       (setq blk (nndoc-oe-dbx-decode-block)))
664     (while (and blk (> (car blk) 0) (or (zerop (nth 3 blk))
665                                         (> (nth 3 blk) p)))
666       (push (list (incf i) p nil nil nil 0) nndoc-dissection-alist)
667       (while (and (> (car blk) 0) (> (nth 3 blk) p))
668         (goto-char (1+ (nth 3 blk)))
669         (setq blk (nndoc-oe-dbx-decode-block)))
670       (if (or (<= (car blk) p)
671               (<= (nth 1 blk) 0)
672               (not (zerop (nth 3 blk))))
673           (setq blk nil)
674         (setq tp (+ (car blk) (nth 1 blk) 17))
675         (if (or (<= tp p) (>= tp (point-max)))
676             (setq blk nil)
677           (goto-char tp)
678           (setq p tp
679                 blk (nndoc-oe-dbx-decode-block)))))))
680
681 (defun nndoc-oe-dbx-generate-article (article &optional head)
682   (let ((entry (cdr (assq article nndoc-dissection-alist)))
683         (cur (current-buffer))
684         (begin (point))
685         blk p)
686     (with-current-buffer nndoc-current-buffer
687       (setq p (car entry))
688       (while (> p (point-min))
689         (goto-char p)
690         (setq blk (nndoc-oe-dbx-decode-block))
691         (setq p (point))
692         (with-current-buffer cur
693           (insert-buffer-substring nndoc-current-buffer p (+ p (nth 2 blk))))
694         (setq p (1+ (nth 3 blk)))))
695     (goto-char begin)
696     (while (re-search-forward "\r$" nil t)
697       (delete-backward-char 1))
698     (when head
699       (goto-char begin)
700       (when (search-forward "\n\n" nil t)
701         (setcar (cddddr entry) (count-lines (point) (point-max)))
702         (delete-region (1- (point)) (point-max))))
703     t))
704
705 (defun nndoc-oe-dbx-generate-head (article)
706   (nndoc-oe-dbx-generate-article article 'head))
707
708 (defun nndoc-mail-in-mail-type-p ()
709   (save-excursion
710     (and (search-forward "\n\n" nil t)
711          (re-search-forward "^[-A-Za-z0-9]+: .*\n\\([ \t]?.*\n\\)*\\(^[-A-Za-z0-9]+: .*\n\\([ \t]?.*\n\\)*\\)+\n" nil t)
712          t)))
713
714 (defun nndoc-mail-in-mail-article-begin ()
715   (when (re-search-forward "^[-A-Za-z0-9]+: .*\n\\([ \t]?.*\n\\)*\\(^[-A-Za-z0-9]+: .*\n\\([ \t]?.*\n\\)*\\)+\n" nil t)
716     (goto-char (match-beginning 0))))
717
718 (deffoo nndoc-request-accept-article (group &optional server last)
719   nil)
720
721 ;;;
722 ;;; Functions for dissecting the documents
723 ;;;
724
725 (defun nndoc-search (regexp)
726   (prog1
727       (re-search-forward regexp nil t)
728     (beginning-of-line)))
729
730 (defun nndoc-dissect-buffer ()
731   "Go through the document and partition it into heads/bodies/articles."
732   (let ((i 0)
733         (first t)
734         head-begin head-end body-begin body-end)
735     (setq nndoc-dissection-alist nil)
736     (save-excursion
737       (set-buffer nndoc-current-buffer)
738       (goto-char (point-min))
739       ;; Remove blank lines.
740       (while (eq (following-char) ?\n)
741         (delete-char 1))
742       (if nndoc-dissection-function
743           (funcall nndoc-dissection-function)
744         ;; Find the beginning of the file.
745         (when nndoc-file-begin
746           (nndoc-search nndoc-file-begin))
747         ;; Go through the file.
748         (while (if (and first nndoc-first-article)
749                    (nndoc-search nndoc-first-article)
750                  (nndoc-article-begin))
751           (setq first nil)
752           (cond (nndoc-head-begin-function
753                  (funcall nndoc-head-begin-function))
754                 (nndoc-head-begin
755                  (nndoc-search nndoc-head-begin)))
756           (if (or (eobp)
757                   (and nndoc-file-end
758                        (looking-at nndoc-file-end)))
759               (goto-char (point-max))
760             (setq head-begin (point))
761             (nndoc-search (or nndoc-head-end "^$"))
762             (setq head-end (point))
763             (if nndoc-body-begin-function
764                 (funcall nndoc-body-begin-function)
765               (nndoc-search (or nndoc-body-begin "^\n")))
766             (setq body-begin (point))
767             (or (and nndoc-body-end-function
768                      (funcall nndoc-body-end-function))
769                 (and nndoc-body-end
770                      (nndoc-search nndoc-body-end))
771                 (nndoc-article-begin)
772                 (progn
773                   (goto-char (point-max))
774                   (when nndoc-file-end
775                     (and (re-search-backward nndoc-file-end nil t)
776                          (beginning-of-line)))))
777             (setq body-end (point))
778             (push (list (incf i) head-begin head-end body-begin body-end
779                         (count-lines body-begin body-end))
780                   nndoc-dissection-alist)))))))
781
782 (defun nndoc-article-begin ()
783   (if nndoc-article-begin-function
784       (funcall nndoc-article-begin-function)
785     (ignore-errors
786       (nndoc-search nndoc-article-begin))))
787
788 (defun nndoc-unquote-dashes ()
789   "Unquote quoted non-separators in digests."
790   (while (re-search-forward "^- -"nil t)
791     (replace-match "-" t t)))
792
793 ;; Against compiler warnings.
794 (defvar nndoc-mime-split-ordinal)
795
796 (defun nndoc-dissect-mime-parts ()
797   "Go through a MIME composite article and partition it into sub-articles.
798 When a MIME entity contains sub-entities, dissection produces one article for
799 the header of this entity, and one article per sub-entity."
800   (setq nndoc-dissection-alist nil
801         nndoc-mime-split-ordinal 0)
802   (save-excursion
803     (set-buffer nndoc-current-buffer)
804     (nndoc-dissect-mime-parts-sub (point-min) (point-max) nil nil nil)))
805
806 (defun nndoc-dissect-mime-parts-sub (head-begin body-end article-insert
807                                                 position parent)
808   "Dissect an entity, within a composite MIME message.
809 The complete message or MIME entity extends from HEAD-BEGIN to BODY-END.
810 ARTICLE-INSERT should be added at beginning for generating a full article.
811 The string POSITION holds a dotted decimal representation of the article
812 position in the hierarchical structure, it is nil for the outer entity.
813 PARENT is the message-ID of the parent summary line, or nil for none."
814   (let ((case-fold-search t)
815         (message-id (nnmail-message-id))
816         head-end body-begin summary-insert message-rfc822 multipart-any
817         subject content-type type subtype boundary-regexp)
818     ;; Gracefully handle a missing body.
819     (goto-char head-begin)
820     (if (or (and (eq (char-after) ?\n) (or (forward-char 1) t))
821             (search-forward "\n\n" body-end t))
822         (setq head-end (1- (point))
823               body-begin (point))
824       (setq head-end body-end
825             body-begin body-end))
826     (narrow-to-region head-begin head-end)
827     ;; Save MIME attributes.
828     (goto-char head-begin)
829     (setq content-type (message-fetch-field "Content-Type"))
830     (when content-type
831       (when (string-match
832              "^ *\\([^ \t\n/;]+\\)/\\([^ \t\n/;]+\\)" content-type)
833         (setq type (downcase (match-string 1 content-type))
834               subtype (downcase (match-string 2 content-type))
835               message-rfc822 (and (string= type "message")
836                                   (string= subtype "rfc822"))
837               multipart-any (string= type "multipart")))
838       (when (string-match ";[ \t\n]*name=\\([^ \t\n;]+\\)" content-type)
839         (setq subject (match-string 1 content-type)))
840       (when (string-match "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)" content-type)
841         (setq boundary-regexp (concat "^--"
842                                       (regexp-quote
843                                        (match-string 1 content-type))
844                                       "\\(--\\)?[ \t]*\n"))))
845     (unless subject
846       (when (or multipart-any (not article-insert))
847         (setq subject (message-fetch-field "Subject"))))
848     (unless type
849       (setq type "text"
850             subtype "plain"))
851     ;; Prepare the article and summary inserts.
852     (unless article-insert
853       (setq article-insert (buffer-substring (point-min) (point-max))
854             head-end head-begin))
855     (setq summary-insert article-insert)
856     ;; - summary Subject.
857     (setq summary-insert
858           (let ((line (concat "Subject: <" position
859                               (and position multipart-any ".")
860                               (and multipart-any "*")
861                               (and (or position multipart-any) " ")
862                               (cond ((string= subtype "plain") type)
863                                     ((string= subtype "basic") type)
864                                     (t subtype))
865                               ">"
866                               (and subject " ")
867                               subject
868                               "\n")))
869             (if (string-match "Subject:.*\n\\([ \t].*\n\\)*" summary-insert)
870                 (replace-match line t t summary-insert)
871               (concat summary-insert line))))
872     ;; - summary Message-ID.
873     (setq summary-insert
874           (let ((line (concat "Message-ID: " message-id "\n")))
875             (if (string-match "Message-ID:.*\n\\([ \t].*\n\\)*" summary-insert)
876                 (replace-match line t t summary-insert)
877               (concat summary-insert line))))
878     ;; - summary References.
879     (when parent
880       (setq summary-insert
881             (let ((line (concat "References: " parent "\n")))
882               (if (string-match "References:.*\n\\([ \t].*\n\\)*"
883                                 summary-insert)
884                   (replace-match line t t summary-insert)
885                 (concat summary-insert line)))))
886     ;; Generate dissection information for this entity.
887     (push (list (incf nndoc-mime-split-ordinal)
888                 head-begin head-end body-begin body-end
889                 (count-lines body-begin body-end)
890                 article-insert summary-insert)
891           nndoc-dissection-alist)
892     ;; Recurse for all sub-entities, if any.
893     (widen)
894     (cond
895      (message-rfc822
896       (save-excursion
897         (nndoc-dissect-mime-parts-sub body-begin body-end nil
898                                       position message-id)))
899      ((and multipart-any boundary-regexp)
900       (let ((part-counter 0)
901             part-begin part-end eof-flag)
902         (while (string-match "\
903 ^\\(Lines\\|Content-\\(Type\\|Transfer-Encoding\\|Disposition\\)\\):.*\n\\([ \t].*\n\\)*"
904                              article-insert)
905           (setq article-insert (replace-match "" t t article-insert)))
906         (let ((case-fold-search nil))
907           (goto-char body-begin)
908           (setq eof-flag (not (re-search-forward boundary-regexp body-end t)))
909           (while (not eof-flag)
910             (setq part-begin (point))
911             (cond ((re-search-forward boundary-regexp body-end t)
912                    (or (not (match-string 1))
913                        (string= (match-string 1) "")
914                        (setq eof-flag t))
915                    (forward-line -1)
916                    (setq part-end (point))
917                    (forward-line 1))
918                   (t (setq part-end body-end
919                            eof-flag t)))
920             (save-excursion
921               (nndoc-dissect-mime-parts-sub
922                part-begin part-end article-insert
923                (concat position
924                        (and position ".")
925                        (format "%d" (incf part-counter)))
926                message-id)))))))))
927
928 ;;;###autoload
929 (defun nndoc-add-type (definition &optional position)
930   "Add document DEFINITION to the list of nndoc document definitions.
931 If POSITION is nil or `last', the definition will be added
932 as the last checked definition, if t or `first', add as the
933 first definition, and if any other symbol, add after that
934 symbol in the alist."
935   ;; First remove any old instances.
936   (gnus-pull (car definition) nndoc-type-alist)
937   ;; Then enter the new definition in the proper place.
938   (cond
939    ((or (null position) (eq position 'last))
940     (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
941    ((or (eq position t) (eq position 'first))
942     (push definition nndoc-type-alist))
943    (t
944     (let ((list (memq (assq position nndoc-type-alist)
945                       nndoc-type-alist)))
946       (unless list
947         (error "No such position: %s" position))
948       (setcdr list (cons definition (cdr list)))))))
949
950 (provide 'nndoc)
951
952 ;;; nndoc.el ends here