URLStreamHandler.java revision 8f99aa098c6a06b8be788abbca1c1d1060342709
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.util.Objects; 22import org.apache.harmony.luni.util.URLUtil; 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 u 75 * the URL to fill in the parsed clear text URL parts. 76 * @param str 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 u, String str, int start, int end) { 86 // For compatibility, refer to Harmony-2941 87 if (str.startsWith("//", start) 88 && str.indexOf('/', start + 2) == -1 89 && end <= Integer.MIN_VALUE + 1) { 90 throw new StringIndexOutOfBoundsException(end - 2 - start); 91 } 92 if (end < start) { 93 if (this != u.streamHandler) { 94 throw new SecurityException(); 95 } 96 return; 97 } 98 String parseString = ""; 99 if (start < end) { 100 parseString = str.substring(start, end); 101 } 102 end -= start; 103 int fileIdx = 0; 104 105 // Default is to use info from context 106 String host = u.getHost(); 107 int port = u.getPort(); 108 String ref = u.getRef(); 109 String file = u.getPath(); 110 String query = u.getQuery(); 111 String authority = u.getAuthority(); 112 String userInfo = u.getUserInfo(); 113 114 int refIdx = parseString.indexOf('#', 0); 115 if (parseString.startsWith("//")) { 116 int hostIdx = 2; 117 port = -1; 118 fileIdx = parseString.indexOf('/', hostIdx); 119 int questionMarkIndex = parseString.indexOf('?', hostIdx); 120 if (questionMarkIndex != -1 && (fileIdx == -1 || fileIdx > questionMarkIndex)) { 121 fileIdx = questionMarkIndex; 122 } 123 if (fileIdx == -1) { 124 fileIdx = end; 125 // Use default 126 file = ""; 127 } 128 int hostEnd = fileIdx; 129 if (refIdx != -1 && refIdx < fileIdx) { 130 hostEnd = refIdx; 131 fileIdx = refIdx; 132 file = ""; 133 } 134 int userIdx = parseString.lastIndexOf('@', hostEnd); 135 authority = parseString.substring(hostIdx, hostEnd); 136 if (userIdx != -1) { 137 userInfo = parseString.substring(hostIdx, userIdx); 138 hostIdx = userIdx + 1; 139 } 140 141 int endOfIPv6Addr = parseString.indexOf(']', hostIdx); 142 if (endOfIPv6Addr >= hostEnd) { 143 endOfIPv6Addr = -1; 144 } 145 146 // the port separator must be immediately after an IPv6 address "http://[::1]:80/" 147 int portIdx = -1; 148 if (endOfIPv6Addr != -1) { 149 int maybeColon = endOfIPv6Addr + 1; 150 if (maybeColon < hostEnd && parseString.charAt(maybeColon) == ':') { 151 portIdx = maybeColon; 152 } 153 } else { 154 portIdx = parseString.indexOf(':', hostIdx); 155 } 156 157 if (portIdx == -1 || portIdx > hostEnd) { 158 host = parseString.substring(hostIdx, hostEnd); 159 } else { 160 host = parseString.substring(hostIdx, portIdx); 161 String portString = parseString.substring(portIdx + 1, hostEnd); 162 if (portString.length() == 0) { 163 port = -1; 164 } else { 165 port = Integer.parseInt(portString); 166 } 167 } 168 } 169 170 if (refIdx > -1) { 171 ref = parseString.substring(refIdx + 1, end); 172 } 173 int fileEnd = (refIdx == -1 ? end : refIdx); 174 175 int queryIdx = parseString.lastIndexOf('?', fileEnd); 176 boolean canonicalize = false; 177 if (queryIdx > -1) { 178 query = parseString.substring(queryIdx + 1, fileEnd); 179 if (queryIdx == 0 && file != null) { 180 if (file.isEmpty()) { 181 file = "/"; 182 } else if (file.startsWith("/")) { 183 canonicalize = true; 184 } 185 int last = file.lastIndexOf('/') + 1; 186 file = file.substring(0, last); 187 } 188 fileEnd = queryIdx; 189 } else 190 // Don't inherit query unless only the ref is changed 191 if (refIdx != 0) { 192 query = null; 193 } 194 195 if (fileIdx > -1) { 196 if (fileIdx < end && parseString.charAt(fileIdx) == '/') { 197 file = parseString.substring(fileIdx, fileEnd); 198 } else if (fileEnd > fileIdx) { 199 if (file == null) { 200 file = ""; 201 } else if (file.isEmpty()) { 202 file = "/"; 203 } else if (file.startsWith("/")) { 204 canonicalize = true; 205 } 206 int last = file.lastIndexOf('/') + 1; 207 if (last == 0) { 208 file = parseString.substring(fileIdx, fileEnd); 209 } else { 210 file = file.substring(0, last) 211 + parseString.substring(fileIdx, fileEnd); 212 } 213 } 214 } 215 if (file == null) { 216 file = ""; 217 } 218 219 if (host == null) { 220 host = ""; 221 } 222 223 if (canonicalize) { 224 // modify file if there's any relative referencing 225 file = URLUtil.canonicalizePath(file); 226 } 227 228 setURL(u, u.getProtocol(), host, port, authority, userInfo, file, 229 query, ref); 230 } 231 232 /** 233 * Sets the fields of the URL {@code u} to the values of the supplied 234 * arguments. 235 * 236 * @param u 237 * the non-null URL object to be set. 238 * @param protocol 239 * the protocol. 240 * @param host 241 * the host name. 242 * @param port 243 * the port number. 244 * @param file 245 * the file component. 246 * @param ref 247 * the reference. 248 * @deprecated use setURL(URL, String String, int, String, String, String, 249 * String, String) instead. 250 */ 251 @Deprecated 252 protected void setURL(URL u, String protocol, String host, int port, 253 String file, String ref) { 254 if (this != u.streamHandler) { 255 throw new SecurityException(); 256 } 257 u.set(protocol, host, port, file, ref); 258 } 259 260 /** 261 * Sets the fields of the URL {@code u} to the values of the supplied 262 * arguments. 263 * 264 * @param u 265 * the non-null URL object to be set. 266 * @param protocol 267 * the protocol. 268 * @param host 269 * the host name. 270 * @param port 271 * the port number. 272 * @param authority 273 * the authority. 274 * @param userInfo 275 * the user info. 276 * @param file 277 * the file component. 278 * @param query 279 * the query. 280 * @param ref 281 * the reference. 282 */ 283 protected void setURL(URL u, String protocol, String host, int port, 284 String authority, String userInfo, String file, String query, 285 String ref) { 286 if (this != u.streamHandler) { 287 throw new SecurityException(); 288 } 289 u.set(protocol, host, port, authority, userInfo, file, query, ref); 290 } 291 292 /** 293 * Returns the clear text representation of a given URL using HTTP format. 294 * 295 * @param url 296 * the URL object to be converted. 297 * @return the clear text representation of the specified URL. 298 * @see #parseURL 299 * @see URL#toExternalForm() 300 */ 301 protected String toExternalForm(URL url) { 302 return toExternalForm(url, false); 303 } 304 305 String toExternalForm(URL url, boolean escapeIllegalCharacters) { 306 StringBuilder result = new StringBuilder(); 307 result.append(url.getProtocol()); 308 result.append(':'); 309 310 String authority = url.getAuthority(); 311 if (authority != null && !authority.isEmpty()) { 312 result.append("//"); 313 if (escapeIllegalCharacters) { 314 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority); 315 } else { 316 result.append(authority); 317 } 318 } 319 320 String fileAndQuery = url.getFile(); 321 if (fileAndQuery != null) { 322 if (escapeIllegalCharacters) { 323 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery); 324 } else { 325 result.append(fileAndQuery); 326 } 327 } 328 329 String ref = url.getRef(); 330 if (ref != null) { 331 result.append('#'); 332 if (escapeIllegalCharacters) { 333 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref); 334 } else { 335 result.append(ref); 336 } 337 } 338 339 return result.toString(); 340 } 341 342 /** 343 * Returns true if {@code a} and {@code b} have the same protocol, host, 344 * port, file, and reference. 345 */ 346 protected boolean equals(URL a, URL b) { 347 return sameFile(a, b) 348 && Objects.equal(a.getRef(), b.getRef()) 349 && Objects.equal(a.getQuery(), b.getQuery()); 350 } 351 352 /** 353 * Returns the default port of the protocol used by the handled URL. The 354 * default implementation always returns {@code -1}. 355 */ 356 protected int getDefaultPort() { 357 return -1; 358 } 359 360 /** 361 * Returns the host address of {@code url}. 362 */ 363 protected InetAddress getHostAddress(URL url) { 364 try { 365 String host = url.getHost(); 366 if (host == null || host.length() == 0) { 367 return null; 368 } 369 return InetAddress.getByName(host); 370 } catch (UnknownHostException e) { 371 return null; 372 } 373 } 374 375 /** 376 * Returns the hash code of {@code url}. 377 */ 378 protected int hashCode(URL url) { 379 return toExternalForm(url).hashCode(); 380 } 381 382 /** 383 * Returns true if the hosts of {@code a} and {@code b} are equal. 384 */ 385 protected boolean hostsEqual(URL a, URL b) { 386 // URLs with the same case-insensitive host name have equal hosts 387 String aHost = getHost(a); 388 String bHost = getHost(b); 389 return aHost != null && aHost.equalsIgnoreCase(bHost); 390 } 391 392 /** 393 * Returns true if {@code a} and {@code b} have the same protocol, host, 394 * port and file. 395 */ 396 protected boolean sameFile(URL a, URL b) { 397 return Objects.equal(a.getProtocol(), b.getProtocol()) 398 && hostsEqual(a, b) 399 && a.getEffectivePort() == b.getEffectivePort() 400 && Objects.equal(a.getFile(), b.getFile()); 401 } 402 403 private static String getHost(URL url) { 404 String host = url.getHost(); 405 if ("file".equals(url.getProtocol()) && host != null && host.isEmpty()) { 406 host = "localhost"; 407 } 408 return host; 409 } 410} 411