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 char firstPortChar = spec.charAt(portStart); 135 if (firstPortChar >= '0' && firstPortChar <= '9') { 136 port = Integer.parseInt(spec.substring(portStart, fileStart)); 137 } else { 138 throw new IllegalArgumentException("invalid port: " + port); 139 } 140 } 141 path = null; 142 query = null; 143 ref = null; 144 } else { 145 // Get the authority from the context URL. 146 fileStart = start; 147 authority = url.getAuthority(); 148 userInfo = url.getUserInfo(); 149 host = url.getHost(); 150 if (host == null) { 151 host = ""; 152 } 153 port = url.getPort(); 154 path = url.getPath(); 155 query = url.getQuery(); 156 ref = url.getRef(); 157 } 158 159 /* 160 * Extract the path, query and fragment. Each part has its own leading 161 * delimiter character. The query can contain slashes and the fragment 162 * can contain slashes and question marks. 163 * / path ? query # fragment 164 */ 165 int pos = fileStart; 166 while (pos < end) { 167 int nextPos; 168 switch (spec.charAt(pos)) { 169 case '#': 170 nextPos = end; 171 ref = spec.substring(pos + 1, nextPos); 172 break; 173 case '?': 174 nextPos = UrlUtils.findFirstOf(spec, "#", pos, end); 175 query = spec.substring(pos + 1, nextPos); 176 ref = null; 177 break; 178 default: 179 nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end); 180 path = relativePath(path, spec.substring(pos, nextPos)); 181 query = null; 182 ref = null; 183 break; 184 } 185 pos = nextPos; 186 } 187 188 if (path == null) { 189 path = ""; 190 } 191 192 path = UrlUtils.authoritySafePath(authority, path); 193 194 setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref); 195 } 196 197 /** 198 * Returns a new path by resolving {@code path} relative to {@code base}. 199 */ 200 private static String relativePath(String base, String path) { 201 if (path.startsWith("/")) { 202 return UrlUtils.canonicalizePath(path, true); 203 } else if (base != null) { 204 String combined = base.substring(0, base.lastIndexOf('/') + 1) + path; 205 return UrlUtils.canonicalizePath(combined, true); 206 } else { 207 return path; 208 } 209 } 210 211 /** 212 * Sets the fields of the URL {@code u} to the values of the supplied 213 * arguments. 214 * 215 * @param u 216 * the non-null URL object to be set. 217 * @param protocol 218 * the protocol. 219 * @param host 220 * the host name. 221 * @param port 222 * the port number. 223 * @param file 224 * the file component. 225 * @param ref 226 * the reference. 227 * @deprecated Use setURL(URL, String String, int, String, String, String, 228 * String, String) instead. 229 */ 230 @Deprecated 231 protected void setURL(URL u, String protocol, String host, int port, 232 String file, String ref) { 233 if (this != u.streamHandler) { 234 throw new SecurityException(); 235 } 236 u.set(protocol, host, port, file, ref); 237 } 238 239 /** 240 * Sets the fields of the URL {@code u} to the values of the supplied 241 * arguments. 242 */ 243 protected void setURL(URL u, String protocol, String host, int port, 244 String authority, String userInfo, String path, String query, 245 String ref) { 246 if (this != u.streamHandler) { 247 throw new SecurityException(); 248 } 249 u.set(protocol, host, port, authority, userInfo, path, query, ref); 250 } 251 252 /** 253 * Returns the clear text representation of a given URL using HTTP format. 254 * 255 * @param url 256 * the URL object to be converted. 257 * @return the clear text representation of the specified URL. 258 * @see #parseURL 259 * @see URL#toExternalForm() 260 */ 261 protected String toExternalForm(URL url) { 262 return toExternalForm(url, false); 263 } 264 265 String toExternalForm(URL url, boolean escapeIllegalCharacters) { 266 StringBuilder result = new StringBuilder(); 267 result.append(url.getProtocol()); 268 result.append(':'); 269 270 String authority = url.getAuthority(); 271 if (authority != null) { 272 result.append("//"); 273 if (escapeIllegalCharacters) { 274 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority); 275 } else { 276 result.append(authority); 277 } 278 } 279 280 String fileAndQuery = url.getFile(); 281 if (fileAndQuery != null) { 282 if (escapeIllegalCharacters) { 283 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery); 284 } else { 285 result.append(fileAndQuery); 286 } 287 } 288 289 String ref = url.getRef(); 290 if (ref != null) { 291 result.append('#'); 292 if (escapeIllegalCharacters) { 293 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref); 294 } else { 295 result.append(ref); 296 } 297 } 298 299 return result.toString(); 300 } 301 302 /** 303 * Returns true if {@code a} and {@code b} have the same protocol, host, 304 * port, file, and reference. 305 */ 306 protected boolean equals(URL a, URL b) { 307 return sameFile(a, b) 308 && Objects.equal(a.getRef(), b.getRef()) 309 && Objects.equal(a.getQuery(), b.getQuery()); 310 } 311 312 /** 313 * Returns the default port of the protocol used by the handled URL. The 314 * default implementation always returns {@code -1}. 315 */ 316 protected int getDefaultPort() { 317 return -1; 318 } 319 320 /** 321 * Returns the host address of {@code url}. 322 */ 323 protected InetAddress getHostAddress(URL url) { 324 try { 325 String host = url.getHost(); 326 if (host == null || host.length() == 0) { 327 return null; 328 } 329 return InetAddress.getByName(host); 330 } catch (UnknownHostException e) { 331 return null; 332 } 333 } 334 335 /** 336 * Returns the hash code of {@code url}. 337 */ 338 protected int hashCode(URL url) { 339 return toExternalForm(url).hashCode(); 340 } 341 342 /** 343 * Returns true if the hosts of {@code a} and {@code b} are equal. 344 */ 345 protected boolean hostsEqual(URL a, URL b) { 346 // URLs with the same case-insensitive host name have equal hosts 347 String aHost = a.getHost(); 348 String bHost = b.getHost(); 349 return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost); 350 } 351 352 /** 353 * Returns true if {@code a} and {@code b} have the same protocol, host, 354 * port and file. 355 */ 356 protected boolean sameFile(URL a, URL b) { 357 return Objects.equal(a.getProtocol(), b.getProtocol()) 358 && hostsEqual(a, b) 359 && a.getEffectivePort() == b.getEffectivePort() 360 && Objects.equal(a.getFile(), b.getFile()); 361 } 362 } 363