summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md2
-rw-r--r--lib/pure/base64.nim37
-rw-r--r--tests/stdlib/tbase64.nim15
3 files changed, 45 insertions, 9 deletions
diff --git a/changelog.md b/changelog.md
index 127579644..e7e3aaf0c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -112,6 +112,8 @@ echo f
 - Added `times.isLeapDay`
 - Added a new module, `std / compilesettings` for querying the compiler about
   diverse configuration settings.
+- `base64` adds URL-Safe Base64, implements RFC-4648 Section-7.
+
 
 ## Library changes
 
diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim
index 067f21c5f..f418a9d44 100644
--- a/lib/pure/base64.nim
+++ b/lib/pure/base64.nim
@@ -46,6 +46,13 @@
 ##    let decoded = decode("SGVsbG8gV29ybGQ=")
 ##    assert decoded == "Hello World"
 ##
+## URL Safe Base64
+## ---------------
+##
+## .. code-block::nim
+##    import base64
+##    doAssert encode("c\xf7>", safe = true) == "Y_c-"
+##    doAssert encode("c\xf7>", safe = false) == "Y/c+"
 ##
 ## See also
 ## ========
@@ -56,9 +63,10 @@
 
 const
   cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+  cb64safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
   invalidChar = 255
 
-template encodeInternal(s: typed): untyped =
+template encodeInternal(s: typed, alphabet: string): untyped =
   ## encodes `s` into base64 representation.
   proc encodeSize(size: int): int =
     return (size * 4 div 3) + 6
@@ -78,7 +86,7 @@ template encodeInternal(s: typed): untyped =
     inc inputIndex
 
   template outputChar(x: untyped) =
-    result[outputIndex] = cb64[x and 63]
+    result[outputIndex] = alphabet[x and 63]
     inc outputIndex
 
   template outputChar(c: char) =
@@ -112,12 +120,18 @@ template encodeInternal(s: typed): untyped =
 
   result.setLen(outputIndex)
 
-proc encode*[T: SomeInteger|char](s: openArray[T]): string =
+proc encode*[T: SomeInteger|char](s: openArray[T], safe = false): string =
   ## Encodes `s` into base64 representation.
   ##
   ## This procedure encodes an openarray (array or sequence) of either integers
   ## or characters.
   ##
+  ## If ``safe`` is ``true`` then it will encode using the
+  ## URL-Safe and Filesystem-safe standard alphabet characters,
+  ## which substitutes ``-`` instead of ``+`` and ``_`` instead of ``/``.
+  ## * https://en.wikipedia.org/wiki/Base64#URL_applications
+  ## * https://tools.ietf.org/html/rfc4648#page-7
+  ##
   ## **See also:**
   ## * `encode proc<#encode,string>`_ for encoding a string
   ## * `decode proc<#decode,string>`_ for decoding a string
@@ -125,19 +139,27 @@ proc encode*[T: SomeInteger|char](s: openArray[T]): string =
     assert encode(['n', 'i', 'm']) == "bmlt"
     assert encode(@['n', 'i', 'm']) == "bmlt"
     assert encode([1, 2, 3, 4, 5]) == "AQIDBAU="
-  encodeInternal(s)
+  if safe: encodeInternal(s, cb64safe)
+  else: encodeInternal(s, cb64)
 
-proc encode*(s: string): string =
+proc encode*(s: string, safe = false): string =
   ## Encodes ``s`` into base64 representation.
   ##
   ## This procedure encodes a string.
   ##
+  ## If ``safe`` is ``true`` then it will encode using the
+  ## URL-Safe and Filesystem-safe standard alphabet characters,
+  ## which substitutes ``-`` instead of ``+`` and ``_`` instead of ``/``.
+  ## * https://en.wikipedia.org/wiki/Base64#URL_applications
+  ## * https://tools.ietf.org/html/rfc4648#page-7
+  ##
   ## **See also:**
   ## * `encode proc<#encode,openArray[T]>`_ for encoding an openarray
   ## * `decode proc<#decode,string>`_ for decoding a string
   runnableExamples:
     assert encode("Hello World") == "SGVsbG8gV29ybGQ="
-  encodeInternal(s)
+  if safe: encodeInternal(s, cb64safe)
+  else: encodeInternal(s, cb64)
 
 proc encodeMIME*(s: string, lineLen = 75, newLine = "\r\n"): string =
   ## Encodes ``s`` into base64 representation as lines.
@@ -232,6 +254,3 @@ proc decode*(s: string): string =
     outputChar(a shl 2 or b shr 4)
     outputChar(b shl 4 or c shr 2)
   result.setLen(outputIndex)
-
-
-
diff --git a/tests/stdlib/tbase64.nim b/tests/stdlib/tbase64.nim
index 9db6e8802..e5b13642c 100644
--- a/tests/stdlib/tbase64.nim
+++ b/tests/stdlib/tbase64.nim
@@ -39,6 +39,21 @@ proc main() =
   except ValueError:
     discard
 
+  block base64urlSafe:
+    doAssert encode("c\xf7>", safe = true) == "Y_c-"
+    doAssert encode("c\xf7>", safe = false) == "Y/c+" # Not a nice URL :(
+    doAssert decode("Y/c+") == decode("Y_c-")
+    # Output must not change with safe=true
+    doAssert encode("Hello World", safe = true) == "SGVsbG8gV29ybGQ="
+    doAssert encode("leasure.", safe = true)  == "bGVhc3VyZS4="
+    doAssert encode("easure.", safe = true) == "ZWFzdXJlLg=="
+    doAssert encode("asure.", safe = true) == "YXN1cmUu"
+    doAssert encode("sure.", safe = true) == "c3VyZS4="
+    doAssert encode([1,2,3], safe = true) == "AQID"
+    doAssert encode(['h','e','y'], safe = true) == "aGV5"
+    doAssert encode("", safe = true) == ""
+    doAssert encode("the quick brown dog jumps over the lazy fox", safe = true) == "dGhlIHF1aWNrIGJyb3duIGRvZyBqdW1wcyBvdmVyIHRoZSBsYXp5IGZveA=="
+
   echo "OK"
 
 main()