base64.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. ;; -*- mode: scheme; coding: utf-8 -*-
  2. ;;
  3. ;; This module was renamed from (weinholt text base64 (1 0 20100612)) to
  4. ;; (guix base64) by Nikita Karetnikov <nikita@karetnikov.org> on
  5. ;; February 12, 2014. It was later renamed to (gcrypt base64) by
  6. ;; Christine Lemmer-Webber <cwebber@dustycloud.org> on May 20, 2017.
  7. ;;
  8. ;; Some optimizations made by Ludovic Courtès <ludo@gnu.org>, 2015.
  9. ;; Turned into a Guile module (instead of R6RS).
  10. ;;
  11. ;;
  12. ;; This library is free software; you can redistribute it and/or
  13. ;; modify it under the terms of the GNU Lesser General Public License
  14. ;; as published by the Free Software Foundation; either version 3 of
  15. ;; the License, or (at your option) any later version.
  16. ;;
  17. ;; guile-gcrypt is distributed in the hope that it will be useful, but
  18. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  19. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  20. ;; Lesser General Public License for more details.
  21. ;;
  22. ;; You should have received a copy of the GNU Lesser General Public License
  23. ;; along with guile-gcrypt. If not, see <http://www.gnu.org/licenses/>.
  24. ;;
  25. ;; This file incorporates work covered by the following copyright and
  26. ;; permission notice:
  27. ;;
  28. ;; Copyright © 2009, 2010, 2012, 2013, 2018 Göran Weinholt <goran@weinholt.se>
  29. ;;
  30. ;; Permission is hereby granted, free of charge, to any person obtaining a
  31. ;; copy of this software and associated documentation files (the "Software"),
  32. ;; to deal in the Software without restriction, including without limitation
  33. ;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
  34. ;; and/or sell copies of the Software, and to permit persons to whom the
  35. ;; Software is furnished to do so, subject to the following conditions:
  36. ;;
  37. ;; The above copyright notice and this permission notice shall be included in
  38. ;; all copies or substantial portions of the Software.
  39. ;;
  40. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  41. ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  42. ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  43. ;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  44. ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  45. ;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  46. ;; DEALINGS IN THE SOFTWARE.
  47. ;; RFC 4648 Base-N Encodings
  48. (define-module (gcrypt base64)
  49. #:export (base64-encode
  50. base64-decode
  51. base64-alphabet
  52. base64url-alphabet
  53. get-delimited-base64
  54. put-delimited-base64)
  55. #:use-module (srfi srfi-11)
  56. #:use-module (srfi srfi-60)
  57. #:use-module (rnrs bytevectors)
  58. #:use-module (rnrs io ports))
  59. (define-syntax define-alias
  60. (syntax-rules ()
  61. ((_ new old)
  62. (define-syntax new (identifier-syntax old)))))
  63. ;; Force the use of Guile's own primitives to avoid the overhead of its 'fx'
  64. ;; procedures.
  65. (define-alias fxbit-field bit-field)
  66. (define-alias fxarithmetic-shift ash)
  67. (define-alias fxarithmetic-shift-left ash)
  68. (define-alias fxand logand)
  69. (define-alias fxior logior)
  70. (define-alias fxxor logxor)
  71. (define-alias fx=? =)
  72. (define-alias fx<=? <=)
  73. (define-alias fxzero? zero?)
  74. (define-alias fx+ +)
  75. (define-alias fx- -)
  76. (define-alias fxmod modulo)
  77. (define-alias mod modulo)
  78. (define-syntax-rule (assert exp)
  79. (unless exp
  80. (throw 'assertion-failure 'exp)))
  81. (define base64-alphabet
  82. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
  83. (define base64url-alphabet
  84. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")
  85. (define base64-encode
  86. (case-lambda
  87. ;; Simple interface. Returns a string containing the canonical
  88. ;; base64 representation of the given bytevector.
  89. ((bv)
  90. (base64-encode bv 0 (bytevector-length bv) #f #f base64-alphabet #f))
  91. ((bv start)
  92. (base64-encode bv start (bytevector-length bv) #f #f base64-alphabet #f))
  93. ((bv start end)
  94. (base64-encode bv start end #f #f base64-alphabet #f))
  95. ((bv start end line-length)
  96. (base64-encode bv start end line-length #f base64-alphabet #f))
  97. ((bv start end line-length no-padding)
  98. (base64-encode bv start end line-length no-padding base64-alphabet #f))
  99. ((bv start end line-length no-padding alphabet)
  100. (base64-encode bv start end line-length no-padding alphabet #f))
  101. ;; Base64 encodes the bytes [start,end[ in the given bytevector.
  102. ;; Lines are limited to line-length characters (unless #f),
  103. ;; which must be a multiple of four. To omit the padding
  104. ;; characters (#\=) set no-padding to a true value. If port is
  105. ;; #f, returns a string.
  106. ((bv start end line-length no-padding alphabet port)
  107. (assert (or (not line-length) (zero? (mod line-length 4))))
  108. (let-values (((p extract) (if port
  109. (values port (lambda () (values)))
  110. (open-string-output-port))))
  111. (letrec ((put (if line-length
  112. (let ((chars 0))
  113. (lambda (p c)
  114. (when (fx=? chars line-length)
  115. (set! chars 0)
  116. (put-char p #\linefeed))
  117. (set! chars (fx+ chars 1))
  118. (put-char p c)))
  119. put-char)))
  120. (let lp ((i start))
  121. (cond ((= i end))
  122. ((<= (+ i 3) end)
  123. (let ((x (bytevector-uint-ref bv i (endianness big) 3)))
  124. (put p (string-ref alphabet (fxbit-field x 18 24)))
  125. (put p (string-ref alphabet (fxbit-field x 12 18)))
  126. (put p (string-ref alphabet (fxbit-field x 6 12)))
  127. (put p (string-ref alphabet (fxbit-field x 0 6)))
  128. (lp (+ i 3))))
  129. ((<= (+ i 2) end)
  130. (let ((x (fxarithmetic-shift-left (bytevector-u16-ref bv i (endianness big)) 8)))
  131. (put p (string-ref alphabet (fxbit-field x 18 24)))
  132. (put p (string-ref alphabet (fxbit-field x 12 18)))
  133. (put p (string-ref alphabet (fxbit-field x 6 12)))
  134. (unless no-padding
  135. (put p #\=))))
  136. (else
  137. (let ((x (fxarithmetic-shift-left (bytevector-u8-ref bv i) 16)))
  138. (put p (string-ref alphabet (fxbit-field x 18 24)))
  139. (put p (string-ref alphabet (fxbit-field x 12 18)))
  140. (unless no-padding
  141. (put p #\=)
  142. (put p #\=)))))))
  143. (extract)))))
  144. ;; Create a lookup table for the alphabet and remember the latest table.
  145. (define get-decode-table
  146. (let ((ascii-table #f)
  147. (extra-table '()) ;in the unlikely case of unicode chars
  148. (table-alphabet #f))
  149. (lambda (alphabet)
  150. (unless (eq? alphabet table-alphabet)
  151. ;; Rebuild the table.
  152. (do ((ascii (make-vector 128 #f))
  153. (extra '())
  154. (i 0 (+ i 1)))
  155. ((= i (string-length alphabet))
  156. (set! ascii-table ascii)
  157. (set! extra-table extra))
  158. (let ((c (char->integer (string-ref alphabet i))))
  159. (if (fx<=? c 127)
  160. (vector-set! ascii c i)
  161. (set! extra (cons (cons c i) extra)))))
  162. (set! table-alphabet alphabet))
  163. (values ascii-table extra-table))))
  164. ;; Decodes a base64 string, optionally ignoring non-alphabet
  165. ;; characters and lack of padding.
  166. (define base64-decode
  167. (case-lambda
  168. ((str)
  169. (base64-decode str base64-alphabet #f))
  170. ((str alphabet)
  171. (base64-decode str alphabet #f))
  172. ((str alphabet port)
  173. (base64-decode str alphabet port #t))
  174. ((str alphabet port strict?)
  175. (base64-decode str alphabet port strict? #t))
  176. ((str alphabet port strict? strict-padding?)
  177. (define (pad? c) (eqv? c (char->integer #\=)))
  178. (let-values (((p extract) (if port
  179. (values port (lambda () (values)))
  180. (open-bytevector-output-port)))
  181. ((ascii extra) (get-decode-table alphabet)))
  182. (define-syntax lookup
  183. (syntax-rules ()
  184. ((_ c) (or (and (fx<=? c 127) (vector-ref ascii c))
  185. (cond ((assv c extra) => cdr)
  186. (else #f))))))
  187. (let lp-restart ((str str))
  188. (let* ((len (if strict?
  189. (string-length str)
  190. (let lp ((i (fx- (string-length str) 1)))
  191. ;; Skip trailing invalid chars.
  192. (cond ((fxzero? i) 0)
  193. ((let ((c (char->integer (string-ref str i))))
  194. (or (lookup c) (pad? c)))
  195. (fx+ i 1))
  196. (else (lp (fx- i 1))))))))
  197. (let lp ((i 0))
  198. (cond
  199. ((fx=? i len)
  200. (extract))
  201. ((fx<=? i (fx- len 4))
  202. (let lp* ((c1 (char->integer (string-ref str i)))
  203. (c2 (char->integer (string-ref str (fx+ i 1))))
  204. (c3 (char->integer (string-ref str (fx+ i 2))))
  205. (c4 (char->integer (string-ref str (fx+ i 3))))
  206. (i i))
  207. (let ((i1 (lookup c1)) (i2 (lookup c2))
  208. (i3 (lookup c3)) (i4 (lookup c4)))
  209. (cond
  210. ((and i1 i2 i3 i4)
  211. ;; All characters present and accounted for.
  212. ;; The most common case.
  213. (let ((x (fxior (fxarithmetic-shift-left i1 18)
  214. (fxarithmetic-shift-left i2 12)
  215. (fxarithmetic-shift-left i3 6)
  216. i4)))
  217. (put-u8 p (fxbit-field x 16 24))
  218. (put-u8 p (fxbit-field x 8 16))
  219. (put-u8 p (fxbit-field x 0 8))
  220. (lp (fx+ i 4))))
  221. ((and i1 i2 i3 (pad? c4) (= i (- len 4)))
  222. ;; One padding character at the end of the input.
  223. (let ((x (fxior (fxarithmetic-shift-left i1 18)
  224. (fxarithmetic-shift-left i2 12)
  225. (fxarithmetic-shift-left i3 6))))
  226. (put-u8 p (fxbit-field x 16 24))
  227. (put-u8 p (fxbit-field x 8 16))
  228. (lp (fx+ i 4))))
  229. ((and i1 i2 (pad? c3) (pad? c4) (= i (- len 4)))
  230. ;; Two padding characters.
  231. (let ((x (fxior (fxarithmetic-shift-left i1 18)
  232. (fxarithmetic-shift-left i2 12))))
  233. (put-u8 p (fxbit-field x 16 24))
  234. (lp (fx+ i 4))))
  235. ((not strict?)
  236. ;; Non-alphabet characters.
  237. (let lp ((i i) (c* '()) (n 4))
  238. (cond ((fxzero? n)
  239. ;; Found four valid characters.
  240. (lp* (cadddr c*) (caddr c*) (cadr c*) (car c*)
  241. (fx- i 4)))
  242. ((fx=? i len)
  243. (error 'base64-decode
  244. "Invalid input in non-strict mode."
  245. i c*))
  246. (else
  247. ;; Gather alphabetic (or valid
  248. ;; padding) characters.
  249. (let ((c (char->integer (string-ref str i))))
  250. (cond ((or (lookup c)
  251. (and (pad? c)
  252. (fx<=? n 2)
  253. (fx=? i (fx- len n))))
  254. (lp (fx+ i 1) (cons c c*) (fx- n 1)))
  255. (else
  256. (lp (fx+ i 1) c* n))))))))
  257. (else
  258. (error 'base64-decode
  259. "Invalid input in strict mode."
  260. c1 c2 c3 c4))))))
  261. ((not strict-padding?)
  262. ;; Append an appropriate amount of padding after the
  263. ;; remaining characters.
  264. (if (<= 2 (- len i) 3)
  265. (lp-restart (string-append (substring str i (string-length str))
  266. (if (= (- len i) 2) "==" "=")))
  267. (error 'base64-decode "The input is too short." i)))
  268. (else
  269. (error 'base64-decode
  270. "The input is too short, it may be missing padding."
  271. i))))))))))
  272. (define (get-line-comp f port)
  273. (if (port-eof? port)
  274. (eof-object)
  275. (f (get-line port))))
  276. ;; Reads the common -----BEGIN/END type----- delimited format from
  277. ;; the given port. Returns two values: a string with the type and a
  278. ;; bytevector containing the base64 decoded data. The second value
  279. ;; is the eof object if there is an eof before the BEGIN delimiter.
  280. (define get-delimited-base64
  281. (case-lambda
  282. ((port)
  283. (get-delimited-base64 port #t))
  284. ((port strict)
  285. (define (get-first-data-line port)
  286. ;; Some MIME data has header fields in the same format as mail
  287. ;; or http. These are ignored.
  288. (let ((line (get-line-comp string-trim-both port)))
  289. (cond ((eof-object? line) line)
  290. ((string-index line #\:)
  291. (let lp () ;read until empty line
  292. (let ((line (get-line-comp string-trim-both port)))
  293. (if (string=? line "")
  294. (get-line-comp string-trim-both port)
  295. (lp)))))
  296. (else line))))
  297. (let ((line (get-line-comp string-trim-both port)))
  298. (cond ((eof-object? line)
  299. (values "" (eof-object)))
  300. ((string=? line "")
  301. (get-delimited-base64 port))
  302. ((and (string-prefix? "-----BEGIN " line)
  303. (string-suffix? "-----" line))
  304. (let* ((type (substring line 11 (- (string-length line) 5)))
  305. (endline (string-append "-----END " type "-----")))
  306. (let-values ([(outp extract) (open-bytevector-output-port)])
  307. (let lp ((previous "") (line (get-first-data-line port)))
  308. (cond ((eof-object? line)
  309. (error 'get-delimited-base64
  310. "unexpected end of file"))
  311. ((string-prefix? "-" line)
  312. (unless (string=? line endline)
  313. (error 'get-delimited-base64
  314. "bad end delimiter" type line))
  315. (values type (extract)))
  316. ((and (= (string-length line) 5)
  317. (string-prefix? "=" line))
  318. ;; Skip Radix-64 checksum
  319. (lp previous (get-line-comp string-trim-both port)))
  320. ((not (fxzero? (fxmod (fx+ (string-length previous)
  321. (string-length line))
  322. 4)))
  323. ;; OpenSSH outputs lines with a bad length
  324. (lp (string-append previous line)
  325. (get-line-comp string-trim-both port)))
  326. (else
  327. (base64-decode (string-append previous line) base64-alphabet outp)
  328. (lp "" (get-line-comp string-trim-both port))))))))
  329. (else ;skip garbage (like in openssl x509 -in foo -text output).
  330. (get-delimited-base64 port)))))))
  331. (define put-delimited-base64
  332. (case-lambda
  333. ((port type bv line-length)
  334. (display (string-append "-----BEGIN " type "-----\n") port)
  335. (base64-encode bv 0 (bytevector-length bv)
  336. line-length #f base64-alphabet port)
  337. (display (string-append "\n-----END " type "-----\n") port))
  338. ((port type bv)
  339. (put-delimited-base64 port type bv 76))))