1/*
2 * Copyright (C) 2011 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.java.net;
18
19import java.io.UnsupportedEncodingException;
20import java.net.URI;
21import java.net.URISyntaxException;
22import java.net.URLDecoder;
23import java.net.URLEncoder;
24import java.nio.charset.IllegalCharsetNameException;
25import java.nio.charset.UnsupportedCharsetException;
26import junit.framework.TestCase;
27
28public final class UrlEncodingTest extends TestCase {
29
30    public void testUriRetainsOriginalEncoding() throws Exception {
31        assertEquals("%61", new URI("http://foo#%61").getRawFragment());
32    }
33
34    /**
35     * URLDecoder and URI disagree on what '+' should decode to.
36     */
37    public void testDecodingPlus() throws Exception {
38        assertEquals("a b", URLDecoder.decode("a+b"));
39        assertEquals("a b", URLDecoder.decode("a+b", "UTF-8"));
40        assertEquals("a+b", new URI("http://foo#a+b").getFragment());
41    }
42
43    public void testEncodingPlus() throws Exception {
44        assertEquals("a%2Bb", URLEncoder.encode("a+b"));
45        assertEquals("a%2Bb", URLEncoder.encode("a+b", "UTF-8"));
46        assertEquals("a+b", new URI("http", "foo", "/", "a+b").getRawFragment());
47    }
48
49    public void testDecodingSpace() throws Exception {
50        assertEquals("a b", URLDecoder.decode("a b"));
51        assertEquals("a b", URLDecoder.decode("a b", "UTF-8"));
52        try {
53            new URI("http://foo#a b");
54            fail();
55        } catch (URISyntaxException expected) {
56        }
57    }
58
59    public void testEncodingSpace() throws Exception {
60        assertEquals("a+b", URLEncoder.encode("a b"));
61        assertEquals("a+b", URLEncoder.encode("a b", "UTF-8"));
62        assertEquals("a%20b", new URI("http", "foo", "/", "a b").getRawFragment());
63    }
64
65    public void testUriDecodingPartial() throws Exception {
66        try {
67            new URI("http://foo#%");
68            fail();
69        } catch (URISyntaxException expected) {
70        }
71        try {
72            new URI("http://foo#%0");
73            fail();
74        } catch (URISyntaxException expected) {
75        }
76    }
77
78    public void testUrlDecoderDecodingPartial() throws Exception {
79        try {
80            URLDecoder.decode("%");
81            fail();
82        } catch (IllegalArgumentException expected) {
83        }
84        try {
85            URLDecoder.decode("%0");
86            fail();
87        } catch (IllegalArgumentException expected) {
88        }
89    }
90
91    public void testUriDecodingInvalid() {
92        try {
93            new URI("http://foo#%0g");
94            fail();
95        } catch (URISyntaxException expected) {
96        }
97    }
98
99    public void testUrlDecoderDecodingInvalid() {
100        try {
101            URLDecoder.decode("%0g");
102            fail();
103        } catch (IllegalArgumentException expected) {
104        }
105    }
106
107    public void testUrlDecoderFailsOnNullCharset() throws Exception {
108        try {
109            URLDecoder.decode("ab", null);
110            fail();
111        } catch (IllegalCharsetNameException expected) {
112        } catch (NullPointerException expected) {
113        }
114    }
115
116    public void testUrlDecoderFailsOnEmptyCharset() {
117        try {
118            URLDecoder.decode("ab", "");
119            fail();
120        } catch (IllegalCharsetNameException expected) {
121        } catch (UnsupportedEncodingException expected) {
122        }
123    }
124
125    public void testUrlEncoderFailsOnNullCharset() throws Exception {
126        try {
127            URLEncoder.encode("ab", null);
128            fail();
129        } catch (IllegalCharsetNameException expected) {
130        } catch (NullPointerException expected) {
131        }
132    }
133
134    public void testUrlEncoderFailsOnEmptyCharset() {
135        try {
136            URLEncoder.encode("ab", "");
137            fail();
138        } catch (IllegalCharsetNameException expected) {
139        } catch (UnsupportedEncodingException expected) {
140        }
141    }
142
143    /**
144     * The RI looks up the charset lazily; Android looks it up eagerly. Either
145     * behavior is acceptable.
146     */
147    public void testUrlDecoderIgnoresUnnecessaryCharset() throws Exception {
148        try {
149            assertEquals("ab", URLDecoder.decode("ab", "no-such-charset"));
150            // no fail()
151        } catch (UnsupportedCharsetException expected) {
152        }
153    }
154
155    public void testUrlEncoderFailsOnInvalidCharset() throws Exception {
156        try {
157            URLEncoder.encode("ab", "no-such-charset");
158            fail();
159        } catch (UnsupportedCharsetException expected) {
160        } catch (UnsupportedEncodingException expected) {
161        }
162    }
163
164    public void testDecoding() throws Exception {
165        assertDecoded("a\u0000b", "a%00b");
166        assertDecoded("a b", "a%20b");
167        assertDecoded("a+b", "a%2bb");
168        assertDecoded("a%b", "a%25b");
169        assertDecoded("a\u007fb", "a%7fb");
170    }
171
172    public void testEncoding() throws Exception {
173        assertEncoded("a%25b", "a%b");
174        assertEncoded("a%7Fb", "a\u007fb");
175    }
176
177    public void testDecodingLiterals() throws Exception {
178        assertDecoded("\ud842\udf9f", "\ud842\udf9f");
179    }
180
181    public void testDecodingBrokenUtf8SequenceYieldsReplacementCharacter() throws Exception {
182        assertDecoded("a\ufffdb", "a%ffb");
183    }
184
185    public void testDecodingUtf8Octets() throws Exception {
186        assertDecoded("\u20AC", "%e2%82%ac");
187        assertDecoded("\ud842\udf9f", "%f0%a0%ae%9f");
188    }
189
190    public void testDecodingNonUsDigits() throws Exception {
191        try {
192            new URI("http://foo#" + "%\u0664\u0661");
193            fail();
194        } catch (URISyntaxException expected) {
195        }
196        try {
197            URLDecoder.decode("%\u0664\u0661");
198            fail(); // RI fails this test returning "A"
199        } catch (IllegalArgumentException expected) {
200        }
201    }
202
203    /**
204     * Android's URLEncoder.encode() failed for surrogate pairs, encoding them
205     * as two replacement characters ("??"). http://b/3436051
206     */
207    public void testUrlEncoderEncodesNonPrintableNonAsciiCharacters() throws Exception {
208        assertEquals("%00", URLEncoder.encode("\u0000", "UTF-8"));
209        assertEquals("%00", URLEncoder.encode("\u0000"));
210        assertEquals("%E2%82%AC", URLEncoder.encode("\u20AC", "UTF-8"));
211        assertEquals("%E2%82%AC", URLEncoder.encode("\u20AC"));
212        assertEquals("%F0%A0%AE%9F", URLEncoder.encode("\ud842\udf9f", "UTF-8"));
213        assertEquals("%F0%A0%AE%9F", URLEncoder.encode("\ud842\udf9f"));
214    }
215
216    public void testUriDoesNotEncodeNonPrintableNonAsciiCharacters() throws Exception {
217        assertEquals("\u20AC", new URI("http", "foo", "/", "\u20AC").getRawFragment());
218        assertEquals("\ud842\udf9f", new URI("http", "foo", "/", "\ud842\udf9f").getRawFragment());
219    }
220
221    public void testUriEncodesControlCharacters() throws Exception {
222        assertEquals("%01", new URI("http", "foo", "/", "\u0001").getRawFragment());
223
224        // The RI fails this, encoding \u0001 but not \u0000
225        assertEquals("%00", new URI("http", "foo", "/", "\u0000").getRawFragment());
226    }
227
228    public void testEncodeAndDecode() throws Exception {
229        assertRoundTrip("http://jcltest.apache.org/test?hl=en&q=te st",
230                "http%3A%2F%2Fjcltest.apache.org%2Ftest%3Fhl%3Den%26q%3Dte+st");
231        assertRoundTrip ("file://a b/c/d.e-f*g_ l",
232                "file%3A%2F%2Fa+b%2Fc%2Fd.e-f*g_+l");
233        assertRoundTrip("jar:file://a.jar !/b.c/\u1052",
234                "jar%3Afile%3A%2F%2Fa.jar+%21%2Fb.c%2F%E1%81%92");
235        assertRoundTrip("ftp://test:pwd@localhost:2121/%D0%9C",
236                "ftp%3A%2F%2Ftest%3Apwd%40localhost%3A2121%2F%25D0%259C");
237    }
238
239    /**
240     * Asserts that {@code original} decodes to {@code decoded} using both URI
241     * and UrlDecoder.
242     */
243    private void assertDecoded(String decoded, String original) throws Exception {
244        assertEquals(decoded, new URI("http://foo#" + original).getFragment());
245        assertEquals(decoded, URLDecoder.decode(original));
246        assertEquals(decoded, URLDecoder.decode(original, "UTF-8"));
247    }
248
249    /**
250     * Asserts that {@code original} encodes to {@code encoded} using both URI
251     * and URLEncoder.
252     */
253    private void assertEncoded(String encoded, String original) throws Exception {
254        assertEquals(encoded, URLEncoder.encode(original, "UTF-8"));
255        assertEquals(encoded, URLEncoder.encode(original));
256        assertEquals(encoded, new URI("http", "foo", "/", original).getRawFragment());
257    }
258
259    private void assertRoundTrip(String original, String encoded) throws Exception {
260        assertEquals(encoded, URLEncoder.encode(original, "UTF-8"));
261        assertEquals(original, URLDecoder.decode(encoded, "UTF-8"));
262    }
263}
264