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
10 ;;;; $Id: encode.lisp,v 1.2 2003/01/12 22:32:40 kevin Exp $
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 (declaim (optimize (debug 3) (speed 3) (safety 1) (compilation-speed 0)))
24 ;;;; Extended by Kevin M. Rosenberg <kevin@rosenberg.net>:
26 ;;;; - numerous speed optimizations
27 ;;;; - conversion to and from integers
28 ;;;; - Renamed functions now that supporting integer conversions
29 ;;;; - URI-compatible encoding using :uri key
31 ;;;; $Id: encode.lisp,v 1.2 2003/01/12 22:32:40 kevin Exp $
33 (in-package #:cl-base64)
36 (defun round-next-multiple (x n)
37 "Round x up to the next highest multiple of n."
40 (let ((remainder (mod x n)))
41 (declare (fixnum remainder))
44 (the fixnum (+ x (the fixnum (- n remainder)))))))
46 (defun string-to-base64 (string &key (uri nil) (columns 0) (stream nil))
47 "Encode a string array to base64. If columns is > 0, designates
48 maximum number of columns in a line and the string will be terminated
50 (declare (string string)
53 (let ((pad (if uri *uri-pad-char* *pad-char*))
54 (encode-table (if uri *uri-encode-table* *encode-table*)))
55 (declare (simple-string encode-table)
57 (let* ((string-length (length string))
58 (complete-group-count (truncate string-length 3))
59 (remainder (nth-value 1 (truncate string-length 3)))
60 (padded-length (* 4 (truncate (+ string-length 2) 3)))
61 (num-lines (if (plusp columns)
62 (truncate (+ padded-length (1- columns)) columns)
64 (num-breaks (if (plusp num-lines)
69 (+ padded-length num-breaks)))
70 (result (make-string strlen))
71 (col (if (plusp columns)
75 (declare (fixnum string-length padded-length col ioutput)
76 (simple-string result))
77 (labels ((output-char (ch)
81 (write-char #\Newline stream)
83 (setf (schar result ioutput) #\Newline)
88 (write-char ch stream)
90 (setf (schar result ioutput) ch)
92 (output-group (svalue chars)
93 (declare (fixnum svalue chars))
98 (the fixnum (ash svalue -18))))))
103 (the fixnum (ash svalue -12))))))
109 (the fixnum (ash svalue -6))))))
115 (logand #x3f svalue))))
117 (do ((igroup 0 (1+ igroup))
118 (isource 0 (+ isource 3)))
119 ((= igroup complete-group-count)
126 (ash (char-code (the character
127 (char string isource))) 16))
129 (ash (char-code (the character
130 (char string (1+ isource)))) 8))))
135 (ash (char-code (the character (char string isource))) 16))
138 (declare (fixnum igroup isource))
143 (ash (char-code (the character
144 (char string isource))) 16))
146 (ash (char-code (the character (char string (1+ isource)))) 8))
148 (char-code (the character (char string (+ 2 isource)))))))
151 (defmacro def-*-to-base64-* (input-type output-type)
152 `(defun ,(intern (concatenate 'string (symbol-name input-type)
153 (symbol-name :-to-base-64-)
154 (symbol-name output-type)))
156 ,@(when (eq output-type :stream)
158 &key (uri nil) (columns 0))
159 "Encode a string array to base64. If columns is > 0, designates
160 maximum number of columns in a line and the string will be terminated
162 (declare (,@(case input-type
166 '((type (array fixnum (*))) input))
168 (optimize (speed 3))))
169 (let ((pad (if uri *uri-pad-char* *pad-char*))
170 (encode-table (if uri *uri-encode-table* *encode-table*)))
171 (declare (simple-string encode-table)
173 (let* ((string-length (length input))
174 (complete-group-count (truncate string-length 3))
175 (remainder (nth-value 1 (truncate string-length 3)))
176 (padded-length (* 4 (truncate (+ string-length 2) 3)))
177 (num-lines (if (plusp columns)
178 (truncate (+ padded-length (1- columns)) columns)
180 (num-breaks (if (plusp num-lines)
185 (+ padded-length num-breaks)))
186 (result (make-string strlen))
187 (col (if (plusp columns)
191 (declare (fixnum string-length padded-length col ioutput)
192 (simple-string result))
193 (macrolet ((output-char (ch)
197 (write-char #\Newline stream)
199 (setf (schar result ioutput) #\Newline)
205 '((write-char ch stream))
207 '((setf (schar result ioutput) ch)
209 (labels ((output-group (svalue chars)
210 (declare (fixnum svalue chars))
215 (the fixnum (ash svalue -18))))))
220 (the fixnum (ash svalue -12))))))
226 (the fixnum (ash svalue -6))))))
232 (logand #x3f svalue))))
234 (do ((igroup 0 (1+ igroup))
235 (isource 0 (+ isource 3)))
236 ((= igroup complete-group-count)
246 '(char-code (the character (char input isource))))
248 '(the fixnum (aref input isource))))
254 '(char-code (the character (char input (1+ isource)))))
256 '(the fixnum (aref input (1+ isource)))))
264 '(char-code (the character (char input isource))))
266 '(the fixnum (aref input isource)))))
269 (declare (fixnum igroup isource))
277 '(char-code (the character (char input isource))))
279 '(the fixnum (aref input isource))))
285 '(char-code (the character (char input (1+ isource)))))
287 '(the fixnum (aref input (1+ isource)))))
292 '(char-code (the character (char input (+ 2 isource)))))
294 '(the fixnum (aref input (+ 2 isource)))))
298 (def-*-to-base64-* :string :string)
299 (def-*-to-base64-* :string :stream)
300 (def-*-to-base64-* :usb8-array :string)
301 (def-*-to-base64-* :usb8-array :stream)
304 (defun integer-to-base64-string (input &key (uri nil) (columns 0))
305 "Encode an integer to base64 format."
306 (declare (integer input)
308 (optimize (speed 3)))
309 (let ((pad (if uri *uri-pad-char* *pad-char*))
310 (encode-table (if uri *uri-encode-table* *encode-table*)))
311 (declare (simple-string encode-table)
313 (let* ((input-bits (integer-length input))
314 (byte-bits (round-next-multiple input-bits 8))
315 (padded-bits (round-next-multiple byte-bits 6))
316 (remainder-padding (mod padded-bits 24))
317 (padding-bits (if (zerop remainder-padding)
319 (- 24 remainder-padding)))
320 (padding-chars (/ padding-bits 6))
321 (padded-length (/ (+ padded-bits padding-bits) 6))
322 (last-line-len (if (plusp columns)
323 (- padded-length (* columns
325 padded-length columns)))
327 (num-lines (if (plusp columns)
328 (truncate (+ padded-length (1- columns)) columns)
330 (num-breaks (if (plusp num-lines)
333 (strlen (+ padded-length num-breaks))
334 (last-char (1- strlen))
335 (str (make-string strlen))
336 (col (if (zerop last-line-len)
339 (declare (fixnum padded-length num-lines col last-char
340 padding-chars last-line-len))
341 (unless (plusp columns)
342 (setq col -1)) ;; set to flag to optimize in loop
344 (dotimes (i padding-chars)
346 (setf (schar str (the fixnum (- last-char i))) pad))
348 (do* ((strpos (- last-char padding-chars) (1- strpos))
349 (int (ash input (/ padding-bits 3))))
352 (declare (fixnum strpos) (integer int))
355 (setf (schar str strpos) #\Newline)
358 (setf (schar str strpos)
359 (schar encode-table (the fixnum (logand int #x3f))))
360 (setq int (ash int -6))
363 (defun integer-to-base64-stream (input stream &key (uri nil) (columns 0))
364 "Encode an integer to base64 format."
365 (declare (integer input)
367 (optimize (speed 3)))
368 (let ((pad (if uri *uri-pad-char* *pad-char*))
369 (encode-table (if uri *uri-encode-table* *encode-table*)))
370 (declare (simple-string encode-table)
372 (let* ((input-bits (integer-length input))
373 (byte-bits (round-next-multiple input-bits 8))
374 (padded-bits (round-next-multiple byte-bits 6))
375 (remainder-padding (mod padded-bits 24))
376 (padding-bits (if (zerop remainder-padding)
378 (- 24 remainder-padding)))
379 (padding-chars (/ padding-bits 6))
380 (padded-length (/ (+ padded-bits padding-bits) 6))
381 (strlen padded-length)
382 (nonpad-chars (- strlen padding-chars))
383 (last-nonpad-char (1- nonpad-chars))
384 (str (make-string strlen)))
385 (declare (fixnum padded-length last-nonpad-char))
386 (do* ((strpos 0 (1+ strpos))
387 (int (ash input (/ padding-bits 3)) (ash int -6))
388 (6bit-value (logand int #x3f) (logand int #x3f)))
389 ((= strpos nonpad-chars)
391 (declare (fixnum col))
392 (dotimes (i nonpad-chars)
394 (write-char (schar str i) stream)
395 (when (plusp columns)
397 (when (= col columns)
398 (write-char #\Newline stream)
400 (dotimes (ipad padding-chars)
401 (declare (fixnum ipad))
402 (write-char pad stream)
403 (when (plusp columns)
405 (when (= col columns)
406 (write-char #\Newline stream)
409 (declare (fixnum 6bit-value strpos)
411 (setf (schar str (- last-nonpad-char strpos))
412 (schar encode-table 6bit-value))