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 or cannot be represented as an
312     * {@code int}.
313     */
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     */
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     */
335    public long getDate() {
336        return getHeaderFieldDate("Date", 0);
337    }
338
339    /**
340     * Returns the default value of {@code allowUserInteraction}. Unused by Android.
341     */
342    public static boolean getDefaultAllowUserInteraction() {
343        return defaultAllowUserInteraction;
344    }
345
346    /**
347     * Returns null.
348     *
349     * @deprecated Use {@link #getRequestProperty} instead.
350     */
351    @Deprecated
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
456    public Map<String, List<String>> getRequestProperties() {
457        checkNotConnected();
458        return Collections.emptyMap();
459    }
460
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     */
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     */
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")
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
1010    public String toString() {
1011        return getClass().getName() + ":" + url.toString();
1012    }
1013
1014    static class DefaultContentHandler extends java.net.ContentHandler {
1015        @Override
1016        public Object getContent(URLConnection u) throws IOException {
1017            return u.getInputStream();
1018        }
1019    }
1020}
1021