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