blob: 6c43036631faf32fb88587856476ce066769c7e8 [file] [log] [blame]
/* 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 <stdio.h>
#include <string.h>
#include "qemu-common.h"
#include "android/utils/system.h" /* strsep */
/* this implements a transparent HTTP rewriting proxy
*
* this is needed because the HTTP spec mandates that
* any query made to a proxy uses an absolute URI as
* in:
*
* GET http://www.example.com/index.html HTTP/1.1
*
* while the Android browser will think it's talking to
* a normal web server and will issue a:
*
* GET /index.html HTTP/1.1
* Host: www.example.com
*
* what we do here is thus the following:
*
* - read the request header
* - rewrite the request's URI to use absolute URI
* - send the rewritten header to the proxy
* - then read the rest of the request, and tunnel it to the
* proxy as well
* - read the answer as-is and send it back to the system
*
* this sounds all easy, but the rules for computing the
* sizes of HTTP Message Bodies makes the implementation
* a *bit* funky.
*/
/* define D_ACTIVE to 1 to dump additionnal debugging
* info when -debug-proxy is used. These are only needed
* when debugging the proxy code.
*/
#define D_ACTIVE 1
#if D_ACTIVE
# define D(...) PROXY_LOG(__VA_ARGS__)
#else
# define D(...) ((void)0)
#endif
/** *************************************************************
**
** HTTP HEADERS
**
**/
/* HttpHeader is a simple structure used to hold a (key,value)
* pair in a linked list.
*/
typedef struct HttpHeader {
struct HttpHeader* next;
const char* key;
const char* value;
} HttpHeader;
static void
http_header_free( HttpHeader* h )
{
if (h) {
g_free((char*)h->value);
g_free(h);
}
}
static int
http_header_append( HttpHeader* h, const char* value )
{
int old = strlen(h->value);
int new = strlen(value);
char* s = realloc((char*)h->value, old+new+1);
if (s == NULL)
return -1;
memcpy(s + old, value, new+1);
h->value = (const char*)s;
return 0;
}
static HttpHeader*
http_header_alloc( const char* key, const char* value )
{
int len = strlen(key)+1;
HttpHeader* h = malloc(sizeof(*h) + len+1);
if (h) {
h->next = NULL;
h->key = (const char*)(h+1);
memcpy( (char*)h->key, key, len );
h->value = g_strdup(value);
}
return h;
}
/** *************************************************************
**
** HTTP HEADERS LIST
**
**/
typedef struct {
HttpHeader* first;
HttpHeader* last;
} HttpHeaderList;
static void
http_header_list_init( HttpHeaderList* l )
{
l->first = l->last = NULL;
}
static void
http_header_list_done( HttpHeaderList* l )
{
while (l->first) {
HttpHeader* h = l->first;
l->first = h->next;
http_header_free(h);
}
l->last = NULL;
}
static void
http_header_list_add( HttpHeaderList* l,
HttpHeader* h )
{
if (!l->first) {
l->first = h;
} else {
l->last->next = h;
}
h->next = NULL;
l->last = h;
}
static const char*
http_header_list_find( HttpHeaderList* l,
const char* key )
{
HttpHeader* h;
for (h = l->first; h; h = h->next)
if (!strcasecmp(h->key, key))
return h->value;
return NULL;
}
/** *************************************************************
**
** HTTP REQUEST AND REPLY
**
**/
typedef enum {
HTTP_REQUEST_UNSUPPORTED = 0,
HTTP_REQUEST_GET,
HTTP_REQUEST_HEAD,
HTTP_REQUEST_POST,
HTTP_REQUEST_PUT,
HTTP_REQUEST_DELETE,
} HttpRequestType;
/* HttpRequest is used both to store information about a specific
* request and the corresponding reply
*/
typedef struct {
HttpRequestType req_type; /* request type */
char* req_method; /* "GET", "POST", "HEAD", etc... */
char* req_uri; /* the request URI */
char* req_version; /* "HTTP/1.0" or "HTTP/1.1" */
char* rep_version; /* reply version string */
int rep_code; /* reply code as decimal */
char* rep_readable; /* human-friendly reply/error message */
HttpHeaderList headers[1]; /* headers */
} HttpRequest;
static HttpRequest*
http_request_alloc( const char* method,
const char* uri,
const char* version )
{
HttpRequest* r = malloc(sizeof(*r));
r->req_method = g_strdup(method);
r->req_uri = g_strdup(uri);
r->req_version = g_strdup(version);
r->rep_version = NULL;
r->rep_code = -1;
r->rep_readable = NULL;
if (!strcmp(method,"GET")) {
r->req_type = HTTP_REQUEST_GET;
} else if (!strcmp(method,"POST")) {
r->req_type = HTTP_REQUEST_POST;
} else if (!strcmp(method,"HEAD")) {
r->req_type = HTTP_REQUEST_HEAD;
} else if (!strcmp(method,"PUT")) {
r->req_type = HTTP_REQUEST_PUT;
} else if (!strcmp(method,"DELETE")) {
r->req_type = HTTP_REQUEST_DELETE;
} else
r->req_type = HTTP_REQUEST_UNSUPPORTED;
http_header_list_init(r->headers);
return r;
}
static void
http_request_replace_uri( HttpRequest* r,
const char* uri )
{
const char* old = r->req_uri;
r->req_uri = g_strdup(uri);
g_free((char*)old);
}
static void
http_request_free( HttpRequest* r )
{
if (r) {
http_header_list_done(r->headers);
g_free(r->req_method);
g_free(r->req_uri);
g_free(r->req_version);
g_free(r->rep_version);
g_free(r->rep_readable);
g_free(r);
}
}
static char*
http_request_find_header( HttpRequest* r,
const char* key )
{
return (char*)http_header_list_find(r->headers, key);
}
static int
http_request_add_header( HttpRequest* r,
const char* key,
const char* value )
{
HttpHeader* h = http_header_alloc(key,value);
if (h) {
http_header_list_add(r->headers, h);
return 0;
}
return -1;
}
static int
http_request_add_to_last_header( HttpRequest* r,
const char* line )
{
if (r->headers->last) {
return http_header_append( r->headers->last, line );
} else {
return -1;
}
}
static int
http_request_set_reply( HttpRequest* r,
const char* version,
const char* code,
const char* readable )
{
if (strcmp(version,"HTTP/1.0") && strcmp(version,"HTTP/1.1")) {
PROXY_LOG("%s: bad reply protocol: %s", __FUNCTION__, version);
return -1;
}
r->rep_code = atoi(code);
if (r->rep_code == 0) {
PROXY_LOG("%s: bad reply code: %d", __FUNCTION__, code);
return -1;
}
r->rep_version = g_strdup(version);
r->rep_readable = g_strdup(readable);
/* reset the list of headers */
http_header_list_done(r->headers);
return 0;
}
/** *************************************************************
**
** REWRITER CONNECTION
**
**/
typedef enum {
STATE_CONNECTING = 0,
STATE_CREATE_SOCKET_PAIR,
STATE_REQUEST_FIRST_LINE,
STATE_REQUEST_HEADERS,
STATE_REQUEST_SEND,
STATE_REQUEST_BODY,
STATE_REPLY_FIRST_LINE,
STATE_REPLY_HEADERS,
STATE_REPLY_SEND,
STATE_REPLY_BODY,
} ConnectionState;
/* root->socket is connected to the proxy server. while
* slirp_fd is connected to the slirp code through a
* socket_pair() we created for this specific purpose.
*/
typedef enum {
BODY_NONE = 0,
BODY_KNOWN_LENGTH,
BODY_UNTIL_CLOSE,
BODY_CHUNKED,
BODY_MODE_MAX
} BodyMode;
static const char* const body_mode_str[BODY_MODE_MAX] = {
"NONE", "KNOWN_LENGTH", "UNTIL_CLOSE", "CHUNKED"
};
enum {
CHUNK_HEADER, // Waiting for a chunk header + CR LF
CHUNK_DATA, // Waiting for chunk data
CHUNK_DATA_END, // Waiting for the CR LF after the chunk data
CHUNK_TRAILER // Waiting for the chunk trailer + CR LF
};
typedef struct {
ProxyConnection root[1];
int slirp_fd;
ConnectionState state;
HttpRequest* request;
BodyMode body_mode;
int64_t body_length;
int64_t body_total;
int64_t body_sent;
int64_t chunk_length;
int64_t chunk_total;
int chunk_state;
char body_has_data;
char body_is_full;
char body_is_closed;
char parse_chunk_header;
char parse_chunk_trailer;
} RewriteConnection;
static void
rewrite_connection_free( ProxyConnection* root )
{
RewriteConnection* conn = (RewriteConnection*)root;
if (conn->slirp_fd >= 0) {
socket_close(conn->slirp_fd);
conn->slirp_fd = -1;
}
http_request_free(conn->request);
proxy_connection_done(root);
g_free(conn);
}
static int
rewrite_connection_init( RewriteConnection* conn )
{
HttpService* service = (HttpService*) conn->root->service;
ProxyConnection* root = conn->root;
conn->slirp_fd = -1;
conn->state = STATE_CONNECTING;
if (socket_connect( root->socket, &service->server_addr ) < 0) {
if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) {
PROXY_LOG("%s: connecting", conn->root->name);
}
else {
PROXY_LOG("%s: cannot connect to proxy: %s", root->name, errno_str);
return -1;
}
}
else {
PROXY_LOG("%s: immediate connection", root->name);
conn->state = STATE_CREATE_SOCKET_PAIR;
}
return 0;
}
static int
rewrite_connection_create_sockets( RewriteConnection* conn )
{
/* immediate connection to the proxy. now create a socket
* pair and send a 'success' event to slirp */
int slirp_1;
ProxyConnection* root = conn->root;
if (socket_pair( &slirp_1, &conn->slirp_fd ) < 0) {
PROXY_LOG("%s: coult not create socket pair: %s",
root->name, errno_str);
return -1;
}
root->ev_func( root->ev_opaque, slirp_1, PROXY_EVENT_CONNECTED );
conn->state = STATE_REQUEST_FIRST_LINE;
return 0;
}
/* read the first line of a given HTTP request. returns -1/0/+1 */
static DataStatus
rewrite_connection_read_request( RewriteConnection* conn )
{
ProxyConnection* root = conn->root;
DataStatus ret;
ret = proxy_connection_receive_line(root, conn->slirp_fd);
if (ret == DATA_COMPLETED) {
/* now parse the first line to see if we can handle it */
char* line = root->str->s;
char* method;
char* uri;
char* version;
char* p = line;
method = strsep(&p, " ");
if (p == NULL) {
PROXY_LOG("%s: can't parse method in '%'",
root->name, line);
return DATA_ERROR;
}
uri = strsep(&p, " ");
if (p == NULL) {
PROXY_LOG( "%s: can't parse URI in '%s'",
root->name, line);
return DATA_ERROR;
}
version = strsep(&p, " ");
if (p != NULL) {
PROXY_LOG( "%s: extra data after version in '%s'",
root->name, line);
return DATA_ERROR;
}
if (conn->request)
http_request_free(conn->request);
conn->request = http_request_alloc( method, uri, version );
if (!conn->request)
return DATA_ERROR;
proxy_connection_rewind(root);
}
return ret;
}
static DataStatus
rewrite_connection_read_reply( RewriteConnection* conn )
{
ProxyConnection* root = conn->root;
DataStatus ret;
ret = proxy_connection_receive_line( root, root->socket );
if (ret == DATA_COMPLETED) {
HttpRequest* request = conn->request;
char* line = stralloc_cstr( root->str );
char* p = line;
char* protocol;
char* number;
char* readable;
protocol = strsep(&p, " ");
if (p == NULL) {
PROXY_LOG("%s: can't parse response protocol: '%s'",
root->name, line);
return DATA_ERROR;
}
number = strsep(&p, " ");
if (p == NULL) {
PROXY_LOG("%s: can't parse response number: '%s'",
root->name, line);
return DATA_ERROR;
}
readable = p;
if (http_request_set_reply(request, protocol, number, readable) < 0)
return DATA_ERROR;
proxy_connection_rewind(root);
}
return ret;
}
static DataStatus
rewrite_connection_read_headers( RewriteConnection* conn,
int fd )
{
int ret;
ProxyConnection* root = conn->root;
for (;;) {
char* line;
stralloc_t* str = root->str;
ret = proxy_connection_receive_line(root, fd);
if (ret != DATA_COMPLETED)
break;
str->n = 0;
line = str->s;
if (line[0] == 0) {
/* an empty line means the end of headers */
ret = 1;
break;
}
/* it this a continuation ? */
if (line[0] == ' ' || line[0] == '\t') {
ret = http_request_add_to_last_header( conn->request, line );
}
else {
char* key;
char* value;
value = line;
key = strsep(&value, ":");
if (value == NULL) {
PROXY_LOG("%s: can't parse header '%s'", root->name, line);
ret = -1;
break;
}
value += strspn(value, " ");
if (http_request_add_header(conn->request, key, value) < 0)
ret = -1;
}
if (ret == DATA_ERROR)
break;
}
return ret;
}
static int
rewrite_connection_rewrite_request( RewriteConnection* conn )
{
ProxyConnection* root = conn->root;
HttpService* service = (HttpService*) root->service;
HttpRequest* r = conn->request;
stralloc_t* str = root->str;
HttpHeader* h;
proxy_connection_rewind(conn->root);
/* only rewrite the URI if it is not absolute */
if (r->req_uri[0] == '/') {
char* host = http_request_find_header(r, "Host");
if (host == NULL) {
PROXY_LOG("%s: uh oh, not Host: in request ?", root->name);
} else {
/* now create new URI */
stralloc_add_str(str, "http://");
stralloc_add_str(str, host);
stralloc_add_str(str, r->req_uri);
http_request_replace_uri(r, stralloc_cstr(str));
proxy_connection_rewind(root);
}
}
stralloc_format( str, "%s %s %s\r\n", r->req_method, r->req_uri, r->req_version );
for (h = r->headers->first; h; h = h->next) {
stralloc_add_format( str, "%s: %s\r\n", h->key, h->value );
}
/* add the service's footer - includes final \r\n */
stralloc_add_bytes( str, service->footer, service->footer_len );
return 0;
}
static int
rewrite_connection_rewrite_reply( RewriteConnection* conn )
{
HttpRequest* r = conn->request;
ProxyConnection* root = conn->root;
stralloc_t* str = root->str;
HttpHeader* h;
proxy_connection_rewind(root);
stralloc_format(str, "%s %d %s\r\n", r->rep_version, r->rep_code, r->rep_readable);
for (h = r->headers->first; h; h = h->next) {
stralloc_add_format(str, "%s: %s\r\n", h->key, h->value);
}
stralloc_add_str(str, "\r\n");
return 0;
}
static int
rewrite_connection_get_body_length( RewriteConnection* conn,
int is_request )
{
HttpRequest* r = conn->request;
ProxyConnection* root = conn->root;
char* content_length;
char* transfer_encoding;
conn->body_mode = BODY_NONE;
conn->body_length = 0;
conn->body_total = 0;
conn->body_sent = 0;
conn->body_is_closed = 0;
conn->body_is_full = 0;
conn->body_has_data = 0;
proxy_connection_rewind(root);
if (is_request) {
/* only POST and PUT should have a body */
if (r->req_type != HTTP_REQUEST_POST &&
r->req_type != HTTP_REQUEST_PUT)
{
return 0;
}
} else {
/* HTTP 1.1 Section 4.3 Message Body states that HEAD requests must not have
* a message body, as well as any 1xx, 204 and 304 replies */
if (r->req_type == HTTP_REQUEST_HEAD || r->rep_code/100 == 1 ||
r->rep_code == 204 || r->rep_code == 304)
return 0;
}
content_length = http_request_find_header(r, "Content-Length");
if (content_length != NULL) {
char* end;
int64_t body_len = strtoll( content_length, &end, 10 );
if (*end != '\0' || *content_length == '\0' || body_len < 0) {
PROXY_LOG("%s: bad content length: %s", root->name, content_length);
return DATA_ERROR;
}
if (body_len > 0) {
conn->body_mode = BODY_KNOWN_LENGTH;
conn->body_length = body_len;
}
} else {
transfer_encoding = http_request_find_header(r, "Transfer-Encoding");
if (transfer_encoding && !strcasecmp(transfer_encoding, "Chunked")) {
conn->body_mode = BODY_CHUNKED;
conn->parse_chunk_header = 0;
conn->parse_chunk_trailer = 0;
conn->chunk_length = -1;
conn->chunk_total = 0;
conn->chunk_state = CHUNK_HEADER;
}
}
if (conn->body_mode == BODY_NONE) {
char* connection = http_request_find_header(r, "Proxy-Connection");
if (!connection)
connection = http_request_find_header(r, "Connection");
if (!connection || strcasecmp(connection, "Close")) {
/* hum, we can't support this at all */
PROXY_LOG("%s: can't determine content length, and client wants"
" to keep connection opened",
root->name);
return -1;
}
/* a negative value means that the data ends when the client
* disconnects the connection.
*/
conn->body_mode = BODY_UNTIL_CLOSE;
}
D("%s: body_length=%lld body_mode=%s",
root->name, conn->body_length,
body_mode_str[conn->body_mode]);
proxy_connection_rewind(root);
return 0;
}
#define MAX_BODY_BUFFER 65536
static DataStatus
rewrite_connection_read_body( RewriteConnection* conn, int fd )
{
ProxyConnection* root = conn->root;
stralloc_t* str = root->str;
int wanted = 0, current, avail;
DataStatus ret;
if (conn->body_is_closed) {
return DATA_NEED_MORE;
}
/* first, determine how many bytes we want to read. */
switch (conn->body_mode) {
case BODY_NONE:
D("%s: INTERNAL ERROR: SHOULDN'T BE THERE", root->name);
return DATA_COMPLETED;
case BODY_KNOWN_LENGTH:
{
if (conn->body_length == 0)
return DATA_COMPLETED;
if (conn->body_length > MAX_BODY_BUFFER)
wanted = MAX_BODY_BUFFER;
else
wanted = (int)conn->body_length;
}
break;
case BODY_UNTIL_CLOSE:
wanted = MAX_BODY_BUFFER;
break;
case BODY_CHUNKED:
if (conn->chunk_state == CHUNK_DATA_END) {
/* We're waiting for the CR LF after the chunk data */
ret = proxy_connection_receive_line(root, fd);
if (ret != DATA_COMPLETED)
return ret;
if (str->s[0] != 0) { /* this should be an empty line */
PROXY_LOG("%s: invalid chunk data end: '%s'",
root->name, str->s);
return DATA_ERROR;
}
/* proxy_connection_receive_line() did remove the
* trailing \r\n, but we must preserve it when we
* send the chunk size end to the proxy.
*/
stralloc_add_str(root->str, "\r\n");
conn->chunk_state = CHUNK_HEADER;
/* fall-through */
}
if (conn->chunk_state == CHUNK_HEADER) {
char* line;
char* end;
long long length;
/* Ensure that the previous chunk was flushed before
* accepting a new header */
if (!conn->parse_chunk_header) {
if (conn->body_has_data)
return DATA_NEED_MORE;
D("%s: waiting chunk header", root->name);
conn->parse_chunk_header = 1;
}
ret = proxy_connection_receive_line(root, fd);
if (ret != DATA_COMPLETED) {
return ret;
}
conn->parse_chunk_header = 0;
line = str->s;
length = strtoll(line, &end, 16);
if (line[0] == ' ' || (end[0] != '\0' && end[0] != ';')) {
PROXY_LOG("%s: invalid chunk header: %s",
root->name, line);
return DATA_ERROR;
}
if (length < 0) {
PROXY_LOG("%s: invalid chunk length %lld",
root->name, length);
return DATA_ERROR;
}
/* proxy_connection_receive_line() did remove the
* trailing \r\n, but we must preserve it when we
* send the chunk size to the proxy.
*/
stralloc_add_str(root->str, "\r\n");
conn->chunk_length = length;
conn->chunk_total = 0;
conn->chunk_state = CHUNK_DATA;
if (length == 0) {
/* the last chunk, no we need to add the trailer */
conn->chunk_state = CHUNK_TRAILER;
conn->parse_chunk_trailer = 0;
}
}
if (conn->chunk_state == CHUNK_TRAILER) {
/* ensure that 'str' is flushed before reading the trailer */
if (!conn->parse_chunk_trailer) {
if (conn->body_has_data)
return DATA_NEED_MORE;
conn->parse_chunk_trailer = 1;
}
ret = rewrite_connection_read_headers(conn, fd);
if (ret == DATA_COMPLETED) {
conn->body_is_closed = 1;
}
return ret;
}
/* if we get here, body_length > 0 */
if (conn->chunk_length > MAX_BODY_BUFFER)
wanted = MAX_BODY_BUFFER;
else
wanted = (int)conn->chunk_length;
break;
default:
;
}
/* we don't want more than MAX_BODY_BUFFER bytes in the
* buffer we used to pass the body */
current = str->n;
avail = MAX_BODY_BUFFER - current;
if (avail <= 0) {
/* wait for some flush */
conn->body_is_full = 1;
D("%s: waiting to flush %d bytes",
root->name, current);
return DATA_NEED_MORE;
}
if (wanted > avail)
wanted = avail;
ret = proxy_connection_receive(root, fd, wanted);
conn->body_has_data = (str->n > 0);
conn->body_is_full = (str->n == MAX_BODY_BUFFER);
if (ret == DATA_ERROR) {
if (conn->body_mode == BODY_UNTIL_CLOSE) {
/* a disconnection here is normal and signals the
* end of the body */
conn->body_total += root->str_recv;
D("%s: body completed by close (%lld bytes)",
root->name, conn->body_total);
conn->body_is_closed = 1;
ret = DATA_COMPLETED;
}
} else {
avail = root->str_recv;
ret = DATA_NEED_MORE; /* we're not really done yet */
switch (conn->body_mode) {
case BODY_CHUNKED:
conn->chunk_total += avail;
conn->chunk_length -= avail;
if (conn->chunk_length == 0) {
D("%s: chunk completed (%lld bytes)",
root->name, conn->chunk_total);
conn->body_total += conn->chunk_total;
conn->chunk_total = 0;
conn->chunk_length = -1;
conn->chunk_state = CHUNK_DATA;
}
break;
case BODY_KNOWN_LENGTH:
conn->body_length -= avail;
conn->body_total += avail;
if (conn->body_length == 0) {
D("%s: body completed (%lld bytes)",
root->name, conn->body_total);
conn->body_is_closed = 1;
ret = DATA_COMPLETED;
}
break;
case BODY_UNTIL_CLOSE:
conn->body_total += avail;
break;
default:
;
}
}
return ret;
}
static DataStatus
rewrite_connection_send_body( RewriteConnection* conn, int fd )
{
ProxyConnection* root = conn->root;
stralloc_t* str = root->str;
DataStatus ret = DATA_NEED_MORE;
if (conn->body_has_data) {
ret = proxy_connection_send(root, fd);
if (ret != DATA_ERROR) {
int pos = root->str_pos;
memmove(str->s, str->s+pos, str->n-pos);
str->n -= pos;
root->str_pos = 0;
conn->body_is_full = (str->n == MAX_BODY_BUFFER);
conn->body_has_data = (str->n > 0);
conn->body_sent += root->str_sent;
/* ensure that we return DATA_COMPLETED only when
* we have sent everything, and there is no more
* body pieces to read */
if (ret == DATA_COMPLETED) {
if (!conn->body_is_closed || conn->body_has_data)
ret = DATA_NEED_MORE;
else {
D("%s: sent all body (%lld bytes)",
root->name, conn->body_sent);
}
}
D("%s: sent closed=%d data=%d n=%d ret=%d",
root->name, conn->body_is_closed,
conn->body_has_data, str->n,
ret);
}
}
return ret;
}
static void
rewrite_connection_select( ProxyConnection* root,
ProxySelect* sel )
{
RewriteConnection* conn = (RewriteConnection*)root;
int slirp = conn->slirp_fd;
int proxy = root->socket;
switch (conn->state) {
case STATE_CONNECTING:
case STATE_CREATE_SOCKET_PAIR:
/* try to connect to the proxy server */
proxy_select_set( sel, proxy, PROXY_SELECT_WRITE );
break;
case STATE_REQUEST_FIRST_LINE:
case STATE_REQUEST_HEADERS:
proxy_select_set( sel, slirp, PROXY_SELECT_READ );
break;
case STATE_REQUEST_SEND:
proxy_select_set( sel, proxy, PROXY_SELECT_WRITE );
break;
case STATE_REQUEST_BODY:
if (!conn->body_is_closed && !conn->body_is_full)
proxy_select_set( sel, slirp, PROXY_SELECT_READ );
if (conn->body_has_data)
proxy_select_set( sel, proxy, PROXY_SELECT_WRITE );
break;
case STATE_REPLY_FIRST_LINE:
case STATE_REPLY_HEADERS:
proxy_select_set( sel, proxy, PROXY_SELECT_READ );
break;
case STATE_REPLY_SEND:
proxy_select_set( sel, slirp, PROXY_SELECT_WRITE );
break;
case STATE_REPLY_BODY:
if (conn->body_has_data)
proxy_select_set( sel, slirp, PROXY_SELECT_WRITE );
if (!conn->body_is_closed && !conn->body_is_full)
proxy_select_set( sel, proxy, PROXY_SELECT_READ );
break;
default:
;
};
}
static void
rewrite_connection_poll( ProxyConnection* root,
ProxySelect* sel )
{
RewriteConnection* conn = (RewriteConnection*)root;
int slirp = conn->slirp_fd;
int proxy = root->socket;
int has_slirp = proxy_select_poll(sel, slirp);
int has_proxy = proxy_select_poll(sel, proxy);
DataStatus ret = DATA_NEED_MORE;
switch (conn->state) {
case STATE_CONNECTING:
if (has_proxy) {
PROXY_LOG("%s: connected to proxy", root->name);
conn->state = STATE_CREATE_SOCKET_PAIR;
}
break;
case STATE_CREATE_SOCKET_PAIR:
if (has_proxy) {
if (rewrite_connection_create_sockets(conn) < 0) {
ret = DATA_ERROR;
} else {
D("%s: socket pair created", root->name);
conn->state = STATE_REQUEST_FIRST_LINE;
}
}
break;
case STATE_REQUEST_FIRST_LINE:
if (has_slirp) {
ret = rewrite_connection_read_request(conn);
if (ret == DATA_COMPLETED) {
PROXY_LOG("%s: request first line ok", root->name);
conn->state = STATE_REQUEST_HEADERS;
}
}
break;
case STATE_REQUEST_HEADERS:
if (has_slirp) {
ret = rewrite_connection_read_headers(conn, slirp);
if (ret == DATA_COMPLETED) {
PROXY_LOG("%s: request headers ok", root->name);
if (rewrite_connection_rewrite_request(conn) < 0)
ret = DATA_ERROR;
else
conn->state = STATE_REQUEST_SEND;
}
}
break;
case STATE_REQUEST_SEND:
if (has_proxy) {
ret = proxy_connection_send(root, proxy);
if (ret == DATA_COMPLETED) {
if (rewrite_connection_get_body_length(conn, 1) < 0) {
ret = DATA_ERROR;
} else if (conn->body_mode != BODY_NONE) {
PROXY_LOG("%s: request sent, waiting for body",
root->name);
conn->state = STATE_REQUEST_BODY;
} else {
PROXY_LOG("%s: request sent, waiting for reply",
root->name);
conn->state = STATE_REPLY_FIRST_LINE;
}
}
}
break;
case STATE_REQUEST_BODY:
if (has_slirp) {
ret = rewrite_connection_read_body(conn, slirp);
}
if (ret != DATA_ERROR && has_proxy) {
ret = rewrite_connection_send_body(conn, proxy);
if (ret == DATA_COMPLETED) {
PROXY_LOG("%s: request body ok, waiting for reply",
root->name);
conn->state = STATE_REPLY_FIRST_LINE;
}
}
break;
case STATE_REPLY_FIRST_LINE:
if (has_proxy) {
ret = rewrite_connection_read_reply(conn);
if (ret == DATA_COMPLETED) {
PROXY_LOG("%s: reply first line ok", root->name);
conn->state = STATE_REPLY_HEADERS;
}
}
break;
case STATE_REPLY_HEADERS:
if (has_proxy) {
ret = rewrite_connection_read_headers(conn, proxy);
if (ret == DATA_COMPLETED) {
PROXY_LOG("%s: reply headers ok", root->name);
if (rewrite_connection_rewrite_reply(conn) < 0)
ret = DATA_ERROR;
else
conn->state = STATE_REPLY_SEND;
}
}
break;
case STATE_REPLY_SEND:
if (has_slirp) {
ret = proxy_connection_send(conn->root, slirp);
if (ret == DATA_COMPLETED) {
if (rewrite_connection_get_body_length(conn, 0) < 0) {
ret = DATA_ERROR;
} else if (conn->body_mode != BODY_NONE) {
PROXY_LOG("%s: reply sent, waiting for body",
root->name);
conn->state = STATE_REPLY_BODY;
} else {
PROXY_LOG("%s: reply sent, looping to waiting request",
root->name);
conn->state = STATE_REQUEST_FIRST_LINE;
}
}
}
break;
case STATE_REPLY_BODY:
if (has_proxy) {
ret = rewrite_connection_read_body(conn, proxy);
}
if (ret != DATA_ERROR && has_slirp) {
ret = rewrite_connection_send_body(conn, slirp);
if (ret == DATA_COMPLETED) {
if (conn->body_mode == BODY_UNTIL_CLOSE) {
PROXY_LOG("%s: closing connection", root->name);
ret = DATA_ERROR;
} else {
PROXY_LOG("%s: reply body ok, looping to waiting request",
root->name);
conn->state = STATE_REQUEST_FIRST_LINE;
}
}
}
break;
default:
;
}
if (ret == DATA_ERROR)
proxy_connection_free(root, 0, PROXY_EVENT_NONE);
return;
}
ProxyConnection*
http_rewriter_connect( HttpService* service,
SockAddress* address )
{
RewriteConnection* conn;
int s;
s = socket_create(address->family, SOCKET_STREAM );
if (s < 0)
return NULL;
conn = g_malloc0(sizeof(*conn));
if (conn == NULL) {
socket_close(s);
return NULL;
}
proxy_connection_init( conn->root, s, address, service->root,
rewrite_connection_free,
rewrite_connection_select,
rewrite_connection_poll );
if ( rewrite_connection_init( conn ) < 0 ) {
rewrite_connection_free( conn->root );
return NULL;
}
return conn->root;
}