URLStreamHandler.java revision 2d99ef561304174b8ae01a0a68d5b96d5edb9f10
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 path = UrlUtils.authoritySafePath(authority, path); 184 185 setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref); 186 } 187 188 /** 189 * Returns the index of the first char of {@code chars} in {@code string} 190 * bounded between {@code start} and {@code end}. This returns {@code end} 191 * if none of the characters exist in the requested range. 192 */ 193 private static int findFirstOf(String string, String chars, int start, int end) { 194 for (int i = start; i < end; i++) { 195 char c = string.charAt(i); 196 if (chars.indexOf(c) != -1) { 197 return i; 198 } 199 } 200 return end; 201 } 202 203 /** 204 * Returns a new path by resolving {@code path} relative to {@code base}. 205 */ 206 private static String relativePath(String base, String path) { 207 if (path.startsWith("/")) { 208 return UrlUtils.canonicalizePath(path, true); 209 } else if (base != null) { 210 String combined = base.substring(0, base.lastIndexOf('/') + 1) + path; 211 return UrlUtils.canonicalizePath(combined, true); 212 } else { 213 return path; 214 } 215 } 216 217 /** 218 * Sets the fields of the URL {@code u} to the values of the supplied 219 * arguments. 220 * 221 * @param u 222 * the non-null URL object to be set. 223 * @param protocol 224 * the protocol. 225 * @param host 226 * the host name. 227 * @param port 228 * the port number. 229 * @param file 230 * the file component. 231 * @param ref 232 * the reference. 233 * @deprecated use setURL(URL, String String, int, String, String, String, 234 * String, String) instead. 235 */ 236 @Deprecated 237 protected void setURL(URL u, String protocol, String host, int port, 238 String file, String ref) { 239 if (this != u.streamHandler) { 240 throw new SecurityException(); 241 } 242 u.set(protocol, host, port, file, ref); 243 } 244 245 /** 246 * Sets the fields of the URL {@code u} to the values of the supplied 247 * arguments. 248 */ 249 protected void setURL(URL u, String protocol, String host, int port, 250 String authority, String userInfo, String path, String query, 251 String ref) { 252 if (this != u.streamHandler) { 253 throw new SecurityException(); 254 } 255 u.set(protocol, host, port, authority, userInfo, path, query, ref); 256 } 257 258 /** 259 * Returns the clear text representation of a given URL using HTTP format. 260 * 261 * @param url 262 * the URL object to be converted. 263 * @return the clear text representation of the specified URL. 264 * @see #parseURL 265 * @see URL#toExternalForm() 266 */ 267 protected String toExternalForm(URL url) { 268 return toExternalForm(url, false); 269 } 270 271 String toExternalForm(URL url, boolean escapeIllegalCharacters) { 272 StringBuilder result = new StringBuilder(); 273 result.append(url.getProtocol()); 274 result.append(':'); 275 276 String authority = url.getAuthority(); 277 if (authority != null) { 278 result.append("//"); 279 if (escapeIllegalCharacters) { 280 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority); 281 } else { 282 result.append(authority); 283 } 284 } 285 286 String fileAndQuery = url.getFile(); 287 if (fileAndQuery != null) { 288 if (escapeIllegalCharacters) { 289 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery); 290 } else { 291 result.append(fileAndQuery); 292 } 293 } 294 295 String ref = url.getRef(); 296 if (ref != null) { 297 result.append('#'); 298 if (escapeIllegalCharacters) { 299 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref); 300 } else { 301 result.append(ref); 302 } 303 } 304 305 return result.toString(); 306 } 307 308 /** 309 * Returns true if {@code a} and {@code b} have the same protocol, host, 310 * port, file, and reference. 311 */ 312 protected boolean equals(URL a, URL b) { 313 return sameFile(a, b) 314 && Objects.equal(a.getRef(), b.getRef()) 315 && Objects.equal(a.getQuery(), b.getQuery()); 316 } 317 318 /** 319 * Returns the default port of the protocol used by the handled URL. The 320 * default implementation always returns {@code -1}. 321 */ 322 protected int getDefaultPort() { 323 return -1; 324 } 325 326 /** 327 * Returns the host address of {@code url}. 328 */ 329 protected InetAddress getHostAddress(URL url) { 330 try { 331 String host = url.getHost(); 332 if (host == null || host.length() == 0) { 333 return null; 334 } 335 return InetAddress.getByName(host); 336 } catch (UnknownHostException e) { 337 return null; 338 } 339 } 340 341 /** 342 * Returns the hash code of {@code url}. 343 */ 344 protected int hashCode(URL url) { 345 return toExternalForm(url).hashCode(); 346 } 347 348 /** 349 * Returns true if the hosts of {@code a} and {@code b} are equal. 350 */ 351 protected boolean hostsEqual(URL a, URL b) { 352 // URLs with the same case-insensitive host name have equal hosts 353 String aHost = a.getHost(); 354 String bHost = b.getHost(); 355 return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost); 356 } 357 358 /** 359 * Returns true if {@code a} and {@code b} have the same protocol, host, 360 * port and file. 361 */ 362 protected boolean sameFile(URL a, URL b) { 363 return Objects.equal(a.getProtocol(), b.getProtocol()) 364 && hostsEqual(a, b) 365 && a.getEffectivePort() == b.getEffectivePort() 366 && Objects.equal(a.getFile(), b.getFile()); 367 } 368} 369