1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.codec.net;
19
20 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertThrows;
24
25 import java.nio.charset.StandardCharsets;
26 import java.util.BitSet;
27
28 import org.apache.commons.codec.CharEncoding;
29 import org.apache.commons.codec.DecoderException;
30 import org.apache.commons.codec.EncoderException;
31 import org.junit.jupiter.api.Test;
32 import org.junit.jupiter.params.ParameterizedTest;
33 import org.junit.jupiter.params.provider.CsvSource;
34 import org.junit.jupiter.params.provider.ValueSource;
35
36
37
38
39 class URLCodecTest {
40
41 static final int[] SWISS_GERMAN_STUFF_UNICODE = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 };
42
43 static final int[] RUSSIAN_STUFF_UNICODE = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 };
44
45 private String constructString(final int[] unicodeChars) {
46 final StringBuilder buffer = new StringBuilder();
47 if (unicodeChars != null) {
48 for (final int unicodeChar : unicodeChars) {
49 buffer.append((char) unicodeChar);
50 }
51 }
52 return buffer.toString();
53 }
54
55 @Test
56 void testBasicEncodeDecode() throws Exception {
57 final URLCodec urlCodec = new URLCodec();
58 final String plain = "Hello there!";
59 final String encoded = urlCodec.encode(plain);
60 assertEquals("Hello+there%21", encoded, "Basic URL encoding test");
61 assertEquals(plain, urlCodec.decode(encoded), "Basic URL decoding test");
62 validateState(urlCodec);
63 }
64
65 @ParameterizedTest(name = "{0}: {1}")
66
67 @CsvSource({
68 "missing escape digits, %",
69 "missing second escape digit, %A",
70 "invalid first escape digit, %WW",
71 "invalid second escape digit, %0W"
72 })
73
74 void testDecodeInvalid(final String description, final String encoded) throws Exception {
75 final URLCodec urlCodec = new URLCodec();
76 assertThrows(DecoderException.class, () -> urlCodec.decode(encoded), description);
77 validateState(urlCodec);
78 }
79
80 @Test
81 void testDecodeInvalidContent() throws DecoderException {
82 final String ch_msg = constructString(SWISS_GERMAN_STUFF_UNICODE);
83 final URLCodec urlCodec = new URLCodec();
84 final byte[] input = ch_msg.getBytes(StandardCharsets.ISO_8859_1);
85 final byte[] output = urlCodec.decode(input);
86 assertEquals(input.length, output.length);
87 for (int i = 0; i < input.length; i++) {
88 assertEquals(input[i], output[i]);
89 }
90 validateState(urlCodec);
91 }
92
93 @Test
94 void testDecodeObjects() throws Exception {
95 final URLCodec urlCodec = new URLCodec();
96 final String plain = "Hello+there%21";
97 String decoded = (String) urlCodec.decode((Object) plain);
98 assertEquals("Hello there!", decoded, "Basic URL decoding test");
99 final byte[] plainBA = plain.getBytes(StandardCharsets.UTF_8);
100 final byte[] decodedBA = (byte[]) urlCodec.decode((Object) plainBA);
101 decoded = new String(decodedBA);
102 assertEquals("Hello there!", decoded, "Basic URL decoding test");
103 final Object result = urlCodec.decode((Object) null);
104
105 assertNull(result, "Decoding a null Object should return null");
106
107 assertThrows(DecoderException.class, () -> urlCodec.decode(Double.valueOf(3.0d)), "Trying to url encode a Double object should cause an exception.");
108 validateState(urlCodec);
109 }
110
111 @Test
112 void testDecodeStringWithNull() throws Exception {
113 final URLCodec urlCodec = new URLCodec();
114 final String test = null;
115 final String result = urlCodec.decode(test, "charset");
116 assertNull(result, "Result should be null");
117 }
118
119 @Test
120 void testDecodeUrlWithCustomBitSetAmbiguousEscapes() throws Exception {
121 final BitSet safe = new BitSet();
122 safe.set('%');
123 final byte[] encoded = URLCodec.encodeUrl(safe, new byte[] { '%', '/' });
124 assertEquals("%%2F", new String(encoded, StandardCharsets.US_ASCII));
125 assertArrayEquals(encoded, URLCodec.decodeUrl(safe, encoded));
126 }
127
128 @ParameterizedTest
129 @ValueSource(strings = { "%", "%A", "%WW", "%0W", "%W0" })
130 void testDecodeUrlWithCustomBitSetInvalidEscapes(final String input) throws Exception {
131 final byte[] bytes = input.getBytes(StandardCharsets.US_ASCII);
132 final BitSet safe = new BitSet();
133 assertThrows(DecoderException.class, () -> URLCodec.decodeUrl(null, bytes));
134 assertThrows(DecoderException.class, () -> URLCodec.decodeUrl(safe, bytes));
135 safe.set('%');
136 assertArrayEquals(bytes, URLCodec.decodeUrl(safe, bytes));
137 }
138
139 @Test
140 void testDecodeUrlWithCustomBitSetNullAndEmpty() throws Exception {
141 assertNull(URLCodec.decodeUrl(null, null));
142 assertNull(URLCodec.decodeUrl(new BitSet(), null));
143 assertArrayEquals(new byte[0], URLCodec.decodeUrl(null, new byte[0]));
144 assertArrayEquals(new byte[0], URLCodec.decodeUrl(new BitSet(), new byte[0]));
145 }
146
147 @ParameterizedTest
148 @ValueSource(ints = { 0, 1, 2, 3 })
149 void testDecodeUrlWithCustomBitSetPlusAndSpace(final int flags) throws Exception {
150 final BitSet safe = new BitSet();
151 safe.set(' ', (flags & 1) != 0);
152 safe.set('+', (flags & 2) != 0);
153 final String expected = flags == 1 ? " +" : "+ +";
154 assertEquals(expected, new String(URLCodec.decodeUrl(safe, "+%20%2b".getBytes(StandardCharsets.US_ASCII)), StandardCharsets.US_ASCII));
155 if (flags == 3) {
156 final byte[] encoded = URLCodec.encodeUrl(safe, " +".getBytes(StandardCharsets.US_ASCII));
157 assertEquals("++", new String(encoded, StandardCharsets.US_ASCII));
158 assertArrayEquals(encoded, URLCodec.decodeUrl(safe, encoded));
159 }
160 }
161
162 @Test
163 void testDecodeUrlWithCustomBitSetRoundTripAllBytes() throws Exception {
164 final byte[] input = new byte[256];
165 for (int i = 0; i < input.length; i++) {
166 input[i] = (byte) i;
167 }
168 final BitSet literalPlus = new BitSet(256);
169 literalPlus.set(0, 256);
170 literalPlus.clear('%');
171 literalPlus.clear(' ');
172 final BitSet spaceAsPlus = (BitSet) literalPlus.clone();
173 spaceAsPlus.clear('+');
174 spaceAsPlus.set(' ');
175 for (final BitSet safe : new BitSet[] { null, new BitSet(), literalPlus, spaceAsPlus }) {
176 final BitSet original = safe == null ? null : (BitSet) safe.clone();
177 final byte[] encoded = URLCodec.encodeUrl(safe, input);
178 assertArrayEquals(input, URLCodec.decodeUrl(safe, encoded));
179 assertEquals(original, safe, "The safe set must not be modified");
180 }
181 }
182
183 @Test
184 void testDecodeWithNullArray() throws Exception {
185 final byte[] plain = null;
186 final byte[] result = URLCodec.decodeUrl(plain);
187 assertNull(result, "Result should be null");
188 }
189
190 @Test
191 void testDefaultEncoding() throws Exception {
192 final String plain = "Hello there!";
193 final URLCodec urlCodec = new URLCodec("UnicodeBig");
194 urlCodec.encode(plain);
195 final String encoded1 = urlCodec.encode(plain, "UnicodeBig");
196 final String encoded2 = urlCodec.encode(plain);
197 assertEquals(encoded1, encoded2);
198 validateState(urlCodec);
199 }
200
201 @Test
202 void testEncodeDecodeNull() throws Exception {
203 final URLCodec urlCodec = new URLCodec();
204 assertNull(urlCodec.encode((String) null), "Null string URL encoding test");
205 assertNull(urlCodec.decode((String) null), "Null string URL decoding test");
206 validateState(urlCodec);
207 }
208
209 @Test
210 void testEncodeNull() throws Exception {
211 final URLCodec urlCodec = new URLCodec();
212 final byte[] plain = null;
213 final byte[] encoded = urlCodec.encode(plain);
214 assertNull(encoded, "Encoding a null string should return null");
215 validateState(urlCodec);
216 }
217
218 @Test
219 void testEncodeObjects() throws Exception {
220 final URLCodec urlCodec = new URLCodec();
221 final String plain = "Hello there!";
222 String encoded = (String) urlCodec.encode((Object) plain);
223 assertEquals("Hello+there%21", encoded, "Basic URL encoding test");
224
225 final byte[] plainBA = plain.getBytes(StandardCharsets.UTF_8);
226 final byte[] encodedBA = (byte[]) urlCodec.encode((Object) plainBA);
227 encoded = new String(encodedBA);
228 assertEquals("Hello+there%21", encoded, "Basic URL encoding test");
229
230 final Object result = urlCodec.encode((Object) null);
231 assertNull(result, "Encoding a null Object should return null");
232
233 assertThrows(EncoderException.class, () -> urlCodec.encode(Double.valueOf(3.0d)), "Trying to url encode a Double object should cause an exception.");
234 validateState(urlCodec);
235 }
236
237 @Test
238 void testEncodeStringWithNull() throws Exception {
239 final URLCodec urlCodec = new URLCodec();
240 final String test = null;
241 final String result = urlCodec.encode(test, "charset");
242 assertNull(result, "Result should be null");
243 }
244
245 @Test
246 void testEncodeUrlWithNullBitSet() throws Exception {
247 final URLCodec urlCodec = new URLCodec();
248 final String plain = "Hello there!%+";
249 final String encoded = new String(URLCodec.encodeUrl(null, plain.getBytes(StandardCharsets.UTF_8)));
250 assertEquals("Hello+there%21%25%2B", encoded, "Basic URL encoding test");
251 assertEquals(plain, urlCodec.decode(encoded), "Basic URL decoding test");
252 validateState(urlCodec);
253 }
254
255 @Test
256 void testEncodeUrlWithPercentMarkedSafePreservesPercent() {
257 final BitSet safe = new BitSet();
258 safe.set('%');
259 final String plain = "%";
260 final byte[] encoded = URLCodec.encodeUrl(safe, plain.getBytes(StandardCharsets.US_ASCII));
261 final String encodedS = new String(encoded, StandardCharsets.US_ASCII);
262 assertEquals(plain, encodedS, "URLCodec should preserve percent when marked safe");
263 assertThrows(DecoderException.class, () -> URLCodec.decodeUrl(encoded));
264 }
265
266 @Test
267 void testEncodeUrlWithPlusMarkedSafePreservesPlus() throws Exception {
268 final BitSet safe = new BitSet();
269 safe.set('+');
270 final String plain = "+";
271 final byte[] encoded = URLCodec.encodeUrl(safe, plain.getBytes(StandardCharsets.US_ASCII));
272 final String encodedS = new String(encoded, StandardCharsets.US_ASCII);
273 assertEquals(plain, encodedS, "URLCodec should preserve plus when marked safe");
274 final byte[] decoded = URLCodec.decodeUrl(encoded);
275 assertEquals(" ", new String(decoded, StandardCharsets.US_ASCII), "Default decoding interprets a literal plus as a space");
276 }
277
278 @Test
279 void testInvalidEncoding() {
280 final URLCodec urlCodec = new URLCodec("NONSENSE");
281 final String plain = "Hello there!";
282 assertThrows(EncoderException.class, () -> urlCodec.encode(plain), "We set the encoding to a bogus NONSENSE value");
283 assertThrows(DecoderException.class, () -> urlCodec.decode(plain), "We set the encoding to a bogus NONSENSE value");
284 validateState(urlCodec);
285 }
286
287 @Test
288 void testSafeCharEncodeDecode() throws Exception {
289 final URLCodec urlCodec = new URLCodec();
290 final String plain = "abc123_-.*";
291 final String encoded = urlCodec.encode(plain);
292 assertEquals(plain, encoded, "Safe chars URL encoding test");
293 assertEquals(plain, urlCodec.decode(encoded), "Safe chars URL decoding test");
294 validateState(urlCodec);
295 }
296
297 @Test
298 void testUnsafeEncodeDecode() throws Exception {
299 final URLCodec urlCodec = new URLCodec();
300 final String plain = "~!@#$%^&()+{}\"\\;:`,/[]";
301 final String encoded = urlCodec.encode(plain);
302 assertEquals("%7E%21%40%23%24%25%5E%26%28%29%2B%7B%7D%22%5C%3B%3A%60%2C%2F%5B%5D", encoded, "Unsafe chars URL encoding test");
303 assertEquals(plain, urlCodec.decode(encoded), "Unsafe chars URL decoding test");
304 validateState(urlCodec);
305 }
306
307 @Test
308 void testUTF8RoundTrip() throws Exception {
309
310 final String ru_msg = constructString(RUSSIAN_STUFF_UNICODE);
311 final String ch_msg = constructString(SWISS_GERMAN_STUFF_UNICODE);
312
313 final URLCodec urlCodec = new URLCodec();
314 validateState(urlCodec);
315
316 assertEquals("%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82", urlCodec.encode(ru_msg, CharEncoding.UTF_8));
317 assertEquals("Gr%C3%BCezi_z%C3%A4m%C3%A4", urlCodec.encode(ch_msg, CharEncoding.UTF_8));
318
319 assertEquals(ru_msg, urlCodec.decode(urlCodec.encode(ru_msg, CharEncoding.UTF_8), CharEncoding.UTF_8));
320 assertEquals(ch_msg, urlCodec.decode(urlCodec.encode(ch_msg, CharEncoding.UTF_8), CharEncoding.UTF_8));
321 validateState(urlCodec);
322 }
323
324 private void validateState(final URLCodec urlCodec) {
325
326 }
327 }