Skip to content

Commit 70d9363

Browse files
Add Base64 safe (#13672)
* Implement RFC-4648 Section-7 * #13672 (comment)
1 parent dd362ab commit 70d9363

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ echo f
112112
- Added `times.isLeapDay`
113113
- Added a new module, `std / compilesettings` for querying the compiler about
114114
diverse configuration settings.
115+
- `base64` adds URL-Safe Base64, implements RFC-4648 Section-7.
116+
115117

116118
## Library changes
117119

lib/pure/base64.nim

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
## let decoded = decode("SGVsbG8gV29ybGQ=")
4747
## assert decoded == "Hello World"
4848
##
49+
## URL Safe Base64
50+
## ---------------
51+
##
52+
## .. code-block::nim
53+
## import base64
54+
## doAssert encode("c\xf7>", safe = true) == "Y_c-"
55+
## doAssert encode("c\xf7>", safe = false) == "Y/c+"
4956
##
5057
## See also
5158
## ========
@@ -56,9 +63,10 @@
5663

5764
const
5865
cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
66+
cb64safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
5967
invalidChar = 255
6068

61-
template encodeInternal(s: typed): untyped =
69+
template encodeInternal(s: typed, alphabet: string): untyped =
6270
## encodes `s` into base64 representation.
6371
proc encodeSize(size: int): int =
6472
return (size * 4 div 3) + 6
@@ -78,7 +86,7 @@ template encodeInternal(s: typed): untyped =
7886
inc inputIndex
7987

8088
template outputChar(x: untyped) =
81-
result[outputIndex] = cb64[x and 63]
89+
result[outputIndex] = alphabet[x and 63]
8290
inc outputIndex
8391

8492
template outputChar(c: char) =
@@ -112,32 +120,46 @@ template encodeInternal(s: typed): untyped =
112120

113121
result.setLen(outputIndex)
114122

115-
proc encode*[T: SomeInteger|char](s: openArray[T]): string =
123+
proc encode*[T: SomeInteger|char](s: openArray[T], safe = false): string =
116124
## Encodes `s` into base64 representation.
117125
##
118126
## This procedure encodes an openarray (array or sequence) of either integers
119127
## or characters.
120128
##
129+
## If ``safe`` is ``true`` then it will encode using the
130+
## URL-Safe and Filesystem-safe standard alphabet characters,
131+
## which substitutes ``-`` instead of ``+`` and ``_`` instead of ``/``.
132+
## * https://en.wikipedia.org/wiki/Base64#URL_applications
133+
## * https://tools.ietf.org/html/rfc4648#page-7
134+
##
121135
## **See also:**
122136
## * `encode proc<#encode,string>`_ for encoding a string
123137
## * `decode proc<#decode,string>`_ for decoding a string
124138
runnableExamples:
125139
assert encode(['n', 'i', 'm']) == "bmlt"
126140
assert encode(@['n', 'i', 'm']) == "bmlt"
127141
assert encode([1, 2, 3, 4, 5]) == "AQIDBAU="
128-
encodeInternal(s)
142+
if safe: encodeInternal(s, cb64safe)
143+
else: encodeInternal(s, cb64)
129144

130-
proc encode*(s: string): string =
145+
proc encode*(s: string, safe = false): string =
131146
## Encodes ``s`` into base64 representation.
132147
##
133148
## This procedure encodes a string.
134149
##
150+
## If ``safe`` is ``true`` then it will encode using the
151+
## URL-Safe and Filesystem-safe standard alphabet characters,
152+
## which substitutes ``-`` instead of ``+`` and ``_`` instead of ``/``.
153+
## * https://en.wikipedia.org/wiki/Base64#URL_applications
154+
## * https://tools.ietf.org/html/rfc4648#page-7
155+
##
135156
## **See also:**
136157
## * `encode proc<#encode,openArray[T]>`_ for encoding an openarray
137158
## * `decode proc<#decode,string>`_ for decoding a string
138159
runnableExamples:
139160
assert encode("Hello World") == "SGVsbG8gV29ybGQ="
140-
encodeInternal(s)
161+
if safe: encodeInternal(s, cb64safe)
162+
else: encodeInternal(s, cb64)
141163

142164
proc encodeMIME*(s: string, lineLen = 75, newLine = "\r\n"): string =
143165
## Encodes ``s`` into base64 representation as lines.
@@ -232,6 +254,3 @@ proc decode*(s: string): string =
232254
outputChar(a shl 2 or b shr 4)
233255
outputChar(b shl 4 or c shr 2)
234256
result.setLen(outputIndex)
235-
236-
237-

tests/stdlib/tbase64.nim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ proc main() =
3939
except ValueError:
4040
discard
4141

42+
block base64urlSafe:
43+
doAssert encode("c\xf7>", safe = true) == "Y_c-"
44+
doAssert encode("c\xf7>", safe = false) == "Y/c+" # Not a nice URL :(
45+
doAssert decode("Y/c+") == decode("Y_c-")
46+
# Output must not change with safe=true
47+
doAssert encode("Hello World", safe = true) == "SGVsbG8gV29ybGQ="
48+
doAssert encode("leasure.", safe = true) == "bGVhc3VyZS4="
49+
doAssert encode("easure.", safe = true) == "ZWFzdXJlLg=="
50+
doAssert encode("asure.", safe = true) == "YXN1cmUu"
51+
doAssert encode("sure.", safe = true) == "c3VyZS4="
52+
doAssert encode([1,2,3], safe = true) == "AQID"
53+
doAssert encode(['h','e','y'], safe = true) == "aGV5"
54+
doAssert encode("", safe = true) == ""
55+
doAssert encode("the quick brown dog jumps over the lazy fox", safe = true) == "dGhlIHF1aWNrIGJyb3duIGRvZyBqdW1wcyBvdmVyIHRoZSBsYXp5IGZveA=="
56+
4257
echo "OK"
4358

4459
main()

0 commit comments

Comments
 (0)