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