Frames | No Frames |
1: /* URLStreamHandler.java -- Abstract superclass for all protocol handlers 2: Copyright (C) 1998, 1999, 2002, 2003, 2004 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package java.net; 39: 40: import gnu.java.lang.CPStringBuilder; 41: 42: import java.io.File; 43: import java.io.IOException; 44: 45: 46: /* 47: * Written using on-line Java Platform 1.2 API Specification, as well 48: * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998). 49: * Status: Believed complete and correct. 50: */ 51: 52: /** 53: * This class is the superclass of all URL protocol handlers. The URL 54: * class loads the appropriate protocol handler to establish a connection 55: * to a (possibly) remote service (eg, "http", "ftp") and to do protocol 56: * specific parsing of URL's. Refer to the URL class documentation for 57: * details on how that class locates and loads protocol handlers. 58: * <p> 59: * A protocol handler implementation should override the openConnection() 60: * method, and optionally override the parseURL() and toExternalForm() 61: * methods if necessary. (The default implementations will parse/write all 62: * URL's in the same form as http URL's). A protocol specific subclass 63: * of URLConnection will most likely need to be created as well. 64: * <p> 65: * Note that the instance methods in this class are called as if they 66: * were static methods. That is, a URL object to act on is passed with 67: * every call rather than the caller assuming the URL is stored in an 68: * instance variable of the "this" object. 69: * <p> 70: * The methods in this class are protected and accessible only to subclasses. 71: * URLStreamConnection objects are intended for use by the URL class only, 72: * not by other classes (unless those classes are implementing protocols). 73: * 74: * @author Aaron M. Renn (arenn@urbanophile.com) 75: * @author Warren Levy (warrenl@cygnus.com) 76: * 77: * @see URL 78: */ 79: public abstract class URLStreamHandler 80: { 81: /** 82: * Creates a URLStreamHander 83: */ 84: public URLStreamHandler() 85: { 86: } 87: 88: /** 89: * Returns a URLConnection for the passed in URL. Note that this should 90: * not actually create the connection to the (possibly) remote host, but 91: * rather simply return a URLConnection object. The connect() method of 92: * URL connection is used to establish the actual connection, possibly 93: * after the caller sets up various connection options. 94: * 95: * @param url The URL to get a connection object for 96: * 97: * @return A URLConnection object for the given URL 98: * 99: * @exception IOException If an error occurs 100: */ 101: protected abstract URLConnection openConnection(URL url) 102: throws IOException; 103: 104: /** 105: * This method parses the string passed in as a URL and set's the 106: * instance data fields in the URL object passed in to the various values 107: * parsed out of the string. The start parameter is the position to start 108: * scanning the string. This is usually the position after the ":" which 109: * terminates the protocol name. The end parameter is the position to 110: * stop scanning. This will be either the end of the String, or the 111: * position of the "#" character, which separates the "file" portion of 112: * the URL from the "anchor" portion. 113: * <p> 114: * This method assumes URL's are formatted like http protocol URL's, so 115: * subclasses that implement protocols with URL's the follow a different 116: * syntax should override this method. The lone exception is that if 117: * the protocol name set in the URL is "file", this method will accept 118: * an empty hostname (i.e., "file:///"), which is legal for that protocol 119: * 120: * @param url The URL object in which to store the results 121: * @param spec The String-ized URL to parse 122: * @param start The position in the string to start scanning from 123: * @param end The position in the string to stop scanning 124: */ 125: protected void parseURL(URL url, String spec, int start, int end) 126: { 127: String host = url.getHost(); 128: int port = url.getPort(); 129: String file = url.getFile(); 130: String ref = url.getRef(); 131: String userInfo = url.getUserInfo(); 132: String authority = url.getAuthority(); 133: String query = null; 134: 135: // On Windows we need to change \ to / for file URLs 136: char separator = File.separatorChar; 137: if (url.getProtocol().equals("file") && separator != '/') 138: { 139: file = file.replace(separator, '/'); 140: spec = spec.replace(separator, '/'); 141: } 142: 143: if (spec.regionMatches(start, "//", 0, 2)) 144: { 145: String genuineHost; 146: int hostEnd; 147: int colon; 148: int at_host; 149: 150: start += 2; 151: int slash = spec.indexOf('/', start); 152: if (slash >= 0) 153: hostEnd = slash; 154: else 155: hostEnd = end; 156: 157: authority = host = spec.substring(start, hostEnd); 158: 159: // We first need a genuine host name (with userinfo). 160: // So we check for '@': if it's present check the port in the 161: // section after '@' in the other case check it in the full string. 162: // P.S.: We don't care having '@' at the beginning of the string. 163: if ((at_host = host.indexOf('@')) >= 0) 164: { 165: genuineHost = host.substring(at_host); 166: userInfo = host.substring(0, at_host); 167: } 168: else 169: genuineHost = host; 170: 171: // Look for optional port number. It is valid for the non-port 172: // part of the host name to be null (e.g. a URL "http://:80"). 173: // TBD: JDK 1.2 in this case sets host to null rather than ""; 174: // this is undocumented and likely an unintended side effect in 1.2 175: // so we'll be simple here and stick with "". Note that 176: // "http://" or "http:///" produce a "" host in JDK 1.2. 177: if ((colon = genuineHost.indexOf(':')) >= 0) 178: { 179: try 180: { 181: port = Integer.parseInt(genuineHost.substring(colon + 1)); 182: } 183: catch (NumberFormatException e) 184: { 185: // Ignore invalid port values; port is already set to u's 186: // port. 187: } 188: 189: // Now we must cut the port number in the original string. 190: if (at_host >= 0) 191: host = host.substring(0, at_host + colon); 192: else 193: host = host.substring(0, colon); 194: } 195: file = null; 196: start = hostEnd; 197: } 198: else if (host == null) 199: host = ""; 200: 201: if (file == null || file.length() == 0 202: || (start < end && spec.charAt(start) == '/')) 203: { 204: // No file context available; just spec for file. 205: // Or this is an absolute path name; ignore any file context. 206: file = spec.substring(start, end); 207: ref = null; 208: } 209: else if (start < end) 210: { 211: // Context is available, but only override it if there is a new file. 212: int lastSlash = file.lastIndexOf('/'); 213: if (lastSlash < 0) 214: file = spec.substring(start, end); 215: else 216: file = (file.substring(0, lastSlash) 217: + '/' + spec.substring(start, end)); 218: 219: // For URLs constructed relative to a context, we 220: // need to canonicalise the file path. 221: file = canonicalizeFilename(file); 222: 223: ref = null; 224: } 225: 226: if (ref == null) 227: { 228: // Normally there should be no '#' in the file part, 229: // but we are nice. 230: int hash = file.indexOf('#'); 231: if (hash != -1) 232: { 233: ref = file.substring(hash + 1, file.length()); 234: file = file.substring(0, hash); 235: } 236: } 237: 238: // We care about the query tag only if there is no reference at all. 239: if (ref == null) 240: { 241: int queryTag = file.indexOf('?'); 242: if (queryTag != -1) 243: { 244: query = file.substring(queryTag + 1); 245: file = file.substring(0, queryTag); 246: } 247: } 248: 249: // XXX - Classpath used to call PlatformHelper.toCanonicalForm() on 250: // the file part. It seems like overhead, but supposedly there is some 251: // benefit in windows based systems (it also lowercased the string). 252: setURL(url, url.getProtocol(), host, port, authority, userInfo, file, query, ref); 253: } 254: 255: /* 256: * Canonicalize a filename. 257: */ 258: private static String canonicalizeFilename(String file) 259: { 260: // XXX - GNU Classpath has an implementation that might be more appropriate 261: // for Windows based systems (gnu.java.io.PlatformHelper.toCanonicalForm) 262: int index; 263: 264: // Replace "/./" with "/". This probably isn't very efficient in 265: // the general case, but it's probably not bad most of the time. 266: while ((index = file.indexOf("/./")) >= 0) 267: file = file.substring(0, index) + file.substring(index + 2); 268: 269: // Process "/../" correctly. This probably isn't very efficient in 270: // the general case, but it's probably not bad most of the time. 271: while ((index = file.indexOf("/../")) >= 0) 272: { 273: // Strip of the previous directory - if it exists. 274: int previous = file.lastIndexOf('/', index - 1); 275: if (previous >= 0) 276: file = file.substring(0, previous) + file.substring(index + 3); 277: else 278: break; 279: } 280: return file; 281: } 282: 283: /** 284: * Compares two URLs, excluding the fragment component 285: * 286: * @param url1 The first url 287: * @param url2 The second url to compare with the first 288: * 289: * @return True if both URLs point to the same file, false otherwise. 290: * 291: * @specnote Now protected 292: */ 293: protected boolean sameFile(URL url1, URL url2) 294: { 295: if (url1 == url2) 296: return true; 297: 298: // This comparison is very conservative. It assumes that any 299: // field can be null. 300: if (url1 == null || url2 == null) 301: return false; 302: int p1 = url1.getPort(); 303: if (p1 == -1) 304: p1 = url1.ph.getDefaultPort(); 305: int p2 = url2.getPort(); 306: if (p2 == -1) 307: p2 = url2.ph.getDefaultPort(); 308: if (p1 != p2) 309: return false; 310: String s1; 311: String s2; 312: s1 = url1.getProtocol(); 313: s2 = url2.getProtocol(); 314: if (s1 != s2 && (s1 == null || ! s1.equals(s2))) 315: return false; 316: s1 = url1.getHost(); 317: s2 = url2.getHost(); 318: if (s1 != s2 && (s1 == null || ! s1.equals(s2))) 319: return false; 320: s1 = canonicalizeFilename(url1.getFile()); 321: s2 = canonicalizeFilename(url2.getFile()); 322: if (s1 != s2 && (s1 == null || ! s1.equals(s2))) 323: return false; 324: return true; 325: } 326: 327: /** 328: * This methods sets the instance variables representing the various fields 329: * of the URL to the values passed in. 330: * 331: * @param u The URL to modify 332: * @param protocol The protocol to set 333: * @param host The host name to et 334: * @param port The port number to set 335: * @param file The filename to set 336: * @param ref The reference 337: * 338: * @exception SecurityException If the protocol handler of the URL is 339: * different from this one 340: * 341: * @deprecated 1.2 Please use 342: * #setURL(URL,String,String,int,String,String,String,String); 343: */ 344: protected void setURL(URL u, String protocol, String host, int port, 345: String file, String ref) 346: { 347: u.set(protocol, host, port, file, ref); 348: } 349: 350: /** 351: * Sets the fields of the URL argument to the indicated values 352: * 353: * @param u The URL to modify 354: * @param protocol The protocol to set 355: * @param host The host name to set 356: * @param port The port number to set 357: * @param authority The authority to set 358: * @param userInfo The user information to set 359: * @param path The path/filename to set 360: * @param query The query part to set 361: * @param ref The reference 362: * 363: * @exception SecurityException If the protocol handler of the URL is 364: * different from this one 365: */ 366: protected void setURL(URL u, String protocol, String host, int port, 367: String authority, String userInfo, String path, 368: String query, String ref) 369: { 370: u.set(protocol, host, port, authority, userInfo, path, query, ref); 371: } 372: 373: /** 374: * This is the default method for computing whether two URLs are 375: * equivalent. This method assumes that neither URL is null. 376: * 377: * @param url1 An URL object 378: * @param url2 Another URL object 379: * 380: * @return True if both given URLs are equal, false otherwise. 381: */ 382: protected boolean equals(URL url1, URL url2) 383: { 384: // This comparison is very conservative. It assumes that any 385: // field can be null. 386: int port1 = url1.getPort(); 387: if (port1 == -1) 388: port1 = url1.getDefaultPort(); 389: int port2 = url2.getPort(); 390: if (port2 == -1) 391: port2 = url2.getDefaultPort(); 392: // Note that we don't bother checking the 'authority'; it is 393: // redundant. 394: return (port1 == port2 395: && ((url1.getProtocol() == null && url2.getProtocol() == null) 396: || (url1.getProtocol() != null 397: && url1.getProtocol().equals(url2.getProtocol()))) 398: && ((url1.getUserInfo() == null && url2.getUserInfo() == null) 399: || (url1.getUserInfo() != null 400: && url1.getUserInfo().equals(url2.getUserInfo()))) 401: && ((url1.getHost() == null && url2.getHost() == null) 402: || (url1.getHost() != null && url1.getHost().equals(url2.getHost()))) 403: && ((url1.getPath() == null && url2.getPath() == null) 404: || (url1.getPath() != null && url1.getPath().equals(url2.getPath()))) 405: && ((url1.getQuery() == null && url2.getQuery() == null) 406: || (url1.getQuery() != null 407: && url1.getQuery().equals(url2.getQuery()))) 408: && ((url1.getRef() == null && url2.getRef() == null) 409: || (url1.getRef() != null && url1.getRef().equals(url2.getRef())))); 410: } 411: 412: /** 413: * Compares the host components of two URLs. 414: * 415: * @param url1 The first URL. 416: * @param url2 The second URL. 417: * 418: * @return True if both URLs contain the same host. 419: */ 420: protected boolean hostsEqual(URL url1, URL url2) 421: { 422: InetAddress addr1 = getHostAddress(url1); 423: InetAddress addr2 = getHostAddress(url2); 424: 425: if (addr1 != null && addr2 != null) 426: return addr1.equals(addr2); 427: 428: String host1 = url1.getHost(); 429: String host2 = url2.getHost(); 430: 431: if (host1 != null && host2 != null) 432: return host1.equalsIgnoreCase(host2); 433: 434: return host1 == null && host2 == null; 435: } 436: 437: /** 438: * Get the IP address of our host. An empty host field or a DNS failure will 439: * result in a null return. 440: * 441: * @param url The URL to return the host address for. 442: * 443: * @return The address of the hostname in url. 444: */ 445: protected InetAddress getHostAddress(URL url) 446: { 447: String hostname = url.getHost(); 448: 449: if (hostname.equals("")) 450: return null; 451: 452: try 453: { 454: return InetAddress.getByName(hostname); 455: } 456: catch (UnknownHostException e) 457: { 458: return null; 459: } 460: } 461: 462: /** 463: * Returns the default port for a URL parsed by this handler. This method is 464: * meant to be overidden by handlers with default port numbers. 465: * 466: * @return The default port number. 467: */ 468: protected int getDefaultPort() 469: { 470: return -1; 471: } 472: 473: /** 474: * Provides the default hash calculation. May be overidden by handlers for 475: * other protocols that have different requirements for hashCode calculation. 476: * 477: * @param url The URL to calc the hashcode for. 478: * 479: * @return The hashcode for the given URL. 480: */ 481: protected int hashCode(URL url) 482: { 483: return url.getProtocol().hashCode() 484: + ((url.getHost() == null) ? 0 : url.getHost().hashCode()) 485: + url.getFile().hashCode() + url.getPort(); 486: } 487: 488: /** 489: * This method converts a URL object into a String. This method creates 490: * Strings in the mold of http URL's, so protocol handlers which use URL's 491: * that have a different syntax should override this method 492: * 493: * @param url The URL object to convert 494: * 495: * @return A string representation of the url 496: */ 497: protected String toExternalForm(URL url) 498: { 499: String protocol; 500: String file; 501: String ref; 502: String authority; 503: 504: protocol = url.getProtocol(); 505: authority = url.getAuthority(); 506: if (authority == null) 507: authority = ""; 508: 509: file = url.getFile(); 510: ref = url.getRef(); 511: 512: // Guess a reasonable size for the string buffer so we have to resize 513: // at most once. 514: int size = protocol.length() + authority.length() + file.length() + 24; 515: CPStringBuilder sb = new CPStringBuilder(size); 516: 517: if (protocol.length() > 0) 518: { 519: sb.append(protocol); 520: sb.append(":"); 521: } 522: 523: // If we have superfluous leading slashes (that means, at least 2) 524: // we always add the authority component ("//" + host) to 525: // avoid ambiguity. Otherwise we would generate an URL like 526: // proto://home/foo 527: // where we meant: 528: // host: <empty> - file: //home/foo 529: // but URL spec says it is: 530: // host: home - file: /foo 531: if (authority.length() != 0 || file.startsWith("//") ) 532: sb.append("//").append(authority).append(file); 533: else 534: sb.append(file); 535: 536: if (ref != null) 537: sb.append('#').append(ref); 538: 539: return sb.toString(); 540: } 541: }