1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2001-2003, Ximian, Inc.
4 */
5
6 #include <stdlib.h>
7 #include <string.h>
8
9 #include <libsoup/soup.h>
10
11 /* WARNING: this is really really really not especially compliant with
12 * RFC 2616. But it does work for basic stuff.
13 */
14
15 static SoupSession *session;
16 static SoupServer *server;
17
18 typedef struct {
19 GIOStream *iostream;
20 GInputStream *istream;
21 GOutputStream *ostream;
22
23 gssize nread, nwrote;
24 guchar *buffer;
25 } TunnelEnd;
26
27 typedef struct {
28 SoupServer *self;
29 SoupMessage *msg;
30 SoupClientContext *context;
31 GCancellable *cancellable;
32
33 TunnelEnd client, server;
34 } Tunnel;
35
36 #define BUFSIZE 8192
37
38 static void tunnel_read_cb (GObject *object,
39 GAsyncResult *result,
40 gpointer user_data);
41
42 static void
tunnel_close(Tunnel * tunnel)43 tunnel_close (Tunnel *tunnel)
44 {
45 if (tunnel->cancellable) {
46 g_cancellable_cancel (tunnel->cancellable);
47 g_object_unref (tunnel->cancellable);
48 }
49
50 if (tunnel->client.iostream) {
51 g_io_stream_close (tunnel->client.iostream, NULL, NULL);
52 g_object_unref (tunnel->client.iostream);
53 }
54 if (tunnel->server.iostream) {
55 g_io_stream_close (tunnel->server.iostream, NULL, NULL);
56 g_object_unref (tunnel->server.iostream);
57 }
58
59 g_free (tunnel->client.buffer);
60 g_free (tunnel->server.buffer);
61
62 g_object_unref (tunnel->self);
63 g_object_unref (tunnel->msg);
64
65 g_free (tunnel);
66 }
67
68 static void
tunnel_wrote_cb(GObject * object,GAsyncResult * result,gpointer user_data)69 tunnel_wrote_cb (GObject *object,
70 GAsyncResult *result,
71 gpointer user_data)
72 {
73 Tunnel *tunnel = user_data;
74 TunnelEnd *write_end, *read_end;
75 GError *error = NULL;
76 gssize nwrote;
77
78 nwrote = g_output_stream_write_finish (G_OUTPUT_STREAM (object), result, &error);
79 if (nwrote <= 0) {
80 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
81 g_error_free (error);
82 return;
83 } else if (error) {
84 g_print ("Tunnel write failed: %s\n", error->message);
85 g_error_free (error);
86 }
87 tunnel_close (tunnel);
88 return;
89 }
90
91 if (object == (GObject *)tunnel->client.ostream) {
92 write_end = &tunnel->client;
93 read_end = &tunnel->server;
94 } else {
95 write_end = &tunnel->server;
96 read_end = &tunnel->client;
97 }
98
99 write_end->nwrote += nwrote;
100 if (write_end->nwrote < read_end->nread) {
101 g_output_stream_write_async (write_end->ostream,
102 read_end->buffer + write_end->nwrote,
103 read_end->nread - write_end->nwrote,
104 G_PRIORITY_DEFAULT, tunnel->cancellable,
105 tunnel_wrote_cb, tunnel);
106 } else {
107 g_input_stream_read_async (read_end->istream,
108 read_end->buffer, BUFSIZE,
109 G_PRIORITY_DEFAULT, tunnel->cancellable,
110 tunnel_read_cb, tunnel);
111 }
112 }
113
114 static void
tunnel_read_cb(GObject * object,GAsyncResult * result,gpointer user_data)115 tunnel_read_cb (GObject *object,
116 GAsyncResult *result,
117 gpointer user_data)
118 {
119 Tunnel *tunnel = user_data;
120 TunnelEnd *read_end, *write_end;
121 GError *error = NULL;
122 gssize nread;
123
124 nread = g_input_stream_read_finish (G_INPUT_STREAM (object), result, &error);
125 if (nread <= 0) {
126 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
127 g_error_free (error);
128 return;
129 } else if (error) {
130 g_print ("Tunnel read failed: %s\n", error->message);
131 g_error_free (error);
132 }
133 tunnel_close (tunnel);
134 return;
135 }
136
137 if (object == (GObject *)tunnel->client.istream) {
138 read_end = &tunnel->client;
139 write_end = &tunnel->server;
140 } else {
141 read_end = &tunnel->server;
142 write_end = &tunnel->client;
143 }
144
145 read_end->nread = nread;
146 write_end->nwrote = 0;
147 g_output_stream_write_async (write_end->ostream,
148 read_end->buffer, read_end->nread,
149 G_PRIORITY_DEFAULT, tunnel->cancellable,
150 tunnel_wrote_cb, tunnel);
151 }
152
153 static void
start_tunnel(SoupMessage * msg,gpointer user_data)154 start_tunnel (SoupMessage *msg, gpointer user_data)
155 {
156 Tunnel *tunnel = user_data;
157
158 tunnel->client.iostream = soup_client_context_steal_connection (tunnel->context);
159 tunnel->client.istream = g_io_stream_get_input_stream (tunnel->client.iostream);
160 tunnel->client.ostream = g_io_stream_get_output_stream (tunnel->client.iostream);
161
162 tunnel->client.buffer = g_malloc (BUFSIZE);
163 tunnel->server.buffer = g_malloc (BUFSIZE);
164
165 tunnel->cancellable = g_cancellable_new ();
166
167 g_input_stream_read_async (tunnel->client.istream,
168 tunnel->client.buffer, BUFSIZE,
169 G_PRIORITY_DEFAULT, tunnel->cancellable,
170 tunnel_read_cb, tunnel);
171 g_input_stream_read_async (tunnel->server.istream,
172 tunnel->server.buffer, BUFSIZE,
173 G_PRIORITY_DEFAULT, tunnel->cancellable,
174 tunnel_read_cb, tunnel);
175 }
176
177
178 static void
tunnel_connected_cb(GObject * object,GAsyncResult * result,gpointer user_data)179 tunnel_connected_cb (GObject *object,
180 GAsyncResult *result,
181 gpointer user_data)
182 {
183 Tunnel *tunnel = user_data;
184 GError *error = NULL;
185
186 tunnel->server.iostream = (GIOStream *)
187 g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (object), result, &error);
188 if (!tunnel->server.iostream) {
189 soup_message_set_status (tunnel->msg, SOUP_STATUS_BAD_GATEWAY);
190 soup_message_set_response (tunnel->msg, "text/plain",
191 SOUP_MEMORY_COPY,
192 error->message, strlen (error->message));
193 g_error_free (error);
194 soup_server_unpause_message (tunnel->self, tunnel->msg);
195 tunnel_close (tunnel);
196 return;
197 }
198
199 tunnel->server.istream = g_io_stream_get_input_stream (tunnel->server.iostream);
200 tunnel->server.ostream = g_io_stream_get_output_stream (tunnel->server.iostream);
201
202 soup_message_set_status (tunnel->msg, SOUP_STATUS_OK);
203 soup_server_unpause_message (tunnel->self, tunnel->msg);
204 g_signal_connect (tunnel->msg, "finished",
205 G_CALLBACK (start_tunnel), tunnel);
206 }
207
208 static void
try_tunnel(SoupServer * server,SoupMessage * msg,SoupClientContext * context)209 try_tunnel (SoupServer *server, SoupMessage *msg, SoupClientContext *context)
210 {
211 Tunnel *tunnel;
212 SoupURI *dest_uri;
213 GSocketClient *sclient;
214
215 soup_server_pause_message (server, msg);
216
217 tunnel = g_new0 (Tunnel, 1);
218 tunnel->self = g_object_ref (server);
219 tunnel->msg = g_object_ref (msg);
220 tunnel->context = context;
221
222 dest_uri = soup_message_get_uri (msg);
223 sclient = g_socket_client_new ();
224 g_socket_client_connect_to_host_async (sclient, dest_uri->host, dest_uri->port,
225 NULL, tunnel_connected_cb, tunnel);
226 g_object_unref (sclient);
227 }
228
229 static void
copy_header(const char * name,const char * value,gpointer dest_headers)230 copy_header (const char *name, const char *value, gpointer dest_headers)
231 {
232 soup_message_headers_append (dest_headers, name, value);
233 }
234
235 static void
send_headers(SoupMessage * from,SoupMessage * to)236 send_headers (SoupMessage *from, SoupMessage *to)
237 {
238 g_print ("[%p] HTTP/1.%d %d %s\n", to,
239 soup_message_get_http_version (from),
240 from->status_code, from->reason_phrase);
241
242 soup_message_set_status_full (to, from->status_code,
243 from->reason_phrase);
244 soup_message_headers_foreach (from->response_headers, copy_header,
245 to->response_headers);
246 soup_message_headers_remove (to->response_headers, "Content-Length");
247 soup_server_unpause_message (server, to);
248 }
249
250 static void
send_chunk(SoupMessage * from,SoupBuffer * chunk,SoupMessage * to)251 send_chunk (SoupMessage *from, SoupBuffer *chunk, SoupMessage *to)
252 {
253 g_print ("[%p] writing chunk of %lu bytes\n", to,
254 (unsigned long)chunk->length);
255
256 soup_message_body_append_buffer (to->response_body, chunk);
257 soup_server_unpause_message (server, to);
258 }
259
260 static void
client_msg_failed(SoupMessage * msg,gpointer msg2)261 client_msg_failed (SoupMessage *msg, gpointer msg2)
262 {
263 soup_session_cancel_message (session, msg2, SOUP_STATUS_IO_ERROR);
264 }
265
266 static void
finish_msg(SoupSession * session,SoupMessage * msg2,gpointer data)267 finish_msg (SoupSession *session, SoupMessage *msg2, gpointer data)
268 {
269 SoupMessage *msg = data;
270
271 g_print ("[%p] done\n\n", msg);
272 g_signal_handlers_disconnect_by_func (msg, client_msg_failed, msg2);
273
274 soup_message_body_complete (msg->response_body);
275 soup_server_unpause_message (server, msg);
276 g_object_unref (msg);
277 }
278
279 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)280 server_callback (SoupServer *server, SoupMessage *msg,
281 const char *path, GHashTable *query,
282 SoupClientContext *context, gpointer data)
283 {
284 SoupMessage *msg2;
285 char *uristr;
286
287 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
288 g_print ("[%p] %s %s HTTP/1.%d\n", msg, msg->method, uristr,
289 soup_message_get_http_version (msg));
290
291 if (msg->method == SOUP_METHOD_CONNECT) {
292 try_tunnel (server, msg, context);
293 return;
294 }
295
296 msg2 = soup_message_new (msg->method, uristr);
297 soup_message_headers_foreach (msg->request_headers, copy_header,
298 msg2->request_headers);
299 soup_message_headers_remove (msg2->request_headers, "Host");
300 soup_message_headers_remove (msg2->request_headers, "Connection");
301
302 if (msg->request_body->length) {
303 SoupBuffer *request = soup_message_body_flatten (msg->request_body);
304 soup_message_body_append_buffer (msg2->request_body, request);
305 soup_buffer_free (request);
306 }
307 soup_message_headers_set_encoding (msg->response_headers,
308 SOUP_ENCODING_CHUNKED);
309
310 g_signal_connect (msg2, "got_headers",
311 G_CALLBACK (send_headers), msg);
312 g_signal_connect (msg2, "got_chunk",
313 G_CALLBACK (send_chunk), msg);
314
315 g_signal_connect (msg, "finished", G_CALLBACK (client_msg_failed), msg2);
316
317 soup_session_queue_message (session, msg2, finish_msg, msg);
318
319 g_object_ref (msg);
320 soup_server_pause_message (server, msg);
321 }
322
323 static gboolean
auth_callback(SoupAuthDomain * auth_domain,SoupMessage * msg,const char * username,const char * password,gpointer data)324 auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
325 const char *username, const char *password, gpointer data)
326 {
327 return !strcmp (username, "user") && !strcmp (password, "password");
328 }
329
330 static void
quit(int sig)331 quit (int sig)
332 {
333 /* Exit cleanly on ^C in case we're valgrinding. */
334 exit (0);
335 }
336
337 static int port;
338 static gboolean require_auth;
339
340 static GOptionEntry entries[] = {
341 { "auth-domain", 'a', 0,
342 G_OPTION_ARG_NONE, &require_auth,
343 "Require authentication", NULL },
344 { "port", 'p', 0,
345 G_OPTION_ARG_INT, &port,
346 "Port to listen on", NULL },
347 { NULL }
348 };
349
350 int
main(int argc,char ** argv)351 main (int argc, char **argv)
352 {
353 GOptionContext *opts;
354 GMainLoop *loop;
355 GSList *uris, *u;
356 char *str;
357 GError *error = NULL;
358
359 opts = g_option_context_new (NULL);
360 g_option_context_add_main_entries (opts, entries, NULL);
361 if (!g_option_context_parse (opts, &argc, &argv, &error)) {
362 g_printerr ("Could not parse arguments: %s\n",
363 error->message);
364 g_printerr ("%s",
365 g_option_context_get_help (opts, TRUE, NULL));
366 exit (1);
367 }
368
369 if (argc != 1) {
370 g_printerr ("%s",
371 g_option_context_get_help (opts, TRUE, NULL));
372 exit (1);
373 }
374 g_option_context_free (opts);
375
376 signal (SIGINT, quit);
377
378 server = g_object_new (SOUP_TYPE_SERVER, NULL);
379 soup_server_add_handler (server, NULL,
380 server_callback, NULL, NULL);
381 if (require_auth) {
382 SoupAuthDomain *auth_domain;
383
384 auth_domain = soup_auth_domain_basic_new (
385 SOUP_AUTH_DOMAIN_REALM, "simple-proxy",
386 SOUP_AUTH_DOMAIN_PROXY, TRUE,
387 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback,
388 NULL);
389 soup_server_add_auth_domain (server, auth_domain);
390 g_object_unref (auth_domain);
391 }
392
393 soup_server_listen_all (server, port, 0, &error);
394 if (error) {
395 g_printerr ("Unable to create server: %s\n", error->message);
396 exit (1);
397 }
398
399 uris = soup_server_get_uris (server);
400 for (u = uris; u; u = u->next) {
401 str = soup_uri_to_string (u->data, FALSE);
402 g_print ("Listening on %s\n", str);
403 g_free (str);
404 soup_uri_free (u->data);
405 }
406 g_slist_free (uris);
407
408 session = soup_session_new ();
409
410 g_print ("\nWaiting for requests...\n");
411
412 loop = g_main_loop_new (NULL, TRUE);
413 g_main_loop_run (loop);
414 g_main_loop_unref (loop);
415
416 return 0;
417 }
418