From ed4a742946374f7ee3c46b93eb943c95f04ec4c4 Mon Sep 17 00:00:00 2001 From: Paolo Borelli Date: Sat, 28 Feb 2015 11:05:02 +0100 Subject: HTTP proxy support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on code from "WockyHttpProxy" written by Nicolas Dufresne and Marc-André Lureau. Initial glib patch by Brian J. Murrell. https://bugzilla.gnome.org/show_bug.cgi?id=733876 --- docs/reference/gio/Makefile.am | 1 + gio/Makefile.am | 2 + gio/ghttpproxy.c | 392 +++++++++++++++++++++++++++++++++++++++++ gio/ghttpproxy.h | 54 ++++++ gio/giomodule.c | 3 + 5 files changed, 452 insertions(+) create mode 100644 gio/ghttpproxy.c create mode 100644 gio/ghttpproxy.h diff --git a/docs/reference/gio/Makefile.am b/docs/reference/gio/Makefile.am index ee1173255..d1e7735bd 100644 --- a/docs/reference/gio/Makefile.am +++ b/docs/reference/gio/Makefile.am @@ -50,6 +50,7 @@ IGNORE_HFILES = \ gdummytlsbackend.h \ gfileattribute-priv.h \ gfileinfo-priv.h \ + ghttpproxy.h \ giomodule-priv.h \ gioprivate.h \ giowin32-priv.h \ diff --git a/gio/Makefile.am b/gio/Makefile.am index bfe27c5fa..28aea41ae 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -183,6 +183,8 @@ application_sources = \ $(NULL) local_sources = \ + ghttpproxy.c \ + ghttpproxy.h \ glocaldirectorymonitor.c \ glocaldirectorymonitor.h \ glocalfile.c \ diff --git a/gio/ghttpproxy.c b/gio/ghttpproxy.c new file mode 100644 index 000000000..0bd11d38f --- /dev/null +++ b/gio/ghttpproxy.c @@ -0,0 +1,392 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2010 Collabora, Ltd. + * Copyright (C) 2014 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Nicolas Dufresne + * Marc-André Lureau + */ + +#include "config.h" + +#include "ghttpproxy.h" + +#include +#include + +#include "giomodule.h" +#include "giomodule-priv.h" +#include "giostream.h" +#include "ginputstream.h" +#include "glibintl.h" +#include "goutputstream.h" +#include "gproxy.h" +#include "gproxyaddress.h" +#include "gsocketconnectable.h" +#include "gtask.h" +#include "gtlsclientconnection.h" +#include "gtlsconnection.h" + + +struct _GHttpProxy +{ + GObject parent; +}; + +struct _GHttpProxyClass +{ + GObjectClass parent_class; +}; + +static void g_http_proxy_iface_init (GProxyInterface *proxy_iface); + +#define g_http_proxy_get_type _g_http_proxy_get_type +G_DEFINE_TYPE_WITH_CODE (GHttpProxy, g_http_proxy, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY, + g_http_proxy_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME, + g_define_type_id, + "http", + 0)) + +static void +g_http_proxy_init (GHttpProxy *proxy) +{ +} + +static gchar * +create_request (GProxyAddress *proxy_address, + gboolean *has_cred) +{ + const gchar *hostname; + gint port; + const gchar *username; + const gchar *password; + GString *request; + gchar *ascii_hostname; + + if (has_cred) + *has_cred = FALSE; + + hostname = g_proxy_address_get_destination_hostname (proxy_address); + port = g_proxy_address_get_destination_port (proxy_address); + username = g_proxy_address_get_username (proxy_address); + password = g_proxy_address_get_password (proxy_address); + + request = g_string_new (NULL); + + ascii_hostname = g_hostname_to_ascii (hostname); + g_string_append_printf (request, + "CONNECT %s:%i HTTP/1.0\r\n" + "Host: %s:%i\r\n" + "Proxy-Connection: keep-alive\r\n" + "User-Agent: GLib/%i.%i\r\n", + ascii_hostname, port, + ascii_hostname, port, + GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION); + g_free (ascii_hostname); + + if (username != NULL && password != NULL) + { + gchar *cred; + gchar *base64_cred; + + if (has_cred) + *has_cred = TRUE; + + cred = g_strdup_printf ("%s:%s", username, password); + base64_cred = g_base64_encode ((guchar *) cred, strlen (cred)); + g_free (cred); + g_string_append_printf (request, + "Proxy-Authorization: Basic %s\r\n", + base64_cred); + g_free (base64_cred); + } + + g_string_append (request, "\r\n"); + + return g_string_free (request, FALSE); +} + +static gboolean +check_reply (const gchar *buffer, + gboolean has_cred, + GError **error) +{ + gint err_code; + const gchar *ptr = buffer + 7; + + if (strncmp (buffer, "HTTP/1.", 7) != 0 || (*ptr != '0' && *ptr != '1')) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, + _("Bad HTTP proxy reply")); + return FALSE; + } + + ptr++; + while (*ptr == ' ') + ptr++; + + err_code = atoi (ptr); + + if (err_code < 200 || err_code >= 300) + { + switch (err_code) + { + case 403: + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_NOT_ALLOWED, + _("HTTP proxy connection not allowed")); + break; + case 407: + if (has_cred) + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED, + _("HTTP proxy authentication failed")); + else + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH, + _("HTTP proxy authentication required")); + break; + default: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, + _("HTTP proxy connection failed: %i"), err_code); + } + + return FALSE; + } + + return TRUE; +} + +#define HTTP_END_MARKER "\r\n\r\n" + +static GIOStream * +g_http_proxy_connect (GProxy *proxy, + GIOStream *io_stream, + GProxyAddress *proxy_address, + GCancellable *cancellable, + GError **error) +{ + GInputStream *in; + GOutputStream *out; + gchar *buffer = NULL; + gsize buffer_length; + gssize bytes_read; + gboolean has_cred; + GIOStream *tlsconn = NULL; + + if (G_IS_HTTPS_PROXY (proxy)) + { + tlsconn = g_tls_client_connection_new (io_stream, + G_SOCKET_CONNECTABLE (proxy_address), + error); + if (!tlsconn) + goto error; + +#ifdef DEBUG + { + GTlsCertificateFlags tls_validation_flags = G_TLS_CERTIFICATE_VALIDATE_ALL; + + tls_validation_flags &= ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY); + g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn), + tls_validation_flags); + } +#endif + + if (!g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn), cancellable, error)) + goto error; + + io_stream = tlsconn; + } + + in = g_io_stream_get_input_stream (io_stream); + out = g_io_stream_get_output_stream (io_stream); + + buffer = create_request (proxy_address, &has_cred); + if (!g_output_stream_write_all (out, buffer, strlen (buffer), NULL, + cancellable, error)) + goto error; + + g_free (buffer); + + bytes_read = 0; + buffer_length = 1024; + buffer = g_malloc (buffer_length); + + /* Read byte-by-byte instead of using GDataInputStream + * since we do not want to read beyond the end marker + */ + do + { + gsize nread; + + nread = g_input_stream_read (in, buffer + bytes_read, 1, cancellable, error); + if (nread == -1) + goto error; + + if (nread == 0) + break; + + ++bytes_read; + + if (bytes_read == buffer_length) + { + buffer_length = 2 * buffer_length; + buffer = g_realloc (buffer, buffer_length); + } + + *(buffer + bytes_read) = '\0'; + + if (g_str_has_suffix (buffer, HTTP_END_MARKER)) + break; + } + while (TRUE); + + if (bytes_read == 0) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, + _("HTTP proxy server closed connection unexpectedly.")); + goto error; + } + + if (!check_reply (buffer, has_cred, error)) + goto error; + + g_free (buffer); + + g_object_ref (io_stream); + g_clear_object (&tlsconn); + + return io_stream; + +error: + g_clear_object (&tlsconn); + g_free (buffer); + return NULL; +} + +typedef struct +{ + GIOStream *io_stream; + GProxyAddress *proxy_address; +} ConnectAsyncData; + +static void +free_connect_data (ConnectAsyncData *data) +{ + g_object_unref (data->io_stream); + g_object_unref (data->proxy_address); + g_slice_free (ConnectAsyncData, data); +} + +static void +connect_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GProxy *proxy = source_object; + ConnectAsyncData *data = task_data; + GIOStream *res; + GError *error = NULL; + + res = g_http_proxy_connect (proxy, data->io_stream, data->proxy_address, + cancellable, &error); + + if (res == NULL) + g_task_return_error (task, error); + else + g_task_return_pointer (task, res, g_object_unref); +} + +static void +g_http_proxy_connect_async (GProxy *proxy, + GIOStream *io_stream, + GProxyAddress *proxy_address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ConnectAsyncData *data; + GTask *task; + + data = g_slice_new0 (ConnectAsyncData); + data->io_stream = g_object_ref (io_stream); + data->proxy_address = g_object_ref (proxy_address); + + task = g_task_new (proxy, cancellable, callback, user_data); + g_task_set_task_data (task, data, (GDestroyNotify) free_connect_data); + + g_task_run_in_thread (task, connect_thread); + g_object_unref (task); +} + +static GIOStream * +g_http_proxy_connect_finish (GProxy *proxy, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); +} + +static gboolean +g_http_proxy_supports_hostname (GProxy *proxy) +{ + return TRUE; +} + +static void +g_http_proxy_class_init (GHttpProxyClass *class) +{ +} + +static void +g_http_proxy_iface_init (GProxyInterface *proxy_iface) +{ + proxy_iface->connect = g_http_proxy_connect; + proxy_iface->connect_async = g_http_proxy_connect_async; + proxy_iface->connect_finish = g_http_proxy_connect_finish; + proxy_iface->supports_hostname = g_http_proxy_supports_hostname; +} + +struct _GHttpsProxy +{ + GHttpProxy parent; +}; + +struct _GHttpsProxyClass +{ + GHttpProxyClass parent_class; +}; + +#define g_https_proxy_get_type _g_https_proxy_get_type +G_DEFINE_TYPE_WITH_CODE (GHttpsProxy, g_https_proxy, G_TYPE_HTTP_PROXY, + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY, + g_http_proxy_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME, + g_define_type_id, + "https", + 0)) + +static void +g_https_proxy_init (GHttpsProxy *proxy) +{ +} + +static void +g_https_proxy_class_init (GHttpsProxyClass *class) +{ +} diff --git a/gio/ghttpproxy.h b/gio/ghttpproxy.h new file mode 100644 index 000000000..f87f40945 --- /dev/null +++ b/gio/ghttpproxy.h @@ -0,0 +1,54 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2010 Collabora, Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Nicolas Dufresne + */ + +#ifndef __G_HTTP_PROXY_H__ +#define __G_HTTP_PROXY_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_HTTP_PROXY (_g_http_proxy_get_type ()) +#define G_HTTP_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_HTTP_PROXY, GHttpProxy)) +#define G_HTTP_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_HTTP_PROXY, GHttpProxyClass)) +#define G_IS_HTTP_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_HTTP_PROXY)) +#define G_IS_HTTP_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_HTTP_PROXY)) +#define G_HTTP_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_HTTP_PROXY, GHttpProxyClass)) + +typedef struct _GHttpProxy GHttpProxy; +typedef struct _GHttpProxyClass GHttpProxyClass; + +GType _g_http_proxy_get_type (void); + +#define G_TYPE_HTTPS_PROXY (_g_https_proxy_get_type ()) +#define G_HTTPS_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_HTTPS_PROXY, GHttpsProxy)) +#define G_HTTPS_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_HTTPS_PROXY, GHttpsProxyClass)) +#define G_IS_HTTPS_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_HTTPS_PROXY)) +#define G_IS_HTTPS_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_HTTPS_PROXY)) +#define G_HTTPS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_HTTPS_PROXY, GHttpsProxyClass)) + +typedef struct _GHttpsProxy GHttpsProxy; +typedef struct _GHttpsProxyClass GHttpsProxyClass; + +GType _g_https_proxy_get_type (void); + +G_END_DECLS + +#endif /* __G_HTTP_PROXY_H__ */ diff --git a/gio/giomodule.c b/gio/giomodule.c index b22ea4993..4b16a831c 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -30,6 +30,7 @@ #include "gproxyresolver.h" #include "gproxy.h" #include "gsettingsbackendinternal.h" +#include "ghttpproxy.h" #include "gsocks4proxy.h" #include "gsocks4aproxy.h" #include "gsocks5proxy.h" @@ -1098,6 +1099,8 @@ _g_io_modules_ensure_loaded (void) #endif g_type_ensure (_g_local_vfs_get_type ()); g_type_ensure (_g_dummy_proxy_resolver_get_type ()); + g_type_ensure (_g_http_proxy_get_type ()); + g_type_ensure (_g_https_proxy_get_type ()); g_type_ensure (_g_socks4a_proxy_get_type ()); g_type_ensure (_g_socks4_proxy_get_type ()); g_type_ensure (_g_socks5_proxy_get_type ()); -- cgit v1.2.1