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 18 package java.net; 19 20 import java.io.IOException; 21 import libcore.net.url.UrlUtils; 22 import libcore.util.Objects; 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 */ 29 public 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 url 75 * the URL to fill in the parsed clear text URL parts. 76 * @param spec 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 url, String spec, int start, int end) { 86 if (this != url.streamHandler) { 87 throw new SecurityException("Only a URL's stream handler is permitted to mutate it"); 88 } 89 if (end < start) { 90 throw new StringIndexOutOfBoundsException(spec, start, end - start); 91 } 92 93 int fileStart; 94 String authority; 95 String userInfo; 96 String host; 97 int port = -1; 98 String path; 99 String query; 100 String ref; 101 if (spec.regionMatches(start, "//", 0, 2)) { 102 // Parse the authority from the spec. 103 int authorityStart = start + 2; 104 fileStart = UrlUtils.findFirstOf(spec, "/?#", authorityStart, end); 105 authority = spec.substring(authorityStart, fileStart); 106 int userInfoEnd = UrlUtils.findFirstOf(spec, "@", authorityStart, fileStart); 107 int hostStart; 108 if (userInfoEnd != fileStart) { 109 userInfo = spec.substring(authorityStart, userInfoEnd); 110 hostStart = userInfoEnd + 1; 111 } else { 112 userInfo = null; 113 hostStart = authorityStart; 114 } 115 116 /* 117 * Extract the host and port. The host may be an IPv6 address with 118 * colons like "[::1]", in which case we look for the port delimiter 119 * colon after the ']' character. 120 */ 121 int colonSearchFrom = hostStart; 122 int ipv6End = UrlUtils.findFirstOf(spec, "]", hostStart, fileStart); 123 if (ipv6End != fileStart) { 124 if (UrlUtils.findFirstOf(spec, ":", hostStart, ipv6End) == ipv6End) { 125 throw new IllegalArgumentException("Expected an IPv6 address: " 126 + spec.substring(hostStart, ipv6End + 1)); 127 } 128 colonSearchFrom = ipv6End; 129 } 130 int hostEnd = UrlUtils.findFirstOf(spec, ":", colonSearchFrom, fileStart); 131 host = spec.substring(hostStart, hostEnd); 132 int portStart = hostEnd + 1; 133 if (portStart < fileStart) { 134 port = Integer.parseInt(spec.substring(portStart, fileStart)); 135 if (port < 0) { 136 throw new IllegalArgumentException("port < 0: " + port); 137 } 138 } 139 path = null; 140 query = null; 141 ref = null; 142 } else { 143 // Get the authority from the context URL. 144 fileStart = start; 145 authority = url.getAuthority(); 146 userInfo = url.getUserInfo(); 147 host = url.getHost(); 148 if (host == null) { 149 host = ""; 150 } 151 port = url.getPort(); 152 path = url.getPath(); 153 query = url.getQuery(); 154 ref = url.getRef(); 155 } 156 157 /* 158 * Extract the path, query and fragment. Each part has its own leading 159 * delimiter character. The query can contain slashes and the fragment 160 * can contain slashes and question marks. 161 * / path ? query # fragment 162 */ 163 int pos = fileStart; 164 while (pos < end) { 165 int nextPos; 166 switch (spec.charAt(pos)) { 167 case '#': 168 nextPos = end; 169 ref = spec.substring(pos + 1, nextPos); 170 break; 171 case '?': 172 nextPos = UrlUtils.findFirstOf(spec, "#", pos, end); 173 query = spec.substring(pos + 1, nextPos); 174 ref = null; 175 break; 176 default: 177 nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end); 178 path = relativePath(path, spec.substring(pos, nextPos)); 179 query = null; 180 ref = null; 181 break; 182 } 183 pos = nextPos; 184 } 185 186 if (path == null) { 187 path = ""; 188 } 189 190 path = UrlUtils.authoritySafePath(authority, path); 191 192 setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref); 193 } 194 195 /** 196 * Returns a new path by resolving {@code path} relative to {@code base}. 197 */ 198 private static String relativePath(String base, String path) { 199 if (path.startsWith("/")) { 200 return UrlUtils.canonicalizePath(path, true); 201 } else if (base != null) { 202 String combined = base.substring(0, base.lastIndexOf('/') + 1) + path; 203 return UrlUtils.canonicalizePath(combined, true); 204 } else { 205 return path; 206 } 207 } 208 209 /** 210 * Sets the fields of the URL {@code u} to the values of the supplied 211 * arguments. 212 * 213 * @param u 214 * the non-null URL object to be set. 215 * @param protocol 216 * the protocol. 217 * @param host 218 * the host name. 219 * @param port 220 * the port number. 221 * @param file 222 * the file component. 223 * @param ref 224 * the reference. 225 * @deprecated use setURL(URL, String String, int, String, String, String, 226 * String, String) instead. 227 */ 228 @Deprecated 229 protected void setURL(URL u, String protocol, String host, int port, 230 String file, String ref) { 231 if (this != u.streamHandler) { 232 throw new SecurityException(); 233 } 234 u.set(protocol, host, port, file, ref); 235 } 236 237 /** 238 * Sets the fields of the URL {@code u} to the values of the supplied 239 * arguments. 240 */ 241 protected void setURL(URL u, String protocol, String host, int port, 242 String authority, String userInfo, String path, String query, 243 String ref) { 244 if (this != u.streamHandler) { 245 throw new SecurityException(); 246 } 247 u.set(protocol, host, port, authority, userInfo, path, query, ref); 248 } 249 250 /** 251 * Returns the clear text representation of a given URL using HTTP format. 252 * 253 * @param url 254 * the URL object to be converted. 255 * @return the clear text representation of the specified URL. 256 * @see #parseURL 257 * @see URL#toExternalForm() 258 */ 259 protected String toExternalForm(URL url) { 260 return toExternalForm(url, false); 261 } 262 263 String toExternalForm(URL url, boolean escapeIllegalCharacters) { 264 StringBuilder result = new StringBuilder(); 265 result.append(url.getProtocol()); 266 result.append(':'); 267 268 String authority = url.getAuthority(); 269 if (authority != null) { 270 result.append("//"); 271 if (escapeIllegalCharacters) { 272 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority); 273 } else { 274 result.append(authority); 275 } 276 } 277 278 String fileAndQuery = url.getFile(); 279 if (fileAndQuery != null) { 280 if (escapeIllegalCharacters) { 281 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery); 282 } else { 283 result.append(fileAndQuery); 284 } 285 } 286 287 String ref = url.getRef(); 288 if (ref != null) { 289 result.append('#'); 290 if (escapeIllegalCharacters) { 291 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref); 292 } else { 293 result.append(ref); 294 } 295 } 296 297 return result.toString(); 298 } 299 300 /** 301 * Returns true if {@code a} and {@code b} have the same protocol, host, 302 * port, file, and reference. 303 */ 304 protected boolean equals(URL a, URL b) { 305 return sameFile(a, b) 306 && Objects.equal(a.getRef(), b.getRef()) 307 && Objects.equal(a.getQuery(), b.getQuery()); 308 } 309 310 /** 311 * Returns the default port of the protocol used by the handled URL. The 312 * default implementation always returns {@code -1}. 313 */ 314 protected int getDefaultPort() { 315 return -1; 316 } 317 318 /** 319 * Returns the host address of {@code url}. 320 */ 321 protected InetAddress getHostAddress(URL url) { 322 try { 323 String host = url.getHost(); 324 if (host == null || host.length() == 0) { 325 return null; 326 } 327 return InetAddress.getByName(host); 328 } catch (UnknownHostException e) { 329 return null; 330 } 331 } 332 333 /** 334 * Returns the hash code of {@code url}. 335 */ 336 protected int hashCode(URL url) { 337 return toExternalForm(url).hashCode(); 338 } 339 340 /** 341 * Returns true if the hosts of {@code a} and {@code b} are equal. 342 */ 343 protected boolean hostsEqual(URL a, URL b) { 344 // URLs with the same case-insensitive host name have equal hosts 345 String aHost = a.getHost(); 346 String bHost = b.getHost(); 347 return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost); 348 } 349 350 /** 351 * Returns true if {@code a} and {@code b} have the same protocol, host, 352 * port and file. 353 */ 354 protected boolean sameFile(URL a, URL b) { 355 return Objects.equal(a.getProtocol(), b.getProtocol()) 356 && hostsEqual(a, b) 357 && a.getEffectivePort() == b.getEffectivePort() 358 && Objects.equal(a.getFile(), b.getFile()); 359 } 360 } 361