1/*
2 * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.net;
27
28import java.io.IOException;
29import java.io.InputStream;
30import java.io.File;
31import java.io.OutputStream;
32import java.util.Hashtable;
33import sun.net.util.IPAddressUtil;
34import sun.net.www.ParseUtil;
35
36/**
37 * The abstract class {@code URLStreamHandler} is the common
38 * superclass for all stream protocol handlers. A stream protocol
39 * handler knows how to make a connection for a particular protocol
40 * type, such as {@code http} or {@code https}.
41 * <p>
42 * In most cases, an instance of a {@code URLStreamHandler}
43 * subclass is not created directly by an application. Rather, the
44 * first time a protocol name is encountered when constructing a
45 * {@code URL}, the appropriate stream protocol handler is
46 * automatically loaded.
47 *
48 * @author  James Gosling
49 * @see     java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
50 * @since   1.0
51 */
52public abstract class URLStreamHandler {
53    /**
54     * Opens a connection to the object referenced by the
55     * {@code URL} argument.
56     * This method should be overridden by a subclass.
57     *
58     * <p>If for the handler's protocol (such as HTTP or JAR), there
59     * exists a public, specialized URLConnection subclass belonging
60     * to one of the following packages or one of their subpackages:
61     * java.lang, java.io, java.util, java.net, the connection
62     * returned will be of that subclass. For example, for HTTP an
63     * HttpURLConnection will be returned, and for JAR a
64     * JarURLConnection will be returned.
65     *
66     * @param      u   the URL that this connects to.
67     * @return     a {@code URLConnection} object for the {@code URL}.
68     * @exception  IOException  if an I/O error occurs while opening the
69     *               connection.
70     */
71    protected abstract URLConnection openConnection(URL u) throws IOException;
72
73    /**
74     * Same as openConnection(URL), except that the connection will be
75     * made through the specified proxy; Protocol handlers that do not
76     * support proxying will ignore the proxy parameter and make a
77     * normal connection.
78     *
79     * Calling this method preempts the system's default
80     * {@link java.net.ProxySelector ProxySelector} settings.
81     *
82     * @param      u   the URL that this connects to.
83     * @param      p   the proxy through which the connection will be made.
84     *                 If direct connection is desired, Proxy.NO_PROXY
85     *                 should be specified.
86     * @return     a {@code URLConnection} object for the {@code URL}.
87     * @exception  IOException  if an I/O error occurs while opening the
88     *               connection.
89     * @exception  IllegalArgumentException if either u or p is null,
90     *               or p has the wrong type.
91     * @exception  UnsupportedOperationException if the subclass that
92     *               implements the protocol doesn't support this method.
93     * @since      1.5
94     */
95    protected URLConnection openConnection(URL u, Proxy p) throws IOException {
96        throw new UnsupportedOperationException("Method not implemented.");
97    }
98
99    /**
100     * Parses the string representation of a {@code URL} into a
101     * {@code URL} object.
102     * <p>
103     * If there is any inherited context, then it has already been
104     * copied into the {@code URL} argument.
105     * <p>
106     * The {@code parseURL} method of {@code URLStreamHandler}
107     * parses the string representation as if it were an
108     * {@code http} specification. Most URL protocol families have a
109     * similar parsing. A stream protocol handler for a protocol that has
110     * a different syntax must override this routine.
111     *
112     * @param   u       the {@code URL} to receive the result of parsing
113     *                  the spec.
114     * @param   spec    the {@code String} representing the URL that
115     *                  must be parsed.
116     * @param   start   the character index at which to begin parsing. This is
117     *                  just past the '{@code :}' (if there is one) that
118     *                  specifies the determination of the protocol name.
119     * @param   limit   the character position to stop parsing at. This is the
120     *                  end of the string or the position of the
121     *                  "{@code #}" character, if present. All information
122     *                  after the sharp sign indicates an anchor.
123     */
124    protected void parseURL(URL u, String spec, int start, int limit) {
125        // These fields may receive context content if this was relative URL
126        String protocol = u.getProtocol();
127        String authority = u.getAuthority();
128        String userInfo = u.getUserInfo();
129        String host = u.getHost();
130        int port = u.getPort();
131        String path = u.getPath();
132        String query = u.getQuery();
133
134        // This field has already been parsed
135        String ref = u.getRef();
136
137        boolean isRelPath = false;
138        boolean queryOnly = false;
139
140// FIX: should not assume query if opaque
141        // Strip off the query part
142        if (start < limit) {
143            int queryStart = spec.indexOf('?');
144            queryOnly = queryStart == start;
145            if ((queryStart != -1) && (queryStart < limit)) {
146                query = spec.substring(queryStart+1, limit);
147                if (limit > queryStart)
148                    limit = queryStart;
149                spec = spec.substring(0, queryStart);
150            }
151        }
152
153        int i = 0;
154        // Parse the authority part if any
155        boolean isUNCName = (start <= limit - 4) &&
156                        (spec.charAt(start) == '/') &&
157                        (spec.charAt(start + 1) == '/') &&
158                        (spec.charAt(start + 2) == '/') &&
159                        (spec.charAt(start + 3) == '/');
160        if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') &&
161            (spec.charAt(start + 1) == '/')) {
162            start += 2;
163            i = spec.indexOf('/', start);
164            if (i < 0 || i > limit) {
165                i = spec.indexOf('?', start);
166                if (i < 0 || i > limit)
167                    i = limit;
168            }
169
170            host = authority = spec.substring(start, i);
171
172            int ind = authority.indexOf('@');
173            if (ind != -1) {
174                if (ind != authority.lastIndexOf('@')) {
175                    // more than one '@' in authority. This is not server based
176                    userInfo = null;
177                    host = null;
178                } else {
179                    userInfo = authority.substring(0, ind);
180                    host = authority.substring(ind+1);
181                }
182            } else {
183                userInfo = null;
184            }
185            if (host != null) {
186                // If the host is surrounded by [ and ] then its an IPv6
187                // literal address as specified in RFC2732
188                if (host.length()>0 && (host.charAt(0) == '[')) {
189                    if ((ind = host.indexOf(']')) > 2) {
190
191                        String nhost = host ;
192                        host = nhost.substring(0,ind+1);
193                        if (!IPAddressUtil.
194                            isIPv6LiteralAddress(host.substring(1, ind))) {
195                            throw new IllegalArgumentException(
196                                "Invalid host: "+ host);
197                        }
198
199                        port = -1 ;
200                        if (nhost.length() > ind+1) {
201                            if (nhost.charAt(ind+1) == ':') {
202                                ++ind ;
203                                // port can be null according to RFC2396
204                                if (nhost.length() > (ind + 1)) {
205                                    port = Integer.parseInt(nhost, ind + 1,
206                                        nhost.length(), 10);
207                                }
208                            } else {
209                                throw new IllegalArgumentException(
210                                    "Invalid authority field: " + authority);
211                            }
212                        }
213                    } else {
214                        throw new IllegalArgumentException(
215                            "Invalid authority field: " + authority);
216                    }
217                } else {
218                    ind = host.indexOf(':');
219                    port = -1;
220                    if (ind >= 0) {
221                        // port can be null according to RFC2396
222                        if (host.length() > (ind + 1)) {
223                            port = Integer.parseInt(host, ind + 1,
224                                    host.length(), 10);
225                        }
226                        host = host.substring(0, ind);
227                    }
228                }
229            } else {
230                host = "";
231            }
232            if (port < -1)
233                throw new IllegalArgumentException("Invalid port number :" +
234                                                   port);
235            start = i;
236            // If the authority is defined then the path is defined by the
237            // spec only; See RFC 2396 Section 5.2.4.
238            if (authority != null && authority.length() > 0)
239                path = "";
240        }
241
242        if (host == null) {
243            host = "";
244        }
245
246        // Parse the file path if any
247        if (start < limit) {
248            if (spec.charAt(start) == '/') {
249                path = spec.substring(start, limit);
250            } else if (path != null && path.length() > 0) {
251                isRelPath = true;
252                int ind = path.lastIndexOf('/');
253                String seperator = "";
254                if (ind == -1 && authority != null)
255                    seperator = "/";
256                path = path.substring(0, ind + 1) + seperator +
257                         spec.substring(start, limit);
258
259            } else {
260                String seperator = (authority != null) ? "/" : "";
261                path = seperator + spec.substring(start, limit);
262            }
263        } else if (queryOnly && path != null) {
264            int ind = path.lastIndexOf('/');
265            if (ind < 0)
266                ind = 0;
267            path = path.substring(0, ind) + "/";
268        }
269        if (path == null)
270            path = "";
271
272        if (isRelPath) {
273            // Remove embedded /./
274            while ((i = path.indexOf("/./")) >= 0) {
275                path = path.substring(0, i) + path.substring(i + 2);
276            }
277            // Remove embedded /../ if possible
278            i = 0;
279            while ((i = path.indexOf("/../", i)) >= 0) {
280                /*
281                 * A "/../" will cancel the previous segment and itself,
282                 * unless that segment is a "/../" itself
283                 * i.e. "/a/b/../c" becomes "/a/c"
284                 * but "/../../a" should stay unchanged
285                 */
286                if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
287                    (path.indexOf("/../", limit) != 0)) {
288                    path = path.substring(0, limit) + path.substring(i + 3);
289                    i = 0;
290                } else {
291                    i = i + 3;
292                }
293            }
294            // Remove trailing .. if possible
295            while (path.endsWith("/..")) {
296                i = path.indexOf("/..");
297                if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
298                    path = path.substring(0, limit+1);
299                } else {
300                    break;
301                }
302            }
303            // Remove starting .
304            if (path.startsWith("./") && path.length() > 2)
305                path = path.substring(2);
306
307            // Remove trailing .
308            if (path.endsWith("/."))
309                path = path.substring(0, path.length() -1);
310        }
311
312        setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
313    }
314
315    /**
316     * Returns the default port for a URL parsed by this handler. This method
317     * is meant to be overidden by handlers with default port numbers.
318     * @return the default port for a {@code URL} parsed by this handler.
319     * @since 1.3
320     */
321    protected int getDefaultPort() {
322        return -1;
323    }
324
325    /**
326     * Provides the default equals calculation. May be overidden by handlers
327     * for other protocols that have different requirements for equals().
328     * This method requires that none of its arguments is null. This is
329     * guaranteed by the fact that it is only called by java.net.URL class.
330     * @param u1 a URL object
331     * @param u2 a URL object
332     * @return {@code true} if the two urls are
333     * considered equal, ie. they refer to the same
334     * fragment in the same file.
335     * @since 1.3
336     */
337    protected boolean equals(URL u1, URL u2) {
338        String ref1 = u1.getRef();
339        String ref2 = u2.getRef();
340        return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2))) &&
341               sameFile(u1, u2);
342    }
343
344    /**
345     * Provides the default hash calculation. May be overidden by handlers for
346     * other protocols that have different requirements for hashCode
347     * calculation.
348     * @param u a URL object
349     * @return an {@code int} suitable for hash table indexing
350     * @since 1.3
351     */
352    protected int hashCode(URL u) {
353        int h = 0;
354
355        // Generate the protocol part.
356        String protocol = u.getProtocol();
357        if (protocol != null)
358            h += protocol.hashCode();
359
360        // Generate the host part.
361        InetAddress addr = getHostAddress(u);
362        if (addr != null) {
363            h += addr.hashCode();
364        } else {
365            String host = u.getHost();
366            if (host != null)
367                h += host.toLowerCase().hashCode();
368        }
369
370        // Generate the file part.
371        String file = u.getFile();
372        if (file != null)
373            h += file.hashCode();
374
375        // Generate the port part.
376        if (u.getPort() == -1)
377            h += getDefaultPort();
378        else
379            h += u.getPort();
380
381        // Generate the ref part.
382        String ref = u.getRef();
383        if (ref != null)
384            h += ref.hashCode();
385
386        return h;
387    }
388
389    /**
390     * Compare two urls to see whether they refer to the same file,
391     * i.e., having the same protocol, host, port, and path.
392     * This method requires that none of its arguments is null. This is
393     * guaranteed by the fact that it is only called indirectly
394     * by java.net.URL class.
395     * @param u1 a URL object
396     * @param u2 a URL object
397     * @return true if u1 and u2 refer to the same file
398     * @since 1.3
399     */
400    protected boolean sameFile(URL u1, URL u2) {
401        // Compare the protocols.
402        if (!((u1.getProtocol() == u2.getProtocol()) ||
403              (u1.getProtocol() != null &&
404               u1.getProtocol().equalsIgnoreCase(u2.getProtocol()))))
405            return false;
406
407        // Compare the files.
408        if (!(u1.getFile() == u2.getFile() ||
409              (u1.getFile() != null && u1.getFile().equals(u2.getFile()))))
410            return false;
411
412        // Compare the ports.
413        int port1, port2;
414        port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort();
415        port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort();
416        if (port1 != port2)
417            return false;
418
419        // Compare the hosts.
420        if (!hostsEqual(u1, u2))
421            return false;
422
423        return true;
424    }
425
426    /**
427     * Get the IP address of our host. An empty host field or a DNS failure
428     * will result in a null return.
429     *
430     * @param u a URL object
431     * @return an {@code InetAddress} representing the host
432     * IP address.
433     * @since 1.3
434     */
435    protected synchronized InetAddress getHostAddress(URL u) {
436        if (u.hostAddress != null)
437            return u.hostAddress;
438
439        String host = u.getHost();
440        if (host == null || host.equals("")) {
441            return null;
442        } else {
443            try {
444                u.hostAddress = InetAddress.getByName(host);
445            } catch (UnknownHostException ex) {
446                return null;
447            } catch (SecurityException se) {
448                return null;
449            }
450        }
451        return u.hostAddress;
452    }
453
454    /**
455     * Compares the host components of two URLs.
456     * @param u1 the URL of the first host to compare
457     * @param u2 the URL of the second host to compare
458     * @return  {@code true} if and only if they
459     * are equal, {@code false} otherwise.
460     * @since 1.3
461     */
462    protected boolean hostsEqual(URL u1, URL u2) {
463        InetAddress a1 = getHostAddress(u1);
464        InetAddress a2 = getHostAddress(u2);
465        // if we have internet address for both, compare them
466        if (a1 != null && a2 != null) {
467            return a1.equals(a2);
468        // else, if both have host names, compare them
469        } else if (u1.getHost() != null && u2.getHost() != null)
470            return u1.getHost().equalsIgnoreCase(u2.getHost());
471         else
472            return u1.getHost() == null && u2.getHost() == null;
473    }
474
475    /**
476     * Converts a {@code URL} of a specific protocol to a
477     * {@code String}.
478     *
479     * @param   u   the URL.
480     * @return  a string representation of the {@code URL} argument.
481     */
482    protected String toExternalForm(URL u) {
483
484        // pre-compute length of StringBuffer
485        int len = u.getProtocol().length() + 1;
486        if (u.getAuthority() != null && u.getAuthority().length() > 0)
487            len += 2 + u.getAuthority().length();
488        if (u.getPath() != null) {
489            len += u.getPath().length();
490        }
491        if (u.getQuery() != null) {
492            len += 1 + u.getQuery().length();
493        }
494        if (u.getRef() != null)
495            len += 1 + u.getRef().length();
496
497        StringBuilder result = new StringBuilder(len);
498        result.append(u.getProtocol());
499        result.append(":");
500        if (u.getAuthority() != null && u.getAuthority().length() > 0) {
501            result.append("//");
502            result.append(u.getAuthority());
503        }
504        if (u.getPath() != null) {
505            result.append(u.getPath());
506        }
507        if (u.getQuery() != null) {
508            result.append('?');
509            result.append(u.getQuery());
510        }
511        if (u.getRef() != null) {
512            result.append("#");
513            result.append(u.getRef());
514        }
515        return result.toString();
516    }
517
518    /**
519     * Sets the fields of the {@code URL} argument to the indicated values.
520     * Only classes derived from URLStreamHandler are able
521     * to use this method to set the values of the URL fields.
522     *
523     * @param   u         the URL to modify.
524     * @param   protocol  the protocol name.
525     * @param   host      the remote host value for the URL.
526     * @param   port      the port on the remote machine.
527     * @param   authority the authority part for the URL.
528     * @param   userInfo the userInfo part of the URL.
529     * @param   path      the path component of the URL.
530     * @param   query     the query part for the URL.
531     * @param   ref       the reference.
532     * @exception       SecurityException       if the protocol handler of the URL is
533     *                                  different from this one
534     * @see     java.net.URL#set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String)
535     * @since 1.3
536     */
537       protected void setURL(URL u, String protocol, String host, int port,
538                             String authority, String userInfo, String path,
539                             String query, String ref) {
540        if (this != u.handler) {
541            throw new SecurityException("handler for url different from " +
542                                        "this handler");
543        }
544        // ensure that no one can reset the protocol on a given URL.
545        u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
546    }
547
548    /**
549     * Sets the fields of the {@code URL} argument to the indicated values.
550     * Only classes derived from URLStreamHandler are able
551     * to use this method to set the values of the URL fields.
552     *
553     * @param   u         the URL to modify.
554     * @param   protocol  the protocol name. This value is ignored since 1.2.
555     * @param   host      the remote host value for the URL.
556     * @param   port      the port on the remote machine.
557     * @param   file      the file.
558     * @param   ref       the reference.
559     * @exception       SecurityException       if the protocol handler of the URL is
560     *                                  different from this one
561     * @deprecated Use setURL(URL, String, String, int, String, String, String,
562     *             String);
563     */
564    @Deprecated
565    protected void setURL(URL u, String protocol, String host, int port,
566                          String file, String ref) {
567        /*
568         * Only old URL handlers call this, so assume that the host
569         * field might contain "user:passwd@host". Fix as necessary.
570         */
571        String authority = null;
572        String userInfo = null;
573        if (host != null && host.length() != 0) {
574            authority = (port == -1) ? host : host + ":" + port;
575            int at = host.lastIndexOf('@');
576            if (at != -1) {
577                userInfo = host.substring(0, at);
578                host = host.substring(at+1);
579            }
580        }
581
582        /*
583         * Assume file might contain query part. Fix as necessary.
584         */
585        String path = null;
586        String query = null;
587        if (file != null) {
588            int q = file.lastIndexOf('?');
589            if (q != -1) {
590                query = file.substring(q+1);
591                path = file.substring(0, q);
592            } else
593                path = file;
594        }
595        setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
596    }
597}
598