diff --git a/docs/libcurl/opts/CURLOPT_HTTPHEADER.md b/docs/libcurl/opts/CURLOPT_HTTPHEADER.md index 4440b4ec4b..c7a705bedc 100644 --- a/docs/libcurl/opts/CURLOPT_HTTPHEADER.md +++ b/docs/libcurl/opts/CURLOPT_HTTPHEADER.md @@ -55,6 +55,10 @@ without content (no data on the right side of the colon) as in `Accept:`, the internally used header is removed. To forcibly add a header without content (nothing after the colon), use the form `name;` (using a trailing semicolon). +There are exceptions when suppressing headers. The `Connection:` header in +HTTP/1.1 cannot be overridden. You can provide values for it, but should a +request require specific ones, they are always added to your own. + The headers included in the linked list **must not** be CRLF-terminated, since libcurl adds CRLF after each header item itself. Failure to comply with this might result in strange behavior. libcurl passes on the verbatim strings you diff --git a/lib/http.c b/lib/http.c index ce31e6dff0..3479bb4ec9 100644 --- a/lib/http.c +++ b/lib/http.c @@ -263,6 +263,19 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, #define Curl_checkProxyheaders(x,y,z,a) NULL #endif +static bool http_header_is_empty(const char *header) +{ + struct Curl_str out; + + if(!curlx_str_cspn(&header, &out, ";:") && + (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) { + curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_trimblanks(&out); + return curlx_strlen(&out) == 0; + } + return TRUE; /* invalid head format, treat as empty */ +} + /* * Strip off leading and trailing whitespace from the value in the given HTTP * header line and return a strdup()ed copy. Returns NULL in case of @@ -1702,7 +1715,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, curlx_str_casecompare(&name, "Content-Length")) ; else if(curlx_str_casecompare(&name, "Connection")) - /* Normal Connection: header generation takes care of this */ + /* Connection headers are handled specially */ ; else if((httpversion >= 20) && curlx_str_casecompare(&name, "Transfer-Encoding")) @@ -2636,17 +2649,29 @@ static CURLcode http_check_new_conn(struct Curl_easy *data) static CURLcode http_add_connection_hd(struct Curl_easy *data, struct dynbuf *req) { - char *custom = Curl_checkheaders(data, STRCONST("Connection")); - char *custom_val = custom ? Curl_copy_header_value(custom) : NULL; - const char *sep = (custom_val && *custom_val) ? ", " : "Connection: "; + struct curl_slist *head; + const char *sep = "Connection: "; CURLcode result = CURLE_OK; size_t rlen = curlx_dyn_len(req); + char *value; + bool skip; - if(custom && !custom_val) - return CURLE_OUT_OF_MEMORY; + /* Add the 1st custom "Connection: " header, if there is one */ + for(head = data->set.headers; head; head = head->next) { + if(curl_strnequal(head->data, "Connection", 10) && + Curl_headersep(head->data[10]) && + !http_header_is_empty(head->data)) { + value = Curl_copy_header_value(head->data); + if(!value) + return CURLE_OUT_OF_MEMORY; + result = curlx_dyn_addf(req, "%s%s", sep, value); + sep = ", "; + free(value); + break; /* leave, having added 1st one */ + } + } - if(custom_val && *custom_val) - result = curlx_dyn_addf(req, "Connection: %s", custom_val); + /* add our internal Connection: header values, if we have any */ if(!result && data->state.http_hd_te) { result = curlx_dyn_addf(req, "%s%s", sep, "TE"); sep = ", "; @@ -2660,9 +2685,26 @@ static CURLcode http_add_connection_hd(struct Curl_easy *data, } if(!result && (rlen < curlx_dyn_len(req))) result = curlx_dyn_addn(req, STRCONST("\r\n")); + if(result) + return result; - free(custom_val); - return result; + /* Add all user-defined Connection: headers after the first */ + skip = TRUE; + for(head = data->set.headers; head; head = head->next) { + if(curl_strnequal(head->data, "Connection", 10) && + Curl_headersep(head->data[10]) && + !http_header_is_empty(head->data)) { + if(skip) { + skip = FALSE; + continue; + } + result = curlx_dyn_addf(req, "%s\r\n", head->data); + if(result) + return result; + } + } + + return CURLE_OK; } /* Header identifier in order we send them by default */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 854791fba0..5063e0210f 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -214,7 +214,7 @@ test1580 test1581 \ test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \ test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \ test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \ -test1614 test1615 test1616 \ +test1614 test1615 test1616 test1617 \ test1620 test1621 \ \ test1630 test1631 test1632 test1633 test1634 test1635 \ diff --git a/tests/data/test1617 b/tests/data/test1617 new file mode 100644 index 0000000000..0fe967bd27 --- /dev/null +++ b/tests/data/test1617 @@ -0,0 +1,75 @@ + + + +HTTP +HTTP GET +compressed +Transfer-Encoding + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Mon, 29 Nov 2004 21:56:53 GMT +Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29 +Vary: Accept-Encoding +Content-Type: text/html; charset=ISO-8859-1 +Transfer-Encoding: gzip, chunked + +2c +%hex[%1f%8b%08%08%79%9e%ab%41%00%03%6c%61%6c%61%6c%61%00%cb%c9%cc%4b%55%30%e4%52%c8%01%d1%46%5c]hex% +%hex[%10%86%31%17%00]hex% +%hex[%02%71%60%18%00%00%00]hex% +0 + + + + +HTTP/1.1 200 OK +Date: Mon, 29 Nov 2004 21:56:53 GMT +Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29 +Vary: Accept-Encoding +Content-Type: text/html; charset=ISO-8859-1 +Transfer-Encoding: gzip, chunked + +line 1 + line 2 + line 3 + + + + +# +# Client-side + + +libz + + +http + + +HTTP GET transfer-encoding with two user Connection: headers + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --tr-encoding -H "Connection: this" -H "Connection: that" + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +TE: gzip +Connection: this, TE +Connection: that + + + +