f9455edfd5a19764a042ef90b55515f5386e8299
[gnus] / lisp / nndoc.el
1 ;;; nndoc.el --- single file access for Gnus
2
3 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
4 ;;   2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
8 ;; Keywords: news
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 3, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
26
27 ;;; Commentary:
28
29 ;; For Outlook mail boxes format, see http://mbx2mbox.sourceforge.net/
30
31 ;;; Code:
32
33 (require 'nnheader)
34 (require 'message)
35 (require 'nnmail)
36 (require 'nnoo)
37 (require 'gnus-util)
38 (require 'mm-util)
39 (eval-when-compile (require 'cl))
40
41 (nnoo-declare nndoc)
42
43 (defvoo nndoc-article-type 'guess
44   "*Type of the file.
45 One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
46 `rfc934', `rfc822-forward', `mime-parts', `standard-digest',
47 `slack-digest', `clari-briefs', `nsmail', `outlook', `oe-dbx',
48 `mailman', `exim-bounce', or `guess'.")
49
50 (defvoo nndoc-post-type 'mail
51   "*Whether the nndoc group is `mail' or `post'.")
52
53 (defvoo nndoc-open-document-hook 'nnheader-ms-strip-cr
54   "Hook run after opening a document.
55 The default function removes all trailing carriage returns
56 from the document.")
57
58 (defvar nndoc-type-alist
59   `((mmdf
60      (article-begin .  "^\^A\^A\^A\^A\n")
61      (body-end .  "^\^A\^A\^A\^A\n"))
62     (mime-digest
63      (article-begin . "")
64      (head-begin . "^ ?\n")
65      (head-end . "^ ?$")
66      (body-end . "")
67      (file-end . "")
68      (subtype digest guess))
69     (mime-parts
70      (generate-head-function . nndoc-generate-mime-parts-head)
71      (article-transform-function . nndoc-transform-mime-parts))
72     (nsmail
73      (article-begin .  "^From - "))
74     (news
75      (article-begin . "^Path:"))
76     (rnews
77      (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
78      (body-end-function . nndoc-rnews-body-end))
79     (mbox
80      (article-begin-function . nndoc-mbox-article-begin)
81      (body-end-function . nndoc-mbox-body-end))
82     (babyl
83      (article-begin . "\^_\^L *\n")
84      (body-end . "\^_")
85      (body-begin-function . nndoc-babyl-body-begin)
86      (head-begin-function . nndoc-babyl-head-begin))
87     (exim-bounce
88      (article-begin . "^------ This is a copy of the message, including all the headers. ------\n\n")
89      (body-end-function . nndoc-exim-bounce-body-end-function))
90     (rfc934
91      (article-begin . "^--.*\n+")
92      (body-end . "^--.*$")
93      (prepare-body-function . nndoc-unquote-dashes))
94     (mailman
95      (article-begin . "^--__--__--\n\nMessage:")
96      (body-end . "^--__--__--$")
97      (prepare-body-function . nndoc-unquote-dashes))
98     (clari-briefs
99      (article-begin . "^ \\*")
100      (body-end . "^\t------*[ \t]^*\n^ \\*")
101      (body-begin . "^\t")
102      (head-end . "^\t")
103      (generate-head-function . nndoc-generate-clari-briefs-head)
104      (article-transform-function . nndoc-transform-clari-briefs))
105     
106     (standard-digest
107      (first-article . ,(concat "^" (make-string 70 ?-) "\n *\n+"))
108      (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n *\n+"))
109      (prepare-body-function . nndoc-unquote-dashes)
110      (body-end-function . nndoc-digest-body-end)
111      (head-end . "^ *$")
112      (body-begin . "^ *\n")
113      (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
114      (subtype digest guess))
115     (slack-digest
116      (article-begin . "^------------------------------*[\n \t]+")
117      (head-end . "^ ?$")
118      (body-end-function . nndoc-digest-body-end)
119      (body-begin . "^ ?$")
120      (file-end . "^End of")
121      (prepare-body-function . nndoc-unquote-dashes)
122      (subtype digest guess))
123     (lanl-gov-announce
124      (article-begin . "^\\\\\\\\\n")
125      (head-begin . "^\\(Paper.*:\\|arXiv:\\)")
126      (head-end   . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
127      (body-begin . "")
128      (body-end   . "\\(-------------------------------------------------\\|%-%-%-%-%-%-%-%-%-%-%-%-%-%-\\|%%--%%--%%--%%--%%--%%--%%--%%--\\|%%%---%%%---%%%---%%%---\\)")
129      (file-end   . "\\(^Title: Recent Seminal\\|%%%---%%%---%%%---%%%---\\)")
130      (generate-head-function . nndoc-generate-lanl-gov-head)
131      (article-transform-function . nndoc-transform-lanl-gov-announce)
132      (subtype preprints guess))
133     (rfc822-forward
134      (article-begin . "^\n+")
135      (body-end-function . nndoc-rfc822-forward-body-end-function)
136      (generate-head-function . nndoc-rfc822-forward-generate-head)
137      (generate-article-function . nndoc-rfc822-forward-generate-article))
138     (outlook
139      (article-begin-function . nndoc-outlook-article-begin)
140      (body-end .  "\0"))
141     (oe-dbx  ;; Outlook Express DBX format
142      (dissection-function . nndoc-oe-dbx-dissection)
143      (generate-head-function . nndoc-oe-dbx-generate-head)
144      (generate-article-function . nndoc-oe-dbx-generate-article))
145     (forward
146      (article-begin . "^-+ \\(Start of \\)?forwarded message.*\n+")
147      (body-end . "^-+ End \\(of \\)?forwarded message.*$")
148      (prepare-body-function . nndoc-unquote-dashes))
149     (mail-in-mail ;; Wild guess on mailer daemon's messages or others
150      (article-begin-function . nndoc-mail-in-mail-article-begin))
151     (guess
152      (guess . t)
153      (subtype nil))
154     (digest
155      (guess . t)
156      (subtype nil))
157     (preprints
158      (guess . t)
159      (subtype nil))))
160
161 (defvar nndoc-binary-file-names ".[Dd][Bb][Xx]$"
162   "Regexp for binary nndoc file names.")
163
164 \f
165 (defvoo nndoc-file-begin nil)
166 (defvoo nndoc-first-article nil)
167 (defvoo nndoc-article-begin nil)
168 (defvoo nndoc-head-begin nil)
169 (defvoo nndoc-head-end nil)
170 (defvoo nndoc-file-end nil)
171 (defvoo nndoc-body-begin nil)
172 (defvoo nndoc-body-end-function nil)
173 (defvoo nndoc-body-begin-function nil)
174 (defvoo nndoc-head-begin-function nil)
175 (defvoo nndoc-body-end nil)
176 ;; nndoc-dissection-alist is a list of sublists.  Each sublist holds the
177 ;; following items.  ARTICLE acts as the association key and is an ordinal
178 ;; starting at 1.  HEAD-BEGIN [0], HEAD-END [1], BODY-BEGIN [2] and BODY-END
179 ;; [3] are positions in the `nndoc' buffer.  LINE-COUNT [4] is a count of
180 ;; lines in the body.  For MIME dissections only, ARTICLE-INSERT [5] and
181 ;; SUMMARY-INSERT [6] give headers to insert for full article or summary line
182 ;; generation, respectively.  Other headers usually follow directly from the
183 ;; buffer.  Value `nil' means no insert.
184 (defvoo nndoc-dissection-alist nil)
185 (defvoo nndoc-prepare-body-function nil)
186 (defvoo nndoc-generate-head-function nil)
187 (defvoo nndoc-article-transform-function nil)
188 (defvoo nndoc-article-begin-function nil)
189 (defvoo nndoc-generate-article-function nil)
190 (defvoo nndoc-dissection-function nil)
191
192 (defvoo nndoc-status-string "")
193 (defvoo nndoc-group-alist nil)
194 (defvoo nndoc-current-buffer nil
195   "Current nndoc news buffer.")
196 (defvoo nndoc-address nil)
197
198 (defconst nndoc-version "nndoc 1.0"
199   "nndoc version.")
200
201 \f
202
203 ;;; Interface functions
204
205 (nnoo-define-basics nndoc)
206
207 (deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
208   (when (nndoc-possibly-change-buffer newsgroup server)
209     (save-excursion
210       (set-buffer nntp-server-buffer)
211       (erase-buffer)
212       (let (article entry)
213         (if (stringp (car articles))
214             'headers
215           (while articles
216             (when (setq entry (cdr (assq (setq article (pop articles))
217                                          nndoc-dissection-alist)))
218               (insert (format "221 %d Article retrieved.\n" article))
219               (if nndoc-generate-head-function
220                   (funcall nndoc-generate-head-function article)
221                 (insert-buffer-substring
222                  nndoc-current-buffer (car entry) (nth 1 entry)))
223               (goto-char (point-max))
224               (unless (eq (char-after (1- (point))) ?\n)
225                 (insert "\n"))
226               (insert (format "Lines: %d\n" (nth 4 entry)))
227               (insert ".\n")))
228
229           (nnheader-fold-continuation-lines)
230           'headers)))))
231
232 (deffoo nndoc-request-article (article &optional newsgroup server buffer)
233   (nndoc-possibly-change-buffer newsgroup server)
234   (save-excursion
235     (let ((buffer (or buffer nntp-server-buffer))
236           (entry (cdr (assq article nndoc-dissection-alist)))
237           beg)
238       (set-buffer buffer)
239       (erase-buffer)
240       (when entry
241         (cond
242          ((stringp article) nil)
243          (nndoc-generate-article-function
244           (funcall nndoc-generate-article-function article))
245          (t
246           (insert-buffer-substring
247            nndoc-current-buffer (car entry) (nth 1 entry))
248           (insert "\n")
249           (setq beg (point))
250           (insert-buffer-substring
251            nndoc-current-buffer (nth 2 entry) (nth 3 entry))
252           (goto-char beg)
253           (when nndoc-prepare-body-function
254             (funcall nndoc-prepare-body-function))
255           (when nndoc-article-transform-function
256             (funcall nndoc-article-transform-function article))
257           t))))))
258
259 (deffoo nndoc-request-group (group &optional server dont-check)
260   "Select news GROUP."
261   (let (number)
262     (cond
263      ((not (nndoc-possibly-change-buffer group server))
264       (nnheader-report 'nndoc "No such file or buffer: %s"
265                        nndoc-address))
266      (dont-check
267       (nnheader-report 'nndoc "Selected group %s" group)
268       t)
269      ((zerop (setq number (length nndoc-dissection-alist)))
270       (nndoc-close-group group)
271       (nnheader-report 'nndoc "No articles in group %s" group))
272      (t
273       (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
274
275 (deffoo nndoc-request-type (group &optional article)
276   (cond ((not article) 'unknown)
277         (nndoc-post-type nndoc-post-type)
278         (t 'unknown)))
279
280 (deffoo nndoc-close-group (group &optional server)
281   (nndoc-possibly-change-buffer group server)
282   (and nndoc-current-buffer
283        (buffer-name nndoc-current-buffer)
284        (kill-buffer nndoc-current-buffer))
285   (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
286                                 nndoc-group-alist))
287   (setq nndoc-current-buffer nil)
288   (nnoo-close-server 'nndoc server)
289   (setq nndoc-dissection-alist nil)
290   t)
291
292 (deffoo nndoc-request-list (&optional server)
293   nil)
294
295 (deffoo nndoc-request-newgroups (date &optional server)
296   nil)
297
298 (deffoo nndoc-request-list-newsgroups (&optional server)
299   nil)
300
301 \f
302 ;;; Internal functions.
303
304 (defun nndoc-possibly-change-buffer (group source)
305   (let (buf)
306     (cond
307      ;; The current buffer is this group's buffer.
308      ((and nndoc-current-buffer
309            (buffer-name nndoc-current-buffer)
310            (eq nndoc-current-buffer
311                (setq buf (cdr (assoc group nndoc-group-alist))))))
312      ;; We change buffers by taking an old from the group alist.
313      ;; `source' is either a string (a file name) or a buffer object.
314      (buf
315       (setq nndoc-current-buffer buf))
316      ;; It's a totally new group.
317      ((or (and (bufferp nndoc-address)
318                (buffer-name nndoc-address))
319           (and (stringp nndoc-address)
320                (file-exists-p nndoc-address)
321                (not (file-directory-p nndoc-address))))
322       (push (cons group (setq nndoc-current-buffer
323                               (get-buffer-create
324                                (concat " *nndoc " group "*"))))
325             nndoc-group-alist)
326       (setq nndoc-dissection-alist nil)
327       (save-excursion
328         (set-buffer nndoc-current-buffer)
329         (erase-buffer)
330         (if (and (stringp nndoc-address)
331                  (string-match nndoc-binary-file-names nndoc-address))
332             (let ((coding-system-for-read 'binary))
333               (mm-insert-file-contents nndoc-address))
334           (if (stringp nndoc-address)
335               (nnheader-insert-file-contents nndoc-address)
336             (insert-buffer-substring nndoc-address))
337           (run-hooks 'nndoc-open-document-hook)))))
338     ;; Initialize the nndoc structures according to this new document.
339     (when (and nndoc-current-buffer
340                (not nndoc-dissection-alist))
341       (save-excursion
342         (set-buffer nndoc-current-buffer)
343         (nndoc-set-delims)
344         (if (eq nndoc-article-type 'mime-parts)
345             (nndoc-dissect-mime-parts)
346           (nndoc-dissect-buffer))))
347     (unless nndoc-current-buffer
348       (nndoc-close-server))
349     ;; Return whether we managed to select a file.
350     nndoc-current-buffer))
351
352 ;;;
353 ;;; Deciding what document type we have
354 ;;;
355
356 (defun nndoc-set-delims ()
357   "Set the nndoc delimiter variables according to the type of the document."
358   (let ((vars '(nndoc-file-begin
359                 nndoc-first-article
360                 nndoc-article-begin-function
361                 nndoc-head-begin nndoc-head-end
362                 nndoc-file-end nndoc-article-begin
363                 nndoc-body-begin nndoc-body-end-function nndoc-body-end
364                 nndoc-prepare-body-function nndoc-article-transform-function
365                 nndoc-generate-head-function nndoc-body-begin-function
366                 nndoc-head-begin-function
367                 nndoc-generate-article-function
368                 nndoc-dissection-function)))
369     (while vars
370       (set (pop vars) nil)))
371   (let (defs)
372     ;; Guess away until we find the real file type.
373     (while (assq 'guess (setq defs (cdr (assq nndoc-article-type
374                                               nndoc-type-alist))))
375       (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
376     ;; Set the nndoc variables.
377     (while defs
378       (set (intern (format "nndoc-%s" (caar defs)))
379            (cdr (pop defs))))))
380
381 (defun nndoc-guess-type (subtype)
382   (let ((alist nndoc-type-alist)
383         results result entry)
384     (while (and (not result)
385                 (setq entry (pop alist)))
386       (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
387         (goto-char (point-min))
388         ;; Remove blank lines.
389         (while (eq (following-char) ?\n)
390           (delete-char 1))
391         (when (numberp (setq result (funcall (intern
392                                               (format "nndoc-%s-type-p"
393                                                       (car entry))))))
394           (push (cons result entry) results)
395           (setq result nil))))
396     (unless (or result results)
397       (error "Document is not of any recognized type"))
398     (if result
399         (car entry)
400       (cadar (last (sort results 'car-less-than-car))))))
401
402 ;;;
403 ;;; Built-in type predicates and functions
404 ;;;
405
406 (defun nndoc-mbox-type-p ()
407   (when (looking-at message-unix-mail-delimiter)
408     t))
409
410 (defun nndoc-mbox-article-begin ()
411   (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
412     (goto-char (match-beginning 0))))
413
414 (defun nndoc-mbox-body-end ()
415   (let ((beg (point))
416         len end)
417     (when
418         (save-excursion
419           (and (re-search-backward
420                 (concat "^" message-unix-mail-delimiter) nil t)
421                (setq end (point))
422                (search-forward "\n\n" beg t)
423                (re-search-backward
424                 "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
425                (setq len (string-to-number (match-string 1)))
426                (search-forward "\n\n" beg t)
427                (unless (= (setq len (+ (point) len)) (point-max))
428                  (and (< len (point-max))
429                       (goto-char len)
430                       (looking-at message-unix-mail-delimiter)))))
431       (goto-char len))))
432
433 (defun nndoc-mmdf-type-p ()
434   (when (looking-at "\^A\^A\^A\^A$")
435     t))
436
437 (defun nndoc-news-type-p ()
438   (when (looking-at "^Path:.*\n")
439     t))
440
441 (defun nndoc-rnews-type-p ()
442   (when (looking-at "#! *rnews")
443     t))
444
445 (defun nndoc-rnews-body-end ()
446   (and (re-search-backward nndoc-article-begin nil t)
447        (forward-line 1)
448        (goto-char (+ (point) (string-to-number (match-string 1))))))
449
450 (defun nndoc-babyl-type-p ()
451   (when (re-search-forward "\^_\^L *\n" nil t)
452     t))
453
454 (defun nndoc-babyl-body-begin ()
455   (re-search-forward "^\n" nil t)
456   (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
457     (let ((next (or (save-excursion
458                       (re-search-forward nndoc-article-begin nil t))
459                     (point-max))))
460       (unless (re-search-forward "^\n" next t)
461         (goto-char next)
462         (forward-line -1)
463         (insert "\n")
464         (forward-line -1)))))
465
466 (defun nndoc-babyl-head-begin ()
467   (when (re-search-forward "^[0-9].*\n" nil t)
468     (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
469       (forward-line 1))
470     t))
471
472 (defun nndoc-forward-type-p ()
473   (when (and (re-search-forward "^-+ \\(Start of \\)?forwarded message.*\n+"
474                                 nil t)
475              (looking-at "[\r\n]*[a-zA-Z][a-zA-Z0-9-]*:\\|^>?From "))
476     t))
477
478 (defun nndoc-rfc934-type-p ()
479   (when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
480              (not (re-search-forward "^Subject:.*digest" nil t))
481              (not (re-search-backward "^From:" nil t 2))
482              (not (re-search-forward "^From:" nil t 2)))
483     t))
484
485 (defun nndoc-mailman-type-p ()
486   (when (re-search-forward "^--__--__--\n+" nil t)
487     t))
488
489 (defun nndoc-rfc822-forward-type-p ()
490   (save-restriction
491     (message-narrow-to-head)
492     (when (re-search-forward "^Content-Type: *message/rfc822" nil t)
493       t)))
494
495 (defun nndoc-rfc822-forward-body-end-function ()
496   (goto-char (point-max)))
497
498 (defun nndoc-rfc822-forward-generate-article (article &optional head)
499   (let ((entry (cdr (assq article nndoc-dissection-alist)))
500         (begin (point))
501         encoding)
502     (with-current-buffer nndoc-current-buffer
503       (save-restriction
504         (message-narrow-to-head)
505         (setq encoding (message-fetch-field "content-transfer-encoding"))))
506     (insert-buffer-substring nndoc-current-buffer (car entry) (nth 3 entry))
507     (when encoding
508       (save-restriction
509         (narrow-to-region begin (point-max))
510         (mm-decode-content-transfer-encoding
511          (intern (downcase (mail-header-strip encoding))))))
512     (when head
513       (goto-char begin)
514       (when (search-forward "\n\n" nil t)
515         (delete-region (1- (point)) (point-max)))))
516   t)
517
518 (defun nndoc-rfc822-forward-generate-head (article)
519   (nndoc-rfc822-forward-generate-article article 'head))
520
521 (defun nndoc-mime-parts-type-p ()
522   (let ((case-fold-search t)
523         (limit (search-forward "\n\n" nil t)))
524     (goto-char (point-min))
525     (when (and limit
526                (re-search-forward
527                 (concat "\
528 ^Content-Type:[ \t]*multipart/[a-z]+ *; *\\(\\(\n[ \t]\\)?.*;\\)*"
529                         "\\(\n[ \t]\\)?[ \t]*boundary=\"?[^\"\n]*[^\" \t\n]")
530                 limit t))
531       t)))
532
533 (defun nndoc-transform-mime-parts (article)
534   (let* ((entry (cdr (assq article nndoc-dissection-alist)))
535          (headers (nth 5 entry)))
536     (when headers
537       (goto-char (point-min))
538       (insert headers))))
539
540 (defun nndoc-generate-mime-parts-head (article)
541   (let* ((entry (cdr (assq article nndoc-dissection-alist)))
542          (headers (nth 6 entry)))
543     (save-restriction
544       (narrow-to-region (point) (point))
545       (insert-buffer-substring
546        nndoc-current-buffer (car entry) (nth 1 entry))
547       (goto-char (point-max)))
548     (when headers
549       (insert headers))))
550
551 (defun nndoc-clari-briefs-type-p ()
552   (when (let ((case-fold-search nil))
553           (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
554     t))
555
556 (defun nndoc-transform-clari-briefs (article)
557   (goto-char (point-min))
558   (when (looking-at " *\\*\\(.*\\)\n")
559     (replace-match "" t t))
560   (nndoc-generate-clari-briefs-head article))
561
562 (defun nndoc-generate-clari-briefs-head (article)
563   (let ((entry (cdr (assq article nndoc-dissection-alist)))
564         subject from)
565     (save-excursion
566       (set-buffer nndoc-current-buffer)
567       (save-restriction
568         (narrow-to-region (car entry) (nth 3 entry))
569         (goto-char (point-min))
570         (when (looking-at " *\\*\\(.*\\)$")
571           (setq subject (match-string 1))
572           (when (string-match "[ \t]+$" subject)
573             (setq subject (substring subject 0 (match-beginning 0)))))
574         (when
575             (let ((case-fold-search nil))
576               (re-search-forward
577                "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
578           (setq from (match-string 1)))))
579     (insert "From: " "clari@clari.net (" (or from "unknown") ")"
580             "\nSubject: " (or subject "(no subject)") "\n")))
581
582 (defun nndoc-exim-bounce-type-p ()
583   (and (re-search-forward "^------ This is a copy of the message, including all the headers. ------" nil t)
584        t))
585
586 (defun nndoc-exim-bounce-body-end-function ()
587   (goto-char (point-max)))
588
589
590 (defun nndoc-mime-digest-type-p ()
591   (let ((case-fold-search t)
592         boundary-id b-delimiter entry)
593     (when (and
594            (re-search-forward
595             (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
596                     "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)")
597             nil t)
598            (match-beginning 1))
599       (setq boundary-id (match-string 1)
600             b-delimiter (concat "\n--" boundary-id "[ \t]*$"))
601       (setq entry (assq 'mime-digest nndoc-type-alist))
602       (setcdr entry
603               (list
604                (cons 'head-begin "^ ?\n")
605                (cons 'head-end "^ ?$")
606                (cons 'body-begin "^ ?\n")
607                (cons 'article-begin b-delimiter)
608                (cons 'body-end-function 'nndoc-digest-body-end)
609                (cons 'file-end (concat "^--" boundary-id "--[ \t]*$"))))
610       t)))
611
612 (defun nndoc-standard-digest-type-p ()
613   (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
614              (re-search-forward
615               (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
616     t))
617
618 (defun nndoc-digest-body-end ()
619   (and (re-search-forward nndoc-article-begin nil t)
620        (goto-char (match-beginning 0))))
621
622 (defun nndoc-slack-digest-type-p ()
623   0)
624
625 (defun nndoc-lanl-gov-announce-type-p ()
626   (when (let ((case-fold-search nil))
627           (re-search-forward "^\\\\\\\\\n\\(Paper\\( (\\*cross-listing\\*)\\)?: [a-zA-Z-\\.]+/[0-9]+\\|arXiv:\\)" nil t))
628     t))
629
630 (defun nndoc-transform-lanl-gov-announce (article)
631   (let ((case-fold-search nil))
632     (goto-char (point-max))
633     (when (re-search-backward "^\\\\\\\\ +( *\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
634       (replace-match "\n\nGet it at \\1 (\\2)" t nil))
635     (goto-char (point-min))
636     (while (re-search-forward "^\\\\\\\\$" nil t)
637       (replace-match "" t nil))
638     (goto-char (point-min))
639     (when (re-search-forward "^replaced with revised version +\\(.*[^ ]\\) +" nil t)
640       (replace-match "Date: \\1 (revised) " t nil))
641     (goto-char (point-min))
642     (unless (re-search-forward "^From" nil t)
643       (goto-char (point-min))
644       (when (re-search-forward "^Authors?: \\(.*\\)" nil t)
645         (goto-char (point-min))
646         (insert "From: " (match-string 1) "\n")))
647     (when (re-search-forward "^arXiv:" nil t)
648       (replace-match "Paper: arXiv:" t nil))))
649
650 (defun nndoc-generate-lanl-gov-head (article)
651   (let ((entry (cdr (assq article nndoc-dissection-alist)))
652         (from "<no address given>")
653         subject date)
654     (save-excursion
655       (set-buffer nndoc-current-buffer)
656       (save-restriction
657         (narrow-to-region (car entry) (nth 1 entry))
658         (goto-char (point-min))
659         (when (looking-at "^\\(Paper.*: \\|arXiv:\\)\\([0-9a-zA-Z-\\./]+\\)")
660           (setq subject (concat " (" (match-string 2) ")"))
661           (when (re-search-forward "^From: \\(.*\\)" nil t)
662             (setq from (concat "<"
663                                (cadr (funcall gnus-extract-address-components
664                                               (match-string 1))) ">")))
665           (if (re-search-forward "^Date: +\\([^(]*\\)" nil t)
666               (setq date (match-string 1))
667             (when (re-search-forward "^replaced with revised version +\\([^(]*\\)" nil t)
668               (setq date (match-string 1))))
669           (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
670                                    nil t)
671             (setq subject (concat (match-string 1) subject))
672             (setq from (concat (match-string 2) " " from))))))
673     (while (and from (string-match "(\[^)\]*)" from))
674       (setq from (replace-match "" t t from)))
675     (insert "From: "  (or from "unknown")
676             "\nSubject: " (or subject "(no subject)") "\n")
677     (if date (insert "Date: " date))))
678
679 (defun nndoc-nsmail-type-p ()
680   (when (looking-at "From - ")
681     t))
682
683 (defun nndoc-outlook-article-begin ()
684   (prog1 (re-search-forward "From:\\|Received:" nil t)
685     (goto-char (match-beginning 0))))
686
687 (defun nndoc-outlook-type-p ()
688   ;; FIXME: Is JMF the magic of outlook mailbox? -- ShengHuo.
689   (looking-at "JMF"))
690
691 (defun nndoc-oe-dbx-type-p ()
692   (looking-at (mm-string-as-multibyte "\317\255\022\376")))
693
694 (defun nndoc-read-little-endian ()
695   (+ (prog1 (char-after) (forward-char 1))
696      (lsh (prog1 (char-after) (forward-char 1)) 8)
697      (lsh (prog1 (char-after) (forward-char 1)) 16)
698      (lsh (prog1 (char-after) (forward-char 1))&nbs