• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                 port = Integer.parseInt(spec.substring(portStart, fileStart));
135                 if (port < 0) {
136                     throw new IllegalArgumentException("port < 0: " + port);
137                 }
138             }
139             path = null;
140             query = null;
141             ref = null;
142         } else {
143             // Get the authority from the context URL.
144             fileStart = start;
145             authority = url.getAuthority();
146             userInfo = url.getUserInfo();
147             host = url.getHost();
148             if (host == null) {
149                 host = "";
150             }
151             port = url.getPort();
152             path = url.getPath();
153             query = url.getQuery();
154             ref = url.getRef();
155         }
156 
157         /*
158          * Extract the path, query and fragment. Each part has its own leading
159          * delimiter character. The query can contain slashes and the fragment
160          * can contain slashes and question marks.
161          *    / path ? query # fragment
162          */
163         int pos = fileStart;
164         while (pos < end) {
165             int nextPos;
166             switch (spec.charAt(pos)) {
167             case '#':
168                 nextPos = end;
169                 ref = spec.substring(pos + 1, nextPos);
170                 break;
171             case '?':
172                 nextPos = UrlUtils.findFirstOf(spec, "#", pos, end);
173                 query = spec.substring(pos + 1, nextPos);
174                 ref = null;
175                 break;
176             default:
177                 nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end);
178                 path = relativePath(path, spec.substring(pos, nextPos));
179                 query = null;
180                 ref = null;
181                 break;
182             }
183             pos = nextPos;
184         }
185 
186         if (path == null) {
187             path = "";
188         }
189 
190         path = UrlUtils.authoritySafePath(authority, path);
191 
192         setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref);
193     }
194 
195     /**
196      * Returns a new path by resolving {@code path} relative to {@code base}.
197      */
relativePath(String base, String path)198     private static String relativePath(String base, String path) {
199         if (path.startsWith("/")) {
200             return UrlUtils.canonicalizePath(path, true);
201         } else if (base != null) {
202             String combined = base.substring(0, base.lastIndexOf('/') + 1) + path;
203             return UrlUtils.canonicalizePath(combined, true);
204         } else {
205             return path;
206         }
207     }
208 
209     /**
210      * Sets the fields of the URL {@code u} to the values of the supplied
211      * arguments.
212      *
213      * @param u
214      *            the non-null URL object to be set.
215      * @param protocol
216      *            the protocol.
217      * @param host
218      *            the host name.
219      * @param port
220      *            the port number.
221      * @param file
222      *            the file component.
223      * @param ref
224      *            the reference.
225      * @deprecated use setURL(URL, String String, int, String, String, String,
226      *             String, String) instead.
227      */
228     @Deprecated
setURL(URL u, String protocol, String host, int port, String file, String ref)229     protected void setURL(URL u, String protocol, String host, int port,
230             String file, String ref) {
231         if (this != u.streamHandler) {
232             throw new SecurityException();
233         }
234         u.set(protocol, host, port, file, ref);
235     }
236 
237     /**
238      * Sets the fields of the URL {@code u} to the values of the supplied
239      * arguments.
240      */
setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)241     protected void setURL(URL u, String protocol, String host, int port,
242             String authority, String userInfo, String path, String query,
243             String ref) {
244         if (this != u.streamHandler) {
245             throw new SecurityException();
246         }
247         u.set(protocol, host, port, authority, userInfo, path, query, ref);
248     }
249 
250     /**
251      * Returns the clear text representation of a given URL using HTTP format.
252      *
253      * @param url
254      *            the URL object to be converted.
255      * @return the clear text representation of the specified URL.
256      * @see #parseURL
257      * @see URL#toExternalForm()
258      */
toExternalForm(URL url)259     protected String toExternalForm(URL url) {
260         return toExternalForm(url, false);
261     }
262 
toExternalForm(URL url, boolean escapeIllegalCharacters)263     String toExternalForm(URL url, boolean escapeIllegalCharacters) {
264         StringBuilder result = new StringBuilder();
265         result.append(url.getProtocol());
266         result.append(':');
267 
268         String authority = url.getAuthority();
269         if (authority != null) {
270             result.append("//");
271             if (escapeIllegalCharacters) {
272                 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
273             } else {
274                 result.append(authority);
275             }
276         }
277 
278         String fileAndQuery = url.getFile();
279         if (fileAndQuery != null) {
280             if (escapeIllegalCharacters) {
281                 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
282             } else {
283                 result.append(fileAndQuery);
284             }
285         }
286 
287         String ref = url.getRef();
288         if (ref != null) {
289             result.append('#');
290             if (escapeIllegalCharacters) {
291                 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
292             } else {
293                 result.append(ref);
294             }
295         }
296 
297         return result.toString();
298     }
299 
300     /**
301      * Returns true if {@code a} and {@code b} have the same protocol, host,
302      * port, file, and reference.
303      */
equals(URL a, URL b)304     protected boolean equals(URL a, URL b) {
305         return sameFile(a, b)
306                 && Objects.equal(a.getRef(), b.getRef())
307                 && Objects.equal(a.getQuery(), b.getQuery());
308     }
309 
310     /**
311      * Returns the default port of the protocol used by the handled URL. The
312      * default implementation always returns {@code -1}.
313      */
getDefaultPort()314     protected int getDefaultPort() {
315         return -1;
316     }
317 
318     /**
319      * Returns the host address of {@code url}.
320      */
getHostAddress(URL url)321     protected InetAddress getHostAddress(URL url) {
322         try {
323             String host = url.getHost();
324             if (host == null || host.length() == 0) {
325                 return null;
326             }
327             return InetAddress.getByName(host);
328         } catch (UnknownHostException e) {
329             return null;
330         }
331     }
332 
333     /**
334      * Returns the hash code of {@code url}.
335      */
hashCode(URL url)336     protected int hashCode(URL url) {
337         return toExternalForm(url).hashCode();
338     }
339 
340     /**
341      * Returns true if the hosts of {@code a} and {@code b} are equal.
342      */
hostsEqual(URL a, URL b)343     protected boolean hostsEqual(URL a, URL b) {
344         // URLs with the same case-insensitive host name have equal hosts
345         String aHost = a.getHost();
346         String bHost = b.getHost();
347         return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost);
348     }
349 
350     /**
351      * Returns true if {@code a} and {@code b} have the same protocol, host,
352      * port and file.
353      */
sameFile(URL a, URL b)354     protected boolean sameFile(URL a, URL b) {
355         return Objects.equal(a.getProtocol(), b.getProtocol())
356                 && hostsEqual(a, b)
357                 && a.getEffectivePort() == b.getEffectivePort()
358                 && Objects.equal(a.getFile(), b.getFile());
359     }
360 }
361