// URL.java - A Uniform Resource Locator. /* Copyright (C) 1999 Cygnus Solutions This file is part of libgcj. This software is copyrighted work licensed under the terms of the Libgcj License. Please consult the file "LIBGCJ_LICENSE" for details. */ package java.net; import java.io.*; import java.util.Hashtable; import java.util.StringTokenizer; /** * @author Warren Levy * @date March 4, 1999. */ /** * Written using on-line Java Platform 1.2 API Specification, as well * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998). * Status: Believed complete and correct. */ public final class URL implements Serializable { private String protocol; private String host; private int port = -1; // Initialize for constructor using context. private String file; private String ref; private URLStreamHandler handler; private static Hashtable handlers = new Hashtable(); private static URLStreamHandlerFactory factory; public URL(String protocol, String host, int port, String file) throws MalformedURLException { this(protocol, host, port, file, null); } public URL(String protocol, String host, String file) throws MalformedURLException { this(protocol, host, -1, file, null); } // JDK1.2 public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException { if (protocol == null) throw new MalformedURLException("null protocol"); this.protocol = protocol; if (handler != null) { // TODO12: Need SecurityManager.checkPermission and // TODO12: java.net.NetPermission from JDK 1.2 to be implemented. // Throw an exception if an extant security mgr precludes // specifying a StreamHandler. // // SecurityManager s = System.getSecurityManager(); // if (s != null) // s.checkPermission(NetPermission("specifyStreamHandler")); this.handler = handler; } else this.handler = setURLStreamHandler(protocol); if (this.handler == null) throw new MalformedURLException("Handler for protocol not found"); this.host = host; this.port = port; int hashAt = file.indexOf('#'); if (hashAt < 0) { this.file = file; this.ref = null; } else { this.file = file.substring(0, hashAt); this.ref = file.substring(hashAt + 1); } } public URL(String spec) throws MalformedURLException { this((URL) null, spec, (URLStreamHandler) null); } public URL(URL context, String spec) throws MalformedURLException { this(context, spec, (URLStreamHandler) null); } // JDK1.2 public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException { /* A protocol is defined by the doc as the substring before a ':' * as long as the ':' occurs before any '/'. * * If context is null, then spec must be an absolute URL. * * The relative URL need not specify all the components of a URL. * If the protocol, host name, or port number is missing, the value * is inherited from the context. A bare file component is appended * to the context's file. The optional anchor is not inherited. */ // If this is an absolute URL, then ignore context completely. // An absolute URL must have chars prior to "://" but cannot have a colon // right after the "://". The second colon is for an optional port value // and implies that the host from the context is used if available. int colon; if ((colon = spec.indexOf("://", 1)) > 0 && ! spec.regionMatches(colon, "://:", 0, 4)) context = null; int slash; if ((colon = spec.indexOf(':')) > 0 && (colon < (slash = spec.indexOf('/')) || slash < 0)) { // Protocol specified in spec string. protocol = spec.substring(0, colon); if (context != null && context.protocol.equals(protocol)) { // The 1.2 doc specifically says these are copied to the new URL. host = context.host; port = context.port; file = context.file; } } else if (context != null) { // Protocol NOT specified in spec string. // Use context fields (except ref) as a foundation for relative URLs. colon = -1; protocol = context.protocol; host = context.host; port = context.port; file = context.file; } else // Protocol NOT specified in spec. and no context available. throw new MalformedURLException("Absolute URL required with null context"); if (handler != null) { // TODO12: Need SecurityManager.checkPermission and // TODO12: java.net.NetPermission from JDK 1.2 to be implemented. // Throw an exception if an extant security mgr precludes // specifying a StreamHandler. // // SecurityManager s = System.getSecurityManager(); // if (s != null) // s.checkPermission(NetPermission("specifyStreamHandler")); this.handler = handler; } else this.handler = setURLStreamHandler(protocol); if (this.handler == null) throw new MalformedURLException("Handler for protocol not found"); // JDK 1.2 doc for parseURL specifically states that any '#' ref // is to be excluded by passing the 'limit' as the indexOf the '#' // if one exists, otherwise pass the end of the string. int hashAt = spec.indexOf('#', colon + 1); this.handler.parseURL(this, spec, colon + 1, hashAt < 0 ? spec.length() : hashAt); if (hashAt >= 0) ref = spec.substring(hashAt + 1); } public boolean equals(Object obj) { if (obj == null || ! (obj instanceof URL)) return false; URL uObj = (URL) obj; if (protocol != uObj.protocol || host != uObj.host || port != uObj.port || file != uObj.file || ref != uObj.ref) return false; return true; } public final Object getContent() throws IOException { return openConnection().getContent(); } public String getFile() { return file; } public String getHost() { return host; } public int getPort() { return port; } public String getProtocol() { return protocol; } public String getRef() { return ref; } public int hashCode() { // JCL book says this is computed using (only) the hashcodes of the // protocol, host and file fields. Empirical evidence indicates this // is probably XOR in JDK 1.1. In JDK 1.2 it seems to be a sum including // the port. // // JDK 1.2 online doc infers that host could be null because it // explicitly states that file cannot be null but is silent on host. // A simple example with protocol "http" (hashcode 3213448), host null, // file "/" (hashcode 47) produced a hashcode (3213494) which appeared // to be the sum of the two hashcodes plus the port. Another example // using "/index.html" for file bore this out; as well as "#" for file // (which was reduced to "" with a hashcode of zero). A "" host also // causes the port number and the two hashcodes to be summed. return (protocol.hashCode() + ((host == null) ? 0 : host.hashCode()) + port + file.hashCode()); } public URLConnection openConnection() throws IOException { return handler.openConnection(this); } public final InputStream openStream() throws IOException { return openConnection().getInputStream(); } public boolean sameFile(URL other) { if (other == null || protocol != other.protocol || host != other.host || port != other.port || file != other.file) return false; return true; } protected void set(String protocol, String host, int port, String file, String ref) { // TBD: Theoretically, a poorly written StreamHandler could pass an // invalid protocol. It will cause the handler to be set to null // thus overriding a valid handler. Callers of this method should // be aware of this. this.handler = setURLStreamHandler(protocol); this.protocol = protocol; this.port = port; this.host = host; this.file = file; this.ref = ref; } public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) { if (factory != null) throw new Error("URLStreamHandlerFactory already set"); // Throw an exception if an extant security mgr precludes // setting the factory. SecurityManager s = System.getSecurityManager(); if (s != null) s.checkSetFactory(); factory = fac; } public String toExternalForm() { // Identical to toString(). return handler.toExternalForm(this); } public String toString() { // Identical to toExternalForm(). return handler.toExternalForm(this); } private URLStreamHandler setURLStreamHandler(String protocol) { URLStreamHandler handler; // See if a handler has been cached for this protocol. if ((handler = (URLStreamHandler) handlers.get(protocol)) != null) return handler; // If a non-default factory has been set, use it to find the protocol. if (factory != null) handler = factory.createURLStreamHandler(protocol); // Non-default factory may have returned null or a factory wasn't set. // Use the default search algorithm to find a handler for this protocol. if (handler == null) { // Get the list of packages to check and append our default handler // to it, along with the JDK specified default as a last resort. // Except in very unusual environments the JDK specified one shouldn't // ever be needed (or available). String propVal = System.getProperty("java.protocol.handler.pkgs"); propVal = (propVal == null) ? "" : (propVal + "|"); propVal = propVal + "gnu.gcj.protocol|sun.net.www.protocol"; StringTokenizer pkgPrefix = new StringTokenizer(propVal, "|"); do { String facName = pkgPrefix.nextToken() + "." + protocol + ".Handler"; try { handler = (URLStreamHandler) Class.forName(facName).newInstance(); } catch (Exception e) { // Can't instantiate; handler still null, go on to next element. } } while ((handler == null || ! (handler instanceof URLStreamHandler)) && pkgPrefix.hasMoreTokens()); } // Update the hashtable with the new protocol handler. if (handler != null) if (handler instanceof URLStreamHandler) handlers.put(protocol, handler); else handler = null; return handler; } }