URLStreamHandler.java 17.2 KB
Newer Older
1
/* URLStreamHandler.java -- Abstract superclass for all protocol handlers
2
   Copyright (C) 1998, 1999, 2002, 2003, 2004 Free Software Foundation, Inc.
3 4 5 6 7 8 9

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
10

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */
Tom Tromey committed
37 38 39

package java.net;

40
import java.io.File;
41 42
import java.io.IOException;

43

44
/*
Tom Tromey committed
45 46 47 48 49
 * 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.
 */

50 51 52 53 54 55 56 57 58 59
/**
 * This class is the superclass of all URL protocol handlers.  The URL
 * class loads the appropriate protocol handler to establish a connection
 * to a (possibly) remote service (eg, "http", "ftp") and to do protocol
 * specific parsing of URL's.  Refer to the URL class documentation for
 * details on how that class locates and loads protocol handlers.
 * <p>
 * A protocol handler implementation should override the openConnection()
 * method, and optionally override the parseURL() and toExternalForm()
 * methods if necessary. (The default implementations will parse/write all
60
 * URL's in the same form as http URL's).  A protocol  specific subclass
61 62 63 64 65 66 67 68 69 70 71 72 73
 * of URLConnection will most likely need to be created as well.
 * <p>
 * Note that the instance methods in this class are called as if they
 * were static methods.  That is, a URL object to act on is passed with
 * every call rather than the caller assuming the URL is stored in an
 * instance variable of the "this" object.
 * <p>
 * The methods in this class are protected and accessible only to subclasses.
 * URLStreamConnection objects are intended for use by the URL class only,
 * not by other classes (unless those classes are implementing protocols).
 *
 * @author Aaron M. Renn (arenn@urbanophile.com)
 * @author Warren Levy (warrenl@cygnus.com)
74
 *
75 76
 * @see URL
 */
Tom Tromey committed
77 78
public abstract class URLStreamHandler
{
79 80 81
  /**
   * Creates a URLStreamHander
   */
82
  public URLStreamHandler()
83 84 85 86
  {
  }

  /**
87 88 89 90 91 92 93 94 95
   * Returns a URLConnection for the passed in URL.  Note that this should
   * not actually create the connection to the (possibly) remote host, but
   * rather simply return a URLConnection object.  The connect() method of
   * URL connection is used to establish the actual connection, possibly
   * after the caller sets up various connection options.
   *
   * @param url The URL to get a connection object for
   *
   * @return A URLConnection object for the given URL
96 97 98
   *
   * @exception IOException If an error occurs
   */
99
  protected abstract URLConnection openConnection(URL url)
100 101 102
    throws IOException;

