Index: HttpMethodBase.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
retrieving revision 1.24
diff -u -r1.24 HttpMethodBase.java
--- HttpMethodBase.java	15 Jan 2002 20:53:47 -0000	1.24
+++ HttpMethodBase.java	19 Feb 2002 15:48:52 -0000
@@ -156,6 +156,28 @@
     }
 
     /**
+     * Turns strict mode on or off.  In strict mode (the default)
+     * we following the letter of RFC 2616, the Http 1.1 specification.
+     * If strict mode is turned off we attempt to violate the specification
+     * in the same way that most Http user agent's do (and many HTTP servers
+     * expect.
+     */
+    public void setStrictMode(boolean strictMode)
+    {
+        this.strictMode = strictMode;
+    }
+
+    /**
+     * Returns the value of strictMode.
+     *
+     * @return true if strict mode is enabled.
+     */
+    public boolean isStrictMode()
+    {
+        return strictMode;
+    }
+
+    /**
      * Set the specified request header, overwriting any
      * previous value.
      * Note that header-name matching is case-insensitive.
@@ -411,32 +433,41 @@
 
         Set visited = new HashSet();
         Set realms = new HashSet();
-
+        int retryCount = 0;
         for(;;) {
             visited.add(connection.getHost() + ":" + connection.getPort() + "|" + HttpMethodBase.generateRequestLine(connection, getName(),getPath(),getQueryString(),(http11 ? "HTTP/1.1" : "HTTP/1.0")));
 
             log.debug("HttpMethodBase.execute(): looping.");
 
-            if(!connection.isOpen()) {
-                log.debug("HttpMethodBase.execute(): opening connection.");
-                connection.open();
-            }
-
-            writeRequest(state,connection);
-            used = true;
+            try{
+                if(!connection.isOpen()) {
+                    log.debug("HttpMethodBase.execute(): opening connection.");
+                    connection.open();
+                }
 
-            // need to close output?, but when?
+                writeRequest(state,connection);
+                used = true;
 
-            readResponse(state,connection);
+                // need to close output?, but when?
 
+                readResponse(state,connection);
+            }catch(HttpRecoverableException e){
+                if(retryCount >= maxRetries){
+                    throw new HttpException(e.toString());
+                }
+                retryCount++;
+                connection.close();
+                log.debug("HttpMethodBase.execute():  Caught recoverable exception, retrying...");
+                continue;
+            }
             if(HttpStatus.SC_CONTINUE == statusCode) {
                 if(!bodySent) {
                     bodySent = writeRequestBody(state,connection);
-                    readResponse(state,connection);
                 } else {
                     log.warn("HttpMethodBase.execute(): received 100 response, but I've already sent the response.");
-                    break;
+                    // According to RFC 2616 this respose should be ignored
                 }
+                readResponse(state,connection);
             }
 
             if(!http11) {
@@ -513,6 +544,19 @@
                                     port = connection.isSecure() ? 443 : 80;
                                 }
                                 url = new URL(protocol,connection.getHost(),port,location.getValue());                                
+                            } else if(!isStrictMode() && location.getValue().indexOf("://") < 0) {
+                                /*
+                                 * Location doesn't start with / but it doesn't contain a protocol.
+                                 * Per RFC 2616, that's an error.  In non-strict mode we'll try
+                                 * to build a URL relative to the current path.
+                                 */
+                                String protocol = connection.isSecure() ? "https" : "http";
+                                int port = connection.getPort();
+                                if(-1 == port) {
+                                    port = connection.isSecure() ? 443 : 80;
+                                }
+                                URL currentUrl = new URL(protocol,connection.getHost(),port,getPath());
+                                url = new URL(currentUrl, location.getValue());
                             } else {
                                 url = new URL(location.getValue());
                             }                            
