base64.nim 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. ## Encoding data
  12. ## -------------
  13. ##
  14. ## In order to encode some text simply call the ``encode`` procedure:
  15. ##
  16. ## .. code-block::nim
  17. ## import base64
  18. ## let encoded = encode("Hello World")
  19. ## echo(encoded) # SGVsbG8gV29ybGQ=
  20. ##
  21. ## Apart from strings you can also encode lists of integers or characters:
  22. ##
  23. ## .. code-block::nim
  24. ## import base64
  25. ## let encodedInts = encode([1,2,3])
  26. ## echo(encodedInts) # AQID
  27. ## let encodedChars = encode(['h','e','y'])
  28. ## echo(encodedChars) # aGV5
  29. ##
  30. ## The ``encode`` procedure takes an ``openarray`` so both arrays and sequences
  31. ## can be passed as parameters.
  32. ##
  33. ## Decoding data
  34. ## -------------
  35. ##
  36. ## To decode a base64 encoded data string simply call the ``decode``
  37. ## procedure:
  38. ##
  39. ## .. code-block::nim
  40. ## import base64
  41. ## echo(decode("SGVsbG8gV29ybGQ=")) # Hello World
  42. const
  43. cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  44. template encodeInternal(s: typed, lineLen: int, newLine: string): untyped =
  45. ## encodes `s` into base64 representation. After `lineLen` characters, a
  46. ## `newline` is added.
  47. var total = ((len(s) + 2) div 3) * 4
  48. let numLines = (total + lineLen - 1) div lineLen
  49. if numLines > 0: inc(total, (numLines - 1) * newLine.len)
  50. result = newString(total)
  51. var
  52. i = 0
  53. r = 0
  54. currLine = 0
  55. while i < s.len - 2:
  56. let
  57. a = ord(s[i])
  58. b = ord(s[i+1])
  59. c = ord(s[i+2])
  60. result[r] = cb64[a shr 2]
  61. result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
  62. result[r+2] = cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)]
  63. result[r+3] = cb64[c and 0x3F]
  64. inc(r, 4)
  65. inc(i, 3)
  66. inc(currLine, 4)
  67. # avoid index out of bounds when lineLen == encoded length
  68. if currLine >= lineLen and i != s.len-2 and r < total:
  69. for x in items(newLine):
  70. result[r] = x
  71. inc(r)
  72. currLine = 0
  73. if i < s.len-1:
  74. let
  75. a = ord(s[i])
  76. b = ord(s[i+1])
  77. result[r] = cb64[a shr 2]
  78. result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
  79. result[r+2] = cb64[((b and 0x0F) shl 2)]
  80. result[r+3] = '='
  81. if r+4 != result.len:
  82. setLen(result, r+4)
  83. elif i < s.len:
  84. let a = ord(s[i])
  85. result[r] = cb64[a shr 2]
  86. result[r+1] = cb64[(a and 3) shl 4]
  87. result[r+2] = '='
  88. result[r+3] = '='
  89. if r+4 != result.len:
  90. setLen(result, r+4)
  91. else:
  92. if r != result.len:
  93. setLen(result, r)
  94. #assert(r == result.len)
  95. discard
  96. proc encode*[T:SomeInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string =
  97. ## encodes `s` into base64 representation. After `lineLen` characters, a
  98. ## `newline` is added.
  99. ##
  100. ## This procedure encodes an openarray (array or sequence) of either integers
  101. ## or characters.
  102. encodeInternal(s, lineLen, newLine)
  103. proc encode*(s: string, lineLen = 75, newLine="\13\10"): string =
  104. ## encodes `s` into base64 representation. After `lineLen` characters, a
  105. ## `newline` is added.
  106. ##
  107. ## This procedure encodes a string.
  108. encodeInternal(s, lineLen, newLine)
  109. proc decodeByte(b: char): int {.inline.} =
  110. case b
  111. of '+': result = ord('>')
  112. of '0'..'9': result = ord(b) + 4
  113. of 'A'..'Z': result = ord(b) - ord('A')
  114. of 'a'..'z': result = ord(b) - 71
  115. else: result = 63
  116. proc decode*(s: string): string =
  117. ## decodes a string in base64 representation back into its original form.
  118. ## Whitespace is skipped.
  119. const Whitespace = {' ', '\t', '\v', '\r', '\l', '\f'}
  120. var total = ((len(s) + 3) div 4) * 3
  121. # total is an upper bound, as we will skip arbitrary whitespace:
  122. result = newString(total)
  123. var
  124. i = 0
  125. r = 0
  126. while true:
  127. while i < s.len and s[i] in Whitespace: inc(i)
  128. if i < s.len-3:
  129. let
  130. a = s[i].decodeByte
  131. b = s[i+1].decodeByte
  132. c = s[i+2].decodeByte
  133. d = s[i+3].decodeByte
  134. result[r] = chr((a shl 2) and 0xff or ((b shr 4) and 0x03))
  135. result[r+1] = chr((b shl 4) and 0xff or ((c shr 2) and 0x0F))
  136. result[r+2] = chr((c shl 6) and 0xff or (d and 0x3F))
  137. inc(r, 3)
  138. inc(i, 4)
  139. else: break
  140. assert i == s.len
  141. # adjust the length:
  142. if i > 0 and s[i-1] == '=':
  143. dec(r)
  144. if i > 1 and s[i-2] == '=': dec(r)
  145. setLen(result, r)
  146. when isMainModule:
  147. assert encode("leasure.") == "bGVhc3VyZS4="
  148. assert encode("easure.") == "ZWFzdXJlLg=="
  149. assert encode("asure.") == "YXN1cmUu"
  150. assert encode("sure.") == "c3VyZS4="
  151. const testInputExpandsTo76 = "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
  152. const testInputExpands = "++++++++++++++++++++++++++++++"
  153. const longText = """Man is distinguished, not only by his reason, but by this
  154. singular passion from other animals, which is a lust of the mind,
  155. that by a perseverance of delight in the continued and indefatigable
  156. generation of knowledge, exceeds the short vehemence of any carnal
  157. pleasure."""
  158. const tests = ["", "abc", "xyz", "man", "leasure.", "sure.", "easure.",
  159. "asure.", longText, testInputExpandsTo76, testInputExpands]
  160. for t in items(tests):
  161. assert decode(encode(t)) == t
  162. assert decode(encode(t, lineLen=40)) == t
  163. assert decode(encode(t, lineLen=76)) == t