8f8c2dc1210c29a5932d5fe28f347913f070b0ce
[elisp/gnus.git-] / lisp / gnus-uu.el
1 ;;; gnus-uu.el --- extract (uu)encoded files in Gnus
2 ;; Copyright (C) 1985, 1986, 1987, 1993, 1994, 1995, 1996, 1997, 1998, 2000,
3 ;;        2001, 2002 Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Created: 2 Oct 1993
7 ;; Keyword: 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 ;;; Code:
29
30 (eval-when-compile (require 'cl))
31
32 (require 'gnus)
33 (require 'gnus-art)
34 (require 'message)
35 (require 'gnus-msg)
36
37 (defgroup gnus-extract nil
38   "Extracting encoded files."
39   :prefix "gnus-uu-"
40   :group 'gnus)
41
42 (defgroup gnus-extract-view nil
43   "Viewwing extracted files."
44   :group 'gnus-extract)
45
46 (defgroup gnus-extract-archive nil
47   "Extracting encoded archives."
48   :group 'gnus-extract)
49
50 (defgroup gnus-extract-post nil
51   "Extracting encoded archives."
52   :prefix "gnus-uu-post"
53   :group 'gnus-extract)
54
55 ;; Default viewing action rules
56
57 (defcustom gnus-uu-default-view-rules
58   '(("\\.te?xt$\\|\\.doc$\\|read.*me\\|\\.c?$\\|\\.h$\\|\\.bat$\\|\\.asm$\\|makefile" "cat %s | sed 's/\r$//'")
59     ("\\.pas$" "cat %s | sed 's/\r$//'")
60     ("\\.[1-9]$" "groff -mandoc -Tascii %s | sed s/\b.//g")
61     ("\\.\\(jpe?g\\|gif\\|tiff?\\|p[pgb]m\\|xwd\\|xbm\\|pcx\\)$" "display")
62     ("\\.tga$" "tgatoppm %s | ee -")
63     ("\\.\\(wav\\|aiff\\|hcom\\|u[blw]\\|s[bfw]\\|voc\\|smp\\)$"
64      "sox -v .5 %s -t .au -u - > /dev/audio")
65     ("\\.au$" "cat %s > /dev/audio")
66     ("\\.midi?$" "playmidi -f")
67     ("\\.mod$" "str32")
68     ("\\.ps$" "ghostview")
69     ("\\.dvi$" "xdvi")
70     ("\\.html$" "xmosaic")
71     ("\\.mpe?g$" "mpeg_play")
72     ("\\.\\(flc\\|fli\\|rle\\|iff\\|pfx\\|avi\\|sme\\|rpza\\|dl\\|qt\\|rsrc\\|mov\\)$" "xanim")
73     ("\\.\\(tar\\|arj\\|zip\\|zoo\\|arc\\|gz\\|Z\\|lzh\\|ar\\|lha\\)$"
74      "gnus-uu-archive"))
75   "*Default actions to be taken when the user asks to view a file.
76 To change the behaviour, you can either edit this variable or set
77 `gnus-uu-user-view-rules' to something useful.
78
79 For example:
80
81 To make gnus-uu use 'xli' to display JPEG and GIF files, put the
82 following in your .emacs file:
83
84   (setq gnus-uu-user-view-rules '((\"jpg$\\\\|gif$\" \"xli\")))
85
86 Both these variables are lists of lists with two string elements.  The
87 first string is a regular expression.  If the file name matches this
88 regular expression, the command in the second string is executed with
89 the file as an argument.
90
91 If the command string contains \"%s\", the file name will be inserted
92 at that point in the command string.  If there's no \"%s\" in the
93 command string, the file name will be appended to the command string
94 before executing.
95
96 There are several user variables to tailor the behaviour of gnus-uu to
97 your needs.  First we have `gnus-uu-user-view-rules', which is the
98 variable gnus-uu first consults when trying to decide how to view a
99 file.  If this variable contains no matches, gnus-uu examines the
100 default rule variable provided in this package.  If gnus-uu finds no
101 match here, it uses `gnus-uu-user-view-rules-end' to try to make a
102 match."
103   :group 'gnus-extract-view
104   :type '(repeat (group regexp (string :tag "Command"))))
105
106 (defcustom gnus-uu-user-view-rules nil
107   "What actions are to be taken to view a file.
108 See the documentation on the `gnus-uu-default-view-rules' variable for
109 details."
110   :group 'gnus-extract-view
111   :type '(repeat (group regexp (string :tag "Command"))))
112
113 (defcustom gnus-uu-user-view-rules-end
114   '(("" "file"))
115   "*What actions are to be taken if no rule matched the file name.
116 See the documentation on the `gnus-uu-default-view-rules' variable for
117 details."
118   :group 'gnus-extract-view
119   :type '(repeat (group regexp (string :tag "Command"))))
120
121 ;; Default unpacking commands
122
123 (defcustom gnus-uu-default-archive-rules
124   '(("\\.tar$" "tar xf")
125     ("\\.zip$" "unzip -o")
126     ("\\.ar$" "ar x")
127     ("\\.arj$" "unarj x")
128     ("\\.zoo$" "zoo -e")
129     ("\\.\\(lzh\\|lha\\)$" "lha x")
130     ("\\.Z$" "uncompress")
131     ("\\.gz$" "gunzip")
132     ("\\.arc$" "arc -x"))
133   "*See `gnus-uu-user-archive-rules'."
134   :group 'gnus-extract-archive
135   :type '(repeat (group regexp (string :tag "Command"))))
136
137 (defvar gnus-uu-destructive-archivers
138   (list "uncompress" "gunzip"))
139
140 (defcustom gnus-uu-user-archive-rules nil
141   "A list that can be set to override the default archive unpacking commands.
142 To use, for instance, 'untar' to unpack tar files and 'zip -x' to
143 unpack zip files, say the following:
144   (setq gnus-uu-user-archive-rules
145     '((\"\\\\.tar$\" \"untar\")
146       (\"\\\\.zip$\" \"zip -x\")))"
147   :group 'gnus-extract-archive
148   :type '(repeat (group regexp (string :tag "Command"))))
149
150 (defcustom gnus-uu-ignore-files-by-name nil
151   "*A regular expression saying what files should not be viewed based on name.
152 If, for instance, you want gnus-uu to ignore all .au and .wav files,
153 you could say something like
154
155   (setq gnus-uu-ignore-files-by-name \"\\\\.au$\\\\|\\\\.wav$\")
156
157 Note that this variable can be used in conjunction with the
158 `gnus-uu-ignore-files-by-type' variable."
159   :group 'gnus-extract
160   :type '(choice (const :tag "off" nil)
161                  (regexp :format "%v")))
162
163 (defcustom gnus-uu-ignore-files-by-type nil
164   "*A regular expression saying what files that shouldn't be viewed, based on MIME file type.
165 If, for instance, you want gnus-uu to ignore all audio files and all mpegs,
166 you could say something like
167
168   (setq gnus-uu-ignore-files-by-type \"audio/\\\\|video/mpeg\")
169
170 Note that this variable can be used in conjunction with the
171 `gnus-uu-ignore-files-by-name' variable."
172   :group 'gnus-extract
173   :type '(choice (const :tag "off" nil)
174                  (regexp :format "%v")))
175
176 ;; Pseudo-MIME support
177
178 (defconst gnus-uu-ext-to-mime-list
179   '(("\\.gif$" "image/gif")
180     ("\\.jpe?g$" "image/jpeg")
181     ("\\.tiff?$" "image/tiff")
182     ("\\.xwd$" "image/xwd")
183     ("\\.pbm$" "image/pbm")
184     ("\\.pgm$" "image/pgm")
185     ("\\.ppm$" "image/ppm")
186     ("\\.xbm$" "image/xbm")
187     ("\\.pcx$" "image/pcx")
188     ("\\.tga$" "image/tga")
189     ("\\.ps$" "image/postscript")
190     ("\\.fli$" "video/fli")
191     ("\\.wav$" "audio/wav")
192     ("\\.aiff$" "audio/aiff")
193     ("\\.hcom$" "audio/hcom")
194     ("\\.voc$" "audio/voc")
195     ("\\.smp$" "audio/smp")
196     ("\\.mod$" "audio/mod")
197     ("\\.dvi$" "image/dvi")
198     ("\\.mpe?g$" "video/mpeg")
199     ("\\.au$" "audio/basic")
200     ("\\.\\(te?xt\\|doc\\|c\\|h\\)$" "text/plain")
201     ("\\.\\(c\\|h\\)$" "text/source")
202     ("read.*me" "text/plain")
203     ("\\.html$" "text/html")
204     ("\\.bat$" "text/bat")
205     ("\\.[1-6]$" "text/man")
206     ("\\.flc$" "video/flc")
207     ("\\.rle$" "video/rle")
208     ("\\.pfx$" "video/pfx")
209     ("\\.avi$" "video/avi")
210     ("\\.sme$" "video/sme")
211     ("\\.rpza$" "video/prza")
212     ("\\.dl$" "video/dl")
213     ("\\.qt$" "video/qt")
214     ("\\.rsrc$" "video/rsrc")
215     ("\\..*$" "unknown/unknown")))
216
217 ;; Various variables users may set
218
219 (defcustom gnus-uu-tmp-dir
220   (cond ((fboundp 'temp-directory) (temp-directory))
221         ((boundp 'temporary-file-directory) temporary-file-directory)
222         ("/tmp/"))
223   "*Variable saying where gnus-uu is to do its work.
224 Default is \"/tmp/\"."
225   :group 'gnus-extract
226   :type 'directory)
227
228 (defcustom gnus-uu-do-not-unpack-archives nil
229   "*Non-nil means that gnus-uu won't peek inside archives looking for files to display.
230 Default is nil."
231   :group 'gnus-extract-archive
232   :type 'boolean)
233
234 (defcustom gnus-uu-ignore-default-view-rules nil
235   "*Non-nil means that gnus-uu will ignore the default viewing rules.
236 Only the user viewing rules will be consulted.  Default is nil."
237   :group 'gnus-extract-view
238   :type 'boolean)
239
240 (defcustom gnus-uu-grabbed-file-functions nil
241   "Functions run on each file after successful decoding.
242 They will be called with the name of the file as the argument.
243 Likely functions you can use in this list are `gnus-uu-grab-view'
244 and `gnus-uu-grab-move'."
245   :group 'gnus-extract
246   :options '(gnus-uu-grab-view gnus-uu-grab-move)
247   :type 'hook)
248
249 (defcustom gnus-uu-ignore-default-archive-rules nil
250   "*Non-nil means that gnus-uu will ignore the default archive unpacking commands.
251 Only the user unpacking commands will be consulted.  Default is nil."
252   :group 'gnus-extract-archive
253   :type 'boolean)
254
255 (defcustom gnus-uu-kill-carriage-return t
256   "*Non-nil means that gnus-uu will strip all carriage returns from articles.
257 Default is t."
258   :group 'gnus-extract
259   :type 'boolean)
260
261 (defcustom gnus-uu-view-with-metamail nil
262   "*Non-nil means that files will be viewed with metamail.
263 The gnus-uu viewing functions will be ignored and gnus-uu will try
264 to guess at a content-type based on file name suffixes.  Default
265 it nil."
266   :group 'gnus-extract
267   :type 'boolean)
268
269 (defcustom gnus-uu-unmark-articles-not-decoded nil
270   "*Non-nil means that gnus-uu will mark articles that were unsuccessfully decoded as unread.
271 Default is nil."
272   :group 'gnus-extract
273   :type 'boolean)
274
275 (defcustom gnus-uu-correct-stripped-uucode nil
276   "*Non-nil means that gnus-uu will *try* to fix uuencoded files that have had trailing spaces deleted.
277 Default is nil."
278   :group 'gnus-extract
279   :type 'boolean)
280
281 (defcustom gnus-uu-save-in-digest nil
282   "*Non-nil means that gnus-uu, when asked to save without decoding, will save in digests.
283 If this variable is nil, gnus-uu will just save everything in a
284 file without any embellishments.  The digesting almost conforms to RFC1153 -
285 no easy way to specify any meaningful volume and issue numbers were found,
286 so I simply dropped them."
287   :group 'gnus-extract
288   :type 'boolean)
289
290 (defcustom gnus-uu-pre-uudecode-hook nil
291   "Hook run before sending a message to uudecode."
292   :group 'gnus-extract
293   :type 'hook)
294
295 (defcustom gnus-uu-digest-headers
296   '("^Date:" "^From:" "^To:" "^Cc:" "^Subject:" "^Message-ID:" "^Keywords:"
297     "^Summary:" "^References:" "^Content-Type:" "^Content-Transfer-Encoding:"
298     "^MIME-Version:" "^Content-Disposition:" "^Content-Description:"
299     "^Content-ID:" "^User-Agent:" "^X-Face:")
300   "*List of regexps to match headers included in digested messages.
301 The headers will be included in the sequence they are matched."
302   :group 'gnus-extract
303   :type '(repeat regexp))
304
305 (defcustom gnus-uu-save-separate-articles nil
306   "*Non-nil means that gnus-uu will save articles in separate files."
307   :group 'gnus-extract
308   :type 'boolean)
309
310 (defcustom gnus-uu-be-dangerous 'ask
311   "*Specifies what to do if unusual situations arise during decoding.
312 If nil, be as conservative as possible.  If t, ignore things that
313 didn't work, and overwrite existing files.  Otherwise, ask each time."
314   :group 'gnus-extract
315   :type '(choice (const :tag "conservative" nil)
316                  (const :tag "ask" ask)
317                  (const :tag "liberal" t)))
318
319 ;; Internal variables
320
321 (defvar gnus-uu-saved-article-name nil)
322
323 (defvar gnus-uu-begin-string "^begin[ \t]+0?[0-7][0-7][0-7][ \t]+\\(.*\\)$")
324 (defvar gnus-uu-end-string "^end[ \t]*$")
325
326 (defvar gnus-uu-body-line "^M")
327 (let ((i 61))
328   (while (> (setq i (1- i)) 0)
329     (setq gnus-uu-body-line (concat gnus-uu-body-line "[^a-z]")))
330   (setq gnus-uu-body-line (concat gnus-uu-body-line ".?$")))
331
332 ;"^M.............................................................?$"
333
334 (defvar gnus-uu-shar-begin-string "^#! */bin/sh")
335
336 (defvar gnus-uu-shar-file-name nil)
337 (defvar gnus-uu-shar-name-marker
338   "begin 0?[0-7][0-7][0-7][ \t]+\\(\\(\\w\\|[.\\:]\\)*\\b\\)")
339
340 (defvar gnus-uu-postscript-begin-string "^%!PS-")
341 (defvar gnus-uu-postscript-end-string "^%%EOF$")
342
343 (defvar gnus-uu-file-name nil)
344 (defvar gnus-uu-uudecode-process nil)
345 (defvar gnus-uu-binhex-article-name nil)
346
347 (defvar gnus-uu-work-dir nil)
348
349 (defvar gnus-uu-output-buffer-name " *Gnus UU Output*")
350
351 (defvar gnus-uu-default-dir gnus-article-save-directory)
352 (defvar gnus-uu-digest-from-subject nil)
353 (defvar gnus-uu-digest-buffer nil)
354
355 ;; Commands.
356
357 (defun gnus-uu-decode-uu (&optional n)
358   "Uudecodes the current article."
359   (interactive "P")
360   (gnus-uu-decode-with-method 'gnus-uu-uustrip-article n))
361
362 (defun gnus-uu-decode-uu-and-save (n dir)
363   "Decodes and saves the resulting file."
364   (interactive
365    (list current-prefix-arg
366          (file-name-as-directory
367           (read-file-name "Uudecode and save in dir: "
368                           gnus-uu-default-dir
369                           gnus-uu-default-dir t))))
370   (gnus-uu-decode-with-method 'gnus-uu-uustrip-article n dir nil nil t))
371
372 (defun gnus-uu-decode-unshar (&optional n)
373   "Unshars the current article."
374   (interactive "P")
375   (gnus-uu-decode-with-method 'gnus-uu-unshar-article n nil nil 'scan t))
376
377 (defun gnus-uu-decode-unshar-and-save (n dir)
378   "Unshars and saves the current article."
379   (interactive
380    (list current-prefix-arg
381          (file-name-as-directory
382           (read-file-name "Unshar and save in dir: "
383                           gnus-uu-default-dir
384                           gnus-uu-default-dir t))))
385   (gnus-uu-decode-with-method 'gnus-uu-unshar-article n dir nil 'scan t))
386
387 (defun gnus-uu-decode-save (n file)
388   "Saves the current article."
389   (interactive
390    (list current-prefix-arg
391          (read-file-name
392           (if gnus-uu-save-separate-articles
393               "Save articles is dir: "
394             "Save articles in file: ")
395           gnus-uu-default-dir
396           gnus-uu-default-dir)))
397   (setq gnus-uu-saved-article-name file)
398   (gnus-uu-decode-with-method 'gnus-uu-save-article n nil t))
399
400 (defun gnus-uu-decode-binhex (n dir)
401   "Unbinhexes the current article."
402   (interactive
403    (list current-prefix-arg
404          (file-name-as-directory
405           (read-file-name "Unbinhex and save in dir: "
406                           gnus-uu-default-dir
407                           gnus-uu-default-dir))))
408   (setq gnus-uu-binhex-article-name
409         (mm-make-temp-file (expand-file-name "binhex" gnus-uu-work-dir)))
410   (gnus-uu-decode-with-method 'gnus-uu-binhex-article n dir))
411
412 (defun gnus-uu-decode-uu-view (&optional n)
413   "Uudecodes and views the current article."
414   (interactive "P")
415   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
416     (gnus-uu-decode-uu n)))
417
418 (defun gnus-uu-decode-uu-and-save-view (n dir)
419   "Decodes, views and saves the resulting file."
420   (interactive
421    (list current-prefix-arg
422          (read-file-name "Uudecode, view and save in dir: "
423                          gnus-uu-default-dir
424                          gnus-uu-default-dir t)))
425   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
426     (gnus-uu-decode-uu-and-save n dir)))
427
428 (defun gnus-uu-decode-unshar-view (&optional n)
429   "Unshars and views the current article."
430   (interactive "P")
431   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
432     (gnus-uu-decode-unshar n)))
433
434 (defun gnus-uu-decode-unshar-and-save-view (n dir)
435   "Unshars and saves the current article."
436   (interactive
437    (list current-prefix-arg
438          (read-file-name "Unshar, view and save in dir: "
439                          gnus-uu-default-dir
440                          gnus-uu-default-dir t)))
441   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
442     (gnus-uu-decode-unshar-and-save n dir)))
443
444 (defun gnus-uu-decode-save-view (n file)
445   "Saves and views the current article."
446   (interactive
447    (list current-prefix-arg
448          (read-file-name  (if gnus-uu-save-separate-articles
449                               "Save articles is dir: "
450                             "Save articles in file: ")
451                           gnus-uu-default-dir gnus-uu-default-dir)))
452   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
453     (gnus-uu-decode-save n file)))
454
455 (defun gnus-uu-decode-binhex-view (n file)
456   "Unbinhexes and views the current article."
457   (interactive
458    (list current-prefix-arg
459          (read-file-name "Unbinhex, view and save in dir: "
460                          gnus-uu-default-dir gnus-uu-default-dir)))
461   (setq gnus-uu-binhex-article-name
462         (mm-make-temp-file (expand-file-name "binhex" gnus-uu-work-dir)))
463   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
464     (gnus-uu-decode-binhex n file)))
465
466
467 ;; Digest and forward articles
468
469 (defun gnus-uu-digest-mail-forward (&optional n post)
470   "Digests and forwards all articles in this series."
471   (interactive "P")
472   (let ((gnus-uu-save-in-digest t)
473         (file (mm-make-temp-file (nnheader-concat gnus-uu-tmp-dir "forward")))
474         (message-forward-as-mime message-forward-as-mime)
475         (mail-parse-charset gnus-newsgroup-charset)
476         (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets)
477         gnus-uu-digest-buffer subject from)
478     (if (and n (not (numberp n)))
479         (setq message-forward-as-mime (not message-forward-as-mime)
480               n nil))
481     (let ((gnus-article-reply (gnus-summary-work-articles n)))
482       (gnus-setup-message 'forward
483         (setq gnus-uu-digest-from-subject nil)
484         (setq gnus-uu-digest-buffer
485               (gnus-get-buffer-create " *gnus-uu-forward*"))
486         (gnus-uu-decode-save n file)
487         (switch-to-buffer gnus-uu-digest-buffer)
488         (let ((fs gnus-uu-digest-from-subject))
489           (when fs
490             (setq from (caar fs)
491                   subject (gnus-simplify-subject-fuzzy (cdar fs))
492                   fs (cdr fs))
493             (while (and fs (or from subject))
494               (when from
495                 (unless (string= from (caar fs))
496                   (setq from nil)))
497               (when subject
498                 (unless (string= (gnus-simplify-subject-fuzzy (cdar fs))
499                                  subject)
500                   (setq subject nil)))
501               (setq fs (cdr fs))))
502           (unless subject
503             (setq subject "Digested Articles"))
504           (unless from
505             (setq from
506                   (if (gnus-news-group-p gnus-newsgroup-name)
507                       gnus-newsgroup-name
508                     "Various"))))
509         (goto-char (point-min))
510         (when (re-search-forward "^Subject: ")
511           (delete-region (point) (gnus-point-at-eol))
512           (insert subject))
513         (goto-char (point-min))
514         (when (re-search-forward "^From:")
515           (delete-region (point) (gnus-point-at-eol))
516           (insert " " from))
517         (let ((message-forward-decoded-p t))
518           (message-forward post))))
519     (setq gnus-uu-digest-from-subject nil)))
520
521 (defun gnus-uu-digest-post-forward (&optional n)
522   "Digest and forward to a newsgroup."
523   (interactive "P")
524   (gnus-uu-digest-mail-forward n t))
525
526 ;; Process marking.
527
528 (defun gnus-message-process-mark (unmarkp new-marked)
529   (let ((old (- (length gnus-newsgroup-processable) (length new-marked))))
530     (message "%d mark%s %s%s"
531              (length new-marked)
532              (if (= (length new-marked) 1) "" "s")
533              (if unmarkp "removed" "added")
534              (cond
535               ((and (zerop old)
536                     (not unmarkp))
537                "")
538               (unmarkp
539                (format ", %d remain marked"
540                        (length gnus-newsgroup-processable)))
541               (t
542                (format ", %d already marked" old))))))
543
544 (defun gnus-new-processable (unmarkp articles)
545   (if unmarkp
546       (gnus-intersection gnus-newsgroup-processable articles)
547     (gnus-set-difference articles gnus-newsgroup-processable)))
548
549 (defun gnus-uu-mark-by-regexp (regexp &optional unmark)
550   "Set the process mark on articles whose subjects match REGEXP.
551 When called interactively, prompt for REGEXP.
552 Optional UNMARK non-nil means unmark instead of mark."
553   (interactive "sMark (regexp): \nP")
554   (save-excursion
555     (let* ((articles (gnus-uu-find-articles-matching regexp))
556            (new-marked (gnus-new-processable unmark articles)))
557       (while articles
558         (if unmark
559             (gnus-summary-remove-process-mark (pop articles))
560           (gnus-summary-set-process-mark (pop articles))))
561       (gnus-message-process-mark unmark new-marked)))
562   (gnus-summary-position-point))
563
564 (defun gnus-uu-unmark-by-regexp (regexp)
565   "Remove the process mark from articles whose subjects match REGEXP.
566 When called interactively, prompt for REGEXP."
567   (interactive "sUnmark (regexp): ")
568   (gnus-uu-mark-by-regexp regexp t))
569
570 (defun gnus-uu-mark-series ()
571   "Mark the current series with the process mark."
572   (interactive)
573   (let ((articles (gnus-uu-find-articles-matching)))
574     (while articles
575       (gnus-summary-set-process-mark (car articles))
576       (setq articles (cdr articles)))
577     (message ""))
578   (gnus-summary-position-point))
579
580 (defun gnus-uu-mark-region (beg end &optional unmark)
581   "Set the process mark on all articles between point and mark."
582   (interactive "r")
583   (save-excursion
584     (goto-char beg)
585     (while (< (point) end)
586       (if unmark
587           (gnus-summary-remove-process-mark (gnus-summary-article-number))
588         (gnus-summary-set-process-mark (gnus-summary-article-number)))
589       (forward-line 1)))
590   (gnus-summary-position-point))
591
592 (defun gnus-uu-unmark-region (beg end)
593   "Remove the process mark from all articles between point and mark."
594   (interactive "r")
595   (gnus-uu-mark-region beg end t))
596
597 (defun gnus-uu-mark-buffer ()
598   "Set the process mark on all articles in the buffer."
599   (interactive)
600   (gnus-uu-mark-region (point-min) (point-max)))
601
602 (defun gnus-uu-unmark-buffer ()
603   "Remove the process mark on all articles in the buffer."
604   (interactive)
605   (gnus-uu-mark-region (point-min) (point-max) t))
606
607 (defun gnus-uu-mark-thread ()
608   "Marks all articles downwards in this thread."
609   (interactive)
610   (gnus-save-hidden-threads
611     (let ((level (gnus-summary-thread-level)))
612       (while (and (gnus-summary-set-process-mark
613                    (gnus-summary-article-number))
614                   (zerop (gnus-summary-next-subject 1 nil t))
615                   (> (gnus-summary-thread-level) level)))))
616   (gnus-summary-position-point))
617
618 (defun gnus-uu-unmark-thread ()
619   "Unmarks all articles downwards in this thread."
620   (interactive)
621   (let ((level (gnus-summary-thread-level)))
622     (while (and (gnus-summary-remove-process-mark
623                  (gnus-summary-article-number))
624                 (zerop (gnus-summary-next-subject 1))
625                 (> (gnus-summary-thread-level) level))))
626   (gnus-summary-position-point))
627
628 (defun gnus-uu-invert-processable ()
629   "Invert the list of process-marked articles."
630   (interactive)
631   (let ((data gnus-newsgroup-data)
632         number)
633     (save-excursion
634       (while data
635         (if (memq (setq number (gnus-data-number (pop data)))
636                   gnus-newsgroup-processable)
637             (gnus-summary-remove-process-mark number)
638           (gnus-summary-set-process-mark number)))))
639   (gnus-summary-position-point))
640
641 (defun gnus-uu-mark-over (&optional score)
642   "Mark all articles with a score over SCORE (the prefix)."
643   (interactive "P")
644   (let ((score (or score gnus-summary-default-score 0))
645         (data gnus-newsgroup-data))
646     (save-excursion
647       (while data
648         (when (> (or (cdr (assq (gnus-data-number (car data))
649                                 gnus-newsgroup-scored))
650                      gnus-summary-default-score 0)
651                  score)
652           (gnus-summary-set-process-mark (caar data)))
653         (setq data (cdr data))))
654     (gnus-summary-position-point)))
655
656 (defun gnus-uu-mark-sparse ()
657   "Mark all series that have some articles marked."
658   (interactive)
659   (let ((marked (nreverse gnus-newsgroup-processable))
660         subject articles total headers)
661     (unless marked
662       (error "No articles marked with the process mark"))
663     (setq gnus-newsgroup-processable nil)
664     (save-excursion
665       (while marked
666         (and (vectorp (setq headers
667                             (gnus-summary-article-header (car marked))))
668              (setq subject (mail-header-subject headers)
669                    articles (gnus-uu-find-articles-matching
670                              (gnus-uu-reginize-string subject))
671                    total (nconc total articles)))
672         (while articles
673           (gnus-summary-set-process-mark (car articles))
674           (setcdr marked (delq (car articles) (cdr marked)))
675           (setq articles (cdr articles)))
676         (setq marked (cdr marked)))
677       (setq gnus-newsgroup-processable (nreverse total)))
678     (gnus-summary-position-point)))
679
680 (defun gnus-uu-mark-all ()
681   "Mark all articles in \"series\" order."
682   (interactive)
683   (setq gnus-newsgroup-processable nil)
684   (save-excursion
685     (let ((data gnus-newsgroup-data)
686           number)
687       (while data
688         (when (and (not (memq (setq number (gnus-data-number (car data)))
689                               gnus-newsgroup-processable))
690                    (vectorp (gnus-data-header (car data))))
691           (gnus-summary-goto-subject number)
692           (gnus-uu-mark-series))
693         (setq data (cdr data)))))
694   (gnus-summary-position-point))
695
696 ;; All PostScript functions written by Erik Selberg <speed@cs.washington.edu>.
697
698 (defun gnus-uu-decode-postscript (&optional n)
699   "Gets postscript of the current article."
700   (interactive "P")
701   (gnus-uu-decode-with-method 'gnus-uu-decode-postscript-article n))
702
703 (defun gnus-uu-decode-postscript-view (&optional n)
704   "Gets and views the current article."
705   (interactive "P")
706   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
707     (gnus-uu-decode-postscript n)))
708
709 (defun gnus-uu-decode-postscript-and-save (n dir)
710   "Extracts postscript and saves the current article."
711   (interactive
712    (list current-prefix-arg
713          (file-name-as-directory
714           (read-file-name "Save in dir: "
715                           gnus-uu-default-dir
716                           gnus-uu-default-dir t))))
717   (gnus-uu-decode-with-method 'gnus-uu-decode-postscript-article
718                               n dir nil nil t))
719
720 (defun gnus-uu-decode-postscript-and-save-view (n dir)
721   "Decodes, views and saves the resulting file."
722   (interactive
723    (list current-prefix-arg
724          (read-file-name "Where do you want to save the file(s)? "
725                          gnus-uu-default-dir
726                          gnus-uu-default-dir t)))
727   (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
728     (gnus-uu-decode-postscript-and-save n dir)))
729
730
731 ;; Internal functions.
732
733 (defun gnus-uu-decode-with-method (method n &optional save not-insert
734                                           scan cdir)
735   (gnus-uu-initialize scan)
736   (when save
737     (setq gnus-uu-default-dir save))
738   ;; Create the directory we save to.
739   (when (and scan cdir save
740              (not (file-exists-p save)))
741     (make-directory save t))
742   (let ((articles (gnus-uu-get-list-of-articles n))
743         files)
744     (setq files (gnus-uu-grab-articles articles method t))
745     (let ((gnus-current-article (car articles)))
746       (when scan
747         (setq files (gnus-uu-scan-directory gnus-uu-work-dir))))
748     (when save
749       (gnus-uu-save-files files save))
750     (when (eq gnus-uu-do-not-unpack-archives nil)
751       (setq files (gnus-uu-unpack-files files)))
752     (setq files (nreverse (gnus-uu-get-actions files)))
753     (or not-insert (not gnus-insert-pseudo-articles)
754         (gnus-summary-insert-pseudos files save))))
755
756 (defun gnus-uu-scan-directory (dir &optional rec)
757   "Return a list of all files under DIR."
758   (let ((files (directory-files dir t))
759         out file)
760     (while (setq file (pop files))
761       (unless (member (file-name-nondirectory file) '("." ".."))
762         (push (list (cons 'name file)
763                     (cons 'article gnus-current-article))
764               out)
765         (when (file-directory-p file)
766           (setq out (nconc (gnus-uu-scan-directory file t) out)))))
767     (if rec
768         out
769       (nreverse out))))
770
771 (defun gnus-uu-save-files (files dir)
772   "Save FILES in DIR."
773   (let ((len (length files))
774         (reg (concat "^" (regexp-quote gnus-uu-work-dir)))
775         to-file file fromdir)
776     (while (setq file (cdr (assq 'name (pop files))))
777       (when (file-exists-p file)
778         (string-match reg file)
779         (setq fromdir (substring file (match-end 0)))
780         (if (file-directory-p file)
781             (gnus-make-directory (concat dir fromdir))
782           (setq to-file (concat dir fromdir))
783           (when (or (not (file-exists-p to-file))
784                     (eq gnus-uu-be-dangerous t)
785                     (and gnus-uu-be-dangerous
786                          (gnus-y-or-n-p (format "%s exists; overwrite? "
787                                                 to-file))))
788             (copy-file file to-file t t)))))
789     (gnus-message 5 "Saved %d file%s" len (if (= len 1) "" "s"))))
790
791 ;; Functions for saving and possibly digesting articles without
792 ;; any decoding.
793
794 ;; Function called by gnus-uu-grab-articles to treat each article.
795 (defun gnus-uu-save-article (buffer in-state)
796   (cond
797    (gnus-uu-save-separate-articles
798     (save-excursion
799       (set-buffer buffer)
800       (gnus-write-buffer-as-coding-system
801        nnheader-text-coding-system
802        (concat gnus-uu-saved-article-name gnus-current-article))
803       (cond ((eq in-state 'first) (list gnus-uu-saved-article-name 'begin))
804             ((eq in-state 'first-and-last) (list gnus-uu-saved-article-name
805                                                  'begin 'end))
806             ((eq in-state 'last) (list 'end))
807             (t (list 'middle)))))
808    ((not gnus-uu-save-in-digest)
809     (save-excursion
810       (set-buffer buffer)
811       (write-region (point-min) (point-max) gnus-uu-saved-article-name t)
812       (cond ((eq in-state 'first) (list gnus-uu-saved-article-name 'begin))
813             ((eq in-state 'first-and-last) (list gnus-uu-saved-article-name
814                                                  'begin 'end))
815             ((eq in-state 'last) (list 'end))
816             (t (list 'middle)))))
817    (t
818     (let ((header (gnus-summary-article-header)))
819       (push (cons (mail-header-from header)
820                   (mail-header-subject header))
821             gnus-uu-digest-from-subject))
822     (let ((name (file-name-nondirectory gnus-uu-saved-article-name))
823           beg subj headers headline sorthead body end-string state)
824       (if (or (eq in-state 'first)
825               (eq in-state 'first-and-last))
826           (progn
827             (setq state (list 'begin))
828             (save-excursion
829               (set-buffer (gnus-get-buffer-create "*gnus-uu-body*"))
830               (erase-buffer))
831             (save-excursion
832               (set-buffer (gnus-get-buffer-create "*gnus-uu-pre*"))
833               (erase-buffer)
834               (insert (format
835                        "Date: %s\nFrom: %s\nSubject: %s Digest\n\n"
836                        (current-time-string) name name))
837               (insert "Topics:\n")))
838         (when (not (eq in-state 'end))
839           (setq state (list 'middle))))
840       (save-excursion
841         (set-buffer "*gnus-uu-body*")
842         (goto-char (setq beg (point-max)))
843         (save-excursion
844           (save-restriction
845             (set-buffer buffer)
846             (let (buffer-read-only)
847               (gnus-set-text-properties (point-min) (point-max) nil)
848               ;; These two are necessary for XEmacs 19.12 fascism.
849               (put-text-property (point-min) (point-max) 'invisible nil)
850               (put-text-property (point-min) (point-max) 'intangible nil))
851             (goto-char (point-min))
852             (re-search-forward "\n\n")
853             (unless gnus-uu-digest-buffer
854               ;; Quote all 30-dash lines.
855               (save-excursion
856                 (while (re-search-forward "^-" nil t)
857                   (beginning-of-line)
858                   (delete-char 1)
859                   (insert "- "))))
860             (setq body (buffer-substring (1- (point)) (point-max)))
861             (narrow-to-region (point-min) (point))
862             (if (not (setq headers gnus-uu-digest-headers))
863                 (setq sorthead (buffer-substring (point-min) (point-max)))
864               (while headers
865                 (setq headline (car headers))
866                 (setq headers (cdr headers))
867                 (goto-char (point-min))
868                 (while (re-search-forward headline nil t)
869                   (setq sorthead
870                         (concat sorthead
871                                 (buffer-substring
872                                  (match-beginning 0)
873                                  (or (and (re-search-forward "^[^ \t]" nil t)
874                                           (1- (point)))
875                                      (progn (forward-line 1) (point)))))))))
876             (widen)))
877         (insert message-forward-start-separator)
878         (insert sorthead) (goto-char (point-max))
879         (insert body) (goto-char (point-max))
880         (goto-char beg)
881         (when (re-search-forward "^Subject:" nil t)
882           (setq subj (nnheader-decode-subject
883                       (buffer-substring (match-end 0) (std11-field-end)))))
884         (when subj
885           (save-excursion
886             (set-buffer "*gnus-uu-pre*")
887             (insert (format "   %s\n" subj)))))
888       (when (or (eq in-state 'last)
889                 (eq in-state 'first-and-last))
890         (if gnus-uu-digest-buffer
891             (with-current-buffer gnus-uu-digest-buffer
892               (erase-buffer)
893               (insert-buffer "*gnus-uu-pre*")
894               (goto-char (point-max))
895               (insert-buffer "*gnus-uu-body*"))
896           (save-excursion
897             (set-buffer "*gnus-uu-pre*")
898             (insert (format "\n\n%s\n\n" (make-string 70 ?-)))
899             (if gnus-uu-digest-buffer
900                 (with-current-buffer gnus-uu-digest-buffer
901                   (erase-buffer)
902                   (insert-buffer "*gnus-uu-pre*"))
903               (gnus-write-buffer-as-coding-system
904                nnheader-text-coding-system gnus-uu-saved-article-name)))
905           (save-excursion
906             (set-buffer "*gnus-uu-body*")
907             (goto-char (point-max))
908             (insert
909              (concat (setq end-string (format "End of %s Digest" name))
910                      "\n"))
911             (insert (concat (make-string (length end-string) ?*) "\n"))
912             (if gnus-uu-digest-buffer
913                 (with-current-buffer gnus-uu-digest-buffer
914                   (goto-char (point-max))
915                   (insert-buffer "*gnus-uu-body*"))
916               (let ((file-name-coding-system nnmail-pathname-coding-system)
917                     (pathname-coding-system nnmail-pathname-coding-system))
918                 (write-region-as-coding-system
919                  nnheader-text-coding-system
920                  (point-min) (point-max) gnus-uu-saved-article-name t)))))
921         (gnus-kill-buffer "*gnus-uu-pre*")
922         (gnus-kill-buffer "*gnus-uu-body*")
923         (push 'end state))
924       (if (memq 'begin state)
925           (cons gnus-uu-saved-article-name state)
926         state)))))
927
928 ;; Binhex treatment - not very advanced.
929
930 (defvar gnus-uu-binhex-body-line
931   "^[^:]...............................................................$")
932 (defvar gnus-uu-binhex-begin-line
933   "^:...............................................................$")
934 (defvar gnus-uu-binhex-end-line
935   ":$")
936
937 (defun gnus-uu-binhex-article (buffer in-state)
938   (let (state start-char)
939     (save-excursion
940       (set-buffer buffer)
941       (widen)
942       (goto-char (point-min))
943       (when (not (re-search-forward gnus-uu-binhex-begin-line nil t))
944         (when (not (re-search-forward gnus-uu-binhex-body-line nil t))
945           (setq state (list 'wrong-type))))
946
947       (if (memq 'wrong-type state)
948           ()
949         (beginning-of-line)
950         (setq start-char (point))
951         (if (looking-at gnus-uu-binhex-begin-line)
952             (progn
953               (setq state (list 'begin))
954               (write-region (point-min) (point-min)
955                             gnus-uu-binhex-article-name))
956           (setq state (list 'middle)))
957         (goto-char (point-max))
958         (re-search-backward (concat gnus-uu-binhex-body-line "\\|"
959                                     gnus-uu-binhex-end-line)
960                             nil t)
961         (when (looking-at gnus-uu-binhex-end-line)
962           (setq state (if (memq 'begin state)
963                           (cons 'end state)
964                         (list 'end))))
965         (beginning-of-line)
966         (forward-line 1)
967         (when (file-exists-p gnus-uu-binhex-article-name)
968           (write-region-as-binary start-char (point)
969                                   gnus-uu-binhex-article-name 'append))))
970     (if (memq 'begin state)
971         (cons gnus-uu-binhex-article-name state)
972       state)))
973
974 ;; PostScript
975
976 (defun gnus-uu-decode-postscript-article (process-buffer in-state)
977   (let ((state (list 'ok))
978         start-char end-char file-name)
979     (save-excursion
980       (set-buffer process-buffer)
981       (goto-char (point-min))
982       (if (not (re-search-forward gnus-uu-postscript-begin-string nil t))
983           (setq state (list 'wrong-type))
984         (beginning-of-line)
985         (setq start-char (point))
986         (if (not (re-search-forward gnus-uu-postscript-end-string nil t))
987             (setq state (list 'wrong-type))
988           (setq end-char (point))
989           (set-buffer (gnus-get-buffer-create gnus-uu-output-buffer-name))
990           (insert-buffer-substring process-buffer start-char end-char)
991           (setq file-name (concat gnus-uu-work-dir
992                                   (cdr gnus-article-current) ".ps"))
993           (write-region (point-min) (point-max) file-name)
994           (setq state (list file-name 'begin 'end)))))
995     state))
996
997
998 ;; Find actions.
999
1000 (defun gnus-uu-get-actions (files)
1001   (let ((ofiles files)
1002         action name)
1003     (while files
1004       (setq name (cdr (assq 'name (car files))))
1005       (and
1006        (setq action (gnus-uu-get-action name))
1007        (setcar files (nconc (list (if (string= action "gnus-uu-archive")
1008                                       (cons 'action "file")
1009                                     (cons 'action action))
1010                                   (cons 'execute (gnus-uu-command
1011                                                   action name)))
1012                             (car files))))
1013       (setq files (cdr files)))
1014     ofiles))
1015
1016 (defun gnus-uu-get-action (file-name)
1017   (let (action)
1018     (setq action
1019           (gnus-uu-choose-action
1020            file-name
1021            (append
1022             gnus-uu-user-view-rules
1023             (if gnus-uu-ignore-default-view-rules
1024                 nil
1025               gnus-uu-default-view-rules)
1026             gnus-uu-user-view-rules-end)))
1027     (when (and (not (string= (or action "") "gnus-uu-archive"))
1028                gnus-uu-view-with-metamail)
1029       (when (setq action
1030                   (gnus-uu-choose-action file-name gnus-uu-ext-to-mime-list))
1031         (setq action (format "metamail -d -b -c \"%s\"" action))))
1032     action))
1033
1034
1035 ;; Functions for treating subjects and collecting series.
1036
1037 (defun gnus-uu-reginize-string (string)
1038   ;; Takes a string and puts a \ in front of every special character;
1039   ;; replaces the last thing that looks like "2/3" with "[0-9]+/3"
1040   ;; or, if it can't find something like that, tries "2 of 3", then
1041   ;; finally just replaces the next to last number with "[0-9]+".
1042   (save-excursion
1043     (set-buffer (gnus-get-buffer-create gnus-uu-output-buffer-name))
1044     (buffer-disable-undo)
1045     (erase-buffer)
1046     (insert (regexp-quote string))
1047
1048     (setq case-fold-search nil)
1049
1050     (end-of-line)
1051     (if (re-search-backward "\\([^0-9]\\)[0-9]+/\\([0-9]+\\)" nil t)
1052         (replace-match "\\1[0-9]+/\\2")
1053
1054       (end-of-line)
1055       (if (re-search-backward "\\([^0-9]\\)[0-9]+[ \t]*of[ \t]*\\([0-9]+\\)"
1056                               nil t)
1057           (replace-match "\\1[0-9]+ of \\2")
1058
1059         (end-of-line)
1060         (if (re-search-backward "\\([^0-9]\\)[0-9]+\\([^0-9]+\\)[0-9]+"
1061                                 nil t)
1062             (replace-match "\\1[0-9]+\\2[0-9]+" t nil nil nil))))
1063
1064     (goto-char 1)
1065     (while (re-search-forward "[ \t]+" nil t)
1066       (replace-match "[ \t]+" t t))
1067
1068     (buffer-substring (point-min) (point-max))))
1069
1070 (defun gnus-uu-get-list-of-articles (n)
1071   ;; If N is non-nil, the article numbers of the N next articles
1072   ;; will be returned.
1073   ;; If any articles have been marked as processable, they will be
1074   ;; returned.
1075   ;; Failing that, articles that have subjects that are part of the
1076   ;; same "series" as the current will be returned.
1077   (let (articles)
1078     (cond
1079      (n
1080       (setq n (prefix-numeric-value n))
1081       (let ((backward (< n 0))
1082             (n (abs n)))
1083         (save-excursion
1084           (while (and (> n 0)
1085                       (push (gnus-summary-article-number)
1086                             articles)
1087                       (gnus-summary-search-forward nil nil backward))
1088             (setq n (1- n))))
1089         (nreverse articles)))
1090      (gnus-newsgroup-processable
1091       (reverse gnus-newsgroup-processable))
1092      (t
1093       (gnus-uu-find-articles-matching)))))
1094
1095 (defun gnus-uu-string< (l1 l2)
1096   (string< (car l1) (car l2)))
1097
1098 (defun gnus-uu-find-articles-matching
1099   (&optional subject only-unread do-not-translate)
1100   ;; Finds all articles that matches the regexp SUBJECT.  If it is
1101   ;; nil, the current article name will be used.  If ONLY-UNREAD is
1102   ;; non-nil, only unread articles are chosen.  If DO-NOT-TRANSLATE is
1103   ;; non-nil, article names are not equalized before sorting.
1104   (let ((subject (or subject
1105                      (gnus-uu-reginize-string (gnus-summary-article-subject))))
1106         list-of-subjects)
1107     (save-excursion
1108       (when subject
1109         ;; Collect all subjects matching subject.
1110         (let ((case-fold-search t)
1111               (data gnus-newsgroup-data)
1112               subj mark d)
1113           (while data
1114             (setq d (pop data))
1115             (and (not (gnus-data-pseudo-p d))
1116                  (or (not only-unread)
1117                      (= (setq mark (gnus-data-mark d))
1118                         gnus-unread-mark)
1119                      (= mark gnus-ticked-mark)
1120                      (= mark gnus-dormant-mark))
1121                  (setq subj (mail-header-subject (gnus-data-header d)))
1122                  (string-match subject subj)
1123                  (push (cons subj (gnus-data-number d))
1124                        list-of-subjects))))
1125
1126         ;; Expand numbers, sort, and return the list of article
1127         ;; numbers.
1128         (mapcar (lambda (sub) (cdr sub))
1129                 (sort (gnus-uu-expand-numbers
1130                        list-of-subjects
1131                        (not do-not-translate))
1132                       'gnus-uu-string<))))))
1133
1134 (defun gnus-uu-expand-numbers (string-list &optional translate)
1135   ;; Takes a list of strings and "expands" all numbers in all the
1136   ;; strings.  That is, this function makes all numbers equal length by
1137   ;; prepending lots of zeroes before each number.  This is to ease later
1138   ;; sorting to find out what sequence the articles are supposed to be
1139   ;; decoded in.  Returns the list of expanded strings.
1140   (let ((out-list string-list)
1141         string)
1142     (save-excursion
1143       (set-buffer (gnus-get-buffer-create gnus-uu-output-buffer-name))
1144       (buffer-disable-undo)
1145       (while string-list
1146         (erase-buffer)
1147         (insert (caar string-list))
1148         ;; Translate multiple spaces to one space.
1149         (goto-char (point-min))
1150         (while (re-search-forward "[ \t]+" nil t)
1151           (replace-match " "))
1152         ;; Translate all characters to "a".
1153         (goto-char (point-min))
1154         (when translate
1155           (while (re-search-forward "[A-Za-z]" nil t)
1156             (replace-match "a" t t)))
1157         ;; Expand numbers.
1158         (goto-char (point-min))
1159         (while (re-search-forward "[0-9]+" nil t)
1160           (ignore-errors
1161             (replace-match
1162              (format "%06d"
1163                      (string-to-int (buffer-substring
1164                                      (match-beginning 0) (match-end 0)))))))
1165         (setq string (buffer-substring 1 (point-max)))
1166         (setcar (car string-list) string)
1167         (setq string-list (cdr string-list))))
1168     out-list))
1169
1170
1171 ;; `gnus-uu-grab-articles' is the general multi-article treatment
1172 ;; function.  It takes a list of articles to be grabbed and a function
1173 ;; to apply to each article.
1174 ;;
1175 ;; The function to be called should take two parameters.  The first
1176 ;; parameter is the article buffer.  The function should leave the
1177 ;; result, if any, in this buffer.  Most treatment functions will just
1178 ;; generate files...
1179 ;;
1180 ;; The second parameter is the state of the list of articles, and can
1181 ;; have four values: `first', `middle', `last' and `first-and-last'.
1182 ;;
1183 ;; The function should return a list.  The list may contain the
1184 ;; following symbols:
1185 ;; `error' if an error occurred
1186 ;; `begin' if the beginning of an encoded file has been received
1187 ;;   If the list returned contains a `begin', the first element of
1188 ;;   the list *must* be a string with the file name of the decoded
1189 ;;   file.
1190 ;; `end' if the end of an encoded file has been received
1191 ;; `middle' if the article was a body part of an encoded file
1192 ;; `wrong-type' if the article was not a part of an encoded file
1193 ;; `ok', which can be used everything is ok
1194
1195 (defvar gnus-uu-has-been-grabbed nil)
1196
1197 (defun gnus-uu-unmark-list-of-grabbed (&optional dont-unmark-last-article)
1198   (let (art)
1199     (if (not (and gnus-uu-has-been-grabbed
1200                   gnus-uu-unmark-articles-not-decoded))
1201         ()
1202       (when dont-unmark-last-article
1203         (setq art (car gnus-uu-has-been-grabbed))
1204         (setq gnus-uu-has-been-grabbed (cdr gnus-uu-has-been-grabbed)))
1205       (while gnus-uu-has-been-grabbed
1206         (gnus-summary-tick-article (car gnus-uu-has-been-grabbed) t)
1207         (setq gnus-uu-has-been-grabbed (cdr gnus-uu-has-been-grabbed)))
1208       (when dont-unmark-last-article
1209         (setq gnus-uu-has-been-grabbed (list art))))))
1210
1211 ;; This function takes a list of articles and a function to apply to
1212 ;; each article grabbed.
1213 ;;
1214 ;; This function returns a list of files decoded if the grabbing and
1215 ;; the process-function has been successful and nil otherwise.
1216 (defun gnus-uu-grab-articles (articles process-function
1217                                        &optional sloppy limit no-errors)
1218   (let ((state 'first)
1219         (gnus-asynchronous nil)
1220         (gnus-inhibit-treatment t)
1221         has-been-begin article result-file result-files process-state
1222         gnus-summary-display-article-function
1223         gnus-article-prepare-hook gnus-display-mime-function
1224         article-series files)
1225
1226     (while (and articles
1227                 (not (memq 'error process-state))
1228                 (or sloppy
1229                     (not (memq 'end process-state))))
1230
1231       (setq article (pop articles))
1232       (when (vectorp (gnus-summary-article-header article))
1233         (push article article-series)
1234
1235         (unless articles
1236           (if (eq state 'first)
1237               (setq state 'first-and-last)
1238             (setq state 'last)))
1239
1240         (let ((part (gnus-uu-part-number article)))
1241           (gnus-message 6 "Getting article %d%s..."
1242                         article (if (string= part "") "" (concat ", " part))))
1243         (gnus-summary-display-article article)
1244
1245         ;; Push the article to the processing function.
1246         (save-excursion
1247           (set-buffer gnus-original-article-buffer)
1248           (let ((buffer-read-only nil))
1249             (save-excursion
1250               (set-buffer gnus-summary-buffer)
1251               (setq process-state
1252                     (funcall process-function
1253                              gnus-original-article-buffer state)))))
1254
1255         (gnus-summary-remove-process-mark article)
1256
1257         ;; If this is the beginning of a decoded file, we push it
1258         ;; on to a list.
1259         (when (or (memq 'begin process-state)
1260                   (and (or (eq state 'first)
1261                            (eq state 'first-and-last))
1262                        (memq 'ok process-state)))
1263           (when has-been-begin
1264             ;; If there is a `result-file' here, that means that the
1265             ;; file was unsuccessfully decoded, so we delete it.
1266             (when (and result-file
1267                        (file-exists-p result-file)
1268                        (not gnus-uu-be-dangerous)
1269                        (or (eq gnus-uu-be-dangerous t)
1270                            (gnus-y-or-n-p
1271                             (format "Delete unsuccessfully decoded file %s"
1272                                     result-file))))
1273               (delete-file result-file)))
1274           (when (memq 'begin process-state)
1275             (setq result-file (car process-state)))
1276           (setq has-been-begin t))
1277
1278         ;; Check whether we have decoded one complete file.
1279         (when (memq 'end process-state)
1280           (setq article-series nil)
1281           (setq has-been-begin nil)
1282           (if (stringp result-file)
1283               (setq files (list result-file))
1284             (setq files result-file))
1285           (setq result-file (car files))
1286           (while files
1287             (push (list (cons 'name (pop files))
1288                         (cons 'article article))
1289                   result-files))
1290           ;; Allow user-defined functions to be run on this file.
1291           (when gnus-uu-grabbed-file-functions
1292             (let ((funcs gnus-uu-grabbed-file-functions))
1293               (unless (listp funcs)
1294                 (setq funcs (list funcs)))
1295               (while funcs
1296                 (funcall (pop funcs) result-file))))
1297           (setq result-file nil)
1298           ;; Check whether we have decoded enough articles.
1299           (and limit (= (length result-files) limit)
1300                (setq articles nil)))
1301
1302         ;; If this is the last article to be decoded, and
1303         ;; we still haven't reached the end, then we delete
1304         ;; the partially decoded file.
1305         (and (or (eq state 'last) (eq state 'first-and-last))
1306              (not (memq 'end process-state))
1307              result-file
1308              (file-exists-p result-file)
1309              (not gnus-uu-be-dangerous)
1310              (or (eq gnus-uu-be-dangerous t)
1311                  (gnus-y-or-n-p
1312                   (format "Delete incomplete file %s? " result-file)))
1313              (delete-file result-file))
1314
1315         ;; If this was a file of the wrong sort, then
1316         (when (and (or (memq 'wrong-type process-state)
1317                        (memq 'error process-state))
1318                    gnus-uu-unmark-articles-not-decoded)
1319           (gnus-summary-tick-article article t))
1320
1321         ;; Set the new series state.
1322         (if (and (not has-been-begin)
1323                  (not sloppy)
1324                  (or (memq 'end process-state)
1325                      (memq 'middle process-state)))
1326             (progn
1327               (setq process-state (list 'error))
1328               (gnus-message 2 "No begin part at the beginning")
1329               (sleep-for 2))
1330           (setq state 'middle)))
1331
1332       ;; When there are no result-files, then something must be wrong.
1333       (if result-files
1334           (message "")
1335         (cond
1336          ((not has-been-begin)
1337           (gnus-message 2 "Wrong type file"))
1338          ((memq 'error process-state)
1339           (gnus-message 2 "An error occurred during decoding"))
1340          ((not (or (memq 'ok process-state)
1341                    (memq 'end process-state)))
1342           (gnus-message 2 "End of articles reached before end of file")))
1343         ;; Make unsuccessfully decoded articles unread.
1344         (when gnus-uu-unmark-articles-not-decoded
1345           (while article-series
1346             (gnus-summary-tick-article (pop article-series) t)))))
1347
1348     ;; The original article buffer is hosed, shoot it down.
1349     (gnus-kill-buffer gnus-original-article-buffer)
1350     (setq gnus-current-article nil)
1351     result-files))
1352
1353 (defun gnus-uu-grab-view (file)
1354   "View FILE using the gnus-uu methods."
1355   (let ((action (gnus-uu-get-action file)))
1356     (gnus-execute-command
1357      (if (string-match "%" action)
1358          (format action file)
1359        (concat action " " file))
1360      (eq gnus-view-pseudos 'not-confirm))))
1361
1362 (defun gnus-uu-grab-move (file)
1363   "Move FILE to somewhere."
1364   (when gnus-uu-default-dir
1365     (let ((to-file (concat (file-name-as-directory gnus-uu-default-dir)
1366                            (file-name-nondirectory file))))
1367       (cond ((fboundp 'make-symbolic-link)
1368              (rename-file file to-file)
1369              (unless (file-exists-p file)
1370                (make-symbolic-link to-file file)))
1371             (t
1372              (copy-file file to-file))))))
1373
1374 (defun gnus-uu-part-number (article)
1375   (let* ((header (gnus-summary-article-header article))
1376          (subject (and header (mail-header-subject header)))
1377          (part nil))
1378     (if subject
1379         (while (string-match "[0-9]+/[0-9]+\\|[0-9]+[ \t]+of[ \t]+[0-9]+"
1380                              subject)
1381           (setq part (match-string 0 subject))
1382           (setq subject (substring subject (match-end 0)))))
1383     (or part
1384         (while (string-match "\\([0-9]+\\)[^0-9]+\\([0-9]+\\)" subject)
1385           (setq part (match-string 0 subject))
1386           (setq subject (substring subject (match-end 0)))))
1387     (or part "")))
1388
1389 (defun gnus-uu-uudecode-sentinel (process event)
1390   (delete-process (get-process process)))
1391
1392 (defun gnus-uu-uustrip-article (process-buffer in-state)
1393   ;; Uudecodes a file asynchronously.
1394   (save-excursion
1395     (set-buffer process-buffer)
1396     (let ((state (list 'wrong-type))
1397           process-connection-type case-fold-search buffer-read-only
1398           files start-char)
1399       (goto-char (point-min))
1400
1401       ;; Deal with ^M at the end of the lines.
1402       (when gnus-uu-kill-carriage-return
1403         (save-excursion
1404           (while (search-forward "\r" nil t)
1405             (delete-backward-char 1))))
1406
1407       (while (or (re-search-forward gnus-uu-begin-string nil t)
1408                  (re-search-forward gnus-uu-body-line nil t))
1409         (setq state (list 'ok))
1410         ;; Ok, we are at the first uucoded line.
1411         (beginning-of-line)
1412         (setq start-char (point))
1413
1414         (if (not (looking-at gnus-uu-begin-string))
1415             (setq state (list 'middle))
1416           ;; This is the beginning of a uuencoded article.
1417           ;; We replace certain characters that could make things messy.
1418           (setq gnus-uu-file-name
1419                 (let ((nnheader-file-name-translation-alist
1420                        '((?/ . ?,) (?  . ?_) (?* . ?_) (?$ . ?_))))
1421                   (nnheader-translate-file-chars (match-string 1))))
1422           (replace-match (concat "begin 644 " gnus-uu-file-name) t t)
1423
1424           ;; Remove any non gnus-uu-body-line right after start.
1425           (forward-line 1)
1426           (while (and (not (eobp))
1427                       (not (looking-at gnus-uu-body-line)))
1428             (gnus-delete-line))
1429
1430           ;; If a process is running, we kill it.
1431           (when (and gnus-uu-uudecode-process
1432                      (memq (process-status gnus-uu-uudecode-process)
1433                            '(run stop)))
1434             (delete-process gnus-uu-uudecode-process)
1435             (gnus-uu-unmark-list-of-grabbed t))
1436
1437           ;; Start a new uudecoding process.
1438           (let ((cdir default-directory))
1439             (unwind-protect
1440                 (progn
1441                   (cd gnus-uu-work-dir)
1442                   (setq gnus-uu-uudecode-process
1443                         (start-process
1444                          "*uudecode*"
1445                          (gnus-get-buffer-create gnus-uu-output-buffer-name)
1446                          shell-file-name shell-command-switch
1447                          (format "cd %s %s uudecode" gnus-uu-work-dir
1448                                  gnus-shell-command-separator))))
1449               (cd cdir)))
1450           (set-process-sentinel
1451            gnus-uu-uudecode-process 'gnus-uu-uudecode-sentinel)
1452           (setq state (list 'begin))
1453           (push (concat gnus-uu-work-dir gnus-uu-file-name) files))
1454
1455         ;; We look for the end of the thing to be decoded.
1456         (if (re-search-forward gnus-uu-end-string nil t)
1457             (push 'end state)
1458           (goto-char (point-max))
1459           (re-search-backward gnus-uu-body-line nil t))
1460
1461         (forward-line 1)
1462
1463         (when gnus-uu-uudecode-process
1464           (when (memq (process-status gnus-uu-uudecode-process) '(run stop))
1465             ;; Try to correct mishandled uucode.
1466             (when gnus-uu-correct-stripped-uucode
1467               (gnus-uu-check-correct-stripped-uucode start-char (point)))
1468             (gnus-run-hooks 'gnus-uu-pre-uudecode-hook)
1469
1470             ;; Send the text to the process.
1471             (condition-case nil
1472                 (process-send-region
1473                  gnus-uu-uudecode-process start-char (point))
1474               (error
1475                (progn
1476                  (delete-process gnus-uu-uudecode-process)
1477                  (gnus-message 2 "gnus-uu: Couldn't uudecode")
1478                  (setq state (list 'wrong-type)))))
1479
1480             (if (memq 'end state)
1481                 (progn
1482                   ;; Send an EOF, just in case.
1483                   (ignore-errors
1484                     (process-send-eof gnus-uu-uudecode-process))
1485                   (while (memq (process-status gnus-uu-uudecode-process)
1486                                '(open run))
1487                     (accept-process-output gnus-uu-uudecode-process 1)))
1488               (when (or (not gnus-uu-uudecode-process)
1489                         (not (memq (process-status gnus-uu-uudecode-process)
1490                                    '(run stop))))
1491                 (setq state (list 'wrong-type)))))))
1492
1493       (if (memq 'begin state)
1494           (cons (if (= (length files) 1) (car files) files) state)
1495         state))))
1496
1497 (defvar gnus-uu-unshar-warning
1498   "*** WARNING ***
1499
1500 Shell archives are an archaic method of bundling files for distribution
1501 across computer networks.  During the unpacking process, arbitrary commands
1502 are executed on your system, and all kinds of nasty things can happen.
1503 Please examine the archive very carefully before you instruct Emacs to
1504 unpack it.  You can browse the archive buffer using \\[scroll-other-window].
1505
1506 If you are unsure what to do, please answer \"no\"."
1507   "Text of warning message displayed by `gnus-uu-unshar-article'.
1508 Make sure that this text consists only of few text lines.  Otherwise,
1509 Gnus might fail to display all of it.")
1510
1511
1512 ;; This function is used by `gnus-uu-grab-articles' to treat
1513 ;; a shared article.
1514 (defun gnus-uu-unshar-article (process-buffer in-state)
1515   (let ((state (list 'ok))
1516         start-char)
1517     (save-excursion
1518       (set-buffer process-buffer)
1519       (goto-char (point-min))
1520       (if (not (re-search-forward gnus-uu-shar-begin-string nil t))
1521           (setq state (list 'wrong-type))
1522         (save-window-excursion
1523           (save-excursion
1524             (switch-to-buffer (current-buffer))
1525             (delete-other-windows)
1526             (let ((buffer (get-buffer-create (generate-new-buffer-name
1527                                               "*Warning*"))))
1528               (unless
1529                   (unwind-protect
1530                       (with-current-buffer buffer
1531                         (insert (substitute-command-keys
1532                                  gnus-uu-unshar-warning))
1533                         (goto-char (point-min))
1534                         (display-buffer buffer)
1535                         (yes-or-no-p "This is a shell archive, unshar it? "))
1536                     (kill-buffer buffer))
1537                 (setq state (list 'error))))))
1538         (unless (memq 'error state)
1539           (beginning-of-line)
1540           (setq start-char (point))
1541           (call-process-region
1542            start-char (point-max) shell-file-name nil
1543            (gnus-get-buffer-create gnus-uu-output-buffer-name) nil
1544            shell-command-switch
1545            (concat "cd " gnus-uu-work-dir " "
1546                    gnus-shell-command-separator  " sh")))))
1547     state))
1548
1549 ;; Returns the name of what the shar file is going to unpack.
1550 (defun gnus-uu-find-name-in-shar ()
1551   (let ((oldpoint (point))
1552         res)
1553     (goto-char (point-min))
1554     (when (re-search-forward gnus-uu-shar-name-marker nil t)
1555       (setq res (buffer-substring (match-beginning 1) (match-end 1))))
1556     (goto-char oldpoint)
1557     res))
1558
1559 ;; `gnus-uu-choose-action' chooses what action to perform given the name
1560 ;; and `gnus-uu-file-action-list'.  Returns either nil if no action is
1561 ;; found, or the name of the command to run if such a rule is found.
1562 (defun gnus-uu-choose-action (file-name file-action-list &optional no-ignore)
1563   (let ((action-list (copy-sequence file-action-list))
1564         (case-fold-search t)
1565         rule action)
1566     (and
1567      (unless no-ignore
1568        (and (not
1569              (and gnus-uu-ignore-files-by-name
1570                   (string-match gnus-uu-ignore-files-by-name file-name)))
1571             (not
1572              (and gnus-uu-ignore-files-by-type
1573                   (string-match gnus-uu-ignore-files-by-type
1574                                 (or (gnus-uu-choose-action
1575                                      file-name gnus-uu-ext-to-mime-list t)
1576                                     ""))))))
1577      (while (not (or (eq action-list ()) action))
1578        (setq rule (car action-list))
1579        (setq action-list (cdr action-list))
1580        (when (string-match (car rule) file-name)
1581          (setq action (cadr rule)))))
1582     action))
1583
1584 (defun gnus-uu-treat-archive (file-path)
1585   ;; Unpacks an archive.  Returns t if unpacking is successful.
1586   (let ((did-unpack t)
1587         action command dir)
1588     (setq action (gnus-uu-choose-action
1589                   file-path (append gnus-uu-user-archive-rules
1590                                     (if gnus-uu-ignore-default-archive-rules
1591                                         nil
1592                                       gnus-uu-default-archive-rules))))
1593
1594     (when (not action)
1595       (error "No unpackers for the file %s" file-path))
1596
1597     (string-match "/[^/]*$" file-path)
1598     (setq dir (substring file-path 0 (match-beginning 0)))
1599
1600     (when (member action gnus-uu-destructive-archivers)
1601       (copy-file file-path (concat file-path "~") t))
1602
1603     (setq command (format "cd %s ; %s" dir (gnus-uu-command action file-path)))
1604
1605     (save-excursion
1606       (set-buffer (gnus-get-buffer-create gnus-uu-output-buffer-name))
1607       (erase-buffer))
1608
1609     (gnus-message 5 "Unpacking: %s..." (gnus-uu-command action file-path))
1610
1611     (if (= 0 (call-process shell-file-name nil
1612                            (gnus-get-buffer-create gnus-uu-output-buffer-name)
1613                            nil shell-command-switch command))
1614         (message "")
1615       (gnus-message 2 "Error during unpacking of archive")
1616       (setq did-unpack nil))
1617
1618     (when (member action gnus-uu-destructive-archivers)
1619       (rename-file (concat file-path "~") file-path t))
1620
1621     did-unpack))
1622
1623 (defun gnus-uu-dir-files (dir)
1624   (let ((dirs (directory-files dir t "[^/][^\\.][^\\.]?$"))
1625         files file)
1626     (while dirs
1627       (if (file-directory-p (setq file (car dirs)))
1628           (setq files (append files (gnus-uu-dir-files file)))
1629         (push file files))
1630       (setq dirs (cdr dirs)))
1631     files))
1632
1633 (defun gnus-uu-unpack-files (files &optional ignore)
1634   ;; Go through FILES and look for files to unpack.
1635   (let* ((totfiles (gnus-uu-ls-r gnus-uu-work-dir))
1636          (ofiles files)
1637          file did-unpack)
1638     (while files
1639       (setq file (cdr (assq 'name (car files))))
1640       (when (and (not (member file ignore))
1641                  (equal (gnus-uu-get-action (file-name-nondirectory file))
1642                         "gnus-uu-archive"))
1643         (push file did-unpack)
1644         (unless (gnus-uu-treat-archive file)
1645           (gnus-message 2 "Error during unpacking of %s" file))
1646         (let* ((newfiles (gnus-uu-ls-r gnus-uu-work-dir))
1647                (nfiles newfiles))
1648           (while nfiles
1649             (unless (member (car nfiles) totfiles)
1650               (push (list (cons 'name (car nfiles))
1651                           (cons 'original file))
1652                     ofiles))
1653             (setq nfiles (cdr nfiles)))
1654           (setq totfiles newfiles)))
1655       (setq files (cdr files)))
1656     (if did-unpack
1657         (gnus-uu-unpack-files ofiles (append did-unpack ignore))
1658       ofiles)))
1659
1660 (defun gnus-uu-ls-r (dir)
1661   (let* ((files (gnus-uu-directory-files dir t))
1662          (ofiles files))
1663     (while files
1664       (when (file-directory-p (car files))
1665         (setq ofiles (delete (car files) ofiles))
1666         (setq ofiles (append ofiles (gnus-uu-ls-r (car files)))))
1667       (setq files (cdr files)))
1668     ofiles))
1669
1670 ;; Various stuff
1671
1672 (defun gnus-uu-directory-files (dir &optional full)
1673   (let (files out file)
1674     (setq files (directory-files dir full))
1675     (while files
1676       (setq file (car files))
1677       (setq files (cdr files))
1678       (unless (member (file-name-nondirectory file) '("." ".."))
1679         (push file out)))
1680     (setq out (nreverse out))
1681     out))
1682
1683 (defun gnus-uu-check-correct-stripped-uucode (start end)
1684   (save-excursion
1685     (let (found beg length)
1686       (if (not gnus-uu-correct-stripped-uucode)
1687           ()
1688         (goto-char start)
1689
1690         (if (re-search-forward " \\|`" end t)
1691             (progn
1692               (goto-char start)
1693               (while (not (eobp))
1694                 (progn
1695                   (when (looking-at "\n")
1696                     (replace-match ""))
1697                   (forward-line 1))))
1698
1699           (while (not (eobp))
1700             (if (looking-at (concat gnus-uu-begin-string "\\|"
1701                                     gnus-uu-end-string))
1702                 ()
1703               (when (not found)
1704                 (beginning-of-line)
1705                 (setq beg (point))
1706                 (end-of-line)
1707                 (setq length (- (point) beg)))
1708               (setq found t)
1709               (beginning-of-line)
1710               (setq beg (point))
1711               (end-of-line)
1712               (when (not (= length (- (point) beg)))
1713                 (insert (make-string (- length (- (point) beg)) ? ))))
1714             (forward-line 1)))))))
1715
1716 (defvar gnus-uu-tmp-alist nil)
1717
1718 (defun gnus-uu-initialize (&optional scan)
1719   (let (entry)
1720     (if (and (not scan)
1721              (when (setq entry (assoc gnus-newsgroup-name gnus-uu-tmp-alist))
1722                (if (file-exists-p (cdr entry))
1723                    (setq gnus-uu-work-dir (cdr entry))
1724                  (setq gnus-uu-tmp-alist (delq entry gnus-uu-tmp-alist))
1725                  nil)))
1726         t
1727       (setq gnus-uu-tmp-dir (file-name-as-directory
1728                              (expand-file-name gnus-uu-tmp-dir)))
1729       (if (not (file-directory-p gnus-uu-tmp-dir))
1730           (error "Temp directory %s doesn't exist" gnus-uu-tmp-dir)
1731         (when (not (file-writable-p gnus-uu-tmp-dir))
1732           (error "Temp directory %s can't be written to"
1733                  gnus-uu-tmp-dir)))
1734
1735       (setq gnus-uu-work-dir
1736             (mm-make-temp-file (concat gnus-uu-tmp-dir "gnus") 'dir))
1737       (set-file-modes gnus-uu-work-dir 448)
1738       (setq gnus-uu-work-dir (file-name-as-directory gnus-uu-work-dir))
1739       (push (cons gnus-newsgroup-name gnus-uu-work-dir)
1740             gnus-uu-tmp-alist))))
1741
1742
1743 ;; Kills the temporary uu buffers, kills any processes, etc.
1744 (defun gnus-uu-clean-up ()
1745   (let (buf)
1746     (and gnus-uu-uudecode-process
1747          (memq (process-status (or gnus-uu-uudecode-process "nevair"))
1748                '(stop run))
1749          (delete-process gnus-uu-uudecode-process))
1750     (when (setq buf (get-buffer gnus-uu-output-buffer-name))
1751       (kill-buffer buf))))
1752
1753 (defun gnus-quote-arg-for-sh-or-csh (arg)
1754   (let ((pos 0) new-pos accum)
1755     ;; *** bug: we don't handle newline characters properly
1756     (while (setq new-pos (string-match "[!`\"$\\& \t{}]" arg pos))
1757       (push (substring arg pos new-pos) accum)
1758       (push "\\" accum)
1759       (push (list (aref arg new-pos)) accum)
1760       (setq pos (1+ new-pos)))
1761     (if (= pos 0)
1762         arg
1763       (apply 'concat (nconc (nreverse accum) (list (substring arg pos)))))))
1764
1765 ;; Inputs an action and a filename and returns a full command, making sure
1766 ;; that the filename will be treated as a single argument when the shell
1767 ;; executes the command.
1768 (defun gnus-uu-command (action file)
1769   (let ((quoted-file (gnus-quote-arg-for-sh-or-csh file)))
1770     (if (string-match "%s" action)
1771         (format action quoted-file)
1772       (concat action " " quoted-file))))
1773
1774 (defun gnus-uu-delete-work-dir (&optional dir)
1775   "Delete recursively all files and directories under `gnus-uu-work-dir'."
1776   (if dir
1777       (gnus-message 7 "Deleting directory %s..." dir)
1778     (setq dir gnus-uu-work-dir))
1779   (when (and dir
1780              (file-exists-p dir))
1781     (let ((files (directory-files dir t nil t))
1782           file)
1783       (while (setq file (pop files))
1784         (unless (member (file-name-nondirectory file) '("." ".."))
1785           (if (file-directory-p file)
1786               (gnus-uu-delete-work-dir file)
1787             (gnus-message 9 "Deleting file %s..." file)
1788             (delete-file file))))
1789       (delete-directory dir)))
1790   (gnus-message 7 ""))
1791
1792 ;; Initializing
1793
1794 (add-hook 'gnus-exit-group-hook 'gnus-uu-clean-up)
1795 (add-hook 'gnus-exit-group-hook 'gnus-uu-delete-work-dir)
1796
1797 \f
1798
1799 ;;;
1800 ;;; uuencoded posting
1801 ;;;
1802
1803 ;; Any function that is to be used as and encoding method will take two
1804 ;; parameters: PATH-NAME and FILE-NAME.  (E.g. "/home/gaga/spiral.jpg"
1805 ;; and "spiral.jpg", respectively.) The function should return nil if
1806 ;; the encoding wasn't successful.
1807 (defcustom gnus-uu-post-encode-method 'gnus-uu-post-encode-uuencode
1808   "Function used for encoding binary files.
1809 There are three functions supplied with gnus-uu for encoding files:
1810 `gnus-uu-post-encode-uuencode', which does straight uuencoding;
1811 `gnus-uu-post-encode-mime', which encodes with base64 and adds MIME
1812 headers; and `gnus-uu-post-encode-mime-uuencode', which encodes with
1813 uuencode and adds MIME headers."
1814   :group 'gnus-extract-post
1815   :type '(radio (function-item gnus-uu-post-encode-uuencode)
1816                 (function-item gnus-uu-post-encode-mime)
1817                 (function-item gnus-uu-post-encode-mime-uuencode)
1818                 (function :tag "Other")))
1819
1820 (defcustom gnus-uu-post-include-before-composing nil
1821   "Non-nil means that gnus-uu will ask for a file to encode before you compose the article.
1822 If this variable is t, you can either include an encoded file with
1823 \\[gnus-uu-post-insert-binary-in-article] or have one included for you when you post the article."
1824   :group 'gnus-extract-post
1825   :type 'boolean)
1826
1827 (defcustom gnus-uu-post-length 990
1828   "Maximum length of an article.
1829 The encoded file will be split into how many articles it takes to
1830 post the entire file."
1831   :group 'gnus-extract-post
1832   :type 'integer)
1833
1834 (defcustom gnus-uu-post-threaded nil
1835   "Non-nil means that gnus-uu will post the encoded file in a thread.
1836 This may not be smart, as no other decoder I have seen are able to
1837 follow threads when collecting uuencoded articles.  (Well, I have seen
1838 one package that does that - gnus-uu, but somehow, I don't think that
1839 counts...)  The default is nil."
1840   :group 'gnus-extract-post
1841   :type 'boolean)
1842
1843 (defcustom gnus-uu-post-separate-description t
1844   "Non-nil means that the description will be posted in a separate article.
1845 The first article will typically be numbered (0/x).  If this variable
1846 is nil, the description the user enters will be included at the
1847 beginning of the first article, which will be numbered (1/x).  Default
1848 is t."
1849   :group 'gnus-extract-post
1850   :type 'boolean)
1851
1852 (defvar gnus-uu-post-binary-separator "--binary follows this line--")
1853 (defvar gnus-uu-post-message-id nil)
1854 (defvar gnus-uu-post-inserted-file-name nil)
1855 (defvar gnus-uu-winconf-post-news nil)
1856
1857 (defun gnus-uu-post-news ()
1858   "Compose an article and post an encoded file."
1859   (interactive)
1860   (setq gnus-uu-post-inserted-file-name nil)
1861   (setq gnus-uu-winconf-post-news (current-window-configuration))
1862
1863   (gnus-summary-post-news)
1864
1865   (let ((map (make-sparse-keymap)))
1866     (set-keymap-parent map (current-local-map))
1867     (use-local-map map))
1868   ;;(local-set-key "\C-c\C-c" 'gnus-summary-edit-article-done)
1869   (local-set-key "\C-c\C-c" 'gnus-uu-post-news-inews)
1870   (local-set-key "\C-c\C-s" 'gnus-uu-post-news-inews)
1871   (local-set-key "\C-c\C-i" 'gnus-uu-post-insert-binary-in-article)
1872
1873   (when gnus-uu-post-include-before-composing
1874     (save-excursion (setq gnus-uu-post-inserted-file-name
1875                           (gnus-uu-post-insert-binary)))))
1876
1877 (defun gnus-uu-post-insert-binary-in-article ()
1878   "Inserts an encoded file in the buffer.
1879 The user will be asked for a file name."
1880   (interactive)
1881   (save-excursion
1882     (setq gnus-uu-post-inserted-file-name (gnus-uu-post-insert-binary))))
1883
1884 ;; Encodes with uuencode and substitutes all spaces with backticks.
1885 (defun gnus-uu-post-encode-uuencode (path file-name)
1886   (when (gnus-uu-post-encode-file "uuencode" path file-name)
1887     (goto-char (point-min))
1888     (forward-line 1)
1889     (while (re-search-forward " " nil t)
1890       (replace-match "`"))
1891     t))
1892
1893 ;; Encodes with uuencode and adds MIME headers.
1894 (defun gnus-uu-post-encode-mime-uuencode (path file-name)
1895   (when (gnus-uu-post-encode-uuencode path file-name)
1896     (gnus-uu-post-make-mime file-name "x-uue")
1897     t))
1898
1899 ;; Encodes with base64 and adds MIME headers
1900 (defun gnus-uu-post-encode-mime (path file-name)
1901   (when (zerop (call-process shell-file-name nil t nil shell-command-switch
1902                              (format "%s %s -o %s" "mmencode" path file-name)))
1903     (gnus-uu-post-make-mime file-name "base64")
1904     t))
1905
1906 ;; Adds MIME headers.
1907 (defun gnus-uu-post-make-mime (file-name encoding)
1908   (goto-char (point-min))
1909   (insert (format "Content-Type: %s; name=\"%s\"\n"
1910                   (gnus-uu-choose-action file-name gnus-uu-ext-to-mime-list)
1911                   file-name))
1912   (insert (format "Content-Transfer-Encoding: %s\n\n" encoding))
1913   (save-restriction
1914     (set-buffer gnus-message-buffer)
1915     (goto-char (point-min))
1916     (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
1917     (forward-line -1)
1918     (narrow-to-region (point-min) (point))
1919     (unless (mail-fetch-field "mime-version")
1920       (widen)
1921       (insert "MIME-Version: 1.0\n"))
1922     (widen)))
1923
1924 ;; Encodes a file PATH with COMMAND, leaving the result in the
1925 ;; current buffer.
1926 (defun gnus-uu-post-encode-file (command path file-name)
1927   (= 0 (call-process shell-file-name nil t nil shell-command-switch
1928                      (format "%s %s %s" command path file-name))))
1929
1930 (defun gnus-uu-post-news-inews ()
1931   "Posts the composed news article and encoded file.
1932 If no file has been included, the user will be asked for a file."
1933   (interactive)
1934
1935   (let (file-name)
1936
1937     (if gnus-uu-post-inserted-file-name
1938         (setq file-name gnus-uu-post-inserted-file-name)
1939       (setq file-name (gnus-uu-post-insert-binary)))
1940
1941     (gnus-uu-post-encoded file-name gnus-uu-post-threaded))
1942   (setq gnus-uu-post-inserted-file-name nil)
1943   (when gnus-uu-winconf-post-news
1944     (set-window-configuration gnus-uu-winconf-post-news)))
1945
1946 ;; Asks for a file to encode, encodes it and inserts the result in
1947 ;; the current buffer.  Returns the file name the user gave.
1948 (defun gnus-uu-post-insert-binary ()
1949   (let ((uuencode-buffer-name "*uuencode buffer*")
1950         file-path uubuf file-name)
1951
1952     (setq file-path (read-file-name
1953                      "What file do you want to encode? "))
1954     (when (not (file-exists-p file-path))
1955       (error "%s: No such file" file-path))
1956
1957     (goto-char (point-max))
1958     (insert (format "\n%s\n" gnus-uu-post-binary-separator))
1959
1960     ;; #### Unix-specific?
1961     (when (string-match "^~/" file-path)
1962       (setq file-path (concat "$HOME" (substring file-path 1))))
1963     ;; #### Unix-specific?
1964     (if (string-match "/[^/]*$" file-path)
1965         (setq file-name (substring file-path (1+ (match-beginning 0))))
1966       (setq file-name file-path))
1967
1968     (unwind-protect
1969         (if (save-excursion
1970               (set-buffer (setq uubuf
1971                                 (gnus-get-buffer-create uuencode-buffer-name)))
1972               (erase-buffer)
1973               (funcall gnus-uu-post-encode-method file-path file-name))
1974             (insert-buffer-substring uubuf)
1975           (error "Encoding unsuccessful"))
1976       (kill-buffer uubuf))
1977     file-name))
1978
1979 ;; Posts the article and all of the encoded file.
1980 (defun gnus-uu-post-encoded (file-name &optional threaded)
1981   (let ((send-buffer-name "*uuencode send buffer*")
1982         (encoded-buffer-name "*encoded buffer*")
1983         (top-string "[ cut here %s (%s %d/%d) %s gnus-uu ]")
1984         (separator (concat mail-header-separator "\n\n"))
1985         uubuf length parts header i end beg
1986         beg-line minlen post-buf whole-len beg-binary end-binary)
1987
1988     (setq post-buf (current-buffer))
1989
1990     (goto-char (point-min))
1991     (when (not (re-search-forward
1992                 (if gnus-uu-post-separate-description
1993                     (concat "^" (regexp-quote gnus-uu-post-binary-separator)
1994                             "$")
1995                   (concat "^" (regexp-quote mail-header-separator) "$"))
1996                 nil t))
1997       (error "Internal error: No binary/header separator"))
1998     (beginning-of-line)
1999     (forward-line 1)
2000     (setq beg-binary (point))
2001     (setq end-binary (point-max))
2002
2003     (save-excursion
2004       (set-buffer (setq uubuf (gnus-get-buffer-create encoded-buffer-name)))
2005       (erase-buffer)
2006       (insert-buffer-substring post-buf beg-binary end-binary)
2007       (goto-char (point-min))
2008       (setq length (count-lines (point-min) (point-max)))
2009       (setq parts (/ length gnus-uu-post-length))
2010       (unless (< (% length gnus-uu-post-length) 4)
2011         (incf parts)))
2012
2013     (when gnus-uu-post-separate-description
2014       (forward-line -1))
2015     (delete-region (point) (point-max))
2016
2017     (goto-char (point-min))
2018     (re-search-forward
2019      (concat "^" (regexp-quote mail-header-separator) "$") nil t)
2020     (beginning-of-line)
2021     (setq header (buffer-substring (point-min) (point)))
2022
2023     (goto-char (point-min))
2024     (when gnus-uu-post-separate-description
2025       (when (re-search-forward "^Subject: " nil t)
2026         (end-of-line)
2027         (insert (format " (0/%d)" parts)))
2028       (save-excursion
2029         (message-send))
2030       (setq gnus-uu-post-message-id (message-fetch-field "message-id")))
2031
2032     (save-excursion
2033       (setq i 1)
2034       (setq beg 1)
2035       (while (not (> i parts))
2036         (set-buffer (gnus-get-buffer-create send-buffer-name))
2037         (erase-buffer)
2038         (insert header)
2039         (when (and threaded gnus-uu-post-message-id)
2040           (insert "References: " gnus-uu-post-message-id "\n"))
2041         (insert separator)
2042         (setq whole-len
2043               (- 62 (length (format top-string "" file-name i parts ""))))
2044         (when (> 1 (setq minlen (/ whole-len 2)))
2045           (setq minlen 1))
2046         (setq
2047          beg-line
2048          (format top-string
2049                  (make-string minlen ?-)
2050                  file-name i parts
2051                  (make-string
2052                   (if (= 0 (% whole-len 2)) (1- minlen) minlen) ?-)))
2053
2054         (goto-char (point-min))
2055         (when (re-search-forward "^Subject: " nil t)
2056           (end-of-line)
2057           (insert (format " (%d/%d)" i parts)))
2058
2059         (goto-char (point-max))
2060         (save-excursion
2061           (set-buffer uubuf)
2062           (goto-char beg)
2063           (if (= i parts)
2064               (goto-char (point-max))
2065             (forward-line gnus-uu-post-length))
2066           (when (and (= (1+ i) parts) (< (count-lines (point) (point-max)) 4))
2067             (forward-line -4))
2068           (setq end (point)))
2069         (insert-buffer-substring uubuf beg end)
2070         (insert beg-line "\n")
2071         (setq beg end)
2072         (incf i)
2073         (goto-char (point-min))
2074         (re-search-forward
2075          (concat "^" (regexp-quote mail-header-separator) "$") nil t)
2076         (beginning-of-line)
2077         (forward-line 2)
2078         (when (re-search-forward
2079                (concat "^" (regexp-quote gnus-uu-post-binary-separator) "$")
2080                nil t)
2081           (replace-match "")
2082           (forward-line 1))
2083         (insert beg-line)
2084         (insert "\n")
2085         (let (message-sent-message-via)
2086           (save-excursion
2087             (message-send))
2088           (setq gnus-uu-post-message-id
2089                 (concat (message-fetch-field "references") " "
2090                         (message-fetch-field "message-id"))))))
2091
2092     (gnus-kill-buffer send-buffer-name)
2093     (gnus-kill-buffer encoded-buffer-name)
2094
2095     (when (not gnus-uu-post-separate-description)
2096       (set-buffer-modified-p nil)
2097       (when (fboundp 'bury-buffer)
2098         (bury-buffer)))))
2099
2100 (provide 'gnus-uu)
2101
2102 ;;; gnus-uu.el ends here