1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 package java.net;
19 
20 import java.io.IOException;
21 import libcore.net.url.UrlUtils;
22 import libcore.util.Objects;
23 
24 /**
25  * The abstract class {@code URLStreamHandler} is the base for all classes which
26  * can handle the communication with a URL object over a particular protocol
27  * type.
28  */
29 public abstract class URLStreamHandler {
30     /**
31      * Establishes a new connection to the resource specified by the URL {@code
32      * u}. Since different protocols also have unique ways of connecting, it
33      * must be overwritten by the subclass.
34      *
35      * @param u
36      *            the URL to the resource where a connection has to be opened.
37      * @return the opened URLConnection to the specified resource.
38      * @throws IOException
39      *             if an I/O error occurs during opening the connection.
40      */
openConnection(URL u)41     protected abstract URLConnection openConnection(URL u) throws IOException;
42 
43     /**
44      * Establishes a new connection to the resource specified by the URL {@code
45      * u} using the given {@code proxy}. Since different protocols also have
46      * unique ways of connecting, it must be overwritten by the subclass.
47      *
48      * @param u
49      *            the URL to the resource where a connection has to be opened.
50      * @param proxy
51      *            the proxy that is used to make the connection.
52      * @return the opened URLConnection to the specified resource.
53      * @throws IOException
54      *             if an I/O error occurs during opening the connection.
55      * @throws IllegalArgumentException
56      *             if any argument is {@code null} or the type of proxy is
57      *             wrong.
58      * @throws UnsupportedOperationException
59      *             if the protocol handler doesn't support this method.
60      */
openConnection(URL u, Proxy proxy)61     protected URLConnection openConnection(URL u, Proxy proxy) throws IOException {
62         throw new UnsupportedOperationException();
63     }
64 
65     /**
66      * Parses the clear text URL in {@code str} into a URL object. URL strings
67      * generally have the following format:
68      * <p>
69      * http://www.company.com/java/file1.java#reference
70      * <p>
71      * The string is parsed in HTTP format. If the protocol has a different URL
72      * format this method must be overridden.
73      *
74      * @param url
75      *            the URL to fill in the parsed clear text URL parts.
76      * @param spec
77      *            the URL string that is to be parsed.
78      * @param start
79      *            the string position from where to begin parsing.
80      * @param end
81      *            the string position to stop parsing.
82      * @see #toExternalForm
83      * @see URL
84      */
parseURL(URL url, String spec, int start, int end)85     protected void parseURL(URL url, String spec, int start, int end) {
86         if (this != url.streamHandler) {
87             throw new SecurityException("Only a URL's stream handler is permitted to mutate it");
88         }
89         if (end < start) {
90             throw new StringIndexOutOfBoundsException(spec, start, end - start);
91         }
92 
93         int fileStart;
94         String authority;
95         String userInfo;
96         String host;
97         int port = -1;
98         String path;
99         String query;
100         String ref;
101         if (spec.regionMatches(start, "//", 0, 2)) {
102             // Parse the authority from the spec.
103             int authorityStart = start + 2;
104             fileStart = UrlUtils.findFirstOf(spec, "/?#", authorityStart, end);
105             authority = spec.substring(authorityStart, fileStart);
106             int userInfoEnd = UrlUtils.findFirstOf(spec, "@", authorityStart, fileStart);
107             int hostStart;
108             if (userInfoEnd != fileStart) {
109                 userInfo = spec.substring(authorityStart, userInfoEnd);
110                 hostStart = userInfoEnd + 1;
111             } else {
112                 userInfo = null;
113                 hostStart = authorityStart;
114             }
115 
116             /*
117              * Extract the host and port. The host may be an IPv6 address with
118              * colons like "[::1]", in which case we look for the port delimiter
119              * colon after the ']' character.
120              */
121             int colonSearchFrom = hostStart;
122             int ipv6End = UrlUtils.findFirstOf(spec, "]", hostStart, fileStart);
123             if (ipv6End != fileStart) {
124                 if (UrlUtils.findFirstOf(spec, ":", hostStart, ipv6End) == ipv6End) {
125                     throw new IllegalArgumentException("Expected an IPv6 address: "
126                             + spec.substring(hostStart, ipv6End + 1));
127                 }
128                 colonSearchFrom = ipv6End;
129             }
130             int hostEnd = UrlUtils.findFirstOf(spec, ":", colonSearchFrom, fileStart);
131             host = spec.substring(hostStart, hostEnd);
132             int portStart = hostEnd + 1;
133             if (portStart < fileStart) {
134                 char firstPortChar = spec.charAt(portStart);
135                 if (firstPortChar >= '0' && firstPortChar <= '9') {
136                     port = Integer.parseInt(spec.substring(portStart, fileStart));
137                 } else {
138                     throw new IllegalArgumentException("invalid port: " + port);
139                 }
140             }
141             path = null;
142             query = null;
143             ref = null;
144         } else {
145             // Get the authority from the context URL.
146             fileStart = start;
147             authority = url.getAuthority();
148             userInfo = url.getUserInfo();
149             host = url.getHost();
150             if (host == null) {
151                 host = "";
152             }
153             port = url.getPort();
154             path = url.getPath();
155             query = url.getQuery();
156             ref = url.getRef();
157         }
158 
159         /*
160          * Extract the path, query and fragment. Each part has its own leading
161          * delimiter character. The query can contain slashes and the fragment
162          * can contain slashes and question marks.
163          *    / path ? query # fragment
164          */
165         int pos = fileStart;
166         while (pos < end) {
167             int nextPos;
168             switch (spec.charAt(pos)) {
169             case '#':
170                 nextPos = end;
171                 ref = spec.substring(pos + 1, nextPos);
172                 break;
173             case '?':
174                 nextPos = UrlUtils.findFirstOf(spec, "#", pos, end);
175                 query = spec.substring(pos + 1, nextPos);
176                 ref = null;
177                 break;
178             default:
179                 nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end);
180                 path = relativePath(path, spec.substring(pos, nextPos));
181                 query = null;
182                 ref = null;
183                 break;
184             }
185             pos = nextPos;
186         }
187 
188         if (path == null) {
189             path = "";
190         }
191 
192         path = UrlUtils.authoritySafePath(authority, path);
193 
194         setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref);
195     }
196 
197     /**
198      * Returns a new path by resolving {@code path} relative to {@code base}.
199      */
relativePath(String base, String path)200     private static String relativePath(String base, String path) {
201         if (path.startsWith("/")) {
202             return UrlUtils.canonicalizePath(path, true);
203         } else if (base != null) {
204             String combined = base.substring(0, base.lastIndexOf('/') + 1) + path;
205             return UrlUtils.canonicalizePath(combined, true);
206         } else {
207             return path;
208         }
209     }
210 
211     /**
212      * Sets the fields of the URL {@code u} to the values of the supplied
213      * arguments.
214      *
215      * @param u
216      *            the non-null URL object to be set.
217      * @param protocol
218      *            the protocol.
219      * @param host
220      *            the host name.
221      * @param port
222      *            the port number.
223      * @param file
224      *            the file component.
225      * @param ref
226      *            the reference.
227      * @deprecated Use setURL(URL, String String, int, String, String, String,
228      *             String, String) instead.
229      */
230     @Deprecated
setURL(URL u, String protocol, String host, int port, String file, String ref)231     protected void setURL(URL u, String protocol, String host, int port,
232             String file, String ref) {
233         if (this != u.streamHandler) {
234             throw new SecurityException();
235         }
236         u.set(protocol, host, port, file, ref);
237     }
238 
239     /**
240      * Sets the fields of the URL {@code u} to the values of the supplied
241      * arguments.
242      */
setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)243     protected void setURL(URL u, String protocol, String host, int port,
244             String authority, String userInfo, String path, String query,
245             String ref) {
246         if (this != u.streamHandler) {
247             throw new SecurityException();
248         }
249         u.set(protocol, host, port, authority, userInfo, path, query, ref);
250     }
251 
252     /**
253      * Returns the clear text representation of a given URL using HTTP format.
254      *
255      * @param url
256      *            the URL object to be converted.
257      * @return the clear text representation of the specified URL.
258      * @see #parseURL
259      * @see URL#toExternalForm()
260      */
toExternalForm(URL url)261     protected String toExternalForm(URL url) {
262         return toExternalForm(url, false);
263     }
264 
toExternalForm(URL url, boolean escapeIllegalCharacters)265     String toExternalForm(URL url, boolean escapeIllegalCharacters) {
266         StringBuilder result = new StringBuilder();
267         result.append(url.getProtocol());
268         result.append(':');
269 
270         String authority = url.getAuthority();
271         if (authority != null) {
272             result.append("//");
273             if (escapeIllegalCharacters) {
274                 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
275             } else {
276                 result.append(authority);
277             }
278         }
279 
280         String fileAndQuery = url.getFile();
281         if (fileAndQuery != null) {
282             if (escapeIllegalCharacters) {
283                 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
284             } else {
285                 result.append(fileAndQuery);
286             }
287         }
288 
289         String ref = url.getRef();
290         if (ref != null) {
291             result.append('#');
292             if (escapeIllegalCharacters) {
293                 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
294             } else {
295                 result.append(ref);
296             }
297         }
298 
299         return result.toString();
300     }
301 
302     /**
303      * Returns true if {@code a} and {@code b} have the same protocol, host,
304      * port, file, and reference.
305      */
equals(URL a, URL b)306     protected boolean equals(URL a, URL b) {
307         return sameFile(a, b)
308                 && Objects.equal(a.getRef(), b.getRef())
309                 && Objects.equal(a.getQuery(), b.getQuery());
310     }
311 
312     /**
313      * Returns the default port of the protocol used by the handled URL. The
314      * default implementation always returns {@code -1}.
315      */
getDefaultPort()316     protected int getDefaultPort() {
317         return -1;
318     }
319 
320     /**
321      * Returns the host address of {@code url}.
322      */
getHostAddress(URL url)323     protected InetAddress getHostAddress(URL url) {
324         try {
325             String host = url.getHost();
326             if (host == null || host.length() == 0) {
327                 return null;
328             }
329             return InetAddress.getByName(host);
330         } catch (UnknownHostException e) {
331             return null;
332         }
333     }
334 
335     /**
336      * Returns the hash code of {@code url}.
337      */
hashCode(URL url)338     protected int hashCode(URL url) {
339         return toExternalForm(url).hashCode();
340     }
341 
342     /**
343      * Returns true if the hosts of {@code a} and {@code b} are equal.
344      */
hostsEqual(URL a, URL b)345     protected boolean hostsEqual(URL a, URL b) {
346         // URLs with the same case-insensitive host name have equal hosts
347         String aHost = a.getHost();
348         String bHost = b.getHost();
349         return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost);
350     }
351 
352     /**
353      * Returns true if {@code a} and {@code b} have the same protocol, host,
354      * port and file.
355      */
sameFile(URL a, URL b)356     protected boolean sameFile(URL a, URL b) {
357         return Objects.equal(a.getProtocol(), b.getProtocol())
358                 && hostsEqual(a, b)
359                 && a.getEffectivePort() == b.getEffectivePort()
360                 && Objects.equal(a.getFile(), b.getFile());
361     }
362 }
363