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