Skip to content

Commit 95e904e

Browse files
authored
Merge pull request #10 from cgay/dev
Modernize to match RFC 4648
2 parents 1b3fa20 + c16a618 commit 95e904e

File tree

7 files changed

+297
-267
lines changed

7 files changed

+297
-267
lines changed

base64.dylan

Lines changed: 117 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,144 @@
11
Module: base64
2-
Synopsis: Base64 encoding/decoding
3-
Author: Carl Gay
2+
Synopsis: Base64 encoding/decoding as defined in RFC 4648
43
License: This code is in the public domain
54
Warranty: Distributed WITHOUT WARRANTY OF ANY KIND
65

76

8-
// This file implements the Base64 transfer encoding algorithm as
9-
// defined in RFC 1521 by Borensten & Freed, September 1993.
10-
//
11-
// Original version written in Common Lisp by Juri Pakaste <[email protected]>.
12-
// Converted to Dylan by Carl Gay, July 2002.
7+
// TODO:
8+
// * support line breaks
9+
// * streaming / chunking a la CL's qbase64
1310

14-
define constant $standard-encoding-vector :: <byte-string>
15-
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1611

17-
define constant $http-encoding-vector :: <byte-string>
18-
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$!@";
12+
define constant $standard-scheme = #"_base64";
13+
define constant $url-scheme = #"_base64url";
14+
define constant <scheme> = one-of($standard-scheme, $url-scheme);
1915

20-
// ---TODO: line breaks?
21-
//define constant $base64-line-break :: <byte-string> = "\n";
16+
define constant $pad-char :: <character> = '=';
2217

23-
// I thought FunDev had <integer-vector> built in, but apparently not.
24-
//
25-
define constant <int-vector> = limited(<vector>, of: <integer>);
18+
// Base 64 Encoding
19+
// https://datatracker.ietf.org/doc/html/rfc4648#section-4
20+
define constant $standard-encoding :: <byte-string>
21+
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
22+
23+
// Base 64 Encoding with URL and Filename Safe Alphabet
24+
// https://datatracker.ietf.org/doc/html/rfc4648#section-5
25+
define constant $url-encoding :: <byte-string>
26+
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
27+
28+
define constant $standard-decoding :: <byte-vector>
29+
= make-decoding-vector($standard-encoding);
30+
31+
define constant $url-decoding :: <byte-vector>
32+
= make-decoding-vector($url-encoding);
2633

