base64.nim 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2010 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements a base64 encoder and decoder.
  10. ##
  11. ## Unstable API.
  12. ##
  13. ## Base64 is an encoding and decoding technique used to convert binary
  14. ## data to an ASCII string format.
  15. ## Each Base64 digit represents exactly 6 bits of data. Three 8-bit
  16. ## bytes (i.e., a total of 24 bits) can therefore be represented by
  17. ## four 6-bit Base64 digits.
  18. ##
  19. ## Basic usage
  20. ## ===========
  21. ##
  22. ## Encoding data
  23. ## -------------
  24. ##
  25. ## .. code-block::nim
  26. ## import base64
  27. ## let encoded = encode("Hello World")
  28. ## assert encoded == "SGVsbG8gV29ybGQ="
  29. ##
  30. ## Apart from strings you can also encode lists of integers or characters:
  31. ##
  32. ## .. code-block::nim
  33. ## import base64
  34. ## let encodedInts = encode([1,2,3])
  35. ## assert encodedInts == "AQID"
  36. ## let encodedChars = encode(['h','e','y'])
  37. ## assert encodedChars == "aGV5"
  38. ##
  39. ##
  40. ## Decoding data
  41. ## -------------
  42. ##
  43. ## .. code-block::nim
  44. ## import base64
  45. ## let decoded = decode("SGVsbG8gV29ybGQ=")
  46. ## assert decoded == "Hello World"
  47. ##
  48. ##
  49. ## See also
  50. ## ========
  51. ##
  52. ## * `hashes module<hashes.html>`_ for efficient computations of hash values for diverse Nim types
  53. ## * `md5 module<md5.html>`_ implements the MD5 checksum algorithm
  54. ## * `sha1 module<sha1.html>`_ implements a sha1 encoder and decoder
  55. const
  56. cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  57. template encodeInternal(s: typed, lineLen: int, newLine: string): untyped =
  58. ## encodes `s` into base64 representation. After `lineLen` characters, a
  59. ## `newline` is added.
  60. var total = ((len(s) + 2) div 3) * 4
  61. let numLines = (total + lineLen - 1) div lineLen
  62. if numLines > 0: inc(total, (numLines - 1) * newLine.len)
  63. result = newString(total)
  64. var
  65. i = 0
  66. r = 0
  67. currLine = 0
  68. while i < s.len - 2:
  69. let
  70. a = ord(s[i])
  71. b = ord(s[i+1])
  72. c = ord(s[i+2])
  73. result[r] = cb64[a shr 2]
  74. result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
  75. result[r+2] = cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)]
  76. result[r+3] = cb64[c and 0x3F]
  77. inc(r, 4)
  78. inc(i, 3)
  79. inc(currLine, 4)
  80. # avoid index out of bounds when lineLen == encoded length
  81. if currLine >= lineLen and i != s.len-2 and r < total:
  82. for x in items(newLine):
  83. result[r] = x
  84. inc(r)
  85. currLine = 0
  86. if i < s.len-1:
  87. let
  88. a = ord(s[i])
  89. b = ord(s[i+1])
  90. result[r] = cb64[a shr 2]
  91. result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
  92. result[r+2] = cb64[((b and 0x0F) shl 2)]
  93. result[r+3] = '='
  94. if r+4 != result.len:
  95. setLen(result, r+4)
  96. elif i < s.len:
  97. let a = ord(s[i])
  98. result[r] = cb64[a shr 2]
  99. result[r+1] = cb64[(a and 3) shl 4]
  100. result[r+2] = '='
  101. result[r+3] = '='
  102. if r+4 != result.len:
  103. setLen(result, r+4)
  104. else:
  105. if r != result.len:
  106. setLen(result, r)
  107. #assert(r == result.len)
  108. discard
  109. proc encode*[T: SomeInteger|char](s: openArray[T], lineLen = 75,
  110. newLine = ""): string =
  111. ## Encodes ``s`` into base64 representation. After ``lineLen`` characters, a
  112. ## ``newline`` is added.
  113. ##
  114. ## This procedure encodes an openarray (array or sequence) of either integers
  115. ## or characters.
  116. ##
  117. ## **See also:**
  118. ## * `encode proc<#encode,string,int,string>`_ for encoding a string
  119. ## * `decode proc<#decode,string>`_ for decoding a string
  120. runnableExamples:
  121. assert encode(['n', 'i', 'm']) == "bmlt"
  122. assert encode(@['n', 'i', 'm']) == "bmlt"
  123. assert encode([1, 2, 3, 4, 5]) == "AQIDBAU="
  124. encodeInternal(s, lineLen, newLine)
  125. proc encode*(s: string, lineLen = 75, newLine = ""): string =
  126. ## Encodes ``s`` into base64 representation. After ``lineLen`` characters, a
  127. ## ``newline`` is added.
  128. ##
  129. ## This procedure encodes a string.
  130. ##
  131. ## **See also:**
  132. ## * `encode proc<#encode,openArray[T],int,string>`_ for encoding an openarray
  133. ## * `decode proc<#decode,string>`_ for decoding a string
  134. runnableExamples:
  135. assert encode("Hello World") == "SGVsbG8gV29ybGQ="
  136. assert encode("Hello World", 3, "\n") == "SGVs\nbG8g\nV29ybGQ="
  137. encodeInternal(s, lineLen, newLine)
  138. proc decodeByte(b: char): int {.inline.} =
  139. case b
  140. of '+': result = ord('>')
  141. of '0'..'9': result = ord(b) + 4
  142. of 'A'..'Z': result = ord(b) - ord('A')
  143. of 'a'..'z': result = ord(b) - 71
  144. else: result = 63
  145. proc decode*(s: string): string =
  146. ## Decodes string ``s`` in base64 representation back into its original form.
  147. ## The initial whitespace is skipped.
  148. ##
  149. ## **See also:**
  150. ## * `encode proc<#encode,openArray[T]>`_ for encoding an openarray
  151. ## * `encode proc<#encode,string>`_ for encoding a string
  152. runnableExamples:
  153. assert decode("SGVsbG8gV29ybGQ=") == "Hello World"
  154. assert decode(" SGVsbG8gV29ybGQ=") == "Hello World"
  155. const Whitespace = {' ', '\t', '\v', '\r', '\l', '\f'}
  156. var total = ((len(s) + 3) div 4) * 3
  157. # total is an upper bound, as we will skip arbitrary whitespace:
  158. result = newString(total)
  159. var
  160. i = 0
  161. r = 0
  162. while true:
  163. while i < s.len and s[i] in Whitespace: inc(i)
  164. if i < s.len-3:
  165. let
  166. a = s[i].decodeByte
  167. b = s[i+1].decodeByte
  168. c = s[i+2].decodeByte
  169. d = s[i+3].decodeByte
  170. result[r] = chr((a shl 2) and 0xff or ((b shr 4) and 0x03))
  171. result[r+1] = chr((b shl 4) and 0xff or ((c shr 2) and 0x0F))
  172. result[r+2] = chr((c shl 6) and 0xff or (d and 0x3F))
  173. inc(r, 3)
  174. inc(i, 4)
  175. else: break
  176. assert i == s.len
  177. # adjust the length:
  178. if i > 0 and s[i-1] == '=':
  179. dec(r)
  180. if i > 1 and s[i-2] == '=': dec(r)
  181. setLen(result, r)
  182. when isMainModule:
  183. assert encode("leasure.") == "bGVhc3VyZS4="
  184. assert encode("easure.") == "ZWFzdXJlLg=="
  185. assert encode("asure.") == "YXN1cmUu"
  186. assert encode("sure.") == "c3VyZS4="
  187. const testInputExpandsTo76 = "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
  188. const testInputExpands = "++++++++++++++++++++++++++++++"
  189. const longText = """Man is distinguished, not only by his reason, but by this
  190. singular passion from other animals, which is a lust of the mind,
  191. that by a perseverance of delight in the continued and indefatigable
  192. generation of knowledge, exceeds the short vehemence of any carnal
  193. pleasure."""
  194. const tests = ["", "abc", "xyz", "man", "leasure.", "sure.", "easure.",
  195. "asure.", longText, testInputExpandsTo76, testInputExpands]
  196. for t in items(tests):
  197. assert decode(encode(t)) == t
  198. assert decode(encode(t, lineLen = 40)) == t
  199. assert decode(encode(t, lineLen = 76)) == t