From 8b23a6c7e1aee255004dd19098d4c2462b61b849 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:30:32 -0800 Subject: auto import from //depot/cupcake/@135843 --- proxy/proxy_http_rewriter.c | 1125 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1125 insertions(+) create mode 100644 proxy/proxy_http_rewriter.c (limited to 'proxy/proxy_http_rewriter.c') diff --git a/proxy/proxy_http_rewriter.c b/proxy/proxy_http_rewriter.c new file mode 100644 index 0000000..812bf9c --- /dev/null +++ b/proxy/proxy_http_rewriter.c @@ -0,0 +1,1125 @@ +/* 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 +#include +#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 + ** + **/ + +typedef struct HttpHeader { + struct HttpHeader* next; + const char* key; + const char* value; +} HttpHeader; + +static void +http_header_free( HttpHeader* h ) +{ + if (h) { + qemu_free((char*)h->value); + qemu_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 = qemu_strdup(value); + } + return h; +} + +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; + +typedef struct { + HttpRequestType req_type; + char* req_method; + char* req_uri; + char* req_version; + char* rep_version; + int rep_code; + char* rep_readable; + HttpHeaderList headers[1]; +} HttpRequest; + + +static HttpRequest* +http_request_alloc( const char* method, + const char* uri, + const char* version ) +{ + HttpRequest* r = malloc(sizeof(*r)); + + r->req_method = qemu_strdup(method); + r->req_uri = qemu_strdup(uri); + r->req_version = qemu_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 = qemu_strdup(uri); + qemu_free((char*)old); +} + +static void +http_request_free( HttpRequest* r ) +{ + if (r) { + http_header_list_done(r->headers); + + qemu_free(r->req_method); + qemu_free(r->req_uri); + qemu_free(r->req_version); + qemu_free(r->rep_version); + qemu_free(r->rep_readable); + qemu_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 = qemu_strdup(version); + r->rep_readable = qemu_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" +}; + +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; + 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); + qemu_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) { + 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 { + 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; + } + 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; + } + 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_length < 0) { + /* chunk_length < 0 means we need to read a chunk header */ + /* ensure that 'str' is flushed before doing this */ + 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) { + char* line = str->s; + char* end; + long long length; + + 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; + } + conn->chunk_length = length; + conn->chunk_total = 0; + if (length == 0) { + /* the last chunk, no we need to add the trailer */ + conn->parse_chunk_trailer = 0; + } + conn->parse_chunk_header = 0; + } + } + + if (conn->chunk_length == 0) { + /* chunk_length == 0 means we're reading the 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_length); + conn->body_total += conn->chunk_total; + conn->chunk_total = 0; + conn->chunk_length = -1; + } + 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_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, + 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; +} -- cgit v1.1