Mercurial > core / lisp/lib/dat/base64.lisp
changeset 698: |
96958d3eb5b0 |
parent: |
0e00dec3de03
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Fri, 04 Oct 2024 22:04:59 -0400 |
permissions: |
-rw-r--r-- |
description: |
fixes |
1 ;;; dat/base64.lisp --- Base64 Strings 5 ;; see http://git.kpe.io/?p=cl-base64.git;a=summary 7 ;; Copyright (c) 2002-2003 by Kevin Rosenberg 10 (in-package :dat/base64) 13 (eval-when (:compile-toplevel :load-toplevel :execute) 14 (defvar *encode-table* 15 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") 16 (declaim (type simple-string *encode-table*)) 18 (defvar *uri-encode-table* 19 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") 20 (declaim (type simple-string *uri-encode-table*)) 22 (defvar *pad-char* #\=) 23 (defvar *uri-pad-char* #\.) 24 (declaim (type character *pad-char* *uri-pad-char*)) 26 (deftype decode-table () '(simple-array (signed-byte 8) (128))) 27 (defun make-decode-table (encode-table pad-char 28 &key (whitespace-chars 29 '(#\Linefeed #\Return #\Space #\Tab))) 30 (assert (< (length encode-table) 128) 32 "Encode table too big: ~S" encode-table) 33 (let ((dt (make-array 128 :element-type '(signed-byte 8) 34 :initial-element -1))) 35 (declare (type decode-table dt)) 36 (loop for char across encode-table 38 do (setf (aref dt (char-code char)) index)) 39 (setf (aref dt (char-code pad-char)) -2) 40 (loop for char in whitespace-chars 41 do (setf (aref dt (char-code char)) -3)) 44 (defconstant +decode-table+ 45 (if (boundp '+decode-table+) 46 (symbol-value '+decode-table+) 47 (make-decode-table *encode-table* *pad-char*))) 48 (declaim (type decode-table +decode-table+)) 50 (defconstant +uri-decode-table+ 51 (if (boundp '+uri-decode-table+) 52 (symbol-value '+uri-decode-table+) 53 (make-decode-table *uri-encode-table* *uri-pad-char*))) 54 (declaim (type decode-table +uri-decode-table+)) 56 (defun round-next-multiple (x n) 57 "Round x up to the next highest multiple of n." 59 (optimize (speed 3) (safety 1) (space 0))) 60 (let ((remainder (mod x n))) 61 (declare (fixnum remainder)) 64 (the fixnum (+ x (the fixnum (- n remainder))))))) 66 (defmacro def-*-to-base64-* (input-type output-type) 67 `(defun ,(intern (concatenate 'string (symbol-name input-type) 68 (symbol-name :-to-base64-) 69 (symbol-name output-type))) 71 ,@(when (eq output-type :stream) 73 &key (uri nil) (columns 0)) 74 "Encode a string array to base64. If columns is > 0, designates 75 maximum number of columns in a line and the string will be terminated 77 (declare ,@(case input-type 81 '((type (array (unsigned-byte 8) (*)) input)))) 83 (optimize (speed 3) (safety 1) (space 0))) 84 (let ((pad (if uri *uri-pad-char* *pad-char*)) 85 (encode-table (if uri *uri-encode-table* *encode-table*))) 86 (declare (simple-string encode-table) 88 (let* ((string-length (length input)) 89 (complete-group-count (truncate string-length 3)) 90 (remainder (nth-value 1 (truncate string-length 3))) 91 (padded-length (* 4 (truncate (+ string-length 2) 3))) 92 ,@(when (eq output-type :string) 93 '((num-lines (if (plusp columns) 94 (truncate (+ padded-length (1- columns)) columns) 96 (num-breaks (if (plusp num-lines) 99 (strlen (+ padded-length num-breaks)) 100 (result (make-string strlen)) 102 (col (if (plusp columns) 104 (the fixnum (1+ padded-length))))) 105 (declare (fixnum string-length padded-length col 106 ,@(when (eq output-type :string) 108 ,@(when (eq output-type :string) 109 '((simple-string result)))) 110 (labels ((output-char (ch) 115 '((write-char #\Newline output))) 117 '((setf (schar result ioutput) #\Newline) 123 '((write-char ch output))) 125 '((setf (schar result ioutput) ch) 127 (output-group (svalue chars) 128 (declare (fixnum svalue chars)) 133 (the fixnum (ash svalue -18)))))) 138 (the fixnum (ash svalue -12)))))) 144 (the fixnum (ash svalue -6)))))) 150 (logand #x3f svalue)))) 152 (do ((igroup 0 (the fixnum (1+ igroup))) 153 (isource 0 (the fixnum (+ isource 3)))) 154 ((= igroup complete-group-count) 164 '(char-code (the character (char input isource)))) 166 '(the fixnum (aref input isource)))) 172 '(char-code (the character (char input 173 (the fixnum (1+ isource)))))) 175 '(the fixnum (aref input (the fixnum 185 '(char-code (the character (char input isource)))) 187 '(the fixnum (aref input isource)))) 195 (declare (fixnum igroup isource)) 204 '(char-code (the character (char input isource)))) 206 '(aref input isource)))) 213 '(char-code (the character (char input 214 (the fixnum (1+ isource)))))) 216 '(aref input (1+ isource))))) 221 '(char-code (the character (char input 222 (the fixnum (+ 2 isource)))))) 224 '(aref input (+ 2 isource)))) 228 (def-*-to-base64-* :string :string) 229 (def-*-to-base64-* :string :stream) 230 (def-*-to-base64-* :usb8-array :string) 231 (def-*-to-base64-* :usb8-array :stream) 234 (defun integer-to-base64-string (input &key (uri nil) (columns 0)) 235 "Encode an integer to base64 format." 236 (declare (integer input) 238 (optimize (speed 3) (space 0) (safety 1))) 239 (let ((pad (if uri *uri-pad-char* *pad-char*)) 240 (encode-table (if uri *uri-encode-table* *encode-table*))) 241 (declare (simple-string encode-table) 243 (let* ((input-bits (integer-length input)) 244 (byte-bits (round-next-multiple input-bits 8)) 245 (padded-bits (round-next-multiple byte-bits 6)) 246 (remainder-padding (mod padded-bits 24)) 247 (padding-bits (if (zerop remainder-padding) 249 (- 24 remainder-padding))) 250 (padding-chars (/ padding-bits 6)) 251 (padded-length (/ (+ padded-bits padding-bits) 6)) 252 (last-line-len (if (plusp columns) 253 (- padded-length (* columns 255 padded-length columns))) 257 (num-lines (if (plusp columns) 258 (truncate (+ padded-length (1- columns)) columns) 260 (num-breaks (if (plusp num-lines) 263 (strlen (+ padded-length num-breaks)) 264 (last-char (1- strlen)) 265 (str (make-string strlen)) 266 (col (if (zerop last-line-len) 269 (declare (fixnum padded-length num-lines col last-char 270 padding-chars last-line-len)) 271 (unless (plusp columns) 272 (setq col -1)) ;; set to flag to optimize in loop 274 (dotimes (i padding-chars) 276 (setf (schar str (the fixnum (- last-char i))) pad)) 278 (do* ((strpos (- last-char padding-chars) (1- strpos)) 279 (int (ash input (/ padding-bits 3)))) 282 (declare (fixnum strpos) (integer int)) 285 (setf (schar str strpos) #\Newline) 288 (setf (schar str strpos) 289 (schar encode-table (the fixnum (logand int #x3f)))) 290 (setq int (ash int -6)) 293 (defun integer-to-base64-stream (input stream &key (uri nil) (columns 0)) 294 "Encode an integer to base64 format." 295 (declare (integer input) 297 (optimize (speed 3) (space 0) (safety 1))) 298 (let ((pad (if uri *uri-pad-char* *pad-char*)) 299 (encode-table (if uri *uri-encode-table* *encode-table*))) 300 (declare (simple-string encode-table) 302 (let* ((input-bits (integer-length input)) 303 (byte-bits (round-next-multiple input-bits 8)) 304 (padded-bits (round-next-multiple byte-bits 6)) 305 (remainder-padding (mod padded-bits 24)) 306 (padding-bits (if (zerop remainder-padding) 308 (- 24 remainder-padding))) 309 (padding-chars (/ padding-bits 6)) 310 (padded-length (/ (+ padded-bits padding-bits) 6)) 311 (strlen padded-length) 312 (nonpad-chars (- strlen padding-chars)) 313 (last-nonpad-char (1- nonpad-chars)) 314 (str (make-string strlen))) 315 (declare (fixnum padded-length last-nonpad-char)) 316 (do* ((strpos 0 (the fixnum (1+ strpos))) 317 (int (ash input (/ padding-bits 3)) (ash int -6)) 318 (6bit-value (the fixnum (logand int #x3f)) 319 (the fixnum (logand int #x3f)))) 320 ((= strpos nonpad-chars) 322 (declare (fixnum col)) 323 (dotimes (i nonpad-chars) 325 (write-char (schar str i) stream) 326 (when (plusp columns) 328 (when (= col columns) 329 (write-char #\Newline stream) 331 (dotimes (ipad padding-chars) 332 (declare (fixnum ipad)) 333 (write-char pad stream) 334 (when (plusp columns) 336 (when (= col columns) 337 (write-char #\Newline stream) 340 (declare (fixnum 6bit-value strpos) 342 (setf (schar str (- last-nonpad-char strpos)) 343 (schar encode-table 6bit-value)) 346 (define-condition base64-error (error) 349 :reader base64-error-input) 352 :reader base64-error-position 353 :type unsigned-byte))) 355 (define-condition bad-base64-character (base64-error) 356 ((code :initarg :code :reader bad-base64-character-code)) 357 (:report (lambda (condition stream) 358 (format stream "Bad character ~S at index ~D of ~S" 359 (code-char (bad-base64-character-code condition)) 360 (base64-error-position condition) 361 (base64-error-input condition))))) 363 (define-condition incomplete-base64-data (base64-error) 365 (:report (lambda (condition stream) 366 (format stream "Unexpected end of Base64 data at index ~D of ~S" 367 (base64-error-position condition) 368 (base64-error-input condition))))) 370 (deftype array-index (&optional (length array-dimension-limit)) 371 `(integer 0 (,length))) 373 (deftype array-length (&optional (length array-dimension-limit)) 374 `(integer 0 ,length)) 376 (deftype character-code () 377 `(integer 0 (,char-code-limit))) 379 (defmacro etypecase/unroll ((var &rest types) &body body) 380 #+sbcl `(etypecase ,var 381 ,@(loop for type in types 382 collect `(,type ,@body))) 384 (declare (type (or ,@types) ,var)) 387 (defmacro let/typed ((&rest vars) &body body) 388 `(let ,(loop for (var value) in vars 389 collect (list var value)) 390 (declare ,@(loop for (var nil type) in vars 392 collect (list 'type type var))) 395 (defmacro define-base64-decoder (hose sink) 396 `(defun ,(intern (format nil "~A-~A-~A-~A" '#:base64 hose '#:to sink)) 397 (input &key (table +decode-table+) 399 ,@(when (eq sink :stream) `(stream)) 400 (whitespace :ignore)) 402 Decode Base64 ~(~A~) to ~(~A~). 404 TABLE is the decode table to use. Two decode tables are provided: 405 +DECODE-TABLE+ (used by default) and +URI-DECODE-TABLE+. See 408 For backwards-compatibility the URI parameter is supported. If it is 409 true, then +URI-DECODE-TABLE+ is used, and the value for TABLE 410 parameter is ignored. 412 WHITESPACE can be one of: 414 :ignore - Whitespace characters are ignored (default). 415 :signal - Signal a BAD-BASE64-CHARACTER condition using SIGNAL. 416 :error - Signal a BAD-BASE64-CHARACTER condition using ERROR." 418 (declare (optimize (speed 3) (safety 1)) 419 (type decode-table table) 424 (let/typed ((decode-table (if uri +uri-decode-table+ table) 431 `((result (make-array 1024 432 :element-type '(unsigned-byte 8) 435 (array (unsigned-byte 8) (*))))) 437 `((result (make-array (* 3 (ceiling (length input) 4)) 438 :element-type '(unsigned-byte 8)) 439 (simple-array (unsigned-byte 8) (*))) 440 (rpos 0 array-index))))) 444 `((result (make-array 1024 445 :element-type 'character 448 (array character (*))))) 450 `((result (make-array (* 3 (ceiling (length input) 4)) 451 :element-type 'character) 452 (simple-array character (*))) 453 (rpos 0 array-index))))) 455 `((result 0 unsigned-byte))))) 456 (flet ((bad-char (pos code &optional (action :error)) 457 (let ((args (list 'bad-base64-character 463 (apply #'error args)) 465 (apply #'cerror "Ignore the error and continue." args)) 467 (apply #'signal args))))) 468 (incomplete-input (pos) 469 (error 'incomplete-base64-data :input input :position pos))) 471 `(let/typed ((ipos 0 array-index) 472 (bitstore 0 (unsigned-byte 24)) 473 (bitcount 0 (integer 0 14)) 474 (svalue -1 (signed-byte 8)) 475 (padchar 0 (integer 0 3)) 480 `((if (< ipos length) 481 (setq code (char-code (aref input ipos))) 484 `((let ((char (read-char input nil nil))) 486 (setq code (char-code char)) 490 (= -1 (setq svalue (aref decode-table code)))) 491 (bad-char ipos code)) 493 (cond ((<= (incf padchar) 2) 494 (unless (<= 2 bitcount) 495 (bad-char ipos code)) 498 (bad-char ipos code)))) 505 (bad-char ipos code :error)) 507 (bad-char ipos code :signal)))) 508 ((not (zerop padchar)) 509 (bad-char ipos code)) 511 (setf bitstore (logior (the (unsigned-byte 24) 515 (when (>= bitcount 8) 517 (let ((byte (logand (the (unsigned-byte 24) 518 (ash bitstore (- bitcount))) 520 (declare (type (unsigned-byte 8) byte)) 525 `((setf (aref result rpos) byte) 528 `((vector-push-extend byte result))))) 532 `((setf (schar result rpos) 536 `((vector-push-extend (code-char byte) 540 (logior (ash result 8) byte)))) 542 '((write-char (code-char byte) stream))))) 543 (setf bitstore (logand bitstore #xFF))))) 545 (unless (zerop bitcount) 546 (incomplete-input ipos)) 548 ((:string :usb8-array) 551 `(if (= rpos (length result)) 553 (subseq result 0 rpos))) 555 `(copy-seq result)))) 562 `(let ((length (length input))) 563 (declare (type array-length length)) 564 (etypecase/unroll (input simple-base-string 571 (define-base64-decoder :string :usb8-array) 572 (define-base64-decoder :string :string) 573 (define-base64-decoder :string :integer) 574 (define-base64-decoder :string :stream) 576 (define-base64-decoder :stream :usb8-array) 577 (define-base64-decoder :stream :string) 578 (define-base64-decoder :stream :integer) 579 (define-base64-decoder :stream :stream) 581 ;; input-mode can be :string or :stream 582 ;; input-format can be :character or :usb8