Browse Source

http: separate response parsing from response action

- move code that triggers on end-of-response into separate function from
  parsing
- simplify some headp/headerlen usage
- add `httpversion` to SingleRequest to indicate the version of the
  current response

Closes #13134
Stefan Eissing 1 month ago
parent
commit
0f08b43557
3 changed files with 339 additions and 317 deletions
  1. 3 4
      lib/c-hyper.c
  2. 335 313
      lib/http.c
  3. 1 0
      lib/request.h

+ 3 - 4
lib/c-hyper.c

@@ -274,14 +274,13 @@ static CURLcode status_line(struct Curl_easy *data,
   /* We need to set 'httpcodeq' for functions that check the response code in
      a single place. */
   data->req.httpcode = http_status;
-
+  data->req.httpversion = http_version == HYPER_HTTP_VERSION_1_1? 11 :
+                          (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10);
   if(data->state.hconnect)
     /* CONNECT */
     data->info.httpproxycode = http_status;
   else {
-    conn->httpversion =
-      http_version == HYPER_HTTP_VERSION_1_1 ? 11 :
-      (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10);
+    conn->httpversion = (unsigned char)data->req.httpversion;
     if(http_version == HYPER_HTTP_VERSION_1_0)
       data->state.httpwant = CURL_HTTP_VERSION_1_0;
 

+ 335 - 313
lib/http.c

@@ -3196,18 +3196,38 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn,
  * Called after the first HTTP response line (the status line) has been
  * received and parsed.
  */
-
 CURLcode Curl_http_statusline(struct Curl_easy *data,
                               struct connectdata *conn)
 {
   struct SingleRequest *k = &data->req;
+
+  switch(k->httpversion) {
+  case 10:
+  case 11:
+#ifdef USE_HTTP2
+  case 20:
+#endif
+#ifdef ENABLE_QUIC
+  case 30:
+#endif
+    /* TODO: we should verify that responses do not switch major
+     * HTTP version of the connection. Now, it seems we might accept
+     * a HTTP/2 response on a HTTP/1.1 connection, which is wrong. */
+    conn->httpversion = (unsigned char)k->httpversion;
+    break;
+  default:
+    failf(data, "Unsupported HTTP version (%u.%d) in response",
+          k->httpversion/10, k->httpversion%10);
+    return CURLE_UNSUPPORTED_PROTOCOL;
+  }
+
   data->info.httpcode = k->httpcode;
+  data->info.httpversion = k->httpversion;
+  conn->httpversion = (unsigned char)k->httpversion;
 
-  data->info.httpversion = conn->httpversion;
-  if(!data->state.httpversion ||
-     data->state.httpversion > conn->httpversion)
+  if(!data->state.httpversion || data->state.httpversion > k->httpversion)
     /* store the lowest server version we encounter */
-    data->state.httpversion = conn->httpversion;
+    data->state.httpversion = (unsigned char)k->httpversion;
 
   /*
    * This code executes as part of processing the header.  As a
@@ -3224,25 +3244,23 @@ CURLcode Curl_http_statusline(struct Curl_easy *data,
     k->ignorebody = TRUE; /* Avoid appending error msg to good data. */
   }
 
-  if(conn->httpversion == 10) {
+  if(k->httpversion == 10) {
     /* Default action for HTTP/1.0 must be to close, unless
        we get one of those fancy headers that tell us the
        server keeps it open for us! */
     infof(data, "HTTP 1.0, assume close after body");
     connclose(conn, "HTTP/1.0 close after body");
   }
-  else if(conn->httpversion == 20 ||
+  else if(k->httpversion == 20 ||
           (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) {
     DEBUGF(infof(data, "HTTP/2 found, allow multiplexing"));
     /* HTTP/2 cannot avoid multiplexing since it is a core functionality
        of the protocol */
     conn->bundle->multiuse = BUNDLE_MULTIPLEX;
   }
-  else if(conn->httpversion >= 11 &&
-          !conn->bits.close) {
+  else if(k->httpversion >= 11 && !conn->bits.close) {
     /* If HTTP version is >= 1.1 and connection is persistent */
-    DEBUGF(infof(data,
-                 "HTTP 1.1 or later with persistent connection"));
+    DEBUGF(infof(data, "HTTP 1.1 or later with persistent connection"));
   }
 
   k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200;
@@ -3350,6 +3368,287 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data,
 }
 
 
+static CURLcode http_on_response(struct Curl_easy *data,
+                                 const char *buf, size_t blen,
+                                 size_t *pconsumed)
+{
+  struct connectdata *conn = data->conn;
+  CURLcode result = CURLE_OK;
+  struct SingleRequest *k = &data->req;
+  bool switch_to_h2 = FALSE;
+
+  (void)buf; /* not used without HTTP2 enabled */
+  *pconsumed = 0;
+
+  if(k->upgr101 == UPGR101_RECEIVED) {
+    /* supposedly upgraded to http2 now */
+    if(conn->httpversion != 20)
+      infof(data, "Lying server, not serving HTTP/2");
+  }
+  if(conn->httpversion < 20) {
+    conn->bundle->multiuse = BUNDLE_NO_MULTIUSE;
+  }
+
+  if(k->httpcode < 100) {
+    failf(data, "Unsupported response code in HTTP response");
+    return CURLE_UNSUPPORTED_PROTOCOL;
+  }
+  else if(k->httpcode < 200) {
+    /* "A user agent MAY ignore unexpected 1xx status responses." */
+    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.
+       * However, we'll get more headers now so we must get
+       * back into the header-parsing state!
+       */
+      k->header = TRUE;
+      k->headerline = 0; /* restart the header line counter */
+
+      /* if we did wait for this do enable write now! */
+      Curl_http_exp100_got100(data);
+      break;
+    case 101:
+      if(conn->httpversion == 11) {
+        /* Switching Protocols only allowed from HTTP/1.1 */
+        if(k->upgr101 == UPGR101_H2) {
+          /* Switching to HTTP/2 */
+          infof(data, "Received 101, Switching to HTTP/2");
+          k->upgr101 = UPGR101_RECEIVED;
+
+          /* we'll get more headers (HTTP/2 response) */
+          k->header = TRUE;
+          k->headerline = 0; /* restart the header line counter */
+          switch_to_h2 = TRUE;
+        }
+#ifdef USE_WEBSOCKETS
+        else if(k->upgr101 == UPGR101_WS) {
+          /* verify the response */
+          result = Curl_ws_accept(data, buf, blen);
+          if(result)
+            return result;
+          k->header = FALSE; /* no more header to parse! */
+          *pconsumed += blen; /* ws accept handled the data */
+          blen = 0;
+          if(data->set.connect_only)
+            k->keepon &= ~KEEP_RECV; /* read no more content */
+        }
+#endif
+        else {
+          /* Not switching to another protocol */
+          k->header = FALSE; /* no more header to parse! */
+        }
+      }
+      else {
+        /* invalid for other HTTP versions */
+        failf(data, "unexpected 101 response code");
+        return CURLE_WEIRD_SERVER_REPLY;
+      }
+      break;
+    default:
+      /* the status code 1xx indicates a provisional response, so
+         we'll get another set of headers */
+      k->header = TRUE;
+      k->headerline = 0; /* restart the header line counter */
+      break;
+    }
+  }
+  else {
+    /* k->httpcode >= 200, final response */
+    k->header = FALSE;
+
+    if(k->upgr101 == UPGR101_H2) {
+      /* A requested upgrade was denied, poke the multi handle to possibly
+         allow a pending pipewait to continue */
+      Curl_multi_connchanged(data->multi);
+    }
+
+    if((k->size == -1) && !k->chunk && !conn->bits.close &&
+       (conn->httpversion == 11) &&
+       !(conn->handler->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");
+      streamclose(conn, "HTTP: No end-of-message indicator");
+    }
+  }
+
+  if(!k->header) {
+    result = Curl_http_size(data);
+    if(result)
+      return result;
+  }
+
+  /* At this point we have some idea about the fate of the connection.
+     If we are closing the connection it may result auth failure. */
+#if defined(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
+#if defined(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
+
+  /*
+   * When all the headers have been parsed, see if we should give
+   * up and return an error.
+   */
+  if(http_should_fail(data)) {
+    failf(data, "The requested URL returned error: %d",
+          k->httpcode);
+    return CURLE_HTTP_RETURNED_ERROR;
+  }
+
+#ifdef USE_WEBSOCKETS
+  /* All non-101 HTTP status codes are bad when wanting to upgrade to
+     websockets */
+  if(data->req.upgr101 == UPGR101_WS) {
+    failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
+    return CURLE_HTTP_RETURNED_ERROR;
+  }
+#endif
+
+
+  /* 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)
+    return result;
+
+  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 simply because of something
+       * else has already deemed the connection to get closed then
+       * something else should've considered the big picture and we
+       * avoid this check.
+       *
+       * rewindbeforesend indicates that something has told libcurl to
+       * continue sending even if it gets discarded
+       */
+
+      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've read the entire response.
+         */
+        if(!Curl_req_done_sending(data)) {
+          if((k->httpcode == 417) && Curl_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;
+            DEBUGASSERT(!data->req.newurl);
+            data->req.newurl = strdup(data->state.url);
+            Curl_req_abort_sending(data);
+          }
+          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");
+      k->keepon |= KEEP_SEND;
+    }
+  }
+
+  if(!k->header) {
+    /*
+     * really end-of-headers.
+     *
+     * If we requested a "no body", this is a good time to get
+     * out and return home.
+     */
+    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!  But for HTTP/2, we'd
+       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(0 == k->maxdownload
+       && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
+       && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
+      k->download_done = TRUE;
+  }
+
+  if(switch_to_h2) {
+    /* Having handled the headers, we can do the HTTP/2 switch.
+     * Any remaining `buf` bytes are already HTTP/2 and passed to
+     * be processed. */
+    result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
+    if(result)
+      return result;
+    *pconsumed += blen;
+  }
+
+  return CURLE_OK;
+}
 /*
  * Read any HTTP header lines from the server and pass them to the client app.
  */
@@ -3449,133 +3748,13 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
     hd = Curl_dyn_ptr(&data->state.headerb);
     hdlen = Curl_dyn_len(&data->state.headerb);
     if((0x0a == *hd) || (0x0d == *hd)) {
-      bool switch_to_h2 = FALSE;
-      /* Zero-length header line means end of headers! */
-
-      if(100 <= k->httpcode && 199 >= k->httpcode) {
-        /* "A user agent MAY ignore unexpected 1xx status responses." */
-        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.
-           * However, we'll get more headers now so we must get
-           * back into the header-parsing state!
-           */
-          k->header = TRUE;
-          k->headerline = 0; /* restart the header line counter */
-
-          /* if we did wait for this do enable write now! */
-          Curl_http_exp100_got100(data);
-          break;
-        case 101:
-          if(conn->httpversion == 11) {
-            /* Switching Protocols only allowed from HTTP/1.1 */
-            if(k->upgr101 == UPGR101_H2) {
-              /* Switching to HTTP/2 */
-              infof(data, "Received 101, Switching to HTTP/2");
-              k->upgr101 = UPGR101_RECEIVED;
-
-              /* we'll get more headers (HTTP/2 response) */
-              k->header = TRUE;
-              k->headerline = 0; /* restart the header line counter */
-              switch_to_h2 = TRUE;
-            }
-#ifdef USE_WEBSOCKETS
-            else if(k->upgr101 == UPGR101_WS) {
-              /* verify the response */
-              result = Curl_ws_accept(data, buf, blen);
-              if(result)
-                return result;
-              k->header = FALSE; /* no more header to parse! */
-              *pconsumed += blen; /* ws accept handled the data */
-              blen = 0;
-              if(data->set.connect_only)
-                k->keepon &= ~KEEP_RECV; /* read no more content */
-            }
-#endif
-            else {
-              /* Not switching to another protocol */
-              k->header = FALSE; /* no more header to parse! */
-            }
-          }
-          else {
-            /* invalid for other HTTP versions */
-            failf(data, "unexpected 101 response code");
-            return CURLE_WEIRD_SERVER_REPLY;
-          }
-          break;
-        default:
-          /* the status code 1xx indicates a provisional response, so
-             we'll get another set of headers */
-          k->header = TRUE;
-          k->headerline = 0; /* restart the header line counter */
-          break;
-        }
-      }
-      else {
-        if(k->upgr101 == UPGR101_H2) {
-          /* A requested upgrade was denied, poke the multi handle to possibly
-             allow a pending pipewait to continue */
-          Curl_multi_connchanged(data->multi);
-        }
-        k->header = FALSE; /* no more header to parse! */
-
-        if((k->size == -1) && !k->chunk && !conn->bits.close &&
-           (conn->httpversion == 11) &&
-           !(conn->handler->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");
-          streamclose(conn, "HTTP: No end-of-message indicator");
-        }
-      }
-
-      if(!k->header) {
-        result = Curl_http_size(data);
-        if(result)
-          return result;
-      }
-
-      /* At this point we have some idea about the fate of the connection.
-         If we are closing the connection it may result auth failure. */
-#if defined(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
-#if defined(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
+      /* Empty header line means end of headers! */
+      size_t consumed;
 
       /* now, only output this if the header AND body are requested:
        */
+      Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
+
       writetype = CLIENTWRITE_HEADER |
         ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0);
 
@@ -3586,147 +3765,24 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
       result = Curl_bump_headersize(data, hdlen, FALSE);
       if(result)
         return result;
-
-      /*
-       * When all the headers have been parsed, see if we should give
-       * up and return an error.
-       */
-      if(http_should_fail(data)) {
-        failf(data, "The requested URL returned error: %d",
-              k->httpcode);
-        return CURLE_HTTP_RETURNED_ERROR;
-      }
-
-#ifdef USE_WEBSOCKETS
-      /* All non-101 HTTP status codes are bad when wanting to upgrade to
-         websockets */
-      if(data->req.upgr101 == UPGR101_WS) {
-        failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
-        return CURLE_HTTP_RETURNED_ERROR;
-      }
-#endif
-
+      /* We are done with this line. We reset because response
+       * processing might switch to HTTP/2 and that might call us
+       * directly again. */
+      Curl_dyn_reset(&data->state.headerb);
 
       data->req.deductheadercount =
         (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0;
 
-      /* 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);
-
+      /* analyze the response to find out what to do */
+      result = http_on_response(data, buf, blen, &consumed);
       if(result)
         return result;
+      *pconsumed += consumed;
+      blen -= consumed;
+      buf += consumed;
 
-      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 simply because of something
-           * else has already deemed the connection to get closed then
-           * something else should've considered the big picture and we
-           * avoid this check.
-           *
-           * rewindbeforesend indicates that something has told libcurl to
-           * continue sending even if it gets discarded
-           */
-
-          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've read the entire response.
-             */
-            if(!Curl_req_done_sending(data)) {
-              if((k->httpcode == 417) && Curl_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;
-                DEBUGASSERT(!data->req.newurl);
-                data->req.newurl = strdup(data->state.url);
-                Curl_req_abort_sending(data);
-              }
-              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");
-          k->keepon |= KEEP_SEND;
-        }
-      }
-
-      if(!k->header) {
-        /*
-         * really end-of-headers.
-         *
-         * If we requested a "no body", this is a good time to get
-         * out and return home.
-         */
-        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!  But for HTTP/2, we'd
-           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(0 == k->maxdownload
-           && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
-           && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
-          k->download_done = TRUE;
-
-        Curl_debug(data, CURLINFO_HEADER_IN,
-                   Curl_dyn_ptr(&data->state.headerb),
-                   Curl_dyn_len(&data->state.headerb));
+      if(!k->header || !blen)
         goto out; /* exit header line loop */
-      }
-
-      /* We continue reading headers, reset the line-based header */
-      Curl_dyn_reset(&data->state.headerb);
-      if(switch_to_h2) {
-        /* Having handled the headers, we can do the HTTP/2 switch.
-         * Any remaining `buf` bytes are already HTTP/2 and passed to
-         * be processed. */
-        result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
-        if(result)
-          return result;
-        *pconsumed += blen;
-        blen = 0;
-      }
 
       continue;
     }
@@ -3740,6 +3796,8 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
       /* This is the first header, it MUST be the error code line
          or else we consider this to be the body right away! */
       bool fine_statusline = FALSE;
+
+      k->httpversion = 0; /* Don't know yet */
       if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
         /*
          * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
@@ -3748,7 +3806,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
          * says. We allow any three-digit number here, but we cannot make
          * guarantees on future behaviors since it isn't within the protocol.
          */
-        int httpversion = 0;
         char *p = hd;
 
         while(*p && ISBLANK(*p))
@@ -3760,7 +3817,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
             p++;
             if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
               if(ISBLANK(p[2])) {
-                httpversion = 10 + (p[1] - '0');
+                k->httpversion = 10 + (p[1] - '0');
                 p += 3;
                 if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
                   k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
@@ -3780,7 +3837,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
           case '3':
             if(!ISBLANK(p[1]))
               break;
-            httpversion = (*p - '0') * 10;
+            k->httpversion = (*p - '0') * 10;
             p += 2;
             if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
               k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
@@ -3797,49 +3854,15 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
           }
         }
 
-        if(fine_statusline) {
-          if(k->httpcode < 100) {
-            failf(data, "Unsupported response code in HTTP response");
-            return CURLE_UNSUPPORTED_PROTOCOL;
-          }
-          switch(httpversion) {
-          case 10:
-          case 11:
-#ifdef USE_HTTP2
-          case 20:
-#endif
-#ifdef ENABLE_QUIC
-          case 30:
-#endif
-            conn->httpversion = (unsigned char)httpversion;
-            break;
-          default:
-            failf(data, "Unsupported HTTP version (%u.%d) in response",
-                  httpversion/10, httpversion%10);
-            return CURLE_UNSUPPORTED_PROTOCOL;
-          }
-
-          if(k->upgr101 == UPGR101_RECEIVED) {
-            /* supposedly upgraded to http2 now */
-            if(conn->httpversion != 20)
-              infof(data, "Lying server, not serving HTTP/2");
-          }
-          if(conn->httpversion < 20) {
-            conn->bundle->multiuse = BUNDLE_NO_MULTIUSE;
-          }
-        }
-        else {
+        if(!fine_statusline) {
           /* If user has set option HTTP200ALIASES,
              compare header line against list of aliases
           */
-          statusline check =
-            checkhttpprefix(data,
-                            Curl_dyn_ptr(&data->state.headerb),
-                            Curl_dyn_len(&data->state.headerb));
+          statusline check = checkhttpprefix(data, hd, hdlen);
           if(check == STATUS_DONE) {
             fine_statusline = TRUE;
             k->httpcode = 200;
-            conn->httpversion = 10;
+            k->httpversion = 10;
           }
         }
       }
@@ -3860,7 +3883,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
                   p += 3;
                   if(ISSPACE(*p)) {
                     fine_statusline = TRUE;
-                    conn->httpversion = 11; /* RTSP acts like HTTP 1.1 */
+                    k->httpversion = 11; /* RTSP acts like HTTP 1.1 */
                   }
                 }
               }
@@ -3892,13 +3915,12 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
       return result;
 
     /*
-     * End of header-checks. Write them to the client.
+     * Taken in one (more) header. Write it to the client.
      */
-    if(k->httpcode/100 == 1)
-      writetype |= CLIENTWRITE_1XX;
-
     Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
 
+    if(k->httpcode/100 == 1)
+      writetype |= CLIENTWRITE_1XX;
     result = Curl_client_write(data, writetype, hd, hdlen);
     if(result)
       return result;

+ 1 - 0
lib/request.h

@@ -78,6 +78,7 @@ struct SingleRequest {
                                    first one */
   curl_off_t offset;            /* possible resume offset read from the
                                    Content-Range: header */
+  int httpversion;              /* Version in response (09, 10, 11, etc.) */
   int httpcode;                 /* error code from the 'HTTP/1.? XXX' or
                                    'RTSP/1.? XXX' line */
   int keepon;