]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: refactor http_on_response into sub functions
authorDaniel Stenberg <daniel@haxx.se>
Tue, 31 Mar 2026 05:56:45 +0000 (07:56 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 31 Mar 2026 21:53:45 +0000 (23:53 +0200)
Reduces complexity.

Closes #21172

lib/http.c
lib/http2.h

index 96e7b0de0c3ec21d5c7cd057de2e8b9655225ffd..0f53eaa9a0ba316726891f8c7f3326607ed12c1b 100644 (file)
@@ -3889,6 +3889,226 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data,
   return CURLE_OK;
 }
 
+/*
+ * Handle a 101 Switching Protocols response. Performs the actual protocol
+ * upgrade to HTTP/2 or WebSocket based on what was requested.
+ */
+static CURLcode http_on_101_upgrade(struct Curl_easy *data,
+                                    const char *buf, size_t blen,
+                                    size_t *pconsumed,
+                                    bool *conn_changed)
+{
+  struct connectdata *conn = data->conn;
+  struct SingleRequest *k = &data->req;
+
+#if !defined(USE_NGHTTP2) && defined(CURL_DISABLE_WEBSOCKETS)
+  (void)buf;
+  (void)blen;
+  (void)pconsumed;
+#else
+  CURLcode result;
+  int upgr101_requested = k->upgr101;
+#endif
+
+  if(k->httpversion_sent != 11) {
+    /* invalid for other HTTP versions */
+    failf(data, "server sent 101 response while not talking HTTP/1.1");
+    return CURLE_WEIRD_SERVER_REPLY;
+  }
+
+  /* Whatever the success, upgrade was selected. */
+  k->upgr101 = UPGR101_RECEIVED;
+  conn->bits.upgrade_in_progress = FALSE;
+  *conn_changed = TRUE;
+
+  /* To be fully compliant, we would check the "Upgrade:" response header to
+   * mention the protocol we requested. */
+#ifdef USE_NGHTTP2
+  if(upgr101_requested == UPGR101_H2) {
+    /* Switch to HTTP/2, where we will get more responses. blen bytes in buf
+     * are already h2 protocol bytes */
+    infof(data, "Received 101, Switching to HTTP/2");
+    result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
+    if(!result)
+      *pconsumed += blen;
+    return result;
+  }
+#endif
+#ifndef CURL_DISABLE_WEBSOCKETS
+  if(upgr101_requested == UPGR101_WS) {
+    /* Switch to WebSocket, where we now stream ws frames. blen bytes in buf
+     * are already ws protocol bytes */
+    infof(data, "Received 101, Switching to WebSocket");
+    result = Curl_ws_accept(data, buf, blen);
+    if(!result)
+      *pconsumed += blen; /* ws accept handled the data */
+    return result;
+  }
+#endif
+  /* We silently accept this as the final response. What are we switching to
+   * if we did not ask for an Upgrade? Maybe the application provided an
+   * `Upgrade: xxx` header? */
+  k->header = FALSE;
+  return CURLE_OK;
+}
+
+/*
+ * Handle 1xx intermediate HTTP responses. Sets up state for more
+ * headers and processes 100-continue and 101 upgrade responses.
+ */
+static CURLcode http_on_1xx_response(struct Curl_easy *data,
+                                     const char *buf, size_t blen,
+                                     size_t *pconsumed,
+                                     bool *conn_changed)
+{
+  struct SingleRequest *k = &data->req;
+
+  /* "A user agent MAY ignore unexpected 1xx status responses."
+   * By default, we expect to get more responses after this one. */
+  k->header = TRUE;
+  k->headerline = 0; /* restart the header line counter */
+
+  switch(k->httpcode) {
+  case 100:
+    /* We have made an HTTP PUT or POST and this is 1.1-lingo that tells us
+     * that the server is OK with this and ready to receive the data. */
+    http_exp100_got100(data);
+    break;
+  case 101:
+    return http_on_101_upgrade(data, buf, blen, pconsumed, conn_changed);
+  default:
+    /* The server may send us other 1xx responses, like informative 103. This
+     * has no influence on request processing and we expect to receive a
+     * final response eventually. */
+    break;
+  }
+  return CURLE_OK;
+}
+
+#if defined(USE_NTLM) || defined(USE_SPNEGO)
+/*
+ * Check if NTLM or SPNEGO authentication negotiation failed due to
+ * connection closure (typically on HTTP/1.0 servers).
+ */
+static void http_check_auth_closure(struct Curl_easy *data,
+                                    struct connectdata *conn)
+{
+  /* At this point we have some idea about the fate of the connection. If we
+     are closing the connection it may result auth failure. */
+#ifdef USE_NTLM
+  if(conn->bits.close &&
+     (((data->req.httpcode == 401) &&
+       (conn->http_ntlm_state == NTLMSTATE_TYPE2)) ||
+      ((data->req.httpcode == 407) &&
+       (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) {
+    infof(data, "Connection closure while negotiating auth (HTTP 1.0?)");
+    data->state.authproblem = TRUE;
+  }
+#endif
+#ifdef USE_SPNEGO
+  if(conn->bits.close &&
+    (((data->req.httpcode == 401) &&
+      (conn->http_negotiate_state == GSS_AUTHRECV)) ||
+     ((data->req.httpcode == 407) &&
+      (conn->proxy_negotiate_state == GSS_AUTHRECV)))) {
+    infof(data, "Connection closure while negotiating auth (HTTP 1.0?)");
+    data->state.authproblem = TRUE;
+  }
+  if((conn->http_negotiate_state == GSS_AUTHDONE) &&
+     (data->req.httpcode != 401)) {
+    conn->http_negotiate_state = GSS_AUTHSUCC;
+  }
+  if((conn->proxy_negotiate_state == GSS_AUTHDONE) &&
+     (data->req.httpcode != 407)) {
+    conn->proxy_negotiate_state = GSS_AUTHSUCC;
+  }
+#endif
+}
+#else
+#define http_check_auth_closure(x,y) /* empty */
+#endif
+
+/*
+ * Handle an error response (>= 300) received while still sending the
+ * request body. Deals with 417 Expectation Failed retries, keep-sending
+ * on error, and aborting the send.
+ */
+static CURLcode http_handle_send_error(struct Curl_easy *data)
+{
+  struct connectdata *conn = data->conn;
+  struct SingleRequest *k = &data->req;
+  CURLcode result = CURLE_OK;
+
+  if(!data->req.authneg && !conn->bits.close &&
+     !Curl_creader_will_rewind(data)) {
+    /*
+     * General treatment of errors when about to send data.
+     * Including: "417 Expectation Failed", while waiting for
+     * 100-continue.
+     *
+     * The check for close above is done because if something
+     * else has already deemed the connection to get closed then
+     * something else should have considered the big picture and
+     * we avoid this check.
+     */
+
+    switch(data->state.httpreq) {
+    case HTTPREQ_PUT:
+    case HTTPREQ_POST:
+    case HTTPREQ_POST_FORM:
+    case HTTPREQ_POST_MIME:
+      /* We got an error response. If this happened before the
+       * whole request body has been sent we stop sending and
+       * mark the connection for closure after we have read the
+       * entire response. */
+      if(!Curl_req_done_sending(data)) {
+        if((k->httpcode == 417) && http_exp100_is_selected(data)) {
+          /* 417 Expectation Failed - try again without the
+             Expect header */
+          if(!k->writebytecount && http_exp100_is_waiting(data)) {
+            infof(data, "Got HTTP failure 417 while waiting for a 100");
+          }
+          else {
+            infof(data, "Got HTTP failure 417 while sending data");
+            streamclose(conn, "Stop sending data before everything sent");
+            result = http_perhapsrewind(data, conn);
+            if(result)
+              return result;
+          }
+          data->state.disableexpect = TRUE;
+          Curl_req_abort_sending(data);
+          DEBUGASSERT(!data->req.newurl);
+          data->req.newurl = Curl_bufref_dup(&data->state.url);
+          if(!data->req.newurl)
+            return CURLE_OUT_OF_MEMORY;
+        }
+        else if(data->set.http_keep_sending_on_error) {
+          infof(data, "HTTP error before end of send, keep sending");
+          http_exp100_send_anyway(data);
+        }
+        else {
+          infof(data, "HTTP error before end of send, stop sending");
+          streamclose(conn, "Stop sending data before everything sent");
+          result = Curl_req_abort_sending(data);
+          if(result)
+            return result;
+        }
+      }
+      break;
+
+    default: /* default label present to avoid compiler warnings */
+      break;
+    }
+  }
+
+  if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) {
+    /* We rewind before next send, continue sending now */
+    infof(data, "Keep sending data to get tossed away");
+    CURL_REQ_SET_SEND(data);
+  }
+  return result;
+}
+
 static CURLcode http_on_response(struct Curl_easy *data,
                                  const char *last_hd, size_t last_hd_len,
                                  const char *buf, size_t blen,
@@ -3909,9 +4129,8 @@ static CURLcode http_on_response(struct Curl_easy *data,
   }
 
   if(k->httpcode < 200 && last_hd) {
-    /* Intermediate responses might trigger processing of more
-     * responses, write the last header to the client before
-     * proceeding. */
+    /* Intermediate responses might trigger processing of more responses,
+     * write the last header to the client before proceeding. */
     result = http_write_header(data, last_hd, last_hd_len);
     last_hd = NULL; /* handled it */
     if(result)
@@ -3924,82 +4143,15 @@ static CURLcode http_on_response(struct Curl_easy *data,
     goto out;
   }
   else if(k->httpcode < 200) {
-    /* "A user agent MAY ignore unexpected 1xx status responses."
-     * By default, we expect to get more responses after this one. */
-    k->header = TRUE;
-    k->headerline = 0; /* restart the header line counter */
-
-    switch(k->httpcode) {
-    case 100:
-      /*
-       * We have made an HTTP PUT or POST and this is 1.1-lingo
-       * that tells us that the server is OK with this and ready
-       * to receive the data.
-       */
-      http_exp100_got100(data);
-      break;
-    case 101: {
-      int upgr101_requested = k->upgr101;
-
-      if(k->httpversion_sent != 11) {
-        /* invalid for other HTTP versions */
-        failf(data, "server sent 101 response while not talking HTTP/1.1");
-        result = CURLE_WEIRD_SERVER_REPLY;
-        goto out;
-      }
-
-      /* Whatever the success, upgrade was selected. */
-      k->upgr101 = UPGR101_RECEIVED;
-      data->conn->bits.upgrade_in_progress = FALSE;
-      conn_changed = TRUE;
-
-      /* To be fully conform, we would check the "Upgrade:" response header
-       * to mention the protocol we requested. */
-      switch(upgr101_requested) {
-      case UPGR101_H2:
-        /* Switch to HTTP/2, where we will get more responses.
-         * blen bytes in bug are already h2 protocol bytes */
-        infof(data, "Received 101, Switching to HTTP/2");
-        result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
-        if(result)
-          goto out;
-        *pconsumed += blen;
-        break;
-#ifndef CURL_DISABLE_WEBSOCKETS
-      case UPGR101_WS:
-        /* Switch to WebSocket, where we now stream ws frames.
-         * blen bytes in bug are already ws protocol bytes */
-        infof(data, "Received 101, Switching to WebSocket");
-        result = Curl_ws_accept(data, buf, blen);
-        if(result)
-          goto out;
-        *pconsumed += blen; /* ws accept handled the data */
-        break;
-#endif
-      default:
-        /* We silently accept this as the final response. What are we
-         * switching to if we did not ask for an Upgrade? Maybe the
-         * application provided an `Upgrade: xxx` header? */
-        k->header = FALSE;
-        break;
-      }
-      /* processed 101 */
-      break;
-    }
-    default:
-      /* The server may send us other 1xx responses, like informative
-       * 103. This have no influence on request processing and we expect
-       * to receive a final response eventually. */
-      break;
-    }
+    result = http_on_1xx_response(data, buf, blen, pconsumed, &conn_changed);
     goto out;
   }
 
   /* k->httpcode >= 200, final response */
   k->header = FALSE;
-  if(data->conn->bits.upgrade_in_progress) {
-    /* Asked for protocol upgrade, but it was not selected by the server */
-    data->conn->bits.upgrade_in_progress = FALSE;
+  if(conn->bits.upgrade_in_progress) {
+    /* Asked for protocol upgrade, but it was not selected */
+    conn->bits.upgrade_in_progress = FALSE;
     conn_changed = TRUE;
   }
 
@@ -4008,48 +4160,17 @@ static CURLcode http_on_response(struct Curl_easy *data,
      !(conn->scheme->protocol & CURLPROTO_RTSP) &&
      data->state.httpreq != HTTPREQ_HEAD) {
     /* On HTTP 1.1, when connection is not to get closed, but no
-       Content-Length nor Transfer-Encoding chunked have been
-       received, according to RFC2616 section 4.4 point 5, we
-       assume that the server will close the connection to
-       signal the end of the document. */
-    infof(data, "no chunk, no close, no size. Assume close to "
-          "signal end");
+       Content-Length nor Transfer-Encoding chunked have been received,
+       according to RFC2616 section 4.4 point 5, we assume that the server
+       will close the connection to signal the end of the document. */
+    infof(data, "no chunk, no close, no size. Assume close to signal end");
     streamclose(conn, "HTTP: No end-of-message indicator");
   }
 
-  /* At this point we have some idea about the fate of the connection.
-     If we are closing the connection it may result auth failure. */
-#ifdef USE_NTLM
-  if(conn->bits.close &&
-     (((data->req.httpcode == 401) &&
-       (conn->http_ntlm_state == NTLMSTATE_TYPE2)) ||
-      ((data->req.httpcode == 407) &&
-       (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) {
-    infof(data, "Connection closure while negotiating auth (HTTP 1.0?)");
-    data->state.authproblem = TRUE;
-  }
-#endif
-#ifdef USE_SPNEGO
-  if(conn->bits.close &&
-    (((data->req.httpcode == 401) &&
-      (conn->http_negotiate_state == GSS_AUTHRECV)) ||
-     ((data->req.httpcode == 407) &&
-      (conn->proxy_negotiate_state == GSS_AUTHRECV)))) {
-    infof(data, "Connection closure while negotiating auth (HTTP 1.0?)");
-    data->state.authproblem = TRUE;
-  }
-  if((conn->http_negotiate_state == GSS_AUTHDONE) &&
-     (data->req.httpcode != 401)) {
-    conn->http_negotiate_state = GSS_AUTHSUCC;
-  }
-  if((conn->proxy_negotiate_state == GSS_AUTHDONE) &&
-     (data->req.httpcode != 407)) {
-    conn->proxy_negotiate_state = GSS_AUTHSUCC;
-  }
-#endif
+  http_check_auth_closure(data, conn);
 
 #ifndef CURL_DISABLE_WEBSOCKETS
-  /* All >=200 HTTP status codes are errors when wanting WebSocket */
+  /* All >=200 HTTP status codes are errors when wanting ws */
   if(data->req.upgr101 == UPGR101_WS) {
     failf(data, "Refused WebSocket upgrade: %d", k->httpcode);
     result = CURLE_HTTP_RETURNED_ERROR;
@@ -4059,88 +4180,23 @@ static CURLcode http_on_response(struct Curl_easy *data,
 
   /* Check if this response means the transfer errored. */
   if(http_should_fail(data, data->req.httpcode)) {
-    failf(data, "The requested URL returned error: %d", k->httpcode);
+    failf(data, "The requested URL returned error: %d",
+          k->httpcode);
     result = CURLE_HTTP_RETURNED_ERROR;
     goto out;
   }
 
-  /* Curl_http_auth_act() checks what authentication methods
-   * that are available and decides which one (if any) to
-   * use. It will set 'newurl' if an auth method was picked. */
+  /* Curl_http_auth_act() checks what authentication methods that are
+   * available and decides which one (if any) to use. It will set 'newurl' if
+   * an auth method was picked. */
   result = Curl_http_auth_act(data);
   if(result)
     goto out;
 
   if(k->httpcode >= 300) {
-    if(!data->req.authneg && !conn->bits.close &&
-       !Curl_creader_will_rewind(data)) {
-      /*
-       * General treatment of errors when about to send data. Including :
-       * "417 Expectation Failed", while waiting for 100-continue.
-       *
-       * The check for close above is done because of something
-       * else has already deemed the connection to get closed then
-       * something else should have considered the big picture and we
-       * avoid this check.
-       *
-       */
-
-      switch(data->state.httpreq) {
-      case HTTPREQ_PUT:
-      case HTTPREQ_POST:
-      case HTTPREQ_POST_FORM:
-      case HTTPREQ_POST_MIME:
-        /* We got an error response. If this happened before the whole
-         * request body has been sent we stop sending and mark the
-         * connection for closure after we have read the entire response.
-         */
-        if(!Curl_req_done_sending(data)) {
-          if((k->httpcode == 417) && http_exp100_is_selected(data)) {
-            /* 417 Expectation Failed - try again without the Expect
-               header */
-            if(!k->writebytecount && http_exp100_is_waiting(data)) {
-              infof(data, "Got HTTP failure 417 while waiting for a 100");
-            }
-            else {
-              infof(data, "Got HTTP failure 417 while sending data");
-              streamclose(conn, "Stop sending data before everything sent");
-              result = http_perhapsrewind(data, conn);
-              if(result)
-                goto out;
-            }
-            data->state.disableexpect = TRUE;
-            Curl_req_abort_sending(data);
-            DEBUGASSERT(!data->req.newurl);
-            data->req.newurl = Curl_bufref_dup(&data->state.url);
-            if(!data->req.newurl) {
-              result = CURLE_OUT_OF_MEMORY;
-              goto out;
-            }
-          }
-          else if(data->set.http_keep_sending_on_error) {
-            infof(data, "HTTP error before end of send, keep sending");
-            http_exp100_send_anyway(data);
-          }
-          else {
-            infof(data, "HTTP error before end of send, stop sending");
-            streamclose(conn, "Stop sending data before everything sent");
-            result = Curl_req_abort_sending(data);
-            if(result)
-              goto out;
-          }
-        }
-        break;
-
-      default: /* default label present to avoid compiler warnings */
-        break;
-      }
-    }
-
-    if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) {
-      /* We rewind before next send, continue sending now */
-      infof(data, "Keep sending data to get tossed away");
-      CURL_REQ_SET_SEND(data);
-    }
+    result = http_handle_send_error(data);
+    if(result)
+      goto out;
   }
 
   /* If we requested a "no body", this is a good time to get
@@ -4149,11 +4205,10 @@ static CURLcode http_on_response(struct Curl_easy *data,
   if(data->req.no_body)
     k->download_done = TRUE;
 
-  /* If max download size is *zero* (nothing) we already have
-     nothing and can safely return ok now! For HTTP/2, we would
-     like to call http2_handle_stream_close to properly close a
-     stream. In order to do this, we keep reading until we
-     close the stream. */
+  /* If max download size is *zero* (nothing) we already have nothing and can
+     safely return ok now! For HTTP/2, we would like to call
+     http2_handle_stream_close to properly close a stream. In order to do
+     this, we keep reading until we close the stream. */
   if((k->maxdownload == 0) && (k->httpversion_sent < 20))
     k->download_done = TRUE;
 
@@ -4161,21 +4216,18 @@ static CURLcode http_on_response(struct Curl_easy *data,
   result = http_firstwrite(data);
 
   if(!result)
-    /* This is the last response that we get for the current request.
-     * Check on the body size and determine if the response is complete.
-     */
+    /* This is the last response that we get for the current request. Check on
+     * the body size and determine if the response is complete. */
     result = http_size(data);
 
 out:
-  if(last_hd) {
+  if(last_hd)
     /* if not written yet, write it now */
-    result = Curl_1st_fatal(
-      result, http_write_header(data, last_hd, last_hd_len));
-  }
-  if(conn_changed) {
-    /* poke the multi handle to allow any pending pipewait to retry now */
+    result = Curl_1st_fatal(result,
+                            http_write_header(data, last_hd, last_hd_len));
+  if(conn_changed)
+    /* poke the multi handle to allow pending pipewait to retry */
     Curl_multi_connchanged(data->multi);
-  }
   return result;
 }
 
index a14c8485cc884baddaaecf3a9d22bef7c2091d1c..e38dc5745ccafd83fe02971763d47032211df345 100644 (file)
@@ -70,7 +70,6 @@ extern struct Curl_cftype Curl_cft_nghttp2;
 
 #define Curl_http2_request_upgrade(x, y)  CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_http2_switch(a)              CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_upgrade(a, b, c, d, e) CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_h2_http_1_1_error(x)         0
 #endif