AU: Proxy Resolver classes

A collection of classes (abstract ProxyResolver interface and two
concrete implementations). The abstraction is one that should suit us
moving forward: a string URL is provided and a collection of proxy
servers is returned. One implementation always returns [no
proxy]. Another returns [manual settings from chrome, if applicable,
..., no proxy].

A future concrete implementation will consult Chrome via DBus with the
URL in question, however this is delayed until Chrome exposes such an
API.

As a result of this API missing from Chrome, this CL only resolves
proxies for a URL with manually input proxy settings.

Future CLs will integrate this into the rest of the update system.

BUG=3167
TEST=unit tests, (with future CLs) working proxy support on device

Review URL: http://codereview.chromium.org/5151005

Change-Id: If9dc6d09da681bca6f6ae74c896ba946ab81cb4d
diff --git a/chrome_proxy_resolver.cc b/chrome_proxy_resolver.cc
new file mode 100644
index 0000000..eaa213a
--- /dev/null
+++ b/chrome_proxy_resolver.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/chrome_proxy_resolver.h"
+
+#include <base/json/json_reader.h>
+#include <base/scoped_ptr.h>
+#include <base/values.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const char kSessionManagerService[] = "org.chromium.SessionManager";
+const char kSessionManagerPath[] = "/org/chromium/SessionManager";
+const char kSessionManagerInterface[] = "org.chromium.SessionManagerInterface";
+const char kSessionManagerRetrievePropertyMethod[] =
+    "RetrieveProperty";
+const char kSessionManagerProxySettingsKey[] = "cros.proxy.everywhere";
+
+bool ChromeProxyResolver::GetProxiesForUrl(
+    const std::string& url,
+    std::vector<std::string>* out_proxies) {
+  // First, query dbus for the currently stored settings
+  DBusGProxy* proxy = DbusProxy();
+  TEST_AND_RETURN_FALSE(proxy);
+  string json_settings;
+  TEST_AND_RETURN_FALSE(GetJsonProxySettings(proxy, &json_settings));
+  LOG(INFO) << "got settings:" << json_settings;
+  TEST_AND_RETURN_FALSE(
+      GetProxiesForUrlWithSettings(url, json_settings, out_proxies));
+  return true;
+}
+
+bool ChromeProxyResolver::GetJsonProxySettings(DBusGProxy* proxy,
+                                               std::string* out_json) {
+  gchar* value = NULL;
+  GArray* sig = NULL;
+  GError* error = NULL;
+  TEST_AND_RETURN_FALSE(
+      dbus_->ProxyCall(proxy,
+                       kSessionManagerRetrievePropertyMethod,
+                       &error,
+                       G_TYPE_STRING, kSessionManagerProxySettingsKey,
+                       G_TYPE_INVALID,
+                       G_TYPE_STRING, &value,
+                       DBUS_TYPE_G_UCHAR_ARRAY, &sig,
+                       G_TYPE_INVALID));
+  g_array_free(sig, false);
+  out_json->assign(value);
+  g_free(value);
+  return true;
+}
+
+DBusGProxy* ChromeProxyResolver::DbusProxy() {
+  GError* error = NULL;
+  DBusGConnection* bus = dbus_->BusGet(DBUS_BUS_SYSTEM, &error);
+  TEST_AND_RETURN_FALSE(bus);
+  DBusGProxy* proxy = dbus_->ProxyNewForNameOwner(bus,
+                                                  kSessionManagerService,
+                                                  kSessionManagerPath,
+                                                  kSessionManagerInterface,
+                                                  &error);
+  if (!proxy) {
+    LOG(ERROR) << "Error getting FlimFlam proxy: "
+               << utils::GetGErrorMessage(error);
+  }
+  return proxy;
+}
+
+namespace {
+enum ProxyMode {
+  kProxyModeDirect = 0,
+  kProxyModeAutoDetect,
+  kProxyModePACScript,
+  kProxyModeSingle,
+  kProxyModeProxyPerScheme
+}; 
+}  // namespace {}
+
+bool ChromeProxyResolver::GetProxiesForUrlWithSettings(
+    const string& url,
+    const string& json_settings,
+    std::vector<std::string>* out_proxies) {
+  base::JSONReader parser;
+
+  scoped_ptr<Value> root(
+      parser.JsonToValue(json_settings,
+                         true,  // check root is obj/arr
+                         false));  // false = disallow trailing comma
+  if (!root.get()) {
+    LOG(ERROR) << "Unable to parse \"" << json_settings << "\": "
+               << parser.GetErrorMessage();
+    return false;
+  }
+
+  TEST_AND_RETURN_FALSE(root->IsType(Value::TYPE_DICTIONARY));
+
+  DictionaryValue* root_dict = dynamic_cast<DictionaryValue*>(root.get());
+  TEST_AND_RETURN_FALSE(root_dict);
+  int mode = -1;
+  TEST_AND_RETURN_FALSE(root_dict->GetInteger("mode", &mode));
+
+  LOG(INFO) << "proxy mode: " << mode;
+  if (mode != kProxyModeSingle &&
+      mode != kProxyModeProxyPerScheme) {
+    LOG(INFO) << "unsupported proxy scheme";
+    out_proxies->clear();
+    out_proxies->push_back(kNoProxy);
+    return true;
+  }
+  if (mode == kProxyModeSingle) {
+    LOG(INFO) << "single proxy mode";
+    string proxy_string;
+    TEST_AND_RETURN_FALSE(root_dict->GetString("single.server", &proxy_string));
+    if (proxy_string.find("://") == string::npos) {
+      // missing protocol, assume http.
+      proxy_string = string("http://") + proxy_string;
+    }
+    out_proxies->clear();
+    out_proxies->push_back(proxy_string);
+    LOG(INFO) << "single proxy: " << (*out_proxies)[0];
+    out_proxies->push_back(kNoProxy);
+    return true;
+  }
+  // Proxy per scheme mode.
+  LOG(INFO) << "proxy per scheme mode";
+
+  // Find which scheme we are
+  bool url_is_http = utils::StringHasPrefix(url, "http://");
+  if (!url_is_http)
+    TEST_AND_RETURN_FALSE(utils::StringHasPrefix(url, "https://"));
+
+  // Using "proto_*" variables to refer to http or https
+  const string proto_path = url_is_http ? "http.server" : "https.server";
+  const string socks_path = "socks.server";
+
+  out_proxies->clear();
+
+  string proto_server, socks_server;
+  if (root_dict->GetString(proto_path, &proto_server)) {
+    if (proto_server.find("://") == string::npos) {
+      // missing protocol, assume http.
+      proto_server = string("http://") + proto_server;
+    }
+    out_proxies->push_back(proto_server);
+    LOG(INFO) << "got http/https server: " << proto_server;
+  }
+  if (root_dict->GetString(socks_path, &socks_server)) {
+    out_proxies->push_back(socks_server);
+    LOG(INFO) << "got socks server: " << proto_server;
+  }
+  out_proxies->push_back(kNoProxy);
+  return true;
+}
+
+bool ChromeProxyResolver::GetProxyType(const std::string& proxy,
+                                       curl_proxytype* out_type) {
+  if (utils::StringHasPrefix(proxy, "socks5://") ||
+      utils::StringHasPrefix(proxy, "socks://")) {
+    *out_type = CURLPROXY_SOCKS5_HOSTNAME;
+    return true;
+  }
+  if (utils::StringHasPrefix(proxy, "socks4://")) {
+    *out_type = CURLPROXY_SOCKS4A;
+    return true;
+  }
+  if (utils::StringHasPrefix(proxy, "http://") ||
+      utils::StringHasPrefix(proxy, "https://")) {
+    *out_type = CURLPROXY_HTTP;
+    return true;
+  }
+  if (utils::StringHasPrefix(proxy, kNoProxy)) {
+    // known failure case. don't log.
+    return false;
+  }
+  LOG(INFO) << "Unknown proxy type: " << proxy;
+  return false;
+}
+
+}  // namespace chromeos_update_engine