;;; base64.clj: Experimental Base-64 encoding and (later) decoding ;; by Stuart Sierra, http://stuartsierra.com/ ;; August 19, 2009 ;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use ;; and distribution terms for this software are covered by the Eclipse ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ;; which can be found in the file epl-v10.html at the root of this ;; distribution. By using this software in any fashion, you are ;; agreeing to be bound by the terms of this license. You must not ;; remove this notice, or any other, from this software. (ns #^{:doc "Base-64 encoding and (maybe later) decoding. This is mainly here as an example. It is much slower than the Apache Commons Codec implementation or sun.misc.BASE64Encoder." :author "Stuart Sierra"} clojure.contrib.base64 (:import (java.io InputStream Writer ByteArrayInputStream StringWriter))) (def *base64-alphabet* "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") (defn encode "Encodes bytes of input, writing Base 64 text on output. alphabet is a 65-character String containing the 64 characters to use in the encoding; the 65th character is the pad character. line-length is the maximum number of characters per line, nil for no line breaks." [#^InputStream input #^Writer output #^String alphabet line-length] (let [buffer (make-array Byte/TYPE 3)] (loop [line 0] (let [len (.read input buffer)] (when (pos? len) ;; Pre-boxing the bytes as Integers is more efficient for ;; Clojure's bit operations. (let [b0 (Integer/valueOf (int (aget buffer 0))) b1 (Integer/valueOf (int (aget buffer 1))) b2 (Integer/valueOf (int (aget buffer 2)))] (cond (= len 3) (let [s0 (bit-and 0x3F (bit-shift-right b0 2)) s1 (bit-and 0x3F (bit-or (bit-shift-left b0 4) (bit-shift-right b1 4))) s2 (bit-and 0x3F (bit-or (bit-shift-left b1 2) (bit-shift-right b2 6))) s3 (bit-and 0x3F b2)] (.append output (.charAt alphabet s0)) (.append output (.charAt alphabet s1)) (.append output (.charAt alphabet s2)) (.append output (.charAt alphabet s3))) (= len 2) (let [s0 (bit-and 0x3F (bit-shift-right b0 2)) s1 (bit-and 0x3F (bit-or (bit-shift-left b0 4) (bit-shift-right b1 4))) s2 (bit-and 0x3F (bit-shift-left b1 2))] (.append output (.charAt alphabet s0)) (.append output (.charAt alphabet s1)) (.append output (.charAt alphabet s2)) (.append output (.charAt alphabet 64))) (= len 1) (let [s0 (bit-and 0x3F (bit-shift-right b0 2)) s1 (bit-and 0x3F (bit-shift-left b0 4))] (.append output (.charAt alphabet s0)) (.append output (.charAt alphabet s1)) (.append output (.charAt alphabet 64)) (.append output (.charAt alphabet 64))))) (if (and line-length (> (+ line 4) line-length)) (do (.append output \newline) (recur 0)) (recur (+ line 4)))))))) (defn encode-str "Encodes String in base 64; returns a String. If not specified, encoding is UTF-8 and line-length is nil." ([s] (encode-str s "UTF-8" nil)) ([#^String s #^String encoding line-length] (let [output (StringWriter.)] (encode (ByteArrayInputStream. (.getBytes s encoding)) output *base64-alphabet* line-length) (.toString output)))) ;;; tests ;; (deftest t-encode-str ;; (is (= (encode-str "") "")) ;; (is (= (encode-str "f") "Zg==")) ;; (is (= (encode-str "fo") "Zm8=")) ;; (is (= (encode-str "foo") "Zm9v")) ;; (is (= (encode-str "foob") "Zm9vYg==")) ;; (is (= (encode-str "fooba") "Zm9vYmE=")) ;; (is (= (encode-str "foobar") "Zm9vYmFy")))