  /**
103 104 105 106 107 108 109 110 111
   * This method parses the string passed in as a URL and set's the
   * instance data fields in the URL object passed in to the various values
   * parsed out of the string.  The start parameter is the position to start
   * scanning the string.  This is usually the position after the ":" which
   * terminates the protocol name.  The end parameter is the position to
   * stop scanning.  This will be either the end of the String, or the
   * position of the "#" character, which separates the "file" portion of
   * the URL from the "anchor" portion.
   * <p>
112 113
   * This method assumes URL's are formatted like http protocol URL's, so
   * subclasses that implement protocols with URL's the follow a different
114 115
   * syntax should override this method.  The lone exception is that if
   * the protocol name set in the URL is "file", this method will accept
116
   * an empty hostname (i.e., "file:///"), which is legal for that protocol
117
   *
118 119 120 121
   * @param url The URL object in which to store the results
   * @param spec The String-ized URL to parse
   * @param start The position in the string to start scanning from
   * @param end The position in the string to stop scanning
122
   */
123
  protected void parseURL(URL url, String spec, int start, int end)
Tom Tromey committed
124
  {
125 126 127 128
    String host = url.getHost();
    int port = url.getPort();
    String file = url.getFile();
    String ref = url.getRef();
Michael Koch committed
129 130 131 132 133
    String userInfo = url.getUserInfo();
    String authority = url.getAuthority();
    String query = null;
    
    // On Windows we need to change \ to / for file URLs
134 135 136 137 138 139
    char separator = File.separatorChar;
    if (url.getProtocol().equals("file") && separator != '/')
      {
	file = file.replace(separator, '/');
	spec = spec.replace(separator, '/');
      }
140 141

    if (spec.regionMatches(start, "//", 0, 2))
Tom Tromey committed
142
      {
143
	String genuineHost;
Warren Levy committed
144
	int hostEnd;
145 146
	int colon;
	int at_host;
Tom Tromey committed
147

Warren Levy committed
148
	start += 2;
149 150
	int slash = spec.indexOf('/', start);
	if (slash >= 0)
Warren Levy committed
151
	  hostEnd = slash;
152
	else
153
	  hostEnd = end;
Warren Levy committed
154

Michael Koch committed
155
	authority = host = spec.substring(start, hostEnd);
156

157 158 159 160
	// We first need a genuine host name (with userinfo).
	// So we check for '@': if it's present check the port in the
	// section after '@' in the other case check it in the full string.
	// P.S.: We don't care having '@' at the beginning of the string.
161
	if ((at_host = host.indexOf('@')) >= 0)
Michael Koch committed
162 163 164 165
	  {
	    genuineHost = host.substring(at_host);
	    userInfo = host.substring(0, at_host);
	  }
166 167 168
	else
	  genuineHost = host;

Warren Levy committed
169 170 171 172 173 174
	// Look for optional port number.  It is valid for the non-port
	// part of the host name to be null (e.g. a URL "http://:80").
	// TBD: JDK 1.2 in this case sets host to null rather than "";
	// this is undocumented and likely an unintended side effect in 1.2
	// so we'll be simple here and stick with "". Note that
	// "http://" or "http:///" produce a "" host in JDK 1.2.
175
	if ((colon = genuineHost.indexOf(':')) >= 0)
Tom Tromey committed
176 177 178
	  {
	    try
	      {
179
		port = Integer.parseInt(genuineHost.substring(colon + 1));
Tom Tromey committed
180 181 182
	      }
	    catch (NumberFormatException e)
	      {
183 184
		// Ignore invalid port values; port is already set to u's
		// port.
Tom Tromey committed
185
	      }
186

187 188
	    // Now we must cut the port number in the original string.
	    if (at_host >= 0)
189
	      host = host.substring(0, at_host + colon);
190
	    else
191
	      host = host.substring(0, colon);
Tom Tromey committed
192
	  }
Warren Levy committed
193 194
	file = null;
	start = hostEnd;
195 196
      }
    else if (host == null)
Warren Levy committed
197 198
      host = "";

199
    if (file == null || file.length() == 0
200
        || (start < end && spec.charAt(start) == '/'))
Warren Levy committed
201 202
      {
	// No file context available; just spec for file.
203 204 205
	// Or this is an absolute path name; ignore any file context.
	file = spec.substring(start, end);
	ref = null;
206
      }
207
    else if (start < end)
Warren Levy committed
208
      {
209
	// Context is available, but only override it if there is a new file.
Michael Koch committed
210
	int lastSlash = file.lastIndexOf('/');
211 212 213 214 215
	if (lastSlash < 0)
	  file = spec.substring(start, end);
	else
	  file = (file.substring(0, lastSlash)
		  + '/' + spec.substring(start, end));
216

217 218 219
	// For URLs constructed relative to a context, we
	// need to canonicalise the file path.
	file = canonicalizeFilename(file);
220

221
	ref = null;
Tom Tromey committed
222 223
      }

224 225 226 227 228 229 230 231 232 233 234 235
    if (ref == null)
      {
	// Normally there should be no '#' in the file part,
	// but we are nice.
	int hash = file.indexOf('#');
	if (hash != -1)
	  {
	    ref = file.substring(hash + 1, file.length());
	    file = file.substring(0, hash);
	  }
      }

Michael Koch committed
236 237 238 239 240 241 242 243 244 245 246
    // We care about the query tag only if there is no reference at all.
    if (ref == null)
      {
	  int queryTag = file.indexOf('?');
	  if (queryTag != -1)
	    {
	      query = file.substring(queryTag + 1);
	      file = file.substring(0, queryTag);
	    }
      }

247 248 249
    // XXX - Classpath used to call PlatformHelper.toCanonicalForm() on
    // the file part. It seems like overhead, but supposedly there is some
    // benefit in windows based systems (it also lowercased the string).
Michael Koch committed
250
    setURL(url, url.getProtocol(), host, port, authority, userInfo, file, query, ref);
251
  }
252

253 254 255
  /*
   * Canonicalize a filename.
   */
256 257
  private static String canonicalizeFilename(String file)
  {
258 259
    // XXX - GNU Classpath has an implementation that might be more appropriate
    // for Windows based systems (gnu.java.io.PlatformHelper.toCanonicalForm)
Warren Levy committed
260
    int index;
Tom Tromey committed
261

Warren Levy committed
262 263 264 265 266 267 268 269 270 271 272 273 274 275
    // Replace "/./" with "/".  This probably isn't very efficient in
    // the general case, but it's probably not bad most of the time.
    while ((index = file.indexOf("/./")) >= 0)
      file = file.substring(0, index) + file.substring(index + 2);

    // Process "/../" correctly.  This probably isn't very efficient in
    // the general case, but it's probably not bad most of the time.
    while ((index = file.indexOf("/../")) >= 0)
      {
	// Strip of the previous directory - if it exists.
	int previous = file.lastIndexOf('/', index - 1);
	if (previous >= 0)
	  file = file.substring(0, previous) + file.substring(index + 3);
	else
276
	  break;
Warren Levy committed
277
      }
278
    return file;
Tom Tromey committed
279
  }
280

281 282 283 284 285
  /**
   * Compares two URLs, excluding the fragment component
   *
   * @param url1 The first url
   * @param url2 The second url to compare with the first
286
   *
287 288
   * @return True if both URLs point to the same file, false otherwise.
   *
289 290 291
   * @specnote Now protected
   */
  protected boolean sameFile(URL url1, URL url2)
292 293 294
  {
    if (url1 == url2)
      return true;
295

296 297
    // This comparison is very conservative.  It assumes that any
    // field can be null.
298 299
    if (url1 == null || url2 == null)
      return false;
300
    int p1 = url1.getPort();
301
    if (p1 == -1)
302 303
      p1 = url1.ph.getDefaultPort();
    int p2 = url2.getPort();
304
    if (p2 == -1)
305
      p2 = url2.ph.getDefaultPort();
306
    if (p1 != p2)
307
      return false;
308 309
    String s1;
    String s2;
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    s1 = url1.getProtocol();
    s2 = url2.getProtocol();
    if (s1 != s2 && (s1 == null || ! s1.equals(s2)))
      return false;
    s1 = url1.getHost();
    s2 = url2.getHost();
    if (s1 != s2 && (s1 == null || ! s1.equals(s2)))
      return false;
    s1 = canonicalizeFilename(url1.getFile());
    s2 = canonicalizeFilename(url2.getFile());
    if (s1 != s2 && (s1 == null || ! s1.equals(s2)))
      return false;
    return true;
  }

325
  /**
326 327
   * This methods sets the instance variables representing the various fields
   * of the URL to the values passed in.
328 329 330 331 332 333 334 335 336 337 338 339 340 341
   *
   * @param u The URL to modify
   * @param protocol The protocol to set
   * @param host The host name to et
   * @param port The port number to set
   * @param file The filename to set
   * @param ref The reference
   *
   * @exception SecurityException If the protocol handler of the URL is
   * different from this one
   *
   * @deprecated 1.2 Please use
   * #setURL(URL,String,String,int,String,String,String,String);
   */
Tom Tromey committed
342
  protected void setURL(URL u, String protocol, String host, int port,
343
                        String file, String ref)
Tom Tromey committed
344 345 346 347
  {
    u.set(protocol, host, port, file, ref);
  }

348
  /**
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
   * Sets the fields of the URL argument to the indicated values
   *
   * @param u The URL to modify
   * @param protocol The protocol to set
   * @param host The host name to set
   * @param port The port number to set
   * @param authority The authority to set
   * @param userInfo The user information to set
   * @param path The path/filename to set
   * @param query The query part to set
   * @param ref The reference
   *
   * @exception SecurityException If the protocol handler of the URL is
   * different from this one
   */
  protected void setURL(URL u, String protocol, String host, int port,
365 366
                        String authority, String userInfo, String path,
                        String query, String ref)
367 368 369 370 371
  {
    u.set(protocol, host, port, authority, userInfo, path, query, ref);
  }

