URLStreamHandler.java revision 32559028b14b9b321b10eede050afd554a376569
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;
21import libcore.util.Objects;
22import org.apache.harmony.luni.util.URLUtil;
23
24/**
25 * The abstract class {@code URLStreamHandler} is the base for all classes which
26 * can handle the communication with a URL object over a particular protocol
27 * type.
28 */
29public abstract class URLStreamHandler {
30    /**
31     * Establishes a new connection to the resource specified by the URL {@code
32     * u}. Since different protocols also have unique ways of connecting, it
33     * must be overwritten by the subclass.
34     *
35     * @param u
36     *            the URL to the resource where a connection has to be opened.
37     * @return the opened URLConnection to the specified resource.
38     * @throws IOException
39     *             if an I/O error occurs during opening the connection.
40     */
41    protected abstract URLConnection openConnection(URL u) throws IOException;
42
43    /**
44     * Establishes a new connection to the resource specified by the URL {@code
45     * u} using the given {@code proxy}. Since different protocols also have
46     * unique ways of connecting, it must be overwritten by the subclass.
47     *
48     * @param u
49     *            the URL to the resource where a connection has to be opened.
50     * @param proxy
51     *            the proxy that is used to make the connection.
52     * @return the opened URLConnection to the specified resource.
53     * @throws IOException
54     *             if an I/O error occurs during opening the connection.
55     * @throws IllegalArgumentException
56     *             if any argument is {@code null} or the type of proxy is
57     *             wrong.
58     * @throws UnsupportedOperationException
59     *             if the protocol handler doesn't support this method.
60     */
61    protected URLConnection openConnection(URL u, Proxy proxy) throws IOException {
62        throw new UnsupportedOperationException();
63    }
64
65    /**
66     * Parses the clear text URL in {@code str} into a URL object. URL strings
67     * generally have the following format:
68     * <p>
69     * http://www.company.com/java/file1.java#reference
70     * <p>
71     * The string is parsed in HTTP format. If the protocol has a different URL
72     * format this method must be overridden.
73     *
74     * @param u
75     *            the URL to fill in the parsed clear text URL parts.
76     * @param str
77     *            the URL string that is to be parsed.
78     * @param start
79     *            the string position from where to begin parsing.
80     * @param end
81     *            the string position to stop parsing.
82     * @see #toExternalForm
83     * @see URL
84     */
85    protected void parseURL(URL u, String str, int start, int end) {
86        // For compatibility, refer to Harmony-2941
87        if (str.startsWith("//", start)
88                && str.indexOf('/', start + 2) == -1
89                && end <= Integer.MIN_VALUE + 1) {
90            throw new StringIndexOutOfBoundsException(end - 2 - start);
91        }
92        if (end < start) {
93            if (this != u.strmHandler) {
94                throw new SecurityException();
95            }
96            return;
97        }
98        String parseString = "";
99        if (start < end) {
100            parseString = str.substring(start, end);
101        }
102        end -= start;
103        int fileIdx = 0;
104
105        // Default is to use info from context
106        String host = u.getHost();
107        int port = u.getPort();
108        String ref = u.getRef();
109        String file = u.getPath();
110        String query = u.getQuery();
111        String authority = u.getAuthority();
112        String userInfo = u.getUserInfo();
113
114        int refIdx = parseString.indexOf('#', 0);
115        if (parseString.startsWith("//")) {
116            int hostIdx = 2;
117            port = -1;
118            fileIdx = parseString.indexOf('/', hostIdx);
119            int questionMarkIndex = parseString.indexOf('?', hostIdx);
120            if (questionMarkIndex != -1 && (fileIdx == -1 || fileIdx > questionMarkIndex)) {
121                fileIdx = questionMarkIndex;
122            }
123            if (fileIdx == -1) {
124                fileIdx = end;
125                // Use default
126                file = "";
127            }
128            int hostEnd = fileIdx;
129            if (refIdx != -1 && refIdx < fileIdx) {
130                hostEnd = refIdx;
131                fileIdx = refIdx;
132                file = "";
133            }
134            int userIdx = parseString.lastIndexOf('@', hostEnd);
135            authority = parseString.substring(hostIdx, hostEnd);
136            if (userIdx != -1) {
137                userInfo = parseString.substring(hostIdx, userIdx);
138                hostIdx = userIdx + 1;
139            }
140
141            int endOfIPv6Addr = parseString.indexOf(']', hostIdx);
142            if (endOfIPv6Addr >= hostEnd) {
143                endOfIPv6Addr = -1;
144            }
145
146            // the port separator must be immediately after an IPv6 address "http://[::1]:80/"
147            int portIdx = -1;
148            if (endOfIPv6Addr != -1) {
149                int maybeColon = endOfIPv6Addr + 1;
150                if (maybeColon < hostEnd && parseString.charAt(maybeColon) == ':') {
151                    portIdx = maybeColon;
152                }
153            } else {
154                portIdx = parseString.indexOf(':', hostIdx);
155            }
156
157            if (portIdx == -1 || portIdx > hostEnd) {
158                host = parseString.substring(hostIdx, hostEnd);
159            } else {
160                host = parseString.substring(hostIdx, portIdx);
161                String portString = parseString.substring(portIdx + 1, hostEnd);
162                if (portString.length() == 0) {
163                    port = -1;
164                } else {
165                    port = Integer.parseInt(portString);
166                }
167            }
168        }
169
170        if (refIdx > -1) {
171            ref = parseString.substring(refIdx + 1, end);
172        }
173        int fileEnd = (refIdx == -1 ? end : refIdx);
174
175        int queryIdx = parseString.lastIndexOf('?', fileEnd);
176        boolean canonicalize = false;
177        if (queryIdx > -1) {
178            query = parseString.substring(queryIdx + 1, fileEnd);
179            if (queryIdx == 0 && file != null) {
180                if (file.isEmpty()) {
181                    file = "/";
182                } else if (file.startsWith("/")) {
183                    canonicalize = true;
184                }
185                int last = file.lastIndexOf('/') + 1;
186                file = file.substring(0, last);
187            }
188            fileEnd = queryIdx;
189        } else
190        // Don't inherit query unless only the ref is changed
191        if (refIdx != 0) {
192            query = null;
193        }
194
195        if (fileIdx > -1) {
196            if (fileIdx < end && parseString.charAt(fileIdx) == '/') {
197                file = parseString.substring(fileIdx, fileEnd);
198            } else if (fileEnd > fileIdx) {
199                if (file == null) {
200                    file = "";
201                } else if (file.isEmpty()) {
202                    file = "/";
203                } else if (file.startsWith("/")) {
204                    canonicalize = true;
205                }
206                int last = file.lastIndexOf('/') + 1;
207                if (last == 0) {
208                    file = parseString.substring(fileIdx, fileEnd);
209                } else {
210                    file = file.substring(0, last)
211                            + parseString.substring(fileIdx, fileEnd);
212                }
213            }
214        }
215        if (file == null) {
216            file = "";
217        }
218
219        if (host == null) {
220            host = "";
221        }
222
223        if (canonicalize) {
224            // modify file if there's any relative referencing
225            file = URLUtil.canonicalizePath(file);
226        }
227
228        setURL(u, u.getProtocol(), host, port, authority, userInfo, file,
229                query, ref);
230    }
231
232    /**
233     * Sets the fields of the URL {@code u} to the values of the supplied
234     * arguments.
235     *
236     * @param u
237     *            the non-null URL object to be set.
238     * @param protocol
239     *            the protocol.
240     * @param host
241     *            the host name.
242     * @param port
243     *            the port number.
244     * @param file
245     *            the file component.
246     * @param ref
247     *            the reference.
248     * @deprecated use setURL(URL, String String, int, String, String, String,
249     *             String, String) instead.
250     */
251    @Deprecated
252    protected void setURL(URL u, String protocol, String host, int port,
253            String file, String ref) {
254        if (this != u.strmHandler) {
255            throw new SecurityException();
256        }
257        u.set(protocol, host, port, file, ref);
258    }
259
260    /**
261     * Sets the fields of the URL {@code u} to the values of the supplied
262     * arguments.
263     *
264     * @param u
265     *            the non-null URL object to be set.
266     * @param protocol
267     *            the protocol.
268     * @param host
269     *            the host name.
270     * @param port
271     *            the port number.
272     * @param authority
273     *            the authority.
274     * @param userInfo
275     *            the user info.
276     * @param file
277     *            the file component.
278     * @param query
279     *            the query.
280     * @param ref
281     *            the reference.
282     */
283    protected void setURL(URL u, String protocol, String host, int port,
284            String authority, String userInfo, String file, String query,
285            String ref) {
286        if (this != u.strmHandler) {
287            throw new SecurityException();
288        }
289        u.set(protocol, host, port, authority, userInfo, file, query, ref);
290    }
291
292    /**
293     * Returns the clear text representation of a given URL using HTTP format.
294     *
295     * @param url
296     *            the URL object to be converted.
297     * @return the clear text representation of the specified URL.
298     * @see #parseURL
299     * @see URL#toExternalForm()
300     */
301    protected String toExternalForm(URL url) {
302        return toExternalForm(url, false);
303    }
304
305    String toExternalForm(URL url, boolean escapeIllegalCharacters) {
306        StringBuilder result = new StringBuilder();
307        result.append(url.getProtocol());
308        result.append(':');
309
310        String authority = url.getAuthority();
311        if (authority != null && !authority.isEmpty()) {
312            result.append("//");
313            if (escapeIllegalCharacters) {
314                URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
315            } else {
316                result.append(authority);
317            }
318        }
319
320        String fileAndQuery = url.getFile();
321        if (fileAndQuery != null) {
322            if (escapeIllegalCharacters) {
323                URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
324            } else {
325                result.append(fileAndQuery);
326            }
327        }
328
329        String ref = url.getRef();
330        if (ref != null) {
331            result.append('#');
332            if (escapeIllegalCharacters) {
333                URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
334            } else {
335                result.append(ref);
336            }
337        }
338
339        return result.toString();
340    }
341
342    /**
343     * Returns true if {@code a} and {@code b} have the same protocol, host,
344     * port, file, and reference.
345     */
346    protected boolean equals(URL a, URL b) {
347        return sameFile(a, b)
348                && Objects.equal(a.getRef(), b.getRef())
349                && Objects.equal(a.getQuery(), b.getQuery());
350    }
351
352    /**
353     * Returns the default port of the protocol used by the handled URL. The
354     * default implementation always returns {@code -1}.
355     */
356    protected int getDefaultPort() {
357        return -1;
358    }
359
360    /**
361     * Returns the host address of {@code url}.
362     */
363    protected InetAddress getHostAddress(URL url) {
364        try {
365            String host = url.getHost();
366            if (host == null || host.length() == 0) {
367                return null;
368            }
369            return InetAddress.getByName(host);
370        } catch (UnknownHostException e) {
371            return null;
372        }
373    }
374
375    /**
376     * Returns the hash code of {@code url}.
377     */
378    protected int hashCode(URL url) {
379        return toExternalForm(url).hashCode();
380    }
381
382    /**
383     * Returns true if the hosts of {@code a} and {@code b} are equal.
384     */
385    protected boolean hostsEqual(URL a, URL b) {
386        // URLs with the same case-insensitive host name have equal hosts
387        String aHost = getHost(a);
388        String bHost = getHost(b);
389        return aHost != null && aHost.equalsIgnoreCase(bHost);
390    }
391
392    /**
393     * Returns true if {@code a} and {@code b} have the same protocol, host,
394     * port and file.
395     */
396    protected boolean sameFile(URL a, URL b) {
397        return Objects.equal(a.getProtocol(), b.getProtocol())
398                && hostsEqual(a, b)
399                && a.getEffectivePort() == b.getEffectivePort()
400                && Objects.equal(a.getFile(), b.getFile());
401    }
402
403    private static String getHost(URL url) {
404        String host = url.getHost();
405        if ("file".equals(url.getProtocol()) && host.isEmpty()) {
406            host = "localhost";
407        }
408        return host;
409    }
410}
411