#include "setup.h"
#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "urldata.h"
#include <curl/curl.h>
#include "http_proxy.h"
#include "sendf.h"
#include "http.h"
#include "url.h"
#include "select.h"
#include "rawstr.h"
#include "progress.h"
#include "non-ascii.h"
#include "connect.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#include "curlx.h"
#include "memdebug.h"
CURLcode Curl_proxyCONNECT(struct connectdata *conn,
int sockindex,
const char *hostname,
unsigned short remote_port)
{
int subversion=0;
struct SessionHandle *data=conn->data;
struct SingleRequest *k = &data->req;
CURLcode result;
long timeout =
data->set.timeout?data->set.timeout:PROXY_TIMEOUT;
curl_socket_t tunnelsocket = conn->sock[sockindex];
curl_off_t cl=0;
bool closeConnection = FALSE;
bool chunked_encoding = FALSE;
long check;
#define SELECT_OK 0
#define SELECT_ERROR 1
#define SELECT_TIMEOUT 2
int error = SELECT_OK;
conn->bits.proxy_connect_closed = FALSE;
do {
if(!conn->bits.tunnel_connecting) {
char *host_port;
Curl_send_buffer *req_buffer;
infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
hostname, remote_port);
if(data->req.newurl) {
free(data->req.newurl);
data->req.newurl = NULL;
}
req_buffer = Curl_add_buffer_init();
if(!req_buffer)
return CURLE_OUT_OF_MEMORY;
host_port = aprintf("%s:%hu", hostname, remote_port);
if(!host_port) {
free(req_buffer);
return CURLE_OUT_OF_MEMORY;
}
result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
if(CURLE_OK == result) {
char *host=(char *)"";
const char *proxyconn="";
const char *useragent="";
const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
"1.0" : "1.1";
if(!Curl_checkheaders(data, "Host:")) {
host = aprintf("Host: %s\r\n", host_port);
if(!host) {
free(req_buffer);
free(host_port);
return CURLE_OUT_OF_MEMORY;
}
}
if(!Curl_checkheaders(data, "Proxy-Connection:"))
proxyconn = "Proxy-Connection: Keep-Alive\r\n";
if(!Curl_checkheaders(data, "User-Agent:") &&
data->set.str[STRING_USERAGENT])
useragent = conn->allocptr.uagent;
result =
Curl_add_bufferf(req_buffer,
"CONNECT %s:%hu HTTP/%s\r\n"
"%s"
"%s"
"%s"
"%s",
hostname, remote_port, http,
host,
conn->allocptr.proxyuserpwd?
conn->allocptr.proxyuserpwd:"",
useragent,
proxyconn);
if(host && *host)
free(host);
if(CURLE_OK == result)
result = Curl_add_custom_headers(conn, req_buffer);
if(CURLE_OK == result)
result = Curl_add_bufferf(req_buffer, "\r\n");
if(CURLE_OK == result) {
result =
Curl_add_buffer_send(req_buffer, conn,
&data->info.request_size, 0, sockindex);
}
req_buffer = NULL;
if(result)
failf(data, "Failed sending CONNECT to proxy");
}
free(host_port);
Curl_safefree(req_buffer);
if(result)
return result;
conn->bits.tunnel_connecting = TRUE;
}
check = timeout -
Curl_tvdiff(Curl_tvnow(), conn->now);
if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout");
return CURLE_RECV_ERROR;
}
if(Curl_if_multi == data->state.used_interface) {
if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
return CURLE_OK;
else {
DEBUGF(infof(data,
"Multi mode finished polling for response from "
"proxy CONNECT\n"));
}
}
else {
DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT\n"));
}
conn->bits.tunnel_connecting = FALSE;
{
size_t nread;
int perline;
int keepon=TRUE;
ssize_t gotbytes;
char *ptr;
char *line_start;
ptr=data->state.buffer;
line_start = ptr;
nread=0;
perline=0;
keepon=TRUE;
while((nread<BUFSIZE) && (keepon && !error)) {
check = timeout -
Curl_tvdiff(Curl_tvnow(), conn->now);
if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout");
error = SELECT_TIMEOUT;
break;
}
switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
check<1000L?check:1000)) {
case -1:
error = SELECT_ERROR;
failf(data, "Proxy CONNECT aborted due to select/poll error");
break;
case 0:
break;
default:
DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
&gotbytes);
if(result==CURLE_AGAIN)
continue;
else if(result)
keepon = FALSE;
else if(gotbytes <= 0) {
keepon = FALSE;
if(data->set.proxyauth && data->state.authproxy.avail) {
conn->bits.proxy_connect_closed = TRUE;
}
else {
error = SELECT_ERROR;
failf(data, "Proxy CONNECT aborted");
}
}
else {
int i;
nread += gotbytes;
if(keepon > TRUE) {
nread = 0;
ptr=data->state.buffer;
if(cl) {
cl -= gotbytes;
if(cl<=0) {
keepon = FALSE;
break;
}
}
else {
CHUNKcode r;
ssize_t tookcareof=0;
r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
if(r == CHUNKE_STOP) {
infof(data, "chunk reading DONE\n");
keepon = FALSE;
}
else
infof(data, "Read %zd bytes of chunk, continue\n",
tookcareof);
}
}
else
for(i = 0; i < gotbytes; ptr++, i++) {
perline++;
if(*ptr == 0x0a) {
char letter;
int writetype;
result = Curl_convert_from_network(data, line_start,
perline);
if(result)
return result;
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN,
line_start, (size_t)perline, conn);
writetype = CLIENTWRITE_HEADER;
if(data->set.include_header)
writetype |= CLIENTWRITE_BODY;
result = Curl_client_write(conn, writetype, line_start,
perline);
if(result)
return result;
if(('\r' == line_start[0]) ||
('\n' == line_start[0])) {
nread = 0;
ptr=data->state.buffer;
if((407 == k->httpcode) && !data->state.authproblem) {
keepon = 2;
if(cl) {
infof(data, "Ignore %" FORMAT_OFF_T
" bytes of response-body\n", cl);
cl -= (gotbytes - i);
if(cl<=0)
keepon=FALSE;
}
else if(chunked_encoding) {
CHUNKcode r;
k->ignorebody = TRUE;
infof(data, "%zd bytes of chunk left\n", gotbytes-i);
if(line_start[1] == '\n') {
line_start++;
i++;
}
r = Curl_httpchunk_read(conn, line_start+1,
gotbytes -i, &gotbytes);
if(r == CHUNKE_STOP) {
infof(data, "chunk reading DONE\n");
keepon = FALSE;
}
else
infof(data, "Read %zd bytes of chunk, continue\n",
gotbytes);
}
else {
keepon=FALSE;
}
}
else {
keepon = FALSE;
if(200 == data->info.httpproxycode) {
if(gotbytes - (i+1))
failf(data, "Proxy CONNECT followed by %zd bytes "
"of opaque data. Data ignored (known bug #39)",
gotbytes - (i+1));
}
}
break;
}
letter = line_start[perline];
line_start[perline]=0;
if((checkprefix("WWW-Authenticate:", line_start) &&
(401 == k->httpcode)) ||
(checkprefix("Proxy-authenticate:", line_start) &&
(407 == k->httpcode))) {
result = Curl_http_input_auth(conn, k->httpcode,
line_start);
if(result)
return result;
}
else if(checkprefix("Content-Length:", line_start)) {
cl = curlx_strtoofft(line_start +
strlen("Content-Length:"), NULL, 10);
}
else if(Curl_compareheader(line_start,
"Connection:", "close"))
closeConnection = TRUE;
else if(Curl_compareheader(line_start,
"Transfer-Encoding:",
"chunked")) {
infof(data, "CONNECT responded chunked\n");
chunked_encoding = TRUE;
Curl_httpchunk_init(conn);
}
else if(Curl_compareheader(line_start,
"Proxy-Connection:", "close"))
closeConnection = TRUE;
else if(2 == sscanf(line_start, "HTTP/1.%d %d",
&subversion,
&k->httpcode)) {
data->info.httpproxycode = k->httpcode;
}
line_start[perline]= letter;
perline=0;
line_start = ptr+1;
}
}
}
break;
}
if(Curl_pgrsUpdate(conn))
return CURLE_ABORTED_BY_CALLBACK;
}
if(error)
return CURLE_RECV_ERROR;
if(data->info.httpproxycode != 200) {
result = Curl_http_auth_act(conn);
if(result)
return result;
if(conn->bits.close)
closeConnection = TRUE;
}
if(closeConnection && data->req.newurl) {
Curl_closesocket(conn, conn->sock[sockindex]);
conn->sock[sockindex] = CURL_SOCKET_BAD;
break;
}
}
} while(data->req.newurl);
if(200 != data->req.httpcode) {
failf(data, "Received HTTP code %d from proxy after CONNECT",
data->req.httpcode);
if(closeConnection && data->req.newurl)
conn->bits.proxy_connect_closed = TRUE;
return CURLE_RECV_ERROR;
}
Curl_safefree(conn->allocptr.proxyuserpwd);
conn->allocptr.proxyuserpwd = NULL;
data->state.authproxy.done = TRUE;
infof (data, "Proxy replied OK to CONNECT request\n");
data->req.ignorebody = FALSE;
return CURLE_OK;
}
#endif