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