diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 83b8239d45..5c764bb59f 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -69,6 +69,8 @@ struct h1_tunnel_state { h1_tunnel_state tunnel_state; BIT(chunked_encoding); BIT(close_connection); + BIT(maybe_folded); + BIT(leading_unfold); }; static bool tunnel_is_established(struct h1_tunnel_state *ts) @@ -94,6 +96,8 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf, ts->keepon = KEEPON_CONNECT; ts->cl = 0; ts->close_connection = FALSE; + ts->maybe_folded = FALSE; + ts->leading_unfold = FALSE; return CURLE_OK; } @@ -345,16 +349,82 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, return result; } +static CURLcode single_header(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) +{ + CURLcode result = CURLE_OK; + char *linep = curlx_dyn_ptr(&ts->rcvbuf); + size_t line_len = curlx_dyn_len(&ts->rcvbuf); /* bytes in this line */ + struct SingleRequest *k = &data->req; + int writetype; + ts->headerlines++; + + /* output debug if that is requested */ + Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); + + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | + (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + result = Curl_client_write(data, writetype, linep, line_len); + if(result) + return result; + + result = Curl_bump_headersize(data, line_len, TRUE); + if(result) + return result; + + /* Newlines are CRLF, so the CR is ignored as the line is not + really terminated until the LF comes. Treat a following CR + as end-of-headers as well.*/ + + if(ISNEWLINE(linep[0])) { + /* end of response-headers from the proxy */ + + if((407 == k->httpcode) && !data->state.authproblem) { + /* If we get a 407 response code with content length + when we have no auth problem, we must ignore the + whole response-body */ + ts->keepon = KEEPON_IGNORE; + + if(ts->cl) { + infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl); + } + else if(ts->chunked_encoding) { + infof(data, "Ignore chunked response-body"); + } + else { + /* without content-length or chunked encoding, we + cannot keep the connection alive since the close is + the end signal so we bail out at once instead */ + CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); + ts->keepon = KEEPON_DONE; + } + } + else { + ts->keepon = KEEPON_DONE; + } + + DEBUGASSERT(ts->keepon == KEEPON_IGNORE || + ts->keepon == KEEPON_DONE); + return result; + } + + result = on_resp_header(cf, data, ts, linep); + if(result) + return result; + + curlx_dyn_reset(&ts->rcvbuf); + return result; +} + static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts, bool *done) { CURLcode result = CURLE_OK; - struct SingleRequest *k = &data->req; - char *linep; - size_t line_len; - int error, writetype; + int error; #define SELECT_OK 0 #define SELECT_ERROR 1 @@ -428,6 +498,31 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, continue; } + if(ts->maybe_folded) { + if(ISBLANK(byte)) { + Curl_http_to_fold(&ts->rcvbuf); + ts->leading_unfold = TRUE; + } + else { + result = single_header(cf, data, ts); + if(result) + return result; + /* now handle the new byte */ + } + ts->maybe_folded = FALSE; + } + + if(ts->leading_unfold) { + if(ISBLANK(byte)) + /* skip a bit brother */ + continue; + /* non-blank, insert a space then continue the unfolding */ + if(curlx_dyn_addn(&ts->rcvbuf, " ", 1)) { + failf(data, "CONNECT response too large"); + return CURLE_RECV_ERROR; + } + ts->leading_unfold = FALSE; + } if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) { failf(data, "CONNECT response too large"); return CURLE_RECV_ERROR; @@ -436,67 +531,19 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, /* if this is not the end of a header line then continue */ if(byte != 0x0a) continue; - - ts->headerlines++; - linep = curlx_dyn_ptr(&ts->rcvbuf); - line_len = curlx_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ - - /* output debug if that is requested */ - Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); - - /* send the header to the callback */ - writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | - (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); - result = Curl_client_write(data, writetype, linep, line_len); - if(result) - return result; - - result = Curl_bump_headersize(data, line_len, TRUE); - if(result) - return result; - - /* Newlines are CRLF, so the CR is ignored as the line is not - really terminated until the LF comes. Treat a following CR - as end-of-headers as well.*/ - - if(('\r' == linep[0]) || - ('\n' == linep[0])) { - /* end of response-headers from the proxy */ - - if((407 == k->httpcode) && !data->state.authproblem) { - /* If we get a 407 response code with content length - when we have no auth problem, we must ignore the - whole response-body */ - ts->keepon = KEEPON_IGNORE; - - if(ts->cl) { - infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl); - } - else if(ts->chunked_encoding) { - infof(data, "Ignore chunked response-body"); - } - else { - /* without content-length or chunked encoding, we - cannot keep the connection alive since the close is - the end signal so we bail out at once instead */ - CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); - ts->keepon = KEEPON_DONE; - } + else { + char *linep = curlx_dyn_ptr(&ts->rcvbuf); + size_t hlen = curlx_dyn_len(&ts->rcvbuf); + if(hlen && ISNEWLINE(linep[0])) { + /* end of headers */ + result = single_header(cf, data, ts); + if(result) + return result; } - else { - ts->keepon = KEEPON_DONE; - } - - DEBUGASSERT(ts->keepon == KEEPON_IGNORE || - ts->keepon == KEEPON_DONE); - continue; + else + ts->maybe_folded = TRUE; } - result = on_resp_header(cf, data, ts, linep); - if(result) - return result; - - curlx_dyn_reset(&ts->rcvbuf); } /* while there is buffer left and loop is requested */ if(error) diff --git a/lib/dynhds.c b/lib/dynhds.c index d0a3eb6a74..3a4c80de69 100644 --- a/lib/dynhds.c +++ b/lib/dynhds.c @@ -56,29 +56,6 @@ entry_new(const char *name, size_t namelen, return e; } -static struct dynhds_entry *entry_append(struct dynhds_entry *e, - const char *value, size_t valuelen) -{ - struct dynhds_entry *e2; - size_t valuelen2 = e->valuelen + 1 + valuelen; - char *p; - - DEBUGASSERT(value); - e2 = curlx_calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2); - if(!e2) - return NULL; - e2->name = p = ((char *)e2) + sizeof(*e2); - memcpy(p, e->name, e->namelen); - e2->namelen = e->namelen; - e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */ - memcpy(p, e->value, e->valuelen); - p += e->valuelen; - p[0] = ' '; - memcpy(p + 1, value, valuelen); - e2->valuelen = valuelen2; - return e2; -} - static void entry_free(struct dynhds_entry *e) { curlx_free(e); @@ -222,48 +199,26 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, if(!line || !line_len) return CURLE_OK; - if((line[0] == ' ') || (line[0] == '\t')) { - struct dynhds_entry *e, *e2; - /* header continuation, yikes! */ - if(!dynhds->hds_len) - return CURLE_BAD_FUNCTION_ARGUMENT; - - while(line_len && ISBLANK(line[0])) { - ++line; - --line_len; - } - if(!line_len) - return CURLE_BAD_FUNCTION_ARGUMENT; - e = dynhds->hds[dynhds->hds_len - 1]; - e2 = entry_append(e, line, line_len); - if(!e2) - return CURLE_OUT_OF_MEMORY; - dynhds->hds[dynhds->hds_len - 1] = e2; - entry_free(e); - return CURLE_OK; + p = memchr(line, ':', line_len); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + name = line; + namelen = p - line; + p++; /* move past the colon */ + for(i = namelen + 1; i < line_len; ++i, ++p) { + if(!ISBLANK(*p)) + break; } - else { - p = memchr(line, ':', line_len); - if(!p) - return CURLE_BAD_FUNCTION_ARGUMENT; - name = line; - namelen = p - line; - p++; /* move past the colon */ - for(i = namelen + 1; i < line_len; ++i, ++p) { - if(!ISBLANK(*p)) - break; - } - value = p; - valuelen = line_len - i; + value = p; + valuelen = line_len - i; - p = memchr(value, '\r', valuelen); - if(!p) - p = memchr(value, '\n', valuelen); - if(p) - valuelen = (size_t)(p - value); + p = memchr(value, '\r', valuelen); + if(!p) + p = memchr(value, '\n', valuelen); + if(p) + valuelen = (size_t)(p - value); - return Curl_dynhds_add(dynhds, name, namelen, value, valuelen); - } + return Curl_dynhds_add(dynhds, name, namelen, value, valuelen); } CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line) diff --git a/lib/http.c b/lib/http.c index bf38d35dde..3aabd0f7be 100644 --- a/lib/http.c +++ b/lib/http.c @@ -4288,18 +4288,23 @@ static CURLcode http_rw_hd(struct Curl_easy *data, return CURLE_OK; } -/* cut off the newline characters */ -static void unfold_header(struct Curl_easy *data) +/* remove trailing CRLF then all trailing whitespace */ +void Curl_http_to_fold(struct dynbuf *bf) { - size_t len = curlx_dyn_len(&data->state.headerb); - char *hd = curlx_dyn_ptr(&data->state.headerb); + size_t len = curlx_dyn_len(bf); + char *hd = curlx_dyn_ptr(bf); if(len && (hd[len - 1] == '\n')) len--; if(len && (hd[len - 1] == '\r')) len--; while(len && (ISBLANK(hd[len - 1]))) /* strip off trailing whitespace */ len--; - curlx_dyn_setlen(&data->state.headerb, len); + curlx_dyn_setlen(bf, len); +} + +static void unfold_header(struct Curl_easy *data) +{ + Curl_http_to_fold(&data->state.headerb); data->state.leading_unfold = TRUE; } diff --git a/lib/http.h b/lib/http.h index 7f944ea33b..89810b1757 100644 --- a/lib/http.h +++ b/lib/http.h @@ -104,6 +104,8 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect, struct dynhds *hds); +void Curl_http_to_fold(struct dynbuf *bf); + void Curl_http_method(struct Curl_easy *data, const char **method, Curl_HttpReq *); diff --git a/tests/data/test1941 b/tests/data/test1941 index 0542b7b943..4532403bc9 100644 --- a/tests/data/test1941 +++ b/tests/data/test1941 @@ -13,7 +13,8 @@ CONNECT HTTP/1.1 200 OK Date: Thu, 09 Nov 2010 14:49:00 GMT Server: test with trailing space%repeat[5 x ]% -Content-Type: text/html +Content-Type: +%SPtext/html Content-Length: 0 Set-Cookie: onecookie=data; Set-Cookie: secondcookie=2data; @@ -23,7 +24,9 @@ Location: /%TESTNUMBER0002 HTTP/1.1 200 Sure go ahead -Server: from the connect +Server: from%SP +%SPthe%TAB +%TAB%TABconnect Silly-thing: yes yes diff --git a/tests/unit/unit2602.c b/tests/unit/unit2602.c index 684fa3db83..c33e8d63d1 100644 --- a/tests/unit/unit2602.c +++ b/tests/unit/unit2602.c @@ -111,18 +111,6 @@ static CURLcode test_unit2602(const char *arg) } Curl_dynhds_free(&hds); - Curl_dynhds_init(&hds, 128, 4 * 1024); - /* continuation without previous header fails */ - res = Curl_dynhds_h1_cadd_line(&hds, " indented value"); - fail_unless(res, "add should have failed"); - - /* continuation with previous header must succeed */ - fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti1: val1"), "add"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, " val2"), "add indent"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti2: val1"), "add"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, "\tval2"), "add indent"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti3: val1"), "add"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, " val2"), "add indent"); curlx_dyn_init(&dbuf, 32 * 1024); fail_if(Curl_dynhds_h1_dprint(&hds, &dbuf), "h1 print failed"); diff --git a/tests/unit/unit2603.c b/tests/unit/unit2603.c index 1de774d26f..b3ade82a62 100644 --- a/tests/unit/unit2603.c +++ b/tests/unit/unit2603.c @@ -159,16 +159,6 @@ static CURLcode test_unit2603(const char *arg) T4_INPUT, NULL, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2 }; - static const char *T5_INPUT[] = { - "OPTIONS * HTTP/1.1\r\nContent-Length: 0\r\nBlabla: xxx.yyy\r", - "\n\tzzzzzz\r\n\r\n", - "123", - NULL, - }; - static const struct tcase TEST5a = { - T5_INPUT, NULL, NULL, "OPTIONS", NULL, NULL, "*", 2, 3 - }; - static const char *T6_INPUT[] = { "PUT /path HTTP/1.1\nHost: test.curl.se\n\n123", NULL, @@ -194,7 +184,6 @@ static CURLcode test_unit2603(const char *arg) parse_success(&TEST2); parse_success(&TEST3a); parse_success(&TEST4a); - parse_success(&TEST5a); parse_success(&TEST6a); parse_success(&TEST7a); parse_success(&TEST7b);