From 8e92600ddd6ed324fc1f8410e859cca3182018f7 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 30 Mar 2004 06:40:01 +0000
Subject: [PATCH] David Byron made CURLOPT_FAILONERROR work with
 authentications such as NTLM or Digest.

---
 lib/http.c     | 129 ++++++++++++++++++++++++++++++++++++++-----------
 lib/http.h     |   4 ++
 lib/transfer.c |  26 +++++++++-
 3 files changed, 129 insertions(+), 30 deletions(-)

diff --git a/lib/http.c b/lib/http.c
index ccbbf49622..7358f7aebe 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -188,23 +188,23 @@ void Curl_http_auth_act(struct connectdata *conn)
   }
 }
 
-/*
+/**
  * Setup the authentication headers for the host/proxy and the correct
- * authentication method.
+ * authentication method.  @p conn->data->state.authdone set to TRUE
+ * when authentication is done.
+ *
+ * @param conn all information about the current connection
  */
-
 static CURLcode http_auth_headers(struct connectdata *conn,
                                   char *request,
-                                  char *path,
-                                  bool *ready) /* set TRUE when the auth phase
-                                           is done and ready to do the *actual*
-                                           request */
+                                  char *path)
 {
   CURLcode result = CURLE_OK;
   struct SessionHandle *data = conn->data;
   char *auth=NULL;
 
-  *ready = FALSE; /* default is no */
+  curlassert(data);
+  data->state.authdone = FALSE; /* default is no */
 
   if(!data->state.authstage) {
     if(conn->bits.httpproxy && conn->bits.proxy_user_passwd)
@@ -212,7 +212,7 @@ static CURLcode http_auth_headers(struct connectdata *conn,
     else if(conn->bits.user_passwd)
       Curl_http_auth_stage(data, 401);
     else {
-      *ready = TRUE;
+      data->state.authdone = TRUE;
       return CURLE_OK; /* no authentication with no user or password */
     }
   }
@@ -229,7 +229,7 @@ static CURLcode http_auth_headers(struct connectdata *conn,
 #ifdef USE_SSLEAY
       if(data->state.authwant == CURLAUTH_NTLM) {
         auth=(char *)"NTLM";
-        result = Curl_output_ntlm(conn, TRUE, ready);
+        result = Curl_output_ntlm(conn, TRUE);
         if(result)
           return result;
       }
@@ -244,7 +244,7 @@ static CURLcode http_auth_headers(struct connectdata *conn,
           if(result)
             return result;
         }
-        *ready = TRUE;
+        data->state.authdone = TRUE;
         /* Switch to web authentication after proxy authentication is done */
         Curl_http_auth_stage(data, 401);
       }
@@ -262,14 +262,14 @@ static CURLcode http_auth_headers(struct connectdata *conn,
         result = Curl_output_negotiate(conn);
         if (result)
           return result;
-        *ready = TRUE;
+        data->state.authdone = TRUE;
       }
       else
 #endif
 #ifdef USE_SSLEAY
       if(data->state.authwant == CURLAUTH_NTLM) {
         auth=(char *)"NTLM";
-        result = Curl_output_ntlm(conn, FALSE, ready);
+        result = Curl_output_ntlm(conn, FALSE);
         if(result)
           return result;
       }
@@ -284,7 +284,7 @@ static CURLcode http_auth_headers(struct connectdata *conn,
                                       (unsigned char *)path);
           if(result)
             return result;
-          *ready = TRUE;
+          data->state.authdone = TRUE;
         }
         else if(data->state.authwant == CURLAUTH_BASIC) {/* Basic */
           if(conn->bits.user_passwd &&
@@ -295,7 +295,7 @@ static CURLcode http_auth_headers(struct connectdata *conn,
               return result;
           }
           /* basic is always ready */
-          *ready = TRUE;
+          data->state.authdone = TRUE;
         }
       }
       if(auth)
@@ -304,7 +304,7 @@ static CURLcode http_auth_headers(struct connectdata *conn,
     }
   }
   else
-    *ready = TRUE;
+    data->state.authdone = TRUE;
 
   return result;
 }
