URLStreamHandler.java revision 80a7fbab52b96c9fd47c72f8987d1babe2cd001d
1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.net;
19
20import java.io.IOException;
21
22import org.apache.harmony.luni.util.Msg;
23import org.apache.harmony.luni.util.URLUtil;
24
25/**
26 * The abstract class {@code URLStreamHandler} is the base for all classes which
27 * can handle the communication with a URL object over a particular protocol
28 * type.
29 */
30public abstract class URLStreamHandler {
31    /**
32     * Establishes a new connection to the resource specified by the URL {@code
33     * u}. Since different protocols also have unique ways of connecting, it
34     * must be overwritten by the subclass.
35     *
36     * @param u
37     *            the URL to the resource where a connection has to be opened.
38     * @return the opened URLConnection to the specified resource.
39     * @throws IOException
40     *             if an I/O error occurs during opening the connection.
41     */
42    protected abstract URLConnection openConnection(URL u) throws IOException;
43
44    /**
45     * Establishes a new connection to the resource specified by the URL {@code
46     * u} using the given {@code proxy}. Since different protocols also have
47     * unique ways of connecting, it must be overwritten by the subclass.
48     *
49     * @param u
50     *            the URL to the resource where a connection has to be opened.
51     * @param proxy
52     *            the proxy that is used to make the connection.
53     * @return the opened URLConnection to the specified resource.
54     * @throws IOException
55     *             if an I/O error occurs during opening the connection.
56     * @throws IllegalArgumentException
57     *             if any argument is {@code null} or the type of proxy is
58     *             wrong.
59     * @throws UnsupportedOperationException
60     *             if the protocol handler doesn't support this method.
61     */
62    protected URLConnection openConnection(URL u, Proxy proxy)
63            throws IOException {
64        throw new UnsupportedOperationException(Msg.getString("K034d"));
65    }
66
67    /**
68     * Parses the clear text URL in {@code str} into a URL object. URL strings
69     * generally have the following format:
70     * <p>
71     * http://www.company.com/java/file1.java#reference
72     * <p>
73     * The string is parsed in HTTP format. If the protocol has a different URL
74     * format this method must be overridden.
75     *
76     * @param u
77     *            the URL to fill in the parsed clear text URL parts.
78     * @param str
79     *            the URL string that is to be parsed.
80     * @param start
81     *            the string position from where to begin parsing.
82     * @param end
83     *            the string position to stop parsing.
84     * @see #toExternalForm
85     * @see URL
86     */
87    protected void parseURL(URL u, String str, int start, int end) {
88        // For compatibility, refer to Harmony-2941
89        if (str.startsWith("//", start)
90                && str.indexOf('/', start + 2) == -1
91                && end <= Integer.MIN_VALUE + 1) {
92            throw new StringIndexOutOfBoundsException(end - 2 - start);
93        }
94        if (end < start) {
95            if (this != u.strmHandler) {
96                throw new SecurityException();
97            }
98            return;
99        }
100        String parseString = "";
101        if (start < end) {
102            parseString = str.substring(start, end);
103        }
104        end -= start;
105        int fileIdx = 0;
106
107        // Default is to use info from context
108        String host = u.getHost();
109        int port = u.getPort();
110        String ref = u.getRef();
111        String file = u.getPath();
112        String query = u.getQuery();
113        String authority = u.getAuthority();
114        String userInfo = u.getUserInfo();
115
116        int refIdx = parseString.indexOf('#', 0);
117        if (parseString.startsWith("//")) {
118            int hostIdx = 2, portIdx = -1;
119            port = -1;
120            fileIdx = parseString.indexOf('/', hostIdx);
121            int questionMarkIndex = parseString.indexOf('?', hostIdx);
122            if ((questionMarkIndex != -1)
123                    && ((fileIdx == -1) || (fileIdx > questionMarkIndex))) {
124                fileIdx = questionMarkIndex;
125            }
126            if (fileIdx == -1) {
127                fileIdx = end;
128                // Use default
129                file = "";
130            }
131            int hostEnd = fileIdx;
132            if (refIdx != -1 && refIdx < fileIdx) {
133                hostEnd = refIdx;
134            }
135            int userIdx = parseString.lastIndexOf('@', hostEnd);
136            authority = parseString.substring(hostIdx, hostEnd);
137            if (userIdx > -1) {
138                userInfo = parseString.substring(hostIdx, userIdx);
139                hostIdx = userIdx + 1;
140            }
141
142            portIdx = parseString.indexOf(':', userIdx == -1 ? hostIdx
143                    : userIdx);
144            int endOfIPv6Addr = parseString.indexOf(']');
145            // if there are square braces, ie. IPv6 address, use last ':'
146            if (endOfIPv6Addr != -1) {
147                try {
148                    if (parseString.length() > endOfIPv6Addr + 1) {
149                        char c = parseString.charAt(endOfIPv6Addr + 1);
150                        if (c == ':') {
151                            portIdx = endOfIPv6Addr + 1;
152                        } else {
153                            portIdx = -1;
154                        }
155                    } else {
156                        portIdx = -1;
157                    }
158                } catch (Exception e) {
159                    // Ignored
160                }
161            }
162
163            if (portIdx == -1 || portIdx > fileIdx) {
164                host = parseString.substring(hostIdx, hostEnd);
165            } else {
166                host = parseString.substring(hostIdx, portIdx);
167                String portString = parseString.substring(portIdx + 1, hostEnd);
168                if (portString.length() == 0) {
169                    port = -1;
170                } else {
171                    port = Integer.parseInt(portString);
172                }
173            }
174        }
175
176        if (refIdx > -1) {
177            ref = parseString.substring(refIdx + 1, end);
178        }
179        int fileEnd = (refIdx == -1 ? end : refIdx);
180
181        int queryIdx = parseString.lastIndexOf('?', fileEnd);
182        boolean canonicalize = false;
183        if (queryIdx > -1) {
184            query = parseString.substring(queryIdx + 1, fileEnd);
185            if (queryIdx == 0 && file != null) {
186                if (file.isEmpty()) {
187                    file = "/";
188                } else if (file.startsWith("/")) {
189                    canonicalize = true;
190                }
191                int last = file.lastIndexOf('/') + 1;
192                file = file.substring(0, last);
193            }
194            fileEnd = queryIdx;
195        } else
196        // Don't inherit query unless only the ref is changed
197        if (refIdx != 0) {
198            query = null;
199        }
200
201        if (fileIdx > -1) {
202            if (fileIdx < end && parseString.charAt(fileIdx) == '/') {
203                file = parseString.substring(fileIdx, fileEnd);
204            } else if (fileEnd > fileIdx) {
205                if (file == null) {
206                    file = "";
207                } else if (file.isEmpty()) {
208                    file = "/";
209                } else if (file.startsWith("/")) {
210                    canonicalize = true;
211                }
212                int last = file.lastIndexOf('/') + 1;
213                if (last == 0) {
214                    file = parseString.substring(fileIdx, fileEnd);
215                } else {
216                    file = file.substring(0, last)
217                            + parseString.substring(fileIdx, fileEnd);
218                }
219            }
220        }
221        if (file == null) {
222            file = "";
223        }
224
225        if (host == null) {
226            host = "";
227        }
228
229        if (canonicalize) {
230            // modify file if there's any relative referencing
231            file = URLUtil.canonicalizePath(file);
232        }
233
234        setURL(u, u.getProtocol(), host, port, authority, userInfo, file,
235                query, ref);
236    }
237
238    /**
239     * Sets the fields of the URL {@code u} to the values of the supplied
240     * arguments.
241     *
242     * @param u
243     *            the non-null URL object to be set.
244     * @param protocol
245     *            the protocol.
246     * @param host
247     *            the host name.
248     * @param port
249     *            the port number.
250     * @param file
251     *            the file component.
252     * @param ref
253     *            the reference.
254     * @deprecated use setURL(URL, String String, int, String, String, String,
255     *             String, String) instead.
256     */
257    @Deprecated
258    protected void setURL(URL u, String protocol, String host, int port,
259            String file, String ref) {
260        if (this != u.strmHandler) {
261            throw new SecurityException();
262        }
263        u.set(protocol, host, port, file, ref);
264    }
265
266    /**
267     * Sets the fields of the URL {@code u} to the values of the supplied
268     * arguments.
269     *
270     * @param u
271     *            the non-null URL object to be set.
272     * @param protocol
273     *            the protocol.
274     * @param host
275     *            the host name.
276     * @param port
277     *            the port number.
278     * @param authority
279     *            the authority.
280     * @param userInfo
281     *            the user info.
282     * @param file
283     *            the file component.
284     * @param query
285     *            the query.
286     * @param ref
287     *            the reference.
288     */
289    protected void setURL(URL u, String protocol, String host, int port,
290            String authority, String userInfo, String file, String query,
291            String ref) {
292        if (this != u.strmHandler) {
293            throw new SecurityException();
294        }
295        u.set(protocol, host, port, authority, userInfo, file, query, ref);
296    }
297
298    /**
299     * Returns the clear text representation of a given URL using HTTP format.
300     *
301     * @param url
302     *            the URL object to be converted.
303     * @return the clear text representation of the specified URL.
304     * @see #parseURL
305     * @see URL#toExternalForm()
306     */
307    protected String toExternalForm(URL url) {
308        StringBuilder answer = new StringBuilder();
309        answer.append(url.getProtocol());
310        answer.append(':');
311        String authority = url.getAuthority();
312        if (authority != null && authority.length() > 0) {
313            answer.append("//");
314            answer.append(url.getAuthority());
315        }
316
317        String file = url.getFile();
318        String ref = url.getRef();
319        if (file != null) {
320            answer.append(file);
321        }
322        if (ref != null) {
323            answer.append('#');
324            answer.append(ref);
325        }
326        return answer.toString();
327    }
328
329    /**
330     * Compares two URL objects whether they represent the same URL. Two URLs
331     * are equal if they have the same file, host, port, protocol, query, and
332     * reference components.
333     *
334     * @param url1
335     *            the first URL to compare.
336     * @param url2
337     *            the second URL to compare.
338     * @return {@code true} if the URLs are the same, {@code false} otherwise.
339     * @see #hashCode
340     */
341    protected boolean equals(URL url1, URL url2) {
342        if (!sameFile(url1, url2)) {
343            return false;
344        }
345        String s1 = url1.getRef(), s2 = url2.getRef();
346        if (s1 != s2 && (s1 == null || !s1.equals(s2))) {
347            return false;
348        }
349        s1 = url1.getQuery();
350        s2 = url2.getQuery();
351        return s1 == s2 || (s1 != null && s1.equals(s2));
352    }
353
354    /**
355     * Returns the default port of the protocol used by the handled URL. The
356     * current implementation returns always {@code -1}.
357     *
358     * @return the appropriate default port number of the protocol.
359     */
360    protected int getDefaultPort() {
361        return -1;
362    }
363
364    /**
365     * Returns the host address of the given URL.
366     *
367     * @param url
368     *            the URL object where to read the host address from.
369     * @return the host address of the specified URL.
370     */
371    protected InetAddress getHostAddress(URL url) {
372        try {
373            String host = url.getHost();
374            if (host == null || host.length() == 0) {
375                return null;
376            }
377            return InetAddress.getByName(host);
378        } catch (UnknownHostException e) {
379            return null;
380        }
381    }
382
383    /**
384     * Returns the hashcode value for the given URL object.
385     *
386     * @param url
387     *            the URL to determine the hashcode.
388     * @return the hashcode of the given URL.
389     */
390    protected int hashCode(URL url) {
391        return toExternalForm(url).hashCode();
392    }
393
394    /**
395     * Compares two URL objects whether they refer to the same host.
396     *
397     * @param url1
398     *            the first URL to be compared.
399     * @param url2
400     *            the second URL to be compared.
401     * @return {@code true} if both URLs refer to the same host, {@code false}
402     *         otherwise.
403     */
404    protected boolean hostsEqual(URL url1, URL url2) {
405        String host1 = getHost(url1), host2 = getHost(url2);
406        if (host1 != null && host1.equalsIgnoreCase(host2)) {
407            return true;
408        }
409        // Compare host address if the host name is not equal.
410        InetAddress address1 = getHostAddress(url1);
411        InetAddress address2 = getHostAddress(url2);
412        if (address1 != null && address1.equals(address2)) {
413            return true;
414        }
415        return false;
416    }
417
418    /**
419     * Compares two URL objects whether they refer to the same file. In the
420     * comparison included are the URL components protocol, host, port and file.
421     *
422     * @param url1
423     *            the first URL to be compared.
424     * @param url2
425     *            the second URL to be compared.
426     * @return {@code true} if both URLs refer to the same file, {@code false}
427     *         otherwise.
428     */
429    protected boolean sameFile(URL url1, URL url2) {
430        String s1 = url1.getProtocol();
431        String s2 = url2.getProtocol();
432        if (s1 != s2 && (s1 == null || !s1.equals(s2))) {
433            return false;
434        }
435
436        s1 = url1.getFile();
437        s2 = url2.getFile();
438        if (s1 != s2 && (s1 == null || !s1.equals(s2))) {
439            return false;
440        }
441        if (!hostsEqual(url1, url2)) {
442            return false;
443        }
444        int p1 = url1.getPort();
445        if (p1 == -1) {
446            p1 = getDefaultPort();
447        }
448        int p2 = url2.getPort();
449        if (p2 == -1) {
450            p2 = getDefaultPort();
451        }
452        return p1 == p2;
453    }
454
455    /*
456     * If the URL host is empty while protocal is file, the host is regarded as
457     * localhost.
458     */
459    private static String getHost(URL url) {
460        String host = url.getHost();
461        if ("file".equals(url.getProtocol()) && host.isEmpty()) {
462            host = "localhost";
463        }
464        return host;
465    }
466}
467