• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
URLConnection(URL url)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      */
connect()167     public abstract void connect() throws IOException;
168 
169     /**
170      * Returns {@code allowUserInteraction}. Unused by Android.
171      */
getAllowUserInteraction()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      */
getContent()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")
getContent(Class[] types)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      */
getContentEncoding()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      */
getContentHandler(String type)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 or cannot be represented as an
312      * {@code int}.
313      */
getContentLength()314     public int getContentLength() {
315         return getHeaderFieldInt("Content-Length", -1);
316     }
317 
318     /**
319      * Returns the MIME-type of the content specified by the response header field
320      * {@code content-type} or {@code null} if type is unknown.
321      *
322      * @return the value of the response header field {@code content-type}.
323      */
getContentType()324     public String getContentType() {
325         return getHeaderField("Content-Type");
326     }
327 
328     /**
329      * Returns the timestamp when this response has been sent as a date in
330      * milliseconds since January 1, 1970 GMT or {@code 0} if this timestamp is
331      * unknown.
332      *
333      * @return the sending timestamp of the current response.
334      */
getDate()335     public long getDate() {
336         return getHeaderFieldDate("Date", 0);
337     }
338 
339     /**
340      * Returns the default value of {@code allowUserInteraction}. Unused by Android.
341      */
getDefaultAllowUserInteraction()342     public static boolean getDefaultAllowUserInteraction() {
343         return defaultAllowUserInteraction;
344     }
345 
346     /**
347      * Returns null.
348      *
349      * @deprecated Use {@link #getRequestProperty} instead.
350      */
351     @Deprecated
getDefaultRequestProperty(String field)352     public static String getDefaultRequestProperty(String field) {
353         return null;
354     }
355 
356     /**
357      * Returns the default setting whether this connection allows using caches.
358      *
359      * @return the value of the default setting {@code defaultUseCaches}.
360      * @see #useCaches
361      */
getDefaultUseCaches()362     public boolean getDefaultUseCaches() {
363         return defaultUseCaches;
364     }
365 
366     /**
367      * Returns the value of the option {@code doInput} which specifies whether this
368      * connection allows to receive data.
369      *
370      * @return {@code true} if this connection allows input, {@code false}
371      *         otherwise.
372      * @see #doInput
373      */
getDoInput()374     public boolean getDoInput() {
375         return doInput;
376     }
377 
378     /**
379      * Returns the value of the option {@code doOutput} which specifies whether
380      * this connection allows to send data.
381      *
382      * @return {@code true} if this connection allows output, {@code false}
383      *         otherwise.
384      * @see #doOutput
385      */
getDoOutput()386     public boolean getDoOutput() {
387         return doOutput;
388     }
389 
390     /**
391      * Returns the timestamp when this response will be expired in milliseconds
392      * since January 1, 1970 GMT or {@code 0} if this timestamp is unknown.
393      *
394      * @return the value of the response header field {@code expires}.
395      */
getExpiration()396     public long getExpiration() {
397         return getHeaderFieldDate("Expires", 0);
398     }
399 
400     /**
401      * Returns the table which is used by all {@code URLConnection} instances to
402      * determine the MIME-type according to a file extension.
403      *
404      * @return the file name map to determine the MIME-type.
405      */
getFileNameMap()406     public static FileNameMap getFileNameMap() {
407         synchronized (URLConnection.class) {
408             if (fileNameMap == null) {
409                 fileNameMap = new DefaultFileNameMap();
410             }
411             return fileNameMap;
412         }
413     }
414 
415     /**
416      * Returns the header value at the field position {@code pos} or {@code null}
417      * if the header has fewer than {@code pos} fields. The base
418      * implementation of this method returns always {@code null}.
419      *
420      * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping
421      * for the null key; in HTTP's case, this maps to the HTTP status line and is
422      * treated as being at position 0 when indexing into the header fields.
423      *
424      * @param pos
425      *            the field position of the response header.
426      * @return the value of the field at position {@code pos}.
427      */
getHeaderField(int pos)428     public String getHeaderField(int pos) {
429         return null;
430     }
431 
432     /**
433      * Returns an unmodifiable map of the response-header fields and values. The
434      * response-header field names are the key values of the map. The map values
435      * are lists of header field values associated with a particular key name.
436      *
437      * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping
438      * for the null key; in HTTP's case, this maps to the HTTP status line and is
439      * treated as being at position 0 when indexing into the header fields.
440      *
441      * @return the response-header representing generic map.
442      * @since 1.4
443      */
getHeaderFields()444     public Map<String, List<String>> getHeaderFields() {
445         return Collections.emptyMap();
446     }
447 
448     /**
449      * Returns an unmodifiable map of general request properties used by this
450      * connection. The request property names are the key values of the map. The
451      * map values are lists of property values of the corresponding key name.
452      *
453      * @return the request-property representing generic map.
454      * @since 1.4
455      */
getRequestProperties()456     public Map<String, List<String>> getRequestProperties() {
457         checkNotConnected();
458         return Collections.emptyMap();
459     }
460 
checkNotConnected()461     private void checkNotConnected() {
462         if (connected) {
463             throw new IllegalStateException("Already connected");
464         }
465     }
466 
467     /**
468      * Adds the given property to the request header. Existing properties with
469      * the same name will not be overwritten by this method.
470      *
471      * @param field
472      *            the request property field name to add.
473      * @param newValue
474      *            the value of the property which is to add.
475      * @throws IllegalStateException
476      *             if the connection has been already established.
477      * @throws NullPointerException
478      *             if the property name is {@code null}.
479      * @since 1.4
480      */
addRequestProperty(String field, String newValue)481     public void addRequestProperty(String field, String newValue) {
482         checkNotConnected();
483         if (field == null) {
484             throw new NullPointerException("field == null");
485         }
486     }
487 
488     /**
489      * Returns the value of the header field specified by {@code key} or {@code
490      * null} if there is no field with this name. The base implementation of
491      * this method returns always {@code null}.
492      *
493      * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping
494      * for the null key; in HTTP's case, this maps to the HTTP status line and is
495      * treated as being at position 0 when indexing into the header fields.
496      *
497      * @param key
498      *            the name of the header field.
499      * @return the value of the header field.
500      */
getHeaderField(String key)501     public String getHeaderField(String key) {
502         return null;
503     }
504 
505     /**
506      * Returns the specified header value as a date in milliseconds since January
507      * 1, 1970 GMT. Returns the {@code defaultValue} if no such header field
508      * could be found.
509      *
510      * @param field
511      *            the header field name whose value is needed.
512      * @param defaultValue
513      *            the default value if no field has been found.
514      * @return the value of the specified header field as a date in
515      *         milliseconds.
516      */
517     @SuppressWarnings("deprecation")
getHeaderFieldDate(String field, long defaultValue)518     public long getHeaderFieldDate(String field, long defaultValue) {
519         String date = getHeaderField(field);
520         if (date == null) {
521             return defaultValue;
522         }
523         try {
524             return Date.parse(date); // TODO: use HttpDate.parse()
525         } catch (Exception e) {
526             return defaultValue;
527         }
528     }
529 
530     /**
531      * Returns the specified header value as a number. Returns the {@code
532      * defaultValue} if no such header field could be found or the value could
533      * not be parsed as an {@code int}.
534      *
535      * @param field
536      *            the header field name whose value is needed.
537      * @param defaultValue
538      *            the default value if no field has been found.
539      * @return the value of the specified header field as a number.
540      */
getHeaderFieldInt(String field, int defaultValue)541     public int getHeaderFieldInt(String field, int defaultValue) {
542         try {
543             return Integer.parseInt(getHeaderField(field));
544         } catch (NumberFormatException e) {
545             return defaultValue;
546         }
547     }
548 
549     /**
550      * Returns the name of the header field at the given position {@code posn} or
551      * {@code null} if there are fewer than {@code posn} fields. The base
552      * implementation of this method returns always {@code null}.
553      *
554      * <p>Some implementations (notably {@code HttpURLConnection}) include a mapping
555      * for the null key; in HTTP's case, this maps to the HTTP status line and is
556      * treated as being at position 0 when indexing into the header fields.
557      *
558      * @param posn
559      *            the position of the header field which has to be returned.
560      * @return the header field name at the given position.
561      */
getHeaderFieldKey(int posn)562     public String getHeaderFieldKey(int posn) {
563         return null;
564     }
565 
566     /**
567      * Returns the point of time since when the data must be modified to be
568      * transmitted. Some protocols transmit data only if it has been modified
569      * more recently than a particular time.
570      *
571      * @return the time in milliseconds since January 1, 1970 GMT.
572      * @see #ifModifiedSince
573      */
getIfModifiedSince()574     public long getIfModifiedSince() {
575         return ifModifiedSince;
576     }
577 
578     /**
579      * Returns an {@code InputStream} for reading data from the resource pointed by
580      * this {@code URLConnection}. It throws an UnknownServiceException by
581      * default. This method must be overridden by its subclasses.
582      *
583      * @return the InputStream to read data from.
584      * @throws IOException
585      *             if no InputStream could be created.
586      */
getInputStream()587     public InputStream getInputStream() throws IOException {
588         throw new UnknownServiceException("Does not support writing to the input stream");
589     }
590 
591     /**
592      * Returns the value of the response header field {@code last-modified} or
593      * {@code 0} if this value is not set.
594      *
595      * @return the value of the {@code last-modified} header field.
596      */
getLastModified()597     public long getLastModified() {
598         if (lastModified != -1) {
599             return lastModified;
600         }
601         return lastModified = getHeaderFieldDate("Last-Modified", 0);
602     }
603 
604     /**
605      * Returns an {@code OutputStream} for writing data to this {@code
606      * URLConnection}. It throws an {@code UnknownServiceException} by default.
607      * This method must be overridden by its subclasses.
608      *
609      * @return the OutputStream to write data.
610      * @throws IOException
611      *             if no OutputStream could be created.
612      */
getOutputStream()613     public OutputStream getOutputStream() throws IOException {
614         throw new UnknownServiceException("Does not support writing to the output stream");
615     }
616 
617     /**
618      * Returns a {@code Permission} object representing all needed permissions to
619      * open this connection. The returned permission object depends on the state
620      * of the connection and will be {@code null} if no permissions are
621      * necessary. By default, this method returns {@code AllPermission}.
622      * Subclasses should overwrite this method to return an appropriate
623      * permission object.
624      *
625      * @return the permission object representing the needed permissions to open
626      *         this connection.
627      * @throws IOException
628      *             if an I/O error occurs while creating the permission object.
629      */
getPermission()630     public java.security.Permission getPermission() throws IOException {
631         return new java.security.AllPermission();
632     }
633 
634     /**
635      * Returns the value of the request header property specified by {code field}
636      * or {@code null} if there is no field with this name. The base
637      * implementation of this method returns always {@code null}.
638      *
639      * @param field
640      *            the name of the request header property.
641      * @return the value of the property.
642      * @throws IllegalStateException
643      *             if the connection has been already established.
644      */
getRequestProperty(String field)645     public String getRequestProperty(String field) {
646         checkNotConnected();
647         return null;
648     }
649 
650     /**
651      * Returns the URL represented by this {@code URLConnection}.
652      *
653      * @return the URL of this connection.
654      */
getURL()655     public URL getURL() {
656         return url;
657     }
658 
659     /**
660      * Returns the value of the flag which specifies whether this {@code
661      * URLConnection} allows to use caches.
662      *
663      * @return {@code true} if using caches is allowed, {@code false} otherwise.
664      */
getUseCaches()665     public boolean getUseCaches() {
666         return useCaches;
667     }
668 
669     /**
670      * Determines the MIME-type of the given resource {@code url} by resolving
671      * the filename extension with the internal FileNameMap. Any fragment
672      * identifier is removed before processing.
673      *
674      * @param url
675      *            the URL with the filename to get the MIME type.
676      * @return the guessed content type or {@code null} if the type could not be
677      *         determined.
678      */
guessContentTypeFromName(String url)679     public static String guessContentTypeFromName(String url) {
680         return getFileNameMap().getContentTypeFor(url);
681     }
682 
683     /**
684      * Determines the MIME-type of the resource represented by the input stream
685      * {@code is} by reading its first few characters.
686      *
687      * @param is
688      *            the resource representing input stream to determine the
689      *            content type.
690      * @return the guessed content type or {@code null} if the type could not be
691      *         determined.
692      * @throws IOException
693      *             if an I/O error occurs while reading from the input stream.
694      */
guessContentTypeFromStream(InputStream is)695     public static String guessContentTypeFromStream(InputStream is) throws IOException {
696         if (!is.markSupported()) {
697             return null;
698         }
699         // Look ahead up to 64 bytes for the longest encoded header
700         is.mark(64);
701         byte[] bytes = new byte[64];
702         int length = is.read(bytes);
703         is.reset();
704 
705         // If there is no data from the input stream, we can't determine content type.
706         if (length == -1) {
707             return null;
708         }
709 
710         // Check for Unicode BOM encoding indicators
711         String encoding = "US-ASCII";
712         int start = 0;
713         if (length > 1) {
714             if ((bytes[0] == (byte) 0xFF) && (bytes[1] == (byte) 0xFE)) {
715                 encoding = "UTF-16LE";
716                 start = 2;
717                 length -= length & 1;
718             }
719             if ((bytes[0] == (byte) 0xFE) && (bytes[1] == (byte) 0xFF)) {
720                 encoding = "UTF-16BE";
721                 start = 2;
722                 length -= length & 1;
723             }
724             if (length > 2) {
725                 if ((bytes[0] == (byte) 0xEF) && (bytes[1] == (byte) 0xBB)
726                         && (bytes[2] == (byte) 0xBF)) {
727                     encoding = "UTF-8";
728                     start = 3;
729                 }
730                 if (length > 3) {
731                     if ((bytes[0] == (byte) 0x00) && (bytes[1] == (byte) 0x00)
732                             && (bytes[2] == (byte) 0xFE)
733                             && (bytes[3] == (byte) 0xFF)) {
734                         encoding = "UTF-32BE";
735                         start = 4;
736                         length -= length & 3;
737                     }
738                     if ((bytes[0] == (byte) 0xFF) && (bytes[1] == (byte) 0xFE)
739                             && (bytes[2] == (byte) 0x00)
740                             && (bytes[3] == (byte) 0x00)) {
741                         encoding = "UTF-32LE";
742                         start = 4;
743                         length -= length & 3;
744                     }
745                 }
746             }
747         }
748 
749         String header = new String(bytes, start, length - start, encoding);
750 
751         // Check binary types
752         if (header.startsWith("PK")) {
753             return "application/zip";
754         }
755         if (header.startsWith("GI")) {
756             return "image/gif";
757         }
758 
759         // Check text types
760         String textHeader = header.trim().toUpperCase(Locale.US);
761         if (textHeader.startsWith("<!DOCTYPE HTML") ||
762                 textHeader.startsWith("<HTML") ||
763                 textHeader.startsWith("<HEAD") ||
764                 textHeader.startsWith("<BODY") ||
765                 textHeader.startsWith("<HEAD")) {
766             return "text/html";
767         }
768 
769         if (textHeader.startsWith("<?XML")) {
770             return "application/xml";
771         }
772 
773         // Give up
774         return null;
775     }
776 
777     /**
778      * Performs any necessary string parsing on the input string such as
779      * converting non-alphanumeric character into underscore.
780      *
781      * @param typeString
782      *            the parsed string
783      * @return the string to be parsed
784      */
parseTypeString(String typeString)785     private String parseTypeString(String typeString) {
786         StringBuilder result = new StringBuilder(typeString);
787         for (int i = 0; i < result.length(); i++) {
788             // if non-alphanumeric, replace it with '_'
789             char c = result.charAt(i);
790             if (!(Character.isLetter(c) || Character.isDigit(c) || c == '.')) {
791                 result.setCharAt(i, '_');
792             }
793         }
794         return result.toString();
795     }
796 
797     /**
798      * Sets {@code allowUserInteraction}. Unused by Android.
799      */
setAllowUserInteraction(boolean newValue)800     public void setAllowUserInteraction(boolean newValue) {
801         checkNotConnected();
802         this.allowUserInteraction = newValue;
803     }
804 
805     /**
806      * Sets the internally used content handler factory. The content factory can
807      * only be set once during the lifetime of the application.
808      *
809      * @param contentFactory
810      *            the content factory to be set.
811      * @throws Error
812      *             if the factory has been already set.
813      */
setContentHandlerFactory(ContentHandlerFactory contentFactory)814     public static synchronized void setContentHandlerFactory(ContentHandlerFactory contentFactory) {
815         if (contentHandlerFactory != null) {
816             throw new Error("Factory already set");
817         }
818         contentHandlerFactory = contentFactory;
819     }
820 
821     /**
822      * Sets the default value for {@code allowUserInteraction}. Unused by Android.
823      */
setDefaultAllowUserInteraction(boolean allows)824     public static void setDefaultAllowUserInteraction(boolean allows) {
825         defaultAllowUserInteraction = allows;
826     }
827 
828     /**
829      * Does nothing.
830      *
831      * @deprecated Use {@link URLConnection#setRequestProperty(String, String)} instead.
832      */
833     @Deprecated
setDefaultRequestProperty(String field, String value)834     public static void setDefaultRequestProperty(String field, String value) {
835     }
836 
837     /**
838      * Sets the default value for the flag indicating whether this connection
839      * allows to use caches. Existing {@code URLConnection}s are unaffected.
840      *
841      * @param newValue
842      *            the default value of the flag to be used for new connections.
843      * @see #useCaches
844      */
setDefaultUseCaches(boolean newValue)845     public void setDefaultUseCaches(boolean newValue) {
846         defaultUseCaches = newValue;
847     }
848 
849     /**
850      * Sets the flag indicating whether this {@code URLConnection} allows input.
851      * It cannot be set after the connection is established.
852      *
853      * @param newValue
854      *            the new value for the flag to be set.
855      * @throws IllegalAccessError
856      *             if this method attempts to change the value after the
857      *             connection has been already established.
858      * @see #doInput
859      */
setDoInput(boolean newValue)860     public void setDoInput(boolean newValue) {
861         checkNotConnected();
862         this.doInput = newValue;
863     }
864 
865     /**
866      * Sets the flag indicating whether this {@code URLConnection} allows
867      * output. It cannot be set after the connection is established.
868      *
869      * @param newValue
870      *            the new value for the flag to be set.
871      * @throws IllegalAccessError
872      *             if this method attempts to change the value after the
873      *             connection has been already established.
874      * @see #doOutput
875      */
setDoOutput(boolean newValue)876     public void setDoOutput(boolean newValue) {
877         checkNotConnected();
878         this.doOutput = newValue;
879     }
880 
881     /**
882      * Sets the internal map which is used by all {@code URLConnection}
883      * instances to determine the MIME-type according to a filename extension.
884      *
885      * @param map
886      *            the MIME table to be set.
887      */
setFileNameMap(FileNameMap map)888     public static void setFileNameMap(FileNameMap map) {
889         synchronized (URLConnection.class) {
890             fileNameMap = map;
891         }
892     }
893 
894     /**
895      * Sets the point of time since when the data must be modified to be
896      * transmitted. Some protocols transmit data only if it has been modified
897      * more recently than a particular time. The data will be transmitted
898      * regardless of its timestamp if this option is set to {@code 0}.
899      *
900      * @param newValue
901      *            the time in milliseconds since January 1, 1970 GMT.
902      * @throws IllegalStateException
903      *             if this {@code URLConnection} has already been connected.
904      * @see #ifModifiedSince
905      */
setIfModifiedSince(long newValue)906     public void setIfModifiedSince(long newValue) {
907         checkNotConnected();
908         this.ifModifiedSince = newValue;
909     }
910 
911     /**
912      * Sets the value of the specified request header field. The value will only
913      * be used by the current {@code URLConnection} instance. This method can
914      * only be called before the connection is established.
915      *
916      * @param field
917      *            the request header field to be set.
918      * @param newValue
919      *            the new value of the specified property.
920      * @throws IllegalStateException
921      *             if the connection has been already established.
922      * @throws NullPointerException
923      *             if the parameter {@code field} is {@code null}.
924      */
setRequestProperty(String field, String newValue)925     public void setRequestProperty(String field, String newValue) {
926         checkNotConnected();
927         if (field == null) {
928             throw new NullPointerException("field == null");
929         }
930     }
931 
932     /**
933      * Sets the flag indicating whether this connection allows to use caches or
934      * not. This method can only be called prior to the connection
935      * establishment.
936      *
937      * @param newValue
938      *            the value of the flag to be set.
939      * @throws IllegalStateException
940      *             if this method attempts to change the flag after the
941      *             connection has been established.
942      * @see #useCaches
943      */
setUseCaches(boolean newValue)944     public void setUseCaches(boolean newValue) {
945         checkNotConnected();
946         this.useCaches = newValue;
947     }
948 
949     /**
950      * Sets the maximum time in milliseconds to wait while connecting.
951      * Connecting to a server will fail with a {@link SocketTimeoutException} if
952      * the timeout elapses before a connection is established. The default value
953      * of {@code 0} causes us to do a blocking connect. This does not mean we
954      * will never time out, but it probably means you'll get a TCP timeout
955      * after several minutes.
956      *
957      * <p><strong>Warning:</strong> if the hostname resolves to multiple IP
958      * addresses, this client will try each in <a
959      * href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order. If
960      * connecting to each of these addresses fails, multiple timeouts will
961      * elapse before the connect attempt throws an exception. Host names that
962      * support both IPv6 and IPv4 always have at least 2 IP addresses.
963      *
964      * @throws IllegalArgumentException if {@code timeoutMillis &lt; 0}.
965      */
setConnectTimeout(int timeoutMillis)966     public void setConnectTimeout(int timeoutMillis) {
967         if (timeoutMillis < 0) {
968             throw new IllegalArgumentException("timeoutMillis < 0");
969         }
970         this.connectTimeout = timeoutMillis;
971     }
972 
973     /**
974      * Returns the connect timeout in milliseconds. (See {#setConnectTimeout}.)
975      */
getConnectTimeout()976     public int getConnectTimeout() {
977         return connectTimeout;
978     }
979 
980     /**
981      * Sets the maximum time to wait for an input stream read to complete before
982      * giving up. Reading will fail with a {@link SocketTimeoutException} if the
983      * timeout elapses before data becomes available. The default value of
984      * {@code 0} disables read timeouts; read attempts will block indefinitely.
985      *
986      * @param timeoutMillis the read timeout in milliseconds. Non-negative.
987      */
setReadTimeout(int timeoutMillis)988     public void setReadTimeout(int timeoutMillis) {
989         if (timeoutMillis < 0) {
990             throw new IllegalArgumentException("timeoutMillis < 0");
991         }
992         this.readTimeout = timeoutMillis;
993     }
994 
995     /**
996      * Returns the read timeout in milliseconds, or {@code 0} if reads never
997      * timeout.
998      */
getReadTimeout()999     public int getReadTimeout() {
1000         return readTimeout;
1001     }
1002 
1003     /**
1004      * Returns the string representation containing the name of this class and
1005      * the URL.
1006      *
1007      * @return the string representation of this {@code URLConnection} instance.
1008      */
1009     @Override
toString()1010     public String toString() {
1011         return getClass().getName() + ":" + url.toString();
1012     }
1013 
1014     static class DefaultContentHandler extends java.net.ContentHandler {
1015         @Override
getContent(URLConnection u)1016         public Object getContent(URLConnection u) throws IOException {
1017             return u.getInputStream();
1018         }
1019     }
1020 }
1021