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