diff --git a/lib/c-hyper.c b/lib/c-hyper.c index 88674ee0a..0593d9706 100644 --- a/lib/c-hyper.c +++ b/lib/c-hyper.c @@ -171,7 +171,7 @@ static int hyper_each_header(void *userdata, len = Curl_dyn_len(&data->state.headerb); headp = Curl_dyn_ptr(&data->state.headerb); - result = Curl_http_header(data, data->conn, headp, len); + result = Curl_http_header(data, headp, len); if(result) { data->state.hresult = result; return HYPER_ITER_BREAK; diff --git a/lib/curl_rtmp.c b/lib/curl_rtmp.c index b2f2adad8..c1fd981b7 100644 --- a/lib/curl_rtmp.c +++ b/lib/curl_rtmp.c @@ -80,6 +80,7 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -103,6 +104,7 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -126,6 +128,7 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -149,6 +152,7 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -172,6 +176,7 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ @@ -195,6 +200,7 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ diff --git a/lib/dict.c b/lib/dict.c index f37767882..5404671c6 100644 --- a/lib/dict.c +++ b/lib/dict.c @@ -90,6 +90,7 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_DICT, /* defport */ diff --git a/lib/file.c b/lib/file.c index bee9e92ec..c436aaaad 100644 --- a/lib/file.c +++ b/lib/file.c @@ -115,6 +115,7 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* perform_getsock */ file_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ 0, /* defport */ diff --git a/lib/ftp.c b/lib/ftp.c index 5bbdeb054..760b54ff3 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -177,6 +177,7 @@ const struct Curl_handler Curl_handler_ftp = { ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTP, /* defport */ @@ -208,6 +209,7 @@ const struct Curl_handler Curl_handler_ftps = { ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTPS, /* defport */ diff --git a/lib/gopher.c b/lib/gopher.c index e1a1ba648..7ba070a76 100644 --- a/lib/gopher.c +++ b/lib/gopher.c @@ -76,6 +76,7 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ @@ -100,6 +101,7 @@ const struct Curl_handler Curl_handler_gophers = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ diff --git a/lib/http.c b/lib/http.c index 7cb8b63f8..dea166c3d 100644 --- a/lib/http.c +++ b/lib/http.c @@ -100,7 +100,7 @@ * Forward declarations. */ -static bool http_should_fail(struct Curl_easy *data); +static bool http_should_fail(struct Curl_easy *data, int httpcode); static bool http_exp100_is_waiting(struct Curl_easy *data); static CURLcode http_exp100_add_reader(struct Curl_easy *data); static void http_exp100_send_anyway(struct Curl_easy *data); @@ -123,6 +123,7 @@ const struct Curl_handler Curl_handler_http = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ @@ -151,6 +152,7 @@ const struct Curl_handler Curl_handler_https = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */ @@ -243,8 +245,6 @@ char *Curl_copy_header_value(const char *header) while(*start && ISSPACE(*start)) start++; - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ end = strchr(start, '\r'); if(!end) end = strchr(start, '\n'); @@ -565,7 +565,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) data->state.authhost.done = TRUE; } } - if(http_should_fail(data)) { + if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", data->req.httpcode); result = CURLE_HTTP_RETURNED_ERROR; @@ -1006,21 +1006,18 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, } /** - * http_should_fail() determines whether an HTTP response has gotten us + * http_should_fail() determines whether an HTTP response code has gotten us * into an error state or not. * * @retval FALSE communications should continue * * @retval TRUE communications should not continue */ -static bool http_should_fail(struct Curl_easy *data) +static bool http_should_fail(struct Curl_easy *data, int httpcode) { - int httpcode; DEBUGASSERT(data); DEBUGASSERT(data->conn); - httpcode = data->req.httpcode; - /* ** If we haven't been asked to fail on error, ** don't fail. @@ -2836,9 +2833,10 @@ checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, /* * Curl_http_header() parses a single response header. */ -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *hd, size_t hdlen) +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { + struct connectdata *conn = data->conn; CURLcode result; struct SingleRequest *k = &data->req; const char *v; @@ -2848,7 +2846,7 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, case 'A': #ifndef CURL_DISABLE_ALTSVC v = (data->asi && - ((conn->handler->flags & PROTOPT_SSL) || + ((data->conn->handler->flags & PROTOPT_SSL) || #ifdef CURLDEBUG /* allow debug builds to circumvent the HTTPS restriction */ getenv("CURL_ALTSVC_HTTP") @@ -3306,12 +3304,11 @@ CURLcode Curl_http_size(struct Curl_easy *data) return CURLE_OK; } -static CURLcode verify_header(struct Curl_easy *data) +static CURLcode verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; - const char *header = Curl_dyn_ptr(&data->state.headerb); - size_t hlen = Curl_dyn_len(&data->state.headerb); - char *ptr = memchr(header, 0x00, hlen); + char *ptr = memchr(hd, 0x00, hdlen); if(ptr) { /* this is bad, bail out */ failf(data, "Nul byte in header"); @@ -3320,11 +3317,11 @@ static CURLcode verify_header(struct Curl_easy *data) if(k->headerline < 2) /* the first "header" is the status-line and it has no colon */ return CURLE_OK; - if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2) + if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) /* line folding, can't happen on line 2 */ ; else { - ptr = memchr(header, ':', hlen); + ptr = memchr(hd, ':', hdlen); if(!ptr) { /* this is bad, bail out */ failf(data, "Header without colon"); @@ -3369,7 +3366,6 @@ static CURLcode http_on_response(struct Curl_easy *data, 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; @@ -3388,96 +3384,92 @@ static CURLcode http_on_response(struct Curl_easy *data, return CURLE_UNSUPPORTED_PROTOCOL; } else if(k->httpcode < 200) { - /* "A user agent MAY ignore unexpected 1xx status responses." */ + /* "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. - * 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 { + /* Switching Protocols only allowed from HTTP/1.1 */ + if(conn->httpversion != 11) { /* invalid for other HTTP versions */ failf(data, "unexpected 101 response code"); return CURLE_WEIRD_SERVER_REPLY; } + 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) + return result; + *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) + return result; + *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 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 */ + /* 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; } - } - 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"); - } + return result; } - if(!k->header) { - result = Curl_http_size(data); - if(result) - return result; + /* 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. @@ -3511,31 +3503,25 @@ static CURLcode http_on_response(struct Curl_easy *data, } #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 */ + /* All >=200 HTTP status codes are errors when wanting websockets */ if(data->req.upgr101 == UPGR101_WS) { failf(data, "Refused WebSockets upgrade: %d", k->httpcode); return CURLE_HTTP_RETURNED_ERROR; } #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); + return CURLE_HTTP_RETURNED_ERROR; + } /* 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; @@ -3606,65 +3592,244 @@ static CURLcode http_on_response(struct Curl_easy *data, 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. + /* 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) + return result; + + /* 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; + + /* final response without error, prepare to receive the body */ + return Curl_http_firstwrite(data); +} + +static CURLcode http_rw_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + const char *buf_remain, size_t blen, + size_t *pconsumed) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + int writetype; + + *pconsumed = 0; + if((0x0a == *hd) || (0x0d == *hd)) { + /* Empty header line means end of headers! */ + size_t consumed; + + /* now, only output this if the header AND body are requested: */ - if(data->req.no_body) - k->download_done = TRUE; + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); - /* 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; - } + writetype = CLIENTWRITE_HEADER | + ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); - 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); + result = Curl_client_write(data, writetype, hd, hdlen); if(result) return result; - *pconsumed += blen; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + data->req.deductheadercount = + (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; + + /* analyze the response to find out what to do. */ + /* Caveat: we clear anything in the header brigade, because a + * response might switch HTTP version which may call use recursively. + * Not nice, but that is currently the way of things. */ + Curl_dyn_reset(&data->state.headerb); + result = http_on_response(data, buf_remain, blen, &consumed); + if(result) + return result; + *pconsumed += consumed; + return CURLE_OK; } + /* + * Checks for special headers coming up. + */ + + writetype = CLIENTWRITE_HEADER; + if(!k->headerline++) { + /* 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(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + * + * The response code is always a three-digit number in HTTP as the spec + * says. We allow any three-digit number here, but we cannot make + * guarantees on future behaviors since it isn't within the protocol. + */ + const char *p = hd; + + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "HTTP/", 5)) { + p += 5; + switch(*p) { + case '1': + p++; + if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { + if(ISBLANK(p[2])) { + 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 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) + fine_statusline = TRUE; + } + } + } + if(!fine_statusline) { + failf(data, "Unsupported HTTP/1 subversion in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + case '2': + case '3': + if(!ISBLANK(p[1])) + break; + 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 + + (p[2] - '0'); + p += 3; + if(!ISSPACE(*p)) + break; + fine_statusline = TRUE; + } + break; + default: /* unsupported */ + failf(data, "Unsupported HTTP version in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } + + if(!fine_statusline) { + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + statusline check = checkhttpprefix(data, hd, hdlen); + if(check == STATUS_DONE) { + fine_statusline = TRUE; + k->httpcode = 200; + k->httpversion = 10; + } + } + } + else if(data->conn->handler->protocol & CURLPROTO_RTSP) { + const char *p = hd; + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "RTSP/", 5)) { + p += 5; + if(ISDIGIT(*p)) { + p++; + if((p[0] == '.') && ISDIGIT(p[1])) { + if(ISBLANK(p[2])) { + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) { + fine_statusline = TRUE; + k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ + } + } + } + } + } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; + } + } + + if(fine_statusline) { + result = Curl_http_statusline(data, data->conn); + if(result) + return result; + writetype |= CLIENTWRITE_STATUS; + } + else { + k->header = FALSE; /* this is not a header line */ + return CURLE_WEIRD_SERVER_REPLY; + } + } + + result = verify_header(data, hd, hdlen); + if(result) + return result; + + result = Curl_http_header(data, hd, hdlen); + if(result) + return result; + + /* + * Taken in one (more) header. Write it to the client. + */ + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + if(k->httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + return CURLE_OK; } + /* * Read any HTTP header lines from the server and pass them to the client app. */ -static CURLcode http_rw_headers(struct Curl_easy *data, - const char *buf, size_t blen, - size_t *pconsumed) +static CURLcode http_parse_headers(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; - char *hd; - size_t hdlen; char *end_ptr; bool leftover_body = FALSE; /* header line within buffer loop */ *pconsumed = 0; - do { - size_t line_length; - int writetype; - - /* data is in network encoding so use 0x0a instead of '\n' */ - end_ptr = memchr(buf, 0x0a, blen); + while(blen && k->header) { + size_t consumed; + end_ptr = memchr(buf, '\n', blen); if(!end_ptr) { /* Not a complete header line within buffer, append the data to the end of the headerbuff. */ @@ -3700,14 +3865,13 @@ static CURLcode http_rw_headers(struct Curl_easy *data, } /* decrease the size of the remaining (supposed) header line */ - line_length = (end_ptr - buf) + 1; - result = Curl_dyn_addn(&data->state.headerb, buf, line_length); + consumed = (end_ptr - buf) + 1; + result = Curl_dyn_addn(&data->state.headerb, buf, consumed); if(result) return result; - - blen -= line_length; - buf += line_length; - *pconsumed += line_length; + blen -= consumed; + buf += consumed; + *pconsumed += consumed; /**** * We now have a FULL header line in 'headerb'. @@ -3735,195 +3899,21 @@ static CURLcode http_rw_headers(struct Curl_easy *data, } } - /* headers are in network encoding so use 0x0a and 0x0d instead of '\n' - and '\r' */ - hd = Curl_dyn_ptr(&data->state.headerb); - hdlen = Curl_dyn_len(&data->state.headerb); - if((0x0a == *hd) || (0x0d == *hd)) { - /* 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); - - result = Curl_client_write(data, writetype, hd, hdlen); - if(result) - return result; - - result = Curl_bump_headersize(data, hdlen, FALSE); - if(result) - return result; - /* 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; - - /* analyze the response to find out what to do */ - result = http_on_response(data, buf, blen, &consumed); - if(result) - return result; - *pconsumed += consumed; + result = http_rw_hd(data, Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb), + buf, blen, &consumed); + /* 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); + if(consumed) { blen -= consumed; buf += consumed; - - if(!k->header || !blen) - goto out; /* exit header line loop */ - - continue; + *pconsumed += consumed; } - - /* - * Checks for special headers coming up. - */ - - writetype = CLIENTWRITE_HEADER; - if(!k->headerline++) { - /* 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 - * - * The response code is always a three-digit number in HTTP as the spec - * says. We allow any three-digit number here, but we cannot make - * guarantees on future behaviors since it isn't within the protocol. - */ - char *p = hd; - - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "HTTP/", 5)) { - p += 5; - switch(*p) { - case '1': - p++; - if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { - if(ISBLANK(p[2])) { - 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 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) - fine_statusline = TRUE; - } - } - } - if(!fine_statusline) { - failf(data, "Unsupported HTTP/1 subversion in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - break; - case '2': - case '3': - if(!ISBLANK(p[1])) - break; - 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 + - (p[2] - '0'); - p += 3; - if(!ISSPACE(*p)) - break; - fine_statusline = TRUE; - } - break; - default: /* unsupported */ - failf(data, "Unsupported HTTP version in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - } - - if(!fine_statusline) { - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - statusline check = checkhttpprefix(data, hd, hdlen); - if(check == STATUS_DONE) { - fine_statusline = TRUE; - k->httpcode = 200; - k->httpversion = 10; - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - char *p = hd; - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "RTSP/", 5)) { - p += 5; - if(ISDIGIT(*p)) { - p++; - if((p[0] == '.') && ISDIGIT(p[1])) { - if(ISBLANK(p[2])) { - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) { - fine_statusline = TRUE; - k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ - } - } - } - } - } - if(!fine_statusline) - return CURLE_WEIRD_SERVER_REPLY; - } - } - - if(fine_statusline) { - result = Curl_http_statusline(data, conn); - if(result) - return result; - writetype |= CLIENTWRITE_STATUS; - } - else { - k->header = FALSE; /* this is not a header line */ - break; - } - } - - result = verify_header(data); if(result) return result; - - result = Curl_http_header(data, conn, hd, hdlen); - if(result) - return result; - - /* - * Taken in one (more) header. Write it to the client. - */ - 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; - - result = Curl_bump_headersize(data, hdlen, FALSE); - if(result) - return result; - - Curl_dyn_reset(&data->state.headerb); } - while(blen); /* We might have reached the end of the header part here, but there might be a non-header part left in the end of the read @@ -3935,6 +3925,22 @@ out: return CURLE_OK; } +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + char tmp = 0; + + result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed); + if(!result && is_eos) { + result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS), + &tmp, 0); + } + return result; +} + /* * HTTP protocol `write_resp` implementation. Will parse headers * when not done yet and otherwise return without consuming data. @@ -3950,11 +3956,8 @@ CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, else { CURLcode result; - result = http_rw_headers(data, buf, blen, pconsumed); + result = http_parse_headers(data, buf, blen, pconsumed); if(!result && !data->req.header) { - /* we have successfully finished parsing the HEADERs */ - result = Curl_http_firstwrite(data); - if(!data->req.no_body && Curl_dyn_len(&data->state.headerb)) { /* leftover from parsing something that turned out not * to be a header, only happens if we allow for diff --git a/lib/http.h b/lib/http.h index 047709f20..47fae63c7 100644 --- a/lib/http.h +++ b/lib/http.h @@ -102,8 +102,8 @@ CURLcode Curl_http_target(struct Curl_easy *data, struct connectdata *conn, struct dynbuf *req); CURLcode Curl_http_statusline(struct Curl_easy *data, struct connectdata *conn); -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp, size_t hdlen); +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen); CURLcode Curl_transferencode(struct Curl_easy *data); CURLcode Curl_http_req_set_reader(struct Curl_easy *data, Curl_HttpReq httpreq, @@ -134,6 +134,9 @@ int Curl_http_getsock_do(struct Curl_easy *data, struct connectdata *conn, CURLcode Curl_http_write_resp(struct Curl_easy *data, const char *buf, size_t blen, bool is_eos); +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos); /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, diff --git a/lib/http2.c b/lib/http2.c index fb097c51b..ca224fd66 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -127,6 +127,7 @@ struct cf_h2_ctx { struct bufq inbufq; /* network input */ struct bufq outbufq; /* network output */ struct bufc_pool stream_bufcp; /* spares for stream buffers */ + struct dynbuf scratch; /* scratch buffer for temp use */ size_t drain_total; /* sum of all stream's UrlState drain */ uint32_t max_concurrent_streams; @@ -153,6 +154,7 @@ static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) Curl_bufq_free(&ctx->inbufq); Curl_bufq_free(&ctx->outbufq); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); memset(ctx, 0, sizeof(*ctx)); ctx->call_data = save; } @@ -408,6 +410,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); ctx->last_stream_id = 2147483647; rc = nghttp2_session_callbacks_new(&cbs); @@ -945,14 +948,6 @@ fail: return rv; } -static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *buf, size_t blen) -{ - (void)cf; - return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE); -} - static CURLcode on_stream_frame(struct Curl_cfilter *cf, struct Curl_easy *data, const nghttp2_frame *frame) @@ -1008,7 +1003,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, stream->status_code = -1; } - result = recvbuf_write_hds(cf, data, STRCONST("\r\n")); + result = Curl_xfer_write_resp_hd(data, STRCONST("\r\n"), stream->closed); if(result) return result; @@ -1359,6 +1354,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct h2_stream_ctx *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; @@ -1468,14 +1464,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - /* the space character after the status code is mandatory */ - result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n")); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + result = Curl_xfer_write_resp_hd(data_s, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ @@ -1490,16 +1487,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to an HTTP1-style header */ - result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST(": ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n")); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, (const char *)name, namelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, (const char *)value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + result = Curl_xfer_write_resp_hd(data_s, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ diff --git a/lib/imap.c b/lib/imap.c index 0e013e740..679bfae97 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -131,6 +131,7 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAP, /* defport */ @@ -160,6 +161,7 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAPS, /* defport */ diff --git a/lib/ldap.c b/lib/ldap.c index 394fd32d4..e2546aa59 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -178,6 +178,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -206,6 +207,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ diff --git a/lib/mqtt.c b/lib/mqtt.c index 9290da031..35458648d 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -89,6 +89,7 @@ const struct Curl_handler Curl_handler_mqtt = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_MQTT, /* defport */ diff --git a/lib/openldap.c b/lib/openldap.c index 85a37b818..a5fac5057 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -131,6 +131,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -159,6 +160,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ diff --git a/lib/pop3.c b/lib/pop3.c index 993b2e1c7..85c12cbf2 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -126,6 +126,7 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3, /* defport */ @@ -155,6 +156,7 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3S, /* defport */ @@ -1450,7 +1452,7 @@ static CURLcode pop3_parse_custom_request(struct Curl_easy *data) * This function scans the body after the end-of-body and writes everything * until the end is found. */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) +CURLcode Curl_pop3_write(struct Curl_easy *data, const char *str, size_t nread) { /* This code could be made into a special function in the handler struct */ CURLcode result = CURLE_OK; diff --git a/lib/pop3.h b/lib/pop3.h index 83f0f831e..e8e98db04 100644 --- a/lib/pop3.h +++ b/lib/pop3.h @@ -92,6 +92,7 @@ extern const struct Curl_handler Curl_handler_pop3s; /* This function scans the body after the end-of-body and writes everything * until the end is found */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread); +CURLcode Curl_pop3_write(struct Curl_easy *data, + const char *str, size_t nread); #endif /* HEADER_CURL_POP3_H */ diff --git a/lib/request.c b/lib/request.c index 40515a990..d1605d32a 100644 --- a/lib/request.c +++ b/lib/request.c @@ -266,7 +266,7 @@ static CURLcode req_set_upload_done(struct Curl_easy *data) else if(data->req.writebytecount) infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T " bytes", data->req.writebytecount); - else + else if(!data->req.download_done) infof(data, Curl_creader_total_length(data)? "We are completely uploaded and fine" : "Request completely sent off"); diff --git a/lib/rtsp.c b/lib/rtsp.c index 7251c062b..ab7fda4c0 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -93,7 +93,7 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport); /* @@ -114,6 +114,7 @@ const struct Curl_handler Curl_handler_rtsp = { ZERO_NULL, /* perform_getsock */ rtsp_disconnect, /* disconnect */ rtsp_rtp_write_resp, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ rtsp_conncheck, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTSP, /* defport */ @@ -913,12 +914,12 @@ CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) return CURLE_OK; } -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) { if(checkprefix("CSeq:", header)) { long CSeq = 0; char *endp; - char *p = &header[5]; + const char *p = &header[5]; while(ISBLANK(*p)) p++; CSeq = strtol(p, &endp, 10); @@ -933,8 +934,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } } else if(checkprefix("Session:", header)) { - char *start; - char *end; + const char *start, *end; size_t idlen; /* Find the first non-space letter */ @@ -989,14 +989,13 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport) { /* If we receive multiple Transport response-headers, the linterleaved channels of each response header is recorded and used together for subsequent data validity checks.*/ /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ - char *start; - char *end; + const char *start, *end; start = transport; while(start && *start) { while(*start && ISBLANK(*start) ) @@ -1005,7 +1004,7 @@ CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) if(checkprefix("interleaved=", start)) { long chan1, chan2, chan; char *endp; - char *p = start + 12; + const char *p = start + 12; chan1 = strtol(p, &endp, 10); if(p != endp && chan1 >= 0 && chan1 <= 255) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; diff --git a/lib/rtsp.h b/lib/rtsp.h index 237b80f80..b1ffa5c7e 100644 --- a/lib/rtsp.h +++ b/lib/rtsp.h @@ -31,7 +31,7 @@ extern const struct Curl_handler Curl_handler_rtsp; -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header); #else /* disabled */ diff --git a/lib/smb.c b/lib/smb.c index 77d34e31c..2ce6dbf7f 100644 --- a/lib/smb.c +++ b/lib/smb.c @@ -273,6 +273,7 @@ const struct Curl_handler Curl_handler_smb = { ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMB, /* defport */ @@ -300,6 +301,7 @@ const struct Curl_handler Curl_handler_smbs = { ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMBS, /* defport */ diff --git a/lib/smtp.c b/lib/smtp.c index 20763c0c8..dc2f1c918 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -132,6 +132,7 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTP, /* defport */ @@ -161,6 +162,7 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTPS, /* defport */ diff --git a/lib/telnet.c b/lib/telnet.c index 56ee0855f..b129ff520 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -187,6 +187,7 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TELNET, /* defport */ diff --git a/lib/tftp.c b/lib/tftp.c index ff2b7b745..4667ae1e4 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -182,6 +182,7 @@ const struct Curl_handler Curl_handler_tftp = { ZERO_NULL, /* perform_getsock */ tftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TFTP, /* defport */ diff --git a/lib/transfer.c b/lib/transfer.c index ac4710ef5..6e2067cd6 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -1156,7 +1156,7 @@ void Curl_xfer_setup( } CURLcode Curl_xfer_write_resp(struct Curl_easy *data, - char *buf, size_t blen, + const char *buf, size_t blen, bool is_eos) { CURLcode result = CURLE_OK; @@ -1195,6 +1195,18 @@ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, return result; } +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos) +{ + if(data->conn->handler->write_resp_hd) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos); + } + /* No special handling by protocol handler, write as response bytes */ + return Curl_xfer_write_resp(data, hd0, hdlen, is_eos); +} + CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature) { (void)premature; diff --git a/lib/transfer.h b/lib/transfer.h index e65b2b147..ad0f3a20c 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -62,12 +62,20 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); * @param blen the amount of bytes in `buf` * @param is_eos TRUE iff the connection indicates this to be the last * bytes of the response - * @param done on returnm, TRUE iff the response is complete */ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, - char *buf, size_t blen, + const char *buf, size_t blen, bool is_eos); +/** + * Write a single "header" line from a server response. + * @param hd0 the 0-terminated, single header line + * @param hdlen the length of the header line + * @param is_eos TRUE iff this is the end of the response + */ +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos); + /* This sets up a forthcoming transfer */ void Curl_xfer_setup(struct Curl_easy *data, int sockindex, /* socket index to read from or -1 */ diff --git a/lib/urldata.h b/lib/urldata.h index 90f4d1bb5..308c51c92 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -701,12 +701,18 @@ struct Curl_handler { CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, bool dead_connection); - /* If used, this function gets called from transfer.c:readwrite_data() to + /* If used, this function gets called from transfer.c to allow the protocol to do extra handling in writing response to the client. */ CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen, bool is_eos); + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing a single response + header line to the client. */ + CURLcode (*write_resp_hd)(struct Curl_easy *data, + const char *hd, size_t hdlen, bool is_eos); + /* This function can perform various checks on the connection. See CONNCHECK_* for more information about the checks that can be performed, and CONNRESULT_* for the results that can be returned. */ diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index eea8e1261..74006b472 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -130,6 +130,7 @@ struct cf_ngtcp2_ctx { struct curltime handshake_at; /* time connect handshake finished */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct dynbuf scratch; /* temp buffer for header construction */ size_t max_stream_window; /* max flow window for one stream */ uint64_t max_idle_ms; /* max idle time for QUIC connection */ int qlogfd; @@ -765,12 +766,6 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t sid, return 0; } -static CURLcode write_resp_hds(struct Curl_easy *data, - const char *buf, size_t blen) -{ - return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE); -} - static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, const uint8_t *buf, size_t blen, void *user_data, void *stream_user_data) @@ -835,7 +830,7 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, if(!stream) return 0; /* add a CRLF only if we've received some headers */ - result = write_resp_hds(data, "\r\n", 2); + result = Curl_xfer_write_resp_hd(data, STRCONST("\r\n"), stream->closed); if(result) { return -1; } @@ -855,6 +850,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); @@ -872,17 +868,23 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, return 0; if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; result = Curl_http_decode_status(&stream->status_code, (const char *)h3val.base, h3val.len); if(result) return -1; - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] status: %s", stream_id, line); - result = write_resp_hds(data, line, ncopy); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + result = Curl_xfer_write_resp_hd(data, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] status: %s", + stream_id, Curl_dyn_ptr(&ctx->scratch)); if(result) { return -1; } @@ -892,19 +894,19 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); - result = write_resp_hds(data, (const char *)h3name.base, h3name.len); - if(result) { - return -1; - } - result = write_resp_hds(data, ": ", 2); - if(result) { - return -1; - } - result = write_resp_hds(data, (const char *)h3val.base, h3val.len); - if(result) { - return -1; - } - result = write_resp_hds(data, "\r\n", 2); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3name.base, h3name.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + result = Curl_xfer_write_resp_hd(data, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) { return -1; } @@ -1857,6 +1859,7 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) if(ctx->qconn) ngtcp2_conn_del(ctx->qconn); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); Curl_ssl_peer_cleanup(&ctx->peer); memset(ctx, 0, sizeof(*ctx)); @@ -1994,6 +1997,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); if(result) diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index da7ce6ad9..d6ba987f1 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -162,6 +162,7 @@ const struct Curl_handler Curl_handler_scp = { myssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -189,6 +190,7 @@ const struct Curl_handler Curl_handler_sftp = { myssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 7d8d5f465..6c5704b6a 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -139,6 +139,7 @@ const struct Curl_handler Curl_handler_scp = { ssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -168,6 +169,7 @@ const struct Curl_handler Curl_handler_sftp = { ssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ diff --git a/lib/vssh/wolfssh.c b/lib/vssh/wolfssh.c index 11275910a..6a5aed88f 100644 --- a/lib/vssh/wolfssh.c +++ b/lib/vssh/wolfssh.c @@ -94,6 +94,7 @@ const struct Curl_handler Curl_handler_scp = { wssh_getsock, /* perform_getsock */ wscp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -123,6 +124,7 @@ const struct Curl_handler Curl_handler_sftp = { wssh_getsock, /* perform_getsock */ wsftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ diff --git a/lib/ws.c b/lib/ws.c index 907f64bac..d6a83be90 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -1199,6 +1199,7 @@ const struct Curl_handler Curl_handler_ws = { ZERO_NULL, /* perform_getsock */ ws_disconnect, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ @@ -1224,6 +1225,7 @@ const struct Curl_handler Curl_handler_wss = { ZERO_NULL, /* perform_getsock */ ws_disconnect, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */