PROXYHEADER: send these headers in "normal" proxy requests too

Updated the docs to clarify and the code accordingly, with test 1528 to
verify:

When CURLHEADER_SEPARATE is set and libcurl is asked to send a request
to a proxy but it isn't CONNECT, then _both_ header lists
(CURLOPT_HTTPHEADER and CURLOPT_PROXYHEADER) will be used since the
single request is then made for both the proxy and the server.
This commit is contained in:
Daniel Stenberg 2014-02-20 17:10:00 +01:00
parent d3d27551e7
commit 74851340bd
6 changed files with 241 additions and 65 deletions

@ -1549,19 +1549,19 @@ There's an alternative option that sets or replaces headers only for requests
that are sent with CONNECT to a proxy: \fICURLOPT_PROXYHEADER\fP. Use that are sent with CONNECT to a proxy: \fICURLOPT_PROXYHEADER\fP. Use
\fICURLOPT_HEADEROPT\fP to control the behavior. \fICURLOPT_HEADEROPT\fP to control the behavior.
.IP CURLOPT_HEADEROPT .IP CURLOPT_HEADEROPT
Pass a long that is a bitmask of options of how to deal with headers. The Pass a long that is a bitmask of options of how to deal with headers. The two
available options are: mutually exclusive options are:
CURLHEADER_UNIFIED - keep working as before. This means CURLOPT_HTTPHEADER CURLHEADER_UNIFIED - keep working as before. This means CURLOPT_HTTPHEADER
headers will be used in requests both to servers and in CONNECT requests. With headers will be used in requests both to servers and proxies. With this option
this option enabled, \fICURLOPT_PROXYHEADER\fP will not have any effect. enabled, \fICURLOPT_PROXYHEADER\fP will not have any effect.
CURLHEADER_SEPARATE - makes \fICURLOPT_HTTPHEADER\fP headers only get sent to CURLHEADER_SEPARATE - makes \fICURLOPT_HTTPHEADER\fP headers only get sent to
a host and not to a proxy if CONNECT is being used. It has to be set to make a server and not to a proxy. Proxy headers must be set with
\fICURLOPT_PROXYHEADER\fP get used. \fICURLOPT_PROXYHEADER\fP to get used. Note that if a non-CONNECT request is
sent to a proxy, libcurl will send both server headers and proxy headers. When
This behavior is set per request and an application can alter it between doing CONNECT, libcurl will send \fICURLOPT_PROXYHEADER\fP headers only do the
different invokes if desired. proxy and then \fICURLOPT_HTTPHEADER\fP headers only to the server.
(Added in 7.36.0) (Added in 7.36.0)
.IP CURLOPT_PROXYHEADER .IP CURLOPT_PROXYHEADER

@ -1544,80 +1544,120 @@ static CURLcode expect100(struct SessionHandle *data,
return result; return result;
} }
enum proxy_use {
HEADER_SERVER, /* direct to server */
HEADER_PROXY, /* regular request to proxy */
HEADER_CONNECT /* sending CONNECT to a proxy */
};
CURLcode Curl_add_custom_headers(struct connectdata *conn, CURLcode Curl_add_custom_headers(struct connectdata *conn,
bool is_proxy, bool is_connect,
Curl_send_buffer *req_buffer) Curl_send_buffer *req_buffer)
{ {
char *ptr; char *ptr;
struct curl_slist *headers= struct curl_slist *h[2];
(is_proxy && conn->data->set.sep_headers)? struct curl_slist *headers;
conn->data->set.proxyheaders:conn->data->set.headers; int numlists=1; /* by default */
struct SessionHandle *data = conn->data;
int i;
while(headers) { enum proxy_use proxy;
ptr = strchr(headers->data, ':');
if(ptr) {
/* we require a colon for this to be a true header */
ptr++; /* pass the colon */ if(is_connect)
while(*ptr && ISSPACE(*ptr)) proxy = HEADER_CONNECT;
ptr++; else
proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy?
HEADER_PROXY:HEADER_SERVER;
if(*ptr) { switch(proxy) {
/* only send this if the contents was non-blank */ case HEADER_SERVER:
h[0] = data->set.headers;
if(conn->allocptr.host && break;
/* a Host: header was sent already, don't pass on any custom Host: case HEADER_PROXY:
header as that will produce *two* in the same request! */ h[0] = data->set.headers;
checkprefix("Host:", headers->data)) if(data->set.sep_headers) {
; h[1] = data->set.proxyheaders;
else if(conn->data->set.httpreq == HTTPREQ_POST_FORM && numlists++;
/* this header (extended by formdata.c) is sent later */
checkprefix("Content-Type:", headers->data))
;
else if(conn->bits.authneg &&
/* while doing auth neg, don't allow the custom length since
we will force length zero then */
checkprefix("Content-Length", headers->data))
;
else if(conn->allocptr.te &&
/* when asking for Transfer-Encoding, don't pass on a custom
Connection: */
checkprefix("Connection", headers->data))
;
else {
CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n",
headers->data);
if(result)
return result;
}
}
} }
else { break;
ptr = strchr(headers->data, ';'); case HEADER_CONNECT:
if(ptr) { if(data->set.sep_headers)
h[0] = data->set.proxyheaders;
else
h[0] = data->set.headers;
break;
}
ptr++; /* pass the semicolon */ /* loop through one or two lists */
for(i=0; i < numlists; i++) {
headers = h[i];
while(headers) {
ptr = strchr(headers->data, ':');
if(ptr) {
/* we require a colon for this to be a true header */
ptr++; /* pass the colon */
while(*ptr && ISSPACE(*ptr)) while(*ptr && ISSPACE(*ptr))
ptr++; ptr++;
if(*ptr) { if(*ptr) {
/* this may be used for something else in the future */ /* only send this if the contents was non-blank */
}
else {
if(*(--ptr) == ';') {
CURLcode result;
/* send no-value custom header if terminated by semicolon */ if(conn->allocptr.host &&
*ptr = ':'; /* a Host: header was sent already, don't pass on any custom Host:
result = Curl_add_bufferf(req_buffer, "%s\r\n", header as that will produce *two* in the same request! */
headers->data); checkprefix("Host:", headers->data))
;
else if(data->set.httpreq == HTTPREQ_POST_FORM &&
/* this header (extended by formdata.c) is sent later */
checkprefix("Content-Type:", headers->data))
;
else if(conn->bits.authneg &&
/* while doing auth neg, don't allow the custom length since
we will force length zero then */
checkprefix("Content-Length", headers->data))
;
else if(conn->allocptr.te &&
/* when asking for Transfer-Encoding, don't pass on a custom
Connection: */
checkprefix("Connection", headers->data))
;
else {
CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n",
headers->data);
if(result) if(result)
return result; return result;
} }
} }
} }
else {
ptr = strchr(headers->data, ';');
if(ptr) {
ptr++; /* pass the semicolon */
while(*ptr && ISSPACE(*ptr))
ptr++;
if(*ptr) {
/* this may be used for something else in the future */
}
else {
if(*(--ptr) == ';') {
CURLcode result;
/* send no-value custom header if terminated by semicolon */
*ptr = ':';
result = Curl_add_bufferf(req_buffer, "%s\r\n",
headers->data);
if(result)
return result;
}
}
}
}
headers = headers->next;
} }
headers = headers->next;
} }
return CURLE_OK; return CURLE_OK;
} }

