decode.scm 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. (define-module (decode)
  2. #:use-module (rnrs base)
  3. #:use-module ((guile)
  4. #:select (lambda* λ
  5. when unless
  6. record-constructor
  7. &programming-error
  8. string-trim-right))
  9. #:use-module (ice-9 textual-ports)
  10. ;; SRFI 1: list procedures
  11. #:use-module ((srfi srfi-1)
  12. #:select (drop take first second third))
  13. ;; SRFI 69: hash tables
  14. #:use-module ((srfi srfi-69))
  15. #:use-module ((base-64) #:prefix base-64:)
  16. #:use-module (bit-integers)
  17. #:use-module (string-helpers)
  18. #:use-module (pipeline)
  19. #:use-module (ice-9 exceptions)
  20. #:export (decode))
  21. (define make-invalid-base64-string-exception
  22. (record-constructor
  23. (make-exception-type '&invalid-base64-string
  24. &programming-error
  25. '(message))))
  26. (define decode-8-bits
  27. (λ (bits)
  28. "Treat bits as an integer index into ASCII characters."
  29. (integer->char bits)))
  30. ;; TODO: IDEA: Make a more generic function for encoding chars, so
  31. ;; that it can be used in both encode and decode.
  32. (define decode-4-chars
  33. (λ (chars)
  34. "Take 4 base64 characters and make 3 ASCII characters out of them."
  35. ;; A character in base64 is represented by 6 bits.
  36. (define char-width-base64 6)
  37. (let ([chars-as-ints (map base-64:char->integer chars)])
  38. ;; Concattenate the integers of the chars into a larger integer,
  39. ;; as a neutral intermediate representation between ASCII and
  40. ;; base64.
  41. (let ([concattenated (concat-integers chars-as-ints char-width-base64)]
  42. ;; A character in ASCII is represented by 8 bits.
  43. [char-width-ascii 8])
  44. (list->string
  45. (list (decode-8-bits (bit-integer-get-range concattenated
  46. char-width-ascii
  47. (* 2 char-width-ascii)))
  48. (decode-8-bits (bit-integer-get-range concattenated
  49. char-width-ascii
  50. char-width-ascii))
  51. (decode-8-bits (bit-integer-get-range concattenated
  52. char-width-ascii
  53. 0))))))))
  54. (define decode
  55. (λ (text)
  56. "Decode a base64 encoded string."
  57. (cond
  58. ;; A valid base64 string will be of a length divisible by 4.
  59. [(= (remainder (string-length text) 4) 0)
  60. (let ([char-width-base64 6]
  61. [char-width-ascii 8]
  62. [text-length (string-length text)]
  63. ;; ASSUMPTION: = characters can only be encountered at the
  64. ;; end of the string, because they are exclusively used as
  65. ;; the padding character.
  66. [padding-start-index (string-index text #\=)])
  67. ;; Remove the padding of the string (the = characters) and put
  68. ;; zeros there instead.
  69. ;; If no index for the padding char is found, it will result
  70. ;; in #f.
  71. (define padding-length (if padding-start-index
  72. (- text-length padding-start-index)
  73. 0))
  74. (define text-without-padding
  75. (substring text
  76. 0
  77. (- text-length padding-length)))
  78. ;; The char at 0 in base64 is #\A.
  79. (define text-with-zero-padding
  80. (call-with-output-string
  81. (λ (port)
  82. (put-string port text-without-padding)
  83. (do ((counter 1 (+ counter 1)))
  84. ((> counter padding-length))
  85. (put-char port #\A)))))
  86. (define decoded-with-zeros
  87. (call-with-output-string
  88. (λ (port)
  89. (let iter ([chars° (string->list text-with-zero-padding)])
  90. (unless (null? chars°)
  91. (put-string port (decode-4-chars (take chars° 4)))
  92. (iter (drop chars° 4)))))))
  93. (string-trim-right decoded-with-zeros (λ (c) (char=? c #\nul))))]
  94. [else
  95. (raise-exception
  96. (make-exception
  97. (make-invalid-base64-string-exception "invalid base64 string")
  98. (make-exception-with-irritants (text))
  99. (make-exception-with-origin 'decode)))])))