;;; base64.el,v --- Base64 encoding functions
;; Author: Kyle E. Jones
;; Created: 1997/03/12 14:37:09
;; Version: 1.6
;; Keywords: extensions

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Copyright (C) 1997 Kyle E. Jones
;;;
;;; This file is not part of GNU Emacs, but the same permissions apply.
;;;
;;; GNU Emacs is free software; you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 2, or (at your option)
;;; any later version.
;;;
;;; GNU Emacs is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Emacs; see the file COPYING.  If not, write to the
;;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;;; Boston, MA 02111-1307, USA.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(eval-when-compile (require 'cl))

;; For non-MULE
(if (not (fboundp 'char-int))
    (defalias 'char-int 'identity))

(defvar base64-alphabet
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")

(defvar base64-decoder-program nil
  "*Non-nil value should be a string that names a MIME base64 decoder.
The program should expect to read base64 data on its standard
input and write the converted data to its standard output.")

(defvar base64-decoder-switches nil
  "*List of command line flags passed to the command named by
base64-decoder-program.")

(defvar base64-encoder-program nil
  "*Non-nil value should be a string that names a MIME base64 encoder.
The program should expect arbitrary data on its standard
input and write base64 data to its standard output.")

(defvar base64-encoder-switches nil
  "*List of command line flags passed to the command named by
base64-encoder-program.")

(defconst base64-alphabet-decoding-alist
  '(
    ( ?A . 00) ( ?B . 01) ( ?C . 02) ( ?D . 03) ( ?E . 04) ( ?F . 05)
    ( ?G . 06) ( ?H . 07) ( ?I . 08) ( ?J . 09) ( ?K . 10) ( ?L . 11)
    ( ?M . 12) ( ?N . 13) ( ?O . 14) ( ?P . 15) ( ?Q . 16) ( ?R . 17)
    ( ?S . 18) ( ?T . 19) ( ?U . 20) ( ?V . 21) ( ?W . 22) ( ?X . 23)
    ( ?Y . 24) ( ?Z . 25) ( ?a . 26) ( ?b . 27) ( ?c . 28) ( ?d . 29)
    ( ?e . 30) ( ?f . 31) ( ?g . 32) ( ?h . 33) ( ?i . 34) ( ?j . 35)
    ( ?k . 36) ( ?l . 37) ( ?m . 38) ( ?n . 39) ( ?o . 40) ( ?p . 41)
    ( ?q . 42) ( ?r . 43) ( ?s . 44) ( ?t . 45) ( ?u . 46) ( ?v . 47)
    ( ?w . 48) ( ?x . 49) ( ?y . 50) ( ?z . 51) ( ?0 . 52) ( ?1 . 53)
    ( ?2 . 54) ( ?3 . 55) ( ?4 . 56) ( ?5 . 57) ( ?6 . 58) ( ?7 . 59)
    ( ?8 . 60) ( ?9 . 61) ( ?+ . 62) ( ?/ . 63)
    ))

(defvar base64-alphabet-decoding-vector
  (let ((v (make-vector 123 nil))
	(p base64-alphabet-decoding-alist))
    (while p
      (aset v (car (car p)) (cdr (car p)))
      (setq p (cdr p)))
    v))

(defvar base64-binary-coding-system 'binary)

(defun base64-run-command-on-region (start end output-buffer command
					   &rest arg-list)
  (let ((tempfile nil) status errstring default-process-coding-system 
	(coding-system-for-write base64-binary-coding-system)
	(coding-system-for-read base64-binary-coding-system))
    (unwind-protect
	(progn
	  (setq tempfile (make-temp-name "base64"))
	  (setq status
		(apply 'call-process-region
		       start end command nil
		       (list output-buffer tempfile)
		       nil arg-list))
	  (cond ((equal status 0) t)
		((zerop (save-excursion
			  (set-buffer (find-file-noselect tempfile))
			  (buffer-size)))
		 t)
		(t (save-excursion
		     (set-buffer (find-file-noselect tempfile))
		     (setq errstring (buffer-string))
		     (kill-buffer nil)
		     (cons status errstring)))))
      (ignore-errors
	(delete-file tempfile)))))

(if (featurep 'xemacs)
    (defalias 'base64-insert-char 'insert-char)
  (defun base64-insert-char (char &optional count ignored buffer)
    (if (or (null buffer) (eq buffer (current-buffer)))
	(insert-char char count)
      (with-current-buffer buffer
	(insert-char char count))))
  (setq base64-binary-coding-system 'no-conversion))

(defun base64-decode-region (start end)
  (interactive "r")
  ;;(message "Decoding base64...")
  (let ((work-buffer nil)
	(done nil)
	(counter 0)
	(bits 0)
	(lim 0) inputpos
	(non-data-chars (concat "^=" base64-alphabet)))
    (unwind-protect
	(save-excursion
	  (setq work-buffer (generate-new-buffer " *base64-work*"))
	  (buffer-disable-undo work-buffer)
	  (if base64-decoder-program
	      (let* ((binary-process-output t) ; any text already has CRLFs
		     (status (apply 'base64-run-command-on-region
				    start end work-buffer
				    base64-decoder-program
				    base64-decoder-switches)))
		(if (not (eq status t))
		    (error "%s" (cdr status))))
	    (goto-char start)
	    (skip-chars-forward non-data-chars end)
	    (while (not done)
	      (setq inputpos (point))
	      (cond
	       ((> (skip-chars-forward base64-alphabet end) 0)
		(setq lim (point))
		(while (< inputpos lim)
		  (setq bits (+ bits
				(aref base64-alphabet-decoding-vector
				      (char-int (char-after inputpos)))))
		  (setq counter (1+ counter)
			inputpos (1+ inputpos))
		  (cond ((= counter 4)
			 (base64-insert-char (lsh bits -16) 1 nil work-buffer)
			 (base64-insert-char (logand (lsh bits -8) 255) 1 nil
					     work-buffer)
			 (base64-insert-char (logand bits 255) 1 nil
					     work-buffer)
			 (setq bits 0 counter 0))
			(t (setq bits (lsh bits 6)))))))
	      (cond
	       ((or (= (point) end)
		    (eq (char-after (point)) ?=))
		(if (and (= (point) end) (> counter 1))
		    (message 
		     "at least %d bits missing at end of base64 encoding"
		     (* (- 4 counter) 6)))
		(setq done t)
		(cond ((= counter 1)
		       (error "at least 2 bits missing at end of base64 encoding"))
		      ((= counter 2)
		       (base64-insert-char (lsh bits -10) 1 nil work-buffer))
		      ((= counter 3)
		       (base64-insert-char (lsh bits -16) 1 nil work-buffer)
		       (base64-insert-char (logand (lsh bits -8) 255)
					   1 nil work-buffer))
		      ((= counter 0) t)))
	       (t (skip-chars-forward non-data-chars end)))))
	  (or (markerp end) (setq end (set-marker (make-marker) end)))
	  (goto-char start)
	  (insert-buffer-substring work-buffer)
	  (delete-region (point) end))
      (and work-buffer (kill-buffer work-buffer))))
  ;;(message "Decoding base64... done")
  )

(defun base64-encode-region (start end &optional no-line-break)
  (interactive "r")
  (message "Encoding base64...")
  (let ((work-buffer nil)
	(counter 0)
	(cols 0)
	(bits 0)
	(alphabet base64-alphabet)
	inputpos)
    (unwind-protect
	(save-excursion
	  (setq work-buffer (generate-new-buffer " *base64-work*"))
	  (buffer-disable-undo work-buffer)
	  (if base64-encoder-program
	      (let ((status (apply 'base64-run-command-on-region
				   start end work-buffer
				   base64-encoder-program
				   base64-encoder-switches)))
		(if (not (eq status t))
		    (error "%s" (cdr status))))
	    (setq inputpos start)
	    (while (< inputpos end)
	      (setq bits (+ bits (char-int (char-after inputpos))))
	      (setq counter (1+ counter))
	      (cond ((= counter 3)
		     (base64-insert-char (aref alphabet (lsh bits -18)) 1 nil
					 work-buffer)
		     (base64-insert-char
		      (aref alphabet (logand (lsh bits -12) 63))
		      1 nil work-buffer)
		     (base64-insert-char
		      (aref alphabet (logand (lsh bits -6) 63))
		      1 nil work-buffer)
		     (base64-insert-char
		      (aref alphabet (logand bits 63))
		      1 nil work-buffer)
		     (setq cols (+ cols 4))
		     (cond ((and (= cols 72)
				 (not no-line-break))
			    (base64-insert-char ?\n 1 nil work-buffer)
			    (setq cols 0)))
		     (setq bits 0 counter 0))
		    (t (setq bits (lsh bits 8))))
	      (setq inputpos (1+ inputpos)))
	    ;; write out any remaining bits with appropriate padding
	    (if (= counter 0)
		nil
	      (setq bits (lsh bits (- 16 (* 8 counter))))
	      (base64-insert-char (aref alphabet (lsh bits -18)) 1 nil
				  work-buffer)
	      (base64-insert-char (aref alphabet (logand (lsh bits -12) 63))
				  1 nil work-buffer)
	      (if (= counter 1)
		  (base64-insert-char ?= 2 nil work-buffer)
		(base64-insert-char (aref alphabet (logand (lsh bits -6) 63))
				    1 nil work-buffer)
		(base64-insert-char ?= 1 nil work-buffer)))
	    (if (and (> cols 0)
		     (not no-line-break))
	    	(base64-insert-char ?\n 1 nil work-buffer)))
	  (or (markerp end) (setq end (set-marker (make-marker) end)))
	  (goto-char start)
	  (insert-buffer-substring work-buffer)
	  (delete-region (point) end))
      (and work-buffer (kill-buffer work-buffer))))
  (message "Encoding base64... done"))

(defun base64-encode (string &optional no-line-break)
  (save-excursion
    (set-buffer (get-buffer-create " *base64-encode*"))
    (erase-buffer)
    (insert string)
    (base64-encode-region (point-min) (point-max) no-line-break)
    (skip-chars-backward " \t\r\n")
    (delete-region (point-max) (point))
    (prog1
	(buffer-string)
      (kill-buffer (current-buffer)))))

(defun base64-decode (string)
  (save-excursion
    (set-buffer (get-buffer-create " *base64-decode*"))
    (erase-buffer)
    (insert string)
    (base64-decode-region (point-min) (point-max))
    (goto-char (point-max))
    (skip-chars-backward " \t\r\n")
    (delete-region (point-max) (point))
    (prog1
	(buffer-string)
      (kill-buffer (current-buffer)))))

(defalias 'base64-decode-string 'base64-decode)
(defalias 'base64-encode-string 'base64-encode)

(provide 'base64)
