[glib-networking/mcatanzaro/pacrunner: 4/4] Rewrite pacrunner to not use libproxy




commit bb13baa964b6136a15d717fdc8ce37b9f8e76b64
Author: Michael Catanzaro <mcatanzaro redhat com>
Date:   Sat Feb 19 09:03:46 2022 -0600

    Rewrite pacrunner to not use libproxy
    
    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 #28
    Fixes #5

 meson.build                                        |   9 +-
 meson_options.txt                                  |   1 +
 proxy/libproxy/glibpacrunner.c                     | 173 ----------
 proxy/libproxy/meson.build                         |  37 ---
 proxy/meson.build                                  |   4 +
 .../glib-pacrunner.service.in                      |   2 +-
 proxy/pacrunner/meson.build                        |  54 ++++
 .../org.gtk.GLib.PACRunner.service.in              |   2 +-
 proxy/pacrunner/pacrunner-service.c                | 275 ++++++++++++++++
 proxy/pacrunner/pacrunner-worker.c                 | 359 +++++++++++++++++++++
 proxy/pacrunner/pacutils.h                         | 242 ++++++++++++++
 tls/base/meson.build                               |   3 +-
 tls/gnutls/gtlsdatabase-gnutls.c                   |   1 -
 tls/base/gtlshttp.c => util/ghttp.c                |  10 +-
 tls/base/gtlshttp.h => util/ghttp.h                |   6 +-
 util/meson.build                                   |  12 +
 16 files changed, 965 insertions(+), 225 deletions(-)
---
diff --git a/meson.build b/meson.build
index 86a86f4e..d5479452 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)
 
 # compiler flags
 common_flags = [
@@ -48,7 +49,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'])
@@ -73,6 +74,9 @@ libproxy_dep = dependency('libproxy-1.0', version: '>= 0.3.1', required: get_opt
 # *** 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          ***
@@ -136,9 +140,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 aaf62274..5d0dc74d 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -5,5 +5,6 @@ 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')
diff --git a/proxy/libproxy/meson.build b/proxy/libproxy/meson.build
index 7a766b3d..713683f7 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'
@@ -56,18 +33,4 @@ if get_option('static_modules')
   pkg.generate(module)
 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]]
diff --git a/proxy/meson.build b/proxy/meson.build
index c6384525..5ab32009 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
similarity index 70%
rename from proxy/libproxy/glib-pacrunner.service.in
rename to proxy/pacrunner/glib-pacrunner.service.in
index 0f289de9..6f977d85 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 00000000..1f513a35
--- /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
similarity index 66%
rename from proxy/libproxy/org.gtk.GLib.PACRunner.service.in
rename to proxy/pacrunner/org.gtk.GLib.PACRunner.service.in
index f1bd6990..d8230f8f 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 00000000..0eea9202
--- /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 00000000..7aeb62d4
--- /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 00000000..38260140
--- /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 f25cd351..79acfdc7 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 eef1b168..37f35f06 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
similarity index 96%
rename from tls/base/gtlshttp.c
rename to util/ghttp.c
index 07334c6d..b2441ce8 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
similarity index 85%
rename from tls/base/gtlshttp.h
rename to util/ghttp.h
index 9e527ea5..b3c3b14e 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 00000000..754881da
--- /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)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]