/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2001-2003, Ximian, Inc. */ #include #include #include /* WARNING: this is really really really not especially compliant with * RFC 2616. But it does work for basic stuff. */ static SoupSession *session; static SoupServer *server; typedef struct { GIOStream *iostream; GInputStream *istream; GOutputStream *ostream; gssize nread, nwrote; guchar *buffer; } TunnelEnd; typedef struct { SoupServer *self; SoupMessage *msg; SoupClientContext *context; GCancellable *cancellable; TunnelEnd client, server; } Tunnel; #define BUFSIZE 8192 static void tunnel_read_cb (GObject *object, GAsyncResult *result, gpointer user_data); static void tunnel_close (Tunnel *tunnel) { if (tunnel->cancellable) { g_cancellable_cancel (tunnel->cancellable); g_object_unref (tunnel->cancellable); } if (tunnel->client.iostream) { g_io_stream_close (tunnel->client.iostream, NULL, NULL); g_object_unref (tunnel->client.iostream); } if (tunnel->server.iostream) { g_io_stream_close (tunnel->server.iostream, NULL, NULL); g_object_unref (tunnel->server.iostream); } g_free (tunnel->client.buffer); g_free (tunnel->server.buffer); g_object_unref (tunnel->self); g_object_unref (tunnel->msg); g_free (tunnel); } static void tunnel_wrote_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Tunnel *tunnel = user_data; TunnelEnd *write_end, *read_end; GError *error = NULL; gssize nwrote; nwrote = g_output_stream_write_finish (G_OUTPUT_STREAM (object), result, &error); if (nwrote <= 0) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error_free (error); return; } else if (error) { g_print ("Tunnel write failed: %s\n", error->message); g_error_free (error); } tunnel_close (tunnel); return; } if (object == (GObject *)tunnel->client.ostream) { write_end = &tunnel->client; read_end = &tunnel->server; } else { write_end = &tunnel->server; read_end = &tunnel->client; } write_end->nwrote += nwrote; if (write_end->nwrote < read_end->nread) { g_output_stream_write_async (write_end->ostream, read_end->buffer + write_end->nwrote, read_end->nread - write_end->nwrote, G_PRIORITY_DEFAULT, tunnel->cancellable, tunnel_wrote_cb, tunnel); } else { g_input_stream_read_async (read_end->istream, read_end->buffer, BUFSIZE, G_PRIORITY_DEFAULT, tunnel->cancellable, tunnel_read_cb, tunnel); } } static void tunnel_read_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Tunnel *tunnel = user_data; TunnelEnd *read_end, *write_end; GError *error = NULL; gssize nread; nread = g_input_stream_read_finish (G_INPUT_STREAM (object), result, &error); if (nread <= 0) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error_free (error); return; } else if (error) { g_print ("Tunnel read failed: %s\n", error->message); g_error_free (error); } tunnel_close (tunnel); return; } if (object == (GObject *)tunnel->client.istream) { read_end = &tunnel->client; write_end = &tunnel->server; } else { read_end = &tunnel->server; write_end = &tunnel->client; } read_end->nread = nread; write_end->nwrote = 0; g_output_stream_write_async (write_end->ostream, read_end->buffer, read_end->nread, G_PRIORITY_DEFAULT, tunnel->cancellable, tunnel_wrote_cb, tunnel); } static void start_tunnel (SoupMessage *msg, gpointer user_data) { Tunnel *tunnel = user_data; tunnel->client.iostream = soup_client_context_steal_connection (tunnel->context); tunnel->client.istream = g_io_stream_get_input_stream (tunnel->client.iostream); tunnel->client.ostream = g_io_stream_get_output_stream (tunnel->client.iostream); tunnel->client.buffer = g_malloc (BUFSIZE); tunnel->server.buffer = g_malloc (BUFSIZE); tunnel->cancellable = g_cancellable_new (); g_input_stream_read_async (tunnel->client.istream, tunnel->client.buffer, BUFSIZE, G_PRIORITY_DEFAULT, tunnel->cancellable, tunnel_read_cb, tunnel); g_input_stream_read_async (tunnel->server.istream, tunnel->server.buffer, BUFSIZE, G_PRIORITY_DEFAULT, tunnel->cancellable, tunnel_read_cb, tunnel); } static void tunnel_connected_cb (GObject *object, GAsyncResult *result, gpointer user_data) { Tunnel *tunnel = user_data; GError *error = NULL; tunnel->server.iostream = (GIOStream *) g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (object), result, &error); if (!tunnel->server.iostream) { soup_message_set_status (tunnel->msg, SOUP_STATUS_BAD_GATEWAY); soup_message_set_response (tunnel->msg, "text/plain", SOUP_MEMORY_COPY, error->message, strlen (error->message)); g_error_free (error); soup_server_unpause_message (tunnel->self, tunnel->msg); tunnel_close (tunnel); return; } tunnel->server.istream = g_io_stream_get_input_stream (tunnel->server.iostream); tunnel->server.ostream = g_io_stream_get_output_stream (tunnel->server.iostream); soup_message_set_status (tunnel->msg, SOUP_STATUS_OK); soup_server_unpause_message (tunnel->self, tunnel->msg); g_signal_connect (tunnel->msg, "finished", G_CALLBACK (start_tunnel), tunnel); } static void try_tunnel (SoupServer *server, SoupMessage *msg, SoupClientContext *context) { Tunnel *tunnel; SoupURI *dest_uri; GSocketClient *sclient; soup_server_pause_message (server, msg); tunnel = g_new0 (Tunnel, 1); tunnel->self = g_object_ref (server); tunnel->msg = g_object_ref (msg); tunnel->context = context; dest_uri = soup_message_get_uri (msg); sclient = g_socket_client_new (); g_socket_client_connect_to_host_async (sclient, dest_uri->host, dest_uri->port, NULL, tunnel_connected_cb, tunnel); g_object_unref (sclient); } static void copy_header (const char *name, const char *value, gpointer dest_headers) { soup_message_headers_append (dest_headers, name, value); } static void send_headers (SoupMessage *from, SoupMessage *to) { g_print ("[%p] HTTP/1.%d %d %s\n", to, soup_message_get_http_version (from), from->status_code, from->reason_phrase); soup_message_set_status_full (to, from->status_code, from->reason_phrase); soup_message_headers_foreach (from->response_headers, copy_header, to->response_headers); soup_message_headers_remove (to->response_headers, "Content-Length"); soup_server_unpause_message (server, to); } static void send_chunk (SoupMessage *from, SoupBuffer *chunk, SoupMessage *to) { g_print ("[%p] writing chunk of %lu bytes\n", to, (unsigned long)chunk->length); soup_message_body_append_buffer (to->response_body, chunk); soup_server_unpause_message (server, to); } static void client_msg_failed (SoupMessage *msg, gpointer msg2) { soup_session_cancel_message (session, msg2, SOUP_STATUS_IO_ERROR); } static void finish_msg (SoupSession *session, SoupMessage *msg2, gpointer data) { SoupMessage *msg = data; g_print ("[%p] done\n\n", msg); g_signal_handlers_disconnect_by_func (msg, client_msg_failed, msg2); soup_message_body_complete (msg->response_body); soup_server_unpause_message (server, msg); g_object_unref (msg); } static void server_callback (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *context, gpointer data) { SoupMessage *msg2; char *uristr; uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE); g_print ("[%p] %s %s HTTP/1.%d\n", msg, msg->method, uristr, soup_message_get_http_version (msg)); if (msg->method == SOUP_METHOD_CONNECT) { try_tunnel (server, msg, context); return; } msg2 = soup_message_new (msg->method, uristr); soup_message_headers_foreach (msg->request_headers, copy_header, msg2->request_headers); soup_message_headers_remove (msg2->request_headers, "Host"); soup_message_headers_remove (msg2->request_headers, "Connection"); if (msg->request_body->length) { SoupBuffer *request = soup_message_body_flatten (msg->request_body); soup_message_body_append_buffer (msg2->request_body, request); soup_buffer_free (request); } soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CHUNKED); g_signal_connect (msg2, "got_headers", G_CALLBACK (send_headers), msg); g_signal_connect (msg2, "got_chunk", G_CALLBACK (send_chunk), msg); g_signal_connect (msg, "finished", G_CALLBACK (client_msg_failed), msg2); soup_session_queue_message (session, msg2, finish_msg, msg); g_object_ref (msg); soup_server_pause_message (server, msg); } static gboolean auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg, const char *username, const char *password, gpointer data) { return !strcmp (username, "user") && !strcmp (password, "password"); } static void quit (int sig) { /* Exit cleanly on ^C in case we're valgrinding. */ exit (0); } static int port; static gboolean require_auth; static GOptionEntry entries[] = { { "auth-domain", 'a', 0, G_OPTION_ARG_NONE, &require_auth, "Require authentication", NULL }, { "port", 'p', 0, G_OPTION_ARG_INT, &port, "Port to listen on", NULL }, { NULL } }; int main (int argc, char **argv) { GOptionContext *opts; GMainLoop *loop; GSList *uris, *u; char *str; GError *error = NULL; opts = g_option_context_new (NULL); g_option_context_add_main_entries (opts, entries, NULL); if (!g_option_context_parse (opts, &argc, &argv, &error)) { g_printerr ("Could not parse arguments: %s\n", error->message); g_printerr ("%s", g_option_context_get_help (opts, TRUE, NULL)); exit (1); } if (argc != 1) { g_printerr ("%s", g_option_context_get_help (opts, TRUE, NULL)); exit (1); } g_option_context_free (opts); signal (SIGINT, quit); server = g_object_new (SOUP_TYPE_SERVER, NULL); soup_server_add_handler (server, NULL, server_callback, NULL, NULL); if (require_auth) { SoupAuthDomain *auth_domain; auth_domain = soup_auth_domain_basic_new ( SOUP_AUTH_DOMAIN_REALM, "simple-proxy", SOUP_AUTH_DOMAIN_PROXY, TRUE, SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback, NULL); soup_server_add_auth_domain (server, auth_domain); g_object_unref (auth_domain); } soup_server_listen_all (server, port, 0, &error); if (error) { g_printerr ("Unable to create server: %s\n", error->message); exit (1); } uris = soup_server_get_uris (server); for (u = uris; u; u = u->next) { str = soup_uri_to_string (u->data, FALSE); g_print ("Listening on %s\n", str); g_free (str); soup_uri_free (u->data); } g_slist_free (uris); session = soup_session_new (); g_print ("\nWaiting for requests...\n"); loop = g_main_loop_new (NULL, TRUE); g_main_loop_run (loop); g_main_loop_unref (loop); return 0; }