(mm-uu-diff-groups-regexp): Fix missing quotes from previous
[gnus] / lisp / mm-uu.el
1 ;;; mm-uu.el --- Return uu stuff as mm handles
2
3 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 ;;   2005 Free Software Foundation, Inc.
5
6 ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu>
7 ;; Keywords: postscript uudecode binhex shar forward gnatsweb pgp
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., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 (eval-when-compile (require 'cl))
31 (require 'mail-parse)
32 (require 'nnheader)
33 (require 'mm-decode)
34 (require 'mailcap)
35 (require 'mml2015)
36
37 (autoload 'uudecode-decode-region "uudecode")
38 (autoload 'uudecode-decode-region-external "uudecode")
39 (autoload 'uudecode-decode-region-internal "uudecode")
40
41 (autoload 'binhex-decode-region "binhex")
42 (autoload 'binhex-decode-region-external "binhex")
43 (autoload 'binhex-decode-region-internal "binhex")
44
45 (autoload 'yenc-decode-region "yenc")
46 (autoload 'yenc-extract-filename "yenc")
47
48 (defcustom mm-uu-decode-function 'uudecode-decode-region
49   "*Function to uudecode.
50 Internal function is done in Lisp by default, therefore decoding may
51 appear to be horribly slow.  You can make Gnus use an external
52 decoder, such as uudecode."
53   :type '(choice
54           (function-item :tag "Auto detect" uudecode-decode-region)
55           (function-item :tag "Internal" uudecode-decode-region-internal)
56           (function-item :tag "External" uudecode-decode-region-external))
57   :group 'gnus-article-mime)
58
59 (defcustom mm-uu-binhex-decode-function 'binhex-decode-region
60   "*Function to binhex decode.
61 Internal function is done in elisp by default, therefore decoding may
62 appear to be horribly slow . You can make Gnus use the external Unix
63 decoder, such as hexbin."
64   :type '(choice (function-item :tag "Auto detect" binhex-decode-region)
65                  (function-item :tag "Internal" binhex-decode-region-internal)
66                  (function-item :tag "External" binhex-decode-region-external))
67   :group 'gnus-article-mime)
68
69 (defvar mm-uu-yenc-decode-function 'yenc-decode-region)
70
71 (defvar mm-uu-pgp-beginning-signature
72      "^-----BEGIN PGP SIGNATURE-----")
73
74 (defvar mm-uu-beginning-regexp nil)
75
76 (defvar mm-dissect-disposition "inline"
77   "The default disposition of uu parts.
78 This can be either \"inline\" or \"attachment\".")
79
80 (defcustom mm-uu-emacs-sources-regexp "\\.emacs\\.sources"
81   "The regexp of Emacs sources groups."
82   :version "22.1"
83   :type 'regexp
84   :group 'gnus-article-mime)
85
86 (defcustom mm-uu-diff-groups-regexp
87   "\\(gmane\\|gnu\\)\\..*\\(diff\\|commit\\|cvs\\|bug\\|devel\\)"
88   "Regexp matching diff groups."
89   :version "22.1"
90   :type 'regexp
91   :group 'gnus-article-mime)
92
93 (defcustom mm-uu-tex-groups-regexp "\\.tex\\>"
94   "*Regexp matching TeX groups."
95   :version "23.0"
96   :type 'regexp
97   :group 'gnus-article-mime)
98
99 (defvar mm-uu-type-alist
100   '((postscript
101      "^%!PS-"
102      "^%%EOF$"
103      mm-uu-postscript-extract
104      nil)
105     (uu
106      "^begin[ \t]+0?[0-7][0-7][0-7][ \t]+"
107      "^end[ \t]*$"
108      mm-uu-uu-extract
109      mm-uu-uu-filename)
110     (binhex
111      "^:...............................................................$"
112      ":$"
113      mm-uu-binhex-extract
114      nil
115      mm-uu-binhex-filename)
116     (yenc
117      "^=ybegin.*size=[0-9]+.*name=.*$"
118      "^=yend.*size=[0-9]+"
119      mm-uu-yenc-extract
120      mm-uu-yenc-filename)
121     (shar
122      "^#! */bin/sh"
123      "^exit 0$"
124      mm-uu-shar-extract)
125     (forward
126      ;; Thanks to Edward J. Sabol <sabol@alderaan.gsfc.nasa.gov> and
127      ;; Peter von der Ah\'e <pahe@daimi.au.dk>
128      "^-+ \\(Start of \\)?Forwarded message"
129      "^-+ End \\(of \\)?forwarded message"
130      mm-uu-forward-extract
131      nil
132      mm-uu-forward-test)
133     (gnatsweb
134      "^----gnatsweb-attachment----"
135      nil
136      mm-uu-gnatsweb-extract)
137     (pgp-signed
138      "^-----BEGIN PGP SIGNED MESSAGE-----"
139      "^-----END PGP SIGNATURE-----"
140      mm-uu-pgp-signed-extract
141      nil
142      nil)
143     (pgp-encrypted
144      "^-----BEGIN PGP MESSAGE-----"
145      "^-----END PGP MESSAGE-----"
146      mm-uu-pgp-encrypted-extract
147      nil
148      nil)
149     (pgp-key
150      "^-----BEGIN PGP PUBLIC KEY BLOCK-----"
151      "^-----END PGP PUBLIC KEY BLOCK-----"
152      mm-uu-pgp-key-extract
153      mm-uu-gpg-key-skip-to-last
154      nil)
155     (emacs-sources
156      "^;;;?[ \t]*[^ \t]+\\.el[ \t]*--"
157      "^;;;?[ \t]*\\([^ \t]+\\.el\\)[ \t]+ends here"
158      mm-uu-emacs-sources-extract
159      nil
160      mm-uu-emacs-sources-test)
161     (diff
162      "^Index: "
163      nil
164      mm-uu-diff-extract
165      nil
166      mm-uu-diff-test)
167     (verbatim-marks
168      ;; slrn-style verbatim marks, see
169      ;; http://www.slrn.org/manual/slrn-manual-6.html#ss6.81
170      "^#v\\+$"
171      "^#v\\-$"
172      mm-uu-verbatim-marks-extract
173      nil)
174     (LaTeX
175      "^\\\\documentclass"
176      "^\\\\end{document}"
177      mm-uu-latex-extract
178      nil
179      mm-uu-latex-test))
180   "A list of specifications for non-MIME attachments.
181 Each element consist of the following entries: label,
182 start-regexp, end-regexp, extract-function, test-function.
183
184 After modifying this list you must run \\[mm-uu-configure].")
185
186 (defcustom mm-uu-configure-list '((shar . disabled))
187   "A list of mm-uu configuration.
188 To disable dissecting shar codes, for instance, add
189 `(shar . disabled)' to this list."
190   :type 'alist
191   :options (mapcar (lambda (entry)
192                      (list (car entry) '(const disabled)))
193                    mm-uu-type-alist)
194   :group 'gnus-article-mime)
195
196 (defvar mm-uu-text-plain-type '("text/plain" (charset . gnus-decoded))
197   "MIME type and parameters for text/plain parts.
198 `gnus-decoded' is a fake charset, which means no further decoding.")
199
200 ;; functions
201
202 (defsubst mm-uu-type (entry)
203   (car entry))
204
205 (defsubst mm-uu-beginning-regexp (entry)
206   (nth 1 entry))
207
208 (defsubst mm-uu-end-regexp (entry)
209   (nth 2 entry))
210
211 (defsubst mm-uu-function-extract (entry)
212   (nth 3 entry))
213
214 (defsubst mm-uu-function-1 (entry)
215   (nth 4 entry))
216
217 (defsubst mm-uu-function-2 (entry)
218   (nth 5 entry))
219
220 (defface mm-uu-extract
221   '((((class color)
222       (background dark))
223      (:background "gray5"))
224     (((class color)
225       (background light))
226      (:background "gray95"))
227     (t
228      ()))
229   "Face for extracted buffers."
230   ;; See `mm-uu-verbatim-marks-extract'.
231   :version "23.0" ;; No Gnus
232   :group 'gnus-article-mime)
233
234 (defun mm-uu-copy-to-buffer (&optional from to properties)
235   "Copy the contents of the current buffer to a fresh buffer.
236 Return that buffer.
237
238 If PROPERTIES is non-nil, PROPERTIES are applied to the buffer,
239 see `set-text-properties'.  If PROPERTIES equals t, this means to
240 apply the face `mm-uu-extract'."
241   (let ((obuf (current-buffer))
242         (coding-system
243          ;; Might not exist in non-MULE XEmacs
244          (when (boundp 'buffer-file-coding-system)
245            buffer-file-coding-system)))
246     (with-current-buffer (generate-new-buffer " *mm-uu*")
247       (setq buffer-file-coding-system coding-system)
248       (insert-buffer-substring obuf from to)
249       (cond ((eq properties  t)
250              (set-text-properties (point-min) (point-max)
251                                   '(face mm-uu-extract)))
252             (properties
253              (set-text-properties (point-min) (point-max) properties)))
254       (current-buffer))))
255
256 (defun mm-uu-configure-p  (key val)
257   (member (cons key val) mm-uu-configure-list))
258
259 (defun mm-uu-configure (&optional symbol value)
260   "Configure detection of non-MIME attachments."
261   (interactive)
262   (if symbol (set-default symbol value))
263   (setq mm-uu-beginning-regexp nil)
264   (mapcar (lambda (entry)
265              (if (mm-uu-configure-p (mm-uu-type entry) 'disabled)
266                  nil
267                (setq mm-uu-beginning-regexp
268                      (concat mm-uu-beginning-regexp
269                              (if mm-uu-beginning-regexp "\\|")
270                              (mm-uu-beginning-regexp entry)))))
271           mm-uu-type-alist))
272
273 (mm-uu-configure)
274
275 (eval-when-compile
276   (defvar file-name)
277   (defvar start-point)
278   (defvar end-point)
279   (defvar entry))
280
281 (defun mm-uu-uu-filename ()
282   (if (looking-at ".+")
283       (setq file-name
284             (let ((nnheader-file-name-translation-alist
285                    '((?/ . ?,) (?\  . ?_) (?* . ?_) (?$ . ?_))))
286               (nnheader-translate-file-chars (match-string 0))))))
287
288 (defun mm-uu-binhex-filename ()
289   (setq file-name
290         (ignore-errors
291           (binhex-decode-region start-point end-point t))))
292
293 (defun mm-uu-yenc-filename ()
294   (goto-char start-point)
295   (setq file-name
296         (ignore-errors
297           (yenc-extract-filename))))
298
299 (defun mm-uu-forward-test ()
300   (save-excursion
301     (goto-char start-point)
302     (forward-line)
303     (looking-at "[\r\n]*[a-zA-Z][a-zA-Z0-9-]*:")))
304
305 (defun mm-uu-postscript-extract ()
306   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
307                   '("application/postscript")))
308
309 (defun mm-uu-verbatim-marks-extract ()
310   (mm-make-handle
311    (mm-uu-copy-to-buffer
312     (progn (goto-char start-point) (forward-line) (point))
313     (progn (goto-char end-point) (forward-line -1) (point))
314     t)
315    '("text/x-gnus-verbatim" (charset . gnus-decoded))))
316
317 (defun mm-uu-latex-extract ()
318   (mm-make-handle
319    (mm-uu-copy-to-buffer start-point end-point t)
320    ;; application/x-tex?
321    '("text/x-gnus-verbatim" (charset . gnus-decoded))))
322
323 (defun mm-uu-emacs-sources-extract ()
324   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
325                   '("application/emacs-lisp")
326                   nil nil
327                   (list mm-dissect-disposition
328                         (cons 'filename file-name))))
329
330 (eval-when-compile
331   (defvar gnus-newsgroup-name))
332
333 (defun mm-uu-emacs-sources-test ()
334   (setq file-name (match-string 1))
335   (and gnus-newsgroup-name
336        mm-uu-emacs-sources-regexp
337        (string-match mm-uu-emacs-sources-regexp gnus-newsgroup-name)))
338
339 (defun mm-uu-diff-extract ()
340   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
341                   '("text/x-patch")))
342
343 (defun mm-uu-diff-test ()
344   (and gnus-newsgroup-name
345        mm-uu-diff-groups-regexp
346        (string-match mm-uu-diff-groups-regexp gnus-newsgroup-name)))
347
348 (defun mm-uu-latex-test ()
349   (and gnus-newsgroup-name
350        mm-uu-tex-groups-regexp
351        (string-match mm-uu-tex-groups-regexp gnus-newsgroup-name)))
352
353 (defun mm-uu-forward-extract ()
354   (mm-make-handle (mm-uu-copy-to-buffer
355                    (progn (goto-char start-point) (forward-line) (point))
356                    (progn (goto-char end-point) (forward-line -1) (point)))
357                   '("message/rfc822" (charset . gnus-decoded))))
358
359 (defun mm-uu-uu-extract ()
360   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
361                   (list (or (and file-name
362                                  (string-match "\\.[^\\.]+$"
363                                                file-name)
364                                  (mailcap-extension-to-mime
365                                   (match-string 0 file-name)))
366                             "application/octet-stream"))
367                   'x-uuencode nil
368                   (if (and file-name (not (equal file-name "")))
369                       (list mm-dissect-disposition
370                             (cons 'filename file-name)))))
371
372 (defun mm-uu-binhex-extract ()
373   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
374                   (list (or (and file-name
375                                  (string-match "\\.[^\\.]+$" file-name)
376                                  (mailcap-extension-to-mime
377                                   (match-string 0 file-name)))
378                             "application/octet-stream"))
379                   'x-binhex nil
380                   (if (and file-name (not (equal file-name "")))
381                       (list mm-dissect-disposition
382                             (cons 'filename file-name)))))
383
384 (defun mm-uu-yenc-extract ()
385   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
386                   (list (or (and file-name
387                                  (string-match "\\.[^\\.]+$" file-name)
388                                  (mailcap-extension-to-mime
389                                   (match-string 0 file-name)))
390                             "application/octet-stream"))
391                   'x-yenc nil
392                   (if (and file-name (not (equal file-name "")))
393                       (list mm-dissect-disposition
394                             (cons 'filename file-name)))))
395
396
397 (defun mm-uu-shar-extract ()
398   (mm-make-handle (mm-uu-copy-to-buffer start-point end-point)
399                   '("application/x-shar")))
400
401 (defun mm-uu-gnatsweb-extract ()
402   (save-restriction
403     (goto-char start-point)
404     (forward-line)
405     (narrow-to-region (point) end-point)
406     (mm-dissect-buffer t)))
407
408 (defun mm-uu-pgp-signed-test (&rest rest)
409   (and
410    mml2015-use
411    (mml2015-clear-verify-function)
412    (cond
413     ((eq mm-verify-option 'never) nil)
414     ((eq mm-verify-option 'always) t)
415     ((eq mm-verify-option 'known) t)
416     (t (y-or-n-p "Verify pgp signed part? ")))))
417
418 (eval-when-compile
419   (defvar gnus-newsgroup-charset))
420
421 (defun mm-uu-pgp-signed-extract-1 (handles ctl)
422   (let ((buf (mm-uu-copy-to-buffer (point-min) (point-max))))
423     (with-current-buffer buf
424       (if (mm-uu-pgp-signed-test)
425           (progn
426             (mml2015-clean-buffer)
427             (let ((coding-system-for-write (or gnus-newsgroup-charset
428                                                'iso-8859-1)))
429               (funcall (mml2015-clear-verify-function))))
430         (when (and mml2015-use (null (mml2015-clear-verify-function)))
431           (mm-set-handle-multipart-parameter
432            mm-security-handle 'gnus-details
433            (format "Clear verification not supported by `%s'.\n" mml2015-use))))
434       (goto-char (point-min))
435       (if (search-forward "\n\n" nil t)
436           (delete-region (point-min) (point)))
437       (if (re-search-forward mm-uu-pgp-beginning-signature nil t)
438           (delete-region (match-beginning 0) (point-max)))
439       (goto-char (point-min))
440       (while (re-search-forward "^- " nil t)
441         (replace-match "" t t)
442         (forward-line 1)))
443     (list (mm-make-handle buf mm-uu-text-plain-type))))
444
445 (defun mm-uu-pgp-signed-extract ()
446   (let ((mm-security-handle (list (format "multipart/signed"))))
447     (mm-set-handle-multipart-parameter
448      mm-security-handle 'protocol "application/x-gnus-pgp-signature")
449     (save-restriction
450       (narrow-to-region start-point end-point)
451       (add-text-properties 0 (length (car mm-security-handle))
452                            (list 'buffer (mm-uu-copy-to-buffer))
453                            (car mm-security-handle))
454       (setcdr mm-security-handle
455               (mm-uu-pgp-signed-extract-1 nil
456                                           mm-security-handle)))
457     mm-security-handle))
458
459 (defun mm-uu-pgp-encrypted-test (&rest rest)
460   (and
461    mml2015-use
462    (mml2015-clear-decrypt-function)
463    (cond
464     ((eq mm-decrypt-option 'never) nil)
465     ((eq mm-decrypt-option 'always) t)
466     ((eq mm-decrypt-option 'known) t)
467     (t (y-or-n-p "Decrypt pgp encrypted part? ")))))
468
469 (defun mm-uu-pgp-encrypted-extract-1 (handles ctl)
470   (let ((buf (mm-uu-copy-to-buffer (point-min) (point-max))))
471     (if (mm-uu-pgp-encrypted-test)
472         (with-current-buffer buf
473           (mml2015-clean-buffer)
474           (funcall (mml2015-clear-decrypt-function))))
475     (list (mm-make-handle buf mm-uu-text-plain-type))))
476
477 (defun mm-uu-pgp-encrypted-extract ()
478   (let ((mm-security-handle (list (format "multipart/encrypted"))))
479     (mm-set-handle-multipart-parameter
480      mm-security-handle 'protocol "application/x-gnus-pgp-encrypted")
481     (save-restriction
482       (narrow-to-region start-point end-point)
483       (add-text-properties 0 (length (car mm-security-handle))
484                            (list 'buffer (mm-uu-copy-to-buffer))
485                            (car mm-security-handle))
486       (setcdr mm-security-handle
487               (mm-uu-pgp-encrypted-extract-1 nil
488                                              mm-security-handle)))
489     mm-security-handle))
490
491 (defun mm-uu-gpg-key-skip-to-last ()
492   (let ((point (point))
493         (end-regexp (mm-uu-end-regexp entry))
494         (beginning-regexp (mm-uu-beginning-regexp entry)))
495     (when (and end-regexp
496                (not (mm-uu-configure-p (mm-uu-type entry) 'disabled)))
497       (while (re-search-forward end-regexp nil t)
498         (skip-chars-forward " \t\n\r")
499         (if (looking-at beginning-regexp)
500             (setq point (match-end 0)))))
501     (goto-char point)))
502
503 (defun mm-uu-pgp-key-extract ()
504   (let ((buf (mm-uu-copy-to-buffer start-point end-point)))
505     (mm-make-handle buf
506                     '("application/pgp-keys"))))
507
508 ;;;###autoload
509 (defun mm-uu-dissect (&optional noheader mime-type)
510   "Dissect the current buffer and return a list of uu handles.
511 The optional NOHEADER means there's no header in the buffer.
512 MIME-TYPE specifies a MIME type and parameters, which defaults to the
513 value of `mm-uu-text-plain-type'."
514   (let ((case-fold-search t)
515         (mm-uu-text-plain-type (or mime-type mm-uu-text-plain-type))
516         text-start start-point end-point file-name result entry func)
517     (save-excursion
518       (goto-char (point-min))
519       (cond
520        (noheader)
521        ((looking-at "\n")
522         (forward-line))
523        ((search-forward "\n\n" nil t)
524         t)
525        (t (goto-char (point-max))))
526       (setq text-start (point))
527       (while (re-search-forward mm-uu-beginning-regexp nil t)
528         (setq start-point (match-beginning 0))
529         (let ((alist mm-uu-type-alist)
530               (beginning-regexp (match-string 0)))
531           (while (not entry)
532             (if (string-match (mm-uu-beginning-regexp (car alist))
533                               beginning-regexp)
534                 (setq entry (car alist))
535               (pop alist))))
536         (if (setq func (mm-uu-function-1 entry))
537             (funcall func))
538         (forward-line);; in case of failure
539         (when (and (not (mm-uu-configure-p (mm-uu-type entry) 'disabled))
540                    (let ((end-regexp (mm-uu-end-regexp entry)))
541                      (if (not end-regexp)
542                          (or (setq end-point (point-max)) t)
543                        (prog1
544                            (re-search-forward end-regexp nil t)
545                          (forward-line)
546                          (setq end-point (point)))))
547                    (or (not (setq func (mm-uu-function-2 entry)))
548                        (funcall func)))
549           (if (and (> start-point text-start)
550                    (progn
551                      (goto-char text-start)
552                      (re-search-forward "." start-point t)))
553               (push
554                (mm-make-handle (mm-uu-copy-to-buffer text-start start-point)
555                                mm-uu-text-plain-type)
556                result))
557           (push
558            (funcall (mm-uu-function-extract entry))
559            result)
560           (goto-char (setq text-start end-point))))
561       (when result
562         (if (and (> (point-max) (1+ text-start))
563                  (save-excursion
564                    (goto-char text-start)
565                    (re-search-forward "." nil t)))
566             (push
567              (mm-make-handle (mm-uu-copy-to-buffer text-start (point-max))
568                              mm-uu-text-plain-type)
569              result))
570         (setq result (cons "multipart/mixed" (nreverse result))))
571       result)))
572
573 (defun mm-uu-dissect-text-parts (handle)
574   "Dissect text parts and put uu handles into HANDLE."
575   (let ((buffer (mm-handle-buffer handle))
576         type children)
577     (cond ((stringp buffer)
578            (mapc 'mm-uu-dissect-text-parts (cdr handle)))
579           ((bufferp buffer)
580            (when (and (setq type (mm-handle-media-type handle))
581                       (stringp type)
582                       (string-match "\\`text/" type)
583                       (with-current-buffer buffer
584                         (setq children
585                               (mm-uu-dissect t (mm-handle-type handle)))))
586              (kill-buffer buffer)
587              (setcar handle (car children))
588              (setcdr handle (cdr children))))
589           (t
590            (mapc 'mm-uu-dissect-text-parts handle)))))
591
592 (provide 'mm-uu)
593
594 ;; arch-tag: 7db076bf-53db-4320-aa19-ca76a1d2ab2c
595 ;;; mm-uu.el ends here