2734
define function make-decoding-vector
28-
(encoding-vector) => (v :: <int-vector>)
29-
let v = make(<int-vector>, size: 256, fill: -1);
30-
for (index from 0 below v.size,
35+
(encoding-vector :: <byte-string>) => (v :: <byte-vector>)
36+
let v = make(<byte-vector>, size: 128);
37+
for (i from 0,
3138
char in encoding-vector)
32-
v[as(<integer>, char)] := index;
39+
let code = as(<integer>, char);
40+
v[code] := i;
3341
end;
3442
v
35-
end;
43+
end function;
3644

37-
define constant $standard-decoding-vector :: <int-vector>
38-
= make-decoding-vector($standard-encoding-vector);
45+
define inline function encoded-length
46+
(input-length :: <integer>, pad? :: <boolean>)
47+
=> (encoded-length :: <integer>)
48+
if (pad?)
49+
// Four chars for every group of 3 bytes including the final group.
50+
ceiling/(input-length, 3) * 4
51+
else
52+
// One char for every 6 bits in the total bits.
53+
ceiling/(input-length * 8, 6)
54+
end
55+
end function;
3956

40-
define constant $http-decoding-vector :: <int-vector>
41-
= make-decoding-vector($http-encoding-vector);
57+
define inline function decoded-length
58+
(input :: <byte-string>) => (decoded-length :: <integer>)
59+
let len = input.size;
60+
// Discard at most two trailing pad chars from the length.
61+
if (len - 2 >= 2 & input[len - 2] == $pad-char)
62+
len := len - 2;
63+
elseif (len - 1 >= 3 & input[len - 1] == $pad-char)
64+
len := len - 1
65+
end;
66+
floor/(len * 6, 8)
67+
end function;
4268

69+
// Encode `bytes` into base 64 in a <byte-string> using the character set specified by
70+
// `scheme`. If `pad?` is true the returned string will be a multiple of 4 characters in
71+
// length, padded with 0 to 2 '=' characters.
4372
define function base64-encode
44-
(string :: <byte-string>, #key encoding :: <symbol> = #"standard")
45-
=> (s :: <byte-string>)
73+
(bytes :: <sequence>,
74+
#key scheme :: <scheme> = $standard-scheme, pad? :: <boolean> = #t)
75+
=> (string :: <byte-string>)
4676
let encoding-vector :: <byte-string>
47-
= select (encoding)
48-
#"standard" => $standard-encoding-vector;
49-
#"http" => $http-encoding-vector;
50-
end;
51-
let result = make(<byte-string>, size: 4 * floor/(2 + string.size, 3));
52-
for (sidx from 0 by 3,
53-
didx from 0 by 4,
54-
while: sidx < string.size)
55-
let chars = 2;
56-
let value = ash(logand(#xFF, as(<integer>, string[sidx])), 8);
57-
for (n from 1 to 2)
58-
when (sidx + n < string.size)
59-
let char-code :: <integer> = as(<integer>, string[sidx + n]);
60-
value := logior(value, logand(#xFF, char-code));
61-
chars := chars + 1;
62-
end;
63-
when (n = 1)
64-
value := ash(value, 8);
77+
= select (scheme)
78+
$standard-scheme => $standard-encoding;
79+
$url-scheme => $url-encoding;
6580
end;
81+
let convert = if (instance?(bytes, <string>))
82+
curry(as, <integer>)
83+
else
84+
identity
85+
end;
86+
let nbytes :: <integer> = bytes.size;
87+
let nchars :: <integer> = encoded-length(nbytes, pad?);
88+
let result = make(<byte-string>, size: nchars);
89+
let bi :: <integer> = 0; // bytes index
90+
let ri :: <integer> = 0; // result index
91+
while (bi < nbytes)
92+
let b1 :: <byte> = convert(bytes[bi]);
93+
let b2 :: <byte> = if (bi + 1 < nbytes) convert(bytes[bi + 1]) else 0 end;
94+
let b3 :: <byte> = if (bi + 2 < nbytes) convert(bytes[bi + 2]) else 0 end;
95+
let n :: <integer> = ash(b1, 16) + ash(b2, 8) + b3;
96+
for (shift from -18 to 0 by 6,
97+
while: ri < nchars) // can happen for pad?: #f
98+
let index = logand(ash(n, shift), #b111111);
99+
result[ri] := encoding-vector[index];
100+
ri := ri + 1;
101+
end;
102+
bi := bi + 3;
103+
end while;
104+
if (pad?)
105+
let len :: <integer> = encoded-length(nbytes, #f);
106+
for (i from len below nchars)
107+
result[i] := $pad-char;
66108
end;
67-
result[didx + 3] := encoding-vector[if (chars > 3) logand(value, #x3F) else 64 end];
68-
value := ash(value, -6);
69-
result[didx + 2] := encoding-vector[if (chars > 2) logand(value, #x3F) else 64 end];
70-
value := ash(value, -6);
71-
result[didx + 1] := encoding-vector[logand(value, #x3F)];
72-
value := ash(value, -6);
73-
result[didx + 0] := encoding-vector[logand(value, #x3F)];
74109
end;
75110
result
76-
end;
77-
111+
end function;
112+
78113
define function base64-decode
79-
(string :: <byte-string>, #key encoding :: <symbol> = #"standard")
80-
=> (s :: <byte-string>)
81-
let result = make(<byte-string>, size: 3 * floor/(string.size, 4));
82-
let ridx :: <integer> = 0;
83-
block (exit-block)
84-
let decoding-vector :: <int-vector>
85-
= select (encoding)
86-
#"standard" => $standard-decoding-vector;
87-
#"http" => $http-decoding-vector;
88-
end;
89-
let bitstore :: <integer> = 0;
90-
let bitcount :: <integer> = 0;
91-
for (char :: <byte-character> in string)
92-
let value = decoding-vector[as(<integer>, char)];
93-
unless (value == -1 | value == 64)
94-
bitstore := logior(ash(bitstore, 6), value);
95-
bitcount := bitcount + 6;
96-
when (bitcount >= 8)
97-
bitcount := bitcount - 8;
98-
let code = logand(ash(bitstore, 0 - bitcount), #xFF);
99-
if (zero?(code))
100-
exit-block();
101-
else
102-
result[ridx] := as(<byte-character>, code);
103-
ridx := ridx + 1;
104-
bitstore := logand(bitstore, #xFF);
105-
end;
106-
end;
114+
(string :: <byte-string>, #key scheme :: <scheme> = $standard-scheme)
115+
=> (bytes :: <byte-vector>)
116+
let decoding-vector :: <byte-vector>
117+
= select (scheme)
118+
$standard-scheme => $standard-decoding;
119+
$url-scheme => $url-decoding;
107120
end;
121+
let nchars :: <integer> = string.size;
122+
let nbytes :: <integer> = decoded-length(string);
123+
let bytes = make(<byte-vector>, size: nbytes);
124+
let bi :: <integer> = 0;
125+
let si :: <integer> = 0;
126+
while (si < nchars)
127+
let c1 = as(<integer>, string[si]); si := si + 1;
128+
let c2 = si < nchars & as(<integer>, string[si]); si := si + 1;
129+
let c3 = si < nchars & as(<integer>, string[si]); si := si + 1;
130+
let c4 = si < nchars & as(<integer>, string[si]); si := si + 1;
131+
let d1 :: <byte> = decoding-vector[c1];
132+
let d2 :: <byte> = if (c2) decoding-vector[c2] else 0 end;
133+
let d3 :: <byte> = if (c3) decoding-vector[c3] else 0 end;
134+
let d4 :: <byte> = if (c4) decoding-vector[c4] else 0 end;
135+
let n :: <integer> = ash(d1, 18) + ash(d2, 12) + ash(d3, 6) + d4;
136+
for (shift from -16 to 0 by 8,
137+
while: bi < nbytes)
138+
bytes[bi] := logand(ash(n, shift), #xff);
139+
bi := bi + 1;
108140
end;
109-
end block;
110-
copy-sequence(result, start: 0, end: ridx)
111-
end;
141+
end while;
142+
bytes
143+
end function;
112144

documentation/source/author.rst

Lines changed: 0 additions & 5 deletions
This file was deleted.

documentation/source/index.rst

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,102 @@
1+
******
12
Base64
2-
======
3+
******
4+
5+
.. current-library:: base64
6+
.. current-module:: base64
7+
8+
This library implements the Base64 encoding algorithm as defined in `RFC 4648
9+
<https://datatracker.ietf.org/doc/html/rfc4648>`_.
310

411
.. toctree::
512
:maxdepth: 3
613
:hidden:
714

8-
usage
9-
reference
10-
author
15+
Usage
16+
=====
17+
18+
Add ``"base64"`` to the dependencies listed in your project's "dylan-package.json" file
19+
and run ``deft update`` to install the base64 package and update your workspace
20+
registry. Add ``use base64;`` to your library and module definitions.
21+
22+
The ``base64`` library exports two functions:
23+
24+
- :func:`base64-encode`
25+
- :func:`base64-decode`
26+
27+
Both of these function accept a ``scheme:`` keyword argument of type :type:`<scheme>`
28+
which may be one of the following constants:
29+
30+
- :const:`$standard-scheme` - the standard base64 character set including ``+`` and
31+
``/``.
32+
- :const:`$url-scheme` - a character set safe for use in URLs and filenames, in which
33+
``+`` is replaced by ``-`` and ``/`` is replaced by ``_``.
34+
35+
.. note:: There is currently no support for line breaks or whitespace in the
36+
input/output, nor for base64 streams. Pull requests welcome.
37+
38+
39+
The base64 Module
40+
=================
41+
42+
.. type:: <scheme>
43+
44+
Equivalent to ``one-of($standard-scheme, $url-scheme)``.
45+
46+
.. constant:: $standard-scheme
47+
48+
An instance of :type:`<scheme>` indicating to use the `standard base64 encoding
49+
character set <https://datatracker.ietf.org/doc/html/rfc4648#section-4>`_.
50+
51+
.. constant:: $url-scheme
52+
53+
An instance of :type:`<scheme>` indicating to use the `URL and filename safe base64
54+
character set <https://datatracker.ietf.org/doc/html/rfc4648#section-5>`_.
55+
56+
.. function:: base64-encode
57+
58+
Encode a byte sequence as a base 64 byte string as defined by `RFC 4648
59+
<https://datatracker.ietf.org/doc/html/rfc4648>`_.
60+
61+
:signature: base64-encode (bytes, #key scheme, pad?) => (byte-string)
62+
:parameter bytes: An instance of :drm:`<sequence>`. An error is signaled if the
63+
elements of this sequence are not either integers in the range 0 - 255 or byte
64+
characters.
65+
:parameter #key scheme: An instance of :type:`<scheme>`. May be either
66+
:const:`$standard-scheme` (the default) or :const:`$url-scheme`.
67+
:parameter #key pad?: An instance of :drm:`<boolean>`. If true (the default) the
68+
returned byte string is padded with "=" to a multiple of 4 characters in
69+
length. This results in 0, 1, or 2 "=" characters at the end of the string.
70+
:value string: An instance of :drm:`<byte-string>`.
71+
72+
:example:
73+
74+
.. code-block:: dylan
75+
76+
base64-encode("foo") => "Zm9v"
77+
base64-encode(#(251, 252, 253, 254, 255)) => "+/z9/v8="
78+
base64-encode(#(251, 252, 253, 254, 255), scheme: $url-scheme) => "-_z9_v8="
79+
80+
.. function:: base64-decode
81+
82+
Decode a base 64 encoded string into a byte sequence as defined by `RFC 4648
83+
<https://datatracker.ietf.org/doc/html/rfc4648>`_.
84+
85+
:signature: base64-decode (string, #key scheme) => (bytes)
86+
:parameter string: An instance of :drm:`<byte-string>`.
87+
:parameter #key scheme: An instance of :type:`<scheme>`. May be either
88+
:const:`$standard-scheme` (the default) or :const:`$url-scheme`.
89+
:value bytes: An instance of :class:`<byte-vector>`.
90+
:example:
91+
92+
:description:
1193

12-
Base64 implementation for Dylan
94+
Padding characters ("=") at the end of the input string are automatically detected
95+
and ignored.
1396

14-
This library implements the Base64 transfer encoding algorithm as
15-
defined in `RFC 1521 <https://datatracker.ietf.org/doc/html/rfc1521>`__
16-
by Borensten & Freed, September 1993.
97+
:example:
1798

18-
Indices and tables
19-
------------------
99+
.. code-block:: dylan
20100
21-
* :ref:`genindex`
101+
base64-decode("Zm8=") => {<simple-byte-vector>: 102, 111}
102+
base64-decode("Zm8") => {<simple-byte-vector>: 102, 111}

documentation/source/reference.rst

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)