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