URLStreamHandler.java revision dd828f42a5c83b4270d4fbf6fce2da1878f1e84a
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; 21 22import org.apache.harmony.luni.util.Msg; 23import org.apache.harmony.luni.util.URLUtil; 24 25/** 26 * The abstract class {@code URLStreamHandler} is the base for all classes which 27 * can handle the communication with a URL object over a particular protocol 28 * type. 29 * 30 * @since Android 1.0 31 */ 32public abstract class URLStreamHandler { 33 34 /** 35 * Establishes a new connection to the resource specified by the URL {@code 36 * u}. Since different protocols also have unique ways of connecting, it 37 * must be overwritten by the subclass. 38 * 39 * @param u 40 * the URL to the resource where a connection has to be opened. 41 * @return the opened URLConnection to the specified resource. 42 * @throws IOException 43 * if an I/O error occurs during opening the connection. 44 * @since Android 1.0 45 */ 46 protected abstract URLConnection openConnection(URL u) throws IOException; 47 48 /** 49 * Establishes a new connection to the resource specified by the URL {@code 50 * u} using the given {@code proxy}. Since different protocols also have 51 * unique ways of connecting, it must be overwritten by the subclass. 52 * 53 * @param u 54 * the URL to the resource where a connection has to be opened. 55 * @param proxy 56 * the proxy that is used to make the connection. 57 * @return the opened URLConnection to the specified resource. 58 * @throws IOException 59 * if an I/O error occurs during opening the connection. 60 * @throws IllegalArgumentException 61 * if any argument is {@code null} or the type of proxy is 62 * wrong. 63 * @throws UnsupportedOperationException 64 * if the protocol handler doesn't support this method. 65 * @since Android 1.0 66 */ 67 protected URLConnection openConnection(URL u, Proxy proxy) 68 throws IOException { 69 throw new UnsupportedOperationException(Msg.getString("K034d")); //$NON-NLS-1$ 70 } 71 72 /** 73 * Parses the clear text URL in {@code str} into a URL object. URL strings 74 * generally have the following format: 75 * <p> 76 * http://www.company.com/java/file1.java#reference 77 * </p> 78 * The string is parsed in HTTP format. If the protocol has a different URL 79 * format this method must be overridden. 80 * 81 * @param u 82 * the URL to fill in the parsed clear text URL parts. 83 * @param str 84 * the URL string that is to be parsed. 85 * @param start 86 * the string position from where to begin parsing. 87 * @param end 88 * the string position to stop parsing. 89 * @see #toExternalForm 90 * @see URL 91 * @since Android 1.0 92 */ 93 protected void parseURL(URL u, String str, int start, int end) { 94 // For compatibility, refer to Harmony-2941 95 if (str.startsWith("//", start) //$NON-NLS-1$ 96 && str.indexOf('/', start + 2) == -1 97 && end <= Integer.MIN_VALUE + 1) { 98 throw new StringIndexOutOfBoundsException(end - 2 - start); 99 } 100 if (end < start) { 101 if (this != u.strmHandler) { 102 throw new SecurityException(); 103 } 104 return; 105 } 106 String parseString = ""; //$NON-NLS-1$ 107 if (start < end) { 108 parseString = str.substring(start, end); 109 } 110 end -= start; 111 int fileIdx = 0; 112 113 // Default is to use info from context 114 String host = u.getHost(); 115 int port = u.getPort(); 116 String ref = u.getRef(); 117 String file = u.getPath(); 118 String query = u.getQuery(); 119 String authority = u.getAuthority(); 120 String userInfo = u.getUserInfo(); 121 122 int refIdx = parseString.indexOf('#', 0); 123 if (parseString.startsWith("//")) { //$NON-NLS-1$ 124 int hostIdx = 2, portIdx = -1; 125 port = -1; 126 fileIdx = parseString.indexOf('/', hostIdx); 127 int questionMarkIndex = parseString.indexOf('?', hostIdx); 128 if ((questionMarkIndex != -1) 129 && ((fileIdx == -1) || (fileIdx > questionMarkIndex))) { 130 fileIdx = questionMarkIndex; 131 } 132 if (fileIdx == -1) { 133 fileIdx = end; 134 // Use default 135 file = ""; //$NON-NLS-1$ 136 } 137 int hostEnd = fileIdx; 138 if (refIdx != -1 && refIdx < fileIdx) { 139 hostEnd = refIdx; 140 } 141 int userIdx = parseString.lastIndexOf('@', hostEnd); 142 authority = parseString.substring(hostIdx, hostEnd); 143 if (userIdx > -1) { 144 userInfo = parseString.substring(hostIdx, userIdx); 145 hostIdx = userIdx + 1; 146 } 147 148 portIdx = parseString.indexOf(':', userIdx == -1 ? hostIdx 149 : userIdx); 150 int endOfIPv6Addr = parseString.indexOf(']'); 151 // if there are square braces, ie. IPv6 address, use last ':' 152 if (endOfIPv6Addr != -1) { 153 try { 154 if (parseString.length() > endOfIPv6Addr + 1) { 155 char c = parseString.charAt(endOfIPv6Addr + 1); 156 if (c == ':') { 157 portIdx = endOfIPv6Addr + 1; 158 } else { 159 portIdx = -1; 160 } 161 } else { 162 portIdx = -1; 163 } 164 } catch (Exception e) { 165 // Ignored 166 } 167 } 168 169 if (portIdx == -1 || portIdx > fileIdx) { 170 host = parseString.substring(hostIdx, hostEnd); 171 } else { 172 host = parseString.substring(hostIdx, portIdx); 173 String portString = parseString.substring(portIdx + 1, hostEnd); 174 if (portString.length() == 0) { 175 port = -1; 176 } else { 177 port = Integer.parseInt(portString); 178 } 179 } 180 } 181 182 if (refIdx > -1) { 183 ref = parseString.substring(refIdx + 1, end); 184 } 185 int fileEnd = (refIdx == -1 ? end : refIdx); 186 187 int queryIdx = parseString.lastIndexOf('?', fileEnd); 188 boolean canonicalize = false; 189 if (queryIdx > -1) { 190 query = parseString.substring(queryIdx + 1, fileEnd); 191 if (queryIdx == 0 && file != null) { 192 if (file.equals("")) { //$NON-NLS-1$ 193 file = "/"; //$NON-NLS-1$ 194 } else if (file.startsWith("/")) { //$NON-NLS-1$ 195 canonicalize = true; 196 } 197 int last = file.lastIndexOf('/') + 1; 198 file = file.substring(0, last); 199 } 200 fileEnd = queryIdx; 201 } else 202 // Don't inherit query unless only the ref is changed 203 if (refIdx != 0) { 204 query = null; 205 } 206 207 if (fileIdx > -1) { 208 if (fileIdx < end && parseString.charAt(fileIdx) == '/') { 209 file = parseString.substring(fileIdx, fileEnd); 210 } else if (fileEnd > fileIdx) { 211 if (file == null) { 212 file = ""; //$NON-NLS-1$ 213 } else if (file.equals("")) { //$NON-NLS-1$ 214 file = "/"; //$NON-NLS-1$ 215 } else if (file.startsWith("/")) { //$NON-NLS-1$ 216 canonicalize = true; 217 } 218 int last = file.lastIndexOf('/') + 1; 219 if (last == 0) { 220 file = parseString.substring(fileIdx, fileEnd); 221 } else { 222 file = file.substring(0, last) 223 + parseString.substring(fileIdx, fileEnd); 224 } 225 } 226 } 227 if (file == null) { 228 file = ""; //$NON-NLS-1$ 229 } 230 231 if (host == null) { 232 host = ""; //$NON-NLS-1$ 233 } 234 235 if (canonicalize) { 236 // modify file if there's any relative referencing 237 file = URLUtil.canonicalizePath(file); 238 } 239 240 setURL(u, u.getProtocol(), host, port, authority, userInfo, file, 241 query, ref); 242 } 243 244 /** 245 * Sets the fields of the URL {@code u} to the values of the supplied 246 * arguments. 247 * 248 * @param u 249 * the non-null URL object to be set. 250 * @param protocol 251 * the protocol. 252 * @param host 253 * the host name. 254 * @param port 255 * the port number. 256 * @param file 257 * the file component. 258 * @param ref 259 * the reference. 260 * @deprecated use setURL(URL, String String, int, String, String, String, 261 * String, String) instead. 262 * @since Android 1.0 263 */ 264 @Deprecated 265 protected void setURL(URL u, String protocol, String host, int port, 266 String file, String ref) { 267 if (this != u.strmHandler) { 268 throw new SecurityException(); 269 } 270 u.set(protocol, host, port, file, ref); 271 } 272 273 /** 274 * Sets the fields of the URL {@code u} to the values of the supplied 275 * arguments. 276 * 277 * @param u 278 * the non-null URL object to be set. 279 * @param protocol 280 * the protocol. 281 * @param host 282 * the host name. 283 * @param port 284 * the port number. 285 * @param authority 286 * the authority. 287 * @param userInfo 288 * the user info. 289 * @param file 290 * the file component. 291 * @param query 292 * the query. 293 * @param ref 294 * the reference. 295 * @since Android 1.0 296 */ 297 protected void setURL(URL u, String protocol, String host, int port, 298 String authority, String userInfo, String file, String query, 299 String ref) { 300 if (this != u.strmHandler) { 301 throw new SecurityException(); 302 } 303 u.set(protocol, host, port, authority, userInfo, file, query, ref); 304 } 305 306 /** 307 * Returns the clear text representation of a given URL using HTTP format. 308 * 309 * @param url 310 * the URL object to be converted. 311 * @return the clear text representation of the specified URL. 312 * @see #parseURL 313 * @see URL#toExternalForm() 314 * @since Android 1.0 315 */ 316 protected String toExternalForm(URL url) { 317 StringBuffer answer = new StringBuffer(url.getProtocol().length() 318 + url.getFile().length() + 16); 319 answer.append(url.getProtocol()); 320 answer.append(':'); 321 String authority = url.getAuthority(); 322 if (authority != null && authority.length() > 0) { 323 answer.append("//"); //$NON-NLS-1$ 324 answer.append(url.getAuthority()); 325 } 326 327 String file = url.getFile(); 328 String ref = url.getRef(); 329 // file is never null 330 answer.append(file); 331 if (ref != null) { 332 answer.append('#'); 333 answer.append(ref); 334 } 335 return answer.toString(); 336 } 337 338 /** 339 * Compares two URL objects whether they represent the same URL. Two URLs 340 * are equal if they have the same file, host, port, protocol, query, and 341 * reference components. 342 * 343 * @param url1 344 * the first URL to compare. 345 * @param url2 346 * the second URL to compare. 347 * @return {@code true} if the URLs are the same, {@code false} otherwise. 348 * @see #hashCode 349 * @since Android 1.0 350 */ 351 protected boolean equals(URL url1, URL url2) { 352 if (!sameFile(url1, url2)) { 353 return false; 354 } 355 String s1 = url1.getRef(), s2 = url2.getRef(); 356 if (s1 != s2 && (s1 == null || !s1.equals(s2))) { 357 return false; 358 } 359 s1 = url1.getQuery(); 360 s2 = url2.getQuery(); 361 return s1 == s2 || (s1 != null && s1.equals(s2)); 362 } 363 364 /** 365 * Returns the default port of the protocol used by the handled URL. The 366 * current implementation returns always {@code -1}. 367 * 368 * @return the appropriate default port number of the protocol. 369 * @since Android 1.0 370 */ 371 protected int getDefaultPort() { 372 return -1; 373 } 374 375 /** 376 * Returns the host address of the given URL. 377 * 378 * @param url 379 * the URL object where to read the host address from. 380 * @return the host address of the specified URL. 381 * @since Android 1.0 382 */ 383 protected InetAddress getHostAddress(URL url) { 384 try { 385 String host = url.getHost(); 386 if (host == null || host.length() == 0) { 387 return null; 388 } 389 return InetAddress.getByName(host); 390 } catch (UnknownHostException e) { 391 return null; 392 } 393 } 394 395 /** 396 * Returns the hashcode value for the given URL object. 397 * 398 * @param url 399 * the URL to determine the hashcode. 400 * @return the hashcode of the given URL. 401 * @since Android 1.0 402 */ 403 protected int hashCode(URL url) { 404 return toExternalForm(url).hashCode(); 405 } 406 407 /** 408 * Compares two URL objects whether they refer to the same host. 409 * 410 * @param url1 411 * the first URL to be compared. 412 * @param url2 413 * the second URL to be compared. 414 * @return {@code true} if both URLs refer to the same host, {@code false} 415 * otherwise. 416 * @since Android 1.0 417 */ 418 protected boolean hostsEqual(URL url1, URL url2) { 419 String host1 = getHost(url1), host2 = getHost(url2); 420 if (host1 == host2 || (host1 != null && host1.equalsIgnoreCase(host2))) { 421 return true; 422 } 423 // Compare host address if the host name is not equal. 424 InetAddress address1 = getHostAddress(url1); 425 InetAddress address2 = getHostAddress(url2); 426 if (address1 != null && address1.equals(address2)) { 427 return true; 428 } 429 return false; 430 } 431 432 /** 433 * Compares two URL objects whether they refer to the same file. In the 434 * comparison included are the URL components protocol, host, port and file. 435 * 436 * @param url1 437 * the first URL to be compared. 438 * @param url2 439 * the second URL to be compared. 440 * @return {@code true} if both URLs refer to the same file, {@code false} 441 * otherwise. 442 * @since Android 1.0 443 */ 444 protected boolean sameFile(URL url1, URL url2) { 445 String s1 = url1.getProtocol(); 446 String s2 = url2.getProtocol(); 447 if (s1 != s2 && (s1 == null || !s1.equals(s2))) { 448 return false; 449 } 450 451 s1 = url1.getFile(); 452 s2 = url2.getFile(); 453 if (s1 != s2 && (s1 == null || !s1.equals(s2))) { 454 return false; 455 } 456 if (!hostsEqual(url1, url2)) { 457 return false; 458 } 459 int p1 = url1.getPort(); 460 if (p1 == -1) { 461 p1 = getDefaultPort(); 462 } 463 int p2 = url2.getPort(); 464 if (p2 == -1) { 465 p2 = getDefaultPort(); 466 } 467 return p1 == p2; 468 } 469 470 /* 471 * If the URL host is empty while protocal is file, the host is regarded as 472 * localhost. 473 */ 474 private static String getHost(URL url) { 475 String host = url.getHost(); 476 if ("file".equals(url.getProtocol()) //$NON-NLS-1$ 477 && "".equals(host)) { //$NON-NLS-1$ 478 host = "localhost"; //$NON-NLS-1$ 479 } 480 return host; 481 } 482} 483