@@ -723,16 +767,12 @@
     }
 
     /**
-     * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s,
-     * if any, as long as no <tt>Cookie</tt> request header
-     * already exists.
+     * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s.
      */
     protected void addCookieRequestHeader(HttpState state, HttpConnection conn) throws IOException, HttpException {
-        if (!requestHeaders.containsKey("cookie")) {
-            Header cookieHeader = Cookie.createCookieHeader(conn.getHost(), conn.getPort(), getPath(), conn.isSecure(), new Date(), state.getCookies());
-            if(null != cookieHeader) {
-                setRequestHeader(cookieHeader);
-            }
+        Header cookieHeader = Cookie.createCookieHeader(conn.getHost(), conn.getPort(), getPath(), conn.isSecure(), new Date(), state.getCookies());
+        if(null != cookieHeader) {
+            setRequestHeader(cookieHeader);
         }
     }
 
@@ -882,7 +922,8 @@
             statusLine = conn.readLine();
         }
         if(statusLine == null) {
-            throw new HttpException("Error in parsing the status line from the response: unable to find line starting with \"HTTP/\"");
+            // A null statusLine means the connection was lost before we got a response.  Try again.
+            throw new HttpRecoverableException("Error in parsing the status line from the response: unable to find line starting with \"HTTP/\"");
         }
 
         if((!statusLine.startsWith("HTTP/1.1") &&
@@ -1046,9 +1087,34 @@
      * @param conn the {@link HttpConnection} to read the response from
      */
     protected void readResponseBody(HttpState state, HttpConnection conn) throws IOException, HttpException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        readResponseBody(state, conn, out);
+
+        out.close();
+        responseBody = out.toByteArray();
+    }
+
+    /**
+     * Read the response body from the given {@link HttpConnection}.
+     * <p>
+     * The current implementation simply consumes the expected
+     * response body (according to the values of the
+     * <tt>Content-Length</tt> and <tt>Transfer-Encoding</tt>
+     * headers, if any).
+     * <p>
+     * Subclasses may want to override this method to
+     * to customize the processing.
+     *
+     * @see #readResponse
+     * @see #processResponseBody
+     *
+     * @param state the client state
+     * @param conn the {@link HttpConnection} to read the response from
+     * @param out OutputStream to write the response body to
+     */
+    protected void readResponseBody(HttpState state, HttpConnection conn, OutputStream out) throws IOException {
         log.debug("HttpMethodBase.readResponseBody(HttpState,HttpConnection)");
         responseBody = null;
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
         int expectedLength = 0;
         int foundLength = 0;
         {
@@ -1064,6 +1130,14 @@
                 if("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
                     expectedLength = -1;
                 }
+            } else if(canResponseHaveBody(statusCode)){
+                /*
+                 * According to the specification, a response with neither Content-Length
+                 * nor Transfer-Encoding indicates that the response has no body.  In
+                 * the real world, this usually means that the server just didn't know
+                 * the content-length when it sent the response.
+                 */
+                expectedLength = -1;
             }
         }
         InputStream is = conn.getResponseInputStream(this);
@@ -1091,8 +1165,6 @@
                 }
             }
         }
-        out.close();
-        responseBody = out.toByteArray();
     }
 
     /**
@@ -1211,6 +1283,26 @@
         }
     }
 
+    /**
+     * Per RFC 2616 section 4.3, some response can never contain
+     * a message body.
+     *
+     * @param status - the HTTP status code
+     * @return true if the message may contain a body, false if it can not contain a message body
+     */
+    private static boolean canResponseHaveBody(int status)
+    {
+        boolean result = true;
+
+        if((status >= 100 && status <= 199) ||    // 1XX
+           status == 204 ||                       // NO CONTENT
+           status == 304){                        // NOT MODIFIED
+            result = false;
+        }
+
+        return result;
+    }
+
     // ----------------------------------------------------- Instance Variables
     /** My request path. */
     private String path = null;
@@ -1234,6 +1326,10 @@
     private boolean bodySent = false;
     /** The response body, assuming it has not be intercepted by a sub-class. */
     private byte[] responseBody = null;
+    /** The maximum number of attempts to attempt recovery from an HttpRecoverableException. */
+    private int maxRetries = 3;
+    /** True if we're in strict mode. */
+    private boolean strictMode = true;
 
     // -------------------------------------------------------------- Constants
 
