1. /*
  2. * @(#)URLStreamHandler.java 1.61 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.net;
  8. import java.io.IOException;
  9. import java.io.InputStream;
  10. import java.io.File;
  11. import java.io.OutputStream;
  12. import java.util.Hashtable;
  13. import sun.net.www.ParseUtil;
  14. /**
  15. * The abstract class <code>URLStreamHandler</code> is the common
  16. * superclass for all stream protocol handlers. A stream protocol
  17. * handler knows how to make a connection for a particular protocol
  18. * type, such as <code>http</code>, <code>ftp</code>, or
  19. * <code>gopher</code>.
  20. * <p>
  21. * In most cases, an instance of a <code>URLStreamHandler</code>
  22. * subclass is not created directly by an application. Rather, the
  23. * first time a protocol name is encountered when constructing a
  24. * <code>URL</code>, the appropriate stream protocol handler is
  25. * automatically loaded.
  26. *
  27. * @author James Gosling
  28. * @version 1.61, 01/23/03
  29. * @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
  30. * @since JDK1.0
  31. */
  32. public abstract class URLStreamHandler {
  33. /**
  34. * Opens a connection to the object referenced by the
  35. * <code>URL</code> argument.
  36. * This method should be overridden by a subclass.
  37. *
  38. * <p>If for the handler's protocol (such as HTTP or JAR), there
  39. * exists a public, specialized URLConnection subclass belonging
  40. * to one of the following packages or one of their subpackages:
  41. * java.lang, java.io, java.util, java.net, the connection
  42. * returned will be of that subclass. For example, for HTTP an
  43. * HttpURLConnection will be returned, and for JAR a
  44. * JarURLConnection will be returned.
  45. *
  46. * @param u the URL that this connects to.
  47. * @return a <code>URLConnection</code> object for the <code>URL</code>.
  48. * @exception IOException if an I/O error occurs while opening the
  49. * connection.
  50. */
  51. abstract protected URLConnection openConnection(URL u) throws IOException;
  52. /**
  53. * Parses the string representation of a <code>URL</code> into a
  54. * <code>URL</code> object.
  55. * <p>
  56. * If there is any inherited context, then it has already been
  57. * copied into the <code>URL</code> argument.
  58. * <p>
  59. * The <code>parseURL</code> method of <code>URLStreamHandler</code>
  60. * parses the string representation as if it were an
  61. * <code>http</code> specification. Most URL protocol families have a
  62. * similar parsing. A stream protocol handler for a protocol that has
  63. * a different syntax must override this routine.
  64. *
  65. * @param u the <code>URL</code> to receive the result of parsing
  66. * the spec.
  67. * @param spec the <code>String</code> representing the URL that
  68. * must be parsed.
  69. * @param start the character index at which to begin parsing. This is
  70. * just past the '<code>:</code>' (if there is one) that
  71. * specifies the determination of the protocol name.
  72. * @param limit the character position to stop parsing at. This is the
  73. * end of the string or the position of the
  74. * "<code>#</code>" character, if present. All information
  75. * after the sharp sign indicates an anchor.
  76. */
  77. protected void parseURL(URL u, String spec, int start, int limit) {
  78. // These fields may receive context content if this was relative URL
  79. String protocol = u.getProtocol();
  80. String authority = u.getAuthority();
  81. String userInfo = u.getUserInfo();
  82. String host = u.getHost();
  83. int port = u.getPort();
  84. String path = u.getPath();
  85. String query = u.getQuery();
  86. // This field has already been parsed
  87. String ref = u.getRef();
  88. boolean isRelPath = false;
  89. boolean queryOnly = false;
  90. // FIX: should not assume query if opaque
  91. // Strip off the query part
  92. if (start < limit) {
  93. int queryStart = spec.indexOf('?');
  94. queryOnly = queryStart == start;
  95. if ((queryStart != -1) && (queryStart < limit)) {
  96. query = spec.substring(queryStart+1, limit);
  97. if (limit > queryStart)
  98. limit = queryStart;
  99. spec = spec.substring(0, queryStart);
  100. }
  101. }
  102. int i = 0;
  103. // Parse the authority part if any
  104. if ((start <= limit - 2) && (spec.charAt(start) == '/') &&
  105. (spec.charAt(start + 1) == '/')) {
  106. start += 2;
  107. i = spec.indexOf('/', start);
  108. if (i < 0) {
  109. i = spec.indexOf('?', start);
  110. if (i < 0)
  111. i = limit;
  112. }
  113. host = authority = spec.substring(start, i);
  114. int ind = authority.indexOf('@');
  115. if (ind != -1) {
  116. userInfo = authority.substring(0, ind);
  117. host = authority.substring(ind+1);
  118. } else {
  119. userInfo = null;
  120. }
  121. if (host != null) {
  122. // If the host is surrounded by [ and ] then its an IPv6
  123. // literal address as specified in RFC2732
  124. if (host.length()>0 && (host.charAt(0) == '[')) {
  125. if ((ind = host.indexOf(']')) > 2) {
  126. String nhost = host ;
  127. host = nhost.substring(0,ind+1);
  128. if (Inet6Address.
  129. textToNumericFormat(host.substring(1, ind)) == null) {
  130. throw new IllegalArgumentException(
  131. "Invalid host: "+ host);
  132. }
  133. port = -1 ;
  134. if (nhost.length() > ind+1) {
  135. if (nhost.charAt(ind+1) == ':') {
  136. ++ind ;
  137. // port can be null according to RFC2396
  138. if (nhost.length() > (ind + 1)) {
  139. port = Integer.parseInt(nhost.substring(ind+1));
  140. }
  141. } else {
  142. throw new IllegalArgumentException(
  143. "Invalid authority field: " + authority);
  144. }
  145. }
  146. } else {
  147. throw new IllegalArgumentException(
  148. "Invalid authority field: " + authority);
  149. }
  150. } else {
  151. ind = host.indexOf(':');
  152. port = -1;
  153. if (ind >= 0) {
  154. // port can be null according to RFC2396
  155. if (host.length() > (ind + 1)) {
  156. port = Integer.parseInt(host.substring(ind + 1));
  157. }
  158. host = host.substring(0, ind);
  159. }
  160. }
  161. } else {
  162. host = "";
  163. }
  164. if (port < -1)
  165. throw new IllegalArgumentException("Invalid port number :" +
  166. port);
  167. start = i;
  168. // If the authority is defined then the path is defined by the
  169. // spec only; See RFC 2396 Section 5.2.4.
  170. if (authority != null && authority.length() > 0)
  171. path = "";
  172. }
  173. if (host == null) {
  174. host = "";
  175. }
  176. // Parse the file path if any
  177. if (start < limit) {
  178. if (spec.charAt(start) == '/') {
  179. path = spec.substring(start, limit);
  180. } else if (path != null && path.length() > 0) {
  181. isRelPath = true;
  182. int ind = path.lastIndexOf('/');
  183. String seperator = "";
  184. if (ind == -1 && authority != null)
  185. seperator = "/";
  186. path = path.substring(0, ind + 1) + seperator +
  187. spec.substring(start, limit);
  188. } else {
  189. String seperator = (authority != null) ? "/" : "";
  190. path = seperator + spec.substring(start, limit);
  191. }
  192. } else if (queryOnly && path != null) {
  193. int ind = path.lastIndexOf('/');
  194. if (ind < 0)
  195. ind = 0;
  196. path = path.substring(0, ind) + "/";
  197. }
  198. if (path == null)
  199. path = "";
  200. if (isRelPath) {
  201. // Remove embedded /./
  202. while ((i = path.indexOf("/./")) >= 0) {
  203. path = path.substring(0, i) + path.substring(i + 2);
  204. }
  205. // Remove embedded /../ if possible
  206. i = 0;
  207. while ((i = path.indexOf("/../", i)) > 0) {
  208. if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
  209. path = path.substring(0, limit) + path.substring(i + 3);
  210. i = 0;
  211. } else {
  212. i = i + 3;
  213. }
  214. }
  215. // Remove trailing .. if possible
  216. while (path.endsWith("/..")) {
  217. i = path.indexOf("/..");
  218. if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
  219. path = path.substring(0, limit+1);
  220. } else {
  221. break;
  222. }
  223. }
  224. // Remove starting .
  225. if (path.startsWith("./") && path.length() > 2)
  226. path = path.substring(2);
  227. // Remove trailing .
  228. if (path.endsWith("/."))
  229. path = path.substring(0, path.length() -1);
  230. }
  231. setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
  232. }
  233. /**
  234. * Returns the default port for a URL parsed by this handler. This method
  235. * is meant to be overidden by handlers with default port numbers.
  236. * @return the default port for a <code>URL</code> parsed by this handler.
  237. */
  238. protected int getDefaultPort() {
  239. return -1;
  240. }
  241. /**
  242. * Provides the default equals calculation. May be overidden by handlers
  243. * for other protocols that have different requirements for equals().
  244. * This method requires that none of its arguments is null. This is
  245. * guaranteed by the fact that it is only called by java.net.URL class.
  246. * @param u1 a URL object
  247. * @param u2 a URL object
  248. * @return <tt>true</tt> if the two urls are
  249. * considered equal, ie. they refer to the same
  250. * fragment in the same file.
  251. */
  252. protected boolean equals(URL u1, URL u2) {
  253. String ref1 = u1.getRef();
  254. String ref2 = u2.getRef();
  255. return sameFile(u1, u2) &&
  256. (ref1 == ref2 ||
  257. (ref1 != null && ref1.equals(ref2)));
  258. }
  259. /**
  260. * Provides the default hash calculation. May be overidden by handlers for
  261. * other protocols that have different requirements for hashCode
  262. * calculation.
  263. * @param u a URL object
  264. * @return an <tt>int</tt> suitable for hash table indexing
  265. */
  266. protected int hashCode(URL u) {
  267. int h = 0;
  268. // Generate the protocol part.
  269. String protocol = u.getProtocol();
  270. if (protocol != null)
  271. h += protocol.hashCode();
  272. // Generate the host part.
  273. InetAddress addr = getHostAddress(u);
  274. if (addr != null) {
  275. h += addr.hashCode();
  276. } else {
  277. String host = u.getHost();
  278. if (host != null)
  279. h += host.toLowerCase().hashCode();
  280. }
  281. // Generate the file part.
  282. String file = u.getFile();
  283. if (file != null)
  284. h += file.hashCode();
  285. // Generate the port part.
  286. if (u.getPort() == -1)
  287. h += getDefaultPort();
  288. else
  289. h += u.getPort();
  290. // Generate the ref part.
  291. String ref = u.getRef();
  292. if (ref != null)
  293. h += ref.hashCode();
  294. return h;
  295. }
  296. /**
  297. * Compare two urls to see whether they refer to the same file,
  298. * i.e., having the same protocol, host, port, and path.
  299. * This method requires that none of its arguments is null. This is
  300. * guaranteed by the fact that it is only called indirectly
  301. * by java.net.URL class.
  302. * @param u1 a URL object
  303. * @param u2 a URL object
  304. * @return true if u1 and u2 refer to the same file
  305. */
  306. protected boolean sameFile(URL u1, URL u2) {
  307. // Compare the protocols.
  308. if (!((u1.getProtocol() == u2.getProtocol()) ||
  309. (u1.getProtocol() != null &&
  310. u1.getProtocol().equalsIgnoreCase(u2.getProtocol()))))
  311. return false;
  312. // Compare the hosts.
  313. if (!hostsEqual(u1, u2))
  314. return false;
  315. // Compare the files.
  316. if (!(u1.getFile() == u2.getFile() ||
  317. (u1.getFile() != null && u1.getFile().equals(u2.getFile()))))
  318. return false;
  319. // Compare the ports.
  320. int port1, port2;
  321. port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort();
  322. port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort();
  323. if (port1 != port2)
  324. return false;
  325. return true;
  326. }
  327. /**
  328. * Get the IP address of our host. An empty host field or a DNS failure
  329. * will result in a null return.
  330. *
  331. * @param u a URL object
  332. * @return an <code>InetAddress</code> representing the host
  333. * IP address.
  334. */
  335. protected synchronized InetAddress getHostAddress(URL u) {
  336. if (u.hostAddress != null)
  337. return u.hostAddress;
  338. String host = u.getHost();
  339. if (host == null || host.equals("")) {
  340. return null;
  341. } else {
  342. try {
  343. u.hostAddress = InetAddress.getByName(host);
  344. } catch (UnknownHostException ex) {
  345. return null;
  346. } catch (SecurityException se) {
  347. return null;
  348. }
  349. }
  350. return u.hostAddress;
  351. }
  352. /**
  353. * Compares the host components of two URLs.
  354. * @param u1 the URL of the first host to compare
  355. * @param u2 the URL of the second host to compare
  356. * @return <tt>true</tt> if and only if they
  357. * are equal, <tt>false</tt> otherwise.
  358. */
  359. protected boolean hostsEqual(URL u1, URL u2) {
  360. InetAddress a1 = getHostAddress(u1);
  361. InetAddress a2 = getHostAddress(u2);
  362. // if we have internet address for both, compare them
  363. if (a1 != null && a2 != null) {
  364. return a1.equals(a2);
  365. // else, if both have host names, compare them
  366. } else if (u1.getHost() != null && u2.getHost() != null)
  367. return u1.getHost().equalsIgnoreCase(u2.getHost());
  368. else
  369. return u1.getHost() == null && u2.getHost() == null;
  370. }
  371. /**
  372. * Converts a <code>URL</code> of a specific protocol to a
  373. * <code>String</code>.
  374. *
  375. * @param u the URL.
  376. * @return a string representation of the <code>URL</code> argument.
  377. */
  378. protected String toExternalForm(URL u) {
  379. // pre-compute length of StringBuffer
  380. int len = u.getProtocol().length() + 1;
  381. if (u.getAuthority() != null && u.getAuthority().length() > 0)
  382. len += 2 + u.getAuthority().length();
  383. if (u.getPath() != null) {
  384. len += u.getPath().length();
  385. }
  386. if (u.getQuery() != null) {
  387. len += 1 + u.getQuery().length();
  388. }
  389. if (u.getRef() != null)
  390. len += 1 + u.getRef().length();
  391. StringBuffer result = new StringBuffer(len);
  392. result.append(u.getProtocol());
  393. result.append(":");
  394. if (u.getAuthority() != null && u.getAuthority().length() > 0) {
  395. result.append("//");
  396. result.append(u.getAuthority());
  397. }
  398. if (u.getPath() != null) {
  399. result.append(u.getPath());
  400. }
  401. if (u.getQuery() != null) {
  402. result.append('?');
  403. result.append(u.getQuery());
  404. }
  405. if (u.getRef() != null) {
  406. result.append("#");
  407. result.append(u.getRef());
  408. }
  409. return result.toString();
  410. }
  411. /**
  412. * Sets the fields of the <code>URL</code> argument to the indicated values.
  413. * Only classes derived from URLStreamHandler are supposed to be able
  414. * to call the set method on a URL.
  415. *
  416. * @param u the URL to modify.
  417. * @param protocol the protocol name.
  418. * @param host the remote host value for the URL.
  419. * @param port the port on the remote machine.
  420. * @param authority the authority part for the URL.
  421. * @param userInfo the userInfo part of the URL.
  422. * @param path the path component of the URL.
  423. * @param query the query part for the URL.
  424. * @param ref the reference.
  425. * @exception SecurityException if the protocol handler of the URL is
  426. * different from this one
  427. * @see java.net.URL#set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String)
  428. */
  429. protected void setURL(URL u, String protocol, String host, int port,
  430. String authority, String userInfo, String path,
  431. String query, String ref) {
  432. if (this != u.handler) {
  433. throw new SecurityException("handler for url different from " +
  434. "this handler");
  435. }
  436. // ensure that no one can reset the protocol on a given URL.
  437. u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
  438. }
  439. /**
  440. * Sets the fields of the <code>URL</code> argument to the indicated values.
  441. * Only classes derived from URLStreamHandler are supposed to be able
  442. * to call the set method on a URL.
  443. *
  444. * @param u the URL to modify.
  445. * @param protocol the protocol name. This value is ignored since 1.2.
  446. * @param host the remote host value for the URL.
  447. * @param port the port on the remote machine.
  448. * @param file the file.
  449. * @param ref the reference.
  450. * @exception SecurityException if the protocol handler of the URL is
  451. * different from this one
  452. * @deprecated Use setURL(URL, String, String, int, String, String, String,
  453. * String);
  454. */
  455. protected void setURL(URL u, String protocol, String host, int port,
  456. String file, String ref) {
  457. /*
  458. * Only old URL handlers call this, so assume that the host
  459. * field might contain "user:passwd@host". Fix as necessary.
  460. */
  461. String authority = null;
  462. String userInfo = null;
  463. if (host != null && host.length() != 0) {
  464. authority = (port == -1) ? host : host + ":" + port;
  465. int at = host.lastIndexOf('@');
  466. if (at != -1) {
  467. userInfo = host.substring(0, at);
  468. host = host.substring(at+1);
  469. }
  470. }
  471. /*
  472. * Assume file might contain query part. Fix as necessary.
  473. */
  474. String path = null;
  475. String query = null;
  476. if (file != null) {
  477. int q = file.lastIndexOf('?');
  478. if (q != -1) {
  479. query = file.substring(q+1);
  480. path = file.substring(0, q);
  481. } else
  482. path = file;
  483. }
  484. setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
  485. }
  486. }