cf-h1-proxy: support folded headers in CONNECT responses
No binary (build failed)
—
(log yok)
| @@ -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 { |
| - | 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; |
| } | |
| - | |
| - | 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) | |
| @@ -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) | |
| @@ -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; | |
| } | |
| @@ -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 *); | |
| @@ -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 | |
| </data> | |
| <connect crlf="headers"> | |
| HTTP/1.1 200 Sure go ahead | |
| - | Server: from the connect |
| + | Server: from%SP |
| + | %SPthe%TAB |
| + | %TAB%TABconnect |
| Silly-thing: yes yes | |
| </connect> | |
| @@ -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"); | |
| @@ -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); | |