@ -129,7 +129,7 @@ test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \ test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \
test1516 \ test1516 \
\ \
test1525 test1526 test1527 \ test1525 test1526 test1527 test1528 \
\ \
test1900 test1901 test1902 test1903 \ test1900 test1901 test1902 test1903 \
\ \

60
tests/data/test1528 Normal file

@ -0,0 +1,60 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
HTTP CONNECT
HTTP proxy
proxytunnel
</keywords>
</info>
# Server-side
<reply>
<connect>
HTTP/1.1 200 OK
We-are: good
</connect>
<data>
HTTP/1.1 200 OK swsclose
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Content-Length: 5
stop
</data>
</reply>
# Client-side
<client>
<server>
http
http-proxy
</server>
<tool>
lib1528
</tool>
<name>
Separately specified proxy/server headers sent in a proxy GET
</name>
<command>
http://the.old.moo:%HTTPPORT/1528 %HOSTIP:%PROXYPORT
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<proxy>
GET http://the.old.moo:%HTTPPORT/1528 HTTP/1.1
Host: the.old.moo:%HTTPPORT
Accept: */*
Proxy-Connection: Keep-Alive
User-Agent: Http Agent
Proxy-User-Agent: Http Agent2
</proxy>
</verify>
</testcase>

@ -23,7 +23,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508 \ lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508 \
lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515 \ lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515 \
lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515 \ lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515 \
lib1525 lib1526 lib1527 \ lib1525 lib1526 lib1527 lib1528 \
lib1900 \ lib1900 \
lib2033 lib2033
@ -369,6 +369,10 @@ lib1527_SOURCES = lib1527.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1527_LDADD = $(TESTUTIL_LIBS) lib1527_LDADD = $(TESTUTIL_LIBS)
lib1527_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1527 lib1527_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1527
lib1528_SOURCES = lib1528.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1528_LDADD = $(TESTUTIL_LIBS)
lib1528_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1528
lib1900_SOURCES = lib1900.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib1900_SOURCES = lib1900.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1900_LDADD = $(TESTUTIL_LIBS) lib1900_LDADD = $(TESTUTIL_LIBS)
lib1900_CPPFLAGS = $(AM_CPPFLAGS) lib1900_CPPFLAGS = $(AM_CPPFLAGS)

72
tests/libtest/lib1528.c Normal file

@ -0,0 +1,72 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include "test.h"
#include "memdebug.h"
int test(char *URL)
{
CURL *curl = NULL;
CURLcode res = CURLE_FAILED_INIT;
/* http header list*/
struct curl_slist *hhl = NULL;
struct curl_slist *phl = NULL;
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
}
if((curl = curl_easy_init()) == NULL) {
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return TEST_ERR_MAJOR_BAD;
}
hhl = curl_slist_append(hhl, "User-Agent: Http Agent");
phl = curl_slist_append(phl, "Proxy-User-Agent: Http Agent2");
if (!hhl) {
goto test_cleanup;
}
test_setopt(curl, CURLOPT_URL, URL);
test_setopt(curl, CURLOPT_PROXY, libtest_arg2);
test_setopt(curl, CURLOPT_HTTPHEADER, hhl);
test_setopt(curl, CURLOPT_PROXYHEADER, phl);
test_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE);
test_setopt(curl, CURLOPT_VERBOSE, 1L);
test_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
test_setopt(curl, CURLOPT_HEADER, 1L);
res = curl_easy_perform(curl);
test_cleanup:
curl_easy_cleanup(curl);
curl_slist_free_all(hhl);
curl_slist_free_all(phl);
curl_global_cleanup();
return (int)res;
}