  /**
372 373 374 375 376 377 378
   * Provides the default equals calculation. May be overidden by handlers for
   * other protocols that have different requirements for equals(). This method
   * requires that none of its arguments is null. This is guaranteed by the
   * fact that it is only called by java.net.URL class.
   *
   * @param url1 An URL object
   * @param url2 An 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 385
    // This comparison is very conservative.  It assumes that any
    // field can be null.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
    return (url1.getPort() == url2.getPort()
           && ((url1.getProtocol() == null && url2.getProtocol() == null)
           || (url1.getProtocol() != null
           && url1.getProtocol().equals(url2.getProtocol())))
           && ((url1.getUserInfo() == null && url2.getUserInfo() == null)
           || (url1.getUserInfo() != null
           && url1.getUserInfo().equals(url2.getUserInfo())))
           && ((url1.getAuthority() == null && url2.getAuthority() == null)
           || (url1.getAuthority() != null
           && url1.getAuthority().equals(url2.getAuthority())))
           && ((url1.getHost() == null && url2.getHost() == null)
           || (url1.getHost() != null && url1.getHost().equals(url2.getHost())))
           && ((url1.getPath() == null && url2.getPath() == null)
           || (url1.getPath() != null && url1.getPath().equals(url2.getPath())))
           && ((url1.getQuery() == null && url2.getQuery() == null)
           || (url1.getQuery() != null
           && url1.getQuery().equals(url2.getQuery())))
           && ((url1.getRef() == null && url2.getRef() == null)
           || (url1.getRef() != null && url1.getRef().equals(url2.getRef()))));
405 406 407 408 409
  }

  /**
   * Compares the host components of two URLs.
   *
410 411 412 413 414
   * @param url1 The first URL.
   * @param url2 The second URL.
   *
   * @return True if both URLs contain the same host.
   *
415 416
   * @exception UnknownHostException If an unknown host is found
   */
417
  protected boolean hostsEqual(URL url1, URL url2)
418
  {
419 420
    InetAddress addr1 = getHostAddress(url1);
    InetAddress addr2 = getHostAddress(url2);
421

422
    if (addr1 != null && addr2 != null)
423
      return addr1.equals(addr2);
424 425 426

    String host1 = url1.getHost();
    String host2 = url2.getHost();
427

428
    if (host1 != null && host2 != null)
429
      return host1.equalsIgnoreCase(host2);
430 431

    return host1 == null && host2 == null;
432 433 434
  }

