View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/HttpMethodBase.java $
3    * $Revision: 608014 $
4    * $Date: 2008-01-02 06:48:53 +0100 (Wed, 02 Jan 2008) $
5    *
6    * ====================================================================
7    *
8    *  Licensed to the Apache Software Foundation (ASF) under one or more
9    *  contributor license agreements.  See the NOTICE file distributed with
10   *  this work for additional information regarding copyright ownership.
11   *  The ASF licenses this file to You under the Apache License, Version 2.0
12   *  (the "License"); you may not use this file except in compliance with
13   *  the License.  You may obtain a copy of the License at
14   *
15   *      http://www.apache.org/licenses/LICENSE-2.0
16   *
17   *  Unless required by applicable law or agreed to in writing, software
18   *  distributed under the License is distributed on an "AS IS" BASIS,
19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   *  See the License for the specific language governing permissions and
21   *  limitations under the License.
22   * ====================================================================
23   *
24   * This software consists of voluntary contributions made by many
25   * individuals on behalf of the Apache Software Foundation.  For more
26   * information on the Apache Software Foundation, please see
27   * <http://www.apache.org/>.
28   *
29   */
30  
31  package org.apache.commons.httpclient;
32  
33  import java.io.ByteArrayInputStream;
34  import java.io.ByteArrayOutputStream;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.InterruptedIOException;
38  import java.util.Collection;
39  
40  import org.apache.commons.httpclient.auth.AuthState;
41  import org.apache.commons.httpclient.cookie.CookiePolicy;
42  import org.apache.commons.httpclient.cookie.CookieSpec;
43  import org.apache.commons.httpclient.cookie.CookieVersionSupport;
44  import org.apache.commons.httpclient.cookie.MalformedCookieException;
45  import org.apache.commons.httpclient.params.HttpMethodParams;
46  import org.apache.commons.httpclient.protocol.Protocol;
47  import org.apache.commons.httpclient.util.EncodingUtil;
48  import org.apache.commons.httpclient.util.ExceptionUtil;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  
52  /***
53   * An abstract base implementation of HttpMethod.
54   * <p>
55   * At minimum, subclasses will need to override:
56   * <ul>
57   *   <li>{@link #getName} to return the approriate name for this method
58   *   </li>
59   * </ul>
60   * </p>
61   *
62   * <p>
63   * When a method requires additional request headers, subclasses will typically
64   * want to override:
65   * <ul>
66   *   <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
67   *      to write those headers
68   *   </li>
69   * </ul>
70   * </p>
71   *
72   * <p>
73   * When a method expects specific response headers, subclasses may want to
74   * override:
75   * <ul>
76   *   <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}
77   *     to handle those headers
78   *   </li>
79   * </ul>
80   * </p>
81   *
82   *
83   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
84   * @author Rodney Waldhoff
85   * @author Sean C. Sullivan
86   * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
87   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
88   * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
89   * @author Ortwin Glueck
90   * @author Eric Johnson
91   * @author Michael Becke
92   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
93   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
94   * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
95   * @author Christian Kohlschuetter
96   *
97   * @version $Revision: 608014 $ $Date: 2008-01-02 06:48:53 +0100 (Wed, 02 Jan 2008) $
98   */
99  public abstract class HttpMethodBase implements HttpMethod {
100 
101     // -------------------------------------------------------------- Constants
102 
103     /*** Log object for this class. */
104     private static final Log LOG = LogFactory.getLog(HttpMethodBase.class);
105 
106     // ----------------------------------------------------- Instance variables 
107 
108     /*** Request headers, if any. */
109     private HeaderGroup requestHeaders = new HeaderGroup();
110 
111     /*** The Status-Line from the response. */
112     protected StatusLine statusLine = null;
113 
114     /*** Response headers, if any. */
115     private HeaderGroup responseHeaders = new HeaderGroup();
116 
117     /*** Response trailer headers, if any. */
118     private HeaderGroup responseTrailerHeaders = new HeaderGroup();
119 
120     /*** Path of the HTTP method. */
121     private String path = null;
122 
123     /*** Query string of the HTTP method, if any. */
124     private String queryString = null;
125 
126     /*** The response body of the HTTP method, assuming it has not be 
127      * intercepted by a sub-class. */
128     private InputStream responseStream = null;
129 
130     /*** The connection that the response stream was read from. */
131     private HttpConnection responseConnection = null;
132 
133     /*** Buffer for the response */
134     private byte[] responseBody = null;
135 
136     /*** True if the HTTP method should automatically follow HTTP redirects.*/
137     private boolean followRedirects = false;
138 
139     /*** True if the HTTP method should automatically handle
140     *  HTTP authentication challenges. */
141     private boolean doAuthentication = true;
142 
143     /*** HTTP protocol parameters. */
144     private HttpMethodParams params = new HttpMethodParams();
145 
146     /*** Host authentication state */
147     private AuthState hostAuthState = new AuthState();
148 
149     /*** Proxy authentication state */
150     private AuthState proxyAuthState = new AuthState();
151 
152     /*** True if this method has already been executed. */
153     private boolean used = false;
154 
155     /*** Count of how many times did this HTTP method transparently handle 
156     * a recoverable exception. */
157     private int recoverableExceptionCount = 0;
158 
159     /*** the host for this HTTP method, can be null */
160     private HttpHost httphost = null;
161 
162     /***
163      * Handles method retries
164      * 
165      * @deprecated no loner used
166      */
167     private MethodRetryHandler methodRetryHandler;
168 
169     /*** True if the connection must be closed when no longer needed */
170     private boolean connectionCloseForced = false;
171 
172     /*** Number of milliseconds to wait for 100-contunue response. */
173     private static final int RESPONSE_WAIT_TIME_MS = 3000;
174 
175     /*** HTTP protocol version used for execution of this method. */
176     protected HttpVersion effectiveVersion = null;
177 
178     /*** Whether the execution of this method has been aborted */
179     private volatile boolean aborted = false;
180 
181     /*** Whether the HTTP request has been transmitted to the target
182      * server it its entirety */
183     private boolean requestSent = false;
184     
185     /*** Actual cookie policy */
186     private CookieSpec cookiespec = null;
187 
188     /*** Default initial size of the response buffer if content length is unknown. */
189     private static final int DEFAULT_INITIAL_BUFFER_SIZE = 4*1024; // 4 kB
190     
191     // ----------------------------------------------------------- Constructors
192 
193     /***
194      * No-arg constructor.
195      */
196     public HttpMethodBase() {
197     }
198 
199     /***
200      * Constructor specifying a URI.
201      * It is responsibility of the caller to ensure that URI elements
202      * (path & query parameters) are properly encoded (URL safe).
203      *
204      * @param uri either an absolute or relative URI. The URI is expected
205      *            to be URL-encoded
206      * 
207      * @throws IllegalArgumentException when URI is invalid
208      * @throws IllegalStateException when protocol of the absolute URI is not recognised
209      */
210     public HttpMethodBase(String uri) 
211         throws IllegalArgumentException, IllegalStateException {
212 
213         try {
214 
215             // create a URI and allow for null/empty uri values
216             if (uri == null || uri.equals("")) {
217                 uri = "/";
218             }
219             String charset = getParams().getUriCharset();
220             setURI(new URI(uri, true, charset));
221         } catch (URIException e) {
222             throw new IllegalArgumentException("Invalid uri '" 
223                 + uri + "': " + e.getMessage() 
224             );
225         }
226     }
227 
228     // ------------------------------------------- Property Setters and Getters
229 
230     /***
231      * Obtains the name of the HTTP method as used in the HTTP request line,
232      * for example <tt>"GET"</tt> or <tt>"POST"</tt>.
233      * 
234      * @return the name of this method
235      */
236     public abstract String getName();
237 
238     /***
239      * Returns the URI of the HTTP method
240      * 
241      * @return The URI
242      * 
243      * @throws URIException If the URI cannot be created.
244      * 
245      * @see org.apache.commons.httpclient.HttpMethod#getURI()
246      */
247     public URI getURI() throws URIException {
248         StringBuffer buffer = new StringBuffer();
249         if (this.httphost != null) {
250             buffer.append(this.httphost.getProtocol().getScheme());
251             buffer.append("://");
252             buffer.append(this.httphost.getHostName());
253             int port = this.httphost.getPort();
254             if (port != -1 && port != this.httphost.getProtocol().getDefaultPort()) {
255                 buffer.append(":");
256                 buffer.append(port);
257             }
258         }
259         buffer.append(this.path);
260         if (this.queryString != null) {
261             buffer.append('?');
262             buffer.append(this.queryString);
263         }
264         String charset = getParams().getUriCharset();
265         return new URI(buffer.toString(), true, charset);
266     }
267 
268     /***
269      * Sets the URI for this method. 
270      * 
271      * @param uri URI to be set 
272      * 
273      * @throws URIException if a URI cannot be set
274      * 
275      * @since 3.0
276      */
277     public void setURI(URI uri) throws URIException {
278         // only set the host if specified by the URI
279         if (uri.isAbsoluteURI()) {
280             this.httphost = new HttpHost(uri);
281         }
282         // set the path, defaulting to root
283         setPath(
284             uri.getPath() == null
285             ? "/"
286             : uri.getEscapedPath()
287         );
288         setQueryString(uri.getEscapedQuery());
289     } 
290 
291     /***
292      * Sets whether or not the HTTP method should automatically follow HTTP redirects 
293      * (status code 302, etc.)
294      * 
295      * @param followRedirects <tt>true</tt> if the method will automatically follow redirects,
296      * <tt>false</tt> otherwise.
297      */
298     public void setFollowRedirects(boolean followRedirects) {
299         this.followRedirects = followRedirects;
300     }
301 
302     /***
303      * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects 
304      * (status code 302, etc.), <tt>false</tt> otherwise.
305      * 
306      * @return <tt>true</tt> if the method will automatically follow HTTP redirects, 
307      * <tt>false</tt> otherwise.
308      */
309     public boolean getFollowRedirects() {
310         return this.followRedirects;
311     }
312 
313     /*** Sets whether version 1.1 of the HTTP protocol should be used per default.
314      *
315      * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
316      * 
317      * @deprecated Use {@link HttpMethodParams#setVersion(HttpVersion)}
318      */
319     public void setHttp11(boolean http11) {
320         if (http11) {
321             this.params.setVersion(HttpVersion.HTTP_1_1);
322         } else {
323             this.params.setVersion(HttpVersion.HTTP_1_0);
324         } 
325     }
326 
327     /***
328      * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP 
329      * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise
330      *
331      * @return <tt>true</tt> if authentication challenges will be processed 
332      * automatically, <tt>false</tt> otherwise.
333      * 
334      * @since 2.0
335      */
336     public boolean getDoAuthentication() {
337         return doAuthentication;
338     }
339 
340     /***
341      * Sets whether or not the HTTP method should automatically handle HTTP 
342      * authentication challenges (status code 401, etc.)
343      *
344      * @param doAuthentication <tt>true</tt> to process authentication challenges
345      * authomatically, <tt>false</tt> otherwise.
346      * 
347      * @since 2.0
348      */
349     public void setDoAuthentication(boolean doAuthentication) {
350         this.doAuthentication = doAuthentication;
351     }
352 
353     // ---------------------------------------------- Protected Utility Methods
354 
355     /***
356      * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be 
357      * used per default, <tt>false</tt> if version 1.0 should be used.
358      *
359      * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
360      * 
361      * @deprecated Use {@link HttpMethodParams#getVersion()}
362      */
363     public boolean isHttp11() {
364         return this.params.getVersion().equals(HttpVersion.HTTP_1_1);
365     }
366 
367     /***
368      * Sets the path of the HTTP method.
369      * It is responsibility of the caller to ensure that the path is
370      * properly encoded (URL safe).
371      *
372      * @param path the path of the HTTP method. The path is expected
373      *        to be URL-encoded
374      */
375     public void setPath(String path) {
376         this.path = path;
377     }
378 
379     /***
380      * Adds the specified request header, NOT overwriting any previous value.
381      * Note that header-name matching is case insensitive.
382      *
383      * @param header the header to add to the request
384      */
385     public void addRequestHeader(Header header) {
386         LOG.trace("HttpMethodBase.addRequestHeader(Header)");
387 
388         if (header == null) {
389             LOG.debug("null header value ignored");
390         } else {
391             getRequestHeaderGroup().addHeader(header);
392         }
393     }
394 
395     /***
396      * Use this method internally to add footers.
397      * 
398      * @param footer The footer to add.
399      */
400     public void addResponseFooter(Header footer) {
401         getResponseTrailerHeaderGroup().addHeader(footer);
402     }
403 
404     /***
405      * Gets the path of this HTTP method.
406      * Calling this method <em>after</em> the request has been executed will 
407      * return the <em>actual</em> path, following any redirects automatically
408      * handled by this HTTP method.
409      *
410      * @return the path to request or "/" if the path is blank.
411      */
412     public String getPath() {
413         return (path == null || path.equals("")) ? "/" : path;
414     }
415 
416     /***
417      * Sets the query string of this HTTP method. The caller must ensure that the string 
418      * is properly URL encoded. The query string should not start with the question 
419      * mark character.
420      *
421      * @param queryString the query string
422      * 
423      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
424      */
425     public void setQueryString(String queryString) {
426         this.queryString = queryString;
427     }
428 
429     /***
430      * Sets the query string of this HTTP method.  The pairs are encoded as UTF-8 characters.  
431      * To use a different charset the parameters can be encoded manually using EncodingUtil 
432      * and set as a single String.
433      *
434      * @param params an array of {@link NameValuePair}s to add as query string
435      *        parameters. The name/value pairs will be automcatically 
436      *        URL encoded
437      * 
438      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
439      * @see #setQueryString(String)
440      */
441     public void setQueryString(NameValuePair[] params) {
442         LOG.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
443         queryString = EncodingUtil.formUrlEncode(params, "UTF-8");
444     }
445 
446     /***
447      * Gets the query string of this HTTP method.
448      *
449      * @return The query string
450      */
451     public String getQueryString() {
452         return queryString;
453     }
454 
455     /***
456      * Set the specified request header, overwriting any previous value. Note
457      * that header-name matching is case-insensitive.
458      *
459      * @param headerName the header's name
460      * @param headerValue the header's value
461      */
462     public void setRequestHeader(String headerName, String headerValue) {
463         Header header = new Header(headerName, headerValue);
464         setRequestHeader(header);
465     }
466 
467     /***
468      * Sets the specified request header, overwriting any previous value.
469      * Note that header-name matching is case insensitive.
470      * 
471      * @param header the header
472      */
473     public void setRequestHeader(Header header) {
474         
475         Header[] headers = getRequestHeaderGroup().getHeaders(header.getName());
476         
477         for (int i = 0; i < headers.length; i++) {
478             getRequestHeaderGroup().removeHeader(headers[i]);
479         }
480         
481         getRequestHeaderGroup().addHeader(header);
482         
483     }
484 
485     /***
486      * Returns the specified request header. Note that header-name matching is
487      * case insensitive. <tt>null</tt> will be returned if either
488      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
489      * <i>headerName</i>.
490      * 
491      * @param headerName The name of the header to be returned.
492      *
493      * @return The specified request header.
494      * 
495      * @since 3.0
496      */
497     public Header getRequestHeader(String headerName) {
498         if (headerName == null) {
499             return null;
500         } else {
501             return getRequestHeaderGroup().getCondensedHeader(headerName);
502         }
503     }
504 
505     /***
506      * Returns an array of the requests headers that the HTTP method currently has
507      *
508      * @return an array of my request headers.
509      */
510     public Header[] getRequestHeaders() {
511         return getRequestHeaderGroup().getAllHeaders();
512     }
513 
514     /***
515      * @see org.apache.commons.httpclient.HttpMethod#getRequestHeaders(java.lang.String)
516      */
517     public Header[] getRequestHeaders(String headerName) {
518         return getRequestHeaderGroup().getHeaders(headerName);
519     }
520 
521     /***
522      * Gets the {@link HeaderGroup header group} storing the request headers.
523      * 
524      * @return a HeaderGroup
525      * 
526      * @since 2.0beta1
527      */
528     protected HeaderGroup getRequestHeaderGroup() {
529         return requestHeaders;
530     }
531 
532     /***
533      * Gets the {@link HeaderGroup header group} storing the response trailer headers 
534      * as per RFC 2616 section 3.6.1.
535      * 
536      * @return a HeaderGroup
537      * 
538      * @since 2.0beta1
539      */
540     protected HeaderGroup getResponseTrailerHeaderGroup() {
541         return responseTrailerHeaders;
542     }
543 
544     /***
545      * Gets the {@link HeaderGroup header group} storing the response headers.
546      * 
547      * @return a HeaderGroup
548      * 
549      * @since 2.0beta1
550      */
551     protected HeaderGroup getResponseHeaderGroup() {
552         return responseHeaders;
553     }
554     
555     /***
556      * @see org.apache.commons.httpclient.HttpMethod#getResponseHeaders(java.lang.String)
557      * 
558      * @since 3.0
559      */
560     public Header[] getResponseHeaders(String headerName) {
561         return getResponseHeaderGroup().getHeaders(headerName);
562     }
563 
564     /***
565      * Returns the response status code.
566      *
567      * @return the status code associated with the latest response.
568      */
569     public int getStatusCode() {
570         return statusLine.getStatusCode();
571     }
572 
573     /***
574      * Provides access to the response status line.
575      *
576      * @return the status line object from the latest response.
577      * @since 2.0
578      */
579     public StatusLine getStatusLine() {
580         return statusLine;
581     }
582 
583     /***
584      * Checks if response data is available.
585      * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise.
586      */
587     private boolean responseAvailable() {
588         return (responseBody != null) || (responseStream != null);
589     }
590 
591     /***
592      * Returns an array of the response headers that the HTTP method currently has
593      * in the order in which they were read.
594      *
595      * @return an array of response headers.
596      */
597     public Header[] getResponseHeaders() {
598         return getResponseHeaderGroup().getAllHeaders();
599     }
600 
601     /***
602      * Gets the response header associated with the given name. Header name
603      * matching is case insensitive. <tt>null</tt> will be returned if either
604      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
605      * <i>headerName</i>.
606      *
607      * @param headerName the header name to match
608      *
609      * @return the matching header
610      */
611     public Header getResponseHeader(String headerName) {        
612         if (headerName == null) {
613             return null;
614         } else {
615             return getResponseHeaderGroup().getCondensedHeader(headerName);
616         }        
617     }
618 
619 
620     /***
621      * Return the length (in bytes) of the response body, as specified in a
622      * <tt>Content-Length</tt> header.
623      *
624      * <p>
625      * Return <tt>-1</tt> when the content-length is unknown.
626      * </p>
627      *
628      * @return content length, if <tt>Content-Length</tt> header is available. 
629      *          <tt>0</tt> indicates that the request has no body.
630      *          If <tt>Content-Length</tt> header is not present, the method 
631      *          returns  <tt>-1</tt>.
632      */
633     public long getResponseContentLength() {
634         Header[] headers = getResponseHeaderGroup().getHeaders("Content-Length");
635         if (headers.length == 0) {
636             return -1;
637         }
638         if (headers.length > 1) {
639             LOG.warn("Multiple content-length headers detected");
640         }
641         for (int i = headers.length - 1; i >= 0; i--) {
642             Header header = headers[i];
643             try {
644                 return Long.parseLong(header.getValue());
645             } catch (NumberFormatException e) {
646                 if (LOG.isWarnEnabled()) {
647                     LOG.warn("Invalid content-length value: " + e.getMessage());
648                 }
649             }
650             // See if we can have better luck with another header, if present
651         }
652         return -1;
653     }
654 
655 
656     /***
657      * Returns the response body of the HTTP method, if any, as an array of bytes.
658      * If response body is not available or cannot be read, returns <tt>null</tt>.
659      * Buffers the response and this method can be called several times yielding
660      * the same result each time.
661      * 
662      * Note: This will cause the entire response body to be buffered in memory. A
663      * malicious server may easily exhaust all the VM memory. It is strongly
664      * recommended, to use getResponseAsStream if the content length of the response
665      * is unknown or resonably large.
666      *  
667      * @return The response body.
668      * 
669      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
670      * response body.
671      */
672     public byte[] getResponseBody() throws IOException {
673         if (this.responseBody == null) {
674             InputStream instream = getResponseBodyAsStream();
675             if (instream != null) {
676                 long contentLength = getResponseContentLength();
677                 if (contentLength > Integer.MAX_VALUE) { //guard below cast from overflow
678                     throw new IOException("Content too large to be buffered: "+ contentLength +" bytes");
679                 }
680                 int limit = getParams().getIntParameter(HttpMethodParams.BUFFER_WARN_TRIGGER_LIMIT, 1024*1024);
681                 if ((contentLength == -1) || (contentLength > limit)) {
682                     LOG.warn("Going to buffer response body of large or unknown size. "
683                             +"Using getResponseBodyAsStream instead is recommended.");
684                 }
685                 LOG.debug("Buffering response body");
686                 ByteArrayOutputStream outstream = new ByteArrayOutputStream(
687                         contentLength > 0 ? (int) contentLength : DEFAULT_INITIAL_BUFFER_SIZE);
688                 byte[] buffer = new byte[4096];
689                 int len;
690                 while ((len = instream.read(buffer)) > 0) {
691                     outstream.write(buffer, 0, len);
692                 }
693                 outstream.close();
694                 setResponseStream(null);
695                 this.responseBody = outstream.toByteArray();
696             }
697         }
698         return this.responseBody;
699     }
700 
701     /***
702      * Returns the response body of the HTTP method, if any, as an array of bytes.
703      * If response body is not available or cannot be read, returns <tt>null</tt>.
704      * Buffers the response and this method can be called several times yielding
705      * the same result each time.
706      * 
707      * Note: This will cause the entire response body to be buffered in memory. This method is
708      * safe if the content length of the response is unknown, because the amount of memory used
709      * is limited.<p>
710      * 
711      * If the response is large this method involves lots of array copying and many object 
712      * allocations, which makes it unsuitable for high-performance / low-footprint applications.
713      * Those applications should use {@link #getResponseBodyAsStream()}.
714      * 
715      * @param maxlen the maximum content length to accept (number of bytes). 
716      * @return The response body.
717      * 
718      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
719      * response body.
720      */
721     public byte[] getResponseBody(int maxlen) throws IOException {
722         if (maxlen < 0) throw new IllegalArgumentException("maxlen must be positive");
723         if (this.responseBody == null) {
724             InputStream instream = getResponseBodyAsStream();
725             if (instream != null) {
726                 // we might already know that the content is larger
727                 long contentLength = getResponseContentLength();
728                 if ((contentLength != -1) && (contentLength > maxlen)) {
729                     throw new HttpContentTooLargeException(
730                             "Content-Length is " + contentLength, maxlen);
731                 }
732                 
733                 LOG.debug("Buffering response body");
734                 ByteArrayOutputStream rawdata = new ByteArrayOutputStream(
735                         contentLength > 0 ? (int) contentLength : DEFAULT_INITIAL_BUFFER_SIZE);
736                 byte[] buffer = new byte[2048];
737                 int pos = 0;
738                 int len;
739                 do {
740                     len = instream.read(buffer, 0, Math.min(buffer.length, maxlen-pos));
741                     if (len == -1) break;
742                     rawdata.write(buffer, 0, len);
743                     pos += len;
744                 } while (pos < maxlen);
745                 
746                 setResponseStream(null);
747                 // check if there is even more data
748                 if (pos == maxlen) {
749                     if (instream.read() != -1)
750                         throw new HttpContentTooLargeException(
751                                 "Content-Length not known but larger than "
752                                 + maxlen, maxlen);
753                 }
754                 this.responseBody = rawdata.toByteArray();
755             }
756         }
757         return this.responseBody;
758     }
759 
760     /***
761      * Returns the response body of the HTTP method, if any, as an {@link InputStream}. 
762      * If response body is not available, returns <tt>null</tt>. If the response has been
763      * buffered this method returns a new stream object on every call. If the response
764      * has not been buffered the returned stream can only be read once.
765      * 
766      * @return The response body or <code>null</code>.
767      * 
768      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
769      * response body.
770      */
771     public InputStream getResponseBodyAsStream() throws IOException {
772         if (responseStream != null) {
773             return responseStream;
774         }
775         if (responseBody != null) {
776             InputStream byteResponseStream = new ByteArrayInputStream(responseBody);
777             LOG.debug("re-creating response stream from byte array");
778             return byteResponseStream;
779         }
780         return null;
781     }
782 
783     /***
784      * Returns the response body of the HTTP method, if any, as a {@link String}. 
785      * If response body is not available or cannot be read, returns <tt>null</tt>
786      * The string conversion on the data is done using the character encoding specified
787      * in <tt>Content-Type</tt> header. Buffers the response and this method can be 
788      * called several times yielding the same result each time.
789      * 
790      * Note: This will cause the entire response body to be buffered in memory. A
791      * malicious server may easily exhaust all the VM memory. It is strongly
792      * recommended, to use getResponseAsStream if the content length of the response
793      * is unknown or resonably large.
794      * 
795      * @return The response body or <code>null</code>.
796      * 
797      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
798      * response body.
799      */
800     public String getResponseBodyAsString() throws IOException {
801         byte[] rawdata = null;
802         if (responseAvailable()) {
803             rawdata = getResponseBody();
804         }
805         if (rawdata != null) {
806             return EncodingUtil.getString(rawdata, getResponseCharSet());
807         } else {
808             return null;
809         }
810     }
811     
812     /***
813      * Returns the response body of the HTTP method, if any, as a {@link String}. 
814      * If response body is not available or cannot be read, returns <tt>null</tt>
815      * The string conversion on the data is done using the character encoding specified
816      * in <tt>Content-Type</tt> header. Buffers the response and this method can be 
817      * called several times yielding the same result each time.</p>
818      * 
819      * Note: This will cause the entire response body to be buffered in memory. This method is
820      * safe if the content length of the response is unknown, because the amount of memory used
821      * is limited.<p>
822      * 
823      * If the response is large this method involves lots of array copying and many object 
824      * allocations, which makes it unsuitable for high-performance / low-footprint applications.
825      * Those applications should use {@link #getResponseBodyAsStream()}.
826      * 
827      * @param maxlen the maximum content length to accept (number of bytes). Note that,
828      * depending on the encoding, this is not equal to the number of characters.
829      * @return The response body or <code>null</code>.
830      * 
831      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
832      * response body.
833      */
834     public String getResponseBodyAsString(int maxlen) throws IOException {
835         if (maxlen < 0) throw new IllegalArgumentException("maxlen must be positive");
836         byte[] rawdata = null;
837         if (responseAvailable()) {
838             rawdata = getResponseBody(maxlen);
839         }
840         if (rawdata != null) {
841             return EncodingUtil.getString(rawdata, getResponseCharSet());
842         } else {
843             return null;
844         }
845     }
846 
847     /***
848      * Returns an array of the response footers that the HTTP method currently has
849      * in the order in which they were read.
850      *
851      * @return an array of footers
852      */
853     public Header[] getResponseFooters() {
854         return getResponseTrailerHeaderGroup().getAllHeaders();
855     }
856 
857     /***
858      * Gets the response footer associated with the given name.
859      * Footer name matching is case insensitive.
860      * <tt>null</tt> will be returned if either <i>footerName</i> is
861      * <tt>null</tt> or there is no matching footer for <i>footerName</i>
862      * or there are no footers available.  If there are multiple footers
863      * with the same name, there values will be combined with the ',' separator
864      * as specified by RFC2616.
865      * 
866      * @param footerName the footer name to match
867      * @return the matching footer
868      */
869     public Header getResponseFooter(String footerName) {
870         if (footerName == null) {
871             return null;
872         } else {
873             return getResponseTrailerHeaderGroup().getCondensedHeader(footerName);
874         }
875     }
876 
877     /***
878      * Sets the response stream.
879      * @param responseStream The new response stream.
880      */
881     protected void setResponseStream(InputStream responseStream) {
882         this.responseStream = responseStream;
883     }
884 
885     /***
886      * Returns a stream from which the body of the current response may be read.
887      * If the method has not yet been executed, if <code>responseBodyConsumed</code>
888      * has been called, or if the stream returned by a previous call has been closed,
889      * <code>null</code> will be returned.
890      *
891      * @return the current response stream
892      */
893     protected InputStream getResponseStream() {
894         return responseStream;
895     }
896     
897     /***
898      * Returns the status text (or "reason phrase") associated with the latest
899      * response.
900      * 
901      * @return The status text.
902      */
903     public String getStatusText() {
904         return statusLine.getReasonPhrase();
905     }
906 
907     /***
908      * Defines how strictly HttpClient follows the HTTP protocol specification  
909      * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely
910      * implements the requirements of the specification, whereas in non-strict mode 
911      * it attempts to mimic the exact behaviour of commonly used HTTP agents, 
912      * which many HTTP servers expect.
913      * 
914      * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise
915      * 
916      * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
917      * to exercise a more granular control over HTTP protocol strictness.
918      */
919     public void setStrictMode(boolean strictMode) {
920         if (strictMode) {
921             this.params.makeStrict();
922         } else {
923             this.params.makeLenient();
924         }
925     }
926 
927     /***
928      * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
929      * to exercise a more granular control over HTTP protocol strictness.
930      *
931      * @return <tt>false</tt>
932      */
933     public boolean isStrictMode() {
934         return false;
935     }
936 
937     /***
938      * Adds the specified request header, NOT overwriting any previous value.
939      * Note that header-name matching is case insensitive.
940      *
941      * @param headerName the header's name
942      * @param headerValue the header's value
943      */
944     public void addRequestHeader(String headerName, String headerValue) {
945         addRequestHeader(new Header(headerName, headerValue));
946     }
947 
948     /***
949      * Tests if the connection should be force-closed when no longer needed.
950      * 
951      * @return <code>true</code> if the connection must be closed
952      */
953     protected boolean isConnectionCloseForced() {
954         return this.connectionCloseForced;
955     }
956 
957     /***
958      * Sets whether or not the connection should be force-closed when no longer 
959      * needed. This value should only be set to <code>true</code> in abnormal 
960      * circumstances, such as HTTP protocol violations. 
961      * 
962      * @param b <code>true</code> if the connection must be closed, <code>false</code>
963      * otherwise.
964      */
965     protected void setConnectionCloseForced(boolean b) {
966         if (LOG.isDebugEnabled()) {
967             LOG.debug("Force-close connection: " + b);
968         }
969         this.connectionCloseForced = b;
970     }
971 
972     /***
973      * Tests if the connection should be closed after the method has been executed.
974      * The connection will be left open when using HTTP/1.1 or if <tt>Connection: 
975      * keep-alive</tt> header was sent.
976      * 
977      * @param conn the connection in question
978      * 
979      * @return boolean true if we should close the connection.
980      */
981     protected boolean shouldCloseConnection(HttpConnection conn) {
982         // Connection must be closed due to an abnormal circumstance 
983         if (isConnectionCloseForced()) {
984             LOG.debug("Should force-close connection.");
985             return true;
986         }
987 
988         Header connectionHeader = null;
989         // In case being connected via a proxy server
990         if (!conn.isTransparent()) {
991             // Check for 'proxy-connection' directive
992             connectionHeader = responseHeaders.getFirstHeader("proxy-connection");
993         }
994         // In all cases Check for 'connection' directive
995         // some non-complaint proxy servers send it instread of
996         // expected 'proxy-connection' directive
997         if (connectionHeader == null) {
998             connectionHeader = responseHeaders.getFirstHeader("connection");
999         }
1000         // In case the response does not contain any explict connection
1001         // directives, check whether the request does
1002         if (connectionHeader == null) {
1003             connectionHeader = requestHeaders.getFirstHeader("connection");
1004         }
1005         if (connectionHeader != null) {
1006             if (connectionHeader.getValue().equalsIgnoreCase("close")) {
1007                 if (LOG.isDebugEnabled()) {
1008                     LOG.debug("Should close connection in response to directive: " 
1009                         + connectionHeader.getValue());
1010                 }
1011                 return true;
1012             } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) {
1013                 if (LOG.isDebugEnabled()) {
1014                     LOG.debug("Should NOT close connection in response to directive: " 
1015                         + connectionHeader.getValue());
1016                 }
1017                 return false;
1018             } else {
1019                 if (LOG.isDebugEnabled()) {
1020                     LOG.debug("Unknown directive: " + connectionHeader.toExternalForm());
1021                 }
1022             }
1023         }
1024         LOG.debug("Resorting to protocol version default close connection policy");
1025         // missing or invalid connection header, do the default
1026         if (this.effectiveVersion.greaterEquals(HttpVersion.HTTP_1_1)) {
1027             if (LOG.isDebugEnabled()) {
1028                 LOG.debug("Should NOT close connection, using " + this.effectiveVersion.toString());
1029             }
1030         } else {
1031             if (LOG.isDebugEnabled()) {
1032                 LOG.debug("Should close connection, using " + this.effectiveVersion.toString());
1033             }
1034         }
1035         return this.effectiveVersion.lessEquals(HttpVersion.HTTP_1_0);
1036     }
1037     
1038     /***
1039      * Tests if the this method is ready to be executed.
1040      * 
1041      * @param state the {@link HttpState state} information associated with this method
1042      * @param conn the {@link HttpConnection connection} to be used
1043      * @throws HttpException If the method is in invalid state.
1044      */
1045     private void checkExecuteConditions(HttpState state, HttpConnection conn)
1046     throws HttpException {
1047 
1048         if (state == null) {
1049             throw new IllegalArgumentException("HttpState parameter may not be null");
1050         }
1051         if (conn == null) {
1052             throw new IllegalArgumentException("HttpConnection parameter may not be null");
1053         }
1054         if (this.aborted) {
1055             throw new IllegalStateException("Method has been aborted");
1056         }
1057         if (!validate()) {
1058             throw new ProtocolException("HttpMethodBase object not valid");
1059         }
1060     }
1061 
1062     /***
1063      * Executes this method using the specified <code>HttpConnection</code> and
1064      * <code>HttpState</code>. 
1065      *
1066      * @param state {@link HttpState state} information to associate with this
1067      *        request. Must be non-null.
1068      * @param conn the {@link HttpConnection connection} to used to execute
1069      *        this HTTP method. Must be non-null.
1070      *
1071      * @return the integer status code if one was obtained, or <tt>-1</tt>
1072      *
1073      * @throws IOException if an I/O (transport) error occurs
1074      * @throws HttpException  if a protocol exception occurs.
1075      */
1076     public int execute(HttpState state, HttpConnection conn)
1077         throws HttpException, IOException {
1078                 
1079         LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
1080 
1081         // this is our connection now, assign it to a local variable so 
1082         // that it can be released later
1083         this.responseConnection = conn;
1084 
1085         checkExecuteConditions(state, conn);
1086         this.statusLine = null;
1087         this.connectionCloseForced = false;
1088 
1089         conn.setLastResponseInputStream(null);
1090 
1091         // determine the effective protocol version
1092         if (this.effectiveVersion == null) {
1093             this.effectiveVersion = this.params.getVersion(); 
1094         }
1095 
1096         writeRequest(state, conn);
1097         this.requestSent = true;
1098         readResponse(state, conn);
1099         // the method has successfully executed
1100         used = true; 
1101 
1102         return statusLine.getStatusCode();
1103     }
1104 
1105     /***
1106      * Aborts the execution of this method.
1107      * 
1108      * @since 3.0
1109      */
1110     public void abort() {
1111         if (this.aborted) {
1112             return;
1113         }
1114         this.aborted = true;
1115         HttpConnection conn = this.responseConnection; 
1116         if (conn != null) {
1117             conn.close();
1118         }
1119     }
1120 
1121     /***
1122      * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed},
1123      * but not {@link #recycle recycled}.
1124      * 
1125      * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise
1126      */
1127     public boolean hasBeenUsed() {
1128         return used;
1129     }
1130 
1131     /***
1132      * Recycles the HTTP method so that it can be used again.
1133      * Note that all of the instance variables will be reset
1134      * once this method has been called. This method will also
1135      * release the connection being used by this HTTP method.
1136      * 
1137      * @see #releaseConnection()
1138      * 
1139      * @deprecated no longer supported and will be removed in the future
1140      *             version of HttpClient
1141      */
1142     public void recycle() {
1143         LOG.trace("enter HttpMethodBase.recycle()");
1144 
1145         releaseConnection();
1146 
1147         path = null;
1148         followRedirects = false;
1149         doAuthentication = true;
1150         queryString = null;
1151         getRequestHeaderGroup().clear();
1152         getResponseHeaderGroup().clear();
1153         getResponseTrailerHeaderGroup().clear();
1154         statusLine = null;
1155         effectiveVersion = null;
1156         aborted = false;
1157         used = false;
1158         params = new HttpMethodParams();
1159         responseBody = null;
1160         recoverableExceptionCount = 0;
1161         connectionCloseForced = false;
1162         hostAuthState.invalidate();
1163         proxyAuthState.invalidate();
1164         cookiespec = null;
1165         requestSent = false;
1166     }
1167 
1168     /***
1169      * Releases the connection being used by this HTTP method. In particular the
1170      * connection is used to read the response(if there is one) and will be held
1171      * until the response has been read. If the connection can be reused by other 
1172      * HTTP methods it is NOT closed at this point.
1173      *
1174      * @since 2.0
1175      */
1176     public void releaseConnection() {
1177         try {
1178             if (this.responseStream != null) {
1179                 try {
1180                     // FYI - this may indirectly invoke responseBodyConsumed.
1181                     this.responseStream.close();
1182                 } catch (IOException ignore) {
1183                 }
1184             }
1185         } finally {
1186             ensureConnectionRelease();
1187         }
1188     }
1189 
1190     /***
1191      * Remove the request header associated with the given name. Note that
1192      * header-name matching is case insensitive.
1193      *
1194      * @param headerName the header name
1195      */
1196     public void removeRequestHeader(String headerName) {
1197         
1198         Header[] headers = getRequestHeaderGroup().getHeaders(headerName);
1199         for (int i = 0; i < headers.length; i++) {
1200             getRequestHeaderGroup().removeHeader(headers[i]);
1201         }
1202         
1203     }
1204     
1205     /***
1206      * Removes the given request header.
1207      * 
1208      * @param header the header
1209      */
1210     public void removeRequestHeader(final Header header) {
1211         if (header == null) {
1212             return;
1213         }
1214         getRequestHeaderGroup().removeHeader(header);
1215     }
1216 
1217     // ---------------------------------------------------------------- Queries
1218 
1219     /***
1220      * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise.
1221      * 
1222      * @return This implementation always returns <tt>true</tt>.
1223      */
1224     public boolean validate() {
1225         return true;
1226     }
1227 
1228 
1229     /*** 
1230      * Returns the actual cookie policy
1231      * 
1232      * @param state HTTP state. TODO: to be removed in the future
1233      * 
1234      * @return cookie spec
1235      */
1236     private CookieSpec getCookieSpec(final HttpState state) {
1237     	if (this.cookiespec == null) {
1238     		int i = state.getCookiePolicy();
1239     		if (i == -1) {
1240         		this.cookiespec = CookiePolicy.getCookieSpec(this.params.getCookiePolicy());
1241     		} else {
1242         		this.cookiespec = CookiePolicy.getSpecByPolicy(i);
1243     		}
1244     		this.cookiespec.setValidDateFormats(
1245             		(Collection)this.params.getParameter(HttpMethodParams.DATE_PATTERNS));
1246     	}
1247     	return this.cookiespec;
1248     }
1249 
1250     /***
1251      * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s
1252      * that match the given host, port and path.
1253      *
1254      * @param state the {@link HttpState state} information associated with this method
1255      * @param conn the {@link HttpConnection connection} used to execute
1256      *        this HTTP method
1257      *
1258      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1259      *                     can be recovered from.
1260      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1261      *                    cannot be recovered from.
1262      */
1263     protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
1264         throws IOException, HttpException {
1265 
1266         LOG.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
1267                   + "HttpConnection)");
1268 
1269         Header[] cookieheaders = getRequestHeaderGroup().getHeaders("Cookie");
1270         for (int i = 0; i < cookieheaders.length; i++) {
1271             Header cookieheader = cookieheaders[i];
1272             if (cookieheader.isAutogenerated()) {
1273                 getRequestHeaderGroup().removeHeader(cookieheader);
1274             }
1275         }
1276 
1277         CookieSpec matcher = getCookieSpec(state);
1278         String host = this.params.getVirtualHost();
1279         if (host == null) {
1280             host = conn.getHost();
1281         }
1282         Cookie[] cookies = matcher.match(host, conn.getPort(),
1283             getPath(), conn.isSecure(), state.getCookies());
1284         if ((cookies != null) && (cookies.length > 0)) {
1285             if (getParams().isParameterTrue(HttpMethodParams.SINGLE_COOKIE_HEADER)) {
1286                 // In strict mode put all cookies on the same header
1287                 String s = matcher.formatCookies(cookies);
1288                 getRequestHeaderGroup().addHeader(new Header("Cookie", s, true));
1289             } else {
1290                 // In non-strict mode put each cookie on a separate header
1291                 for (int i = 0; i < cookies.length; i++) {
1292                     String s = matcher.formatCookie(cookies[i]);
1293                     getRequestHeaderGroup().addHeader(new Header("Cookie", s, true));
1294                 }
1295             }
1296             if (matcher instanceof CookieVersionSupport) {
1297                 CookieVersionSupport versupport = (CookieVersionSupport) matcher;
1298                 int ver = versupport.getVersion();
1299                 boolean needVersionHeader = false;
1300                 for (int i = 0; i < cookies.length; i++) {
1301                     if (ver != cookies[i].getVersion()) {
1302                         needVersionHeader = true;
1303                     }
1304                 }
1305                 if (needVersionHeader) {
1306                     // Advertise cookie version support
1307                     getRequestHeaderGroup().addHeader(versupport.getVersionHeader());
1308                 }
1309             }
1310         }
1311     }
1312 
1313     /***
1314      * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
1315      * header already exists.
1316      *
1317      * @param state the {@link HttpState state} information associated with this method
1318      * @param conn the {@link HttpConnection connection} used to execute
1319      *        this HTTP method
1320      *
1321      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1322      *                     can be recovered from.
1323      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1324      *                    cannot be recovered from.
1325      */
1326     protected void addHostRequestHeader(HttpState state, HttpConnection conn)
1327     throws IOException, HttpException {
1328         LOG.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
1329                   + "HttpConnection)");
1330 
1331         // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
1332         // applications to send the Host request-header.
1333         // TODO: Add the ability to disable the sending of this header for
1334         //       HTTP/1.0 requests.
1335         String host = this.params.getVirtualHost();
1336         if (host != null) {
1337             LOG.debug("Using virtual host name: " + host);
1338         } else {
1339             host = conn.getHost();
1340         }
1341         int port = conn.getPort();
1342 
1343         // Note: RFC 2616 uses the term "internet host name" for what goes on the
1344         // host line.  It would seem to imply that host should be blank if the
1345         // host is a number instead of an name.  Based on the behavior of web
1346         // browsers, and the fact that RFC 2616 never defines the phrase "internet
1347         // host name", and the bad behavior of HttpClient that follows if we
1348         // send blank, I interpret this as a small misstatement in the RFC, where
1349         // they meant to say "internet host".  So IP numbers get sent as host
1350         // entries too. -- Eric Johnson 12/13/2002
1351         if (LOG.isDebugEnabled()) {
1352             LOG.debug("Adding Host request header");
1353         }
1354 
1355         //appends the port only if not using the default port for the protocol
1356         if (conn.getProtocol().getDefaultPort() != port) {
1357             host += (":" + port);
1358         }
1359 
1360         setRequestHeader("Host", host);
1361     }
1362 
1363     /***
1364      * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when 
1365      * communicating via a proxy server.
1366      *
1367      * @param state the {@link HttpState state} information associated with this method
1368      * @param conn the {@link HttpConnection connection} used to execute
1369      *        this HTTP method
1370      *
1371      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1372      *                     can be recovered from.
1373      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1374      *                    cannot be recovered from.
1375      */
1376     protected void addProxyConnectionHeader(HttpState state,
1377                                             HttpConnection conn)
1378     throws IOException, HttpException {
1379         LOG.trace("enter HttpMethodBase.addProxyConnectionHeader("
1380                   + "HttpState, HttpConnection)");
1381         if (!conn.isTransparent()) {
1382         	if (getRequestHeader("Proxy-Connection") == null) {
1383                 addRequestHeader("Proxy-Connection", "Keep-Alive");
1384         	}
1385         }
1386     }
1387 
1388     /***
1389      * Generates all the required request {@link Header header}s 
1390      * to be submitted via the given {@link HttpConnection connection}.
1391      *
1392      * <p>
1393      * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
1394      * <tt>Cookie</tt>, <tt>Authorization</tt>, <tt>Proxy-Authorization</tt>
1395      * and <tt>Proxy-Connection</tt> headers, when appropriate.
1396      * </p>
1397      *
1398      * <p>
1399      * Subclasses may want to override this method to to add additional
1400      * headers, and may choose to invoke this implementation (via
1401      * <tt>super</tt>) to add the "standard" headers.
1402      * </p>
1403      *
1404      * @param state the {@link HttpState state} information associated with this method
1405      * @param conn the {@link HttpConnection connection} used to execute
1406      *        this HTTP method
1407      *
1408      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1409      *                     can be recovered from.
1410      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1411      *                    cannot be recovered from.
1412      *
1413      * @see #writeRequestHeaders
1414      */
1415     protected void addRequestHeaders(HttpState state, HttpConnection conn)
1416     throws IOException, HttpException {
1417         LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
1418             + "HttpConnection)");
1419 
1420         addUserAgentRequestHeader(state, conn);
1421         addHostRequestHeader(state, conn);
1422         addCookieRequestHeader(state, conn);
1423         addProxyConnectionHeader(state, conn);
1424     }
1425 
1426     /***
1427      * Generates default <tt>User-Agent</tt> request header, as long as no
1428      * <tt>User-Agent</tt> request header already exists.
1429      *
1430      * @param state the {@link HttpState state} information associated with this method
1431      * @param conn the {@link HttpConnection connection} used to execute
1432      *        this HTTP method
1433      *
1434      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1435      *                     can be recovered from.
1436      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1437      *                    cannot be recovered from.
1438      */
1439     protected void addUserAgentRequestHeader(HttpState state,
1440                                              HttpConnection conn)
1441     throws IOException, HttpException {
1442         LOG.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
1443             + "HttpConnection)");
1444 
1445         if (getRequestHeader("User-Agent") == null) {
1446             String agent = (String)getParams().getParameter(HttpMethodParams.USER_AGENT);
1447             if (agent == null) {
1448                 agent = "