/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "proxy_http_int.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "qemu-common.h"
/* A HttpConnector implements a non-HTTP proxied connection
* through the CONNECT method. Many firewalls are configured
* to reject these for port 80, so these connections should
* use a HttpRewriter instead.
*/
typedef enum {
STATE_NONE = 0,
STATE_CONNECTING, /* connecting to the server */
STATE_SEND_HEADER, /* connected, sending header to the server */
STATE_RECEIVE_ANSWER_LINE1,
STATE_RECEIVE_ANSWER_LINE2 /* connected, reading server's answer */
} ConnectorState;
typedef struct Connection {
ProxyConnection root[1];
ConnectorState state;
} Connection;
static void
connection_free( ProxyConnection* root )
{
proxy_connection_done(root);
qemu_free(root);
}
#define HTTP_VERSION "1.1"
static int
connection_init( Connection* conn )
{
HttpService* service = (HttpService*) conn->root->service;
ProxyConnection* root = conn->root;
stralloc_t* str = root->str;
proxy_connection_rewind(root);
stralloc_add_format(str, "CONNECT %s HTTP/" HTTP_VERSION "\r\n",
sock_address_to_string(&root->address));
stralloc_add_bytes(str, service->footer, service->footer_len);
if (!socket_connect( root->socket, &service->server_addr )) {
/* immediate connection ?? */
conn->state = STATE_SEND_HEADER;
PROXY_LOG("%s: immediate connection", root->name);
}
else {
if (errno == EINPROGRESS || errno == EWOULDBLOCK) {
conn->state = STATE_CONNECTING;
PROXY_LOG("%s: connecting", root->name);
}
else {
PROXY_LOG("%s: cannot connect to proxy: %s", root->name, errno_str);
return -1;
}
}
return 0;
}
static void
connection_select( ProxyConnection* root,
ProxySelect* sel )
{
unsigned flags;
Connection* conn = (Connection*)root;
switch (conn->state) {
case STATE_RECEIVE_ANSWER_LINE1:
case STATE_RECEIVE_ANSWER_LINE2:
flags = PROXY_SELECT_READ;
break;
case STATE_CONNECTING:
case STATE_SEND_HEADER:
flags = PROXY_SELECT_WRITE;
break;
default:
flags = 0;
};
proxy_select_set(sel, root->socket, flags);
}
static void
connection_poll( ProxyConnection* root,
ProxySelect* sel )
{
DataStatus ret = DATA_NEED_MORE;
Connection* conn = (Connection*)root;
int fd = root->socket;
if (!proxy_select_poll(sel, fd))
return;
switch (conn->state)
{
case STATE_CONNECTING:
PROXY_LOG("%s: connected to http proxy, sending header", root->name);
conn->state = STATE_SEND_HEADER;
break;
case STATE_SEND_HEADER:
ret = proxy_connection_send(root, fd);
if (ret == DATA_COMPLETED) {
conn->state = STATE_RECEIVE_ANSWER_LINE1;
PROXY_LOG("%s: header sent, receiving first answer line", root->name);
}
break;
case STATE_RECEIVE_ANSWER_LINE1:
case STATE_RECEIVE_ANSWER_LINE2:
ret = proxy_connection_receive_line(root, root->socket);
if (ret == DATA_COMPLETED) {
if (conn->state == STATE_RECEIVE_ANSWER_LINE1) {
int http1, http2, codenum;
const char* line = root->str->s;
if ( sscanf(line, "HTTP/%d.%d %d", &http1, &http2, &codenum) != 3 ) {
PROXY_LOG( "%s: invalid answer from proxy: '%s'",
root->name, line );
ret = DATA_ERROR;
break;
}
/* success is 2xx */
if (codenum/2 != 100) {
PROXY_LOG( "%s: connection refused, error=%d",
root->name, codenum );
proxy_connection_free( root, 0, PROXY_EVENT_CONNECTION_REFUSED );
return;
}
PROXY_LOG("%s: receiving second answer line", root->name);
conn->state = STATE_RECEIVE_ANSWER_LINE2;
proxy_connection_rewind(root);
} else {
/* ok, we're connected */
PROXY_LOG("%s: connection succeeded", root->name);
proxy_connection_free( root, 1, PROXY_EVENT_CONNECTED );
}
}
break;
default:
PROXY_LOG("%s: invalid state for read event: %d", root->name, conn->state);
}
if (ret == DATA_ERROR) {
proxy_connection_free( root, 0, PROXY_EVENT_SERVER_ERROR );
}
}
ProxyConnection*
http_connector_connect( HttpService* service,
SockAddress* address )
{
Connection* conn;
int s;
s = socket_create_inet( SOCKET_STREAM );
if (s < 0)
return NULL;
conn = qemu_mallocz(sizeof(*conn));
if (conn == NULL) {
socket_close(s);
return NULL;
}
proxy_connection_init( conn->root, s, address, service->root,
connection_free,
connection_select,
connection_poll );
if ( connection_init( conn ) < 0 ) {
connection_free( conn->root );
return NULL;
}
return conn->root;
}