diff options
author | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-28 23:13:43 +0000 |
---|---|---|
committer | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-28 23:13:43 +0000 |
commit | d7395e73bc35be3247bdcf5c68fd2fff7497a78c (patch) | |
tree | 7bb0daf83b0675100a6b974e2342be68fdf1028e /net/proxy | |
parent | f9bcd26a1e7bbe4d4f4bbbc72ba40e59d3be1fbb (diff) | |
download | chromium_src-d7395e73bc35be3247bdcf5c68fd2fff7497a78c.zip chromium_src-d7395e73bc35be3247bdcf5c68fd2fff7497a78c.tar.gz chromium_src-d7395e73bc35be3247bdcf5c68fd2fff7497a78c.tar.bz2 |
Linux: get GNOME or KDE proxy settings.
BUG=17363, 20407
TEST=none
Review URL: http://codereview.chromium.org/174327
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24831 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/proxy')
-rw-r--r-- | net/proxy/proxy_config_service_linux.cc | 628 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_linux.h | 45 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_linux_unittest.cc | 55 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 20 | ||||
-rw-r--r-- | net/proxy/proxy_service.h | 9 |
5 files changed, 615 insertions, 142 deletions
diff --git a/net/proxy/proxy_config_service_linux.cc b/net/proxy/proxy_config_service_linux.cc index 2998a0c..a3a9993 100644 --- a/net/proxy/proxy_config_service_linux.cc +++ b/net/proxy/proxy_config_service_linux.cc @@ -4,13 +4,23 @@ #include "net/proxy/proxy_config_service_linux.h" +#include <errno.h> +#include <fcntl.h> #include <gconf/gconf-client.h> +#include <limits.h> +#include <stdio.h> #include <stdlib.h> +#include <sys/inotify.h> +#include <unistd.h> +#include "base/file_path.h" +#include "base/file_util.h" #include "base/logging.h" +#include "base/message_loop.h" #include "base/string_tokenizer.h" #include "base/string_util.h" #include "base/task.h" +#include "base/timer.h" #include "googleurl/src/url_canon.h" #include "net/base/net_errors.h" #include "net/http/http_util.h" @@ -55,6 +65,10 @@ std::string FixupProxyHostScheme(ProxyServer::Scheme scheme, host = "socks4://" + host; else if (scheme == ProxyServer::SCHEME_SOCKS5) host = "socks5://" + host; + // If there is a trailing slash, remove it so |host| will parse correctly + // even if it includes a port number (since the slash is not numeric). + if (host.length() && host[host.length() - 1] == '/') + host.resize(host.length() - 1); return host; } @@ -155,30 +169,16 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromEnv(ProxyConfig* config) { namespace { -// static -// gconf notification callback, dispatched from the default -// glib main loop. -void OnGConfChangeNotification( - GConfClient* client, guint cnxn_id, - GConfEntry* entry, gpointer user_data) { - // It would be nice to debounce multiple callbacks in quick - // succession, since I guess we'll get one for each changed key. As - // it is we will read settings from gconf once for each callback. - LOG(INFO) << "gconf change notification for key " - << gconf_entry_get_key(entry); - // We don't track which key has changed, just that something did change. - // Forward to a method on the proxy config service delegate object. - ProxyConfigServiceLinux::Delegate* config_service_delegate = - reinterpret_cast<ProxyConfigServiceLinux::Delegate*>(user_data); - config_service_delegate->OnCheckProxyConfigSettings(); -} +const int kDebounceTimeoutMilliseconds = 250; -class GConfSettingGetterImpl +// This is the "real" gconf version that actually uses gconf. +class GConfSettingGetterImplGConf : public ProxyConfigServiceLinux::GConfSettingGetter { public: - GConfSettingGetterImpl() : client_(NULL), loop_(NULL) {} + GConfSettingGetterImplGConf() + : client_(NULL), notify_delegate_(NULL), loop_(NULL) {} - virtual ~GConfSettingGetterImpl() { + virtual ~GConfSettingGetterImplGConf() { // client_ should have been released before now, from // Delegate::OnDestroy(), while running on the UI thread. However // on exiting the process, it may happen that @@ -191,20 +191,22 @@ class GConfSettingGetterImpl // We are on the UI thread so we can clean it safely. This is // the case at least for ui_tests running under Valgrind in // bug 16076. - LOG(INFO) << "~GConfSettingGetterImpl: releasing gconf client"; - Release(); + LOG(INFO) << "~GConfSettingGetterImplGConf: releasing gconf client"; + Shutdown(); } else { - LOG(WARNING) << "~GConfSettingGetterImpl: leaking gconf client"; + LOG(WARNING) << "~GConfSettingGetterImplGConf: leaking gconf client"; client_ = NULL; } } DCHECK(!client_); } - virtual bool Init() { + virtual bool Init(MessageLoop* glib_default_loop, + MessageLoopForIO* file_loop) { + DCHECK(MessageLoop::current() == glib_default_loop); DCHECK(!client_); DCHECK(!loop_); - loop_ = MessageLoopForUI::current(); + loop_ = glib_default_loop; client_ = gconf_client_get_default(); if (!client_) { // It's not clear whether/when this can return NULL. @@ -224,13 +226,13 @@ class GConfSettingGetterImpl if (error != NULL) { LOG(ERROR) << "Error requesting gconf directory: " << error->message; g_error_free(error); - Release(); + Shutdown(); return false; } return true; } - void Release() { + void Shutdown() { if (client_) { DCHECK(MessageLoop::current() == loop_); // This also disables gconf notifications. @@ -240,29 +242,38 @@ class GConfSettingGetterImpl } } - bool SetupNotification(void* callback_user_data) { + bool SetupNotification(ProxyConfigServiceLinux::Delegate* delegate) { DCHECK(client_); DCHECK(MessageLoop::current() == loop_); GError* error = NULL; + notify_delegate_ = delegate; gconf_client_notify_add( client_, "/system/proxy", - OnGConfChangeNotification, callback_user_data, + OnGConfChangeNotification, this, NULL, &error); if (error == NULL) { gconf_client_notify_add( client_, "/system/http_proxy", - OnGConfChangeNotification, callback_user_data, + OnGConfChangeNotification, this, NULL, &error); } if (error != NULL) { LOG(ERROR) << "Error requesting gconf notifications: " << error->message; g_error_free(error); - Release(); + Shutdown(); return false; } return true; } + MessageLoop* GetNotificationLoop() { + return loop_; + } + + virtual const char* GetDataSource() { + return "gconf"; + } + virtual bool GetString(const char* key, std::string* result) { DCHECK(client_); DCHECK(MessageLoop::current() == loop_); @@ -345,14 +356,451 @@ class GConfSettingGetterImpl return false; } + // This is the callback from the debounce timer. + void OnDebouncedNotification() { + DCHECK(MessageLoop::current() == loop_); + DCHECK(notify_delegate_); + // Forward to a method on the proxy config service delegate object. + notify_delegate_->OnCheckProxyConfigSettings(); + } + + void OnChangeNotification() { + // We don't use Reset() because the timer may not yet be running. + // (In that case Stop() is a no-op.) + debounce_timer_.Stop(); + debounce_timer_.Start(base::TimeDelta::FromMilliseconds( + kDebounceTimeoutMilliseconds), this, + &GConfSettingGetterImplGConf::OnDebouncedNotification); + } + + // gconf notification callback, dispatched from the default glib main loop. + static void OnGConfChangeNotification( + GConfClient* client, guint cnxn_id, + GConfEntry* entry, gpointer user_data) { + LOG(INFO) << "gconf change notification for key " + << gconf_entry_get_key(entry); + // We don't track which key has changed, just that something did change. + GConfSettingGetterImplGConf* setting_getter = + reinterpret_cast<GConfSettingGetterImplGConf*>(user_data); + setting_getter->OnChangeNotification(); + } + GConfClient* client_; + ProxyConfigServiceLinux::Delegate* notify_delegate_; + base::OneShotTimer<GConfSettingGetterImplGConf> debounce_timer_; // Message loop of the thread that we make gconf calls on. It should // be the UI thread and all our methods should be called on this // thread. Only for assertions. MessageLoop* loop_; - DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImpl); + DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImplGConf); +}; + +// This is the KDE version that reads kioslaverc and simulates gconf. +// Doing this allows the main Delegate code, as well as the unit tests +// for it, to stay the same - and the settings map fairly well besides. +class GConfSettingGetterImplKDE + : public ProxyConfigServiceLinux::GConfSettingGetter, + public base::MessagePumpLibevent::Watcher { + public: + explicit GConfSettingGetterImplKDE( + base::EnvironmentVariableGetter* env_var_getter) + : inotify_fd_(-1), notify_delegate_(NULL), indirect_manual_(false), + auto_no_pac_(false), reversed_exception_(false), file_loop_(NULL) { + // We don't save the env var getter for later use since we don't own it. + // Instead we use it here and save the result we actually care about. + std::string kde_home; + if (!env_var_getter->Getenv("KDE_HOME", &kde_home)) { + if (!env_var_getter->Getenv("HOME", &kde_home)) + // User has no $HOME? Give up. Later we'll report the failure. + return; + kde_home = FilePath(kde_home).Append(FILE_PATH_LITERAL(".kde")).value(); + } + kde_config_dir_ = FilePath(kde_home).Append( + FILE_PATH_LITERAL("share")).Append(FILE_PATH_LITERAL("config")); + } + + virtual ~GConfSettingGetterImplKDE() { + // inotify_fd_ should have been closed before now, from + // Delegate::OnDestroy(), while running on the file thread. However + // on exiting the process, it may happen that Delegate::OnDestroy() + // task is left pending on the file loop after the loop was quit, + // and pending tasks may then be deleted without being run. + // Here in the KDE version, we can safely close the file descriptor + // anyway. (Not that it really matters; the process is exiting.) + if (inotify_fd_ >= 0) + Shutdown(); + DCHECK(inotify_fd_ < 0); + } + + virtual bool Init(MessageLoop* glib_default_loop, + MessageLoopForIO* file_loop) { + DCHECK(inotify_fd_ < 0); + inotify_fd_ = inotify_init(); + if (inotify_fd_ < 0) { + LOG(ERROR) << "inotify_init failed: " << strerror(errno); + return false; + } + int flags = fcntl(inotify_fd_, F_GETFL); + if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) { + LOG(ERROR) << "fcntl failed: " << strerror(errno); + close(inotify_fd_); + inotify_fd_ = -1; + return false; + } + file_loop_ = file_loop; + // The initial read is done on the current thread, not |file_loop_|, + // since we will need to have it for SetupAndFetchInitialConfig(). + UpdateCachedSettings(); + return true; + } + + void Shutdown() { + if (inotify_fd_ >= 0) { + ResetCachedSettings(); + inotify_watcher_.StopWatchingFileDescriptor(); + close(inotify_fd_); + inotify_fd_ = -1; + } + } + + bool SetupNotification(ProxyConfigServiceLinux::Delegate* delegate) { + DCHECK(inotify_fd_ >= 0); + DCHECK(file_loop_); + // We can't just watch the kioslaverc file directly, since KDE will write + // a new copy of it and then rename it whenever settings are changed and + // inotify watches inodes (so we'll be watching the old deleted file after + // the first change, and it will never change again). So, we watch the + // directory instead. We then act only on changes to the kioslaverc entry. + if (inotify_add_watch(inotify_fd_, kde_config_dir_.value().c_str(), + IN_MODIFY | IN_MOVED_TO) < 0) + return false; + notify_delegate_ = delegate; + return file_loop_->WatchFileDescriptor(inotify_fd_, true, + MessageLoopForIO::WATCH_READ, &inotify_watcher_, this); + } + + MessageLoop* GetNotificationLoop() { + return file_loop_; + } + + // Implement base::MessagePumpLibevent::Delegate. + void OnFileCanReadWithoutBlocking(int fd) { + DCHECK(fd == inotify_fd_); + DCHECK(MessageLoop::current() == file_loop_); + OnChangeNotification(); + } + void OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); + } + + virtual const char* GetDataSource() { + return "KDE"; + } + + virtual bool GetString(const char* key, std::string* result) { + string_map_type::iterator it = string_table_.find(key); + if (it == string_table_.end()) + return false; + *result = it->second; + return true; + } + virtual bool GetBoolean(const char* key, bool* result) { + // We don't ever have any booleans. + return false; + } + virtual bool GetInt(const char* key, int* result) { + // We don't ever have any integers. (See AddProxy() below about ports.) + return false; + } + virtual bool GetStringList(const char* key, + std::vector<std::string>* result) { + strings_map_type::iterator it = strings_table_.find(key); + if (it == strings_table_.end()) + return false; + *result = it->second; + return true; + } + + private: + void ResetCachedSettings() { + string_table_.clear(); + strings_table_.clear(); + indirect_manual_ = false; + auto_no_pac_ = false; + reversed_exception_ = false; + } + + void AddProxy(std::string prefix, std::string value) { + if (value.empty() || value.substr(0, 3) == "//:") + // No proxy. + return; + // We don't need to parse the port number out; GetProxyFromGConf() + // would only append it right back again. So we just leave the port + // number right in the host string. + string_table_[prefix + "host"] = value; + } + + void AddKDESetting(std::string key, const char* value) { + // The astute reader may notice that there is no mention of SOCKS + // here. That's because KDE handles socks is a strange way, and we + // don't support it. Rather than just a setting for the SOCKS server, + // it has a setting for a library to LD_PRELOAD in all your programs + // that will transparently SOCKSify them. Such libraries each have + // their own configuration, and thus, we can't get it from KDE. + if (key == "ProxyType") { + const char* mode = "none"; + indirect_manual_ = false; + auto_no_pac_ = false; + switch (StringToInt(value)) { + case 0: // No proxy, or maybe kioslaverc syntax error. + break; + case 1: // Manual configuration. + mode = "manual"; + break; + case 2: // PAC URL. + mode = "auto"; + break; + case 3: // WPAD. + mode = "auto"; + auto_no_pac_ = true; + break; + case 4: // Indirect manual via environment variables. + mode = "manual"; + indirect_manual_ = true; + break; + } + string_table_["/system/proxy/mode"] = mode; + } else if (key == "Proxy Config Script") { + string_table_["/system/proxy/autoconfig_url"] = value; + } else if (key == "httpProxy") { + AddProxy("/system/http_proxy/", value); + } else if (key == "httpsProxy") { + AddProxy("/system/proxy/secure_", value); + } else if (key == "ftpProxy") { + AddProxy("/system/proxy/ftp_", value); + } else if (key == "ReversedException") { + // We count "true" or any nonzero number as true, otherwise false. + // Note that if the value is not actually numeric StringToInt() + // will return 0, which we count as false. + reversed_exception_ = !strcmp(value, "true") || StringToInt(value); + } else if (key == "NoProxyFor") { + std::vector<std::string> exceptions; + StringTokenizer tk(value, ","); + while (tk.GetNext()) { + std::string token = tk.token(); + if (!token.empty()) + exceptions.push_back(token); + } + strings_table_["/system/http_proxy/ignore_hosts"] = exceptions; + } else if (key == "AuthMode") { + // Check for authentication, just so we can warn. + int mode = StringToInt(value); + if (mode) { + // ProxyConfig does not support authentication parameters, but + // Chrome will prompt for the password later. So we ignore this. + LOG(WARNING) << + "Proxy authentication parameters ignored, see bug 16709"; + } + } + } + + void ResolveIndirect(std::string key) { + // We can't save the environment variable getter that was passed + // when this object was constructed, but this setting is likely + // to be pretty unusual and the actual values it would return can + // be tested without using it. So we just use getenv() here. + string_map_type::iterator it = string_table_.find(key); + if (it != string_table_.end()) { + char* value = getenv(it->second.c_str()); + if (value) + it->second = value; + } + } + + // The settings in kioslaverc could occur in any order, but some affect + // others. Rather than read the whole file in and then query them in an + // order that allows us to handle that, we read the settings in whatever + // order they occur and do any necessary tweaking after we finish. + void ResolveModeEffects() { + if (indirect_manual_) { + ResolveIndirect("/system/http_proxy/host"); + ResolveIndirect("/system/proxy/secure_host"); + ResolveIndirect("/system/proxy/ftp_host"); + } + if (auto_no_pac_) { + // Remove the PAC URL; we're not supposed to use it. + string_table_.erase("/system/proxy/autoconfig_url"); + } + if (reversed_exception_) { + // We don't actually support this setting. (It means to use the proxy + // *only* for the exception list, rather than everything but them.) + // Nevertheless we can do better than *exactly the opposite* of the + // desired behavior by clearing the exception list and warning. + strings_table_.erase("/system/http_proxy/ignore_hosts"); + LOG(WARNING) << "KDE reversed proxy exception list not supported"; + } + } + + // Reads kioslaverc one line at a time and calls AddKDESetting() to add + // each relevant name-value pair to the appropriate value table. + void UpdateCachedSettings() { + FilePath kioslaverc = kde_config_dir_.Append( + FILE_PATH_LITERAL("kioslaverc")); + file_util::ScopedFILE input(file_util::OpenFile(kioslaverc, "r")); + if (!input.get()) + return; + ResetCachedSettings(); + bool in_proxy_settings = false; + bool line_too_long = false; + char line[512]; + // feof() and ferror() only tell you if you have hit EOF or an error + // after you try to read some data that encounters that condition. So, + // the initialize statement of this loop is the same as the update + // statement. We need to start out each iteration with fgets(). + while (fgets(line, sizeof(line), input.get())) { + // fgets() guarantees the line will be properly terminated. + size_t length = strlen(line); + if (!length) + continue; + // This should be true even with CRLF endings. + if (line[length - 1] != '\n') { + line_too_long = true; + continue; + } + if (line_too_long) { + // The previous line had no line ending, but this done does. This is + // the end of the line that was too long, so warn here and skip it. + LOG(WARNING) << "skipped very long line in " << kioslaverc.value(); + line_too_long = false; + continue; + } + // Remove the LF at the end, and the CR if there is one. + line[--length] = '\0'; + if (length && line[length - 1] == '\r') + line[--length] = '\0'; + // Now parse the line. + if (line[0] == '[') { + // Switching sections. All we care about is whether this is + // the (a?) proxy settings section, for both KDE3 and KDE4. + in_proxy_settings = !strncmp(line, "[Proxy Settings]", 16); + } else if (in_proxy_settings) { + // A regular line, in the (a?) proxy settings section. + char* value = strchr(line, '='); + if (!value) + continue; + // The length of the value name. + length = value - line; + if (!length) + continue; + // Is the value name localized? + if (line[length - 1] == ']') { + // Find the matching '['. + for (; length && line[length - 1] != '['; --length); + if (!length) + continue; + // Trim the localization indicator off. + line[length - 1] = '\0'; + } + // Split the string on the = sign, and advance |value| to the value. + *(value++) = '\0'; + // Now fill in the tables. + AddKDESetting(line, value); + } + } + if (ferror(input.get())) + LOG(ERROR) << "error reading " << kioslaverc.value(); + ResolveModeEffects(); + } + + // This is the callback from the debounce timer. + void OnDebouncedNotification() { + DCHECK(MessageLoop::current() == file_loop_); + LOG(INFO) << "inotify change notification for kioslaverc"; + UpdateCachedSettings(); + DCHECK(notify_delegate_); + // Forward to a method on the proxy config service delegate object. + notify_delegate_->OnCheckProxyConfigSettings(); + } + + // Called by OnFileCanReadWithoutBlocking() on the file thread. Reads + // from the inotify file descriptor and starts up a debounce timer if + // an event for kioslaverc is seen. + void OnChangeNotification() { + DCHECK(inotify_fd_ >= 0); + DCHECK(MessageLoop::current() == file_loop_); + char event_buf[(sizeof(inotify_event) + NAME_MAX + 1) * 4]; + bool kioslaverc_touched = false; + ssize_t r; + while ((r = read(inotify_fd_, event_buf, sizeof(event_buf))) > 0) { + // inotify returns variable-length structures, which is why we have + // this strange-looking loop instead of iterating through an array. + char* event_ptr = event_buf; + while (event_ptr < event_buf + r) { + inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr); + // The kernel always feeds us whole events. + CHECK(event_ptr + sizeof(inotify_event) <= event_buf + r); + CHECK(event->name + event->len <= event_buf + r); + if (!strcmp(event->name, "kioslaverc")) + kioslaverc_touched = true; + // Advance the pointer just past the end of the filename. + event_ptr = event->name + event->len; + } + // We keep reading even if |kioslaverc_touched| is true to drain the + // inotify event queue. + } + if (!r) + // Instead of returning -1 and setting errno to EINVAL if there is not + // enough buffer space, older kernels (< 2.6.21) return 0. Simulate the + // new behavior (EINVAL) so we can reuse the code below. + errno = EINVAL; + if (errno != EAGAIN) { + LOG(WARNING) << "error reading inotify file descriptor: " + << strerror(errno); + if (errno == EINVAL) { + // Our buffer is not large enough to read the next event. This should + // not happen (because its size is calculated to always be sufficiently + // large), but if it does we'd warn continuously since |inotify_fd_| + // would be forever ready to read. Close it and stop watching instead. + LOG(ERROR) << "inotify failure; no longer watching kioslaverc!"; + inotify_watcher_.StopWatchingFileDescriptor(); + close(inotify_fd_); + inotify_fd_ = -1; + } + } + if (kioslaverc_touched) { + // We don't use Reset() because the timer may not yet be running. + // (In that case Stop() is a no-op.) + debounce_timer_.Stop(); + debounce_timer_.Start(base::TimeDelta::FromMilliseconds( + kDebounceTimeoutMilliseconds), this, + &GConfSettingGetterImplKDE::OnDebouncedNotification); + } + } + + typedef std::map<std::string, std::string> string_map_type; + typedef std::map<std::string, std::vector<std::string> > strings_map_type; + + int inotify_fd_; + base::MessagePumpLibevent::FileDescriptorWatcher inotify_watcher_; + ProxyConfigServiceLinux::Delegate* notify_delegate_; + base::OneShotTimer<GConfSettingGetterImplKDE> debounce_timer_; + FilePath kde_config_dir_; + bool indirect_manual_; + bool auto_no_pac_; + bool reversed_exception_; + + // We cache these settings whenever we re-read the kioslaverc file. + string_map_type string_table_; + strings_map_type strings_table_; + + // Message loop of the file thread, for reading kioslaverc. If NULL, + // just read it directly (for testing). We also handle inotify events + // on this thread. + MessageLoopForIO* file_loop_; + + DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImplKDE); }; } // namespace @@ -367,7 +815,7 @@ bool ProxyConfigServiceLinux::Delegate::GetProxyFromGConf( return false; } // Check for an optional port. - int port; + int port = 0; gconf_getter_->GetInt((key + "port").c_str(), &port); if (port != 0) { // If a port is set and non-zero: @@ -478,7 +926,7 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( } // Check for authentication, just so we can warn. - bool use_auth; + bool use_auth = false; gconf_getter_->GetBoolean("/system/http_proxy/use_authentication", &use_auth); if (use_auth) { @@ -498,6 +946,24 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( } ProxyConfigServiceLinux::Delegate::Delegate( + base::EnvironmentVariableGetter* env_var_getter) + : env_var_getter_(env_var_getter), + glib_default_loop_(NULL), io_loop_(NULL) { + // Figure out which GConfSettingGetterImpl to use, if any. + switch (base::GetDesktopEnvironment(env_var_getter)) { + case base::DESKTOP_ENVIRONMENT_GNOME: + gconf_getter_.reset(new GConfSettingGetterImplGConf()); + break; + case base::DESKTOP_ENVIRONMENT_KDE3: + case base::DESKTOP_ENVIRONMENT_KDE4: + gconf_getter_.reset(new GConfSettingGetterImplKDE(env_var_getter)); + break; + case base::DESKTOP_ENVIRONMENT_OTHER: + break; + } +} + +ProxyConfigServiceLinux::Delegate::Delegate( base::EnvironmentVariableGetter* env_var_getter, GConfSettingGetter* gconf_getter) : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter), @@ -505,18 +971,19 @@ ProxyConfigServiceLinux::Delegate::Delegate( } void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig( - MessageLoop* glib_default_loop, MessageLoop* io_loop) { + MessageLoop* glib_default_loop, MessageLoop* io_loop, + MessageLoopForIO* file_loop) { // We should be running on the default glib main loop thread right // now. gconf can only be accessed from this thread. DCHECK(MessageLoop::current() == glib_default_loop); glib_default_loop_ = glib_default_loop; io_loop_ = io_loop; - // If we are passed a NULL io_loop, then we don't set up gconf - // notifications. This should not be the usual case but is intended - // to simplify test setups. - if (!io_loop_) - LOG(INFO) << "Monitoring of gconf setting changes is disabled"; + // If we are passed a NULL io_loop or file_loop, then we don't set up + // proxy setting change notifications. This should not be the usual + // case but is intended to simplify test setups. + if (!io_loop_ || !file_loop) + LOG(INFO) << "Monitoring of proxy setting changes is disabled"; // Fetch and cache the current proxy config. The config is left in // cached_config_, where GetProxyConfig() running on the IO thread @@ -531,36 +998,27 @@ void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig( // mislead us. bool got_config = false; - switch (base::GetDesktopEnvironment(env_var_getter_.get())) { - case base::DESKTOP_ENVIRONMENT_GNOME: - if (gconf_getter_->Init() && - (!io_loop || gconf_getter_->SetupNotification(this))) { - if (GetConfigFromGConf(&cached_config_)) { - cached_config_.set_id(1); // mark it as valid - got_config = true; - LOG(INFO) << "Obtained proxy setting from gconf"; - // If gconf proxy mode is "none", meaning direct, then we take - // that to be a valid config and will not check environment - // variables. The alternative would have been to look for a proxy - // where ever we can find one. - // - // Keep a copy of the config for use from this thread for - // comparison with updated settings when we get notifications. - reference_config_ = cached_config_; - reference_config_.set_id(1); // mark it as valid - } else { - gconf_getter_->Release(); // Stop notifications - } + if (gconf_getter_.get()) { + if (gconf_getter_->Init(glib_default_loop, file_loop) && + (!io_loop || !file_loop || gconf_getter_->SetupNotification(this))) { + if (GetConfigFromGConf(&cached_config_)) { + cached_config_.set_id(1); // mark it as valid + got_config = true; + LOG(INFO) << "Obtained proxy settings from " << + gconf_getter_->GetDataSource(); + // If gconf proxy mode is "none", meaning direct, then we take + // that to be a valid config and will not check environment + // variables. The alternative would have been to look for a proxy + // whereever we can find one. + // + // Keep a copy of the config for use from this thread for + // comparison with updated settings when we get notifications. + reference_config_ = cached_config_; + reference_config_.set_id(1); // mark it as valid + } else { + gconf_getter_->Shutdown(); // Stop notifications } - break; - - case base::DESKTOP_ENVIRONMENT_KDE3: - case base::DESKTOP_ENVIRONMENT_KDE4: - NOTIMPLEMENTED() << "Bug 17363: obey KDE proxy settings."; - break; - - case base::DESKTOP_ENVIRONMENT_OTHER: - break; + } } if (!got_config) { @@ -571,14 +1029,14 @@ void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig( // work. if (GetConfigFromEnv(&cached_config_)) { cached_config_.set_id(1); // mark it as valid - LOG(INFO) << "Obtained proxy setting from environment variables"; + LOG(INFO) << "Obtained proxy settings from environment variables"; } } } void ProxyConfigServiceLinux::Delegate::Reset() { DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_); - gconf_getter_->Release(); + gconf_getter_->Shutdown(); cached_config_ = ProxyConfig(); } @@ -592,11 +1050,11 @@ int ProxyConfigServiceLinux::Delegate::GetProxyConfig(ProxyConfig* config) { return cached_config_.is_valid() ? OK : ERR_FAILED; } +// Depending on the GConfSettingGetter in use, this method will be called +// on either the UI thread (GConf) or the file thread (KDE). void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() { - // This should be dispatched from the thread with the default glib - // main loop, which allows us to access gconf. - DCHECK(MessageLoop::current() == glib_default_loop_); - + MessageLoop* required_loop = gconf_getter_->GetNotificationLoop(); + DCHECK(!required_loop || MessageLoop::current() == required_loop); ProxyConfig new_config; bool valid = GetConfigFromGConf(&new_config); if (valid) @@ -626,15 +1084,17 @@ void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig( } void ProxyConfigServiceLinux::Delegate::PostDestroyTask() { - if (MessageLoop::current() == glib_default_loop_) { + if (!gconf_getter_.get()) + return; + MessageLoop* shutdown_loop = gconf_getter_->GetNotificationLoop(); + if (!shutdown_loop || MessageLoop::current() == shutdown_loop) { // Already on the right thread, call directly. // This is the case for the unittests. OnDestroy(); } else { - // Post to UI thread. Note that on browser shutdown, we may quit - // the UI MessageLoop and exit the program before ever running - // this. - glib_default_loop_->PostTask( + // Post to shutdown thread. Note that on browser shutdown, we may quit + // this MessageLoop and exit the program before ever running this. + shutdown_loop->PostTask( FROM_HERE, NewRunnableMethod( this, @@ -642,13 +1102,13 @@ void ProxyConfigServiceLinux::Delegate::PostDestroyTask() { } } void ProxyConfigServiceLinux::Delegate::OnDestroy() { - DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_); - gconf_getter_->Release(); + MessageLoop* shutdown_loop = gconf_getter_->GetNotificationLoop(); + DCHECK(!shutdown_loop || MessageLoop::current() == shutdown_loop); + gconf_getter_->Shutdown(); } ProxyConfigServiceLinux::ProxyConfigServiceLinux() - : delegate_(new Delegate(base::EnvironmentVariableGetter::Create(), - new GConfSettingGetterImpl())) { + : delegate_(new Delegate(base::EnvironmentVariableGetter::Create())) { } ProxyConfigServiceLinux::ProxyConfigServiceLinux( diff --git a/net/proxy/proxy_config_service_linux.h b/net/proxy/proxy_config_service_linux.h index a95a288..1da3749 100644 --- a/net/proxy/proxy_config_service_linux.h +++ b/net/proxy/proxy_config_service_linux.h @@ -24,22 +24,38 @@ namespace net { class ProxyConfigServiceLinux : public ProxyConfigService { public: + // Forward declaration of Delegate. + class Delegate; + class GConfSettingGetter { public: virtual ~GConfSettingGetter() {} - // Initializes the class: obtains a gconf client, in the concrete - // implementation. Returns true on success. Must be called before - // using other methods. - virtual bool Init() = 0; + // Initializes the class: obtains a gconf client, or simulates one, in + // the concrete implementations. Returns true on success. Must be called + // before using other methods, and should be called on the thread running + // the glib main loop. + // One of |glib_default_loop| and |file_loop| will be used for gconf calls + // or reading necessary files, depending on the implementation. + virtual bool Init(MessageLoop* glib_default_loop, + MessageLoopForIO* file_loop) = 0; // Releases the gconf client, which clears cached directories and // stops notifications. - virtual void Release() = 0; + virtual void Shutdown() = 0; // Requests notification of gconf setting changes for proxy // settings. Returns true on success. - virtual bool SetupNotification(void* callback_user_data) = 0; + virtual bool SetupNotification(Delegate* delegate) = 0; + + // Returns the message loop for the thread on which this object + // handles notifications, and also on which it must be destroyed. + // Returns NULL if it does not matter. + virtual MessageLoop* GetNotificationLoop() = 0; + + // Returns the data source's name (e.g. "gconf", "KDE", "test"). + // Used only for diagnostic purposes (e.g. LOG(INFO) etc.). + virtual const char* GetDataSource() = 0; // Gets a string type value from gconf and stores it in // result. Returns false if the key is unset or on error. Must @@ -79,8 +95,11 @@ class ProxyConfigServiceLinux : public ProxyConfigService { class Delegate : public base::RefCountedThreadSafe<Delegate> { public: + // Constructor receives env var getter implementation to use, and + // takes ownership of it. This is the normal constructor. + explicit Delegate(base::EnvironmentVariableGetter* env_var_getter); // Constructor receives gconf and env var getter implementations - // to use, and takes ownership of them. + // to use, and takes ownership of them. Used for testing. Delegate(base::EnvironmentVariableGetter* env_var_getter, GConfSettingGetter* gconf_getter); // Synchronously obtains the proxy configuration. If gconf is @@ -89,9 +108,11 @@ class ProxyConfigServiceLinux : public ProxyConfigService { // the default glib main loop, and so this method must be called // from the UI thread. The message loop for the IO thread is // specified so that notifications can post tasks to it (and for - // assertions). + // assertions). The message loop for the file thread is used to + // read any files needed to determine proxy settings. void SetupAndFetchInitialConfig(MessageLoop* glib_default_loop, - MessageLoop* io_loop); + MessageLoop* io_loop, + MessageLoopForIO* file_loop); // Resets cached_config_ and releases the gconf_getter_, making it // possible to call SetupAndFetchInitialConfig() again. Only used // in testing. @@ -182,8 +203,10 @@ class ProxyConfigServiceLinux : public ProxyConfigService { } void SetupAndFetchInitialConfig(MessageLoop* glib_default_loop, - MessageLoop* io_loop) { - delegate_->SetupAndFetchInitialConfig(glib_default_loop, io_loop); + MessageLoop* io_loop, + MessageLoopForIO* file_loop) { + delegate_->SetupAndFetchInitialConfig(glib_default_loop, io_loop, + file_loop); } void Reset() { delegate_->Reset(); diff --git a/net/proxy/proxy_config_service_linux_unittest.cc b/net/proxy/proxy_config_service_linux_unittest.cc index f208a06..a83694c 100644 --- a/net/proxy/proxy_config_service_linux_unittest.cc +++ b/net/proxy/proxy_config_service_linux_unittest.cc @@ -153,16 +153,25 @@ class MockGConfSettingGetter values = zero_values; } - virtual bool Init() { + virtual bool Init(MessageLoop* glib_default_loop, + MessageLoopForIO* file_loop) { return true; } - virtual void Release() {} + virtual void Shutdown() {} - virtual bool SetupNotification(void* callback_user_data) { + virtual bool SetupNotification(ProxyConfigServiceLinux::Delegate* delegate) { return true; } + virtual MessageLoop* GetNotificationLoop() { + return NULL; + } + + virtual const char* GetDataSource() { + return "test"; + } + virtual bool GetString(const char* key, std::string* result) { const char* value = strings_table.Get(key); if (value) { @@ -243,9 +252,12 @@ class SynchConfigGetter { // all on the calling thread (meant to be the thread with the // default glib main loop, which is the UI thread). void SetupAndInitialFetch() { + MessageLoop* file_loop = io_thread_.message_loop(); + DCHECK_EQ(MessageLoop::TYPE_IO, file_loop->type()); config_service_->Reset(); config_service_->SetupAndFetchInitialConfig( - MessageLoop::current(), io_thread_.message_loop()); + MessageLoop::current(), io_thread_.message_loop(), + static_cast<MessageLoopForIO*>(file_loop)); } // Synchronously gets the proxy config. int SyncGetProxyConfig(net::ProxyConfig* config) { @@ -479,7 +491,7 @@ TEST(ProxyConfigServiceLinuxTest, BasicGConfTest) { // Expected result. false, // auto_detect - GURL(), // pac_aurl + GURL(), // pac_url MakeSingleProxyRules("www.google.com:88"), // proxy_rules "", // proxy_bypass_list false, // bypass_local_names @@ -488,11 +500,11 @@ TEST(ProxyConfigServiceLinuxTest, BasicGConfTest) { { TEST_DESC("Per-scheme proxy rules"), { // Input. - "manual", // mode + "manual", // mode "", // autoconfig_url "www.google.com", // http_host "www.foo.com", // secure_host - "ftpfoo.com", // ftp + "ftp.foo.com", // ftp "", // socks 88, 110, 121, 0, // ports TRUE, FALSE, FALSE, // use, same, auth @@ -504,7 +516,7 @@ TEST(ProxyConfigServiceLinuxTest, BasicGConfTest) { GURL(), // pac_url MakeProxyPerSchemeRules("www.google.com:88", // proxy_rules "www.foo.com:110", - "ftpfoo.com:121"), + "ftp.foo.com:121"), "", // proxy_bypass_list false, // bypass_local_names }, @@ -834,33 +846,6 @@ TEST(ProxyConfigServiceLinuxTest, BasicEnvTest) { } } -// Verify that we fall back on consulting the environment when -// GNOME-specific environment variables aren't available. -TEST(ProxyConfigServiceLinuxTest, FallbackOnEnv) { - MockEnvironmentVariableGetter* env_getter = - new MockEnvironmentVariableGetter; - MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter; - ProxyConfigServiceLinux service(env_getter, gconf_getter); - - // Imagine we're: - // 1) Running a non-GNOME desktop session: - env_getter->values.DESKTOP_SESSION = "default"; - // 2) Have settings in gconf. - gconf_getter->values.mode = "auto"; - gconf_getter->values.autoconfig_url = "http://incorrect/wpad.dat"; - // 3) But we have a proxy-specifying environment variable set: - env_getter->values.auto_proxy = "http://correct/wpad.dat"; - - ProxyConfig config; - - SynchConfigGetter sync_config_getter(&service); - sync_config_getter.SetupAndInitialFetch(); - sync_config_getter.SyncGetProxyConfig(&config); - - // Then we expect the environment variable to win. - EXPECT_EQ(GURL(env_getter->values.auto_proxy), config.pac_url); -} - TEST(ProxyConfigServiceLinuxTest, GconfNotification) { MockEnvironmentVariableGetter* env_getter = new MockEnvironmentVariableGetter; diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index cc08e53..975b7df 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -217,11 +217,11 @@ ProxyService* ProxyService::Create( const ProxyConfig* pc, bool use_v8_resolver, URLRequestContext* url_request_context, - MessageLoop* io_loop) { + MessageLoop* io_loop, MessageLoop* file_loop) { // Choose the system configuration service appropriate for each platform. ProxyConfigService* proxy_config_service = pc ? new ProxyConfigServiceFixed(*pc) : - CreateSystemProxyConfigService(io_loop); + CreateSystemProxyConfigService(io_loop, file_loop); ProxyResolver* proxy_resolver; @@ -255,7 +255,7 @@ ProxyService* ProxyService::Create( // static ProxyService* ProxyService::CreateFixed(const ProxyConfig& pc) { - return Create(&pc, false, NULL, NULL); + return Create(&pc, false, NULL, NULL, NULL); } // static @@ -537,7 +537,7 @@ void ProxyService::DidCompletePacRequest(int config_id, int result_code) { // static ProxyConfigService* ProxyService::CreateSystemProxyConfigService( - MessageLoop* io_loop) { + MessageLoop* io_loop, MessageLoop* file_loop) { #if defined(OS_WIN) return new ProxyConfigServiceWin(); #elif defined(OS_MACOSX) @@ -551,11 +551,15 @@ ProxyConfigService* ProxyService::CreateSystemProxyConfigService( // running gconf calls from. MessageLoop* glib_default_loop = MessageLoopForUI::current(); + // The file loop should be a MessageLoopForIO on Linux. + DCHECK_EQ(MessageLoop::TYPE_IO, file_loop->type()); + // Synchronously fetch the current proxy config (since we are - // running on glib_default_loop). Additionally register for gconf - // notifications (delivered in |glib_default_loop|) to keep us - // updated on when the proxy config has changed. - linux_config_service->SetupAndFetchInitialConfig(glib_default_loop, io_loop); + // running on glib_default_loop). Additionally register for + // notifications (delivered in either |glib_default_loop| or + // |file_loop|) to keep us updated when the proxy config changes. + linux_config_service->SetupAndFetchInitialConfig(glib_default_loop, io_loop, + static_cast<MessageLoopForIO*>(file_loop)); return linux_config_service; #else diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h index 0ac3261..cd76453 100644 --- a/net/proxy/proxy_service.h +++ b/net/proxy/proxy_service.h @@ -117,8 +117,9 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService> { // script needs to be fetched. // |io_loop| points to the IO thread's message loop. It is only used // when pc is NULL. If both pc and io_loop are NULL, then monitoring - // of gconf setting changes will be disabled in - // ProxyConfigServiceLinux. + // of proxy setting changes will be disabled in ProxyConfigServiceLinux. + // |file_loop| points to the file thread's message loop. It is used + // to read any files necessary to get proxy settings. // ########################################################################## // # See the warnings in net/proxy/proxy_resolver_v8.h describing the // # multi-threading model. In order for this to be safe to use, *ALL* the @@ -128,7 +129,7 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService> { const ProxyConfig* pc, bool use_v8_resolver, URLRequestContext* url_request_context, - MessageLoop* io_loop); + MessageLoop* io_loop, MessageLoop* file_loop); // Convenience method that creates a proxy service using the // specified fixed settings. |pc| must not be NULL. @@ -152,7 +153,7 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService> { // Creates a config service appropriate for this platform that fetches the // system proxy settings. static ProxyConfigService* CreateSystemProxyConfigService( - MessageLoop* io_loop); + MessageLoop* io_loop, MessageLoop* file_loop); // Creates a proxy resolver appropriate for this platform that doesn't rely // on V8. |