View Javadoc

1   /*
2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.222 2005/01/14 21:16:40 olegk Exp $
3    * $Revision: 345322 $
4    * $Date: 2005-11-17 15:20:08 -0500 (Thu, 17 Nov 2005) $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   */
29  
30  package org.apache.commons.httpclient;
31  
32  import java.io.ByteArrayInputStream;
33  import java.io.ByteArrayOutputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.InterruptedIOException;
37  import java.util.Collection;
38  
39  import org.apache.commons.httpclient.auth.AuthState;
40  import org.apache.commons.httpclient.cookie.CookiePolicy;
41  import org.apache.commons.httpclient.cookie.CookieSpec;
42  import org.apache.commons.httpclient.cookie.MalformedCookieException;
43  import org.apache.commons.httpclient.params.HttpMethodParams;
44  import org.apache.commons.httpclient.protocol.Protocol;
45  import org.apache.commons.httpclient.util.EncodingUtil;
46  import org.apache.commons.httpclient.util.ExceptionUtil;
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  
50  /***
51   * An abstract base implementation of HttpMethod.
52   * <p>
53   * At minimum, subclasses will need to override:
54   * <ul>
55   *   <li>{@link #getName} to return the approriate name for this method
56   *   </li>
57   * </ul>
58   * </p>
59   *
60   * <p>
61   * When a method requires additional request headers, subclasses will typically
62   * want to override:
63   * <ul>
64   *   <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
65   *      to write those headers
66   *   </li>
67   * </ul>
68   * </p>
69   *
70   * <p>
71   * When a method expects specific response headers, subclasses may want to
72   * override:
73   * <ul>
74   *   <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}
75   *     to handle those headers
76   *   </li>
77   * </ul>
78   * </p>
79   *
80   *
81   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
82   * @author Rodney Waldhoff
83   * @author Sean C. Sullivan
84   * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
85   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
86   * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
87   * @author Ortwin Glueck
88   * @author Eric Johnson
89   * @author Michael Becke
90   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
91   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
92   * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
93   * @author Christian Kohlschuetter
94   *
95   * @version $Revision: 345322 $ $Date: 2005-11-17 15:20:08 -0500 (Thu, 17 Nov 2005) $
96   */
97  public abstract class HttpMethodBase implements HttpMethod {
98  
99      // -------------------------------------------------------------- Constants
100 
101     /*** Log object for this class. */
102     private static final Log LOG = LogFactory.getLog(HttpMethodBase.class);
103 
104     // ----------------------------------------------------- Instance variables 
105 
106     /*** Request headers, if any. */
107     private HeaderGroup requestHeaders = new HeaderGroup();
108 
109     /*** The Status-Line from the response. */
110     private StatusLine statusLine = null;
111 
112     /*** Response headers, if any. */
113     private HeaderGroup responseHeaders = new HeaderGroup();
114 
115     /*** Response trailer headers, if any. */
116     private HeaderGroup responseTrailerHeaders = new HeaderGroup();
117 
118     /*** Path of the HTTP method. */
119     private String path = null;
120 
121     /*** Query string of the HTTP method, if any. */
122     private String queryString = null;
123 
124     /*** The response body of the HTTP method, assuming it has not be 
125      * intercepted by a sub-class. */
126     private InputStream responseStream = null;
127 
128     /*** The connection that the response stream was read from. */
129     private HttpConnection responseConnection = null;
130 
131     /*** Buffer for the response */
132     private byte[] responseBody = null;
133 
134     /*** True if the HTTP method should automatically follow HTTP redirects.*/
135     private boolean followRedirects = false;
136 
137     /*** True if the HTTP method should automatically handle
138     *  HTTP authentication challenges. */
139     private boolean doAuthentication = true;
140 
141     /*** HTTP protocol parameters. */
142     private HttpMethodParams params = new HttpMethodParams();
143 
144     /*** Host authentication state */
145     private AuthState hostAuthState = new AuthState();
146 
147     /*** Proxy authentication state */
148     private AuthState proxyAuthState = new AuthState();
149 
150     /*** True if this method has already been executed. */
151     private boolean used = false;
152 
153     /*** Count of how many times did this HTTP method transparently handle 
154     * a recoverable exception. */
155     private int recoverableExceptionCount = 0;
156 
157     /*** the host for this HTTP method, can be null */
158     private HttpHost httphost = null;
159 
160     /***
161      * Handles method retries
162      * 
163      * @deprecated no loner used
164      */
165     private MethodRetryHandler methodRetryHandler;
166 
167     /*** True if the connection must be closed when no longer needed */
168     private boolean connectionCloseForced = false;
169 
170     /*** Number of milliseconds to wait for 100-contunue response. */
171     private static final int RESPONSE_WAIT_TIME_MS = 3000;
172 
173     /*** HTTP protocol version used for execution of this method. */
174     private HttpVersion effectiveVersion = null;
175 
176     /*** Whether the execution of this method has been aborted */
177     private transient boolean aborted = false;
178 
179     /*** Whether the HTTP request has been transmitted to the target
180      * server it its entirety */
181     private boolean requestSent = false;
182     
183     /*** Actual cookie policy */
184     private CookieSpec cookiespec = null;
185 
186     /*** Default initial size of the response buffer if content length is unknown. */
187     private static final int DEFAULT_INITIAL_BUFFER_SIZE = 4*1024; // 4 kB
188     
189     // ----------------------------------------------------------- Constructors
190 
191     /***
192      * No-arg constructor.
193      */
194     public HttpMethodBase() {
195     }
196 
197     /***
198      * Constructor specifying a URI.
199      * It is responsibility of the caller to ensure that URI elements
200      * (path & query parameters) are properly encoded (URL safe).
201      *
202      * @param uri either an absolute or relative URI. The URI is expected
203      *            to be URL-encoded
204      * 
205      * @throws IllegalArgumentException when URI is invalid
206      * @throws IllegalStateException when protocol of the absolute URI is not recognised
207      */
208     public HttpMethodBase(String uri) 
209         throws IllegalArgumentException, IllegalStateException {
210 
211         try {
212 
213             // create a URI and allow for null/empty uri values
214             if (uri == null || uri.equals("")) {
215                 uri = "/";
216             }
217             setURI(new URI(uri, true));
218         } catch (URIException e) {
219             throw new IllegalArgumentException("Invalid uri '" 
220                 + uri + "': " + e.getMessage() 
221             );
222         }
223     }
224 
225     // ------------------------------------------- Property Setters and Getters
226 
227     /***
228      * Obtains the name of the HTTP method as used in the HTTP request line,
229      * for example <tt>"GET"</tt> or <tt>"POST"</tt>.
230      * 
231      * @return the name of this method
232      */
233     public abstract String getName();
234 
235     /***
236      * Returns the URI of the HTTP method
237      * 
238      * @return The URI
239      * 
240      * @throws URIException If the URI cannot be created.
241      * 
242      * @see org.apache.commons.httpclient.HttpMethod#getURI()
243      */
244     public URI getURI() throws URIException {
245         StringBuffer buffer = new StringBuffer();
246         if (this.httphost != null) {
247             buffer.append(this.httphost.getProtocol().getScheme());
248             buffer.append("://");
249             buffer.append(this.httphost.getHostName());
250             int port = this.httphost.getPort();
251             if (port != -1 && port != this.httphost.getProtocol().getDefaultPort()) {
252                 buffer.append(":");
253                 buffer.append(port);
254             }
255         }
256         buffer.append(this.path);
257         if (this.queryString != null) {
258             buffer.append('?');
259             buffer.append(this.queryString);
260         }
261         return new URI(buffer.toString(), true);
262     }
263 
264     /***
265      * Sets the URI for this method. 
266      * 
267      * @param uri URI to be set 
268      * 
269      * @throws URIException if a URI cannot be set
270      * 
271      * @since 3.0
272      */
273     public void setURI(URI uri) throws URIException {
274         // only set the host if specified by the URI
275         if (uri.isAbsoluteURI()) {
276             this.httphost = new HttpHost(uri);
277         }
278         // set the path, defaulting to root
279         setPath(
280             uri.getPath() == null
281             ? "/"
282             : uri.getEscapedPath()
283         );
284         setQueryString(uri.getEscapedQuery());
285     } 
286 
287     /***
288      * Sets whether or not the HTTP method should automatically follow HTTP redirects 
289      * (status code 302, etc.)
290      * 
291      * @param followRedirects <tt>true</tt> if the method will automatically follow redirects,
292      * <tt>false</tt> otherwise.
293      */
294     public void setFollowRedirects(boolean followRedirects) {
295         this.followRedirects = followRedirects;
296     }
297 
298     /***
299      * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects 
300      * (status code 302, etc.), <tt>false</tt> otherwise.
301      * 
302      * @return <tt>true</tt> if the method will automatically follow HTTP redirects, 
303      * <tt>false</tt> otherwise.
304      */
305     public boolean getFollowRedirects() {
306         return this.followRedirects;
307     }
308 
309     /*** Sets whether version 1.1 of the HTTP protocol should be used per default.
310      *
311      * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
312      * 
313      * @deprecated Use {@link HttpMethodParams#setVersion(HttpVersion)}
314      */
315     public void setHttp11(boolean http11) {
316         if (http11) {
317             this.params.setVersion(HttpVersion.HTTP_1_1);
318         } else {
319             this.params.setVersion(HttpVersion.HTTP_1_0);
320         } 
321     }
322 
323     /***
324      * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP 
325      * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise
326      *
327      * @return <tt>true</tt> if authentication challenges will be processed 
328      * automatically, <tt>false</tt> otherwise.
329      * 
330      * @since 2.0
331      */
332     public boolean getDoAuthentication() {
333         return doAuthentication;
334     }
335 
336     /***
337      * Sets whether or not the HTTP method should automatically handle HTTP 
338      * authentication challenges (status code 401, etc.)
339      *
340      * @param doAuthentication <tt>true</tt> to process authentication challenges
341      * authomatically, <tt>false</tt> otherwise.
342      * 
343      * @since 2.0
344      */
345     public void setDoAuthentication(boolean doAuthentication) {
346         this.doAuthentication = doAuthentication;
347     }
348 
349     // ---------------------------------------------- Protected Utility Methods
350 
351     /***
352      * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be 
353      * used per default, <tt>false</tt> if version 1.0 should be used.
354      *
355      * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
356      * 
357      * @deprecated Use {@link HttpMethodParams#getVersion()}
358      */
359     public boolean isHttp11() {
360         return this.params.getVersion().equals(HttpVersion.HTTP_1_1);
361     }
362 
363     /***
364      * Sets the path of the HTTP method.
365      * It is responsibility of the caller to ensure that the path is
366      * properly encoded (URL safe).
367      *
368      * @param path the path of the HTTP method. The path is expected
369      *        to be URL-encoded
370      */
371     public void setPath(String path) {
372         this.path = path;
373     }
374 
375     /***
376      * Adds the specified request header, NOT overwriting any previous value.
377      * Note that header-name matching is case insensitive.
378      *
379      * @param header the header to add to the request
380      */
381     public void addRequestHeader(Header header) {
382         LOG.trace("HttpMethodBase.addRequestHeader(Header)");
383 
384         if (header == null) {
385             LOG.debug("null header value ignored");
386         } else {
387             getRequestHeaderGroup().addHeader(header);
388         }
389     }
390 
391     /***
392      * Use this method internally to add footers.
393      * 
394      * @param footer The footer to add.
395      */
396     public void addResponseFooter(Header footer) {
397         getResponseTrailerHeaderGroup().addHeader(footer);
398     }
399 
400     /***
401      * Gets the path of this HTTP method.
402      * Calling this method <em>after</em> the request has been executed will 
403      * return the <em>actual</em> path, following any redirects automatically
404      * handled by this HTTP method.
405      *
406      * @return the path to request or "/" if the path is blank.
407      */
408     public String getPath() {
409         return (path == null || path.equals("")) ? "/" : path;
410     }
411 
412     /***
413      * Sets the query string of this HTTP method. The caller must ensure that the string 
414      * is properly URL encoded. The query string should not start with the question 
415      * mark character.
416      *
417      * @param queryString the query string
418      * 
419      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
420      */
421     public void setQueryString(String queryString) {
422         this.queryString = queryString;
423     }
424 
425     /***
426      * Sets the query string of this HTTP method.  The pairs are encoded as UTF-8 characters.  
427      * To use a different charset the parameters can be encoded manually using EncodingUtil 
428      * and set as a single String.
429      *
430      * @param params an array of {@link NameValuePair}s to add as query string
431      *        parameters. The name/value pairs will be automcatically 
432      *        URL encoded
433      * 
434      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
435      * @see #setQueryString(String)
436      */
437     public void setQueryString(NameValuePair[] params) {
438         LOG.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
439         queryString = EncodingUtil.formUrlEncode(params, "UTF-8");
440     }
441 
442     /***
443      * Gets the query string of this HTTP method.
444      *
445      * @return The query string
446      */
447     public String getQueryString() {
448         return queryString;
449     }
450 
451     /***
452      * Set the specified request header, overwriting any previous value. Note
453      * that header-name matching is case-insensitive.
454      *
455      * @param headerName the header's name
456      * @param headerValue the header's value
457      */
458     public void setRequestHeader(String headerName, String headerValue) {
459         Header header = new Header(headerName, headerValue);
460         setRequestHeader(header);
461     }
462 
463     /***
464      * Sets the specified request header, overwriting any previous value.
465      * Note that header-name matching is case insensitive.
466      * 
467      * @param header the header
468      */
469     public void setRequestHeader(Header header) {
470         
471         Header[] headers = getRequestHeaderGroup().getHeaders(header.getName());
472         
473         for (int i = 0; i < headers.length; i++) {
474             getRequestHeaderGroup().removeHeader(headers[i]);
475         }
476         
477         getRequestHeaderGroup().addHeader(header);
478         
479     }
480 
481     /***
482      * Returns the specified request header. Note that header-name matching is
483      * case insensitive. <tt>null</tt> will be returned if either
484      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
485      * <i>headerName</i>.
486      * 
487      * @param headerName The name of the header to be returned.
488      *
489      * @return The specified request header.
490      * 
491      * @since 3.0
492      */
493     public Header getRequestHeader(String headerName) {
494         if (headerName == null) {
495             return null;
496         } else {
497             return getRequestHeaderGroup().getCondensedHeader(headerName);
498         }
499     }
500 
501     /***
502      * Returns an array of the requests headers that the HTTP method currently has
503      *
504      * @return an array of my request headers.
505      */
506     public Header[] getRequestHeaders() {
507         return getRequestHeaderGroup().getAllHeaders();
508     }
509 
510     /***
511      * @see org.apache.commons.httpclient.HttpMethod#getRequestHeaders(java.lang.String)
512      */
513     public Header[] getRequestHeaders(String headerName) {
514         return getRequestHeaderGroup().getHeaders(headerName);
515     }
516 
517     /***
518      * Gets the {@link HeaderGroup header group} storing the request headers.
519      * 
520      * @return a HeaderGroup
521      * 
522      * @since 2.0beta1
523      */
524     protected HeaderGroup getRequestHeaderGroup() {
525         return requestHeaders;
526     }
527 
528     /***
529      * Gets the {@link HeaderGroup header group} storing the response trailer headers 
530      * as per RFC 2616 section 3.6.1.
531      * 
532      * @return a HeaderGroup
533      * 
534      * @since 2.0beta1
535      */
536     protected HeaderGroup getResponseTrailerHeaderGroup() {
537         return responseTrailerHeaders;
538     }
539 
540     /***
541      * Gets the {@link HeaderGroup header group} storing the response headers.
542      * 
543      * @return a HeaderGroup
544      * 
545      * @since 2.0beta1
546      */
547     protected HeaderGroup getResponseHeaderGroup() {
548         return responseHeaders;
549     }
550     
551     /***
552      * @see org.apache.commons.httpclient.HttpMethod#getResponseHeaders(java.lang.String)
553      * 
554      * @since 3.0
555      */
556     public Header[] getResponseHeaders(String headerName) {
557         return getResponseHeaderGroup().getHeaders(headerName);
558     }
559 
560     /***
561      * Returns the response status code.
562      *
563      * @return the status code associated with the latest response.
564      */
565     public int getStatusCode() {
566         return statusLine.getStatusCode();
567     }
568 
569     /***
570      * Provides access to the response status line.
571      *
572      * @return the status line object from the latest response.
573      * @since 2.0
574      */
575     public StatusLine getStatusLine() {
576         return statusLine;
577     }
578 
579     /***
580      * Checks if response data is available.
581      * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise.
582      */
583     private boolean responseAvailable() {
584         return (responseBody != null) || (responseStream != null);
585     }
586 
587     /***
588      * Returns an array of the response headers that the HTTP method currently has
589      * in the order in which they were read.
590      *
591      * @return an array of response headers.
592      */
593     public Header[] getResponseHeaders() {
594         return getResponseHeaderGroup().getAllHeaders();
595     }
596 
597     /***
598      * Gets the response header associated with the given name. Header name
599      * matching is case insensitive. <tt>null</tt> will be returned if either
600      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
601      * <i>headerName</i>.
602      *
603      * @param headerName the header name to match
604      *
605      * @return the matching header
606      */
607     public Header getResponseHeader(String headerName) {        
608         if (headerName == null) {
609             return null;
610         } else {
611             return getResponseHeaderGroup().getCondensedHeader(headerName);
612         }        
613     }
614 
615 
616     /***
617      * Return the length (in bytes) of the response body, as specified in a
618      * <tt>Content-Length</tt> header.
619      *
620      * <p>
621      * Return <tt>-1</tt> when the content-length is unknown.
622      * </p>
623      *
624      * @return content length, if <tt>Content-Length</tt> header is available. 
625      *          <tt>0</tt> indicates that the request has no body.
626      *          If <tt>Content-Length</tt> header is not present, the method 
627      *          returns  <tt>-1</tt>.
628      */
629     public long getResponseContentLength() {
630         Header[] headers = getResponseHeaderGroup().getHeaders("Content-Length");
631         if (headers.length == 0) {
632             return -1;
633         }
634         if (headers.length > 1) {
635             LOG.warn("Multiple content-length headers detected");
636         }
637         for (int i = headers.length - 1; i >= 0; i--) {
638             Header header = headers[i];
639             try {
640                 return Long.parseLong(header.getValue());
641             } catch (NumberFormatException e) {
642                 if (LOG.isWarnEnabled()) {
643                     LOG.warn("Invalid content-length value: " + e.getMessage());
644                 }
645             }
646             // See if we can have better luck with another header, if present
647         }
648         return -1;
649     }
650 
651 
652     /***
653      * Returns the response body of the HTTP method, if any, as an array of bytes.
654      * If response body is not available or cannot be read, returns <tt>null</tt>
655      * 
656      * Note: This will cause the entire response body to be buffered in memory. A
657      * malicious server may easily exhaust all the VM memory. It is strongly
658      * recommended, to use getResponseAsStream if the content length of the response
659      * is unknown or resonably large.
660      *  
661      * @return The response body.
662      * 
663      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
664      * response body.
665      */
666     public byte[] getResponseBody() throws IOException {
667         if (this.responseBody == null) {
668             InputStream instream = getResponseBodyAsStream();
669             if (instream != null) {
670                 long contentLength = getResponseContentLength();
671                 if (contentLength > Integer.MAX_VALUE) { //guard below cast from overflow
672                     throw new IOException("Content too large to be buffered: "+ contentLength +" bytes");
673                 }
674                 int limit = getParams().getIntParameter(HttpMethodParams.BUFFER_WARN_TRIGGER_LIMIT, 1024*1024);
675                 if ((contentLength == -1) || (contentLength > limit)) {
676                     LOG.warn("Going to buffer response body of large or unknown size. "
677                             +"Using getResponseBodyAsStream instead is recommended.");
678                 }
679                 LOG.debug("Buffering response body");
680                 ByteArrayOutputStream outstream = new ByteArrayOutputStream(
681                         contentLength > 0 ? (int) contentLength : DEFAULT_INITIAL_BUFFER_SIZE);
682                 byte[] buffer = new byte[4096];
683                 int len;
684                 while ((len = instream.read(buffer)) > 0) {
685                     outstream.write(buffer, 0, len);
686                 }
687                 outstream.close();
688                 setResponseStream(null);
689                 this.responseBody = outstream.toByteArray();
690             }
691         }
692         return this.responseBody;
693     }
694 
695     /***
696      * Returns the response body of the HTTP method, if any, as an {@link InputStream}. 
697      * If response body is not available, returns <tt>null</tt>
698      * 
699      * @return The response body
700      * 
701      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
702      * response body.
703      */
704     public InputStream getResponseBodyAsStream() throws IOException {
705         if (responseStream != null) {
706             return responseStream;
707         }
708         if (responseBody != null) {
709             InputStream byteResponseStream = new ByteArrayInputStream(responseBody);
710             LOG.debug("re-creating response stream from byte array");
711             return byteResponseStream;
712         }
713         return null;
714     }
715 
716     /***
717      * Returns the response body of the HTTP method, if any, as a {@link String}. 
718      * If response body is not available or cannot be read, returns <tt>null</tt>
719      * The string conversion on the data is done using the character encoding specified
720      * in <tt>Content-Type</tt> header.
721      * 
722      * Note: This will cause the entire response body to be buffered in memory. A
723      * malicious server may easily exhaust all the VM memory. It is strongly
724      * recommended, to use getResponseAsStream if the content length of the response
725      * is unknown or resonably large.
726      * 
727      * @return The response body.
728      * 
729      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
730      * response body.
731      */
732     public String getResponseBodyAsString() throws IOException {
733         byte[] rawdata = null;
734         if (responseAvailable()) {
735             rawdata = getResponseBody();
736         }
737         if (rawdata != null) {
738             return EncodingUtil.getString(rawdata, getResponseCharSet());
739         } else {
740             return null;
741         }
742     }
743 
744     /***
745      * Returns an array of the response footers that the HTTP method currently has
746      * in the order in which they were read.
747      *
748      * @return an array of footers
749      */
750     public Header[] getResponseFooters() {
751         return getResponseTrailerHeaderGroup().getAllHeaders();
752     }
753 
754     /***
755      * Gets the response footer associated with the given name.
756      * Footer name matching is case insensitive.
757      * <tt>null</tt> will be returned if either <i>footerName</i> is
758      * <tt>null</tt> or there is no matching footer for <i>footerName</i>
759      * or there are no footers available.  If there are multiple footers
760      * with the same name, there values will be combined with the ',' separator
761      * as specified by RFC2616.
762      * 
763      * @param footerName the footer name to match
764      * @return the matching footer
765      */
766     public Header getResponseFooter(String footerName) {
767         if (footerName == null) {
768             return null;
769         } else {
770             return getResponseTrailerHeaderGroup().getCondensedHeader(footerName);
771         }
772     }
773 
774     /***
775      * Sets the response stream.
776      * @param responseStream The new response stream.
777      */
778     protected void setResponseStream(InputStream responseStream) {
779         this.responseStream = responseStream;
780     }
781 
782     /***
783      * Returns a stream from which the body of the current response may be read.
784      * If the method has not yet been executed, if <code>responseBodyConsumed</code>
785      * has been called, or if the stream returned by a previous call has been closed,
786      * <code>null</code> will be returned.
787      *
788      * @return the current response stream
789      */
790     protected InputStream getResponseStream() {
791         return responseStream;
792     }
793     
794     /***
795      * Returns the status text (or "reason phrase") associated with the latest
796      * response.
797      * 
798      * @return The status text.
799      */
800     public String getStatusText() {
801         return statusLine.getReasonPhrase();
802     }
803 
804     /***
805      * Defines how strictly HttpClient follows the HTTP protocol specification  
806      * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely
807      * implements the requirements of the specification, whereas in non-strict mode 
808      * it attempts to mimic the exact behaviour of commonly used HTTP agents, 
809      * which many HTTP servers expect.
810      * 
811      * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise
812      * 
813      * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
814      * to exercise a more granular control over HTTP protocol strictness.
815      */
816     public void setStrictMode(boolean strictMode) {
817         if (strictMode) {
818             this.params.makeStrict();
819         } else {
820             this.params.makeLenient();
821         }
822     }
823 
824     /***
825      * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
826      * to exercise a more granular control over HTTP protocol strictness.
827      *
828      * @return <tt>false</tt>
829      */
830     public boolean isStrictMode() {
831         return false;
832     }
833 
834     /***
835      * Adds the specified request header, NOT overwriting any previous value.
836      * Note that header-name matching is case insensitive.
837      *
838      * @param headerName the header's name
839      * @param headerValue the header's value
840      */
841     public void addRequestHeader(String headerName, String headerValue) {
842         addRequestHeader(new Header(headerName, headerValue));
843     }
844 
845     /***
846      * Tests if the connection should be force-closed when no longer needed.
847      * 
848      * @return <code>true</code> if the connection must be closed
849      */
850     protected boolean isConnectionCloseForced() {
851         return this.connectionCloseForced;
852     }
853 
854     /***
855      * Sets whether or not the connection should be force-closed when no longer 
856      * needed. This value should only be set to <code>true</code> in abnormal 
857      * circumstances, such as HTTP protocol violations. 
858      * 
859      * @param b <code>true</code> if the connection must be closed, <code>false</code>
860      * otherwise.
861      */
862     protected void setConnectionCloseForced(boolean b) {
863         if (LOG.isDebugEnabled()) {
864             LOG.debug("Force-close connection: " + b);
865         }
866         this.connectionCloseForced = b;
867     }
868 
869     /***
870      * Tests if the connection should be closed after the method has been executed.
871      * The connection will be left open when using HTTP/1.1 or if <tt>Connection: 
872      * keep-alive</tt> header was sent.
873      * 
874      * @param conn the connection in question
875      * 
876      * @return boolean true if we should close the connection.
877      */
878     protected boolean shouldCloseConnection(HttpConnection conn) {
879         // Connection must be closed due to an abnormal circumstance 
880         if (isConnectionCloseForced()) {
881             LOG.debug("Should force-close connection.");
882             return true;
883         }
884 
885         Header connectionHeader = null;
886         // In case being connected via a proxy server
887         if (!conn.isTransparent()) {
888             // Check for 'proxy-connection' directive
889             connectionHeader = responseHeaders.getFirstHeader("proxy-connection");
890         }
891         // In all cases Check for 'connection' directive
892         // some non-complaint proxy servers send it instread of
893         // expected 'proxy-connection' directive
894         if (connectionHeader == null) {
895             connectionHeader = responseHeaders.getFirstHeader("connection");
896         }
897         // In case the response does not contain any explict connection
898         // directives, check whether the request does
899         if (connectionHeader == null) {
900             connectionHeader = requestHeaders.getFirstHeader("connection");
901         }
902         if (connectionHeader != null) {
903             if (connectionHeader.getValue().equalsIgnoreCase("close")) {
904                 if (LOG.isDebugEnabled()) {
905                     LOG.debug("Should close connection in response to directive: " 
906                         + connectionHeader.getValue());
907                 }
908                 return true;
909             } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) {
910                 if (LOG.isDebugEnabled()) {
911                     LOG.debug("Should NOT close connection in response to directive: " 
912                         + connectionHeader.getValue());
913                 }
914                 return false;
915             } else {
916                 if (LOG.isDebugEnabled()) {
917                     LOG.debug("Unknown directive: " + connectionHeader.toExternalForm());
918                 }
919             }
920         }
921         LOG.debug("Resorting to protocol version default close connection policy");
922         // missing or invalid connection header, do the default
923         if (this.effectiveVersion.greaterEquals(HttpVersion.HTTP_1_1)) {
924             if (LOG.isDebugEnabled()) {
925                 LOG.debug("Should NOT close connection, using " + this.effectiveVersion.toString());
926             }
927         } else {
928             if (LOG.isDebugEnabled()) {
929                 LOG.debug("Should close connection, using " + this.effectiveVersion.toString());
930             }
931         }
932         return this.effectiveVersion.lessEquals(HttpVersion.HTTP_1_0);
933     }
934     
935     /***
936      * Tests if the this method is ready to be executed.
937      * 
938      * @param state the {@link HttpState state} information associated with this method
939      * @param conn the {@link HttpConnection connection} to be used
940      * @throws HttpException If the method is in invalid state.
941      */
942     private void checkExecuteConditions(HttpState state, HttpConnection conn)
943     throws HttpException {
944 
945         if (state == null) {
946             throw new IllegalArgumentException("HttpState parameter may not be null");
947         }
948         if (conn == null) {
949             throw new IllegalArgumentException("HttpConnection parameter may not be null");
950         }
951         if (this.aborted) {
952             throw new IllegalStateException("Method has been aborted");
953         }
954         if (!validate()) {
955             throw new ProtocolException("HttpMethodBase object not valid");
956         }
957     }
958 
959     /***
960      * Executes this method using the specified <code>HttpConnection</code> and
961      * <code>HttpState</code>. 
962      *
963      * @param state {@link HttpState state} information to associate with this
964      *        request. Must be non-null.
965      * @param conn the {@link HttpConnection connection} to used to execute
966      *        this HTTP method. Must be non-null.
967      *
968      * @return the integer status code if one was obtained, or <tt>-1</tt>
969      *
970      * @throws IOException if an I/O (transport) error occurs
971      * @throws HttpException  if a protocol exception occurs.
972      */
973     public int execute(HttpState state, HttpConnection conn)
974         throws HttpException, IOException {
975                 
976         LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
977 
978         // this is our connection now, assign it to a local variable so 
979         // that it can be released later
980         this.responseConnection = conn;
981 
982         checkExecuteConditions(state, conn);
983         this.statusLine = null;
984         this.connectionCloseForced = false;
985 
986         conn.setLastResponseInputStream(null);
987 
988         // determine the effective protocol version
989         if (this.effectiveVersion == null) {
990             this.effectiveVersion = this.params.getVersion(); 
991         }
992 
993         writeRequest(state, conn);
994         this.requestSent = true;
995         readResponse(state, conn);
996         // the method has successfully executed
997         used = true; 
998 
999         return statusLine.getStatusCode();
1000     }
1001 
1002     /***
1003      * Aborts the execution of this method.
1004      * 
1005      * @since 3.0
1006      */
1007     public void abort() {
1008         if (this.aborted) {
1009             return;
1010         }
1011         this.aborted = true;
1012         HttpConnection conn = this.responseConnection; 
1013         if (conn != null) {
1014             conn.close();
1015         }
1016     }
1017 
1018     /***
1019      * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed},
1020      * but not {@link #recycle recycled}.
1021      * 
1022      * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise
1023      */
1024     public boolean hasBeenUsed() {
1025         return used;
1026     }
1027 
1028     /***
1029      * Recycles the HTTP method so that it can be used again.
1030      * Note that all of the instance variables will be reset
1031      * once this method has been called. This method will also
1032      * release the connection being used by this HTTP method.
1033      * 
1034      * @see #releaseConnection()
1035      * 
1036      * @deprecated no longer supported and will be removed in the future
1037      *             version of HttpClient
1038      */
1039     public void recycle() {
1040         LOG.trace("enter HttpMethodBase.recycle()");
1041 
1042         releaseConnection();
1043 
1044         path = null;
1045         followRedirects = false;
1046         doAuthentication = true;
1047         queryString = null;
1048         getRequestHeaderGroup().clear();
1049         getResponseHeaderGroup().clear();
1050         getResponseTrailerHeaderGroup().clear();
1051         statusLine = null;
1052         effectiveVersion = null;
1053         aborted = false;
1054         used = false;
1055         params = new HttpMethodParams();
1056         responseBody = null;
1057         recoverableExceptionCount = 0;
1058         connectionCloseForced = false;
1059         hostAuthState.invalidate();
1060         proxyAuthState.invalidate();
1061         cookiespec = null;
1062         requestSent = false;
1063     }
1064 
1065     /***
1066      * Releases the connection being used by this HTTP method. In particular the
1067      * connection is used to read the response(if there is one) and will be held
1068      * until the response has been read. If the connection can be reused by other 
1069      * HTTP methods it is NOT closed at this point.
1070      *
1071      * @since 2.0
1072      */
1073     public void releaseConnection() {
1074         try {
1075             if (this.responseStream != null) {
1076                 try {
1077                     // FYI - this may indirectly invoke responseBodyConsumed.
1078                     this.responseStream.close();
1079                 } catch (IOException ignore) {
1080                 }
1081             }
1082         } finally {
1083             ensureConnectionRelease();
1084         }
1085     }
1086 
1087     /***
1088      * Remove the request header associated with the given name. Note that
1089      * header-name matching is case insensitive.
1090      *
1091      * @param headerName the header name
1092      */
1093     public void removeRequestHeader(String headerName) {
1094         
1095         Header[] headers = getRequestHeaderGroup().getHeaders(headerName);
1096         for (int i = 0; i < headers.length; i++) {
1097             getRequestHeaderGroup().removeHeader(headers[i]);
1098         }
1099         
1100     }
1101     
1102     /***
1103      * Removes the given request header.
1104      * 
1105      * @param header the header
1106      */
1107     public void removeRequestHeader(final Header header) {
1108         if (header == null) {
1109             return;
1110         }
1111         getRequestHeaderGroup().removeHeader(header);
1112     }
1113 
1114     // ---------------------------------------------------------------- Queries
1115 
1116     /***
1117      * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise.
1118      * 
1119      * @return This implementation always returns <tt>true</tt>.
1120      */
1121     public boolean validate() {
1122         return true;
1123     }
1124 
1125 
1126     /*** 
1127      * Returns the actual cookie policy
1128      * 
1129      * @param state HTTP state. TODO: to be removed in the future
1130      * 
1131      * @return cookie spec
1132      */
1133     private CookieSpec getCookieSpec(final HttpState state) {
1134     	if (this.cookiespec == null) {
1135     		int i = state.getCookiePolicy();
1136     		if (i == -1) {
1137         		this.cookiespec = CookiePolicy.getCookieSpec(this.params.getCookiePolicy());
1138     		} else {
1139         		this.cookiespec = CookiePolicy.getSpecByPolicy(i);
1140     		}
1141     		this.cookiespec.setValidDateFormats(
1142             		(Collection)this.params.getParameter(HttpMethodParams.DATE_PATTERNS));
1143     	}
1144     	return this.cookiespec;
1145     }
1146 
1147     /***
1148      * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s
1149      * that match the given host, port and path.
1150      *
1151      * @param state the {@link HttpState state} information associated with this method
1152      * @param conn the {@link HttpConnection connection} used to execute
1153      *        this HTTP method
1154      *
1155      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1156      *                     can be recovered from.
1157      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1158      *                    cannot be recovered from.
1159      */
1160     protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
1161         throws IOException, HttpException {
1162 
1163         LOG.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
1164                   + "HttpConnection)");
1165 
1166         Header[] cookieheaders = getRequestHeaderGroup().getHeaders("Cookie");
1167         for (int i = 0; i < cookieheaders.length; i++) {
1168             Header cookieheader = cookieheaders[i];
1169             if (cookieheader.isAutogenerated()) {
1170                 getRequestHeaderGroup().removeHeader(cookieheader);
1171             }
1172         }
1173 
1174         CookieSpec matcher = getCookieSpec(state);
1175         String host = this.params.getVirtualHost();
1176         if (host == null) {
1177             host = conn.getHost();
1178         }
1179         Cookie[] cookies = matcher.match(host, conn.getPort(),
1180             getPath(), conn.isSecure(), state.getCookies());
1181         if ((cookies != null) && (cookies.length > 0)) {
1182             if (getParams().isParameterTrue(HttpMethodParams.SINGLE_COOKIE_HEADER)) {
1183                 // In strict mode put all cookies on the same header
1184                 String s = matcher.formatCookies(cookies);
1185                 getRequestHeaderGroup().addHeader(new Header("Cookie", s, true));
1186             } else {
1187                 // In non-strict mode put each cookie on a separate header
1188                 for (int i = 0; i < cookies.length; i++) {
1189                     String s = matcher.formatCookie(cookies[i]);
1190                     getRequestHeaderGroup().addHeader(new Header("Cookie", s, true));
1191                 }
1192             }
1193         }
1194     }
1195 
1196     /***
1197      * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
1198      * header already exists.
1199      *
1200      * @param state the {@link HttpState state} information associated with this method
1201      * @param conn the {@link HttpConnection connection} used to execute
1202      *        this HTTP method
1203      *
1204      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1205      *                     can be recovered from.
1206      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1207      *                    cannot be recovered from.
1208      */
1209     protected void addHostRequestHeader(HttpState state, HttpConnection conn)
1210     throws IOException, HttpException {
1211         LOG.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
1212                   + "HttpConnection)");
1213 
1214         // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
1215         // applications to send the Host request-header.
1216         // TODO: Add the ability to disable the sending of this header for
1217         //       HTTP/1.0 requests.
1218         String host = this.params.getVirtualHost();
1219         if (host != null) {
1220             LOG.debug("Using virtual host name: " + host);
1221         } else {
1222             host = conn.getHost();
1223         }
1224         int port = conn.getPort();
1225 
1226         // Note: RFC 2616 uses the term "internet host name" for what goes on the
1227         // host line.  It would seem to imply that host should be blank if the
1228         // host is a number instead of an name.  Based on the behavior of web
1229         // browsers, and the fact that RFC 2616 never defines the phrase "internet
1230         // host name", and the bad behavior of HttpClient that follows if we
1231         // send blank, I interpret this as a small misstatement in the RFC, where
1232         // they meant to say "internet host".  So IP numbers get sent as host
1233         // entries too. -- Eric Johnson 12/13/2002
1234         if (LOG.isDebugEnabled()) {
1235             LOG.debug("Adding Host request header");
1236         }
1237 
1238         //appends the port only if not using the default port for the protocol
1239         if (conn.getProtocol().getDefaultPort() != port) {
1240             host += (":" + port);
1241         }
1242 
1243         setRequestHeader("Host", host);
1244     }
1245 
1246     /***
1247      * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when 
1248      * communicating via a proxy server.
1249      *
1250      * @param state the {@link HttpState state} information associated with this method
1251      * @param conn the {@link HttpConnection connection} used to execute
1252      *        this HTTP method
1253      *
1254      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1255      *                     can be recovered from.
1256      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1257      *                    cannot be recovered from.
1258      */
1259     protected void addProxyConnectionHeader(HttpState state,
1260                                             HttpConnection conn)
1261     throws IOException, HttpException {
1262         LOG.trace("enter HttpMethodBase.addProxyConnectionHeader("
1263                   + "HttpState, HttpConnection)");
1264         if (!conn.isTransparent()) {
1265         	if (getRequestHeader("Proxy-Connection") == null) {
1266                 addRequestHeader("Proxy-Connection", "Keep-Alive");
1267         	}
1268         }
1269     }
1270 
1271     /***
1272      * Generates all the required request {@link Header header}s 
1273      * to be submitted via the given {@link HttpConnection connection}.
1274      *
1275      * <p>
1276      * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
1277      * <tt>Cookie</tt>, <tt>Authorization</tt>, <tt>Proxy-Authorization</tt>
1278      * and <tt>Proxy-Connection</tt> headers, when appropriate.
1279      * </p>
1280      *
1281      * <p>
1282      * Subclasses may want to override this method to to add additional
1283      * headers, and may choose to invoke this implementation (via
1284      * <tt>super</tt>) to add the "standard" headers.
1285      * </p>
1286      *
1287      * @param state the {@link HttpState state} information associated with this method
1288      * @param conn the {@link HttpConnection connection} used to execute
1289      *        this HTTP method
1290      *
1291      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1292      *                     can be recovered from.
1293      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1294      *                    cannot be recovered from.
1295      *
1296      * @see #writeRequestHeaders
1297      */
1298     protected void addRequestHeaders(HttpState state, HttpConnection conn)
1299     throws IOException, HttpException {
1300         LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
1301             + "HttpConnection)");
1302 
1303         addUserAgentRequestHeader(state, conn);
1304         addHostRequestHeader(state, conn);
1305         addCookieRequestHeader(state, conn);
1306         addProxyConnectionHeader(state, conn);
1307     }
1308 
1309     /***
1310      * Generates default <tt>User-Agent</tt> request header, as long as no
1311      * <tt>User-Agent</tt> request header already exists.
1312      *
1313      * @param state the {@link HttpState state} information associated with this method
1314      * @param conn the {@link HttpConnection connection} used to execute
1315      *        this HTTP method
1316      *
1317      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1318      *                     can be recovered from.
1319      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1320      *                    cannot be recovered from.
1321      */
1322     protected void addUserAgentRequestHeader(HttpState state,
1323                                              HttpConnection conn)
1324     throws IOException, HttpException {
1325         LOG.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
1326             + "HttpConnection)");
1327 
1328         if (getRequestHeader("User-Agent") == null) {
1329             String agent = (String)getParams().getParameter(HttpMethodParams.USER_AGENT);
1330             if (agent == null) {
1331                 agent = "Jakarta Commons-HttpClient";
1332             }
1333             setRequestHeader("User-Agent", agent);
1334         }
1335     }
1336 
1337     /***
1338      * Throws an {@link IllegalStateException} if the HTTP method has been already
1339      * {@link #execute executed}, but not {@link #recycle recycled}.
1340      *
1341      * @throws IllegalStateException if the method has been used and not
1342      *      recycled
1343      */
1344     protected void checkNotUsed() throws IllegalStateException {
1345         if (used) {
1346             throw new IllegalStateException("Already used.");
1347         }
1348     }
1349 
1350     /***
1351      * Throws an {@link IllegalStateException} if the HTTP method has not been
1352      * {@link #execute executed} since last {@link #recycle recycle}.
1353      *
1354      *
1355      * @throws IllegalStateException if not used
1356      */
1357     protected void checkUsed()  throws IllegalStateException {
1358         if (!used) {
1359             throw new IllegalStateException("Not Used.");
1360         }
1361     }
1362 
1363     // ------------------------------------------------- Static Utility Methods
1364 
1365     /***
1366      * Generates HTTP request line according to the specified attributes.
1367      *
1368      * @param connection the {@link HttpConnection connection} used to execute
1369      *        this HTTP method
1370      * @param name the method name generate a request for
1371      * @param requestPath the path string for the request
1372      * @param query the query string for the request
1373      * @param version the protocol version to use (e.g. HTTP/1.0)
1374      *
1375      * @return HTTP request line
1376      */
1377     protected static String generateRequestLine(HttpConnection connection,
1378         String name, String requestPath, String query, String version) {
1379         LOG.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
1380             + "String, String, String, String)");
1381 
1382         StringBuffer buf = new StringBuffer();
1383         // Append method name
1384         buf.append(name);
1385         buf.append(" ");
1386         // Absolute or relative URL?
1387         if (!connection.isTransparent()) {
1388             Protocol protocol = connection.getProtocol();
1389             buf.append(protocol.getScheme().toLowerCase());
1390             buf.append("://");
1391             buf.append(connection.getHost());
1392             if ((connection.getPort() != -1) 
1393                 && (connection.getPort() != protocol.getDefaultPort())
1394             ) {
1395                 buf.append(":");
1396                 buf.append(connection.getPort());
1397             }
1398         }
1399         // Append path, if any
1400         if (requestPath == null) {
1401             buf.append("/");
1402         } else {
1403             if (!connection.isTransparent() && !requestPath.startsWith("/")) {
1404                 buf.append("/");
1405             }
1406             buf.append(requestPath);
1407         }
1408         // Append query, if any
1409         if (query != null) {
1410             if (query.indexOf("?") != 0) {
1411                 buf.append("?");
1412             }
1413             buf.append(query);
1414         }
1415         // Append protocol
1416         buf.append(" ");
1417         buf.append(version);
1418         buf.append("\r\n");
1419         
1420         return buf.toString();
1421     }
1422     
1423     /***
1424      * This method is invoked immediately after 
1425      * {@link #readResponseBody(HttpState,HttpConnection)} and can be overridden by
1426      * sub-classes in order to provide custom body processing.
1427      *
1428      * <p>
1429      * This implementation does nothing.
1430      * </p>
1431      *
1432      * @param state the {@link HttpState state} information associated with this method
1433      * @param conn the {@link HttpConnection connection} used to execute
1434      *        this HTTP method
1435      *
1436      * @see #readResponse
1437      * @see #readResponseBody
1438      */
1439     protected void processResponseBody(HttpState state, HttpConnection conn) {
1440     }
1441 
1442     /***
1443      * This method is invoked immediately after 
1444      * {@link #readResponseHeaders(HttpState,HttpConnection)} and can be overridden by
1445      * sub-classes in order to provide custom response headers processing.
1446 
1447      * <p>
1448      * This implementation will handle the <tt>Set-Cookie</tt> and
1449      * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
1450      * the given {@link HttpState}.
1451      * </p>
1452      *
1453      * @param state the {@link HttpState state} information associated with this method
1454      * @param conn the {@link HttpConnection connection} used to execute
1455      *        this HTTP method
1456      *
1457      * @see #readResponse
1458      * @see #readResponseHeaders
1459      */
1460     protected void processResponseHeaders(HttpState state,
1461         HttpConnection conn) {
1462         LOG.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
1463             + "HttpConnection)");
1464 
1465         Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie2");
1466         //Only process old style set-cookie headers if new style headres
1467         //are not present
1468         if (headers.length == 0) { 
1469             headers = getResponseHeaderGroup().getHeaders("set-cookie");
1470         }
1471         
1472         CookieSpec parser = getCookieSpec(state);
1473         String host = this.params.getVirtualHost();
1474         if (host == null) {
1475             host = conn.getHost();
1476         }
1477         for (int i = 0; i < headers.length; i++) {
1478             Header header = headers[i];
1479             Cookie[] cookies = null;
1480             try {
1481                 cookies = parser.parse(
1482                   host,
1483                   conn.getPort(),
1484                   getPath(),
1485                   conn.isSecure(),
1486                   header);
1487             } catch (MalformedCookieException e) {
1488                 if (LOG.isWarnEnabled()) {
1489                     LOG.warn("Invalid cookie header: \"" 
1490                         + header.getValue() 
1491                         + "\". " + e.getMessage());
1492                 }
1493             }
1494             if (cookies != null) {
1495                 for (int j = 0; j < cookies.length; j++) {
1496                     Cookie cookie = cookies[j];
1497                     try {
1498                         parser.validate(
1499                           host,
1500                           conn.getPort(),
1501                           getPath(),
1502                           conn.isSecure(),
1503                           cookie);
1504                         state.addCookie(cookie);
1505                         if (LOG.isDebugEnabled()) {
1506                             LOG.debug("Cookie accepted: \"" 
1507                                 + parser.formatCookie(cookie) + "\"");
1508                         }
1509                     } catch (MalformedCookieException e) {
1510                         if (LOG.isWarnEnabled()) {
1511                             LOG.warn("Cookie rejected: \"" + parser.formatCookie(cookie) 
1512                                 + "\". " + e.getMessage());
1513                         }
1514                     }
1515                 }
1516             }
1517         }
1518     }
1519 
1520     /***
1521      * This method is invoked immediately after 
1522      * {@link #readStatusLine(HttpState,HttpConnection)} and can be overridden by
1523      * sub-classes in order to provide custom response status line processing.
1524      *
1525      * @param state the {@link HttpState state} information associated with this method
1526      * @param conn the {@link HttpConnection connection} used to execute
1527      *        this HTTP method
1528      *
1529      * @see #readResponse
1530      * @see #readStatusLine
1531      */
1532     protected void processStatusLine(HttpState state, HttpConnection conn) {
1533     }
1534 
1535     /***
1536      * Reads the response from the given {@link HttpConnection connection}.
1537      *
1538      * <p>
1539      * The response is processed as the following sequence of actions:
1540      *
1541      * <ol>
1542      * <li>
1543      * {@link #readStatusLine(HttpState,HttpConnection)} is
1544      * invoked to read the request line.
1545      * </li>
1546      * <li>
1547      * {@link #processStatusLine(HttpState,HttpConnection)}
1548      * is invoked, allowing the method to process the status line if
1549      * desired.
1550      * </li>
1551      * <li>
1552      * {@link #readResponseHeaders(HttpState,HttpConnection)} is invoked to read
1553      * the associated headers.
1554      * </li>
1555      * <li>
1556      * {@link #processResponseHeaders(HttpState,HttpConnection)} is invoked, allowing
1557      * the method to process the headers if desired.
1558      * </li>
1559      * <li>
1560      * {@link #readResponseBody(HttpState,HttpConnection)} is
1561      * invoked to read the associated body (if any).
1562      * </li>
1563      * <li>
1564      * {@link #processResponseBody(HttpState,HttpConnection)} is invoked, allowing the
1565      * method to process the response body if desired.
1566      * </li>
1567      * </ol>
1568      *
1569      * Subclasses may want to override one or more of the above methods to to
1570      * customize the processing. (Or they may choose to override this method
1571      * if dramatically different processing is required.)
1572      * </p>
1573      *
1574      * @param state the {@link HttpState state} information associated with this method
1575      * @param conn the {@link HttpConnection connection} used to execute
1576      *        this HTTP method
1577      *
1578      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1579      *                     can be recovered from.
1580      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1581      *                    cannot be recovered from.
1582      */
1583     protected void readResponse(HttpState state, HttpConnection conn)
1584     throws IOException, HttpException {
1585         LOG.trace(
1586         "enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
1587         // Status line & line may have already been received
1588         // if 'expect - continue' handshake has been used
1589         while (this.statusLine == null) {
1590             readStatusLine(state, conn);
1591             processStatusLine(state, conn);
1592             readResponseHeaders(state, conn);
1593             processResponseHeaders(state, conn);
1594             
1595             int status = this.statusLine.getStatusCode();
1596             if ((status >= 100) && (status < 200)) {
1597                 if (LOG.isInfoEnabled()) {
1598                     LOG.info("Discarding unexpected response: " + this.statusLine.toString()); 
1599                 }
1600                 this.statusLine = null;
1601             }
1602         }
1603         readResponseBody(state, conn);
1604         processResponseBody(state, conn);
1605     }
1606 
1607     /***
1608      * Read the response body from the given {@link HttpConnection}.
1609      *
1610      * <p>
1611      * The current implementation wraps the socket level stream with
1612      * an appropriate stream for the type of response (chunked, content-length,
1613      * or auto-close).  If there is no response body, the connection associated
1614      * with the request will be returned to the connection manager.
1615      * </p>
1616      *
1617      * <p>
1618      * Subclasses may want to override this method to to customize the
1619      * processing.
1620      * </p>
1621      *
1622      * @param state the {@link HttpState state} information associated with this method
1623      * @param conn the {@link HttpConnection connection} used to execute
1624      *        this HTTP method
1625      *
1626      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1627      *                     can be recovered from.
1628      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1629      *                    cannot be recovered from.
1630      *
1631      * @see #readResponse
1632      * @see #processResponseBody
1633      */
1634     protected void readResponseBody(HttpState state, HttpConnection conn)
1635     throws IOException, HttpException {
1636         LOG.trace(
1637             "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1638 
1639         // assume we are not done with the connection if we get a stream
1640         InputStream stream = readResponseBody(conn);
1641         if (stream == null) {
1642             // done using the connection!
1643             responseBodyConsumed();
1644         } else {
1645             conn.setLastResponseInputStream(stream);
1646             setResponseStream(stream);
1647         }
1648     }
1649 
1650     /***
1651      * Returns the response body as an {@link InputStream input stream}
1652      * corresponding to the values of the <tt>Content-Length</tt> and 
1653      * <tt>Transfer-Encoding</tt> headers. If no response body is available
1654      * returns <tt>null</tt>.
1655      * <p>
1656      *
1657      * @see #readResponse
1658      * @see #processResponseBody
1659      *
1660      * @param conn the {@link HttpConnection connection} used to execute
1661      *        this HTTP method
1662      *
1663      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1664      *                     can be recovered from.
1665      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1666      *                    cannot be recovered from.
1667      */
1668     private InputStream readResponseBody(HttpConnection conn)
1669         throws HttpException, IOException {
1670 
1671         LOG.trace("enter HttpMethodBase.readResponseBody(HttpConnection)");
1672 
1673         responseBody = null;
1674         InputStream is = conn.getResponseInputStream();
1675         if (Wire.CONTENT_WIRE.enabled()) {
1676             is = new WireLogInputStream(is, Wire.CONTENT_WIRE);
1677         }
1678         boolean canHaveBody = canResponseHaveBody(statusLine.getStatusCode());
1679         InputStream result = null;
1680         Header transferEncodingHeader = responseHeaders.getFirstHeader("Transfer-Encoding");
1681         // We use Transfer-Encoding if present and ignore Content-Length.
1682         // RFC2616, 4.4 item number 3
1683         if (transferEncodingHeader != null) {
1684 
1685             String transferEncoding = transferEncodingHeader.getValue();
1686             if (!"chunked".equalsIgnoreCase(transferEncoding) 
1687                 && !"identity".equalsIgnoreCase(transferEncoding)) {
1688                 if (LOG.isWarnEnabled()) {
1689                     LOG.warn("Unsupported transfer encoding: " + transferEncoding);
1690                 }
1691             }
1692             HeaderElement[] encodings = transferEncodingHeader.getElements();
1693             // The chunked encoding must be the last one applied
1694             // RFC2616, 14.41
1695             int len = encodings.length;            
1696             if ((len > 0) && ("chunked".equalsIgnoreCase(encodings[len - 1].getName()))) { 
1697                 // if response body is empty
1698                 if (conn.isResponseAvailable(conn.getParams().getSoTimeout())) {
1699                     result = new ChunkedInputStream(is, this);
1700                 } else {
1701                     if (getParams().isParameterTrue(HttpMethodParams.STRICT_TRANSFER_ENCODING)) {
1702                         throw new ProtocolException("Chunk-encoded body declared but not sent");
1703                     } else {
1704                         LOG.warn("Chunk-encoded body missing");
1705                     }
1706                 }
1707             } else {
1708                 LOG.info("Response content is not chunk-encoded");
1709                 // The connection must be terminated by closing 
1710                 // the socket as per RFC 2616, 3.6
1711                 setConnectionCloseForced(true);
1712                 result = is;  
1713             }
1714         } else {
1715             long expectedLength = getResponseContentLength();
1716             if (expectedLength == -1) {
1717                 if (canHaveBody && this.effectiveVersion.greaterEquals(HttpVersion.HTTP_1_1)) {
1718                     Header connectionHeader = responseHeaders.getFirstHeader("Connection");
1719                     String connectionDirective = null;
1720                     if (connectionHeader != null) {
1721                         connectionDirective = connectionHeader.getValue();
1722                     }
1723                     if (!"close".equalsIgnoreCase(connectionDirective)) {
1724                         LOG.info("Response content length is not known");
1725                         setConnectionCloseForced(true);
1726                     }
1727                 }
1728                 result = is;            
1729             } else {
1730                 result = new ContentLengthInputStream(is, expectedLength);
1731             }
1732         } 
1733 
1734         // See if the response is supposed to have a response body
1735         if (!canHaveBody) {
1736             result = null;
1737         }
1738         // if there is a result - ALWAYS wrap it in an observer which will
1739         // close the underlying stream as soon as it is consumed, and notify
1740         // the watcher that the stream has been consumed.
1741         if (result != null) {
1742 
1743             result = new AutoCloseInputStream(
1744                 result,
1745                 new ResponseConsumedWatcher() {
1746                     public void responseConsumed() {
1747                         responseBodyConsumed();
1748                     }
1749                 }
1750             );
1751         }
1752 
1753         return result;
1754     }
1755 
1756     /***
1757      * Reads the response headers from the given {@link HttpConnection connection}.
1758      *
1759      * <p>
1760      * Subclasses may want to override this method to to customize the
1761      * processing.
1762      * </p>
1763      *
1764      * <p>
1765      * "It must be possible to combine the multiple header fields into one
1766      * "field-name: field-value" pair, without changing the semantics of the
1767      * message, by appending each subsequent field-value to the first, each
1768      * separated by a comma." - HTTP/1.0 (4.3)
1769      * </p>
1770      *
1771      * @param state the {@link HttpState state} information associated with this method
1772      * @param conn the {@link HttpConnection connection} used to execute
1773      *        this HTTP method
1774      *
1775      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1776      *                     can be recovered from.
1777      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1778      *                    cannot be recovered from.
1779      *
1780      * @see #readResponse
1781      * @see #processResponseHeaders
1782      */
1783     protected void readResponseHeaders(HttpState state, HttpConnection conn)
1784     throws IOException, HttpException {
1785         LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
1786             + "HttpConnection)");
1787 
1788         getResponseHeaderGroup().clear();
1789         
1790         Header[] headers = HttpParser.parseHeaders(
1791             conn.getResponseInputStream(), getParams().getHttpElementCharset());
1792         if (Wire.HEADER_WIRE.enabled()) {
1793             for (int i = 0; i < headers.length; i++) {
1794                 Wire.HEADER_WIRE.input(headers[i].toExternalForm());
1795             }
1796         }
1797         getResponseHeaderGroup().setHeaders(headers);
1798     }
1799 
1800     /***
1801      * Read the status line from the given {@link HttpConnection}, setting my
1802      * {@link #getStatusCode status code} and {@link #getStatusText status
1803      * text}.
1804      *
1805      * <p>
1806      * Subclasses may want to override this method to to customize the
1807      * processing.
1808      * </p>
1809      *
1810      * @param state the {@link HttpState state} information associated with this method
1811      * @param conn the {@link HttpConnection connection} used to execute
1812      *        this HTTP method
1813      *
1814      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1815      *                     can be recovered from.
1816      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1817      *                    cannot be recovered from.
1818      *
1819      * @see StatusLine
1820      */
1821     protected void readStatusLine(HttpState state, HttpConnection conn)
1822     throws IOException, HttpException {
1823         LOG.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
1824 
1825         final int maxGarbageLines = getParams().
1826             getIntParameter(HttpMethodParams.STATUS_LINE_GARBAGE_LIMIT, Integer.MAX_VALUE);
1827 
1828         //read out the HTTP status string
1829         int count = 0;
1830         String s;
1831         do {
1832             s = conn.readLine(getParams().getHttpElementCharset());
1833             if (s == null && count == 0) {
1834                 // The server just dropped connection on us
1835                 throw new NoHttpResponseException("The server " + conn.getHost() + 
1836                     " failed to respond");
1837             }
1838             if (Wire.HEADER_WIRE.enabled()) {
1839                 Wire.HEADER_WIRE.input(s + "\r\n");
1840             }
1841             if (s != null && StatusLine.startsWithHTTP(s)) {
1842                 // Got one
1843                 break;
1844             } else if (s == null || count >= maxGarbageLines) {
1845                 // Giving up
1846                 throw new ProtocolException("The server " + conn.getHost() + 
1847                         " failed to respond with a valid HTTP response");
1848             }
1849             count++;
1850         } while(true);
1851 
1852         //create the status line from the status string
1853         statusLine = new StatusLine(s);
1854 
1855         //check for a valid HTTP-Version
1856         String versionStr = statusLine.getHttpVersion();
1857         if (getParams().isParameterFalse(HttpMethodParams.UNAMBIGUOUS_STATUS_LINE) 
1858            && versionStr.equals("HTTP")) {
1859             getParams().setVersion(HttpVersion.HTTP_1_0);
1860             if (LOG.isWarnEnabled()) {
1861                 LOG.warn("Ambiguous status line (HTTP protocol version missing):" +
1862                 statusLine.toString());
1863             }
1864         } else {
1865             this.effectiveVersion = HttpVersion.parse(versionStr);
1866         }
1867 
1868     }
1869 
1870     // ------------------------------------------------------ Protected Methods
1871 
1872     /***
1873      * <p>
1874      * Sends the request via the given {@link HttpConnection connection}.
1875      * </p>
1876      *
1877      * <p>
1878      * The request is written as the following sequence of actions:
1879      * </p>
1880      *
1881      * <ol>
1882      * <li>
1883      * {@link #writeRequestLine(HttpState, HttpConnection)} is invoked to 
1884      * write the request line.
1885      * </li>
1886      * <li>
1887      * {@link #writeRequestHeaders(HttpState, HttpConnection)} is invoked 
1888      * to write the associated headers.
1889      * </li>
1890      * <li>
1891      * <tt>\r\n</tt> is sent to close the head part of the request.
1892      * </li>
1893      * <li>
1894      * {@link #writeRequestBody(HttpState, HttpConnection)} is invoked to 
1895      * write the body part of the request.
1896      * </li>
1897      * </ol>
1898      *
1899      * <p>
1900      * Subclasses may want to override one or more of the above methods to to
1901      * customize the processing. (Or they may choose to override this method
1902      * if dramatically different processing is required.)
1903      * </p>
1904      *
1905      * @param state the {@link HttpState state} information associated with this method
1906      * @param conn the {@link HttpConnection connection} used to execute
1907      *        this HTTP method
1908      *
1909      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1910      *                     can be recovered from.
1911      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1912      *                    cannot be recovered from.
1913      */
1914     protected void writeRequest(HttpState state, HttpConnection conn)
1915     throws IOException, HttpException {
1916         LOG.trace(
1917             "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
1918         writeRequestLine(state, conn);
1919         writeRequestHeaders(state, conn);
1920         conn.writeLine(); // close head
1921         if (Wire.HEADER_WIRE.enabled()) {
1922             Wire.HEADER_WIRE.output("\r\n");
1923         }
1924 
1925         HttpVersion ver = getParams().getVersion();
1926         Header expectheader = getRequestHeader("Expect");
1927         String expectvalue = null;
1928         if (expectheader != null) {
1929             expectvalue = expectheader.getValue();
1930         }
1931         if ((expectvalue != null) 
1932          && (expectvalue.compareToIgnoreCase("100-continue") == 0)) {
1933             if (ver.greaterEquals(HttpVersion.HTTP_1_1)) {
1934 
1935                 // make sure the status line and headers have been sent
1936                 conn.flushRequestOutputStream();
1937                 
1938                 int readTimeout = conn.getParams().getSoTimeout();
1939                 try {
1940                     conn.setSocketTimeout(RESPONSE_WAIT_TIME_MS);
1941                     readStatusLine(state, conn);
1942                     processStatusLine(state, conn);
1943                     readResponseHeaders(state, conn);
1944                     processResponseHeaders(state, conn);
1945 
1946                     if (this.statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
1947                         // Discard status line
1948                         this.statusLine = null;
1949                         LOG.debug("OK to continue received");
1950                     } else {
1951                         return;
1952                     }
1953                 } catch (InterruptedIOException e) {
1954                     if (!ExceptionUtil.isSocketTimeoutException(e)) {
1955                         throw e;
1956                     }
1957                     // Most probably Expect header is not recongnized
1958                     // Remove the header to signal the method 
1959                     // that it's okay to go ahead with sending data
1960                     removeRequestHeader("Expect");
1961                     LOG.info("100 (continue) read timeout. Resume sending the request");
1962                 } finally {
1963                     conn.setSocketTimeout(readTimeout);
1964                 }
1965                 
1966             } else {
1967                 removeRequestHeader("Expect");
1968                 LOG.info("'Expect: 100-continue' handshake is only supported by "
1969                     + "HTTP/1.1 or higher");
1970             }
1971         }
1972 
1973         writeRequestBody(state, conn);
1974         // make sure the entire request body has been sent
1975         conn.flushRequestOutputStream();
1976     }
1977 
1978     /***
1979      * Writes the request body to the given {@link HttpConnection connection}.
1980      *
1981      * <p>
1982      * This method should return <tt>true</tt> if the request body was actually
1983      * sent (or is empty), or <tt>false</tt> if it could not be sent for some
1984      * reason.
1985      * </p>
1986      *
1987      * <p>
1988      * This implementation writes nothing and returns <tt>true</tt>.
1989      * </p>
1990      *
1991      * @param state the {@link HttpState state} information associated with this method
1992      * @param conn the {@link HttpConnection connection} used to execute
1993      *        this HTTP method
1994      *
1995      * @return <tt>true</tt>
1996      *
1997      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1998      *                     can be recovered from.
1999      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
2000      *                    cannot be recovered from.
2001      */
2002     protected boolean writeRequestBody(HttpState state, HttpConnection conn)
2003     throws IOException, HttpException {
2004         return true;
2005     }
2006 
2007     /***
2008      * Writes the request headers to the given {@link HttpConnection connection}.
2009      *
2010      * <p>
2011      * This implementation invokes {@link #addRequestHeaders(HttpState,HttpConnection)},
2012      * and then writes each header to the request stream.
2013      * </p>
2014      *
2015      * <p>
2016      * Subclasses may want to override this method to to customize the
2017      * processing.
2018      * </p>
2019      *
2020      * @param state the {@link HttpState state} information associated with this method
2021      * @param conn the {@link HttpConnection connection} used to execute
2022      *        this HTTP method
2023      *
2024      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2025      *                     can be recovered from.
2026      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
2027      *                    cannot be recovered from.
2028      *
2029      * @see #addRequestHeaders
2030      * @see #getRequestHeaders
2031      */
2032     protected void writeRequestHeaders(HttpState state, HttpConnection conn)
2033     throws IOException, HttpException {
2034         LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
2035             + "HttpConnection)");
2036         addRequestHeaders(state, conn);
2037 
2038         String charset = getParams().getHttpElementCharset();
2039         
2040         Header[] headers = getRequestHeaders();
2041         for (int i = 0; i < headers.length; i++) {
2042             String s = headers[i].toExternalForm();
2043             if (Wire.HEADER_WIRE.enabled()) {
2044                 Wire.HEADER_WIRE.output(s);
2045             }
2046             conn.print(s, charset);
2047         }
2048     }
2049 
2050     /***
2051      * Writes the request line to the given {@link HttpConnection connection}.
2052      *
2053      * <p>
2054      * Subclasses may want to override this method to to customize the
2055      * processing.
2056      * </p>
2057      *
2058      * @param state the {@link HttpState state} information associated with this method
2059      * @param conn the {@link HttpConnection connection} used to execute
2060      *        this HTTP method
2061      *
2062      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2063      *                     can be recovered from.
2064      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
2065      *                    cannot be recovered from.
2066      *
2067      * @see #generateRequestLine
2068      */
2069     protected void writeRequestLine(HttpState state, HttpConnection conn)
2070     throws IOException, HttpException {
2071         LOG.trace(
2072             "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
2073         String requestLine = getRequestLine(conn);
2074         if (Wire.HEADER_WIRE.enabled()) {
2075             Wire.HEADER_WIRE.output(requestLine);
2076         }
2077         conn.print(requestLine, getParams().getHttpElementCharset());
2078     }
2079 
2080     /***
2081      * Returns the request line.
2082      * 
2083      * @param conn the {@link HttpConnection connection} used to execute
2084      *        this HTTP method
2085      * 
2086      * @return The request line.
2087      */
2088     private String getRequestLine(HttpConnection conn) {
2089         return  HttpMethodBase.generateRequestLine(conn, getName(),
2090                 getPath(), getQueryString(), this.effectiveVersion.toString());
2091     }
2092 
2093     /***
2094      * Returns {@link HttpMethodParams HTTP protocol parameters} associated with this method.
2095      *
2096      * @return HTTP parameters.
2097      *
2098      * @since 3.0
2099      */
2100     public HttpMethodParams getParams() {
2101         return this.params;
2102     }
2103 
2104     /***
2105      * Assigns {@link HttpMethodParams HTTP protocol parameters} for this method.
2106      * 
2107      * @since 3.0
2108      * 
2109      * @see HttpMethodParams
2110      */
2111     public void setParams(final HttpMethodParams params) {
2112         if (params == null) {
2113             throw new IllegalArgumentException("Parameters may not be null");
2114         }
2115         this.params = params;
2116     }
2117 
2118     /***
2119      * Returns the HTTP version used with this method (may be <tt>null</tt>
2120      * if undefined, that is, the method has not been executed)
2121      *
2122      * @return HTTP version.
2123      *
2124      * @since 3.0
2125      */
2126     public HttpVersion getEffectiveVersion() {
2127         return this.effectiveVersion;
2128     }
2129 
2130     /***
2131      * Per RFC 2616 section 4.3, some response can never contain a message
2132      * body.
2133      *
2134      * @param status - the HTTP status code
2135      *
2136      * @return <tt>true</tt> if the message may contain a body, <tt>false</tt> if it can not
2137      *         contain a message body
2138      */
2139     private static boolean canResponseHaveBody(int status) {
2140         LOG.trace("enter HttpMethodBase.canResponseHaveBody(int)");
2141 
2142         boolean result = true;
2143 
2144         if ((status >= 100 && status <= 199) || (status == 204)
2145             || (status == 304)) { // NOT MODIFIED
2146             result = false;
2147         }
2148 
2149         return result;
2150     }
2151 
2152     /***
2153      * Returns proxy authentication realm, if it has been used during authentication process. 
2154      * Otherwise returns <tt>null</tt>.
2155      * 
2156      * @return proxy authentication realm
2157      * 
2158      * @deprecated use #getProxyAuthState()
2159      */
2160     public String getProxyAuthenticationRealm() {
2161         return this.proxyAuthState.getRealm();
2162     }
2163 
2164     /***
2165      * Returns authentication realm, if it has been used during authentication process. 
2166      * Otherwise returns <tt>null</tt>.
2167      * 
2168      * @return authentication realm
2169      * 
2170      * @deprecated use #getHostAuthState()
2171      */
2172     public String getAuthenticationRealm() {
2173         return this.hostAuthState.getRealm();
2174     }
2175 
2176     /***
2177      * Returns the character set from the <tt>Content-Type</tt> header.
2178      * 
2179      * @param contentheader The content header.
2180      * @return String The character set.
2181      */
2182     protected String getContentCharSet(Header contentheader) {
2183         LOG.trace("enter getContentCharSet( Header contentheader )");
2184         String charset = null;
2185         if (contentheader != null) {
2186             HeaderElement values[] = contentheader.getElements();
2187             // I expect only one header element to be there
2188             // No more. no less
2189             if (values.length == 1) {
2190                 NameValuePair param = values[0].getParameterByName("charset");
2191                 if (param != null) {
2192                     // If I get anything "funny" 
2193                     // UnsupportedEncondingException will result
2194                     charset = param.getValue();
2195                 }
2196             }
2197         }
2198         if (charset == null) {
2199             charset = getParams().getContentCharset();
2200             if (LOG.isDebugEnabled()) {
2201                 LOG.debug("Default charset used: " + charset);
2202             }
2203         }
2204         return charset;
2205     }
2206 
2207 
2208     /***
2209      * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
2210      * 
2211      * @return String The character set.
2212      */
2213     public String getRequestCharSet() {
2214         return getContentCharSet(getRequestHeader("Content-Type"));
2215     }
2216 
2217 
2218     /***  
2219      * Returns the character encoding of the response from the <tt>Content-Type</tt> header.
2220      * 
2221      * @return String The character set.
2222      */
2223     public String getResponseCharSet() {
2224         return getContentCharSet(getResponseHeader("Content-Type"));
2225     }
2226 
2227     /***
2228      * @deprecated no longer used
2229      * 
2230      * Returns the number of "recoverable" exceptions thrown and handled, to
2231      * allow for monitoring the quality of the connection.
2232      *
2233      * @return The number of recoverable exceptions handled by the method.
2234      */
2235     public int getRecoverableExceptionCount() {
2236         return recoverableExceptionCount;
2237     }
2238 
2239     /***
2240      * A response has been consumed.
2241      *
2242      * <p>The default behavior for this class is to check to see if the connection
2243      * should be closed, and close if need be, and to ensure that the connection
2244      * is returned to the connection manager - if and only if we are not still
2245      * inside the execute call.</p>
2246      *
2247      */
2248     protected void responseBodyConsumed() {
2249 
2250         // make sure this is the initial invocation of the notification,
2251         // ignore subsequent ones.
2252         responseStream = null;
2253         if (responseConnection != null) {
2254             responseConnection.setLastResponseInputStream(null);
2255 
2256             // At this point, no response data should be available.
2257             // If there is data available, regard the connection as being
2258             // unreliable and close it.
2259             
2260             if (shouldCloseConnection(responseConnection)) {
2261                 responseConnection.close();
2262             } else {
2263                 try {
2264                     if(responseConnection.isResponseAvailable()) {
2265                         boolean logExtraInput =
2266                             getParams().isParameterTrue(HttpMethodParams.WARN_EXTRA_INPUT);
2267 
2268                         if(logExtraInput) {
2269                             LOG.warn("Extra response data detected - closing connection");
2270                         } 
2271                         responseConnection.close();
2272                     }
2273                 }
2274                 catch (IOException e) {
2275                     LOG.warn(e.getMessage());
2276                     responseConnection.close();
2277                 }
2278             }
2279         }
2280         this.connectionCloseForced = false;
2281         ensureConnectionRelease();
2282     }
2283 
2284     /***
2285      * Insure that the connection is released back to the pool.
2286      */
2287     private void ensureConnectionRelease() {
2288         if (responseConnection != null) {
2289             responseConnection.releaseConnection();
2290             responseConnection = null;
2291         }
2292     }
2293 
2294     /***
2295      * Returns the {@link HostConfiguration host configuration}.
2296      * 
2297      * @return the host configuration
2298      * 
2299      * @deprecated no longer applicable
2300      */
2301     public HostConfiguration getHostConfiguration() {
2302         HostConfiguration hostconfig = new HostConfiguration();
2303         hostconfig.setHost(this.httphost);
2304         return hostconfig;
2305     }
2306     /***
2307      * Sets the {@link HostConfiguration host configuration}.
2308      * 
2309      * @param hostconfig The hostConfiguration to set
2310      * 
2311      * @deprecated no longer applicable
2312      */
2313     public void setHostConfiguration(final HostConfiguration hostconfig) {
2314         if (hostconfig != null) {
2315             this.httphost = new HttpHost(
2316                     hostconfig.getHost(),
2317                     hostconfig.getPort(),
2318                     hostconfig.getProtocol());
2319         } else {
2320             this.httphost = null;
2321         }
2322     }
2323 
2324     /***
2325      * Returns the {@link MethodRetryHandler retry handler} for this HTTP method
2326      * 
2327      * @return the methodRetryHandler
2328      * 
2329      * @deprecated use {@link HttpMethodParams}
2330      */
2331     public MethodRetryHandler getMethodRetryHandler() {
2332         return methodRetryHandler;
2333     }
2334 
2335     /***
2336      * Sets the {@link MethodRetryHandler retry handler} for this HTTP method
2337      * 
2338      * @param handler the methodRetryHandler to use when this method executed
2339      * 
2340      * @deprecated use {@link HttpMethodParams}
2341      */
2342     public void setMethodRetryHandler(MethodRetryHandler handler) {
2343         methodRetryHandler = handler;
2344     }
2345 
2346     /***
2347      * This method is a dirty hack intended to work around 
2348      * current (2.0) design flaw that prevents the user from
2349      * obtaining correct status code, headers and response body from the 
2350      * preceding HTTP CONNECT method.
2351      * 
2352      * TODO: Remove this crap as soon as possible
2353      */
2354     void fakeResponse(
2355         StatusLine statusline, 
2356         HeaderGroup responseheaders,
2357         InputStream responseStream
2358     ) {
2359         // set used so that the response can be read
2360         this.used = true;
2361         this.statusLine = statusline;
2362         this.responseHeaders = responseheaders;
2363         this.responseBody = null;
2364         this.responseStream = responseStream;
2365     }
2366     
2367     /***
2368      * Returns the target host {@link AuthState authentication state}
2369      * 
2370      * @return host authentication state
2371      * 
2372      * @since 3.0
2373      */
2374     public AuthState getHostAuthState() {
2375         return this.hostAuthState;
2376     }
2377 
2378     /***
2379      * Returns the proxy {@link AuthState authentication state}
2380      * 
2381      * @return host authentication state
2382      * 
2383      * @since 3.0
2384      */
2385     public AuthState getProxyAuthState() {
2386         return this.proxyAuthState;
2387     }
2388     
2389     /***
2390      * Tests whether the execution of this method has been aborted
2391      * 
2392      * @return <tt>true</tt> if the execution of this method has been aborted,
2393      *  <tt>false</tt> otherwise
2394      * 
2395      * @since 3.0
2396      */
2397     public boolean isAborted() {
2398         return this.aborted;
2399     }
2400     
2401     /***
2402      * Returns <tt>true</tt> if the HTTP has been transmitted to the target
2403      * server in its entirety, <tt>false</tt> otherwise. This flag can be useful 
2404      * for recovery logic. If the request has not been transmitted in its entirety,
2405      * it is safe to retry the failed method.
2406      * 
2407      * @return <tt>true</tt> if the request has been sent, <tt>false</tt> otherwise
2408      */
2409     public boolean isRequestSent() {
2410         return this.requestSent;
2411     }
2412     
2413 }