1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package libcore.net;
18
19import junit.framework.TestCase;
20
21import java.net.URISyntaxException;
22import java.nio.charset.StandardCharsets;
23
24/**
25 * Tests for {@link UriCodec}
26 */
27public class UriCodecTest extends TestCase {
28    private static final UriCodec CODEC = new UriCodec() {
29        @Override
30        protected boolean isRetained(char c) {
31            return c == '$';
32        }
33    };
34
35    private static final String VALID_ENCODED_STRING = "a0b$CD%01a%23b%45c%67%89%abd%cd%efq";
36
37    public void testValidate_stringOK_passes() throws Exception {
38        assertEquals(
39                VALID_ENCODED_STRING,
40                CODEC.validate(
41                        VALID_ENCODED_STRING, 0, VALID_ENCODED_STRING.length(), "test OK string"));
42    }
43
44    // Hex codes in upper case are valid as well.
45    public void testValidate_stringUppercaseOK_passes() throws Exception {
46        String stringOKUpperCase = VALID_ENCODED_STRING.toUpperCase();
47        CODEC.validate(stringOKUpperCase, 0, stringOKUpperCase.length(), "test OK UC string");
48    }
49
50    // Characters before the start index are ignored.
51    public void testValidate_wrongCharsBeforeStart_passes() throws Exception {
52        assertEquals(VALID_ENCODED_STRING, CODEC.validate(
53                "%p" + VALID_ENCODED_STRING,
54                2,
55                VALID_ENCODED_STRING.length() + 2,
56                "test string"));
57    }
58
59    // Fails with character 'p', invalid after '%'
60    public void testValidate_wrongCharsAtStart_fails() throws Exception {
61        try {
62            CODEC.validate(
63                    "%p" + VALID_ENCODED_STRING,
64                    0,
65                    VALID_ENCODED_STRING.length() + 2,
66                    "test string");
67            fail("Expected URISyntaxException");
68        } catch (URISyntaxException expected) {
69            // Expected.
70        }
71    }
72
73    // Fails with character 'p', invalid after '%'
74    public void testValidate_wrongCharsBeyondEnd_passes() throws Exception {
75        assertEquals(VALID_ENCODED_STRING, CODEC.validate(
76                VALID_ENCODED_STRING + "%p",
77                0,
78                VALID_ENCODED_STRING.length(),
79                "test string"));
80    }
81
82    // Fails with character 'p', invalid after '%'
83    public void testValidate_wrongCharsAtEnd_fails() throws Exception {
84        try {
85            CODEC.validate(
86                    VALID_ENCODED_STRING + "%p",
87                    0,
88                    VALID_ENCODED_STRING.length() + 2,
89                    "test string");
90            fail("Expected URISyntaxException");
91        } catch (URISyntaxException expected) {
92            // Expected.
93        }
94    }
95
96    public void testValidate_secondDigitWrong_fails() throws Exception {
97        try {
98            CODEC.validate(
99                    VALID_ENCODED_STRING + "%1p",
100                    0,
101                    VALID_ENCODED_STRING.length() + 2,
102                    "test string");
103            fail("Expected URISyntaxException");
104        } catch (URISyntaxException expected) {
105            // Expected.
106        }
107    }
108
109    public void testValidate_emptyString_passes() throws Exception {
110        assertEquals("", CODEC.validate("", 0, 0, "empty string"));
111    }
112
113    public void testValidate_stringEndingWithPercent_fails() throws Exception {
114        try {
115            CODEC.validate("a%", 0, 0, "a% string");
116        } catch (URISyntaxException expected) {
117            // Expected.
118        }
119    }
120
121    public void testValidate_stringEndingWithPercentAndSingleDigit_fails() throws Exception {
122        try {
123            CODEC.validate("a%1", 0, 0, "a%1 string");
124        } catch (URISyntaxException expected) {
125            // Expected.
126        }
127    }
128
129    public void testValidateSimple_stringOK_passes() throws Exception {
130        UriCodec.validateSimple(VALID_ENCODED_STRING, "$%");
131    }
132
133    // Hex codes in upper case are valid as well.
134    public void testValidateSimple_stringUppercaseOK_passes() throws Exception {
135        UriCodec.validateSimple(VALID_ENCODED_STRING.toUpperCase(), "$%");
136    }
137
138    // Fails with character 'p', invalid after '%'
139    public void testValidateSimple_wrongCharsAtStart_fails() throws Exception {
140        try {
141            UriCodec.validateSimple("%/" + VALID_ENCODED_STRING, "$%");
142            fail("Expected URISyntaxException");
143        } catch (URISyntaxException expected) {
144            // Expected.
145        }
146    }
147
148    // Fails with character 'p', invalid after '%'
149    public void testValidateSimple_wrongCharsAtEnd_fails() throws Exception {
150        try {
151            UriCodec.validateSimple(VALID_ENCODED_STRING + "%/", "$%");
152            fail("Expected URISyntaxException");
153        } catch (URISyntaxException expected) {
154            // Expected.
155        }
156    }
157
158    public void testValidateSimple_emptyString_passes() throws Exception {
159        UriCodec.validateSimple("", "$%");
160    }
161
162    public void testValidateSimple_stringEndingWithPercent_passes() throws Exception {
163        UriCodec.validateSimple("a%", "$%");
164    }
165
166    public void testValidateSimple_stringEndingWithPercentAndSingleDigit_passes() throws Exception {
167        UriCodec.validateSimple("a%1", "$%");
168    }
169
170    public void testEncode_emptyString_returnsEmptyString() {
171        assertEquals("", CODEC.encode("", StandardCharsets.UTF_8));
172    }
173
174    public void testEncode() {
175        assertEquals("ab%2F$%C4%82%2512", CODEC.encode("ab/$\u0102%12", StandardCharsets.UTF_8));
176    }
177
178    public void testEncode_convertWhitespace() {
179        // Whitespace is not retained, output %20.
180        assertEquals("ab%2F$%C4%82%2512%20",
181                CODEC.encode("ab/$\u0102%12 ", StandardCharsets.UTF_8));
182
183        UriCodec withWhitespaceRetained = new UriCodec() {
184            @Override
185            protected boolean isRetained(char c) {
186                return c == '$' || c == ' ';
187            }
188        };
189        // Whitespace is retained, convert to plus.
190        assertEquals("ab%2F$%C4%82%2512+",
191                withWhitespaceRetained.encode("ab/$\u0102%12 ", StandardCharsets.UTF_8));
192    }
193
194    /** Confirm that '%' can be retained, disabling '%' encoding. http://b/24806835 */
195    public void testEncode_percentRetained() {
196        UriCodec withPercentRetained = new UriCodec() {
197            @Override
198            protected boolean isRetained(char c) {
199                return c == '%';
200            }
201        };
202        // Percent is retained
203        assertEquals("ab%34%20", withPercentRetained.encode("ab%34 ", StandardCharsets.UTF_8));
204    }
205
206    public void testEncode_partially_returnsPercentUnchanged() {
207        StringBuilder stringBuilder = new StringBuilder();
208        // Check it's really appending instead of returning a new builder.
209        stringBuilder.append("pp");
210        CODEC.appendPartiallyEncoded(stringBuilder, "ab/$\u0102%");
211        // Returns % at the end instead of %25.
212        assertEquals("ppab%2F$%C4%82%", stringBuilder.toString());
213    }
214
215    public void testEncode_partially_returnsCharactersAfterPercentEncoded() {
216        StringBuilder stringBuilder = new StringBuilder();
217        // Check it's really appending instead of returning a new builder.
218        stringBuilder.append("pp");
219        CODEC.appendPartiallyEncoded(stringBuilder, "ab/$\u0102%\u0102");
220        // Returns %C4%82 at the end.
221        assertEquals("ppab%2F$%C4%82%%C4%82", stringBuilder.toString());
222    }
223
224    public void testEncode_partially_returnsDigitsAfterPercentUnchanged() {
225        StringBuilder stringBuilder = new StringBuilder();
226        // Check it's really appending instead of returning a new builder.
227        stringBuilder.append("pp");
228        CODEC.appendPartiallyEncoded(stringBuilder, "ab/$\u0102%38");
229        // Returns %38 at the end.
230        assertEquals("ppab%2F$%C4%82%38", stringBuilder.toString());
231    }
232
233    // Last character needs encoding (make sure we are flushing the buffer with chars to encode).
234    public void testEncode_lastCharacter() {
235        assertEquals("ab%2F$%C4%82%25%E0%A1%80",
236                CODEC.encode("ab/$\u0102%\u0840", StandardCharsets.UTF_8));
237    }
238
239    // Last character needs encoding (make sure we are flushing the buffer with chars to encode).
240    public void testEncode_flushBufferBeforePlusFromSpace() {
241        UriCodec withSpaceRetained = new UriCodec() {
242            @Override
243            protected boolean isRetained(char c) {
244                return c == ' ';
245            }
246        };
247        assertEquals("%2F+",
248                withSpaceRetained.encode("/ ", StandardCharsets.UTF_8));
249    }
250
251    public void testDecode_emptyString_returnsEmptyString() {
252        assertEquals("", UriCodec.decode(""));
253    }
254
255    public void testDecode_wrongHexDigit_fails() {
256        try {
257            // %p in the end.
258            UriCodec.decode("ab%2f$%C4%82%25%e0%a1%80%p");
259            fail("Expected URISyntaxException");
260        } catch (IllegalArgumentException expected) {
261            // Expected.
262        }
263    }
264
265    public void testDecode_secondHexDigitWrong_fails() {
266        try {
267            // %1p in the end.
268            UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%1p");
269            fail("Expected URISyntaxException");
270        } catch (IllegalArgumentException expected) {
271            // Expected.
272        }
273    }
274
275    public void testDecode_endsWithPercent_fails() {
276        try {
277            // % in the end.
278            UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%");
279            fail("Expected URISyntaxException");
280        } catch (IllegalArgumentException expected) {
281            // Expected.
282        }
283    }
284
285    public void testDecode_dontThrowException_appendsUnknownCharacter() {
286        assertEquals("ab/$\u0102%\u0840\ufffd",
287                UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%",
288                        false /* convertPlus */,
289                        StandardCharsets.UTF_8,
290                        false /* throwOnFailure */));
291    }
292
293    public void testDecode_convertPlus() {
294        assertEquals("ab/$\u0102% \u0840",
295                UriCodec.decode("ab%2f$%c4%82%25+%e0%a1%80",
296                        true /* convertPlus */,
297                        StandardCharsets.UTF_8,
298                        false /* throwOnFailure */));
299    }
300
301    // Last character needs decoding (make sure we are flushing the buffer with chars to decode).
302    public void testDecode_lastCharacter() {
303        assertEquals("ab/$\u0102%\u0840",
304                UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80"));
305    }
306
307    // Check that a second row of encoded characters is decoded properly (internal buffers are
308    // reset properly).
309    public void testDecode_secondRowOfEncoded() {
310        assertEquals("ab/$\u0102%\u0840aa\u0840",
311                UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80aa%e0%a1%80"));
312    }
313}
314