|
| 1 | +(ns ring.util.codec |
| 2 | + "Functions for encoding and decoding data." |
| 3 | + (:require [clojure.string :as str]) |
| 4 | + (:import java.io.File |
| 5 | + java.util.Map |
| 6 | + [java.net URLEncoder URLDecoder] |
| 7 | + org.apache.commons.codec.binary.Base64)) |
| 8 | + |
| 9 | +(defn assoc-conj |
| 10 | + "Associate a key with a value in a map. If the key already exists in the map, |
| 11 | + a vector of values is associated with the key." |
| 12 | + [map key val] |
| 13 | + (assoc map key |
| 14 | + (if-let [cur (get map key)] |
| 15 | + (if (vector? cur) |
| 16 | + (conj cur val) |
| 17 | + [cur val]) |
| 18 | + val))) |
| 19 | + |
| 20 | +(defn- double-escape [^String x] |
| 21 | + (.replace (.replace x "\\" "\\\\") "$" "\\$")) |
| 22 | + |
| 23 | +(def ^:private string-replace-bug? |
| 24 | + (= "x" (str/replace "x" #"." (fn [x] "$0")))) |
| 25 | + |
| 26 | +(defmacro fix-string-replace-bug [x] |
| 27 | + (if string-replace-bug? |
| 28 | + `(double-escape ~x) |
| 29 | + x)) |
| 30 | + |
| 31 | +(defn percent-encode |
| 32 | + "Percent-encode every character in the given string using either the specified |
| 33 | + encoding, or UTF-8 by default." |
| 34 | + [^String unencoded & [^String encoding]] |
| 35 | + (->> (.getBytes unencoded (or encoding "UTF-8")) |
| 36 | + (map (partial format "%%%02X")) |
| 37 | + (str/join))) |
| 38 | + |
| 39 | +(defn- parse-bytes [encoded-bytes] |
| 40 | + (->> (re-seq #"%.." encoded-bytes) |
| 41 | + (map #(subs % 1)) |
| 42 | + (map #(.byteValue (Integer/valueOf % 16))) |
| 43 | + (byte-array))) |
| 44 | + |
| 45 | +(defn percent-decode |
| 46 | + "Decode every percent-encoded character in the given string using the |
| 47 | + specified encoding, or UTF-8 by default." |
| 48 | + [^String encoded & [^String encoding]] |
| 49 | + (str/replace encoded |
| 50 | + #"(?:%..)+" |
| 51 | + (fn [chars] |
| 52 | + (-> ^bytes (parse-bytes chars) |
| 53 | + (String. (or encoding "UTF-8")) |
| 54 | + (fix-string-replace-bug))))) |
| 55 | + |
| 56 | +(defn url-encode |
| 57 | + "Returns the url-encoded version of the given string, using either a specified |
| 58 | + encoding or UTF-8 by default." |
| 59 | + [unencoded & [encoding]] |
| 60 | + (str/replace |
| 61 | + unencoded |
| 62 | + #"[^A-Za-z0-9_~.+-]+" |
| 63 | + #(double-escape (percent-encode % encoding)))) |
| 64 | + |
| 65 | +(defn ^String url-decode |
| 66 | + "Returns the url-decoded version of the given string, using either a specified |
| 67 | + encoding or UTF-8 by default. If the encoding is invalid, nil is returned." |
| 68 | + [encoded & [encoding]] |
| 69 | + (percent-decode encoded encoding)) |
| 70 | + |
| 71 | +(defn base64-encode |
| 72 | + "Encode an array of bytes into a base64 encoded string." |
| 73 | + [unencoded] |
| 74 | + (String. (Base64/encodeBase64 unencoded))) |
| 75 | + |
| 76 | +(defn base64-decode |
| 77 | + "Decode a base64 encoded string into an array of bytes." |
| 78 | + [^String encoded] |
| 79 | + (Base64/decodeBase64 (.getBytes encoded))) |
| 80 | + |
| 81 | +(defprotocol FormEncodeable |
| 82 | + (form-encode* [x encoding])) |
| 83 | + |
| 84 | +(extend-protocol FormEncodeable |
| 85 | + String |
| 86 | + (form-encode* [unencoded encoding] |
| 87 | + (URLEncoder/encode unencoded encoding)) |
| 88 | + Map |
| 89 | + (form-encode* [params encoding] |
| 90 | + (letfn [(encode [x] (form-encode* x encoding)) |
| 91 | + (encode-param [[k v]] (str (encode (name k)) "=" (encode v)))] |
| 92 | + (->> params |
| 93 | + (mapcat |
| 94 | + (fn [[k v]] |
| 95 | + (if (or (seq? v) (sequential? v) ) |
| 96 | + (map #(encode-param [k %]) v) |
| 97 | + [(encode-param [k v])]))) |
| 98 | + (str/join "&")))) |
| 99 | + Object |
| 100 | + (form-encode* [x encoding] |
| 101 | + (form-encode* (str x) encoding))) |
| 102 | + |
| 103 | +(defn form-encode |
| 104 | + "Encode the supplied value into www-form-urlencoded format, often used in |
| 105 | + URL query strings and POST request bodies, using the specified encoding. |
| 106 | + If the encoding is not specified, it defaults to UTF-8" |
| 107 | + [x & [encoding]] |
| 108 | + (form-encode* x (or encoding "UTF-8"))) |
| 109 | + |
| 110 | +(defn form-decode-str |
| 111 | + "Decode the supplied www-form-urlencoded string using the specified encoding, |
| 112 | + or UTF-8 by default." |
| 113 | + [^String encoded & [encoding]] |
| 114 | + (try |
| 115 | + (URLDecoder/decode encoded (or encoding "UTF-8")) |
| 116 | + (catch Exception _ nil))) |
| 117 | + |
| 118 | +(defn form-decode |
| 119 | + "Decode the supplied www-form-urlencoded string using the specified encoding, |
| 120 | + or UTF-8 by default. If the encoded value is a string, a string is returned. |
| 121 | + If the encoded value is a map of parameters, a map is returned." |
| 122 | + [^String encoded & [encoding]] |
| 123 | + (if-not (.contains encoded "=") |
| 124 | + (form-decode-str encoded encoding) |
| 125 | + (reduce |
| 126 | + (fn [m param] |
| 127 | + (if-let [[k v] (str/split param #"=" 2)] |
| 128 | + (assoc-conj m (form-decode-str k encoding) (form-decode-str v encoding)) |
| 129 | + m)) |
| 130 | + {} |
| 131 | + (str/split encoded #"&")))) |
0 commit comments