| /* 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; |
| } |