1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
6 ;;;; Purpose: cl-base64 encoding routines
7 ;;;; Programmer: Kevin M. Rosenberg
8 ;;;; Date Started: Dec 2002
12 ;;;; This file implements the Base64 transfer encoding algorithm as
13 ;;;; defined in RFC 1521 by Borensten & Freed, September 1993.
14 ;;;; See: http://www.ietf.org/rfc/rfc1521.txt
16 ;;;; Based on initial public domain code by Juri Pakaste <juri@iki.fi>
18 ;;;; Copyright 2002-2003 Kevin M. Rosenberg
19 ;;;; Permission to use with BSD-style license included in the COPYING file
20 ;;;; *************************************************************************
22 (in-package #:cl-base64)
24 (define-condition base64-error (error)
27 :reader base64-error-input)
30 :reader base64-error-position
31 :type unsigned-byte)))
33 (define-condition bad-base64-character (base64-error)
34 ((code :initarg :code :reader bad-base64-character-code))
35 (:report (lambda (condition stream)
36 (format stream "Bad character ~S at index ~D of ~S"
37 (code-char (bad-base64-character-code condition))
38 (base64-error-position condition)
39 (base64-error-input condition)))))
41 (define-condition incomplete-base64-data (base64-error)
43 (:report (lambda (condition stream)
44 (format stream "Unexpected end of Base64 data at index ~D of ~S"
45 (base64-error-position condition)
46 (base64-error-input condition)))))
48 (deftype array-index (&optional (length array-dimension-limit))
49 `(integer 0 (,length)))
51 (deftype array-length (&optional (length array-dimension-limit))
54 (deftype character-code ()
55 `(integer 0 (,char-code-limit)))
57 (defmacro etypecase/unroll ((var &rest types) &body body)
58 #+sbcl `(etypecase ,var
59 ,@(loop for type in types
60 collect `(,type ,@body)))
62 (declare (type (or ,@types) ,var))
65 (defmacro let/typed ((&rest vars) &body body)
66 `(let ,(loop for (var value) in vars
67 collect (list var value))
68 (declare ,@(loop for (var nil type) in vars
70 collect (list 'type type var)))
73 (defmacro define-base64-decoder (hose sink)
74 `(defun ,(intern (format nil "~A-~A-~A-~A" '#:base64 hose '#:to sink))
75 (input &key (table +decode-table+)
77 ,@(when (eq sink :stream) `(stream))
80 Decode Base64 ~(~A~) to ~(~A~).
82 TABLE is the decode table to use. Two decode tables are provided:
83 +DECODE-TABLE+ (used by default) and +URI-DECODE-TABLE+. See
86 For backwards-compatibility the URI parameter is supported. If it is
87 true, then +URI-DECODE-TABLE+ is used, and the value for TABLE
90 WHITESPACE can be one of:
92 :ignore - Whitespace characters are ignored (default).
93 :signal - Signal a BAD-BASE64-CHARACTER condition using SIGNAL.
94 :error - Signal a BAD-BASE64-CHARACTER condition using ERROR."
96 (declare (optimize (speed 3) (safety 1))
97 (type decode-table table)
102 (let/typed ((decode-table (if uri +uri-decode-table+ table)
109 `((result (make-array 1024
110 :element-type '(unsigned-byte 8)
113 (array (unsigned-byte 8) (*)))))
115 `((result (make-array (* 3 (ceiling (length input) 4))
116 :element-type '(unsigned-byte 8))
117 (simple-array (unsigned-byte 8) (*)))
118 (rpos 0 array-index)))))
122 `((result (make-array 1024
123 :element-type 'character
126 (array character (*)))))
128 `((result (make-array (* 3 (ceiling (length input) 4))
129 :element-type 'character)
130 (simple-array character (*)))
131 (rpos 0 array-index)))))
133 `((result 0 unsigned-byte)))))
134 (flet ((bad-char (pos code &optional (action :error))
135 (let ((args (list 'bad-base64-character
141 (apply #'error args))
143 (apply #'cerror "Ignore the error and continue." args))
145 (apply #'signal args)))))
146 (incomplete-input (pos)
147 (error 'incomplete-base64-data :input input :position pos)))
149 `(let/typed ((ipos 0 array-index)
150 (bitstore 0 (unsigned-byte 24))
151 (bitcount 0 (integer 0 14))
152 (svalue -1 (signed-byte 8))
153 (padchar 0 (integer 0 3))
158 `((if (< ipos length)
159 (setq code (char-code (aref input ipos)))
162 `((let ((char (read-char input nil nil)))
164 (setq code (char-code char))
168 (= -1 (setq svalue (aref decode-table code))))
169 (bad-char ipos code))
171 (cond ((<= (incf padchar) 2)
172 (unless (<= 2 bitcount)
173 (bad-char ipos code))
176 (bad-char ipos code))))
183 (bad-char ipos code :error))
185 (bad-char ipos code :signal))))
186 ((not (zerop padchar))
187 (bad-char ipos code))
189 (setf bitstore (logior (the (unsigned-byte 24)
193 (when (>= bitcount 8)
195 (let ((byte (logand (the (unsigned-byte 24)
196 (ash bitstore (- bitcount)))
198 (declare (type (unsigned-byte 8) byte))
203 `((setf (aref result rpos) byte)
206 `((vector-push-extend byte result)))))
210 `((setf (schar result rpos)
214 `((vector-push-extend (code-char byte)
218 (logior (ash result 8) byte))))
220 '((write-char (code-char byte) stream)))))
221 (setf bitstore (logand bitstore #xFF)))))
223 (unless (zerop bitcount)
224 (incomplete-input ipos))
226 ((:string :usb8-array)
229 `(if (= rpos (length result))
231 (subseq result 0 rpos)))
233 `(copy-seq result))))
240 `(let ((length (length input)))
241 (declare (type array-length length))
242 (etypecase/unroll (input simple-base-string
249 (define-base64-decoder :string :usb8-array)
250 (define-base64-decoder :string :string)
251 (define-base64-decoder :string :integer)
252 (define-base64-decoder :string :stream)
254 (define-base64-decoder :stream :usb8-array)
255 (define-base64-decoder :stream :string)
256 (define-base64-decoder :stream :integer)
257 (define-base64-decoder :stream :stream)
259 ;; input-mode can be :string or :stream
260 ;; input-format can be :character or :usb8