aboutsummaryrefslogtreecommitdiff
path: root/module/base64.scm
blob: e9dc935727bf7e623d23dcd5f507304dde94f4dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
(define-module (base64)
  :use-module ((srfi srfi-71) :select (let*))
  :use-module (srfi srfi-88) ; suffix keywords
  :use-module ((rnrs bytevectors)
               :select (bytevector-u8-ref
                        bytevector-u8-set!
                        bytevector-length
                        make-bytevector))
  :use-module ((rnrs io ports)
               :select (string->bytevector
                        bytevector->string
                        make-transcoder
                        latin-1-codec
                        native-transcoder))
  :export (base64->bytevector
           bytevector->base64
           base64-string->bytevector
           bytevector->base64-string
           base64encode
           base64decode
           ))

(define table
  (list->vector
   (map char->integer
        (string->list
         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"))))

(define (real->encoded byte)
  (vector-ref table byte))

(define (encoded->real byte)
  (define A (char->integer #\A))
  (define Z (char->integer #\Z))
  (define a (char->integer #\a))
  (define z (char->integer #\z))
  (define zero (char->integer #\0))
  (define nine (char->integer #\9))
  (cond [(= byte (char->integer #\=)) 0]
        [(= byte (char->integer #\+)) 62]
        [(= byte (char->integer #\/)) 63]
        [(<= A byte Z)
         (- byte A)]
        [(<= a byte z)
         (+ 26 (- byte a))]
        [(<= zero byte nine)
         (+ 26 26 (- byte zero))]
        [else (scm-error 'decoding-error
                         "encoded->real"
                         "Invalid character in Base64 string: ~s"
                         (list byte) #f)]))

(define ref
  (make-procedure-with-setter
   bytevector-u8-ref
   bytevector-u8-set!))

(define (base64->bytevector bv)
  (catch 'out-of-range
    (lambda ()
     (let ((len* (bytevector-length bv)))
       (if (zero? len*)
           (make-bytevector 0)
           (let* ((rest (+ (if (= (char->integer #\=) (ref bv (- len* 1)))
                               1 0)
                           (if (= (char->integer #\=) (ref bv (- len* 2)))
                               1 0)))
                  (x (/ (- len* rest) 4))
                  (ret-len (floor (* 3 x))))

             (define ret (make-bytevector ret-len))

             (do ((i 0 (1+ i)))
                 ((>= i (floor x)))
               (let ((a (encoded->real (ref bv (+ (* i 4) 0))))
                     (b (encoded->real (ref bv (+ (* i 4) 1))))
                     (c (encoded->real (ref bv (+ (* i 4) 2))))
                     (d (encoded->real (ref bv (+ (* i 4) 3)))))
                 (let ((aa (logior (ash a 2) (ash b -4)))
                       (ab (logior (ash (logand b #xF) 4) (ash c -2)))
                       (ac (logior (ash (logand c 3) 6) d)))
                   (set! (ref ret (+ (* i 3) 0)) aa)
                   (set! (ref ret (+ (* i 3) 1)) ab)
                   (set! (ref ret (+ (* i 3) 2)) ac))))


             (case rest
               [(2)
                (let ((a (encoded->real (ref bv (+ (* (floor x) 4) 0))))
                      (b (encoded->real (ref bv (+ (* (floor x) 4) 1)))))
                  (let ((aa (logior (ash a 2) (ash b -4))))
                    (set! (ref ret (- ret-len 1)) aa)))]
               [(1)
                (let ((a (encoded->real (ref bv (+ (* (floor x) 4) 0))))
                      (b (encoded->real (ref bv (+ (* (floor x) 4) 1))))
                      (c (encoded->real (ref bv (+ (* (floor x) 4) 2)))))
                  (let ((aa (logior (ash a 2) (ash b -4)))
                        (ab (logior (ash (logand b #xF) 4) (ash c -2))))
                    (set! (ref ret (- ret-len 2)) aa)
                    (set! (ref ret (- ret-len 1)) ab)))])

             ret))))
    (lambda (err proc fmt args data)
      (scm-error 'decoding-error "base64->bytevector"
                 (format #f "~~a in ~~a: ~a" fmt)
                 (cons* err (or proc "unknown function") args)
                 data))))

(define (bytevector->base64 bv)
  (let* ((len (bytevector-length bv))
         (iterations rest (floor/ len 3)))
    (define ret (make-bytevector (+ (* 4 iterations)
                                    (if (zero? rest)
                                        0 4))))

    (do ((i 0 (1+ i)))
        ((>= i iterations))
      (let ((a (ref bv (+ (* i 3) 0)))
            (b (ref bv (+ (* i 3) 1)))
            (c (ref bv (+ (* i 3) 2))))
        (let ((aa (ash a -2))
              (ab (logior (ash (logand #b11 a) 4)   (ash b -4)))
              (ac (logior (ash (logand b #b1111) 2) (ash c -6)))
              (ad (logand c #x3F)))
          (set! (ref ret (+ (* i 4) 0)) (real->encoded aa))
          (set! (ref ret (+ (* i 4) 1)) (real->encoded ab))
          (set! (ref ret (+ (* i 4) 2)) (real->encoded ac))
          (set! (ref ret (+ (* i 4) 3)) (real->encoded ad)))))

    (case rest
      [(1)
       (let ((byte (ref bv (- len 1))))
         (let ((a (ash byte -2))
               (b (ash (logand byte 3) 4)))
           (set! (ref ret (+ 0 (* 4 iterations))) (real->encoded a))
           (set! (ref ret (+ 1 (* 4 iterations))) (real->encoded b))
           (set! (ref ret (+ 2 (* 4 iterations))) (char->integer #\=))
           (set! (ref ret (+ 3 (* 4 iterations))) (char->integer #\=))))]
      [(2)
       (let ((byte1 (ref bv (- len 2)))
             (byte2 (ref bv (- len 1))))
         (let ((a (ash byte1 -2))
               (b (logior (ash (logand byte1 3) 4) (ash byte2 -4 )))
               (c (ash (logand byte2 #xF) 2)))
           (set! (ref ret (+ 0 (* 4 iterations))) (real->encoded a))
           (set! (ref ret (+ 1 (* 4 iterations))) (real->encoded b))
           (set! (ref ret (+ 2 (* 4 iterations))) (real->encoded c))
           (set! (ref ret (+ 3 (* 4 iterations))) (char->integer #\=))))])

    ret))

;; string -> bv
(define (base64-string->bytevector string)
  (base64->bytevector
   (string->bytevector string (make-transcoder (latin-1-codec)))))

;; bv -> string
(define (bytevector->base64-string bv)
  (bytevector->string (bytevector->base64 bv)
                      (make-transcoder (latin-1-codec))))

;; string -> string
(define* (base64encode string optional: (transcoder (native-transcoder)))
  (bytevector->string
   (bytevector->base64 (string->bytevector string transcoder))
   (make-transcoder (latin-1-codec))))

;; string -> string
(define* (base64decode string optional: (transcoder (native-transcoder)))
  (bytevector->string
   (base64->bytevector
    (string->bytevector string (make-transcoder (latin-1-codec))))
   transcoder))