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