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