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