001    /* URLConnection.java -- Abstract superclass for reading from URL's
002       Copyright (C) 1998, 2002, 2003, 2004, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.net;
040    
041    import gnu.classpath.SystemProperties;
042    
043    import java.io.IOException;
044    import java.io.InputStream;
045    import java.io.OutputStream;
046    import java.security.AllPermission;
047    import java.security.Permission;
048    import java.text.ParsePosition;
049    import java.text.SimpleDateFormat;
050    import java.util.Collections;
051    import java.util.Date;
052    import java.util.List;
053    import java.util.Locale;
054    import java.util.Map;
055    import java.util.StringTokenizer;
056    
057    /**
058     * Written using on-line Java Platform 1.2 API Specification, as well
059     * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
060     * Status:  One guessContentTypeFrom... methods not implemented.
061     *    getContent method assumes content type from response; see comment there.
062     */
063    /**
064     * This class models a connection that retrieves the information pointed
065     * to by a URL object.  This is typically a connection to a remote node
066     * on the network, but could be a simple disk read.
067     * <p>
068     * A URLConnection object is normally created by calling the openConnection()
069     * method of a URL object.  This method is somewhat misnamed because it does
070     * not actually open the connection.  Instead, it return an unconnected
071     * instance of this object.  The caller then has the opportunity to set
072     * various connection options prior to calling the actual connect() method.
073     * <p>
074     * After the connection has been opened, there are a number of methods in
075     * this class that access various attributes of the data, typically
076     * represented by headers sent in advance of the actual data itself.
077     * <p>
078     * Also of note are the getInputStream and getContent() methods which allow
079     * the caller to retrieve the actual data from the connection.  Note that
080     * for some types of connections, writing is also allowed.  The setDoOutput()
081     * method must be called prior to connecing in order to enable this, then
082     * the getOutputStream method called after the connection in order to
083     * obtain a stream to write the output to.
084     * <p>
085     * The getContent() method is of particular note.  This method returns an
086     * Object that encapsulates the data returned.  There is no way do determine
087     * the type of object that will be returned in advance.  This is determined
088     * by the actual content handlers as described in the description of that
089     * method.
090     *
091     * @author Aaron M. Renn (arenn@urbanophile.com)
092     * @author Warren Levy (warrenl@cygnus.com)
093     */
094    public abstract class URLConnection
095    {
096      /**
097       * This is an object that maps filenames to MIME types.  The interface
098       * to do this is implemented by this class, so just create an empty
099       * instance and store it here.
100       */
101      private static FileNameMap fileNameMap;
102    
103      /**
104       * This is the ContentHandlerFactory set by the caller, if any
105       */
106      private static ContentHandlerFactory factory;
107    
108      /**
109       * This is the default value that will be used to determine whether or
110       * not user interaction should be allowed.
111       */
112      private static boolean defaultAllowUserInteraction;
113    
114      /**
115       * This is the default flag indicating whether or not to use caches to
116       * store the data returned from a server
117       */
118      private static boolean defaultUseCaches = true;
119    
120      /**
121       * Default internal content handler factory.
122       */
123      private static ContentHandlerFactory defaultFactory
124        = new gnu.java.net.DefaultContentHandlerFactory();
125    
126      /**
127       * This variable determines whether or not interaction is allowed with
128       * the user.  For example, to prompt for a username and password.
129       */
130      protected boolean allowUserInteraction;
131    
132      /**
133       * Indicates whether or not a connection has been established to the
134       * destination specified in the URL
135       */
136      protected boolean connected;
137    
138      /**
139       * Indicates whether or not input can be read from this URL
140       */
141      protected boolean doInput = true;
142    
143      /**
144       * Indicates whether or not output can be sent to this URL
145       */
146      protected boolean doOutput;
147    
148      /**
149       * If this flag is set, the protocol is allowed to cache data whenever
150       * it can (caching is not guaranteed). If it is not set, the protocol
151       * must a get a fresh copy of the data.
152       * <p>
153       * This field is set by the setUseCaches method and returned by the
154       * getUseCaches method.
155       *
156       * Its default value is that determined by the last invocation of
157       * setDefaultUseCaches
158       */
159      protected boolean useCaches;
160    
161      /**
162       * If this value is non-zero, then the connection will only attempt to
163       * fetch the document pointed to by the URL if the document has been
164       * modified more recently than the date set in this variable.  That date
165       * should be specified as the number of seconds since 1/1/1970 GMT.
166       */
167      protected long ifModifiedSince;
168    
169      /**
170       * This is the URL associated with this connection
171       */
172      protected URL url;
173    
174      private static SimpleDateFormat[] dateFormats;
175      private static boolean dateformats_initialized;
176    
177      /**
178       * The connection timeout period.
179       */
180      private int connectTimeout;
181    
182      /**
183       * The read timeout period.
184       */
185      private int readTimeout;
186    
187      /* Cached ParsePosition, used when parsing dates. */
188      private ParsePosition position;
189    
190      /**
191       * Creates a URL connection to a given URL. A real connection is not made.
192       * Use <code>connect()</code> to do this.
193       *
194       * @param url The Object to create the URL connection to
195       *
196       * @see URLConnection#connect()
197       */
198      protected URLConnection(URL url)
199      {
200        // Set up all our instance variables
201        this.url = url;
202        allowUserInteraction = defaultAllowUserInteraction;
203        useCaches = defaultUseCaches;
204      }
205    
206      /**
207       * Establishes the actual connection to the URL associated with this
208       * connection object
209       *
210       * @exception IOException if an error occurs
211       */
212      public abstract void connect() throws IOException;
213    
214      /**
215       * Returns the URL object associated with this connection
216       *
217       * @return The URL for this connection.
218       */
219      public URL getURL()
220      {
221        return url;
222      }
223    
224      /**
225       * Returns the connection timeout speed, in milliseconds, or zero if
226       * the timeout is infinite or not set.
227       *
228       * @return The timeout.
229       *
230       * @since 1.5
231       */
232      public int getConnectTimeout()
233      {
234        return connectTimeout;
235      }
236    
237      /**
238       * Set the connection timeout speed, in milliseconds, or zero if the timeout
239       * is to be considered infinite. Note that in certain socket
240       * implementations/platforms this method may not have any effect.
241       *
242       * Throws an <code>IllegalArgumentException</code> if timeout < 0.
243       *
244       * @param timeout the timeout, in milliseconds.
245       *
246       * @since 1.5
247       */
248      public void setConnectTimeout(int timeout)
249        throws IllegalArgumentException
250      {
251        if( timeout < 0 )
252          throw new IllegalArgumentException("Timeout must be 0 or positive.");
253        connectTimeout = timeout;
254      }
255    
256      /**
257       * Returns the read timeout, in milliseconds, or zero if the timeout
258       * is infinite or not set.
259       *
260       * @return The timeout.
261       *
262       * @see #setReadTimeout
263       *
264       * @since 1.5
265       */
266      public int getReadTimeout()
267      {
268        return readTimeout;
269      }
270    
271      /**
272       * Set the read timeout, in milliseconds, or zero if the timeout
273       * is to be considered infinite. Note that in certain socket
274       * implementations/platforms this method may not have any effect.
275       *
276       * Throws an <code>IllegalArgumentException</code> if timeout < 0.
277       *
278       * @param timeout - The timeout, in milliseconds.
279       *
280       * @throws IllegalArgumentException if timeout is negative.
281       *
282       * @see #getReadTimeout
283       *
284       * @since 1.5
285       */
286      public void setReadTimeout(int timeout)
287        throws IllegalArgumentException
288      {
289        if( timeout < 0 )
290          throw new IllegalArgumentException("Timeout must be 0 or positive.");
291        readTimeout = timeout;
292      }
293    
294      /**
295       * Returns the value of the content-length header field or -1 if the value
296       * is not known or not present.
297       *
298       * @return The content-length field
299       */
300      public int getContentLength()
301      {
302        return getHeaderFieldInt("content-length", -1);
303      }
304    
305      /**
306       * Returns the the content-type of the data pointed to by the URL.  This
307       * method first tries looking for a content-type header.  If that is not
308       * present, it attempts to use the file name to determine the content's
309       * MIME type.  If that is unsuccessful, the method returns null.  The caller
310       * may then still attempt to determine the MIME type by a call to
311       * guessContentTypeFromStream()
312       *
313       * @return The content MIME type
314       */
315      public String getContentType()
316      {
317        return getHeaderField("content-type");
318      }
319    
320      /**
321       * Returns the value of the content-encoding field or null if it is not
322       * known or not present.
323       *
324       * @return The content-encoding field
325       */
326      public String getContentEncoding()
327      {
328        return getHeaderField("content-encoding");
329      }
330    
331      /**
332       * Returns the value of the expires header or 0 if not known or present.
333       * If populated, the return value is number of seconds since midnight
334       * on 1/1/1970 GMT.
335       *
336       * @return The expiration time.
337       */
338      public long getExpiration()
339      {
340        return getHeaderFieldDate("expires", 0L);
341      }
342    
343      /**
344       * Returns the date of the document pointed to by the URL as reported in
345       * the date field of the header or 0 if the value is not present or not
346       * known. If populated, the return value is number of seconds since
347       * midnight on 1/1/1970 GMT.
348       *
349       * @return The document date
350       */
351      public long getDate()
352      {
353        return getHeaderFieldDate("date", 0L);
354      }
355    
356      /**
357       * Returns the value of the last-modified header field or 0 if not known known
358       * or not present.  If populated, the return value is the number of seconds
359       * since midnight on 1/1/1970.
360       *
361       * @return The last modified time
362       */
363      public long getLastModified()
364      {
365        return getHeaderFieldDate("last-modified", 0L);
366      }
367    
368      /**
369       * Return a String representing the header value at the specified index.
370       * This allows the caller to walk the list of header fields.  The analogous
371       * {@link #getHeaderField(int)} method allows access to the corresponding
372       * key for this header field
373       *
374       * @param index The index into the header field list to retrieve the value for
375       *
376       * @return The header value or null if index is past the end of the headers
377       */
378      public String getHeaderField(int index)
379      {
380        // Subclasses for specific protocols override this.
381        return null;
382      }
383    
384      /**
385       * Returns a String representing the value of the header field having
386       * the named key.  Returns null if the header field does not exist.
387       *
388       * @param name The key of the header field
389       *
390       * @return The value of the header field as a String
391       */
392      public String getHeaderField(String name)
393      {
394        // Subclasses for specific protocols override this.
395        return null;
396      }
397    
398      /**
399       * Returns an unmodifiable Map containing all sent header fields.
400       *
401       * @return The map of header fields. The map consists of String keys with
402       * an unmodifiable List of String objects as value.
403       *
404       * @since 1.4
405       */
406      public Map<String,List<String>> getHeaderFields()
407      {
408        // Subclasses for specific protocols override this.
409        return Collections.emptyMap();
410      }
411    
412      /**
413       * Returns the value of the named header field as an int.  If the field
414       * is not present or cannot be parsed as an integer, the default value
415       * will be returned.
416       *
417       * @param name The header field key to lookup
418       * @param defaultValue The defaule value if the header field is not found
419       * or can't be parsed.
420       *
421       * @return The value of the header field or the default value if the field
422       * is missing or malformed
423       */
424      public int getHeaderFieldInt(String name, int defaultValue)
425      {
426        String value = getHeaderField(name);
427    
428        if (value == null)
429          return defaultValue;
430    
431        try
432          {
433            return Integer.parseInt(value);
434          }
435        catch (NumberFormatException e)
436          {
437            return defaultValue;
438          }
439      }
440    
441      /**
442       * Returns the value of the named header field as a date.  This date will
443       * be the number of seconds since midnight 1/1/1970 GMT or the default
444       * value if the field is not present or cannot be converted to a date.
445       *
446       * @param name The name of the header field
447       * @param defaultValue The default date if the header field is not found
448       * or can't be converted.
449       *
450       * @return The date value of the header filed or the default value
451       * if the field is missing or malformed
452       */
453      public long getHeaderFieldDate(String name, long defaultValue)
454      {
455        if (! dateformats_initialized)
456          initializeDateFormats();
457    
458        if (position == null)
459          position = new ParsePosition(0);
460    
461        long result = defaultValue;
462        String str = getHeaderField(name);
463    
464        if (str != null)
465          {
466            for (int i = 0; i < dateFormats.length; i++)
467              {
468                SimpleDateFormat df = dateFormats[i];
469                position.setIndex(0);
470                position.setErrorIndex(0);
471                Date date = df.parse(str, position);
472                if (date != null)
473                  return date.getTime();
474              }
475          }
476    
477        return result;
478      }
479    
480      /**
481       * Returns a String representing the header key at the specified index.
482       * This allows the caller to walk the list of header fields.  The analogous
483       * {@link #getHeaderField(int)} method allows access to the corresponding
484       * value for this tag.
485       *
486       * @param index The index into the header field list to retrieve the key for.
487       *
488       * @return The header field key or null if index is past the end
489       * of the headers.
490       */
491      public String getHeaderFieldKey(int index)
492      {
493        // Subclasses for specific protocols override this.
494        return null;
495      }
496    
497      /**
498       * This method returns the content of the document pointed to by the
499       * URL as an Object.  The type of object depends on the MIME type of
500       * the object and particular content hander loaded.  Most text type
501       * content handlers will return a subclass of
502       * <code>InputStream</code>.  Images usually return a class that
503       * implements <code>ImageProducer</code>.  There is not guarantee
504       * what type of object will be returned, however.
505       *
506       * <p>This class first determines the MIME type of the content, then
507       * creates a ContentHandler object to process the input.  If the
508       * <code>ContentHandlerFactory</code> is set, then that object is
509       * called to load a content handler, otherwise a class called
510       * gnu.java.net.content.&lt;content_type&gt; is tried.  If this
511       * handler does not exist, the method will simple return the
512       * <code>InputStream</code> returned by
513       * <code>getInputStream()</code>.  Note that the default
514       * implementation of <code>getInputStream()</code> throws a
515       * <code>UnknownServiceException</code> so subclasses are encouraged
516       * to override this method.</p>
517       *
518       * @return the content
519       *
520       * @exception IOException If an error with the connection occurs.
521       * @exception UnknownServiceException If the protocol does not support the
522       * content type at all.
523       */
524      public Object getContent() throws IOException
525      {
526        if (!connected)
527          connect();
528    
529        // FIXME: Doc indicates that other criteria should be applied as
530        // heuristics to determine the true content type, e.g. see
531        // guessContentTypeFromName() and guessContentTypeFromStream methods
532        // as well as FileNameMap class & fileNameMap field & get/set methods.
533        String type = getContentType();
534        ContentHandler ch = getContentHandler(type);
535    
536        if (ch != null)
537          return ch.getContent(this);
538    
539        return getInputStream();
540      }
541    
542      /**
543       * Retrieves the content of this URLConnection
544       *
545       * @param classes The allowed classes for the content
546       *
547       * @return the content
548       *
549       * @exception IOException If an error occurs
550       * @exception UnknownServiceException If the protocol does not support the
551       * content type
552       */
553      public Object getContent(Class[] classes)
554        throws IOException
555      {
556        if (! connected)
557          connect();
558        String type = getContentType();
559        ContentHandler ch = getContentHandler(type);
560        if (ch != null)
561          return ch.getContent(this, classes);
562        throw new UnknownServiceException("protocol does not support the content type");
563      }
564    
565      /**
566       * This method returns a <code>Permission</code> object representing the
567       * permissions required to access this URL.  This method returns
568       * <code>java.security.AllPermission</code> by default.  Subclasses should
569       * override it to return a more specific permission.  For example, an
570       * HTTP URL should return an instance of <code>SocketPermission</code>
571       * for the appropriate host and port.
572       * <p>
573       * Note that because of items such as HTTP redirects, the permission
574       * object returned might be different before and after connecting.
575       *
576       * @return A Permission object
577       *
578       * @exception IOException If the computation of the permission requires
579       * network or file I/O and an exception occurs while computing it
580       */
581      public Permission getPermission() throws IOException
582      {
583        // Subclasses may override this.
584        return new AllPermission();
585      }
586    
587      /**
588       * Returns an InputStream for this connection.  As this default
589       * implementation returns null, subclasses should override this method
590       *
591       * @return An InputStream for this connection
592       *
593       * @exception IOException If an error occurs
594       * @exception UnknownServiceException If the protocol does not support input
595       */
596      public InputStream getInputStream() throws IOException
597      {
598        // Subclasses for specific protocols override this.
599        throw new UnknownServiceException("Protocol " + url.getProtocol()
600                                          + " does not support input.");
601      }
602    
603      /**
604       * Returns an OutputStream for this connection.  As this default
605       * implementation returns null, subclasses should override this method
606       *
607       * @return An OutputStream for this connection
608       *
609       * @exception IOException If an error occurs
610       * @exception UnknownServiceException If the protocol does not support output
611       */
612      public OutputStream getOutputStream() throws IOException
613      {
614        // Subclasses for specific protocols override this.
615        throw new UnknownServiceException("Protocol " + url.getProtocol()
616                                          + " does not support output.");
617      }
618    
619      /**
620       * The methods prints the value of this object as a String by calling the
621       * toString() method of its associated URL.  Overrides Object.toString()
622       *
623       * @return A String representation of this object
624       */
625      public String toString()
626      {
627        return this.getClass().getName() + ":" + url.toString();
628      }
629    
630      /**
631       * Sets the value of a flag indicating whether or not input is going
632       * to be done for this connection.  This default to true unless the
633       * doOutput flag is set to false, in which case this defaults to false.
634       *
635       * @param input <code>true</code> if input is to be done,
636       * <code>false</code> otherwise
637       *
638       * @exception IllegalStateException If already connected
639       */
640      public void setDoInput(boolean input)
641      {
642        if (connected)
643          throw new IllegalStateException("Already connected");
644    
645        doInput = input;
646      }
647    
648      /**
649       * Returns the value of a flag indicating whether or not input is going
650       * to be done for this connection.  This default to true unless the
651       * doOutput flag is set to false, in which case this defaults to false.
652       *
653       * @return true if input is to be done, false otherwise
654       */
655      public boolean getDoInput()
656      {
657        return doInput;
658      }
659    
660      /**
661       * Sets a boolean flag indicating whether or not output will be done
662       * on this connection.  The default value is false, so this method can
663       * be used to override the default
664       *
665       * @param output ture if output is to be done, false otherwise
666       *
667       * @exception IllegalStateException If already connected
668       */
669      public void setDoOutput(boolean output)
670      {
671        if (connected)
672          throw new IllegalStateException("Already connected");
673    
674        doOutput = output;
675      }
676    
677      /**
678       * Returns a boolean flag indicating whether or not output will be done
679       * on this connection.  This defaults to false.
680       *
681       * @return true if output is to be done, false otherwise
682       */
683      public boolean getDoOutput()
684      {
685        return doOutput;
686      }
687    
688      /**
689       * Sets a boolean flag indicating whether or not user interaction is
690       * allowed for this connection.  (For example, in order to prompt for
691       * username and password info.
692       *
693       * @param allow true if user interaction should be allowed, false otherwise.
694       *
695       * @exception IllegalStateException If already connected
696       */
697      public void setAllowUserInteraction(boolean allow)
698      {
699        if (connected)
700          throw new IllegalStateException("Already connected");
701    
702        allowUserInteraction = allow;
703      }
704    
705      /**
706       * Returns a boolean flag indicating whether or not user interaction is
707       * allowed for this connection.  (For example, in order to prompt for
708       * username and password info.
709       *
710       * @return true if user interaction is allowed, false otherwise
711       */
712      public boolean getAllowUserInteraction()
713      {
714        return allowUserInteraction;
715      }
716    
717      /**
718       * Sets the default flag for whether or not interaction with a user
719       * is allowed.  This will be used for all connections unless overridden
720       *
721       * @param allow true to allow user interaction, false otherwise
722       */
723      public static void setDefaultAllowUserInteraction(boolean allow)
724      {
725        defaultAllowUserInteraction = allow;
726      }
727    
728      /**
729       * Returns the default flag for whether or not interaction with a user
730       * is allowed.  This will be used for all connections unless overridden
731       *
732       * @return true if user interaction is allowed, false otherwise
733       */
734      public static boolean getDefaultAllowUserInteraction()
735      {
736        return defaultAllowUserInteraction;
737      }
738    
739      /**
740       * Sets a boolean flag indicating whether or not caching will be used
741       * (if possible) to store data downloaded via the connection.
742       *
743       * @param usecaches The new value
744       *
745       * @exception IllegalStateException If already connected
746       */
747      public void setUseCaches(boolean usecaches)
748      {
749        if (connected)
750          throw new IllegalStateException("Already connected");
751    
752        useCaches = usecaches;
753      }
754    
755      /**
756       * Returns a boolean flag indicating whether or not caching will be used
757       * (if possible) to store data downloaded via the connection.
758       *
759       * @return true if caching should be used if possible, false otherwise
760       */
761      public boolean getUseCaches()
762      {
763        return useCaches;
764      }
765    
766      /**
767       * Sets the ifModified since instance variable.  If this value is non
768       * zero and the underlying protocol supports it, the actual document will
769       * not be fetched unless it has been modified since this time.  The value
770       * passed should  be 0 if this feature is to be disabled or the time expressed
771       * as the number of seconds since midnight 1/1/1970 GMT otherwise.
772       *
773       * @param ifmodifiedsince The new value in milliseconds
774       * since January 1, 1970 GMT
775       *
776       * @exception IllegalStateException If already connected
777       */
778      public void setIfModifiedSince(long ifmodifiedsince)
779      {
780        if (connected)
781          throw new IllegalStateException("Already connected");
782    
783        ifModifiedSince = ifmodifiedsince;
784      }
785    
786      /**
787       * Returns the ifModified since instance variable.  If this value is non
788       * zero and the underlying protocol supports it, the actual document will
789       * not be fetched unless it has been modified since this time.  The value
790       * returned will be 0 if this feature is disabled or the time expressed
791       * as the number of seconds since midnight 1/1/1970 GMT otherwise
792       *
793       * @return The ifModifiedSince value
794       */
795      public long getIfModifiedSince()
796      {
797        return ifModifiedSince;
798      }
799    
800      /**
801       * Returns the default value used to determine whether or not caching
802       * of documents will be done when possible.
803       *
804       * @return true if caches will be used, false otherwise
805       */
806      public boolean getDefaultUseCaches()
807      {
808        return defaultUseCaches;
809      }
810    
811      /**
812       * Sets the default value used to determine whether or not caching
813       * of documents will be done when possible.
814       *
815       * @param use true to use caches if possible by default, false otherwise
816       */
817      public void setDefaultUseCaches(boolean use)
818      {
819        defaultUseCaches = use;
820      }
821    
822      /**
823       * Sets the value of the named request property.
824       * This method does overwrite the value of existing properties with
825       * the new value.
826       *
827       * @param key The name of the property
828       * @param value The value of the property
829       *
830       * @exception IllegalStateException If already connected
831       * @exception NullPointerException If key is null
832       *
833       * @see URLConnection#getRequestProperty(String key)
834       * @see URLConnection#addRequestProperty(String key, String value)
835       *
836       * @since 1.4
837       */
838      public void setRequestProperty(String key, String value)
839      {
840        if (connected)
841          throw new IllegalStateException("Already connected");
842    
843        if (key == null)
844          throw new NullPointerException("key is null");
845    
846        // Do nothing unless overridden by subclasses that support setting
847        // header fields in the request.
848      }
849    
850      /**
851       * Adds a new request property by a key/value pair.
852       * This method does not overwrite existing properties with the same key.
853       *
854       * @param key Key of the property to add
855       * @param value Value of the Property to add
856       *
857       * @exception IllegalStateException If already connected
858       * @exception NullPointerException If key is null
859       *
860       * @see URLConnection#getRequestProperty(String)
861       * @see URLConnection#setRequestProperty(String, String)
862       *
863       * @since 1.4
864       */
865      public void addRequestProperty(String key, String value)
866      {
867        if (connected)
868          throw new IllegalStateException("Already connected");
869    
870        if (key == null)
871          throw new NullPointerException("key is null");
872    
873        // Do nothing unless overridden by subclasses that support adding
874        // header fields in the request.
875      }
876    
877      /**
878       * Returns the value of the named request property.
879       *
880       * @param key The name of the property
881       *
882       * @return Value of the property, or <code>null</code> if key is null.
883       *
884       * @exception IllegalStateException If already connected
885       *
886       * @see URLConnection#setRequestProperty(String, String)
887       * @see URLConnection#addRequestProperty(String, String)
888       */
889      public String getRequestProperty(String key)
890      {
891        if (connected)
892          throw new IllegalStateException("Already connected");
893    
894        // Overridden by subclasses that support reading header fields from the
895        // request.
896        return null;
897      }
898    
899      /**
900       * Returns an unmodifiable Map containing the request properties.
901       *
902       * @return The map of properties. The map consists of String keys with an
903       * unmodifiable List of String objects as value.
904       *
905       * @exception IllegalStateException If already connected
906       *
907       * @since 1.4
908       */
909      public Map<String,List<String>> getRequestProperties()
910      {
911        if (connected)
912          throw new IllegalStateException("Already connected");
913    
914        // Overridden by subclasses that support reading header fields from the
915        // request.
916        return Collections.emptyMap();
917      }
918    
919      /**
920       * Sets the default value of a request property.  This will be used
921       * for all connections unless the value of the property is manually
922       * overridden.
923       *
924       * @param key The request property name the default is being set for
925       * @param value The value to set the default to
926       *
927       * @deprecated 1.3 The method setRequestProperty should be used instead.
928       * This method does nothing now.
929       *
930       * @see URLConnection#setRequestProperty(String, String)
931       */
932      public static void setDefaultRequestProperty(String key, String value)
933      {
934        // This method does nothing since JDK 1.3.
935      }
936    
937      /**
938       * Returns the default value of a request property.  This will be used
939       * for all connections unless the value of the property is manually
940       * overridden.
941       *
942       * @param key The request property to return the default value of
943       *
944       * @return The value of the default property or null if not available
945       *
946       * @deprecated 1.3 The method getRequestProperty should be used instead.
947       * This method does nothing now.
948       *
949       * @see URLConnection#getRequestProperty(String)
950       */
951      public static String getDefaultRequestProperty(String key)
952      {
953        // This method does nothing since JDK 1.3.
954        return null;
955      }
956    
957      /**
958       * Sets the ContentHandlerFactory for an application.  This can be called
959       * once and only once.  If it is called again, then an Error is thrown.
960       * Unlike for other set factory methods, this one does not do a security
961       * check prior to setting the factory.
962       *
963       * @param factory The ContentHandlerFactory for this application
964       *
965       * @exception Error If the factory has already been defined
966       * @exception SecurityException If a security manager exists and its
967       * checkSetFactory method doesn't allow the operation
968       */
969      public static synchronized void setContentHandlerFactory(ContentHandlerFactory factory)
970      {
971        if (URLConnection.factory != null)
972          throw new Error("ContentHandlerFactory already set");
973    
974        // Throw an exception if an extant security mgr precludes
975        // setting the factory.
976        SecurityManager s = System.getSecurityManager();
977        if (s != null)
978          s.checkSetFactory();
979    
980        URLConnection.factory = factory;
981      }
982    
983      /**
984       * Returns the MIME type of a file based on the name of the file.  This
985       * works by searching for the file's extension in a list of file extensions
986       * and returning the MIME type associated with it.  If no type is found,
987       * then a MIME type of "application/octet-stream" will be returned.
988       *
989       * @param filename The filename to determine the MIME type for
990       *
991       * @return The MIME type String
992       *
993       * @specnote public since JDK 1.4
994       */
995      public static String guessContentTypeFromName(String filename)
996      {
997        return getFileNameMap().getContentTypeFor(filename.toLowerCase());
998      }
999    
1000      /**
1001       * Returns the MIME type of a stream based on the first few characters
1002       * at the beginning of the stream.  This routine can be used to determine
1003       * the MIME type if a server is believed to be returning an incorrect
1004       * MIME type.  This method returns "application/octet-stream" if it
1005       * cannot determine the MIME type.
1006       * <p>
1007       * NOTE: Overriding MIME types sent from the server can be obnoxious
1008       * to user's.  See Internet Exploder 4 if you don't believe me.
1009       *
1010       * @param is The InputStream to determine the MIME type from
1011       *
1012       * @return The MIME type
1013       *
1014       * @exception IOException If an error occurs
1015       */
1016      public static String guessContentTypeFromStream(InputStream is)
1017        throws IOException
1018      {
1019        String result = VMURLConnection.guessContentTypeFromStream(is);
1020        if (result == null)
1021          return "application/octet-stream";
1022        return result;
1023      }
1024    
1025      /**
1026       * This method returns the <code>FileNameMap</code> object being used
1027       * to decode MIME types by file extension.
1028       *
1029       * @return The <code>FileNameMap</code>.
1030       *
1031       * @since 1.2
1032       */
1033      public static synchronized FileNameMap getFileNameMap()
1034      {
1035        // Delayed initialization.
1036        if (fileNameMap == null)
1037          fileNameMap = new MimeTypeMapper();
1038    
1039        return fileNameMap;
1040      }
1041    
1042      /**
1043       * This method sets the <code>FileNameMap</code> object being used
1044       * to decode MIME types by file extension.
1045       *
1046       * @param map The <code>FileNameMap</code>.
1047       *
1048       * @exception SecurityException If a security manager exists and its
1049       * checkSetFactory method doesn't allow the operation
1050       *
1051       * @since 1.2
1052       */
1053      public static synchronized void setFileNameMap(FileNameMap map)
1054      {
1055        // Throw an exception if an extant security manager precludes
1056        // setting the factory.
1057        SecurityManager s = System.getSecurityManager();
1058        if (s != null)
1059          s.checkSetFactory();
1060    
1061        fileNameMap = map;
1062      }
1063    
1064      private ContentHandler getContentHandler(String contentType)
1065      {
1066        // No content type so just handle it as the default.
1067        if (contentType == null || contentType.equals(""))
1068          return null;
1069    
1070        ContentHandler handler = null;
1071    
1072        // If a non-default factory has been set, use it.
1073        if (factory != null)
1074          handler = factory.createContentHandler(contentType);
1075    
1076        // Now try default factory. Using this factory to instantiate built-in
1077        // content handlers is preferable
1078        if (handler == null)
1079          handler = defaultFactory.createContentHandler(contentType);
1080    
1081        // User-set factory has not returned a handler. Use the default search
1082        // algorithm.
1083        if (handler == null)
1084          {
1085            // Get the list of packages to check and append our default handler
1086            // to it, along with the JDK specified default as a last resort.
1087            // Except in very unusual environments the JDK specified one shouldn't
1088            // ever be needed (or available).
1089            String propVal = SystemProperties.getProperty("java.content.handler.pkgs");
1090            propVal = (((propVal == null) ? "" : (propVal + "|"))
1091                       + "gnu.java.net.content|sun.net.www.content");
1092    
1093            // Deal with "Content-Type: text/html; charset=ISO-8859-1".
1094            int parameterBegin = contentType.indexOf(';');
1095            if (parameterBegin >= 1)
1096              contentType = contentType.substring(0, parameterBegin);
1097            contentType = contentType.trim();
1098    
1099            // Replace the '/' character in the content type with '.' and
1100            // all other non-alphabetic, non-numeric characters with '_'.
1101            char[] cArray = contentType.toCharArray();
1102            for (int i = 0; i < cArray.length; i++)
1103              {
1104                if (cArray[i] == '/')
1105                  cArray[i] = '.';
1106                else if (! ((cArray[i] >= 'A' && cArray[i] <= 'Z') ||
1107                            (cArray[i] >= 'a' && cArray[i] <= 'z') ||
1108                            (cArray[i] >= '0' && cArray[i] <= '9')))
1109                  cArray[i] = '_';
1110              }
1111            String contentClass = new String(cArray);
1112    
1113            // See if a class of this content type exists in any of the packages.
1114            StringTokenizer pkgPrefix = new StringTokenizer(propVal, "|");
1115            do
1116              {
1117                String facName = pkgPrefix.nextToken() + "." + contentClass;
1118                try
1119                  {
1120                    handler =
1121                      (ContentHandler) Class.forName(facName).newInstance();
1122                  }
1123                catch (Exception e)
1124                  {
1125                    // Can't instantiate; handler still null, go on to next element.
1126                  }
1127              } while (handler == null && pkgPrefix.hasMoreTokens());
1128          }
1129    
1130        return handler;
1131      }
1132    
1133      // We don't put these in a static initializer, because it creates problems
1134      // with initializer co-dependency: SimpleDateFormat's constructors
1135      // eventually depend on URLConnection (via the java.text.*Symbols classes).
1136      private static synchronized void initializeDateFormats()
1137      {
1138        if (dateformats_initialized)
1139          return;
1140    
1141        Locale locale = new Locale("En", "Us", "Unix");
1142        dateFormats = new SimpleDateFormat[3];
1143        dateFormats[0] =
1144          new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss 'GMT'", locale);
1145        dateFormats[1] =
1146          new SimpleDateFormat("EEEE, dd-MMM-yy hh:mm:ss 'GMT'", locale);
1147        dateFormats[2] = new SimpleDateFormat("EEE MMM d hh:mm:ss yyyy", locale);
1148        dateformats_initialized = true;
1149      }
1150    }