  /**
435 436
   * Get the IP address of our host. An empty host field or a DNS failure will
   * result in a null return.
437 438 439 440
   *
   * @param url The URL to return the host address for.
   *
   * @return The address of the hostname in url.
441
   */
442
  protected InetAddress getHostAddress(URL url)
443
  {
444
    String hostname = url.getHost();
445

446
    if (hostname.equals(""))
447
      return null;
448

449 450
    try
      {
451
	return InetAddress.getByName(hostname);
452 453 454 455 456 457 458 459 460 461
      }
    catch (UnknownHostException e)
      {
	return null;
      }
  }

  /**
   * Returns the default port for a URL parsed by this handler. This method is
   * meant to be overidden by handlers with default port numbers.
462 463
   *
   * @return The default port number.
464
   */
465
  protected int getDefaultPort()
466 467 468 469 470
  {
    return -1;
  }

  /**
471 472
   * Provides the default hash calculation. May be overidden by handlers for
   * other protocols that have different requirements for hashCode calculation.
473 474
   *
   * @param url The URL to calc the hashcode for.
475
   *
476
   * @return The hashcode for the given URL.
477
   */
478
  protected int hashCode(URL url)
479
  {
480 481 482
    return url.getProtocol().hashCode()
           + ((url.getHost() == null) ? 0 : url.getHost().hashCode())
           + url.getFile().hashCode() + url.getPort();
483 484 485
  }

  /**
486 487 488
   * This method converts a URL object into a String.  This method creates
   * Strings in the mold of http URL's, so protocol handlers which use URL's
   * that have a different syntax should override this method
489
   *
490
   * @param url The URL object to convert
491 492
   *
   * @return A string representation of the url
493
   */
494
  protected String toExternalForm(URL url)
Tom Tromey committed
495
  {
496 497 498
    String protocol;
    String file;
    String ref;
Michael Koch committed
499
    String authority;
Tom Tromey committed
500

501
    protocol = url.getProtocol();
Michael Koch committed
502 503 504 505
    authority = url.getAuthority();
    if (authority == null)
      authority = "";
    
506 507
    file = url.getFile();
    ref = url.getRef();
Tom Tromey committed
508

509 510
    // Guess a reasonable size for the string buffer so we have to resize
    // at most once.
Michael Koch committed
511
    int size = protocol.length() + authority.length() + file.length() + 24;
512 513
    StringBuffer sb = new StringBuffer(size);

Michael Koch committed
514
    if (protocol != null && protocol.length() > 0)
515 516 517 518
      {
	sb.append(protocol);
	sb.append(":");
      }
Michael Koch committed
519 520
    
    if (authority.length() != 0)
521
      {
Michael Koch committed
522
	sb.append("//").append(authority);
523
      }
Tom Tromey committed
524

525
    sb.append(file);
Tom Tromey committed
526 527

    if (ref != null)
528
      sb.append('#').append(ref);
Tom Tromey committed
529

530
    return sb.toString();
Tom Tromey committed
531 532
  }
}