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 java.io.InputStream; 22 import java.io.OutputStream; 23 import java.util.Collections; 24 import java.util.Date; 25 import java.util.Hashtable; 26 import java.util.List; 27 import java.util.Locale; 28 import java.util.Map; 29 30 /** 31 * A connection to a URL for reading or writing. For HTTP connections, see 32 * {@link HttpURLConnection} for documentation of HTTP-specific features. 33 * 34 * <p>For example, to retrieve {@code 35 * ftp://mirror.csclub.uwaterloo.ca/index.html}: <pre> {@code 36 * URL url = new URL("ftp://mirror.csclub.uwaterloo.ca/index.html"); 37 * URLConnection urlConnection = url.openConnection(); 38 * InputStream in = new BufferedInputStream(urlConnection.getInputStream()); 39 * try { 40 * readStream(in); 41 * } finally { 42 * in.close(); 43 * } 44 * }</pre> 45 * 46 * <p>{@code URLConnection} must be configured before it has connected to the 47 * remote resource. Instances of {@code URLConnection} are not reusable: you 48 * must use a different instance for each connection to a resource. 49 * 50 * <h3>Timeouts</h3> 51 * {@code URLConnection} supports two timeouts: a {@link #setConnectTimeout 52 * connect timeout} and a {@link #setReadTimeout read timeout}. By default, 53 * operations never time out. 54 * 55 * <h3>Built-in Protocols</h3> 56 * <ul> 57 * <li><strong>File</strong><br> 58 * Resources from the local file system can be loaded using {@code file:} 59 * URIs. File connections can only be used for input. 60 * <li><strong>FTP</strong><br> 61 * File Transfer Protocol (<a href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</a>) 62 * is supported, but with no public subclass. FTP connections can 63 * be used for input or output but not both. 64 * <p>By default, FTP connections will be made using {@code anonymous} as 65 * the username and the empty string as the password. Specify alternate 66 * usernames and passwords in the URL: {@code 67 * ftp://username:password@host/path}. 68 * <li><strong>HTTP and HTTPS</strong><br> 69 * Refer to the {@link HttpURLConnection} and {@link 70 * javax.net.ssl.HttpsURLConnection HttpsURLConnection} subclasses. 71 * <li><strong>Jar</strong><br> 72 * Refer to the {@link JarURLConnection} subclass. 73 * </ul> 74 * 75 * <h3>Registering Additional Protocols</h3> 76 * Use {@link URL#setURLStreamHandlerFactory} to register handlers for other 77 * protocol types. 78 */ 79 public abstract class URLConnection { 80 81 /** 82 * The URL which represents the remote target of this {@code URLConnection}. 83 */ 84 protected URL url; 85 86 private String contentType; 87 88 private static boolean defaultAllowUserInteraction; 89 90 private static boolean defaultUseCaches = true; 91 92 ContentHandler defaultHandler = new DefaultContentHandler(); 93 94 private long lastModified = -1; 95 96 /** 97 * The data must be modified more recently than this time in milliseconds 98 * since January 1, 1970, GMT to be transmitted. 99 */ 100 protected long ifModifiedSince; 101 102 /** 103 * Specifies whether the using of caches is enabled or the data has to be 104 * recent for every request. 105 */ 106 protected boolean useCaches = defaultUseCaches; 107 108 /** 109 * Specifies whether this {@code URLConnection} is already connected to the 110 * remote resource. If this field is set to {@code true} the flags for 111 * setting up the connection are not changeable anymore. 112 */ 113 protected boolean connected; 114 115 /** 116 * Specifies whether this {@code URLConnection} allows sending data. 117 */ 118 protected boolean doOutput; 119 120 /** 121 * Specifies whether this {@code URLConnection} allows receiving data. 122 */ 123 protected boolean doInput = true; 124 125 /** 126 * Unused by Android. This field can be accessed via {@link #getAllowUserInteraction} 127 * and {@link #setAllowUserInteraction}. 128 */ 129 protected boolean allowUserInteraction = defaultAllowUserInteraction; 130 131 private static ContentHandlerFactory contentHandlerFactory; 132 133 private int readTimeout = 0; 134 135 private int connectTimeout = 0; 136 137 /** 138 * Cache for storing content handler 139 */ 140 static Hashtable<String, Object> contentHandlers = new Hashtable<String, Object>(); 141 142 /** 143 * A hashtable that maps the filename extension (key) to a MIME-type 144 * (element) 145 */ 146 private static FileNameMap fileNameMap; 147 148 /** 149 * Creates a new {@code URLConnection} instance pointing to the resource 150 * specified by the given URL. 151 * 152 * @param url 153 * the URL which represents the resource this {@code 154 * URLConnection} will point to. 155 */ 156 protected URLConnection(URL url) { 157 this.url = url; 158 } 159 160 /** 161 * Opens a connection to the resource. This method will <strong>not</strong> 162 * reconnect to a resource after the initial connection has been closed. 163 * 164 * @throws IOException 165 * if an error occurs while connecting to the resource. 166 */ 167 public abstract void connect() throws IOException; 168 169 /** 170 * Returns {@code allowUserInteraction}. Unused by Android. 171 */ 172 public boolean getAllowUserInteraction() { 173 return allowUserInteraction; 174 } 175 176 /** 177 * Returns an object representing the content of the resource this {@code 178 * URLConnection} is connected to. First, it attempts to get the content 179 * type from the method {@code getContentType()} which looks at the response 180 * header field "Content-Type". If none is found it will guess the content 181 * type from the filename extension. If that fails the stream itself will be 182 * used to guess the content type. 183 * 184 * @return the content representing object. 185 * @throws IOException 186 * if an error occurs obtaining the content. 187 */ 188 public Object getContent() throws java.io.IOException { 189 if (!connected) { 190 connect(); 191 } 192 193 if ((contentType = getContentType()) == null) { 194 if ((contentType = guessContentTypeFromName(url.getFile())) == null) { 195 contentType = guessContentTypeFromStream(getInputStream()); 196 } 197 } 198 if (contentType != null) { 199 return getContentHandler(contentType).getContent(this); 200 } 201 return null; 202 } 203 204 /** 205 * Returns an object representing the content of the resource this {@code 206 * URLConnection} is connected to. First, it attempts to get the content 207 * type from the method {@code getContentType()} which looks at the response 208 * header field "Content-Type". If none is found it will guess the content 209 * type from the filename extension. If that fails the stream itself will be 210 * used to guess the content type. The content type must match with one of 211 * the list {@code types}. 212 * 213 * @param types 214 * the list of acceptable content types. 215 * @return the content representing object or {@code null} if the content 216 * type does not match with one of the specified types. 217 * @throws IOException 218 * if an error occurs obtaining the content. 219 */ 220 // Param is not generic in spec 221 @SuppressWarnings("unchecked") 222 public Object getContent(Class[] types) throws IOException { 223 if (!connected) { 224 connect(); 225 } 226 227 if ((contentType = getContentType()) == null) { 228 if ((contentType = guessContentTypeFromName(url.getFile())) == null) { 229 contentType = guessContentTypeFromStream(getInputStream()); 230 } 231 } 232 if (contentType != null) { 233 return getContentHandler(contentType).getContent(this, types); 234 } 235 return null; 236 } 237 238 /** 239 * Returns the content encoding type specified by the response header field 240 * {@code content-encoding} or {@code null} if this field is not set. 241 * 242 * @return the value of the response header field {@code content-encoding}. 243 */ 244 public String getContentEncoding() { 245 return getHeaderField("Content-Encoding"); 246 } 247 248 /** 249 * Returns the specific ContentHandler that will handle the type {@code 250 * contentType}. 251 * 252 * @param type 253 * The type that needs to be handled 254 * @return An instance of the Content Handler 255 */ 256 private ContentHandler getContentHandler(String type) throws IOException { 257 // Replace all non-alphanumeric character by '_' 258 final String typeString = parseTypeString(type.replace('/', '.')); 259 260 // if there's a cached content handler, use it 261 Object cHandler = contentHandlers.get(type); 262 if (cHandler != null) { 263 return (ContentHandler) cHandler; 264 } 265 266 if (contentHandlerFactory != null) { 267 cHandler = contentHandlerFactory.createContentHandler(type); 268 contentHandlers.put(type, cHandler); 269 return (ContentHandler) cHandler; 270 } 271 272 // search through the package list for the right class for the Content Type 273 String packageList = System.getProperty("java.content.handler.pkgs"); 274 if (packageList != null) { 275 for (String packageName : packageList.split("\\|")) { 276 String className = packageName + "." + typeString; 277 try { 278 Class<?> klass = Class.forName(className, true, ClassLoader.getSystemClassLoader()); 279 cHandler = klass.newInstance(); 280 } catch (ClassNotFoundException e) { 281 } catch (IllegalAccessException e) { 282 } catch (InstantiationException e) { 283 } 284 } 285 } 286 287 if (cHandler == null) { 288 try { 289 // Try looking up AWT image content handlers 290 String className = "org.apache.harmony.awt.www.content." + typeString; 291 cHandler = Class.forName(className).newInstance(); 292 } catch (ClassNotFoundException e) { 293 } catch (IllegalAccessException e) { 294 } catch (InstantiationException e) { 295 } 296 } 297 if (cHandler != null) { 298 if (!(cHandler instanceof ContentHandler)) { 299 throw new UnknownServiceException(); 300 } 301 contentHandlers.put(type, cHandler); // if we got the handler, 302 // cache it for next time 303 return (ContentHandler) cHandler; 304 } 305 306 return defaultHandler; 307 } 308 309 /** 310 * Returns the content length in bytes specified by the response header field 311 * {@code content-length} or {@code -1} if this field is not set. 312 * 313 * @return the value of the response header field {@code content-length}. 314 */ 315 public int getContentLength() { 316 return getHeaderFieldInt("Content-Length", -1); 317 } 318 319 /** 320 * Returns the MIME-type of the content specified by the response header field 321 * {@code content-type} or {@code null} if type is unknown. 322 * 323 * @return the value of the response header field {@code content-type}. 324 */ 325 public String getContentType() { 326 return getHeaderField("Content-Type"); 327 } 328 329 /** 330 * Returns the timestamp when this response has been sent as a date in 331 * milliseconds since January 1, 1970 GMT or {@code 0} if this timestamp is 332 * unknown. 333 * 334 * @return the sending timestamp of the current response. 335 */ 336 public long getDate() { 337 return getHeaderFieldDate("Date", 0); 338 } 339 340 /** 341 * Returns the default value of {@code allowUserInteraction}. Unused by Android. 342 */ 343 public static boolean getDefaultAllowUserInteraction() { 344 return defaultAllowUserInteraction; 345 } 346 347 /** 348 * Returns null. 349 * 350 * @deprecated Use {@link #getRequestProperty} instead. 351 */ 352 @Deprecated 353 public static String getDefaultRequestProperty(String field) { 354 return null; 355 } 356 357 /** 358 * Returns the default setting whether this connection allows using caches. 359 * 360 * @return the value of the default setting {@code defaultUseCaches}. 361 * @see #useCaches 362 */ 363 public boolean getDefaultUseCaches() { 364 return defaultUseCaches; 365 } 366 367 /** 368 * Returns the value of the option {@code doInput} which specifies whether this 369 * connection allows to receive data. 370 * 371 * @return {@code true} if this connection allows input, {@code false} 372 * otherwise. 373 * @see #doInput 374 */ 375 public boolean getDoInput() { 376 return doInput; 377 } 378 379 /** 380 * Returns the value of the option {@code doOutput} which specifies whether 381 * this connection allows to send data. 382 * 383 * @return {@code true} if this connection allows output, {@code false} 384 * otherwise. 385 * @see #doOutput 386 */ 387 public boolean getDoOutput() { 388 return doOutput; 389 } 390 391 /** 392 * Returns the timestamp when this response will be expired in milliseconds 393 * since January 1, 1970 GMT or {@code 0} if this timestamp is unknown. 394 * 395 * @return the value of the response header field {@code expires}. 396 */ 397 public long getExpiration() { 398 return getHeaderFieldDate("Expires", 0); 399 } 400 401 /** 402 * Returns the table which is used by all {@code URLConnection} instances to 403 * determine the MIME-type according to a file extension. 404 * 405 * @return the file name map to determine the MIME-type. 406 */ 407 public static FileNameMap getFileNameMap() { 408 synchronized (URLConnection.class) { 409 if (fileNameMap == null) { 410 fileNameMap = new DefaultFileNameMap(); 411 } 412 return fileNameMap; 413 } 414 } 415 416 /** 417 * Returns the header value at the field position {@code pos} or {@code null} 418 * if the header has fewer than {@code pos} fields. The base 419 * implementation of this method returns always {@code null}. 420 * 421 * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping 422 * for the null key; in HTTP's case, this maps to the HTTP status line and is 423 * treated as being at position 0 when indexing into the header fields. 424 * 425 * @param pos 426 * the field position of the response header. 427 * @return the value of the field at position {@code pos}. 428 */ 429 public String getHeaderField(int pos) { 430 return null; 431 } 432 433 /** 434 * Returns an unmodifiable map of the response-header fields and values. The 435 * response-header field names are the key values of the map. The map values 436 * are lists of header field values associated with a particular key name. 437 * 438 * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping 439 * for the null key; in HTTP's case, this maps to the HTTP status line and is 440 * treated as being at position 0 when indexing into the header fields. 441 * 442 * @return the response-header representing generic map. 443 * @since 1.4 444 */ 445 public Map<String, List<String>> getHeaderFields() { 446 return Collections.emptyMap(); 447 } 448 449 /** 450 * Returns an unmodifiable map of general request properties used by this 451 * connection. The request property names are the key values of the map. The 452 * map values are lists of property values of the corresponding key name. 453 * 454 * @return the request-property representing generic map. 455 * @since 1.4 456 */ 457 public Map<String, List<String>> getRequestProperties() { 458 checkNotConnected(); 459 return Collections.emptyMap(); 460 } 461 462 private void checkNotConnected() { 463 if (connected) { 464 throw new IllegalStateException("Already connected"); 465 } 466 } 467 468 /** 469 * Adds the given property to the request header. Existing properties with 470 * the same name will not be overwritten by this method. 471 * 472 * @param field 473 * the request property field name to add. 474 * @param newValue 475 * the value of the property which is to add. 476 * @throws IllegalStateException 477 * if the connection has been already established. 478 * @throws NullPointerException 479 * if the property name is {@code null}. 480 * @since 1.4 481 */ 482 public void addRequestProperty(String field, String newValue) { 483 checkNotConnected(); 484 if (field == null) { 485 throw new NullPointerException("field == null"); 486 } 487 } 488 489 /** 490 * Returns the value of the header field specified by {@code key} or {@code 491 * null} if there is no field with this name. The base implementation of 492 * this method returns always {@code null}. 493 * 494 * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping 495 * for the null key; in HTTP's case, this maps to the HTTP status line and is 496 * treated as being at position 0 when indexing into the header fields. 497 * 498 * @param key 499 * the name of the header field. 500 * @return the value of the header field. 501 */ 502 public String getHeaderField(String key) { 503 return null; 504 } 505 506 /** 507 * Returns the specified header value as a date in milliseconds since January 508 * 1, 1970 GMT. Returns the {@code defaultValue} if no such header field 509 * could be found. 510 * 511 * @param field 512 * the header field name whose value is needed. 513 * @param defaultValue 514 * the default value if no field has been found. 515 * @return the value of the specified header field as a date in 516 * milliseconds. 517 */ 518 @SuppressWarnings("deprecation") 519 public long getHeaderFieldDate(String field, long defaultValue) { 520 String date = getHeaderField(field); 521 if (date == null) { 522 return defaultValue; 523 } 524 try { 525 return Date.parse(date); // TODO: use HttpDate.parse() 526 } catch (Exception e) { 527 return defaultValue; 528 } 529 } 530 531 /** 532 * Returns the specified header value as a number. Returns the {@code 533 * defaultValue} if no such header field could be found or the value could 534 * not be parsed as an {@code Integer}. 535 * 536 * @param field 537 * the header field name whose value is needed. 538 * @param defaultValue 539 * the default value if no field has been found. 540 * @return the value of the specified header field as a number. 541 */ 542 public int getHeaderFieldInt(String field, int defaultValue) { 543 try { 544 return Integer.parseInt(getHeaderField(field)); 545 } catch (NumberFormatException e) { 546 return defaultValue; 547 } 548 } 549 550 /** 551 * Returns the name of the header field at the given position {@code posn} or 552 * {@code null} if there are fewer than {@code posn} fields. The base 553 * implementation of this method returns always {@code null}. 554 * 555 * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping 556 * for the null key; in HTTP's case, this maps to the HTTP status line and is 557 * treated as being at position 0 when indexing into the header fields. 558 * 559 * @param posn 560 * the position of the header field which has to be returned. 561 * @return the header field name at the given position. 562 */ 563 public String getHeaderFieldKey(int posn) { 564 return null; 565 } 566 567 /** 568 * Returns the point of time since when the data must be modified to be 569 * transmitted. Some protocols transmit data only if it has been modified 570 * more recently than a particular time. 571 * 572 * @return the time in milliseconds since January 1, 1970 GMT. 573 * @see #ifModifiedSince 574 */ 575 public long getIfModifiedSince() { 576 return ifModifiedSince; 577 } 578 579 /** 580 * Returns an {@code InputStream} for reading data from the resource pointed by 581 * this {@code URLConnection}. It throws an UnknownServiceException by 582 * default. This method must be overridden by its subclasses. 583 * 584 * @return the InputStream to read data from. 585 * @throws IOException 586 * if no InputStream could be created. 587 */ 588 public InputStream getInputStream() throws IOException { 589 throw new UnknownServiceException("Does not support writing to the input stream"); 590 } 591 592 /** 593 * Returns the value of the response header field {@code last-modified} or 594 * {@code 0} if this value is not set. 595 * 596 * @return the value of the {@code last-modified} header field. 597 */ 598 public long getLastModified() { 599 if (lastModified != -1) { 600 return lastModified; 601 } 602 return lastModified = getHeaderFieldDate("Last-Modified", 0); 603 } 604 605 /** 606 * Returns an {@code OutputStream} for writing data to this {@code 607 * URLConnection}. It throws an {@code UnknownServiceException} by default. 608 * This method must be overridden by its subclasses. 609 * 610 * @return the OutputStream to write data. 611 * @throws IOException 612 * if no OutputStream could be created. 613 */ 614 public OutputStream getOutputStream() throws IOException { 615 throw new UnknownServiceException("Does not support writing to the output stream"); 616 } 617 618 /** 619 * Returns a {@code Permission} object representing all needed permissions to 620 * open this connection. The returned permission object depends on the state 621 * of the connection and will be {@code null} if no permissions are 622 * necessary. By default, this method returns {@code AllPermission}. 623 * Subclasses should overwrite this method to return an appropriate 624 * permission object. 625 * 626 * @return the permission object representing the needed permissions to open 627 * this connection. 628 * @throws IOException 629 * if an I/O error occurs while creating the permission object. 630 */ 631 public java.security.Permission getPermission() throws IOException { 632 return new java.security.AllPermission(); 633 } 634 635 /** 636 * Returns the value of the request header property specified by {code field} 637 * or {@code null} if there is no field with this name. The base 638 * implementation of this method returns always {@code null}. 639 * 640 * @param field 641 * the name of the request header property. 642 * @return the value of the property. 643 * @throws IllegalStateException 644 * if the connection has been already established. 645 */ 646 public String getRequestProperty(String field) { 647 checkNotConnected(); 648 return null; 649 } 650 651 /** 652 * Returns the URL represented by this {@code URLConnection}. 653 * 654 * @return the URL of this connection. 655 */ 656 public URL getURL() { 657 return url; 658 } 659 660 /** 661 * Returns the value of the flag which specifies whether this {@code 662 * URLConnection} allows to use caches. 663 * 664 * @return {@code true} if using caches is allowed, {@code false} otherwise. 665 */ 666 public boolean getUseCaches() { 667 return useCaches; 668 } 669 670 /** 671 * Determines the MIME-type of the given resource {@code url} by resolving 672 * the filename extension with the internal FileNameMap. Any fragment 673 * identifier is removed before processing. 674 * 675 * @param url 676 * the URL with the filename to get the MIME type. 677 * @return the guessed content type or {@code null} if the type could not be 678 * determined. 679 */ 680 public static String guessContentTypeFromName(String url) { 681 return getFileNameMap().getContentTypeFor(url); 682 } 683 684 /** 685 * Determines the MIME-type of the resource represented by the input stream 686 * {@code is} by reading its first few characters. 687 * 688 * @param is 689 * the resource representing input stream to determine the 690 * content type. 691 * @return the guessed content type or {@code null} if the type could not be 692 * determined. 693 * @throws IOException 694 * if an I/O error occurs while reading from the input stream. 695 */ 696 public static String guessContentTypeFromStream(InputStream is) throws IOException { 697 if (!is.markSupported()) { 698 return null; 699 } 700 // Look ahead up to 64 bytes for the longest encoded header 701 is.mark(64); 702 byte[] bytes = new byte[64]; 703 int length = is.read(bytes); 704 is.reset(); 705 706 // If there is no data from the input stream, we can't determine content type. 707 if (length == -1) { 708 return null; 709 } 710 711 // Check for Unicode BOM encoding indicators 712 String encoding = "US-ASCII"; 713 int start = 0; 714 if (length > 1) { 715 if ((bytes[0] == (byte) 0xFF) && (bytes[1] == (byte) 0xFE)) { 716 encoding = "UTF-16LE"; 717 start = 2; 718 length -= length & 1; 719 } 720 if ((bytes[0] == (byte) 0xFE) && (bytes[1] == (byte) 0xFF)) { 721 encoding = "UTF-16BE"; 722 start = 2; 723 length -= length & 1; 724 } 725 if (length > 2) { 726 if ((bytes[0] == (byte) 0xEF) && (bytes[1] == (byte) 0xBB) 727 && (bytes[2] == (byte) 0xBF)) { 728 encoding = "UTF-8"; 729 start = 3; 730 } 731 if (length > 3) { 732 if ((bytes[0] == (byte) 0x00) && (bytes[1] == (byte) 0x00) 733 && (bytes[2] == (byte) 0xFE) 734 && (bytes[3] == (byte) 0xFF)) { 735 encoding = "UTF-32BE"; 736 start = 4; 737 length -= length & 3; 738 } 739 if ((bytes[0] == (byte) 0xFF) && (bytes[1] == (byte) 0xFE) 740 && (bytes[2] == (byte) 0x00) 741 && (bytes[3] == (byte) 0x00)) { 742 encoding = "UTF-32LE"; 743 start = 4; 744 length -= length & 3; 745 } 746 } 747 } 748 } 749 750 String header = new String(bytes, start, length - start, encoding); 751 752 // Check binary types 753 if (header.startsWith("PK")) { 754 return "application/zip"; 755 } 756 if (header.startsWith("GI")) { 757 return "image/gif"; 758 } 759 760 // Check text types 761 String textHeader = header.trim().toUpperCase(Locale.US); 762 if (textHeader.startsWith("<!DOCTYPE HTML") || 763 textHeader.startsWith("<HTML") || 764 textHeader.startsWith("<HEAD") || 765 textHeader.startsWith("<BODY") || 766 textHeader.startsWith("<HEAD")) { 767 return "text/html"; 768 } 769 770 if (textHeader.startsWith("<?XML")) { 771 return "application/xml"; 772 } 773 774 // Give up 775 return null; 776 } 777 778 /** 779 * Performs any necessary string parsing on the input string such as 780 * converting non-alphanumeric character into underscore. 781 * 782 * @param typeString 783 * the parsed string 784 * @return the string to be parsed 785 */ 786 private String parseTypeString(String typeString) { 787 StringBuilder result = new StringBuilder(typeString); 788 for (int i = 0; i < result.length(); i++) { 789 // if non-alphanumeric, replace it with '_' 790 char c = result.charAt(i); 791 if (!(Character.isLetter(c) || Character.isDigit(c) || c == '.')) { 792 result.setCharAt(i, '_'); 793 } 794 } 795 return result.toString(); 796 } 797 798 /** 799 * Sets {@code allowUserInteraction}. Unused by Android. 800 */ 801 public void setAllowUserInteraction(boolean newValue) { 802 checkNotConnected(); 803 this.allowUserInteraction = newValue; 804 } 805 806 /** 807 * Sets the internally used content handler factory. The content factory can 808 * only be set once during the lifetime of the application. 809 * 810 * @param contentFactory 811 * the content factory to be set. 812 * @throws Error 813 * if the factory has been already set. 814 */ 815 public static synchronized void setContentHandlerFactory(ContentHandlerFactory contentFactory) { 816 if (contentHandlerFactory != null) { 817 throw new Error("Factory already set"); 818 } 819 contentHandlerFactory = contentFactory; 820 } 821 822 /** 823 * Sets the default value for {@code allowUserInteraction}. Unused by Android. 824 */ 825 public static void setDefaultAllowUserInteraction(boolean allows) { 826 defaultAllowUserInteraction = allows; 827 } 828 829 /** 830 * Does nothing. 831 * 832 * @deprecated Use {@link URLConnection#setRequestProperty(String, String)} instead. 833 */ 834 @Deprecated 835 public static void setDefaultRequestProperty(String field, String value) { 836 } 837 838 /** 839 * Sets the default value for the flag indicating whether this connection 840 * allows to use caches. Existing {@code URLConnection}s are unaffected. 841 * 842 * @param newValue 843 * the default value of the flag to be used for new connections. 844 * @see #useCaches 845 */ 846 public void setDefaultUseCaches(boolean newValue) { 847 defaultUseCaches = newValue; 848 } 849 850 /** 851 * Sets the flag indicating whether this {@code URLConnection} allows input. 852 * It cannot be set after the connection is established. 853 * 854 * @param newValue 855 * the new value for the flag to be set. 856 * @throws IllegalAccessError 857 * if this method attempts to change the value after the 858 * connection has been already established. 859 * @see #doInput 860 */ 861 public void setDoInput(boolean newValue) { 862 checkNotConnected(); 863 this.doInput = newValue; 864 } 865 866 /** 867 * Sets the flag indicating whether this {@code URLConnection} allows 868 * output. It cannot be set after the connection is established. 869 * 870 * @param newValue 871 * the new value for the flag to be set. 872 * @throws IllegalAccessError 873 * if this method attempts to change the value after the 874 * connection has been already established. 875 * @see #doOutput 876 */ 877 public void setDoOutput(boolean newValue) { 878 checkNotConnected(); 879 this.doOutput = newValue; 880 } 881 882 /** 883 * Sets the internal map which is used by all {@code URLConnection} 884 * instances to determine the MIME-type according to a filename extension. 885 * 886 * @param map 887 * the MIME table to be set. 888 */ 889 public static void setFileNameMap(FileNameMap map) { 890 synchronized (URLConnection.class) { 891 fileNameMap = map; 892 } 893 } 894 895 /** 896 * Sets the point of time since when the data must be modified to be 897 * transmitted. Some protocols transmit data only if it has been modified 898 * more recently than a particular time. The data will be transmitted 899 * regardless of its timestamp if this option is set to {@code 0}. 900 * 901 * @param newValue 902 * the time in milliseconds since January 1, 1970 GMT. 903 * @throws IllegalStateException 904 * if this {@code URLConnection} has already been connected. 905 * @see #ifModifiedSince 906 */ 907 public void setIfModifiedSince(long newValue) { 908 checkNotConnected(); 909 this.ifModifiedSince = newValue; 910 } 911 912 /** 913 * Sets the value of the specified request header field. The value will only 914 * be used by the current {@code URLConnection} instance. This method can 915 * only be called before the connection is established. 916 * 917 * @param field 918 * the request header field to be set. 919 * @param newValue 920 * the new value of the specified property. 921 * @throws IllegalStateException 922 * if the connection has been already established. 923 * @throws NullPointerException 924 * if the parameter {@code field} is {@code null}. 925 */ 926 public void setRequestProperty(String field, String newValue) { 927 checkNotConnected(); 928 if (field == null) { 929 throw new NullPointerException("field == null"); 930 } 931 } 932 933 /** 934 * Sets the flag indicating whether this connection allows to use caches or 935 * not. This method can only be called prior to the connection 936 * establishment. 937 * 938 * @param newValue 939 * the value of the flag to be set. 940 * @throws IllegalStateException 941 * if this method attempts to change the flag after the 942 * connection has been established. 943 * @see #useCaches 944 */ 945 public void setUseCaches(boolean newValue) { 946 checkNotConnected(); 947 this.useCaches = newValue; 948 } 949 950 /** 951 * Sets the maximum time in milliseconds to wait while connecting. 952 * Connecting to a server will fail with a {@link SocketTimeoutException} if 953 * the timeout elapses before a connection is established. The default value 954 * of {@code 0} causes us to do a blocking connect. This does not mean we 955 * will never time out, but it probably means you'll get a TCP timeout 956 * after several minutes. 957 * 958 * <p><strong>Warning:</strong> if the hostname resolves to multiple IP 959 * addresses, this client will try each in <a 960 * href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order. If 961 * connecting to each of these addresses fails, multiple timeouts will 962 * elapse before the connect attempt throws an exception. Host names that 963 * support both IPv6 and IPv4 always have at least 2 IP addresses. 964 * 965 * @throws IllegalArgumentException if {@code timeoutMillis < 0}. 966 */ 967 public void setConnectTimeout(int timeoutMillis) { 968 if (timeoutMillis < 0) { 969 throw new IllegalArgumentException("timeoutMillis < 0"); 970 } 971 this.connectTimeout = timeoutMillis; 972 } 973 974 /** 975 * Returns the connect timeout in milliseconds. (See {#setConnectTimeout}.) 976 */ 977 public int getConnectTimeout() { 978 return connectTimeout; 979 } 980 981 /** 982 * Sets the maximum time to wait for an input stream read to complete before 983 * giving up. Reading will fail with a {@link SocketTimeoutException} if the 984 * timeout elapses before data becomes available. The default value of 985 * {@code 0} disables read timeouts; read attempts will block indefinitely. 986 * 987 * @param timeoutMillis the read timeout in milliseconds. Non-negative. 988 */ 989 public void setReadTimeout(int timeoutMillis) { 990 if (timeoutMillis < 0) { 991 throw new IllegalArgumentException("timeoutMillis < 0"); 992 } 993 this.readTimeout = timeoutMillis; 994 } 995 996 /** 997 * Returns the read timeout in milliseconds, or {@code 0} if reads never 998 * timeout. 999 */ 1000 public int getReadTimeout() { 1001 return readTimeout; 1002 } 1003 1004 /** 1005 * Returns the string representation containing the name of this class and 1006 * the URL. 1007 * 1008 * @return the string representation of this {@code URLConnection} instance. 1009 */ 1010 @Override 1011 public String toString() { 1012 return getClass().getName() + ":" + url.toString(); 1013 } 1014 1015 static class DefaultContentHandler extends java.net.ContentHandler { 1016 @Override 1017 public Object getContent(URLConnection u) throws IOException { 1018 return u.getInputStream(); 1019 } 1020 } 1021 } 1022