@@ -438,6 +438,83 @@ CURLcode Curl_http_auth(struct connectdata *conn,
   return CURLE_OK;
 }
 
+/**
+ * determine whether an http response has gotten us into an
+ * error state or not.
+ *
+ * @param conn all information about the current connection
+ *
+ * @retval 0 communications should continue
+ *
+ * @retval 1 communications should not continue
+ */
+int Curl_http_should_fail(struct connectdata *conn)
+{
+  struct SessionHandle *data;
+  struct Curl_transfer_keeper *k;
+
+  curlassert(conn);
+  data = conn->data;
+  curlassert(data);
+
+  /*
+  ** For readability
+  */
+  k = &conn->keep;
+
+  /*
+  ** If we haven't been asked to fail on error,
+  ** don't fail.
+  */
+  if (!data->set.http_fail_on_error)
+    return 0;
+
+  /*
+  ** Any code < 400 is never terminal.
+  */
+  if (k->httpcode < 400)
+    return 0;
+
+  /*
+  ** Any code >= 400 that's not 401 or 407 is always
+  ** a terminal error
+  */
+  if ((k->httpcode != 401) &&
+      (k->httpcode != 407))
+    return 1;
+
+  /*
+  ** All we have left to deal with is 401 and 407
+  */
+  curlassert((k->httpcode == 401) || (k->httpcode == 407));
+
+  /*
+  ** Examine the current authentication state to see if this
+  ** is an error.  The idea is for this function to get
+  ** called after processing all the headers in a response
+  ** message.  So, if we've been to asked to authenticate a
+  ** particular stage, and we've done it, we're OK.  But, if
+  ** we're already completely authenticated, it's not OK to
+  ** get another 401 or 407.
+  **
+  ** It is possible for authentication to go stale such that
+  ** the client needs to reauthenticate.  Once that info is
+  ** available, use it here.
+  */
+  infof(data,"%s: authstage = %d\n",__FUNCTION__,data->state.authstage);
+  infof(data,"%s: httpcode = %d\n",__FUNCTION__,k->httpcode);
+  infof(data,"%s: authdone = %d\n",__FUNCTION__,data->state.authdone);
+
+  if (data->state.authstage &&
+      (data->state.authstage == k->httpcode))
+    return data->state.authdone;
+
+  /*
+  ** Either we're not authenticating, or we're supposed to
+  ** be authenticating something else.  This is an error.
+  */
+  return 1;
+}
 
 /* fread() emulation to provide POST and/or request data */
 static size_t readmoredata(char *buffer,
@@ -760,9 +837,6 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn,
   infof(data, "Establish HTTP proxy tunnel to %s:%d\n", hostname, remote_port);
 
   do {
-    bool auth; /* we don't really have to know when the auth phase is done,
-                  but this variable will be set to true then */
-
     if(conn->newurl) {
       /* This only happens if we've looped here due to authentication reasons,
          and we don't really use the newly cloned URL here then. Just free()
@@ -776,7 +850,7 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn,
       return CURLE_OUT_OF_MEMORY;
 
     /* Setup the proxy-authorization header, if any */
-    result = http_auth_headers(conn, (char *)"CONNECT", host_port, &auth);
+    result = http_auth_headers(conn, (char *)"CONNECT", host_port);
     if(CURLE_OK == result) {
 
       /* OK, now send the connect request to the proxy */
@@ -1066,7 +1140,6 @@ CURLcode Curl_http(struct connectdata *conn)
   const char *te = ""; /* tranfer-encoding */
   char *ptr;
   char *request;
-  bool authdone=TRUE; /* if the authentication phase is done */
 
   if(!conn->proto.http) {
     /* Only allocate this struct if we don't already have it! */
@@ -1105,7 +1178,7 @@ CURLcode Curl_http(struct connectdata *conn)
   }
 
   /* setup the authentication headers */
-  result = http_auth_headers(conn, request, ppath, &authdone);
+  result = http_auth_headers(conn, request, ppath);
   if(result)
     return result;
 
@@ -1535,8 +1608,8 @@ CURLcode Curl_http(struct connectdata *conn)
         /* setup variables for the upcoming transfer */
         result = Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE,
                                &http->readbytecount,
-                               authdone?FIRSTSOCKET:-1,
-                               authdone?&http->writebytecount:NULL);
+                               data->state.authdone?FIRSTSOCKET:-1,
+                               data->state.authdone?&http->writebytecount:NULL);
       if(result) {
         Curl_formclean(http->sendit); /* free that whole lot */
         return result;
@@ -1574,8 +1647,8 @@ CURLcode Curl_http(struct connectdata *conn)
         /* prepare for transfer */
         result = Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE,
                                &http->readbytecount,
-                               authdone?FIRSTSOCKET:-1,
-                               authdone?&http->writebytecount:NULL);
+                               data->state.authdone?FIRSTSOCKET:-1,
+                               data->state.authdone?&http->writebytecount:NULL);
       if(result)
         return result;
       break;
@@ -1606,7 +1679,7 @@ CURLcode Curl_http(struct connectdata *conn)
 
       if(data->set.postfields) {
 
-        if(authdone && (postsize < (100*1024))) {
+        if(data->state.authdone && (postsize < (100*1024))) {
           /* If we're not done with the authentication phase, we don't expect
              to actually send off any data yet. Hence, we delay the sending of
              the body until we receive that friendly 100-continue response */
@@ -1642,7 +1715,7 @@ CURLcode Curl_http(struct connectdata *conn)
           /* set the upload size to the progress meter */
           Curl_pgrsSetUploadSize(data, http->postsize);
 
-          if(!authdone && !checkheaders(data, "Expect:")) {
+          if(!data->state.authdone && !checkheaders(data, "Expect:")) {
             /* if not disabled explicitly we add a Expect: 100-continue to the
                headers which actually speeds up post operations (as there is
                one packet coming back from the web server) */
diff --git a/lib/http.h b/lib/http.h
index ad7179ff98..5dff8cb715 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -42,9 +42,13 @@ CURLcode Curl_http_connect(struct connectdata *conn);
 void Curl_httpchunk_init(struct connectdata *conn);
 CHUNKcode Curl_httpchunk_read(struct connectdata *conn, char *datap,
                               ssize_t length, ssize_t *wrote);
+
+/* These functions are in http.c */
 void Curl_http_auth_stage(struct SessionHandle *data, int stage);
 CURLcode Curl_http_auth(struct connectdata *conn,
                         int httpcode, char *header);
 void Curl_http_auth_act(struct connectdata *conn);
+
+int Curl_http_should_fail(struct connectdata *conn);
 #endif
 #endif
diff --git a/lib/transfer.c b/lib/transfer.c
index 2a0df0f64c..4e5d711376 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -441,6 +441,16 @@ CURLcode Curl_readwrite(struct connectdata *conn,
                 FD_ZERO(&k->wkeepfd);
               }
 
+              /*
+              ** Now that all of the headers have been parsed, see
+              ** if we should give up and return an error.
+              */
+              if (Curl_http_should_fail(conn)) {
+                failf (data, "The requested URL returned error: %d",
+                       k->httpcode);
+                return CURLE_HTTP_RETURNED_ERROR;
+              }
+
               /* now, only output this if the header AND body are requested:
                */
               writetype = CLIENTWRITE_HEADER;
@@ -576,9 +586,21 @@ CURLcode Curl_readwrite(struct connectdata *conn,
                 data->info.httpcode = k->httpcode;
                 data->info.httpversion = k->httpversion;
 
-                /* 404 -> URL not found! */
+                /*
+                ** This code executes as part of processing
+                ** the header.  As a result, it's not
+                ** totally clear how to interpret the
+                ** response code yet as that depends on what
+                ** other headers may be present.  401 and
+                ** 407 may be errors, but may be OK
+                ** depending on how authentication is
+                ** working.  Other codes are definitely
+                ** errors, so give up here.
+                */
                 if (data->set.http_fail_on_error &&
-                    (k->httpcode >= 400)) {
+                    (k->httpcode >= 400) &&
+                    (k->httpcode != 401) &&
+                    (k->httpcode != 407)) {
                   /* If we have been told to fail hard on HTTP-errors,
                      here is the check for that: */
                   /* serious error, go home! */
-- 
GitLab