diff options
author | Michael Catanzaro <mcatanzaro@redhat.com> | 2022-02-19 09:03:46 -0600 |
---|---|---|
committer | Michael Catanzaro <mcatanzaro@redhat.com> | 2022-07-21 18:09:40 -0500 |
commit | 06e83aa84267b154ce2aa76dc18412c9c9839a68 (patch) | |
tree | fd600446d72a4ca9761fe93a5012e3422c2218af | |
parent | af191b187e7217e4104e635bd471a1ae89f11650 (diff) | |
download | glib-networking-mcatanzaro/pacrunner.tar.gz |
Rewrite pacrunner to not use libproxymcatanzaro/pacrunner
Red Hat wants to get rid of libproxy, but we do not want to get rid of
proxy autoconfig. So let's do it ourselves. It's not hard.
Fixes #163, fixes #28, fixes #5
-rw-r--r-- | meson.build | 9 | ||||
-rw-r--r-- | meson_options.txt | 1 | ||||
-rw-r--r-- | proxy/libproxy/glibpacrunner.c | 173 | ||||
-rw-r--r-- | proxy/libproxy/meson.build | 37 | ||||
-rw-r--r-- | proxy/meson.build | 4 | ||||
-rw-r--r-- | proxy/pacrunner/glib-pacrunner.service.in (renamed from proxy/libproxy/glib-pacrunner.service.in) | 2 | ||||
-rw-r--r-- | proxy/pacrunner/meson.build | 54 | ||||
-rw-r--r-- | proxy/pacrunner/org.gtk.GLib.PACRunner.service.in (renamed from proxy/libproxy/org.gtk.GLib.PACRunner.service.in) | 2 | ||||
-rw-r--r-- | proxy/pacrunner/pacrunner-service.c | 275 | ||||
-rw-r--r-- | proxy/pacrunner/pacrunner-worker.c | 359 | ||||
-rw-r--r-- | proxy/pacrunner/pacutils.h | 242 | ||||
-rw-r--r-- | tls/base/meson.build | 3 | ||||
-rw-r--r-- | tls/gnutls/gtlsdatabase-gnutls.c | 1 | ||||
-rw-r--r-- | util/ghttp.c (renamed from tls/base/gtlshttp.c) | 10 | ||||
-rw-r--r-- | util/ghttp.h (renamed from tls/base/gtlshttp.h) | 6 | ||||
-rw-r--r-- | util/meson.build | 12 |
16 files changed, 965 insertions, 225 deletions
diff --git a/meson.build b/meson.build index c816dc6..2e05c7b 100644 --- a/meson.build +++ b/meson.build @@ -21,6 +21,7 @@ host_system = host_machine.system() config_h = configuration_data() config_h.set_quoted('GETTEXT_PACKAGE', meson.project_name()) +config_h.set_quoted('LIBEXEC_DIR', libexecdir) config_h.set10('ENABLE_DEBUG_LOGS', get_option('debug_logs')) # compiler flags @@ -49,7 +50,7 @@ if host_system.contains('linux') or host_system == 'android' endif # *** Check GLib GIO *** -glib_dep = dependency('glib-2.0', version: '>= 2.69.0', +glib_dep = dependency('glib-2.0', version: '>= 2.70.0', fallback: ['glib', 'libglib_dep']) gio_dep = dependency('gio-2.0', fallback: ['glib', 'libgio_dep']) @@ -74,6 +75,9 @@ libproxy_dep = dependency('libproxy-1.0', version: '>= 0.4.16', required: get_op # *** Checks for GNOME *** gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas', required: get_option('gnome_proxy')) +# *** Checks for WebKitGTK *** +javascriptcoregtk_dep = dependency('javascriptcoregtk-4.1', required: get_option('pacrunner')) + backends = [] # *** Check for dl *** @@ -137,9 +141,10 @@ endif proxy_test_programs = [] -subdir('po') +subdir('util') subdir('proxy') subdir('tls') +subdir('po') # Will automatically pick it up from the cross file if defined gio_querymodules = find_program('gio-querymodules', required : false) diff --git a/meson_options.txt b/meson_options.txt index f022e3d..e31c87f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,6 +5,7 @@ option('gnutls', type: 'feature', value: 'auto', description: 'support for GnuTL option('openssl', type: 'feature', value: 'disabled', description: 'support for OpenSSL networking configration') option('libproxy', type: 'feature', value: 'auto', description: 'support for libproxy proxy configration') option('gnome_proxy', type: 'feature', value: 'auto', description: 'support for GNOME desktop proxy configuration') +option('pacrunner', type: 'feature', value: 'auto', description: 'support for web proxy autoconfig') option('installed_tests', type: 'boolean', value: false, description: 'enable installed tests') option('static_modules', type: 'boolean', value: false, description: 'build static modules') option('debug_logs', type: 'boolean', value: false, description: 'enable debug log messages (slow)') diff --git a/proxy/libproxy/glibpacrunner.c b/proxy/libproxy/glibpacrunner.c deleted file mode 100644 index c72304f..0000000 --- a/proxy/libproxy/glibpacrunner.c +++ /dev/null @@ -1,173 +0,0 @@ -/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* - * GIO - GLib Input, Output and Streaming Library - * - * Copyright 2011 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.1 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 - * <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include <stdlib.h> - -#include <gio/gio.h> -#include "glibproxyresolver.h" - -static const gchar introspection_xml[] = - "<node>" - " <interface name='org.gtk.GLib.PACRunner'>" - " <method name='Lookup'>" - " <arg type='s' name='pac_url' direction='in'/>" - " <arg type='s' name='lookup_url' direction='in'/>" - " <arg type='as' name='proxies' direction='out'/>" - " </method>" - " </interface>" - "</node>"; - -static GProxyResolver *resolver; -static GMainLoop *loop; - -static void -got_proxies (GObject *source, - GAsyncResult *result, - gpointer user_data) -{ - GDBusMethodInvocation *invocation = user_data; - gchar **proxies; - GError *error = NULL; - - proxies = g_proxy_resolver_lookup_finish (resolver, result, &error); - if (error) - g_dbus_method_invocation_take_error (invocation, error); - else - { - g_dbus_method_invocation_return_value (invocation, - g_variant_new ("(^as)", proxies)); - g_strfreev (proxies); - } -} - -static void -handle_method_call (GDBusConnection *connection, - const gchar *sender, - const gchar *object_path, - const gchar *interface_name, - const gchar *method_name, - GVariant *parameters, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - const gchar *pac_url, *lookup_url; - - g_variant_get (parameters, "(&s&s)", &pac_url, &lookup_url); - - if (!g_ascii_strncasecmp (pac_url, "http", 4) || - !g_ascii_strncasecmp (pac_url, "file:", 5)) - { - gchar *libproxy_url = g_strdup_printf ("pac+%s", pac_url); - g_setenv ("http_proxy", libproxy_url, TRUE); - g_free (libproxy_url); - } - else - g_setenv ("http_proxy", "wpad://", TRUE); - - g_proxy_resolver_lookup_async (resolver, lookup_url, - NULL, got_proxies, invocation); -} - -static const GDBusInterfaceVTable interface_vtable = - { - handle_method_call, - NULL, - NULL - }; - -static void -on_bus_acquired (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - GDBusNodeInfo *introspection_data; - GError *error = NULL; - - introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); - g_dbus_connection_register_object (connection, - "/org/gtk/GLib/PACRunner", - introspection_data->interfaces[0], - &interface_vtable, - NULL, - NULL, - &error); - if (error) - g_error ("Could not register server: %s", error->message); -} - -static void -on_name_acquired (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ -} - -static void -on_name_lost (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - g_main_loop_quit (loop); -} - -int -main (int argc, char *argv[]) -{ - int owner_id; - - /* Unset variables that would make libproxy try to use gconf or ksettings */ - g_unsetenv ("GNOME_DESKTOP_SESSION_ID"); - g_unsetenv ("DESKTOP_SESSION"); - g_unsetenv ("KDE_FULL_SESSION"); - - /* Unset variables that libproxy would look at if it were smarter, and which - * it might possibly look at in the future. Just covering our bases. */ - g_unsetenv ("XDG_CURRENT_DESKTOP"); - - /* Unset static proxy settings */ - g_unsetenv ("http_proxy"); - g_unsetenv ("HTTP_PROXY"); - g_unsetenv ("https_proxy"); - g_unsetenv ("HTTPS_PROXY"); - g_unsetenv ("ftp_proxy"); - g_unsetenv ("FTP_PROXY"); - g_unsetenv ("no_proxy"); - g_unsetenv ("NO_PROXY"); - - resolver = g_object_new (G_TYPE_LIBPROXY_RESOLVER, NULL); - - owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, - "org.gtk.GLib.PACRunner", - G_BUS_NAME_OWNER_FLAGS_NONE, - on_bus_acquired, - on_name_acquired, - on_name_lost, - NULL, - NULL); - - loop = g_main_loop_new (NULL, FALSE); - g_main_loop_run (loop); - - g_bus_unown_name (owner_id); - return 0; -} diff --git a/proxy/libproxy/meson.build b/proxy/libproxy/meson.build index 4db3d48..cc0f62c 100644 --- a/proxy/libproxy/meson.build +++ b/proxy/libproxy/meson.build @@ -1,26 +1,3 @@ -service_conf = configuration_data() -service_conf.set('libexecdir', libexecdir) - -service = 'org.gtk.GLib.PACRunner.service' - -configure_file( - input: service + '.in', - output: service, - install: true, - install_dir: join_paths(datadir, 'dbus-1', 'services'), - configuration: service_conf -) - -service = 'glib-pacrunner.service' - -configure_file( - input: service + '.in', - output: service, - install: true, - install_dir: join_paths('lib', 'systemd', 'user'), - configuration: service_conf -) - sources = files( 'glibproxyresolver.c', 'libproxy-module.c' @@ -59,20 +36,6 @@ if get_option('static_modules') meson.override_dependency('giolibproxy', giolibproxy_dep) endif -sources = files( - 'glibproxyresolver.c', - 'glibpacrunner.c' -) - -executable( - 'glib-pacrunner', - sources, - include_directories: top_inc, - dependencies: deps, - install: true, - install_dir: libexecdir -) - proxy_test_programs += [['environment', 'libproxy', deps]] if meson.version().version_compare('>=0.58') diff --git a/proxy/meson.build b/proxy/meson.build index c638452..5ab3200 100644 --- a/proxy/meson.build +++ b/proxy/meson.build @@ -12,4 +12,8 @@ if not ['windows'].contains(host_system) subdir('environment') endif +if javascriptcoregtk_dep.found() + subdir('pacrunner') +endif + subdir('tests') diff --git a/proxy/libproxy/glib-pacrunner.service.in b/proxy/pacrunner/glib-pacrunner.service.in index 0f289de..6f977d8 100644 --- a/proxy/libproxy/glib-pacrunner.service.in +++ b/proxy/pacrunner/glib-pacrunner.service.in @@ -4,4 +4,4 @@ Description=GLib proxy auto-configuration service [Service] Type=dbus BusName=org.gtk.GLib.PACRunner -ExecStart=@libexecdir@/glib-pacrunner +ExecStart=@libexecdir@/glib-pacrunner-service diff --git a/proxy/pacrunner/meson.build b/proxy/pacrunner/meson.build new file mode 100644 index 0000000..1f513a3 --- /dev/null +++ b/proxy/pacrunner/meson.build @@ -0,0 +1,54 @@ +service_conf = configuration_data() +service_conf.set('libexecdir', libexecdir) + +service = 'org.gtk.GLib.PACRunner.service' + +configure_file( + input: service + '.in', + output: service, + install: true, + install_dir: join_paths(datadir, 'dbus-1', 'services'), + configuration: service_conf +) + +service = 'glib-pacrunner.service' + +configure_file( + input: service + '.in', + output: service, + install: true, + install_dir: join_paths('lib', 'systemd', 'user'), + configuration: service_conf +) + +service_deps = [ + gio_dep, + glib_dep, + gobject_dep, +] + +executable( + 'glib-pacrunner-service', + 'pacrunner-service.c', + include_directories: top_inc, + dependencies: service_deps, + install: true, + install_dir: libexecdir +) + +worker_deps = [ + gio_dep, + glib_dep, + gobject_dep, + javascriptcoregtk_dep, + util_dep +] + +executable( + 'glib-pacrunner-worker', + 'pacrunner-worker.c', + include_directories: top_inc, + dependencies: worker_deps, + install: true, + install_dir: libexecdir +) diff --git a/proxy/libproxy/org.gtk.GLib.PACRunner.service.in b/proxy/pacrunner/org.gtk.GLib.PACRunner.service.in index f1bd699..d8230f8 100644 --- a/proxy/libproxy/org.gtk.GLib.PACRunner.service.in +++ b/proxy/pacrunner/org.gtk.GLib.PACRunner.service.in @@ -1,4 +1,4 @@ [D-BUS Service] Name=org.gtk.GLib.PACRunner -Exec=@libexecdir@/glib-pacrunner +Exec=@libexecdir@/glib-pacrunner-service SystemdService=glib-pacrunner.service diff --git a/proxy/pacrunner/pacrunner-service.c b/proxy/pacrunner/pacrunner-service.c new file mode 100644 index 0000000..0eea920 --- /dev/null +++ b/proxy/pacrunner/pacrunner-service.c @@ -0,0 +1,275 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2011, 2022 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.1 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 + * <http://www.gnu.org/licenses/>. + */ +#include "config.h" + +#include <gio/gio.h> +#include <glib/gi18n.h> +#include <locale.h> +#include <stdlib.h> + +static const gchar introspection_xml[] = + "<node>" + " <interface name='org.gtk.GLib.PACRunner'>" + " <method name='Lookup'>" + " <arg type='s' name='pac_url' direction='in'/>" + " <arg type='s' name='lookup_url' direction='in'/>" + " <arg type='as' name='proxies' direction='out'/>" + " </method>" + " </interface>" + "</node>"; + +static GMainLoop *loop; +static GCancellable *cancellable; + +static void +add_to_results_if_valid (const char *scheme, + const char *server, + GPtrArray *results) +{ + char *url_string; + + if (!server || !*server) + return; + + url_string = g_strconcat (scheme, server, NULL); + if (g_uri_is_valid (url_string, G_URI_FLAGS_NONE, NULL)) + g_ptr_array_add (results, g_steal_pointer (&url_string)); + + g_free (url_string); +} + +/* Loosely based on format_pac_response() in libproxy's proxy.cpp */ +static char ** +format_pac_response (char *response) +{ + char **directives; + GPtrArray *results; + + if (response[0] == ';') + response++; + + response = g_strstrip (response); + directives = g_strsplit (response, ";", 0); + results = g_ptr_array_sized_new (g_strv_length (directives)); + + for (char **remaining_directives = directives; + remaining_directives && *remaining_directives; + remaining_directives++) + { + const char *directive = *remaining_directives; + const char *method; + const char *server; + char **split_directive; + + directive = g_strstrip ((char *)directive); + if (g_ascii_strcasecmp (directive, "direct") == 0) + { + g_ptr_array_add (results, g_strdup ("direct://")); + continue; + } + + split_directive = g_strsplit_set (directive, " \t", 2); + method = split_directive[0]; + server = split_directive[1]; + + if (g_ascii_strcasecmp (method, "proxy") == 0) + add_to_results_if_valid ("http://", server, results); + else if (g_ascii_strcasecmp (method, "socks") == 0) + add_to_results_if_valid ("socks://", server, results); + else if (g_ascii_strcasecmp (method, "socks4") == 0) + add_to_results_if_valid ("socks4://", server, results); + else if (g_ascii_strcasecmp (method, "socks4a") == 0) + add_to_results_if_valid ("socks4a://", server, results); + else if (g_ascii_strcasecmp (method, "socks5") == 0) + add_to_results_if_valid ("socks5://", server, results); + + g_strfreev (split_directive); + } + + g_ptr_array_add (results, NULL); + g_strfreev (directives); + return (char **)g_ptr_array_free (results, FALSE); +} + +static void +subprocess_finished_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSubprocess *subprocess = G_SUBPROCESS (source); + GDBusMethodInvocation *invocation = user_data; + char *stdout_buf = NULL; + char *stderr_buf = NULL; + char **proxies = NULL; + GError *error = NULL; + + g_subprocess_communicate_utf8_finish (subprocess, result, + &stdout_buf, &stderr_buf, + &error); + if (error) + { + g_dbus_method_invocation_take_error (g_steal_pointer (&invocation), error); + goto out; + } + + proxies = format_pac_response (stdout_buf); + g_dbus_method_invocation_return_value (g_steal_pointer (&invocation), + g_variant_new ("(^as)", proxies)); + +out: + g_free (stdout_buf); + g_free (stderr_buf); + g_strfreev (proxies); + g_object_unref (subprocess); +} + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + const char *pac_url, *lookup_url; + GSubprocessLauncher *launcher; + GSubprocess *subprocess; + GError *error = NULL; + + g_variant_get (parameters, "(&s&s)", &pac_url, &lookup_url); + + if (g_ascii_strncasecmp (pac_url, "http:", 5) && + g_ascii_strncasecmp (pac_url, "https:", 6) && + g_ascii_strncasecmp (pac_url, "file:", 5)) + { + g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "PAC URL %s has unsupported protocol", pac_url); + return; + } + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); + subprocess = g_subprocess_launcher_spawn (launcher, &error, + LIBEXEC_DIR "/glib-pacrunner-worker", + pac_url, lookup_url, + NULL); + g_object_unref (launcher); + if (!subprocess) + { + g_prefix_error (&error, _("Failed to spawn pacrunner-worker")); + g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); + g_error_free (error); + return; + } + + g_subprocess_communicate_utf8_async (g_steal_pointer (&subprocess), + NULL, + cancellable, + subprocess_finished_cb, + g_steal_pointer (&invocation)); +} + +static const GDBusInterfaceVTable interface_vtable = + { + handle_method_call, + NULL, + NULL + }; + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GDBusNodeInfo *introspection_data; + GError *error = NULL; + + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_dbus_connection_register_object (connection, + "/org/gtk/GLib/PACRunner", + introspection_data->interfaces[0], + &interface_vtable, + NULL, + NULL, + &error); + if (error) + g_error ("Could not register server: %s", error->message); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_cancellable_cancel (cancellable); + g_main_loop_quit (loop); +} + +int +main (int argc, char *argv[]) +{ + GOptionContext *context; + int owner_id; + GError *error = NULL; + + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + context = g_option_context_new ("Start pacrunner service"); + g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + if (error) + { + g_warning ("Failed to parse options: %s", error->message); + g_error_free (error); + return 1; + } + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gtk.GLib.PACRunner", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + cancellable = g_cancellable_new (); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unown_name (owner_id); + g_main_loop_unref (loop); + g_object_unref (cancellable); + + return 0; +} diff --git a/proxy/pacrunner/pacrunner-worker.c b/proxy/pacrunner/pacrunner-worker.c new file mode 100644 index 0000000..7aeb62d --- /dev/null +++ b/proxy/pacrunner/pacrunner-worker.c @@ -0,0 +1,359 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2022 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.1 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 + * <http://www.gnu.org/licenses/>. + */ + +/* This file is (very loosely) based on libproxy's pacrunner_webkit.cpp. */ + +#include "config.h" + +#include "ghttp.h" +#include "pacutils.h" +#include <gio/gio.h> +#include <glib/gstdio.h> +#include <jsc/jsc.h> +#include <stdlib.h> +#include <unistd.h> + +static char * +dns_resolve (const char *hostname) +{ + GResolver *resolver = g_resolver_get_default (); + GList *addresses; + char *first_address; + GError *error = NULL; + + addresses = g_resolver_lookup_by_name (resolver, + hostname, + NULL, + &error); + if (error) { + g_warning ("Failed to resolve %s: %s", hostname, error->message); + g_error_free (error); + return NULL; + } + + first_address = g_inet_address_to_string (addresses->data); + g_resolver_free_addresses (addresses); + return first_address; +} + +static char * +my_ip_address (void) +{ + char hostname[HOST_NAME_MAX + 1]; + + if (gethostname (hostname, sizeof (hostname)) == -1) + { + g_warning ("Failed to get system hostname: %s", g_strerror (errno)); + return NULL; + } + + return dns_resolve (hostname); +} + +static char * +download_pac (const char *pac_url, + GError **error) +{ + const char *http = g_intern_static_string ("http"); + const char *https = g_intern_static_string ("https"); + const char *file = g_intern_static_string ("file"); + const char *scheme; + char *result = NULL; + GInputStream *pac; + GByteArray *bytes; + guchar buffer[2048]; + gssize n_read; + GFile *f; + + scheme = g_uri_peek_scheme (pac_url); + if (scheme == http || scheme == https) + { + pac = g_request_uri (pac_url, NULL, error); + if (!pac) + return NULL; + } + else if (scheme == file) + { + f = g_file_new_for_uri (pac_url); + pac = G_INPUT_STREAM (g_file_read (f, NULL, error)); + g_object_unref (f); + if (!pac) + return NULL; + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "PAC URL %s has unsupported scheme %s", pac_url, scheme); + return NULL; + } + + bytes = g_byte_array_sized_new (sizeof (buffer)); + do + { + n_read = g_input_stream_read (pac, buffer, sizeof (buffer), + NULL, error); + if (n_read == -1) + { + g_byte_array_free (bytes, TRUE); + return NULL; + } + g_byte_array_append (bytes, buffer, n_read); + } while (n_read > 0); + + result = (char *)g_byte_array_free (bytes, FALSE); + + g_object_unref (pac); + return result; +} + +static char * +evaluate_pac (const char *pac, + const char *lookup_url, + const char *host, + GError **error) +{ + JSCContext *context; + JSCValue *value = NULL; + char *statement = NULL; + char *result = NULL; + JSCException *exception = NULL; + + context = jsc_context_new (); + value = jsc_value_new_function (context, + "dnsResolve", + G_CALLBACK (dns_resolve), NULL, NULL, + G_TYPE_STRING, 1, + G_TYPE_STRING); + jsc_context_set_value (context, "dnsResolve", value); + g_clear_object (&value); + + value = jsc_value_new_function (context, + "myIpAddress", + G_CALLBACK (my_ip_address), NULL, NULL, + G_TYPE_STRING, 0); + jsc_context_set_value (context, "dnsResolve", value); + g_clear_object (&value); + + jsc_context_check_syntax (context, + JAVASCRIPT_ROUTINES, -1, + JSC_CHECK_SYNTAX_MODE_SCRIPT, + NULL, 0, &exception); + if (exception) + g_error ("Fatal: pacrunner JS failed syntax sanity check: %s", jsc_exception_report (exception)); + value = jsc_context_evaluate (context, JAVASCRIPT_ROUTINES, -1); + g_clear_object (&value); + + jsc_context_check_syntax (context, + pac, -1, + JSC_CHECK_SYNTAX_MODE_SCRIPT, + NULL, 0, &exception); + if (exception) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Syntax error in proxy autoconfig script: %s", jsc_exception_report (exception)); + g_object_unref (exception); + goto out; + } + value = jsc_context_evaluate (context, pac, -1); + g_clear_object (&value); + + statement = g_strdup_printf ("FindProxyForURL('%s', '%s');", lookup_url, host); + jsc_context_check_syntax (context, + statement, -1, + JSC_CHECK_SYNTAX_MODE_SCRIPT, + NULL, 0, &exception); + if (exception) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Syntax error in script \"%s\": %s", statement, jsc_exception_report (exception)); + g_object_unref (exception); + goto out; + } + value = jsc_context_evaluate (context, statement, -1); + if (!jsc_value_is_string (value)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Proxy autoconfig script result '%s' is not a string", jsc_value_to_string (value)); + g_clear_object (&value); + } + +out: + if (value) + { + result = jsc_value_to_string (value); + g_object_unref (value); + } + g_free (statement); + g_object_unref (context); + return result; +} + +/* Paranoia: prevent a malicious URL from executing script in the PAC context by + * encoding any use of the quote character ' that it needs to use to break out + * of its intended context. It's generally better to encode everything that's + * not alphanumeric, but in this case that is not possible because the PAC + * script will expect to operate on unencoded URLs, so we really cannot encode + * anything more than necessary. + */ +static char * +encode_single_quotes (const char *input, + GError **error) +{ + GString *str; + const char *c = input; + + if (!g_utf8_validate (input, -1, NULL)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Input is not valid UTF-8"); + return NULL; + } + + str = g_string_new (NULL); + do + { + gunichar u = g_utf8_get_char (c); + if (u == '\'') + g_string_append_printf (str, "\\u%04u", u); + else + g_string_append_unichar (str, u); + c = g_utf8_next_char (c); + } while (*c); + + return g_string_free (str, FALSE); +} + +static void +process_lookup_url (const char *lookup_url, + char **out_sanitized_url, + char **out_sanitized_host, + GError **error) +{ + GUri *uri; + GUri *tmp; + char *url_string = NULL; + char *encoded_url = NULL; + char *encoded_host = NULL; + + uri = g_uri_parse (lookup_url, G_URI_FLAGS_NONE, error); + if (!uri) + return; + + /* In the future, we probably want to sanitize all URLs down to only + * protocol://host:port, but for now browsers only do this for https + * URLs, so let's remain compatible. + */ + if (strcmp (g_uri_get_scheme (uri), "https") == 0) + { + tmp = g_uri_build (G_URI_FLAGS_NONE, + g_uri_get_scheme (uri), + NULL, + g_uri_get_host (uri), + g_uri_get_port (uri), + NULL, NULL, NULL); + g_uri_unref (uri); + uri = g_steal_pointer (&tmp); + } + + url_string = g_uri_to_string (uri); + encoded_url = encode_single_quotes (url_string, error); + if (!encoded_url) + goto out; + + encoded_host = encode_single_quotes (g_uri_get_host (uri), error); + if (!encoded_url) + goto out; + + *out_sanitized_url = g_steal_pointer (&encoded_url); + *out_sanitized_host = g_steal_pointer (&encoded_host); + +out: + g_uri_unref (uri); + g_free (url_string); + g_free (encoded_url); + g_free (encoded_host); +} + +int +main (int argc, char *argv[]) +{ + GOptionContext *context; + const char *pac_url; + const char *lookup_url; + char *sanitized_url = NULL; + char *sanitized_host = NULL; + char *pac = NULL; + char *result = NULL; + int exit_status = 1; + GError *error = NULL; + + context = g_option_context_new ("PAC_URL LOOKUP_URL"); + g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + if (error) + { + g_warning ("Failed to parse options: %s", error->message); + g_error_free (error); + goto out; + } + + if (argc != 3) + { + g_fprintf (stderr, "Usage: %s PAC_URL LOOKUP_URL\n", argv[0]); + goto out; + } + + pac_url = argv[1]; + lookup_url = argv[2]; + + process_lookup_url (lookup_url, &sanitized_url, &sanitized_host, &error); + if (error) + { + g_warning ("Failed to parse lookup URL %s: %s", lookup_url, error->message); + g_error_free (error); + goto out; + } + + pac = download_pac (pac_url, &error); + if (!pac) + { + g_warning ("Failed to download proxy autoconfig script %s: %s", pac_url, error->message); + g_error_free (error); + goto out; + } + + result = evaluate_pac (pac, sanitized_url, sanitized_host, &error); + if (!result) + { + g_warning ("Failed to resolve proxy for URL %s using proxy autoconfig script %s: %s", + lookup_url, pac_url, error->message); + g_error_free (error); + goto out; + } + + g_printf ("%s\n", result); + exit_status = 0; + +out: + g_free (pac); + g_free (sanitized_url); + g_free (sanitized_host); + + return exit_status; +} diff --git a/proxy/pacrunner/pacutils.h b/proxy/pacrunner/pacutils.h new file mode 100644 index 0000000..3826014 --- /dev/null +++ b/proxy/pacrunner/pacutils.h @@ -0,0 +1,242 @@ +/* + * The following Javascript code was taken from Mozilla (http://www.mozilla.org) + * and is licensed according to the LGPLv2.1+. Below is the original copyright + * header as contained in the source file (nsProxyAutoConfig.js) + */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Akhil Arora <akhil.arora@sun.com> + * Tomi Leppikangas <Tomi.Leppikangas@oulu.fi> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#define JAVASCRIPT_ROUTINES \ +"function dnsDomainIs(host, domain) {\n" \ +" return (host.length >= domain.length &&\n" \ +" host.substring(host.length - domain.length) == domain);\n" \ +"}\n" \ +"function dnsDomainLevels(host) {\n" \ +" return host.split('.').length-1;\n" \ +"}\n" \ +"function convert_addr(ipchars) {\n" \ +" var bytes = ipchars.split('.');\n" \ +" var result = ((bytes[0] & 0xff) << 24) |\n" \ +" ((bytes[1] & 0xff) << 16) |\n" \ +" ((bytes[2] & 0xff) << 8) |\n" \ +" (bytes[3] & 0xff);\n" \ +" return result;\n" \ +"}\n" \ +"function isInNet(ipaddr, pattern, maskstr) {\n"\ +" var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n"\ +" if (test == null) {\n"\ +" ipaddr = dnsResolve(ipaddr);\n"\ +" if (ipaddr == null)\n"\ +" return false;\n"\ +" } else if (test[1] > 255 || test[2] > 255 || \n"\ +" test[3] > 255 || test[4] > 255) {\n"\ +" return false; // not an IP address\n"\ +" }\n"\ +" var host = convert_addr(ipaddr);\n"\ +" var pat = convert_addr(pattern);\n"\ +" var mask = convert_addr(maskstr);\n"\ +" return ((host & mask) == (pat & mask));\n"\ +" \n"\ +"}\n"\ +"function isPlainHostName(host) {\n" \ +" return (host.search('\\\\.') == -1);\n" \ +"}\n" \ +"function isResolvable(host) {\n" \ +" var ip = dnsResolve(host);\n" \ +" return (ip != null);\n" \ +"}\n" \ +"function localHostOrDomainIs(host, hostdom) {\n" \ +" if (isPlainHostName(host)) {\n" \ +" return (hostdom.search('/^' + host + '/') != -1);\n" \ +" }\n" \ +" else {\n" \ +" return (host == hostdom);\n" \ +" }\n" \ +"}\n" \ +"function shExpMatch(url, pattern) {\n" \ +" pattern = pattern.replace(/\\./g, '\\\\.');\n" \ +" pattern = pattern.replace(/\\*/g, '.*');\n" \ +" pattern = pattern.replace(/\\?/g, '.');\n" \ +" var newRe = new RegExp('^'+pattern+'$');\n" \ +" return newRe.test(url);\n" \ +"}\n" \ +"var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" \ +"var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n"\ +"function weekdayRange() {\n" \ +" function getDay(weekday) {\n" \ +" if (weekday in wdays) {\n" \ +" return wdays[weekday];\n" \ +" }\n" \ +" return -1;\n" \ +" }\n" \ +" var date = new Date();\n" \ +" var argc = arguments.length;\n" \ +" var wday;\n" \ +" if (argc < 1)\n" \ +" return false;\n" \ +" if (arguments[argc - 1] == 'GMT') {\n" \ +" argc--;\n" \ +" wday = date.getUTCDay();\n" \ +" } else {\n" \ +" wday = date.getDay();\n" \ +" }\n" \ +" var wd1 = getDay(arguments[0]);\n" \ +" var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" \ +" return (wd1 == -1 || wd2 == -1) ? false\n" \ +" : (wd1 <= wday && wday <= wd2);\n" \ +"}\n" \ +"function dateRange() {\n" \ +" function getMonth(name) {\n" \ +" if (name in months) {\n" \ +" return months[name];\n" \ +" }\n" \ +" return -1;\n" \ +" }\n" \ +" var date = new Date();\n" \ +" var argc = arguments.length;\n" \ +" if (argc < 1) {\n" \ +" return false;\n" \ +" }\n" \ +" var isGMT = (arguments[argc - 1] == 'GMT');\n" \ +" if (isGMT) {\n" \ +" argc--;\n" \ +" }\n" \ +" if (argc == 1) {\n" \ +" var tmp = parseInt(arguments[0]);\n" \ +" if (isNaN(tmp)) {\n" \ +" return ((isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]));\n" \ +" } else if (tmp < 32) {\n" \ +" return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" \ +" } else {\n" \ +" return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp);\n" \ +" }\n" \ +" }\n" \ +" var year = date.getFullYear();\n" \ +" var date1, date2;\n" \ +" date1 = new Date(year, 0, 1, 0, 0, 0);\n" \ +" date2 = new Date(year, 11, 31, 23, 59, 59);\n" \ +" var adjustMonth = false;\n" \ +" for (var i = 0; i < (argc >> 1); i++) {\n" \ +" var tmp = parseInt(arguments[i]);\n" \ +" if (isNaN(tmp)) {\n" \ +" var mon = getMonth(arguments[i]);\n" \ +" date1.setMonth(mon);\n" \ +" } else if (tmp < 32) {\n" \ +" adjustMonth = (argc <= 2);\n" \ +" date1.setDate(tmp);\n" \ +" } else {\n" \ +" date1.setFullYear(tmp);\n" \ +" }\n" \ +" }\n" \ +" for (var i = (argc >> 1); i < argc; i++) {\n" \ +" var tmp = parseInt(arguments[i]);\n" \ +" if (isNaN(tmp)) {\n" \ +" var mon = getMonth(arguments[i]);\n" \ +" date2.setMonth(mon);\n" \ +" } else if (tmp < 32) {\n" \ +" date2.setDate(tmp);\n" \ +" } else {\n" \ +" date2.setFullYear(tmp);\n" \ +" }\n" \ +" }\n" \ +" if (adjustMonth) {\n" \ +" date1.setMonth(date.getMonth());\n" \ +" date2.setMonth(date.getMonth());\n" \ +" }\n" \ +" if (isGMT) {\n" \ +" var tmp = date;\n" \ +" tmp.setFullYear(date.getUTCFullYear());\n" \ +" tmp.setMonth(date.getUTCMonth());\n" \ +" tmp.setDate(date.getUTCDate());\n" \ +" tmp.setHours(date.getUTCHours());\n" \ +" tmp.setMinutes(date.getUTCMinutes());\n" \ +" tmp.setSeconds(date.getUTCSeconds());\n" \ +" date = tmp;\n" \ +" }\n" \ +" return ((date1 <= date) && (date <= date2));\n" \ +"}\n" \ +"function timeRange() {\n" \ +" var argc = arguments.length;\n" \ +" var date = new Date();\n" \ +" var isGMT= false;\n" \ +" if (argc < 1) {\n" \ +" return false;\n" \ +" }\n" \ +" if (arguments[argc - 1] == 'GMT') {\n" \ +" isGMT = true;\n" \ +" argc--;\n" \ +" }\n" \ +" var hour = isGMT ? date.getUTCHours() : date.getHours();\n" \ +" var date1, date2;\n" \ +" date1 = new Date();\n" \ +" date2 = new Date();\n" \ +" if (argc == 1) {\n" \ +" return (hour == arguments[0]);\n" \ +" } else if (argc == 2) {\n" \ +" return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" \ +" } else {\n" \ +" switch (argc) {\n" \ +" case 6:\n" \ +" date1.setSeconds(arguments[2]);\n" \ +" date2.setSeconds(arguments[5]);\n" \ +" case 4:\n" \ +" var middle = argc >> 1;\n" \ +" date1.setHours(arguments[0]);\n" \ +" date1.setMinutes(arguments[1]);\n" \ +" date2.setHours(arguments[middle]);\n" \ +" date2.setMinutes(arguments[middle + 1]);\n" \ +" if (middle == 2) {\n" \ +" date2.setSeconds(59);\n" \ +" }\n" \ +" break;\n" \ +" default:\n" \ +" throw 'timeRange: bad number of arguments'\n" \ +" }\n" \ +" }\n" \ +" if (isGMT) {\n" \ +" date.setFullYear(date.getUTCFullYear());\n" \ +" date.setMonth(date.getUTCMonth());\n" \ +" date.setDate(date.getUTCDate());\n" \ +" date.setHours(date.getUTCHours());\n" \ +" date.setMinutes(date.getUTCMinutes());\n" \ +" date.setSeconds(date.getUTCSeconds());\n" \ +" }\n" \ +" return ((date1 <= date) && (date <= date2));\n" \ +"}\n" \ +"" diff --git a/tls/base/meson.build b/tls/base/meson.build index f25cd35..79acfdc 100644 --- a/tls/base/meson.build +++ b/tls/base/meson.build @@ -1,6 +1,5 @@ tlsbase_sources = files( 'gtlsconnection-base.c', - 'gtlshttp.c', 'gtlsinputstream.c', 'gtlslog.c', 'gtlsoutputstream.c', @@ -13,4 +12,4 @@ tlsbase = static_library('tlsbase', tlsbase_dep = declare_dependency(link_with: tlsbase, include_directories: include_directories('.'), - dependencies: gio_dep) + dependencies: [gio_dep, util_dep]) diff --git a/tls/gnutls/gtlsdatabase-gnutls.c b/tls/gnutls/gtlsdatabase-gnutls.c index eef1b16..37f35f0 100644 --- a/tls/gnutls/gtlsdatabase-gnutls.c +++ b/tls/gnutls/gtlsdatabase-gnutls.c @@ -34,7 +34,6 @@ #include <gnutls/x509.h> #include "gtlscertificate-gnutls.h" -#include "gtlshttp.h" #include "gtlsgnutls-version.h" typedef struct diff --git a/tls/base/gtlshttp.c b/util/ghttp.c index 07334c6..b2441ce 100644 --- a/tls/base/gtlshttp.c +++ b/util/ghttp.c @@ -28,7 +28,7 @@ #include <dlfcn.h> #endif -#include "gtlshttp.h" +#include "ghttp.h" typedef gpointer SoupSession; typedef gpointer SoupMessage; @@ -107,7 +107,7 @@ init_libsoup (void) } /** - * g_tls_request_uri: + * g_request_uri: * @uri: An HTTP URI to request * @cancellable: (nullable): A #GCancellable * @error: A #GError @@ -119,9 +119,9 @@ init_libsoup (void) * Returns: A #GInputStream of the response body or %NULL on failure */ GInputStream * -g_tls_request_uri (const char *uri, - GCancellable *cancellable, - GError **error) +g_request_uri (const char *uri, + GCancellable *cancellable, + GError **error) { GInputStream *istream = NULL; diff --git a/tls/base/gtlshttp.h b/util/ghttp.h index 9e527ea..b3c3b14 100644 --- a/tls/base/gtlshttp.h +++ b/util/ghttp.h @@ -26,6 +26,6 @@ #include <gio/gio.h> -GInputStream *g_tls_request_uri (const char *uri, - GCancellable *cancellable, - GError **error); +GInputStream *g_request_uri (const char *uri, + GCancellable *cancellable, + GError **error); diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..754881d --- /dev/null +++ b/util/meson.build @@ -0,0 +1,12 @@ +util_sources = files( + 'ghttp.c' +) + +util = static_library('util', + util_sources, + dependencies: [gio_dep, gmodule_dep], + include_directories: top_inc) + +util_dep = declare_dependency(link_with: util, + include_directories: include_directories('.'), + dependencies: gio_dep) |