static CURLcode http_on_response()

in libs/curl/lib/http.c [3379:3665]


static CURLcode http_on_response(struct Curl_easy *data,
                                 const char *last_hd, size_t last_hd_len,
                                 const char *buf, size_t blen,
                                 size_t *pconsumed)
{
  struct connectdata *conn = data->conn;
  CURLcode result = CURLE_OK;
  struct SingleRequest *k = &data->req;

  (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 < 200 && last_hd) {
    /* 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)
      goto out;
  }

  if(k->httpcode < 100) {
    failf(data, "Unsupported response code in HTTP response");
    result = CURLE_UNSUPPORTED_PROTOCOL;
    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.
       */
      Curl_http_exp100_got100(data);
      break;
    case 101:
      /* Switching Protocols only allowed from HTTP/1.1 */

      if(conn->httpversion != 11) {
        /* invalid for other HTTP versions */
        failf(data, "unexpected 101 response code");
        result = CURLE_WEIRD_SERVER_REPLY;
        goto out;
      }
      if(k->upgr101 == UPGR101_H2) {
        /* Switching to HTTP/2, where we will get more responses */
        infof(data, "Received 101, Switching to HTTP/2");
        k->upgr101 = UPGR101_RECEIVED;
        /* We expect more response from HTTP/2 later */
        k->header = TRUE;
        k->headerline = 0; /* restart the header line counter */
        /* Any remaining `buf` bytes are already HTTP/2 and passed to
         * be processed. */
        result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
        if(result)
          goto out;
        *pconsumed += blen;
      }
#ifdef USE_WEBSOCKETS
      else if(k->upgr101 == UPGR101_WS) {
        /* verify the response. Any passed `buf` bytes are already in
         * WebSockets format and taken in by the protocol handler. */
        result = Curl_ws_accept(data, buf, blen);
        if(result)
          goto out;
        *pconsumed += blen; /* ws accept handled the data */
        k->header = FALSE; /* we will not get more responses */
        if(data->set.connect_only)
          k->keepon &= ~KEEP_RECV; /* read no more content */
      }
#endif
      else {
        /* We silently accept this as the final response.
         * TODO: this looks, uhm, wrong. 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;
    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;
    }
    goto out;
  }

  /* 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");
  }

  /* 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

#ifdef USE_WEBSOCKETS
  /* All >=200 HTTP status codes are errors when wanting websockets */
  if(data->req.upgr101 == UPGR101_WS) {
    failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
    result = CURLE_HTTP_RETURNED_ERROR;
    goto out;
  }
#endif

  /* 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);
    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. */
  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 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.
       *
       */

      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) && 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)
                goto out;
            }
            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)
              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");
      k->keepon |= KEEP_SEND;
    }

  }

  /* This is the last response that we will got for the current request.
   * Check on the body size and determine if the response is complete.
   */
  result = Curl_http_size(data);
  if(result)
    goto out;

  /* 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 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(0 == k->maxdownload
     && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
     && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
    k->download_done = TRUE;

  /* final response without error, prepare to receive the body */
  result = Curl_http_firstwrite(data);

out:
  if(last_hd) {
    /* if not written yet, write it now */
    CURLcode r2 = http_write_header(data, last_hd, last_hd_len);
    if(!result)
      result = r2;
  }
  return result;
}