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_common.c | 532 ++++++++++++++++++++ proxy/proxy_common.h | 88 ++++ proxy/proxy_http.c | 186 +++++++ proxy/proxy_http.h | 24 + proxy/proxy_http_connector.c | 203 ++++++++ proxy/proxy_http_int.h | 38 ++ proxy/proxy_http_rewriter.c | 1125 ++++++++++++++++++++++++++++++++++++++++++ proxy/proxy_int.h | 201 ++++++++ 8 files changed, 2397 insertions(+) create mode 100644 proxy/proxy_common.c create mode 100644 proxy/proxy_common.h create mode 100644 proxy/proxy_http.c create mode 100644 proxy/proxy_http.h create mode 100644 proxy/proxy_http_connector.c create mode 100644 proxy/proxy_http_int.h create mode 100644 proxy/proxy_http_rewriter.c create mode 100644 proxy/proxy_int.h (limited to 'proxy') diff --git a/proxy/proxy_common.c b/proxy/proxy_common.c new file mode 100644 index 0000000..7794a62 --- /dev/null +++ b/proxy/proxy_common.c @@ -0,0 +1,532 @@ +/* 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_int.h" +#include "sockets.h" +#include +#include +#include +#include +#include "android/utils/misc.h" +#include "android/utils/system.h" +#include + +int proxy_log = 0; + +void +proxy_LOG(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +void +proxy_set_verbose(int mode) +{ + proxy_log = mode; +} + +/** Global connection list + **/ + +static ProxyConnection s_connections[1]; + +#define MAX_HEX_DUMP 512 + +static void +hex_dump( void* base, int size, const char* prefix ) +{ + STRALLOC_DEFINE(s); + if (size > MAX_HEX_DUMP) + size = MAX_HEX_DUMP; + stralloc_add_hexdump(s, base, size, prefix); + proxy_LOG( "%s", stralloc_cstr(s) ); + stralloc_reset(s); +} + +void +proxy_connection_init( ProxyConnection* conn, + int socket, + SockAddress* address, + ProxyService* service, + ProxyConnectionFreeFunc conn_free, + ProxyConnectionSelectFunc conn_select, + ProxyConnectionPollFunc conn_poll ) +{ + conn->socket = socket; + conn->address = address[0]; + conn->service = service; + conn->next = NULL; + + conn->conn_free = conn_free; + conn->conn_select = conn_select; + conn->conn_poll = conn_poll; + + socket_set_nonblock(socket); + + { + SocketType type = socket_get_type(socket); + + snprintf( conn->name, sizeof(conn->name), + "%s:%s(%d)", + (type == SOCKET_STREAM) ? "tcp" : "udp", + sock_address_to_string(address), socket ); + + /* just in case */ + conn->name[sizeof(conn->name)-1] = 0; + } + + stralloc_reset(conn->str); + conn->str_pos = 0; +} + +void +proxy_connection_done( ProxyConnection* conn ) +{ + stralloc_reset( conn->str ); + if (conn->socket >= 0) { + socket_close(conn->socket); + conn->socket = -1; + } +} + + +void +proxy_connection_rewind( ProxyConnection* conn ) +{ + stralloc_t* str = conn->str; + + /* only keep a small buffer in the heap */ + conn->str_pos = 0; + str->n = 0; + if (str->a > 1024) + stralloc_reset(str); +} + +DataStatus +proxy_connection_send( ProxyConnection* conn, int fd ) +{ + stralloc_t* str = conn->str; + int avail = str->n - conn->str_pos; + + conn->str_sent = 0; + + if (avail <= 0) + return 1; + + if (proxy_log) { + PROXY_LOG("%s: sending %d bytes:", conn->name, avail ); + hex_dump( str->s + conn->str_pos, avail, ">> " ); + } + + while (avail > 0) { + int n = socket_send(fd, str->s + conn->str_pos, avail); + if (n == 0) { + PROXY_LOG("%s: connection reset by peer (send)", + conn->name); + return DATA_ERROR; + } + if (n < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + return DATA_NEED_MORE; + + PROXY_LOG("%s: error: %s", conn->name, errno_str); + return DATA_ERROR; + } + conn->str_pos += n; + conn->str_sent += n; + avail -= n; + } + + proxy_connection_rewind(conn); + return DATA_COMPLETED; +} + + +DataStatus +proxy_connection_receive( ProxyConnection* conn, int fd, int wanted ) +{ + stralloc_t* str = conn->str; + + conn->str_recv = 0; + + while (wanted > 0) { + int n; + + stralloc_readyplus( str, wanted ); + n = socket_recv(fd, str->s + str->n, wanted); + if (n == 0) { + PROXY_LOG("%s: connection reset by peer (receive)", + conn->name); + return DATA_ERROR; + } + if (n < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + return DATA_NEED_MORE; + + PROXY_LOG("%s: error: %s", conn->name, errno_str); + return DATA_ERROR; + } + + if (proxy_log) { + PROXY_LOG("%s: received %d bytes:", conn->name, n ); + hex_dump( str->s + str->n, n, "<< " ); + } + + str->n += n; + wanted -= n; + conn->str_recv += n; + } + return DATA_COMPLETED; +} + + +DataStatus +proxy_connection_receive_line( ProxyConnection* conn, int fd ) +{ + stralloc_t* str = conn->str; + + for (;;) { + char c; + int n = socket_recv(fd, &c, 1); + if (n == 0) { + PROXY_LOG("%s: disconnected from server", conn->name ); + return DATA_ERROR; + } + if (n < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + PROXY_LOG("%s: blocked", conn->name); + return DATA_NEED_MORE; + } + PROXY_LOG("%s: error: %s", conn->name, errno_str); + return DATA_ERROR; + } + + stralloc_add_c(str, c); + if (c == '\n') { + str->s[--str->n] = 0; + if (str->n > 0 && str->s[str->n-1] == '\r') + str->s[--str->n] = 0; + + PROXY_LOG("%s: received '%s'", conn->name, + quote_bytes(str->s, str->n)); + return DATA_COMPLETED; + } + } +} + +static void +proxy_connection_insert( ProxyConnection* conn, ProxyConnection* after ) +{ + conn->next = after->next; + after->next->prev = conn; + after->next = conn; + conn->prev = after; +} + +static void +proxy_connection_remove( ProxyConnection* conn ) +{ + conn->prev->next = conn->next; + conn->next->prev = conn->prev; + + conn->next = conn->prev = conn; +} + +/** Global service list + **/ + +#define MAX_SERVICES 4 + +static ProxyService* s_services[ MAX_SERVICES ]; +static int s_num_services; +static int s_init; + +static void proxy_manager_atexit( void ); + +static void +proxy_manager_init(void) +{ + s_init = 1; + s_connections->next = s_connections; + s_connections->prev = s_connections; + atexit( proxy_manager_atexit ); +} + + +extern int +proxy_manager_add_service( ProxyService* service ) +{ + if (!service || s_num_services >= MAX_SERVICES) + return -1; + + if (!s_init) + proxy_manager_init(); + + s_services[s_num_services++] = service; + return 0; +} + + +extern void +proxy_manager_atexit( void ) +{ + ProxyConnection* conn = s_connections->next; + int n; + + /* free all proxy connections */ + while (conn != s_connections) { + ProxyConnection* next = conn->next; + conn->conn_free( conn ); + conn = next; + } + conn->next = conn; + conn->prev = conn; + + /* free all proxy services */ + for (n = s_num_services; n-- > 0;) { + ProxyService* service = s_services[n]; + service->serv_free( service->opaque ); + } + s_num_services = 0; +} + + +void +proxy_connection_free( ProxyConnection* conn, + int keep_alive, + ProxyEvent event ) +{ + if (conn) { + int fd = conn->socket; + + proxy_connection_remove(conn); + + if (event != PROXY_EVENT_NONE) + conn->ev_func( conn->ev_opaque, fd, event ); + + if (keep_alive) + conn->socket = -1; + + conn->conn_free(conn); + } +} + + +int +proxy_manager_add( SockAddress* address, + SocketType sock_type, + ProxyEventFunc ev_func, + void* ev_opaque ) +{ + int n; + + if (!s_init) { + proxy_manager_init(); + } + + for (n = 0; n < s_num_services; n++) { + ProxyService* service = s_services[n]; + ProxyConnection* conn = service->serv_connect( service->opaque, + sock_type, + address ); + if (conn != NULL) { + conn->ev_func = ev_func; + conn->ev_opaque = ev_opaque; + proxy_connection_insert(conn, s_connections->prev); + return 0; + } + } + return -1; +} + + +/* remove an on-going proxified socket connection from the manager's list. + * this is only necessary when the socket connection must be canceled before + * the connection accept/refusal occured + */ +void +proxy_manager_del( void* ev_opaque ) +{ + ProxyConnection* conn = s_connections->next; + for ( ; conn != s_connections; conn = conn->next ) { + if (conn->ev_opaque == ev_opaque) { + proxy_connection_remove(conn); + conn->conn_free(conn); + return; + } + } +} + +void +proxy_select_set( ProxySelect* sel, + int fd, + unsigned flags ) +{ + if (fd < 0 || !flags) + return; + + if (*sel->pcount < fd+1) + *sel->pcount = fd+1; + + if (flags & PROXY_SELECT_READ) { + FD_SET( fd, sel->reads ); + } else { + FD_CLR( fd, sel->reads ); + } + if (flags & PROXY_SELECT_WRITE) { + FD_SET( fd, sel->writes ); + } else { + FD_CLR( fd, sel->writes ); + } + if (flags & PROXY_SELECT_ERROR) { + FD_SET( fd, sel->errors ); + } else { + FD_CLR( fd, sel->errors ); + } +} + +unsigned +proxy_select_poll( ProxySelect* sel, int fd ) +{ + unsigned flags = 0; + + if (fd >= 0) { + if ( FD_ISSET(fd, sel->reads) ) + flags |= PROXY_SELECT_READ; + if ( FD_ISSET(fd, sel->writes) ) + flags |= PROXY_SELECT_WRITE; + if ( FD_ISSET(fd, sel->errors) ) + flags |= PROXY_SELECT_ERROR; + } + return flags; +} + +/* this function is called to update the select file descriptor sets + * with those of the proxified connection sockets that are currently managed */ +void +proxy_manager_select_fill( int *pcount, fd_set* read_fds, fd_set* write_fds, fd_set* err_fds) +{ + ProxyConnection* conn; + ProxySelect sel[1]; + + if (!s_init) + proxy_manager_init(); + + sel->pcount = pcount; + sel->reads = read_fds; + sel->writes = write_fds; + sel->errors = err_fds; + + conn = s_connections->next; + while (conn != s_connections) { + ProxyConnection* next = conn->next; + conn->conn_select(conn, sel); + conn = next; + } +} + +/* this function is called to act on proxified connection sockets when network events arrive */ +void +proxy_manager_poll( fd_set* read_fds, fd_set* write_fds, fd_set* err_fds ) +{ + ProxyConnection* conn = s_connections->next; + ProxySelect sel[1]; + + sel->pcount = NULL; + sel->reads = read_fds; + sel->writes = write_fds; + sel->errors = err_fds; + + while (conn != s_connections) { + ProxyConnection* next = conn->next; + conn->conn_poll( conn, sel ); + conn = next; + } +} + + +int +proxy_base64_encode( const char* src, int srclen, + char* dst, int dstlen ) +{ + static const char cb64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const char* srcend = src + srclen; + int result = 0; + + while (src+3 <= srcend && result+4 <= dstlen) + { + dst[result+0] = cb64[ src[0] >> 2 ]; + dst[result+1] = cb64[ ((src[0] & 3) << 4) | ((src[1] & 0xf0) >> 4) ]; + dst[result+2] = cb64[ ((src[1] & 0xf) << 2) | ((src[2] & 0xc0) >> 6) ]; + dst[result+3] = cb64[ src[2] & 0x3f ]; + src += 3; + result += 4; + } + + if (src < srcend) { + unsigned char in[4]; + + if (result+4 > dstlen) + return -1; + + in[0] = src[0]; + in[1] = src+1 < srcend ? src[1] : 0; + in[2] = src+2 < srcend ? src[2] : 0; + + dst[result+0] = cb64[ in[0] >> 2 ]; + dst[result+1] = cb64[ ((in[0] & 3) << 4) | ((in[1] & 0xf0) >> 4) ]; + dst[result+2] = (unsigned char) (src+1 < srcend ? cb64[ ((in[1] & 0xf) << 2) | ((in[2] & 0xc0) >> 6) ] : '='); + dst[result+3] = (unsigned char) (src+2 < srcend ? cb64[ in[2] & 0x3f ] : '='); + result += 4; + } + return result; +} + +int +proxy_resolve_server( SockAddress* addr, + const char* servername, + int servernamelen, + int serverport ) +{ + char name0[64], *name = name0; + int result = -1; + + if (servernamelen < 0) + servernamelen = strlen(servername); + + if (servernamelen >= sizeof(name0)) { + AARRAY_NEW(name, servernamelen+1); + } + + memcpy(name, servername, servernamelen); + name[servernamelen] = 0; + + if (sock_address_init_resolve( addr, name, serverport, 0 ) < 0) { + PROXY_LOG("%s: can't resolve proxy server name '%s'", + __FUNCTION__, name); + goto Exit; + } + + PROXY_LOG("server name '%s' resolved to %s", name, sock_address_to_string(addr)); + result = 0; + +Exit: + if (name != name0) + AFREE(name); + + return result; +} + + diff --git a/proxy/proxy_common.h b/proxy/proxy_common.h new file mode 100644 index 0000000..78eddd8 --- /dev/null +++ b/proxy/proxy_common.h @@ -0,0 +1,88 @@ +/* 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. +*/ +#ifndef _PROXY_COMMON_H_ +#define _PROXY_COMMON_H_ + +#include "sockets.h" + +#ifdef _WIN32 +#include +#else +#include +#endif + +/* types and definitions used by all proxy connections */ + +typedef enum { + PROXY_EVENT_NONE, + PROXY_EVENT_CONNECTED, + PROXY_EVENT_CONNECTION_REFUSED, + PROXY_EVENT_SERVER_ERROR +} ProxyEvent; + +/* event can't be NONE when this callback is called */ +typedef void (*ProxyEventFunc)( void* opaque, int fd, ProxyEvent event ); + +extern void proxy_set_verbose(int mode); + + +typedef enum { + PROXY_OPTION_AUTH_USERNAME = 1, + PROXY_OPTION_AUTH_PASSWORD, + + PROXY_OPTION_HTTP_NOCACHE = 100, + PROXY_OPTION_HTTP_KEEPALIVE, + PROXY_OPTION_HTTP_USER_AGENT, + + PROXY_OPTION_MAX + +} ProxyOptionType; + + +typedef struct { + ProxyOptionType type; + const char* string; + int string_len; +} ProxyOption; + + +/* add a new proxified socket connection to the manager's list. the event function + * will be called when the connection is established or refused. + * + * only IPv4 is supported at the moment, since our slirp code cannot handle IPv6 + * + * returns 0 on success, or -1 if there is no proxy service for this type of connection + */ +extern int proxy_manager_add( SockAddress* address, + SocketType sock_type, + ProxyEventFunc ev_func, + void* ev_opaque ); + +/* remove an on-going proxified socket connection from the manager's list. + * this is only necessary when the socket connection must be canceled before + * the connection accept/refusal occured + */ +extern void proxy_manager_del( void* ev_opaque ); + +/* this function is called to update the select file descriptor sets + * with those of the proxified connection sockets that are currently managed */ +extern void proxy_manager_select_fill( int *pcount, + fd_set* read_fds, + fd_set* write_fds, + fd_set* err_fds); + +/* this function is called to act on proxified connection sockets when network events arrive */ +extern void proxy_manager_poll( fd_set* read_fds, + fd_set* write_fds, + fd_set* err_fds ); + +#endif /* END */ diff --git a/proxy/proxy_http.c b/proxy/proxy_http.c new file mode 100644 index 0000000..f753587 --- /dev/null +++ b/proxy/proxy_http.c @@ -0,0 +1,186 @@ +/* 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_int.h" +#include "proxy_http_int.h" +#include "qemu-common.h" +#include +#include +#include + +#define HTTP_VERSION "1.1" + +static void +http_service_free( HttpService* service ) +{ + PROXY_LOG("%s", __FUNCTION__); + if (service->footer != service->footer0) + qemu_free(service->footer); + qemu_free(service); +} + + +static ProxyConnection* +http_service_connect( HttpService* service, + SocketType sock_type, + SockAddress* address ) +{ + /* the HTTP proxy can only handle TCP connections */ + if (sock_type != SOCKET_STREAM) + return NULL; + + /* if the client tries to directly connect to the proxy, let it do so */ + if (sock_address_equal( address, &service->server_addr )) + return NULL; + + PROXY_LOG("%s: trying to connect to %s", + __FUNCTION__, sock_address_to_string(address)); + + if (sock_address_get_port(address) == 80) { + /* use the rewriter for HTTP */ + PROXY_LOG("%s: using HTTP rewriter", __FUNCTION__); + return http_rewriter_connect(service, address); + } else { + PROXY_LOG("%s: using HTTP rewriter", __FUNCTION__); + return http_connector_connect(service, address); + } +} + + +int +proxy_http_setup( const char* servername, + int servernamelen, + int serverport, + int num_options, + const ProxyOption* options ) +{ + HttpService* service; + SockAddress server_addr; + const ProxyOption* opt_nocache = NULL; + const ProxyOption* opt_keepalive = NULL; + const ProxyOption* opt_auth_user = NULL; + const ProxyOption* opt_auth_pass = NULL; + const ProxyOption* opt_user_agent = NULL; + + if (servernamelen < 0) + servernamelen = strlen(servername); + + PROXY_LOG( "%s: creating http proxy service connecting to: %.*s:%d", + __FUNCTION__, servernamelen, servername, serverport ); + + /* resolve server address */ + if (proxy_resolve_server(&server_addr, servername, + servernamelen, serverport) < 0) + { + return -1; + } + + /* create service object */ + service = qemu_mallocz(sizeof(*service)); + if (service == NULL) { + PROXY_LOG("%s: not enough memory to allocate new proxy service", __FUNCTION__); + return -1; + } + + service->server_addr = server_addr; + + /* parse options */ + { + const ProxyOption* opt = options; + const ProxyOption* end = opt + num_options; + + for ( ; opt < end; opt++ ) { + switch (opt->type) { + case PROXY_OPTION_HTTP_NOCACHE: opt_nocache = opt; break; + case PROXY_OPTION_HTTP_KEEPALIVE: opt_keepalive = opt; break; + case PROXY_OPTION_AUTH_USERNAME: opt_auth_user = opt; break; + case PROXY_OPTION_AUTH_PASSWORD: opt_auth_pass = opt; break; + case PROXY_OPTION_HTTP_USER_AGENT: opt_user_agent = opt; break; + default: ; + } + } + } + + /* prepare footer */ + { + int wlen; + char* p = service->footer0; + char* end = p + sizeof(service->footer0); + + /* no-cache */ + if (opt_nocache) { + p += snprintf(p, end-p, "Pragma: no-cache\r\nCache-Control: no-cache\r\n"); + if (p >= end) goto FooterOverflow; + } + /* keep-alive */ + if (opt_keepalive) { + p += snprintf(p, end-p, "Connection: Keep-Alive\r\nProxy-Connection: Keep-Alive\r\n"); + if (p >= end) goto FooterOverflow; + } + /* authentication */ + if (opt_auth_user && opt_auth_pass) { + char user_pass[256]; + char encoded[512]; + int uplen; + + uplen = snprintf( user_pass, sizeof(user_pass), "%.*s:%.*s", + opt_auth_user->string_len, opt_auth_user->string, + opt_auth_pass->string_len, opt_auth_pass->string ); + + if (uplen >= sizeof(user_pass)) goto FooterOverflow; + + wlen = proxy_base64_encode(user_pass, uplen, encoded, (int)sizeof(encoded)); + if (wlen < 0) { + PROXY_LOG( "could not base64 encode '%.*s'", uplen, user_pass); + goto FooterOverflow; + } + + p += snprintf(p, end-p, "Proxy-authorization: Basic %.*s\r\n", wlen, encoded); + if (p >= end) goto FooterOverflow; + } + /* user agent */ + if (opt_user_agent) { + p += snprintf(p, end-p, "User-Agent: %.*s\r\n", + opt_user_agent->string_len, + opt_user_agent->string); + if (p >= end) goto FooterOverflow; + } + + p += snprintf(p, end-p, "\r\n"); + + if (p >= end) { + FooterOverflow: + PROXY_LOG( "%s: buffer overflow when creating connection footer", + __FUNCTION__); + http_service_free(service); + return -1; + } + + service->footer = service->footer0; + service->footer_len = (p - service->footer); + } + + PROXY_LOG( "%s: creating HTTP Proxy Service Footer is (len=%d):\n'%.*s'", + __FUNCTION__, service->footer_len, + service->footer_len, service->footer ); + + service->root->opaque = service; + service->root->serv_free = (ProxyServiceFreeFunc) http_service_free; + service->root->serv_connect = (ProxyServiceConnectFunc) http_service_connect; + + if (proxy_manager_add_service( service->root ) < 0) { + PROXY_LOG("%s: could not register service ?", __FUNCTION__); + http_service_free(service); + return -1; + } + return 0; +} + diff --git a/proxy/proxy_http.h b/proxy/proxy_http.h new file mode 100644 index 0000000..a2e2917 --- /dev/null +++ b/proxy/proxy_http.h @@ -0,0 +1,24 @@ +/* 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. +*/ +#ifndef _PROXY_HTTP_H +#define _PROXY_HTTP_H + +#include "proxy_common.h" + +extern int +proxy_http_setup( const char* servername, + int servernamelen, + int serverport, + int num_options, + const ProxyOption* options ); + +#endif /* END */ diff --git a/proxy/proxy_http_connector.c b/proxy/proxy_http_connector.c new file mode 100644 index 0000000..6f03d9b --- /dev/null +++ b/proxy/proxy_http_connector.c @@ -0,0 +1,203 @@ +/* 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 +#include "qemu-common.h" + +/* A HttpConnector implements a non-HTTP proxied connection + * through the CONNECT method. Many firewalls are configured + * to reject these for port 80, so these connections should + * use a HttpRewriter instead. + */ + +typedef enum { + STATE_NONE = 0, + STATE_CONNECTING, /* connecting to the server */ + STATE_SEND_HEADER, /* connected, sending header to the server */ + STATE_RECEIVE_ANSWER_LINE1, + STATE_RECEIVE_ANSWER_LINE2 /* connected, reading server's answer */ +} ConnectorState; + +typedef struct Connection { + ProxyConnection root[1]; + ConnectorState state; +} Connection; + + +static void +connection_free( ProxyConnection* root ) +{ + proxy_connection_done(root); + qemu_free(root); +} + + + +#define HTTP_VERSION "1.1" + +static int +connection_init( Connection* conn ) +{ + HttpService* service = (HttpService*) conn->root->service; + ProxyConnection* root = conn->root; + stralloc_t* str = root->str; + + proxy_connection_rewind(root); + stralloc_add_format(str, "CONNECT %s HTTP/" HTTP_VERSION "\r\n", + sock_address_to_string(&root->address)); + + stralloc_add_bytes(str, service->footer, service->footer_len); + + if (!socket_connect( root->socket, &service->server_addr )) { + /* immediate connection ?? */ + conn->state = STATE_SEND_HEADER; + PROXY_LOG("%s: immediate connection", root->name); + } + else { + if (errno == EINPROGRESS || errno == EWOULDBLOCK) { + conn->state = STATE_CONNECTING; + PROXY_LOG("%s: connecting", root->name); + } + else { + PROXY_LOG("%s: cannot connect to proxy: %s", root->name, errno_str); + return -1; + } + } + return 0; +} + + +static void +connection_select( ProxyConnection* root, + ProxySelect* sel ) +{ + unsigned flags; + Connection* conn = (Connection*)root; + + switch (conn->state) { + case STATE_RECEIVE_ANSWER_LINE1: + case STATE_RECEIVE_ANSWER_LINE2: + flags = PROXY_SELECT_READ; + break; + + case STATE_CONNECTING: + case STATE_SEND_HEADER: + flags = PROXY_SELECT_WRITE; + break; + + default: + flags = 0; + }; + proxy_select_set(sel, root->socket, flags); +} + +static void +connection_poll( ProxyConnection* root, + ProxySelect* sel ) +{ + DataStatus ret = DATA_NEED_MORE; + Connection* conn = (Connection*)root; + int fd = root->socket; + + if (!proxy_select_poll(sel, fd)) + return; + + switch (conn->state) + { + case STATE_CONNECTING: + PROXY_LOG("%s: connected to http proxy, sending header", root->name); + conn->state = STATE_SEND_HEADER; + break; + + case STATE_SEND_HEADER: + ret = proxy_connection_send(root, fd); + if (ret == DATA_COMPLETED) { + conn->state = STATE_RECEIVE_ANSWER_LINE1; + PROXY_LOG("%s: header sent, receiving first answer line", root->name); + } + break; + + case STATE_RECEIVE_ANSWER_LINE1: + case STATE_RECEIVE_ANSWER_LINE2: + ret = proxy_connection_receive_line(root, root->socket); + if (ret == DATA_COMPLETED) { + if (conn->state == STATE_RECEIVE_ANSWER_LINE1) { + int http1, http2, codenum; + const char* line = root->str->s; + + if ( sscanf(line, "HTTP/%d.%d %d", &http1, &http2, &codenum) != 3 ) { + PROXY_LOG( "%s: invalid answer from proxy: '%s'", + root->name, line ); + ret = DATA_ERROR; + break; + } + + /* success is 2xx */ + if (codenum/2 != 100) { + PROXY_LOG( "%s: connection refused, error=%d", + root->name, codenum ); + proxy_connection_free( root, 0, PROXY_EVENT_CONNECTION_REFUSED ); + return; + } + PROXY_LOG("%s: receiving second answer line", root->name); + conn->state = STATE_RECEIVE_ANSWER_LINE2; + proxy_connection_rewind(root); + } else { + /* ok, we're connected */ + PROXY_LOG("%s: connection succeeded", root->name); + proxy_connection_free( root, 1, PROXY_EVENT_CONNECTED ); + } + } + break; + + default: + PROXY_LOG("%s: invalid state for read event: %d", root->name, conn->state); + } + + if (ret == DATA_ERROR) { + proxy_connection_free( root, 0, PROXY_EVENT_SERVER_ERROR ); + } +} + + + +ProxyConnection* +http_connector_connect( HttpService* service, + SockAddress* address ) +{ + Connection* conn; + int s; + + s = socket_create_inet( SOCKET_STREAM ); + if (s < 0) + return NULL; + + conn = qemu_mallocz(sizeof(*conn)); + if (conn == NULL) { + socket_close(s); + return NULL; + } + + proxy_connection_init( conn->root, s, address, service->root, + connection_free, + connection_select, + connection_poll ); + + if ( connection_init( conn ) < 0 ) { + connection_free( conn->root ); + return NULL; + } + + return conn->root; +} diff --git a/proxy/proxy_http_int.h b/proxy/proxy_http_int.h new file mode 100644 index 0000000..6daa9cb --- /dev/null +++ b/proxy/proxy_http_int.h @@ -0,0 +1,38 @@ +/* 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. +*/ +#ifndef _PROXY_HTTP_INT_H +#define _PROXY_HTTP_INT_H + +#include "proxy_http.h" +#include "proxy_int.h" + +/* the HttpService object */ +typedef struct HttpService { + ProxyService root[1]; + SockAddress server_addr; /* server address and port */ + char* footer; /* the footer contains the static parts of the */ + int footer_len; /* connection header, we generate it only once */ + char footer0[512]; +} HttpService; + +/* create a CONNECT connection (for port != 80) */ +extern ProxyConnection* http_connector_connect( + HttpService* service, + SockAddress* address ); + +/* create a HTTP rewriting connection (for port == 80) */ +extern ProxyConnection* http_rewriter_connect( + HttpService* service, + SockAddress* address ); + + +#endif /* _PROXY_HTTP_INT_H */ 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; +} diff --git a/proxy/proxy_int.h b/proxy/proxy_int.h new file mode 100644 index 0000000..739bb75 --- /dev/null +++ b/proxy/proxy_int.h @@ -0,0 +1,201 @@ +/* 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. +*/ +#ifndef _PROXY_INT_H +#define _PROXY_INT_H + +#include "proxy_common.h" +#include "sockets.h" +#include "android/utils/stralloc.h" + +extern int proxy_log; + +extern void +proxy_LOG(const char* fmt, ...); + +#define PROXY_LOG(...) \ + do { if (proxy_log) proxy_LOG(__VA_ARGS__); } while (0) + + +/* ProxySelect is used to handle events */ + +enum { + PROXY_SELECT_READ = (1 << 0), + PROXY_SELECT_WRITE = (1 << 1), + PROXY_SELECT_ERROR = (1 << 2) +}; + +typedef struct { + int* pcount; + fd_set* reads; + fd_set* writes; + fd_set* errors; +} ProxySelect; + +extern void proxy_select_set( ProxySelect* sel, + int fd, + unsigned flags ); + +extern unsigned proxy_select_poll( ProxySelect* sel, int fd ); + + +/* sockets proxy manager internals */ + +typedef struct ProxyConnection ProxyConnection; +typedef struct ProxyService ProxyService; + +/* free a given proxified connection */ +typedef void (*ProxyConnectionFreeFunc) ( ProxyConnection* conn ); + +/* modify the ProxySelect to tell which events to listen to */ +typedef void (*ProxyConnectionSelectFunc) ( ProxyConnection* conn, + ProxySelect* sel ); + +/* action a proxy connection when select() returns certain events for its socket */ +typedef void (*ProxyConnectionPollFunc) ( ProxyConnection* conn, + ProxySelect* sel ); + + +/* root ProxyConnection object */ +struct ProxyConnection { + int socket; + SockAddress address; /* for debugging */ + ProxyConnection* next; + ProxyConnection* prev; + ProxyEventFunc ev_func; + void* ev_opaque; + ProxyService* service; + + /* the following is useful for all types of services */ + char name[64]; /* for debugging purposes */ + + stralloc_t str[1]; /* network buffer (dynamic) */ + int str_pos; /* see proxy_connection_send() */ + int str_sent; /* see proxy_connection_send() */ + int str_recv; /* see proxy_connection_receive() */ + + /* connection methods */ + ProxyConnectionFreeFunc conn_free; + ProxyConnectionSelectFunc conn_select; + ProxyConnectionPollFunc conn_poll; + + /* rest of data depend on exact implementation */ +}; + + + +extern void +proxy_connection_init( ProxyConnection* conn, + int socket, + SockAddress* address, + ProxyService* service, + ProxyConnectionFreeFunc conn_free, + ProxyConnectionSelectFunc conn_select, + ProxyConnectionPollFunc conn_poll ); + +extern void +proxy_connection_done( ProxyConnection* conn ); + +/* free the proxy connection object. this will also + * close the corresponding socket unless the + * 'keep_alive' flag is set to TRUE. + */ +extern void +proxy_connection_free( ProxyConnection* conn, + int keep_alive, + ProxyEvent event ); + +/* status of data transfer operations */ +typedef enum { + DATA_ERROR = -1, + DATA_NEED_MORE = 0, + DATA_COMPLETED = 1 +} DataStatus; + +/* try to send data from the connection's buffer to a socket. + * starting from offset conn->str_pos in the buffer + * + * returns DATA_COMPLETED if everything could be written + * returns DATA_ERROR for a socket disconnection or error + * returns DATA_NEED_MORE if all data could not be sent. + * + * on exit, conn->str_sent contains the number of bytes + * that were really sent. conn->str_pos will be incremented + * by conn->str_sent as well. + * + * note that in case of success (DATA_COMPLETED), this also + * performs a proxy_connection_rewind which sets conn->str_pos + * to 0. + */ +extern DataStatus +proxy_connection_send( ProxyConnection* conn, int fd ); + +/* try to read 'wanted' bytes into conn->str from a socket + * + * returns DATA_COMPLETED if all bytes could be read + * returns DATA_NEED_MORE if not all bytes could be read + * returns DATA_ERROR in case of socket disconnection or error + * + * on exit, the amount of data received is in conn->str_recv + */ +extern DataStatus +proxy_connection_receive( ProxyConnection* conn, int fd, int wanted ); + +/* tries to receive a line of text from the proxy. + * when an entire line is read, the trailing \r\n is stripped + * and replaced by a terminating zero. str->n will be the + * lenght of the line, exclusing the terminating zero. + * returns 1 when a line has been received + * returns 0 if there is still some data to receive + * returns -1 in case of error + */ +extern DataStatus +proxy_connection_receive_line( ProxyConnection* conn, int fd ); + +/* rewind the string buffer for a new operation */ +extern void +proxy_connection_rewind( ProxyConnection* conn ); + +/* base64 encode a source string, returns size of encoded result, + * or -1 if there was not enough room in the destination buffer + */ +extern int +proxy_base64_encode( const char* src, int srclen, + char* dst, int dstlen ); + +extern int +proxy_resolve_server( SockAddress* addr, + const char* servername, + int servernamelen, + int serverport ); + +/* a ProxyService is really a proxy server and associated options */ + +/* destroy a given proxy service */ +typedef void (*ProxyServiceFreeFunc) ( void* opaque ); + +/* tries to create a new proxified connection, returns NULL if the service can't + * handle this address */ +typedef ProxyConnection* (*ProxyServiceConnectFunc)( void* opaque, + SocketType socket_type, + const SockAddress* address ); + +struct ProxyService { + void* opaque; + ProxyServiceFreeFunc serv_free; + ProxyServiceConnectFunc serv_connect; +}; + +extern int +proxy_manager_add_service( ProxyService* service ); + + +#endif /* _PROXY_INT_H */ -- cgit v1.1