http-proxy: do the HTTP CONNECT process entirely non-blocking

Mentioned as a problem since 2007 (8f87c15bda) and of course it
existed even before that.

Closes #1547
This commit is contained in:
Daniel Stenberg 2017-06-07 23:02:26 +02:00
parent 1213baba27
commit 5113ad0424
9 changed files with 177 additions and 133 deletions

View File

@ -5,7 +5,7 @@
.\" * | (__| |_| | _ <| |___ .\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____| .\" * \___|\___/|_| \_\_____|
.\" * .\" *
.\" * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. .\" * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" * .\" *
.\" * This software is licensed as described in the file COPYING, which .\" * This software is licensed as described in the file COPYING, which
.\" * you should have received as part of this distribution. The terms .\" * you should have received as part of this distribution. The terms
@ -173,7 +173,6 @@ the future, you should be aware of the following current restrictions:
.nf .nf
- Name resolves unless the c-ares or threaded-resolver backends are used - Name resolves unless the c-ares or threaded-resolver backends are used
- HTTP proxy CONNECT operations
- SOCKS proxy handshakes - SOCKS proxy handshakes
- file:// transfers - file:// transfers
- TELNET transfers - TELNET transfers

View File

@ -272,7 +272,6 @@ static void close_secondarysocket(struct connectdata *conn)
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
} }
conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE;
conn->tunnel_state[SECONDARYSOCKET] = TUNNEL_INIT;
} }
/* /*
@ -3585,7 +3584,7 @@ static CURLcode ftp_do_more(struct connectdata *conn, int *completep)
/* if the second connection isn't done yet, wait for it */ /* if the second connection isn't done yet, wait for it */
if(!conn->bits.tcpconnect[SECONDARYSOCKET]) { if(!conn->bits.tcpconnect[SECONDARYSOCKET]) {
if(conn->tunnel_state[SECONDARYSOCKET] == TUNNEL_CONNECT) { if(Curl_connect_ongoing(conn)) {
/* As we're in TUNNEL_CONNECT state now, we know the proxy name and port /* As we're in TUNNEL_CONNECT state now, we know the proxy name and port
aren't used so we blank their arguments. TODO: make this nicer */ aren't used so we blank their arguments. TODO: make this nicer */
result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, NULL, 0); result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, NULL, 0);
@ -3617,7 +3616,7 @@ static CURLcode ftp_do_more(struct connectdata *conn, int *completep)
return result; return result;
if(conn->bits.tunnel_proxy && conn->bits.httpproxy && if(conn->bits.tunnel_proxy && conn->bits.httpproxy &&
conn->tunnel_state[SECONDARYSOCKET] != TUNNEL_COMPLETE) Curl_connect_ongoing(conn))
return result; return result;

View File

@ -1369,7 +1369,7 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
if(CONNECT_FIRSTSOCKET_PROXY_SSL()) if(CONNECT_FIRSTSOCKET_PROXY_SSL())
return CURLE_OK; /* wait for HTTPS proxy SSL initialization to complete */ return CURLE_OK; /* wait for HTTPS proxy SSL initialization to complete */
if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) if(!Curl_connect_complete(conn))
/* nothing else to do except wait right now - we're not done here. */ /* nothing else to do except wait right now - we're not done here. */
return CURLE_OK; return CURLE_OK;

View File

@ -135,7 +135,47 @@ CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex)
return CURLE_OK; return CURLE_OK;
} }
#define CONNECT_BUFFER_SIZE 16384 bool Curl_connect_complete(struct connectdata *conn)
{
return conn->connect_state &&
(conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
}
bool Curl_connect_ongoing(struct connectdata *conn)
{
return conn->connect_state &&
(conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
}
static CURLcode connect_init(struct connectdata *conn, bool reinit)
{
struct http_connect_state *s;
if(!reinit) {
DEBUGASSERT(!conn->connect_state);
s = calloc(1, sizeof(struct http_connect_state));
if(!s)
return CURLE_OUT_OF_MEMORY;
infof(conn->data, "allocate connect buffer!\n");
conn->connect_state = s;
}
else {
DEBUGASSERT(conn->connect_state);
s = conn->connect_state;
}
s->tunnel_state = TUNNEL_INIT;
s->keepon=TRUE;
s->line_start = s->connect_buffer;
s->ptr = s->line_start;
s->cl=0;
return CURLE_OK;
}
static void connect_done(struct connectdata *conn)
{
struct http_connect_state *s = conn->connect_state;
s->tunnel_state = TUNNEL_COMPLETE;
infof(conn->data, "CONNECT phase completed!\n");
}
static CURLcode CONNECT(struct connectdata *conn, static CURLcode CONNECT(struct connectdata *conn,
int sockindex, int sockindex,
@ -147,23 +187,22 @@ static CURLcode CONNECT(struct connectdata *conn,
struct SingleRequest *k = &data->req; struct SingleRequest *k = &data->req;
CURLcode result; CURLcode result;
curl_socket_t tunnelsocket = conn->sock[sockindex]; curl_socket_t tunnelsocket = conn->sock[sockindex];
curl_off_t cl=0;
bool closeConnection = FALSE; bool closeConnection = FALSE;
bool chunked_encoding = FALSE; bool chunked_encoding = FALSE;
time_t check; time_t check;
struct http_connect_state *s = conn->connect_state;
#define SELECT_OK 0 #define SELECT_OK 0
#define SELECT_ERROR 1 #define SELECT_ERROR 1
#define SELECT_TIMEOUT 2 #define SELECT_TIMEOUT 2
int error = SELECT_OK;
if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE) if(Curl_connect_complete(conn))
return CURLE_OK; /* CONNECT is already completed */ return CURLE_OK; /* CONNECT is already completed */
conn->bits.proxy_connect_closed = FALSE; conn->bits.proxy_connect_closed = FALSE;
do { do {
if(TUNNEL_INIT == conn->tunnel_state[sockindex]) { if(TUNNEL_INIT == s->tunnel_state) {
/* BEGIN CONNECT PHASE */ /* BEGIN CONNECT PHASE */
char *host_port; char *host_port;
Curl_send_buffer *req_buffer; Curl_send_buffer *req_buffer;
@ -271,65 +310,47 @@ static CURLcode CONNECT(struct connectdata *conn,
if(result) if(result)
return result; return result;
conn->tunnel_state[sockindex] = TUNNEL_CONNECT; s->tunnel_state = TUNNEL_CONNECT;
} /* END CONNECT PHASE */ } /* END CONNECT PHASE */
check = Curl_timeleft(data, NULL, TRUE); check = Curl_timeleft(data, NULL, TRUE);
if(check <= 0) { if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout"); failf(data, "Proxy CONNECT aborted due to timeout");
return CURLE_RECV_ERROR; return CURLE_OPERATION_TIMEDOUT;
} }
if(!Curl_conn_data_pending(conn, sockindex)) if(!Curl_conn_data_pending(conn, sockindex))
/* return so we'll be called again polling-style */ /* return so we'll be called again polling-style */
return CURLE_OK; return CURLE_OK;
DEBUGF(infof(data, "Read response immediately from proxy CONNECT\n"));
/* at this point, the tunnel_connecting phase is over. */ /* at this point, the tunnel_connecting phase is over. */
{ /* READING RESPONSE PHASE */ { /* READING RESPONSE PHASE */
size_t nread; /* total size read */ int error = SELECT_OK;
int perline; /* count bytes per line */
int keepon=TRUE; s->perline = 0;
while(s->keepon && !error) {
ssize_t gotbytes; ssize_t gotbytes;
char *ptr;
char *line_start;
ptr = conn->connect_buffer; /* make sure we have space to read more data */
line_start = ptr; if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
nread = 0;
perline = 0;
while(nread < (size_t)CONNECT_BUFFER_SIZE && keepon && !error) {
if(Curl_pgrsUpdate(conn))
return CURLE_ABORTED_BY_CALLBACK;
if(ptr >= &conn->connect_buffer[CONNECT_BUFFER_SIZE]) {
failf(data, "CONNECT response too large!"); failf(data, "CONNECT response too large!");
return CURLE_RECV_ERROR; return CURLE_RECV_ERROR;
} }
check = Curl_timeleft(data, NULL, TRUE);
if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout");
error = SELECT_TIMEOUT; /* already too little time */
break;
}
/* Read one byte at a time to avoid a race condition. Wait at most one /* Read one byte at a time to avoid a race condition. Wait at most one
second before looping to ensure continuous pgrsUpdates. */ second before looping to ensure continuous pgrsUpdates. */
result = Curl_read(conn, tunnelsocket, ptr, 1, &gotbytes); result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
if(result == CURLE_AGAIN) { if(result == CURLE_AGAIN)
if(SOCKET_READABLE(tunnelsocket, check<1000L?check:1000) == -1) { /* socket buffer drained, return */
error = SELECT_ERROR; return CURLE_OK;
failf(data, "Proxy CONNECT aborted due to select/poll error");
break; if(Curl_pgrsUpdate(conn))
} return CURLE_ABORTED_BY_CALLBACK;
continue;
}
if(result) { if(result) {
keepon = FALSE; s->keepon = FALSE;
break; break;
} }
else if(gotbytes <= 0) { else if(gotbytes <= 0) {
@ -343,24 +364,22 @@ static CURLcode CONNECT(struct connectdata *conn,
error = SELECT_ERROR; error = SELECT_ERROR;
failf(data, "Proxy CONNECT aborted"); failf(data, "Proxy CONNECT aborted");
} }
keepon = FALSE; s->keepon = FALSE;
break; break;
} }
/* We got a byte of data */
nread++;
if(keepon > TRUE) { if(s->keepon > TRUE) {
/* This means we are currently ignoring a response-body */ /* This means we are currently ignoring a response-body */
nread = 0; /* make next read start over in the read buffer */ s->ptr = s->connect_buffer;
ptr = conn->connect_buffer; if(s->cl) {
if(cl) {
/* A Content-Length based body: simply count down the counter /* A Content-Length based body: simply count down the counter
and make sure to break out of the loop when we're done! */ and make sure to break out of the loop when we're done! */
cl--; s->cl--;
if(cl <= 0) { if(s->cl <= 0) {
keepon = FALSE; s->keepon = FALSE;
s->tunnel_state = TUNNEL_COMPLETE;
break; break;
} }
} }
@ -372,23 +391,23 @@ static CURLcode CONNECT(struct connectdata *conn,
/* now parse the chunked piece of data so that we can /* now parse the chunked piece of data so that we can
properly tell when the stream ends */ properly tell when the stream ends */
r = Curl_httpchunk_read(conn, ptr, 1, &tookcareof); r = Curl_httpchunk_read(conn, s->ptr, 1, &tookcareof);
if(r == CHUNKE_STOP) { if(r == CHUNKE_STOP) {
/* we're done reading chunks! */ /* we're done reading chunks! */
infof(data, "chunk reading DONE\n"); infof(data, "chunk reading DONE\n");
keepon = FALSE; s->keepon = FALSE;
/* we did the full CONNECT treatment, go COMPLETE */ /* we did the full CONNECT treatment, go COMPLETE */
conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; s->tunnel_state = TUNNEL_COMPLETE;
} }
} }
continue; continue;
} }
perline++; /* amount of bytes in this line so far */ s->perline++; /* amount of bytes in this line so far */
/* if this is not the end of a header line then continue */ /* if this is not the end of a header line then continue */
if(*ptr != 0x0a) { if(*s->ptr != 0x0a) {
ptr++; s->ptr++;
continue; continue;
} }
@ -401,7 +420,7 @@ static CURLcode CONNECT(struct connectdata *conn,
/* output debug if that is requested */ /* output debug if that is requested */
if(data->set.verbose) if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN, Curl_debug(data, CURLINFO_HEADER_IN,
line_start, (size_t)perline, conn); s->line_start, (size_t)s->perline, conn);
if(!data->set.suppress_connect_headers) { if(!data->set.suppress_connect_headers) {
/* send the header to the callback */ /* send the header to the callback */
@ -409,33 +428,32 @@ static CURLcode CONNECT(struct connectdata *conn,
if(data->set.include_header) if(data->set.include_header)
writetype |= CLIENTWRITE_BODY; writetype |= CLIENTWRITE_BODY;
result = Curl_client_write(conn, writetype, line_start, perline); result = Curl_client_write(conn, writetype,
s->line_start, s->perline);
if(result) if(result)
return result; return result;
} }
data->info.header_size += (long)perline; data->info.header_size += (long)s->perline;
data->req.headerbytecount += (long)perline; data->req.headerbytecount += (long)s->perline;
/* Newlines are CRLF, so the CR is ignored as the line isn't /* Newlines are CRLF, so the CR is ignored as the line isn't
really terminated until the LF comes. Treat a following CR really terminated until the LF comes. Treat a following CR
as end-of-headers as well.*/ as end-of-headers as well.*/
if(('\r' == line_start[0]) || if(('\r' == s->line_start[0]) ||
('\n' == line_start[0])) { ('\n' == s->line_start[0])) {
/* end of response-headers from the proxy */ /* end of response-headers from the proxy */
nread = 0; /* make next read start over in the read s->ptr = s->connect_buffer;
buffer */
ptr = conn->connect_buffer;
if((407 == k->httpcode) && !data->state.authproblem) { if((407 == k->httpcode) && !data->state.authproblem) {
/* If we get a 407 response code with content length /* If we get a 407 response code with content length
when we have no auth problem, we must ignore the when we have no auth problem, we must ignore the
whole response-body */ whole response-body */
keepon = 2; s->keepon = 2;
if(cl) { if(s->cl) {
infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
" bytes of response-body\n", cl); " bytes of response-body\n", s->cl);
} }
else if(chunked_encoding) { else if(chunked_encoding) {
CHUNKcode r; CHUNKcode r;
@ -448,46 +466,46 @@ static CURLcode CONNECT(struct connectdata *conn,
function returns! */ function returns! */
k->ignorebody = TRUE; k->ignorebody = TRUE;
if(line_start[1] == '\n') { if(s->line_start[1] == '\n') {
/* this can only be a LF if the letter at index 0 /* this can only be a LF if the letter at index 0
was a CR */ was a CR */
line_start++; s->line_start++;
} }
/* now parse the chunked piece of data so that we can /* now parse the chunked piece of data so that we can
properly tell when the stream ends */ properly tell when the stream ends */
r = Curl_httpchunk_read(conn, line_start + 1, 1, &gotbytes); r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes);
if(r == CHUNKE_STOP) { if(r == CHUNKE_STOP) {
/* we're done reading chunks! */ /* we're done reading chunks! */
infof(data, "chunk reading DONE\n"); infof(data, "chunk reading DONE\n");
keepon = FALSE; s->keepon = FALSE;
/* we did the full CONNECT treatment, go to /* we did the full CONNECT treatment, go to COMPLETE */
COMPLETE */ s->tunnel_state = TUNNEL_COMPLETE;
conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
} }
} }
else { else {
/* without content-length or chunked encoding, we /* without content-length or chunked encoding, we
can't keep the connection alive since the close is can't keep the connection alive since the close is
the end signal so we bail out at once instead */ the end signal so we bail out at once instead */
keepon = FALSE; s->keepon = FALSE;
} }
} }
else else
keepon = FALSE; s->keepon = FALSE;
if(!s->cl)
/* we did the full CONNECT treatment, go to COMPLETE */ /* we did the full CONNECT treatment, go to COMPLETE */
conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; s->tunnel_state = TUNNEL_COMPLETE;
continue; continue;
} }
line_start[perline] = 0; /* zero terminate the buffer */ s->line_start[s->perline] = 0; /* zero terminate the buffer */
if((checkprefix("WWW-Authenticate:", line_start) && if((checkprefix("WWW-Authenticate:", s->line_start) &&
(401 == k->httpcode)) || (401 == k->httpcode)) ||
(checkprefix("Proxy-authenticate:", line_start) && (checkprefix("Proxy-authenticate:", s->line_start) &&
(407 == k->httpcode))) { (407 == k->httpcode))) {
bool proxy = (k->httpcode == 407) ? TRUE : FALSE; bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
char *auth = Curl_copy_header_value(line_start); char *auth = Curl_copy_header_value(s->line_start);
if(!auth) if(!auth)
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
@ -498,7 +516,7 @@ static CURLcode CONNECT(struct connectdata *conn,
if(result) if(result)
return result; return result;
} }
else if(checkprefix("Content-Length:", line_start)) { else if(checkprefix("Content-Length:", s->line_start)) {
if(k->httpcode/100 == 2) { if(k->httpcode/100 == 2) {
/* A client MUST ignore any Content-Length or Transfer-Encoding /* A client MUST ignore any Content-Length or Transfer-Encoding
header fields received in a successful response to CONNECT. header fields received in a successful response to CONNECT.
@ -507,13 +525,13 @@ static CURLcode CONNECT(struct connectdata *conn,
k->httpcode); k->httpcode);
} }
else { else {
cl = curlx_strtoofft(line_start + s->cl = curlx_strtoofft(s->line_start +
strlen("Content-Length:"), NULL, 10); strlen("Content-Length:"), NULL, 10);
} }
} }
else if(Curl_compareheader(line_start, "Connection:", "close")) else if(Curl_compareheader(s->line_start, "Connection:", "close"))
closeConnection = TRUE; closeConnection = TRUE;
else if(checkprefix("Transfer-Encoding:", line_start)) { else if(checkprefix("Transfer-Encoding:", s->line_start)) {
if(k->httpcode/100 == 2) { if(k->httpcode/100 == 2) {
/* A client MUST ignore any Content-Length or Transfer-Encoding /* A client MUST ignore any Content-Length or Transfer-Encoding
header fields received in a successful response to CONNECT. header fields received in a successful response to CONNECT.
@ -521,7 +539,7 @@ static CURLcode CONNECT(struct connectdata *conn,
infof(data, "Ignoring Transfer-Encoding in " infof(data, "Ignoring Transfer-Encoding in "
"CONNECT %03d response\n", k->httpcode); "CONNECT %03d response\n", k->httpcode);
} }
else if(Curl_compareheader(line_start, else if(Curl_compareheader(s->line_start,
"Transfer-Encoding:", "chunked")) { "Transfer-Encoding:", "chunked")) {
infof(data, "CONNECT responded chunked\n"); infof(data, "CONNECT responded chunked\n");
chunked_encoding = TRUE; chunked_encoding = TRUE;
@ -529,18 +547,19 @@ static CURLcode CONNECT(struct connectdata *conn,
Curl_httpchunk_init(conn); Curl_httpchunk_init(conn);
} }
} }
else if(Curl_compareheader(line_start, "Proxy-Connection:", "close")) else if(Curl_compareheader(s->line_start,
"Proxy-Connection:", "close"))
closeConnection = TRUE; closeConnection = TRUE;
else if(2 == sscanf(line_start, "HTTP/1.%d %d", else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
&subversion, &subversion,
&k->httpcode)) { &k->httpcode)) {
/* store the HTTP code from the proxy */ /* store the HTTP code from the proxy */
data->info.httpproxycode = k->httpcode; data->info.httpproxycode = k->httpcode;
} }
perline = 0; /* line starts over here */ s->perline = 0; /* line starts over here */
ptr = conn->connect_buffer; s->ptr = s->connect_buffer;
line_start = ptr; s->line_start = s->ptr;
} /* while there's buffer left and loop is requested */ } /* while there's buffer left and loop is requested */
if(Curl_pgrsUpdate(conn)) if(Curl_pgrsUpdate(conn))
@ -574,11 +593,8 @@ static CURLcode CONNECT(struct connectdata *conn,
/* If we are supposed to continue and request a new URL, which basically /* If we are supposed to continue and request a new URL, which basically
* means the HTTP authentication is still going on so if the tunnel * means the HTTP authentication is still going on so if the tunnel
* is complete we start over in INIT state */ * is complete we start over in INIT state */
if(data->req.newurl && if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
(TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) { connect_init(conn, TRUE); /* reinit */
conn->tunnel_state[sockindex] = TUNNEL_INIT;
infof(data, "TUNNEL_STATE switched to: %d\n",
conn->tunnel_state[sockindex]);
} }
} while(data->req.newurl); } while(data->req.newurl);
@ -587,6 +603,7 @@ static CURLcode CONNECT(struct connectdata *conn,
if(closeConnection && data->req.newurl) { if(closeConnection && data->req.newurl) {
conn->bits.proxy_connect_closed = TRUE; conn->bits.proxy_connect_closed = TRUE;
infof(data, "Connect me again please\n"); infof(data, "Connect me again please\n");
connect_done(conn);
} }
else { else {
free(data->req.newurl); free(data->req.newurl);
@ -598,7 +615,7 @@ static CURLcode CONNECT(struct connectdata *conn,
} }
/* to back to init state */ /* to back to init state */
conn->tunnel_state[sockindex] = TUNNEL_INIT; s->tunnel_state = TUNNEL_INIT;
if(conn->bits.proxy_connect_closed) if(conn->bits.proxy_connect_closed)
/* this is not an error, just part of the connection negotiation */ /* this is not an error, just part of the connection negotiation */
@ -608,7 +625,7 @@ static CURLcode CONNECT(struct connectdata *conn,
return CURLE_RECV_ERROR; return CURLE_RECV_ERROR;
} }
conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; s->tunnel_state = TUNNEL_COMPLETE;
/* If a proxy-authorization header was used for the proxy, then we should /* If a proxy-authorization header was used for the proxy, then we should
make sure that it isn't accidentally used for the document request make sure that it isn't accidentally used for the document request
@ -625,6 +642,16 @@ static CURLcode CONNECT(struct connectdata *conn,
return CURLE_OK; return CURLE_OK;
} }
void Curl_connect_free(struct Curl_easy *data)
{
struct connectdata *conn = data->easy_conn;
struct http_connect_state *s = conn->connect_state;
if(s) {
free(s);
conn->connect_state = NULL;
}
}
/* /*
* Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
* function will issue the necessary commands to get a seamless tunnel through * function will issue the necessary commands to get a seamless tunnel through
@ -637,17 +664,15 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
int remote_port) int remote_port)
{ {
CURLcode result; CURLcode result;
if(TUNNEL_INIT == conn->tunnel_state[sockindex]) { if(!conn->connect_state) {
if(!conn->connect_buffer) { result = connect_init(conn, FALSE);
conn->connect_buffer = malloc(CONNECT_BUFFER_SIZE); if(result)
if(!conn->connect_buffer) return result;
return CURLE_OUT_OF_MEMORY;
}
} }
result = CONNECT(conn, sockindex, hostname, remote_port); result = CONNECT(conn, sockindex, hostname, remote_port);
if(result || (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) if(result || Curl_connect_complete(conn))
Curl_safefree(conn->connect_buffer); connect_done(conn);
return result; return result;
} }

View File

@ -33,6 +33,10 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex); CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex);
bool Curl_connect_complete(struct connectdata *conn);
bool Curl_connect_ongoing(struct connectdata *conn);
void Curl_connect_free(struct Curl_easy *data);
#else #else
#define Curl_proxyCONNECT(x,y,z,w) CURLE_NOT_BUILT_IN #define Curl_proxyCONNECT(x,y,z,w) CURLE_NOT_BUILT_IN
#define Curl_proxy_connect(x,y) CURLE_OK #define Curl_proxy_connect(x,y) CURLE_OK

View File

@ -44,6 +44,7 @@
#include "sigpipe.h" #include "sigpipe.h"
#include "vtls/vtls.h" #include "vtls/vtls.h"
#include "connect.h" #include "connect.h"
#include "http_proxy.h"
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
#include "curl_memory.h" #include "curl_memory.h"
@ -114,6 +115,13 @@ static void mstate(struct Curl_easy *data, CURLMstate state
NULL, NULL,
NULL, NULL,
Curl_init_CONNECT, /* CONNECT */ Curl_init_CONNECT, /* CONNECT */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
Curl_connect_free /* DO */
/* the rest is NULL too */ /* the rest is NULL too */
}; };
@ -826,7 +834,7 @@ static int waitproxyconnect_getsock(struct connectdata *conn,
/* when we've sent a CONNECT to a proxy, we should rather wait for the /* when we've sent a CONNECT to a proxy, we should rather wait for the
socket to become readable to be able to get the response headers */ socket to become readable to be able to get the response headers */
if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) if(conn->connect_state)
return GETSOCK_READSOCK(0); return GETSOCK_READSOCK(0);
return GETSOCK_WRITESOCK(0); return GETSOCK_WRITESOCK(0);
@ -1455,7 +1463,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
CURLM_STATE_WAITDO:CURLM_STATE_DO); CURLM_STATE_WAITDO:CURLM_STATE_DO);
else { else {
#ifndef CURL_DISABLE_HTTP #ifndef CURL_DISABLE_HTTP
if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) if(Curl_connect_ongoing(data->easy_conn))
multistate(data, CURLM_STATE_WAITPROXYCONNECT); multistate(data, CURLM_STATE_WAITPROXYCONNECT);
else else
#endif #endif
@ -1520,7 +1528,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
CURLM_STATE_WAITDO:CURLM_STATE_DO); CURLM_STATE_WAITDO:CURLM_STATE_DO);
else { else {
#ifndef CURL_DISABLE_HTTP #ifndef CURL_DISABLE_HTTP
if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) if(Curl_connect_ongoing(data->easy_conn))
multistate(data, CURLM_STATE_WAITPROXYCONNECT); multistate(data, CURLM_STATE_WAITPROXYCONNECT);
else else
#endif #endif
@ -1552,7 +1560,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
else if(!result) { else if(!result) {
if((data->easy_conn->http_proxy.proxytype != CURLPROXY_HTTPS || if((data->easy_conn->http_proxy.proxytype != CURLPROXY_HTTPS ||
data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) && data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) &&
(data->easy_conn->tunnel_state[FIRSTSOCKET] != TUNNEL_CONNECT)) { Curl_connect_complete(data->easy_conn)) {
rc = CURLM_CALL_MULTI_PERFORM; rc = CURLM_CALL_MULTI_PERFORM;
/* initiate protocol connect phase */ /* initiate protocol connect phase */
multistate(data, CURLM_STATE_SENDPROTOCONNECT); multistate(data, CURLM_STATE_SENDPROTOCONNECT);
@ -1568,7 +1576,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
#ifndef CURL_DISABLE_HTTP #ifndef CURL_DISABLE_HTTP
if((data->easy_conn->http_proxy.proxytype == CURLPROXY_HTTPS && if((data->easy_conn->http_proxy.proxytype == CURLPROXY_HTTPS &&
!data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) || !data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) ||
(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)) { Curl_connect_ongoing(data->easy_conn)) {
multistate(data, CURLM_STATE_WAITPROXYCONNECT); multistate(data, CURLM_STATE_WAITPROXYCONNECT);
break; break;
} }

View File

@ -3023,7 +3023,7 @@ static void conn_free(struct connectdata *conn)
Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */
Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */
Curl_safefree(conn->master_buffer); Curl_safefree(conn->master_buffer);
Curl_safefree(conn->connect_buffer); Curl_safefree(conn->connect_state);
conn_reset_all_postponed_data(conn); conn_reset_all_postponed_data(conn);
@ -4013,7 +4013,7 @@ CURLcode Curl_protocol_connect(struct connectdata *conn,
return CURLE_OK; return CURLE_OK;
if(conn->bits.tunnel_proxy && conn->bits.httpproxy && if(conn->bits.tunnel_proxy && conn->bits.httpproxy &&
(conn->tunnel_state[FIRSTSOCKET] != TUNNEL_COMPLETE)) Curl_connect_ongoing(conn))
/* when using an HTTP tunnel proxy, await complete tunnel establishment /* when using an HTTP tunnel proxy, await complete tunnel establishment
before proceeding further. Return CURLE_OK so we'll be called again */ before proceeding further. Return CURLE_OK so we'll be called again */
return CURLE_OK; return CURLE_OK;

View File

@ -892,6 +892,23 @@ struct proxy_info {
char *passwd; /* proxy password string, allocated */ char *passwd; /* proxy password string, allocated */
}; };
#define CONNECT_BUFFER_SIZE 16384
/* struct for HTTP CONNECT state data */
struct http_connect_state {
char connect_buffer[CONNECT_BUFFER_SIZE];
int perline; /* count bytes per line */
int keepon;
char *line_start;
char *ptr; /* where to store more data */
curl_off_t cl; /* size of content to read and ignore */
enum {
TUNNEL_INIT, /* init/default/no tunnel state */
TUNNEL_CONNECT, /* CONNECT has been sent off */
TUNNEL_COMPLETE /* CONNECT response received completely */
} tunnel_state;
};
/* /*
* The connectdata struct contains all fields and variables that should be * The connectdata struct contains all fields and variables that should be
* unique for an entire connection. * unique for an entire connection.
@ -1134,17 +1151,9 @@ struct connectdata {
char *localdev; char *localdev;
unsigned short localport; unsigned short localport;
int localportrange; int localportrange;
struct http_connect_state *connect_state; /* for HTTP CONNECT */
/* tunnel as in tunnel through a HTTP proxy with CONNECT */
enum {
TUNNEL_INIT, /* init/default/no tunnel state */
TUNNEL_CONNECT, /* CONNECT has been sent off */
TUNNEL_COMPLETE /* CONNECT response received completely */
} tunnel_state[2]; /* two separate ones to allow FTP */
struct connectbundle *bundle; /* The bundle we are member of */ struct connectbundle *bundle; /* The bundle we are member of */
int negnpn; /* APLN or NPN TLS negotiated protocol, CURL_HTTP_VERSION* */ int negnpn; /* APLN or NPN TLS negotiated protocol, CURL_HTTP_VERSION* */
char *connect_buffer; /* for CONNECT business */
#ifdef USE_UNIX_SOCKETS #ifdef USE_UNIX_SOCKETS
char *unix_domain_socket; char *unix_domain_socket;

View File

@ -15,7 +15,7 @@ HTTP proxy NTLM auth
<connect> <connect>
HTTP/1.1 407 Authorization Required to proxy me my dear swsclose HTTP/1.1 407 Authorization Required to proxy me my dear swsclose
Proxy-Authenticate: NTLM Proxy-Authenticate: NTLM
Content-Length: 21 Content-Length: 16
Connection: close Connection: close
data to discard data to discard
@ -62,7 +62,7 @@ Nice proxy auth sir!
<datacheck> <datacheck>
HTTP/1.1 407 Authorization Required to proxy me my dear swsclose HTTP/1.1 407 Authorization Required to proxy me my dear swsclose
Proxy-Authenticate: NTLM Proxy-Authenticate: NTLM
Content-Length: 21 Content-Length: 16
Connection: close Connection: close
HTTP/1.1 407 Authorization Required to proxy me my dear HTTP/1.1 407 Authorization Required to proxy me my dear