1/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12#include "proxy_http_int.h"
13#include <errno.h>
14#include <stdio.h>
15#include <string.h>
16#include "qemu-common.h"
17
18/* A HttpConnector implements a non-HTTP proxied connection
19 * through the CONNECT method. Many firewalls are configured
20 * to reject these for port 80, so these connections should
21 * use a HttpRewriter instead.
22 */
23
24typedef enum {
25    STATE_NONE = 0,
26    STATE_CONNECTING,           /* connecting to the server */
27    STATE_SEND_HEADER,          /* connected, sending header to the server */
28    STATE_RECEIVE_ANSWER_LINE1,
29    STATE_RECEIVE_ANSWER_LINE2  /* connected, reading server's answer */
30} ConnectorState;
31
32typedef struct Connection {
33    ProxyConnection  root[1];
34    ConnectorState   state;
35} Connection;
36
37
38static void
39connection_free( ProxyConnection*  root )
40{
41    proxy_connection_done(root);
42    qemu_free(root);
43}
44
45
46
47#define  HTTP_VERSION  "1.1"
48
49static int
50connection_init( Connection*  conn )
51{
52    HttpService*      service = (HttpService*) conn->root->service;
53    ProxyConnection*  root    = conn->root;
54    stralloc_t*       str     = root->str;
55
56    proxy_connection_rewind(root);
57    stralloc_add_format(str, "CONNECT %s HTTP/" HTTP_VERSION "\r\n",
58                        sock_address_to_string(&root->address));
59
60    stralloc_add_bytes(str, service->footer, service->footer_len);
61
62    if (!socket_connect( root->socket, &service->server_addr )) {
63        /* immediate connection ?? */
64        conn->state = STATE_SEND_HEADER;
65        PROXY_LOG("%s: immediate connection", root->name);
66    }
67    else {
68        if (errno == EINPROGRESS || errno == EWOULDBLOCK) {
69            conn->state = STATE_CONNECTING;
70            PROXY_LOG("%s: connecting", root->name);
71        }
72        else {
73            PROXY_LOG("%s: cannot connect to proxy: %s", root->name, errno_str);
74            return -1;
75        }
76    }
77    return 0;
78}
79
80
81static void
82connection_select( ProxyConnection*   root,
83                   ProxySelect*       sel )
84{
85    unsigned     flags;
86    Connection*  conn = (Connection*)root;
87
88    switch (conn->state) {
89        case STATE_RECEIVE_ANSWER_LINE1:
90        case STATE_RECEIVE_ANSWER_LINE2:
91            flags = PROXY_SELECT_READ;
92            break;
93
94        case STATE_CONNECTING:
95        case STATE_SEND_HEADER:
96            flags = PROXY_SELECT_WRITE;
97            break;
98
99        default:
100            flags = 0;
101    };
102    proxy_select_set(sel, root->socket, flags);
103}
104
105static void
106connection_poll( ProxyConnection*   root,
107                 ProxySelect*       sel )
108{
109    DataStatus   ret  = DATA_NEED_MORE;
110    Connection*  conn = (Connection*)root;
111    int          fd   = root->socket;
112
113    if (!proxy_select_poll(sel, fd))
114        return;
115
116    switch (conn->state)
117    {
118        case STATE_CONNECTING:
119            PROXY_LOG("%s: connected to http proxy, sending header", root->name);
120            conn->state = STATE_SEND_HEADER;
121            break;
122
123        case STATE_SEND_HEADER:
124            ret = proxy_connection_send(root, fd);
125            if (ret == DATA_COMPLETED) {
126                conn->state = STATE_RECEIVE_ANSWER_LINE1;
127                PROXY_LOG("%s: header sent, receiving first answer line", root->name);
128            }
129            break;
130
131        case STATE_RECEIVE_ANSWER_LINE1:
132        case STATE_RECEIVE_ANSWER_LINE2:
133            ret = proxy_connection_receive_line(root, root->socket);
134            if (ret == DATA_COMPLETED) {
135                if (conn->state == STATE_RECEIVE_ANSWER_LINE1) {
136                    int  http1, http2, codenum;
137                    const char*  line = root->str->s;
138
139                    if ( sscanf(line, "HTTP/%d.%d %d", &http1, &http2, &codenum) != 3 ) {
140                        PROXY_LOG( "%s: invalid answer from proxy: '%s'",
141                                    root->name, line );
142                        ret = DATA_ERROR;
143                        break;
144                    }
145
146                    /* success is 2xx */
147                    if (codenum/2 != 100) {
148                        PROXY_LOG( "%s: connection refused, error=%d",
149                                    root->name, codenum );
150                        proxy_connection_free( root, 0, PROXY_EVENT_CONNECTION_REFUSED );
151                        return;
152                    }
153                    PROXY_LOG("%s: receiving second answer line", root->name);
154                    conn->state = STATE_RECEIVE_ANSWER_LINE2;
155                    proxy_connection_rewind(root);
156                } else {
157                    /* ok, we're connected */
158                    PROXY_LOG("%s: connection succeeded", root->name);
159                    proxy_connection_free( root, 1, PROXY_EVENT_CONNECTED );
160                }
161            }
162            break;
163
164        default:
165            PROXY_LOG("%s: invalid state for read event: %d", root->name, conn->state);
166    }
167
168    if (ret == DATA_ERROR) {
169        proxy_connection_free( root, 0, PROXY_EVENT_SERVER_ERROR );
170    }
171}
172
173
174
175ProxyConnection*
176http_connector_connect( HttpService*  service,
177                        SockAddress*  address )
178{
179    Connection*  conn;
180    int          s;
181
182    s = socket_create_inet( SOCKET_STREAM );
183    if (s < 0)
184        return NULL;
185
186    conn = qemu_mallocz(sizeof(*conn));
187    if (conn == NULL) {
188        socket_close(s);
189        return NULL;
190    }
191
192    proxy_connection_init( conn->root, s, address, service->root,
193                           connection_free,
194                           connection_select,
195                           connection_poll );
196
197    if ( connection_init( conn ) < 0 ) {
198        connection_free( conn->root );
199        return NULL;
200    }
201
202    return conn->root;
203}
204