1781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller/*
2781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * Copyright (C) 2015 Square, Inc.
3781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller *
4781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * Licensed under the Apache License, Version 2.0 (the "License");
5781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * you may not use this file except in compliance with the License.
6781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * You may obtain a copy of the License at
7781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller *
8781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller *      http://www.apache.org/licenses/LICENSE-2.0
9781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller *
10781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * Unless required by applicable law or agreed to in writing, software
11781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * distributed under the License is distributed on an "AS IS" BASIS,
12781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * See the License for the specific language governing permissions and
14781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller * limitations under the License.
15781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller */
16781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerpackage com.squareup.okhttp;
17781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
18781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerimport java.util.Collections;
19781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerimport java.util.LinkedHashMap;
20781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerimport java.util.Map;
21781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerimport okio.Buffer;
22781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerimport okio.ByteString;
23781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
24781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerimport static org.junit.Assert.assertEquals;
25781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
26781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller/** Tests how each code point is encoded and decoded in the context of each URL component. */
27781c9c216deed11c44044d23841a4ba6a012106eNeil Fullerclass UrlComponentEncodingTester {
28781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  /**
29781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller   * The default encode set for the ASCII range. The specific rules vary per-component: for example,
30781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller   * '?' may be identity-encoded in a fragment, but must be percent-encoded in a path.
31781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller   *
32781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller   * See https://url.spec.whatwg.org/#percent-encoded-bytes
33781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller   */
34781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  private static final Map<Integer, Encoding> defaultEncodings;
35781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  static {
36781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    Map<Integer, Encoding> map = new LinkedHashMap<>();
37781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x0, Encoding.PERCENT); // Null character
38781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x1, Encoding.PERCENT); // Start of Header
39781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x2, Encoding.PERCENT); // Start of Text
40781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x3, Encoding.PERCENT); // End of Text
41781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x4, Encoding.PERCENT); // End of Transmission
42781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x5, Encoding.PERCENT); // Enquiry
43781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x6, Encoding.PERCENT); // Acknowledgment
44781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0x7, Encoding.PERCENT); // Bell
45781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\b', Encoding.PERCENT); // Backspace
46781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\t', Encoding.SKIP);    // Horizontal Tab
47781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\n', Encoding.SKIP);    // Line feed
48781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0xb, Encoding.PERCENT); // Vertical Tab
49781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\f', Encoding.SKIP);    // Form feed
50781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\r', Encoding.SKIP);    // Carriage return
51781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0xe, Encoding.PERCENT); // Shift Out
52781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(       0xf, Encoding.PERCENT); // Shift In
53781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x10, Encoding.PERCENT); // Data Link Escape
54781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x11, Encoding.PERCENT); // Device Control 1 (oft. XON)
55781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x12, Encoding.PERCENT); // Device Control 2
56781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x13, Encoding.PERCENT); // Device Control 3 (oft. XOFF)
57781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x14, Encoding.PERCENT); // Device Control 4
58781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x15, Encoding.PERCENT); // Negative Acknowledgment
59781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x16, Encoding.PERCENT); // Synchronous idle
60781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x17, Encoding.PERCENT); // End of Transmission Block
61781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x18, Encoding.PERCENT); // Cancel
62781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x19, Encoding.PERCENT); // End of Medium
63781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x1a, Encoding.PERCENT); // Substitute
64781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x1b, Encoding.PERCENT); // Escape
65781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x1c, Encoding.PERCENT); // File Separator
66781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x1d, Encoding.PERCENT); // Group Separator
67781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x1e, Encoding.PERCENT); // Record Separator
68781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x1f, Encoding.PERCENT); // Unit Separator
69781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  ' ', Encoding.PERCENT);
70781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '!', Encoding.IDENTITY);
71781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '"', Encoding.PERCENT);
72781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '#', Encoding.PERCENT);
73781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '$', Encoding.IDENTITY);
74781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '%', Encoding.IDENTITY);
75781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '&', Encoding.IDENTITY);
76781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\'', Encoding.IDENTITY);
77781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '(', Encoding.IDENTITY);
78781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  ')', Encoding.IDENTITY);
79781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '*', Encoding.IDENTITY);
80781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '+', Encoding.IDENTITY);
81781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  ',', Encoding.IDENTITY);
82781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '-', Encoding.IDENTITY);
83781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '.', Encoding.IDENTITY);
84781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '/', Encoding.IDENTITY);
85781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '0', Encoding.IDENTITY);
86781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '1', Encoding.IDENTITY);
87781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '2', Encoding.IDENTITY);
88781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '3', Encoding.IDENTITY);
89781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '4', Encoding.IDENTITY);
90781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '5', Encoding.IDENTITY);
91781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '6', Encoding.IDENTITY);
92781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '7', Encoding.IDENTITY);
93781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '8', Encoding.IDENTITY);
94781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '9', Encoding.IDENTITY);
95781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  ':', Encoding.IDENTITY);
96781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  ';', Encoding.IDENTITY);
97781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '<', Encoding.PERCENT);
98781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '=', Encoding.IDENTITY);
99781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '>', Encoding.PERCENT);
100781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '?', Encoding.PERCENT);
101781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '@', Encoding.IDENTITY);
102781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'A', Encoding.IDENTITY);
103781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'B', Encoding.IDENTITY);
104781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'C', Encoding.IDENTITY);
105781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'D', Encoding.IDENTITY);
106781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'E', Encoding.IDENTITY);
107781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'F', Encoding.IDENTITY);
108781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'G', Encoding.IDENTITY);
109781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'H', Encoding.IDENTITY);
110781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'I', Encoding.IDENTITY);
111781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'J', Encoding.IDENTITY);
112781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'K', Encoding.IDENTITY);
113781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'L', Encoding.IDENTITY);
114781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'M', Encoding.IDENTITY);
115781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'N', Encoding.IDENTITY);
116781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'O', Encoding.IDENTITY);
117781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'P', Encoding.IDENTITY);
118781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'Q', Encoding.IDENTITY);
119781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'R', Encoding.IDENTITY);
120781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'S', Encoding.IDENTITY);
121781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'T', Encoding.IDENTITY);
122781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'U', Encoding.IDENTITY);
123781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'V', Encoding.IDENTITY);
124781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'W', Encoding.IDENTITY);
125781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'X', Encoding.IDENTITY);
126781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'Y', Encoding.IDENTITY);
127781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'Z', Encoding.IDENTITY);
128781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '[', Encoding.IDENTITY);
129781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int) '\\', Encoding.IDENTITY);
130781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  ']', Encoding.IDENTITY);
131781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '^', Encoding.IDENTITY);
132781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '_', Encoding.IDENTITY);
133781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '`', Encoding.PERCENT);
134781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'a', Encoding.IDENTITY);
135781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'b', Encoding.IDENTITY);
136781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'c', Encoding.IDENTITY);
137781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'd', Encoding.IDENTITY);
138781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'e', Encoding.IDENTITY);
139781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'f', Encoding.IDENTITY);
140781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'g', Encoding.IDENTITY);
141781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'h', Encoding.IDENTITY);
142781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'i', Encoding.IDENTITY);
143781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'j', Encoding.IDENTITY);
144781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'k', Encoding.IDENTITY);
145781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'l', Encoding.IDENTITY);
146781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'm', Encoding.IDENTITY);
147781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'n', Encoding.IDENTITY);
148781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'o', Encoding.IDENTITY);
149781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'p', Encoding.IDENTITY);
150781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'q', Encoding.IDENTITY);
151781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'r', Encoding.IDENTITY);
152781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  's', Encoding.IDENTITY);
153781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  't', Encoding.IDENTITY);
154781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'u', Encoding.IDENTITY);
155781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'v', Encoding.IDENTITY);
156781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'w', Encoding.IDENTITY);
157781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'x', Encoding.IDENTITY);
158781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'y', Encoding.IDENTITY);
159781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  'z', Encoding.IDENTITY);
160781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '{', Encoding.IDENTITY);
161781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '|', Encoding.IDENTITY);
162781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '}', Encoding.IDENTITY);
163781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put((int)  '~', Encoding.IDENTITY);
164781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    map.put(      0x7f, Encoding.PERCENT); // Delete
165781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    defaultEncodings = Collections.unmodifiableMap(map);
166781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
167781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
168781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  private final Map<Integer, Encoding> encodings;
169781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
170781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  public UrlComponentEncodingTester() {
171781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    this.encodings = new LinkedHashMap<>(defaultEncodings);
172781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
173781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
174781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  public UrlComponentEncodingTester override(Encoding encoding, int... codePoints) {
175781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    for (int codePoint : codePoints) {
176781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      encodings.put(codePoint, encoding);
177781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    }
178781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    return this;
179781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
180781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
181781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  public UrlComponentEncodingTester test(Component component) {
182781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    for (Map.Entry<Integer, Encoding> entry : encodings.entrySet()) {
183781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      if (entry.getValue() == Encoding.SKIP) continue;
184781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
185781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      testParseOriginal(entry.getKey(), entry.getValue(), component);
186781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      testParseAlreadyEncoded(entry.getKey(), entry.getValue(), component);
187781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      testSerialize(entry.getKey(), entry.getValue(), component);
188781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    }
189781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    return this;
190781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
191781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
192781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  private void testParseAlreadyEncoded(int codePoint, Encoding encoding, Component component) {
193781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    String encoded = encoding.encode(codePoint);
194781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    String urlString = component.urlString(encoded);
195781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    HttpUrl url = HttpUrl.parse(urlString);
196781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    if (!component.decodedValue(url).equals(encoded)) {
197781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      assertEquals(String.format("Encoding %s %#x using %s", component, codePoint, encoding),
198781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller          encoded, component.decodedValue(url));
199781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    }
200781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
201781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
202781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  private void testParseOriginal(int codePoint, Encoding encoding, Component component) {
203781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    String encoded = encoding.encode(codePoint);
204781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    if (encoding != Encoding.PERCENT) return;
205781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    String identity = Encoding.IDENTITY.encode(codePoint);
206781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    String urlString = component.urlString(identity);
207781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    HttpUrl url = HttpUrl.parse(urlString);
208781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
209781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    String s = component.decodedValue(url);
210781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    if (!s.equals(encoded)) {
211781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      assertEquals(String.format("Encoding %s %#02x using %s", component, codePoint, encoding),
212781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller          encoded, component.decodedValue(url));
213781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    }
214781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
215781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
216781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  private void testSerialize(int codePoint, Encoding encoding, Component component) {
217781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    // TODO.
218781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
219781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
220781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  public enum Encoding {
221781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    IDENTITY {
222781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      public String encode(int codePoint) {
223781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return new String(new int[] { codePoint }, 0, 1);
224781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
225781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
226781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
227781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    PERCENT {
228781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      public String encode(int codePoint) {
229781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        ByteString utf8 = ByteString.encodeUtf8(IDENTITY.encode(codePoint));
230781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        Buffer percentEncoded = new Buffer();
231781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        for (int i = 0; i < utf8.size(); i++) {
232781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller          percentEncoded.writeUtf8(String.format("%%%02X", utf8.getByte(i) & 0xff));
233781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        }
234781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return percentEncoded.readUtf8();
235781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
236781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
237781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
238781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    SKIP {
239781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      public String encode(int codePoint) {
240781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        throw new UnsupportedOperationException();
241781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
242781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    };
243781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
244781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    public abstract String encode(int codePoint);
245781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
246781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
247781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
248781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  public enum Component {
249781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    USER {
250781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
251781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return "http://" + value + "@example.com/";
252781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
253781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
254781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return url.username();
255781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
256781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
257781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    PASSWORD {
258781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
259781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return "http://:" + value + "@example.com/";
260781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
261781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
262781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return url.password();
263781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
264781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
265781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    HOST {
266781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
267781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        throw new UnsupportedOperationException("TODO");
268781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
269781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
270781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
271781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        throw new UnsupportedOperationException("TODO");
272781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
273781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
274781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    PORT {
275781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
276781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        throw new UnsupportedOperationException("TODO");
277781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
278781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
279781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
280781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        throw new UnsupportedOperationException("TODO");
281781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
282781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
283781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    PATH {
284781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
285781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return "http://example.com/a" + value + "z/";
286781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
287781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
288781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        String path = url.path();
289781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return path.substring(2, path.length() - 2);
290781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
291781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
292781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    QUERY {
293781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
294781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return "http://example.com/?a" + value + "z";
295781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
296781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
297781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
298781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        String query = url.query();
299781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return query.substring(1, query.length() - 1);
300781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
301781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    },
302781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    FRAGMENT {
303781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String urlString(String value) {
304781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return "http://example.com/#a" + value + "z";
305781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
306781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
307781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      @Override public String decodedValue(HttpUrl url) {
308781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        String fragment = url.fragment();
309781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller        return fragment.substring(1, fragment.length() - 1);
310781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller      }
311781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    };
312781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
313781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    public abstract String urlString(String value);
314781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller
315781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller    public abstract String decodedValue(HttpUrl url);
316781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller  }
317781c9c216deed11c44044d23841a4ba6a012106eNeil Fuller}
318