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.io.InputStream;
22import java.io.OutputStream;
23import java.util.Collections;
24import java.util.Date;
25import java.util.Hashtable;
26import java.util.List;
27import java.util.Locale;
28import 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 */
79public 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 &lt; 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