base64.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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.
  6. ;;
  7. ;; Some optimizations made by Ludovic Courtès <ludo@gnu.org>, 2015.
  8. ;; Turned into a Guile module (instead of R6RS).
  9. ;;
  10. ;; This program is free software: you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation, either version 3 of the License, or
  13. ;; (at your option) any later version.
  14. ;;
  15. ;; This program is distributed in the hope that it will be useful,
  16. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;; GNU General Public License for more details.
  19. ;;
  20. ;; You should have received a copy of the GNU General Public License
  21. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. ;;
  23. ;; This file incorporates work covered by the following copyright and
  24. ;; permission notice:
  25. ;;
  26. ;; Copyright © 2009, 2010 Göran Weinholt <goran@weinholt.se>
  27. ;;
  28. ;; Permission is hereby granted, free of charge, to any person obtaining a
  29. ;; copy of this software and associated documentation files (the "Software"),
  30. ;; to deal in the Software without restriction, including without limitation
  31. ;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
  32. ;; and/or sell copies of the Software, and to permit persons to whom the
  33. ;; Software is furnished to do so, subject to the following conditions:
  34. ;;
  35. ;; The above copyright notice and this permission notice shall be included in
  36. ;; all copies or substantial portions of the Software.
  37. ;;
  38. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  39. ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  40. ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  41. ;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  42. ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  43. ;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  44. ;; DEALINGS IN THE SOFTWARE.
  45. ;; RFC 4648 Base-N Encodings
  46. (define-module (guix base64)
  47. #:export (base64-encode
  48. base64-decode
  49. base64-alphabet
  50. base64url-alphabet
  51. get-delimited-base64
  52. put-delimited-base64)
  53. #:use-module (srfi srfi-11)
  54. #:use-module (srfi srfi-60)
  55. #:use-module (rnrs bytevectors)
  56. #:use-module (rnrs io ports))
  57. (define-syntax define-alias
  58. (syntax-rules ()
  59. ((_ new old)
  60. (define-syntax new (identifier-syntax old)))))
  61. ;; Force the use of Guile's own primitives to avoid the overhead of its 'fx'
  62. ;; procedures.
  63. (define-alias fxbit-field bit-field)
  64. (define-alias fxarithmetic-shift ash)
  65. (define-alias fxarithmetic-shift-left ash)
  66. (define-alias fxand logand)
  67. (define-alias fxior logior)
  68. (define-alias fxxor logxor)
  69. (define-alias fx=? =)
  70. (define-alias fx+ +)
  71. (define-alias mod modulo)
  72. (define-syntax-rule (assert exp)
  73. (unless exp
  74. (throw 'assertion-failure 'exp)))
  75. (define base64-alphabet
  76. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
  77. (define base64url-alphabet
  78. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")
  79. (define base64-encode
  80. (case-lambda
  81. ;; Simple interface. Returns a string containing the canonical
  82. ;; base64 representation of the given bytevector.
  83. ((bv)
  84. (base64-encode bv 0 (bytevector-length bv) #f #f base64-alphabet #f))
  85. ((bv start)
  86. (base64-encode bv start (bytevector-length bv) #f #f base64-alphabet #f))
  87. ((bv start end)
  88. (base64-encode bv start end #f #f base64-alphabet #f))
  89. ((bv start end line-length)
  90. (base64-encode bv start end line-length #f base64-alphabet #f))
  91. ((bv start end line-length no-padding)
  92. (base64-encode bv start end line-length no-padding base64-alphabet #f))
  93. ((bv start end line-length no-padding alphabet)
  94. (base64-encode bv start end line-length no-padding alphabet #f))
  95. ;; Base64 encodes the bytes [start,end[ in the given bytevector.
  96. ;; Lines are limited to line-length characters (unless #f),
  97. ;; which must be a multiple of four. To omit the padding
  98. ;; characters (#\=) set no-padding to a true value. If port is
  99. ;; #f, returns a string.
  100. ((bv start end line-length no-padding alphabet port)
  101. (assert (or (not line-length) (zero? (mod line-length 4))))
  102. (let-values (((p extract) (if port
  103. (values port (lambda () (values)))
  104. (open-string-output-port))))
  105. (letrec ((put (if line-length
  106. (let ((chars 0))
  107. (lambda (p c)
  108. (when (fx=? chars line-length)
  109. (set! chars 0)
  110. (put-char p #\linefeed))
  111. (set! chars (fx+ chars 1))
  112. (put-char p c)))
  113. put-char)))
  114. (let lp ((i start))
  115. (cond ((= i end))
  116. ((<= (+ i 3) end)
  117. (let ((x (bytevector-uint-ref bv i (endianness big) 3)))
  118. (put p (string-ref alphabet (fxbit-field x 18 24)))
  119. (put p (string-ref alphabet (fxbit-field x 12 18)))
  120. (put p (string-ref alphabet (fxbit-field x 6 12)))
  121. (put p (string-ref alphabet (fxbit-field x 0 6)))
  122. (lp (+ i 3))))
  123. ((<= (+ i 2) end)
  124. (let ((x (fxarithmetic-shift-left (bytevector-u16-ref bv i (endianness big)) 8)))
  125. (put p (string-ref alphabet (fxbit-field x 18 24)))
  126. (put p (string-ref alphabet (fxbit-field x 12 18)))
  127. (put p (string-ref alphabet (fxbit-field x 6 12)))
  128. (unless no-padding
  129. (put p #\=))))
  130. (else
  131. (let ((x (fxarithmetic-shift-left (bytevector-u8-ref bv i) 16)))
  132. (put p (string-ref alphabet (fxbit-field x 18 24)))
  133. (put p (string-ref alphabet (fxbit-field x 12 18)))
  134. (unless no-padding
  135. (put p #\=)
  136. (put p #\=)))))))
  137. (extract)))))
  138. ;; Decodes a base64 string. The string must contain only pure
  139. ;; unpadded base64 data.
  140. (define base64-decode
  141. (case-lambda
  142. ((str)
  143. (base64-decode str base64-alphabet #f))
  144. ((str alphabet)
  145. (base64-decode str alphabet #f))
  146. ((str alphabet port)
  147. (unless (zero? (mod (string-length str) 4))
  148. (error 'base64-decode
  149. "input string must be a multiple of four characters"))
  150. (let-values (((p extract) (if port
  151. (values port (lambda () (values)))
  152. (open-bytevector-output-port))))
  153. (do ((i 0 (+ i 4)))
  154. ((= i (string-length str))
  155. (extract))
  156. (let ((c1 (string-ref str i))
  157. (c2 (string-ref str (+ i 1)))
  158. (c3 (string-ref str (+ i 2)))
  159. (c4 (string-ref str (+ i 3))))
  160. ;; TODO: be more clever than string-index
  161. (let ((i1 (string-index alphabet c1))
  162. (i2 (string-index alphabet c2))
  163. (i3 (string-index alphabet c3))
  164. (i4 (string-index alphabet c4)))
  165. (cond ((and i1 i2 i3 i4)
  166. (let ((x (fxior (fxarithmetic-shift-left i1 18)
  167. (fxarithmetic-shift-left i2 12)
  168. (fxarithmetic-shift-left i3 6)
  169. i4)))
  170. (put-u8 p (fxbit-field x 16 24))
  171. (put-u8 p (fxbit-field x 8 16))
  172. (put-u8 p (fxbit-field x 0 8))))
  173. ((and i1 i2 i3 (char=? c4 #\=)
  174. (= i (- (string-length str) 4)))
  175. (let ((x (fxior (fxarithmetic-shift-left i1 18)
  176. (fxarithmetic-shift-left i2 12)
  177. (fxarithmetic-shift-left i3 6))))
  178. (put-u8 p (fxbit-field x 16 24))
  179. (put-u8 p (fxbit-field x 8 16))))
  180. ((and i1 i2 (char=? c3 #\=) (char=? c4 #\=)
  181. (= i (- (string-length str) 4)))
  182. (let ((x (fxior (fxarithmetic-shift-left i1 18)
  183. (fxarithmetic-shift-left i2 12))))
  184. (put-u8 p (fxbit-field x 16 24))))
  185. (else
  186. (error 'base64-decode "invalid input"
  187. (list c1 c2 c3 c4)))))))))))
  188. (define (get-line-comp f port)
  189. (if (port-eof? port)
  190. (eof-object)
  191. (f (get-line port))))
  192. ;; Reads the common -----BEGIN/END type----- delimited format from
  193. ;; the given port. Returns two values: a string with the type and a
  194. ;; bytevector containing the base64 decoded data. The second value
  195. ;; is the eof object if there is an eof before the BEGIN delimiter.
  196. (define (get-delimited-base64 port)
  197. (define (get-first-data-line port)
  198. ;; Some MIME data has header fields in the same format as mail
  199. ;; or http. These are ignored.
  200. (let ((line (get-line-comp string-trim-both port)))
  201. (cond ((eof-object? line) line)
  202. ((string-index line #\:)
  203. (let lp () ;read until empty line
  204. (let ((line (get-line-comp string-trim-both port)))
  205. (if (string=? line "")
  206. (get-line-comp string-trim-both port)
  207. (lp)))))
  208. (else line))))
  209. (let ((line (get-line-comp string-trim-both port)))
  210. (cond ((eof-object? line)
  211. (values "" (eof-object)))
  212. ((string=? line "")
  213. (get-delimited-base64 port))
  214. ((and (string-prefix? "-----BEGIN " line)
  215. (string-suffix? "-----" line))
  216. (let* ((type (substring line 11 (- (string-length line) 5)))
  217. (endline (string-append "-----END " type "-----")))
  218. (let-values (((outp extract) (open-bytevector-output-port)))
  219. (let lp ((line (get-first-data-line port)))
  220. (cond ((eof-object? line)
  221. (error 'get-delimited-base64
  222. "unexpected end of file"))
  223. ((string-prefix? "-" line)
  224. (unless (string=? line endline)
  225. (error 'get-delimited-base64
  226. "bad end delimiter" type line))
  227. (values type (extract)))
  228. (else
  229. (unless (and (= (string-length line) 5)
  230. (string-prefix? "=" line)) ;Skip Radix-64 checksum
  231. (base64-decode line base64-alphabet outp))
  232. (lp (get-line-comp string-trim-both port))))))))
  233. (else ;skip garbage (like in openssl x509 -in foo -text output).
  234. (get-delimited-base64 port)))))
  235. (define put-delimited-base64
  236. (case-lambda
  237. ((port type bv line-length)
  238. (display (string-append "-----BEGIN " type "-----\n") port)
  239. (base64-encode bv 0 (bytevector-length bv)
  240. line-length #f base64-alphabet port)
  241. (display (string-append "\n-----END " type "-----\n") port))
  242. ((port type bv)
  243. (put-delimited-base64 port type bv 76))))