ba5315462606fa7cf9f438ea9e51b777e56dcde4
[elisp/gnus.git-] / lisp / uudecode.el
1 ;;; uudecode.el -- elisp native uudecode
2
3 ;; Copyright (c) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
4
5 ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu>
6 ;; Keywords: uudecode news
7
8 ;; This file is a part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; This looks as though it could be made rather more efficient for
28 ;; internal working.  Encoding could use a lookup table and decoding
29 ;; should presumably use a vector or list buffer for partial results
30 ;; rather than with-current-buffer.  -- fx
31
32 ;;; Code:
33
34 (autoload 'executable-find "executable")
35
36 (eval-when-compile (require 'cl))
37
38 (eval-and-compile
39   (defalias 'uudecode-char-int
40     (if (fboundp 'char-int)
41         'char-int
42       'identity))
43
44   (if (featurep 'xemacs)
45       (defalias 'uudecode-insert-char 'insert-char)
46     (defun uudecode-insert-char (char &optional count ignored buffer)
47       (if (or (null buffer) (eq buffer (current-buffer)))
48           (insert-char char count)
49         (with-current-buffer buffer
50           (insert-char char count))))))
51
52 (defcustom uudecode-decoder-program "uudecode"
53   "*Non-nil value should be a string that names a uu decoder.
54 The program should expect to read uu data on its standard
55 input and write the converted data to its standard output."
56   :type 'string
57   :group 'gnus-extract)
58
59 (defcustom uudecode-decoder-switches nil
60   "*List of command line flags passed to `uudecode-decoder-program'."
61   :group 'gnus-extract
62   :type '(repeat string))
63
64 (defcustom uudecode-use-external 
65   (executable-find uudecode-decoder-program)
66   "*Use external uudecode program."
67   :group 'gnus-extract
68   :type 'boolean)
69
70 (defconst uudecode-alphabet "\040-\140")
71
72 (defconst uudecode-begin-line "^begin[ \t]+[0-7][0-7][0-7][ \t]+\\(.*\\)$")
73 (defconst uudecode-end-line "^end[ \t]*$")
74
75 (defconst uudecode-body-line
76   (let ((i 61) (str "^M"))
77     (while (> (setq i (1- i)) 0)
78       (setq str (concat str "[^a-z]")))
79     (concat str ".?$")))
80
81 (defvar uudecode-temporary-file-directory
82   (cond ((fboundp 'temp-directory) (temp-directory))
83         ((boundp 'temporary-file-directory) temporary-file-directory)
84         ("/tmp")))
85
86 ;;;###autoload
87 (defun uudecode-decode-region-external (start end &optional file-name)
88   "Uudecode region between START and END using external program.
89 If FILE-NAME is non-nil, save the result to FILE-NAME.  The program
90 used is specified by `uudecode-decoder-program'."
91   (interactive "r\nP")
92   (let ((cbuf (current-buffer)) tempfile firstline status)
93     (save-excursion
94       (goto-char start)
95       (when (re-search-forward uudecode-begin-line nil t)
96         (forward-line 1)
97         (setq firstline (point))
98         (cond ((null file-name))
99               ((stringp file-name))
100               (t
101                (setq file-name (read-file-name "File to Name:"
102                                                nil nil nil
103                                                (match-string 1)))))
104         (setq tempfile (if file-name
105                            (expand-file-name file-name)
106                          (if (fboundp 'make-temp-file)
107                              (let ((temporary-file-directory
108                                     uudecode-temporary-file-directory))
109                                (make-temp-file "uu"))
110                            (expand-file-name
111                             (make-temp-name "uu")
112                             uudecode-temporary-file-directory))))
113         (let ((cdir default-directory)
114               default-process-coding-system)
115           (unwind-protect
116               (with-temp-buffer
117                 (insert "begin 600 " (file-name-nondirectory tempfile) "\n")
118                 (insert-buffer-substring cbuf firstline end)
119                 (cd (file-name-directory tempfile))
120                 (apply 'call-process-region
121                        (point-min)
122                        (point-max)
123                        uudecode-decoder-program
124                        nil
125                        nil
126                        nil
127                        uudecode-decoder-switches))
128             (cd cdir) (set-buffer cbuf)))
129         (if (file-exists-p tempfile)
130             (unless file-name
131               (goto-char start)
132               (delete-region start end)
133               (let (format-alist)
134                 (insert-file-contents-literally tempfile)))
135           (message "Can not uudecode")))
136       (ignore-errors (or file-name (delete-file tempfile))))))
137
138 ;;;###autoload
139 (defun uudecode-decode-region-internal (start end &optional file-name)
140   "Uudecode region between START and END without using an external program.
141 If FILE-NAME is non-nil, save the result to FILE-NAME."
142   (interactive "r\nP")
143   (let ((work-buffer nil)
144         (done nil)
145         (counter 0)
146         (remain 0)
147         (bits 0)
148         (lim 0) inputpos
149         (non-data-chars (concat "^" uudecode-alphabet)))
150     (unwind-protect
151         (save-excursion
152           (goto-char start)
153           (when (re-search-forward uudecode-begin-line nil t)
154             (cond ((null file-name))
155                   ((stringp file-name))
156                   (t
157                    (setq file-name (expand-file-name
158                                     (read-file-name "File to Name:"
159                                                     nil nil nil
160                                                     (match-string 1))))))
161             (setq work-buffer (generate-new-buffer " *uudecode-work*"))
162             (forward-line 1)
163             (skip-chars-forward non-data-chars end)
164             (while (not done)
165               (setq inputpos (point))
166               (setq remain 0 bits 0 counter 0)
167               (cond
168                ((> (skip-chars-forward uudecode-alphabet end) 0)
169                 (setq lim (point))
170                 (setq remain
171                       (logand (- (uudecode-char-int (char-after inputpos)) 32)
172                               63))
173                 (setq inputpos (1+ inputpos))
174                 (if (= remain 0) (setq done t))
175                 (while (and (< inputpos lim) (> remain 0))
176                   (setq bits (+ bits
177                                 (logand
178                                  (-
179                                   (uudecode-char-int (char-after inputpos)) 32)
180                                  63)))
181                   (if (/= counter 0) (setq remain (1- remain)))
182                   (setq counter (1+ counter)
183                         inputpos (1+ inputpos))
184                   (cond ((= counter 4)
185                          (uudecode-insert-char
186                           (lsh bits -16) 1 nil work-buffer)
187                          (uudecode-insert-char
188                           (logand (lsh bits -8) 255) 1 nil work-buffer)
189                          (uudecode-insert-char (logand bits 255) 1 nil
190                                                work-buffer)
191                          (setq bits 0 counter 0))
192                         (t (setq bits (lsh bits 6)))))))
193               (cond
194                (done)
195                ((> 0 remain)
196                 (error "uucode line ends unexpectly")
197                 (setq done t))
198                ((and (= (point) end) (not done))
199                 ;;(error "uucode ends unexpectly")
200                 (setq done t))
201                ((= counter 3)
202                 (uudecode-insert-char (logand (lsh bits -16) 255) 1 nil
203                                       work-buffer)
204                 (uudecode-insert-char (logand (lsh bits -8) 255) 1 nil
205                                       work-buffer))
206                ((= counter 2)
207                 (uudecode-insert-char (logand (lsh bits -10) 255) 1 nil
208                                       work-buffer)))
209               (skip-chars-forward non-data-chars end))
210             (if file-name
211                 (save-excursion
212                   (set-buffer work-buffer)
213                   (write-file file-name))
214               (or (markerp end) (setq end (set-marker (make-marker) end)))
215               (goto-char start)
216               (insert-buffer-substring work-buffer)
217               (delete-region (point) end))))
218       (and work-buffer (kill-buffer work-buffer)))))
219
220 ;;;###autoload
221 (defun uudecode-decode-region (start end &optional file-name)
222   "Uudecode region between START and END.
223 If FILE-NAME is non-nil, save the result to FILE-NAME."
224   (if uudecode-use-external 
225       (uudecode-decode-region-external start end file-name)
226     (uudecode-decode-region-internal start end file-name)))
227
228 (provide 'uudecode)
229
230 ;;; uudecode.el ends here