aboutsummaryrefslogtreecommitdiffstats
path: root/proxy/proxy_http_rewriter.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:32 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:32 -0800
commit8b23a6c7e1aee255004dd19098d4c2462b61b849 (patch)
tree7a4d682ba51f0ff0364c5ca2509f515bdaf96de9 /proxy/proxy_http_rewriter.c
parentf721e3ac031f892af46f255a47d7f54a91317b30 (diff)
downloadexternal_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.zip
external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.gz
external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'proxy/proxy_http_rewriter.c')
-rw-r--r--proxy/proxy_http_rewriter.c1125
1 files changed, 1125 insertions, 0 deletions
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 <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
+ **
+ **/
+
+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;
+}