diff options
author | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-17 22:19:50 +0000 |
---|---|---|
committer | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-17 22:19:50 +0000 |
commit | 573c0504ca29c38670f43da59fc1c3ab3d584006 (patch) | |
tree | dab4229da43ba611512c07a51eaf63575b60f5b8 /net | |
parent | d231828ebc146578393f38c34161013d0c7bf161 (diff) | |
download | chromium_src-573c0504ca29c38670f43da59fc1c3ab3d584006.zip chromium_src-573c0504ca29c38670f43da59fc1c3ab3d584006.tar.gz chromium_src-573c0504ca29c38670f43da59fc1c3ab3d584006.tar.bz2 |
Linux: refactor ProxyConfigServiceLinux to use an enum of setting ids rather than gconf key names.
This will make it easier to add a third way to get proxy settings, via the new gsettings API.
BUG=80453
TEST=proxy settings still work as expected on linux
Review URL: http://codereview.chromium.org/7027002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85686 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/proxy/proxy_config_service_linux.cc | 279 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_linux.h | 174 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_linux_unittest.cc | 92 |
3 files changed, 318 insertions, 227 deletions
diff --git a/net/proxy/proxy_config_service_linux.cc b/net/proxy/proxy_config_service_linux.cc index bf40622..c2ad145 100644 --- a/net/proxy/proxy_config_service_linux.cc +++ b/net/proxy/proxy_config_service_linux.cc @@ -189,14 +189,13 @@ namespace { const int kDebounceTimeoutMilliseconds = 250; #if defined(USE_GCONF) -// This is the "real" gconf version that actually uses gconf. -class GConfSettingGetterImplGConf - : public ProxyConfigServiceLinux::GConfSettingGetter { +// This setting getter uses gconf, as used in GNOME 2 and some GNOME 3 desktops. +class SettingGetterImplGConf : public ProxyConfigServiceLinux::SettingGetter { public: - GConfSettingGetterImplGConf() + SettingGetterImplGConf() : client_(NULL), notify_delegate_(NULL), loop_(NULL) {} - virtual ~GConfSettingGetterImplGConf() { + virtual ~SettingGetterImplGConf() { // 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 @@ -209,10 +208,10 @@ class GConfSettingGetterImplGConf // 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. - VLOG(1) << "~GConfSettingGetterImplGConf: releasing gconf client"; + VLOG(1) << "~SettingGetterImplGConf: releasing gconf client"; ShutDown(); } else { - LOG(WARNING) << "~GConfSettingGetterImplGConf: leaking gconf client"; + LOG(WARNING) << "~SettingGetterImplGConf: leaking gconf client"; client_ = NULL; } } @@ -294,7 +293,71 @@ class GConfSettingGetterImplGConf return "gconf"; } - virtual bool GetString(const char* key, std::string* result) { + virtual bool GetString(Setting key, std::string* result) { + switch (key) { + case PROXY_MODE: + return GetStringByPath("/system/proxy/mode", result); + case PROXY_AUTOCONF_URL: + return GetStringByPath("/system/proxy/autoconfig_url", result); + case PROXY_HTTP_HOST: + return GetStringByPath("/system/http_proxy/host", result); + case PROXY_HTTPS_HOST: + return GetStringByPath("/system/proxy/secure_host", result); + case PROXY_FTP_HOST: + return GetStringByPath("/system/proxy/ftp_host", result); + case PROXY_SOCKS_HOST: + return GetStringByPath("/system/proxy/socks_host", result); + default: + return false; + } + } + virtual bool GetBool(Setting key, bool* result) { + switch (key) { + case PROXY_USE_HTTP_PROXY: + return GetBoolByPath("/system/http_proxy/use_http_proxy", result); + case PROXY_USE_SAME_PROXY: + return GetBoolByPath("/system/http_proxy/use_same_proxy", result); + case PROXY_USE_AUTHENTICATION: + return GetBoolByPath("/system/http_proxy/use_authentication", result); + default: + return false; + } + } + virtual bool GetInt(Setting key, int* result) { + switch (key) { + case PROXY_HTTP_PORT: + return GetIntByPath("/system/http_proxy/port", result); + case PROXY_HTTPS_PORT: + return GetIntByPath("/system/proxy/secure_port", result); + case PROXY_FTP_PORT: + return GetIntByPath("/system/proxy/ftp_port", result); + case PROXY_SOCKS_PORT: + return GetIntByPath("/system/proxy/socks_port", result); + default: + return false; + } + } + virtual bool GetStringList(Setting key, + std::vector<std::string>* result) { + switch (key) { + case PROXY_IGNORE_HOSTS: + return GetStringListByPath("/system/http_proxy/ignore_hosts", result); + default: + return false; + } + } + + virtual bool BypassListIsReversed() { + // This is a KDE-specific setting. + return false; + } + + virtual bool MatchHostsUsingSuffixMatching() { + return false; + } + + private: + bool GetStringByPath(const char* key, std::string* result) { DCHECK(client_); DCHECK(MessageLoop::current() == loop_); GError* error = NULL; @@ -307,7 +370,7 @@ class GConfSettingGetterImplGConf g_free(value); return true; } - virtual bool GetBoolean(const char* key, bool* result) { + bool GetBoolByPath(const char* key, bool* result) { DCHECK(client_); DCHECK(MessageLoop::current() == loop_); GError* error = NULL; @@ -330,7 +393,7 @@ class GConfSettingGetterImplGConf gconf_value_free(gconf_value); return true; } - virtual bool GetInt(const char* key, int* result) { + bool GetIntByPath(const char* key, int* result) { DCHECK(client_); DCHECK(MessageLoop::current() == loop_); GError* error = NULL; @@ -342,8 +405,7 @@ class GConfSettingGetterImplGConf *result = value; return true; } - virtual bool GetStringList(const char* key, - std::vector<std::string>* result) { + bool GetStringListByPath(const char* key, std::vector<std::string>* result) { DCHECK(client_); DCHECK(MessageLoop::current() == loop_); GError* error = NULL; @@ -363,16 +425,6 @@ class GConfSettingGetterImplGConf return true; } - virtual bool BypassListIsReversed() { - // This is a KDE-specific setting. - return false; - } - - virtual bool MatchHostsUsingSuffixMatching() { - return false; - } - - private: // Logs and frees a glib error. Returns false if there was no error // (error is NULL). bool HandleGError(GError* error, const char* key) { @@ -399,7 +451,7 @@ class GConfSettingGetterImplGConf debounce_timer_.Stop(); debounce_timer_.Start(base::TimeDelta::FromMilliseconds( kDebounceTimeoutMilliseconds), this, - &GConfSettingGetterImplGConf::OnDebouncedNotification); + &SettingGetterImplGConf::OnDebouncedNotification); } // gconf notification callback, dispatched from the default glib main loop. @@ -409,32 +461,31 @@ class GConfSettingGetterImplGConf VLOG(1) << "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); + SettingGetterImplGConf* setting_getter = + reinterpret_cast<SettingGetterImplGConf*>(user_data); setting_getter->OnChangeNotification(); } GConfClient* client_; ProxyConfigServiceLinux::Delegate* notify_delegate_; - base::OneShotTimer<GConfSettingGetterImplGConf> debounce_timer_; + base::OneShotTimer<SettingGetterImplGConf> 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(GConfSettingGetterImplGConf); + DISALLOW_COPY_AND_ASSIGN(SettingGetterImplGConf); }; #endif // defined(USE_GCONF) // 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 { +class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter, + public base::MessagePumpLibevent::Watcher { public: - explicit GConfSettingGetterImplKDE(base::Environment* env_var_getter) + explicit SettingGetterImplKDE(base::Environment* env_var_getter) : inotify_fd_(-1), notify_delegate_(NULL), indirect_manual_(false), auto_no_pac_(false), reversed_bypass_list_(false), env_var_getter_(env_var_getter), file_loop_(NULL) { @@ -493,7 +544,7 @@ class GConfSettingGetterImplKDE } } - virtual ~GConfSettingGetterImplKDE() { + virtual ~SettingGetterImplKDE() { // 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() @@ -577,22 +628,22 @@ class GConfSettingGetterImplKDE return "KDE"; } - virtual bool GetString(const char* key, std::string* result) { + virtual bool GetString(Setting 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) { + virtual bool GetBool(Setting key, bool* result) { // We don't ever have any booleans. return false; } - virtual bool GetInt(const char* key, int* result) { + virtual bool GetInt(Setting key, int* result) { // We don't ever have any integers. (See AddProxy() below about ports.) return false; } - virtual bool GetStringList(const char* key, + virtual bool GetStringList(Setting key, std::vector<std::string>* result) { strings_map_type::iterator it = strings_table_.find(key); if (it == strings_table_.end()) @@ -622,17 +673,17 @@ class GConfSettingGetterImplKDE return kde_home.Append("share").Append("config"); } - void AddProxy(const std::string& prefix, const std::string& value) { + void AddProxy(Setting host_key, const std::string& value) { if (value.empty() || value.substr(0, 3) == "//:") // No proxy. return; - // We don't need to parse the port number out; GetProxyFromGConf() + // We don't need to parse the port number out; GetProxyFromSettings() // would only append it right back again. So we just leave the port // number right in the host string. - string_table_[prefix + "host"] = value; + string_table_[host_key] = value; } - void AddHostList(const std::string& key, const std::string& value) { + void AddHostList(Setting key, const std::string& value) { std::vector<std::string> tokens; StringTokenizer tk(value, ", "); while (tk.GetNext()) { @@ -674,15 +725,15 @@ class GConfSettingGetterImplKDE indirect_manual_ = true; break; } - string_table_["/system/proxy/mode"] = mode; + string_table_[PROXY_MODE] = mode; } else if (key == "Proxy Config Script") { - string_table_["/system/proxy/autoconfig_url"] = value; + string_table_[PROXY_AUTOCONF_URL] = value; } else if (key == "httpProxy") { - AddProxy("/system/http_proxy/", value); + AddProxy(PROXY_HTTP_HOST, value); } else if (key == "httpsProxy") { - AddProxy("/system/proxy/secure_", value); + AddProxy(PROXY_HTTPS_HOST, value); } else if (key == "ftpProxy") { - AddProxy("/system/proxy/ftp_", value); + AddProxy(PROXY_FTP_HOST, 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() @@ -691,7 +742,7 @@ class GConfSettingGetterImplKDE base::StringToInt(value, &int_value); reversed_bypass_list_ = (value == "true" || int_value); } else if (key == "NoProxyFor") { - AddHostList("/system/http_proxy/ignore_hosts", value); + AddHostList(PROXY_IGNORE_HOSTS, value); } else if (key == "AuthMode") { // Check for authentication, just so we can warn. int mode; @@ -705,7 +756,7 @@ class GConfSettingGetterImplKDE } } - void ResolveIndirect(const std::string& key) { + void ResolveIndirect(Setting key) { string_map_type::iterator it = string_table_.find(key); if (it != string_table_.end()) { std::string value; @@ -716,7 +767,7 @@ class GConfSettingGetterImplKDE } } - void ResolveIndirectList(const std::string& key) { + void ResolveIndirectList(Setting key) { strings_map_type::iterator it = strings_table_.find(key); if (it != strings_table_.end()) { std::string value; @@ -734,14 +785,14 @@ class GConfSettingGetterImplKDE // 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"); - ResolveIndirectList("/system/http_proxy/ignore_hosts"); + ResolveIndirect(PROXY_HTTP_HOST); + ResolveIndirect(PROXY_HTTPS_HOST); + ResolveIndirect(PROXY_FTP_HOST); + ResolveIndirectList(PROXY_IGNORE_HOSTS); } if (auto_no_pac_) { // Remove the PAC URL; we're not supposed to use it. - string_table_.erase("/system/proxy/autoconfig_url"); + string_table_.erase(PROXY_AUTOCONF_URL); } } @@ -882,17 +933,17 @@ class GConfSettingGetterImplKDE debounce_timer_.Stop(); debounce_timer_.Start(base::TimeDelta::FromMilliseconds( kDebounceTimeoutMilliseconds), this, - &GConfSettingGetterImplKDE::OnDebouncedNotification); + &SettingGetterImplKDE::OnDebouncedNotification); } } - typedef std::map<std::string, std::string> string_map_type; - typedef std::map<std::string, std::vector<std::string> > strings_map_type; + typedef std::map<Setting, std::string> string_map_type; + typedef std::map<Setting, std::vector<std::string> > strings_map_type; int inotify_fd_; base::MessagePumpLibevent::FileDescriptorWatcher inotify_watcher_; ProxyConfigServiceLinux::Delegate* notify_delegate_; - base::OneShotTimer<GConfSettingGetterImplKDE> debounce_timer_; + base::OneShotTimer<SettingGetterImplKDE> debounce_timer_; FilePath kde_config_dir_; bool indirect_manual_; bool auto_no_pac_; @@ -911,35 +962,35 @@ class GConfSettingGetterImplKDE // on this thread. MessageLoopForIO* file_loop_; - DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImplKDE); + DISALLOW_COPY_AND_ASSIGN(SettingGetterImplKDE); }; } // namespace -bool ProxyConfigServiceLinux::Delegate::GetProxyFromGConf( - const char* key_prefix, bool is_socks, ProxyServer* result_server) { - std::string key(key_prefix); +bool ProxyConfigServiceLinux::Delegate::GetProxyFromSettings( + SettingGetter::Setting host_key, + ProxyServer* result_server) { std::string host; - if (!gconf_getter_->GetString((key + "host").c_str(), &host) - || host.empty()) { + if (!setting_getter_->GetString(host_key, &host) || host.empty()) { // Unset or empty. return false; } // Check for an optional port. int port = 0; - gconf_getter_->GetInt((key + "port").c_str(), &port); + SettingGetter::Setting port_key = + SettingGetter::HostSettingToPortSetting(host_key); + setting_getter_->GetInt(port_key, &port); if (port != 0) { // If a port is set and non-zero: host += ":" + base::IntToString(port); } - // gconf settings do not appear to distinguish between SOCKS - // version. We default to version 5. For more information on this policy - // decisions, see: + // gconf settings do not appear to distinguish between SOCKS version. We + // default to version 5. For more information on this policy decision, see: // http://code.google.com/p/chromium/issues/detail?id=55912#c2 - host = FixupProxyHostScheme( - is_socks ? ProxyServer::SCHEME_SOCKS5 : ProxyServer::SCHEME_HTTP, - host); + ProxyServer::Scheme scheme = (host_key == SettingGetter::PROXY_SOCKS_HOST) ? + ProxyServer::SCHEME_SOCKS5 : ProxyServer::SCHEME_HTTP; + host = FixupProxyHostScheme(scheme, host); ProxyServer proxy_server = ProxyServer::FromURI(host, ProxyServer::SCHEME_HTTP); if (proxy_server.is_valid()) { @@ -949,12 +1000,12 @@ bool ProxyConfigServiceLinux::Delegate::GetProxyFromGConf( return false; } -bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( +bool ProxyConfigServiceLinux::Delegate::GetConfigFromSettings( ProxyConfig* config) { std::string mode; - if (!gconf_getter_->GetString("/system/proxy/mode", &mode)) { + if (!setting_getter_->GetString(SettingGetter::PROXY_MODE, &mode)) { // We expect this to always be set, so if we don't see it then we - // probably have a gconf problem, and so we don't have a valid + // probably have a gconf/gsettings problem, and so we don't have a valid // proxy config. return false; } @@ -966,8 +1017,8 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( if (mode == "auto") { // automatic proxy config std::string pac_url_str; - if (gconf_getter_->GetString("/system/proxy/autoconfig_url", - &pac_url_str)) { + if (setting_getter_->GetString(SettingGetter::PROXY_AUTOCONF_URL, + &pac_url_str)) { if (!pac_url_str.empty()) { GURL pac_url(pac_url_str); if (!pac_url.is_valid()) @@ -985,8 +1036,8 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( return false; } bool use_http_proxy; - if (gconf_getter_->GetBoolean("/system/http_proxy/use_http_proxy", - &use_http_proxy) + if (setting_getter_->GetBool(SettingGetter::PROXY_USE_HTTP_PROXY, + &use_http_proxy) && !use_http_proxy) { // Another master switch for some reason. If set to false, then no // proxy. But we don't panic if the key doesn't exist. @@ -995,10 +1046,10 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( bool same_proxy = false; // Indicates to use the http proxy for all protocols. This one may - // not exist (presumably on older versions), assume false in that + // not exist (presumably on older versions); we assume false in that // case. - gconf_getter_->GetBoolean("/system/http_proxy/use_same_proxy", - &same_proxy); + setting_getter_->GetBool(SettingGetter::PROXY_USE_SAME_PROXY, + &same_proxy); ProxyServer proxy_for_http; ProxyServer proxy_for_https; @@ -1010,13 +1061,13 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( // Extract the per-scheme proxies. If we failed to parse it, or no proxy was // specified for the scheme, then the resulting ProxyServer will be invalid. - if (GetProxyFromGConf("/system/http_proxy/", false, &proxy_for_http)) + if (GetProxyFromSettings(SettingGetter::PROXY_HTTP_HOST, &proxy_for_http)) num_proxies_specified++; - if (GetProxyFromGConf("/system/proxy/secure_", false, &proxy_for_https)) + if (GetProxyFromSettings(SettingGetter::PROXY_HTTPS_HOST, &proxy_for_https)) num_proxies_specified++; - if (GetProxyFromGConf("/system/proxy/ftp_", false, &proxy_for_ftp)) + if (GetProxyFromSettings(SettingGetter::PROXY_FTP_HOST, &proxy_for_ftp)) num_proxies_specified++; - if (GetProxyFromGConf("/system/proxy/socks_", true, &socks_proxy)) + if (GetProxyFromSettings(SettingGetter::PROXY_SOCKS_HOST, &socks_proxy)) num_proxies_specified++; if (same_proxy) { @@ -1048,8 +1099,8 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( // Check for authentication, just so we can warn. bool use_auth = false; - gconf_getter_->GetBoolean("/system/http_proxy/use_authentication", - &use_auth); + setting_getter_->GetBool(SettingGetter::PROXY_USE_AUTHENTICATION, + &use_auth); if (use_auth) { // ProxyConfig does not support authentication parameters, but // Chrome will prompt for the password later. So we ignore @@ -1060,11 +1111,11 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( // Now the bypass list. std::vector<std::string> ignore_hosts_list; config->proxy_rules().bypass_rules.Clear(); - if (gconf_getter_->GetStringList("/system/http_proxy/ignore_hosts", - &ignore_hosts_list)) { + if (setting_getter_->GetStringList(SettingGetter::PROXY_IGNORE_HOSTS, + &ignore_hosts_list)) { std::vector<std::string>::const_iterator it(ignore_hosts_list.begin()); for (; it != ignore_hosts_list.end(); ++it) { - if (gconf_getter_->MatchHostsUsingSuffixMatching()) { + if (setting_getter_->MatchHostsUsingSuffixMatching()) { config->proxy_rules().bypass_rules. AddRuleFromStringUsingSuffixMatching(*it); } else { @@ -1077,7 +1128,8 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( // as a hostname rule. // KDE allows one to reverse the bypass rules. - config->proxy_rules().reverse_bypass = gconf_getter_->BypassListIsReversed(); + config->proxy_rules().reverse_bypass = + setting_getter_->BypassListIsReversed(); return true; } @@ -1085,16 +1137,16 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf( ProxyConfigServiceLinux::Delegate::Delegate(base::Environment* env_var_getter) : env_var_getter_(env_var_getter), glib_default_loop_(NULL), io_loop_(NULL) { - // Figure out which GConfSettingGetterImpl to use, if any. + // Figure out which SettingGetterImpl to use, if any. switch (base::nix::GetDesktopEnvironment(env_var_getter)) { case base::nix::DESKTOP_ENVIRONMENT_GNOME: #if defined(USE_GCONF) - gconf_getter_.reset(new GConfSettingGetterImplGConf()); + setting_getter_.reset(new SettingGetterImplGConf()); #endif break; case base::nix::DESKTOP_ENVIRONMENT_KDE3: case base::nix::DESKTOP_ENVIRONMENT_KDE4: - gconf_getter_.reset(new GConfSettingGetterImplKDE(env_var_getter)); + setting_getter_.reset(new SettingGetterImplKDE(env_var_getter)); break; case base::nix::DESKTOP_ENVIRONMENT_XFCE: case base::nix::DESKTOP_ENVIRONMENT_OTHER: @@ -1102,9 +1154,9 @@ ProxyConfigServiceLinux::Delegate::Delegate(base::Environment* env_var_getter) } } -ProxyConfigServiceLinux::Delegate::Delegate(base::Environment* env_var_getter, - GConfSettingGetter* gconf_getter) - : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter), +ProxyConfigServiceLinux::Delegate::Delegate( + base::Environment* env_var_getter, SettingGetter* setting_getter) + : env_var_getter_(env_var_getter), setting_getter_(setting_getter), glib_default_loop_(NULL), io_loop_(NULL) { } @@ -1136,12 +1188,12 @@ void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig( // mislead us. bool got_config = false; - if (gconf_getter_.get() && - gconf_getter_->Init(glib_default_loop, file_loop) && - GetConfigFromGConf(&cached_config_)) { + if (setting_getter_.get() && + setting_getter_->Init(glib_default_loop, file_loop) && + GetConfigFromSettings(&cached_config_)) { cached_config_.set_id(1); // Mark it as valid. VLOG(1) << "Obtained proxy settings from " - << gconf_getter_->GetDataSource(); + << setting_getter_->GetDataSource(); // If gconf proxy mode is "none", meaning direct, then we take // that to be a valid config and will not check environment @@ -1162,7 +1214,7 @@ void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig( // fetch and before setting up notifications. We'll detect the common case // of no changes in OnCheckProxyConfigSettings() (or sooner) and ignore it. if (io_loop && file_loop) { - MessageLoop* required_loop = gconf_getter_->GetNotificationLoop(); + MessageLoop* required_loop = setting_getter_->GetNotificationLoop(); if (!required_loop || MessageLoop::current() == required_loop) { // In this case we are already on an acceptable thread. SetUpNotifications(); @@ -1189,12 +1241,12 @@ void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig( } } -// Depending on the GConfSettingGetter in use, this method will be called +// Depending on the SettingGetter in use, this method will be called // on either the UI thread (GConf) or the file thread (KDE). void ProxyConfigServiceLinux::Delegate::SetUpNotifications() { - MessageLoop* required_loop = gconf_getter_->GetNotificationLoop(); + MessageLoop* required_loop = setting_getter_->GetNotificationLoop(); DCHECK(!required_loop || MessageLoop::current() == required_loop); - if (!gconf_getter_->SetUpNotifications(this)) + if (!setting_getter_->SetUpNotifications(this)) LOG(ERROR) << "Unable to set up proxy configuration change notifications"; } @@ -1225,13 +1277,13 @@ ProxyConfigService::ConfigAvailability return CONFIG_VALID; } -// Depending on the GConfSettingGetter in use, this method will be called +// Depending on the SettingGetter in use, this method will be called // on either the UI thread (GConf) or the file thread (KDE). void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() { - MessageLoop* required_loop = gconf_getter_->GetNotificationLoop(); + MessageLoop* required_loop = setting_getter_->GetNotificationLoop(); DCHECK(!required_loop || MessageLoop::current() == required_loop); ProxyConfig new_config; - bool valid = GetConfigFromGConf(&new_config); + bool valid = GetConfigFromSettings(&new_config); if (valid) new_config.set_id(1); // mark it as valid @@ -1264,9 +1316,9 @@ void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig( } void ProxyConfigServiceLinux::Delegate::PostDestroyTask() { - if (!gconf_getter_.get()) + if (!setting_getter_.get()) return; - MessageLoop* shutdown_loop = gconf_getter_->GetNotificationLoop(); + MessageLoop* shutdown_loop = setting_getter_->GetNotificationLoop(); if (!shutdown_loop || MessageLoop::current() == shutdown_loop) { // Already on the right thread, call directly. // This is the case for the unittests. @@ -1282,9 +1334,9 @@ void ProxyConfigServiceLinux::Delegate::PostDestroyTask() { } } void ProxyConfigServiceLinux::Delegate::OnDestroy() { - MessageLoop* shutdown_loop = gconf_getter_->GetNotificationLoop(); + MessageLoop* shutdown_loop = setting_getter_->GetNotificationLoop(); DCHECK(!shutdown_loop || MessageLoop::current() == shutdown_loop); - gconf_getter_->ShutDown(); + setting_getter_->ShutDown(); } ProxyConfigServiceLinux::ProxyConfigServiceLinux() @@ -1301,9 +1353,8 @@ ProxyConfigServiceLinux::ProxyConfigServiceLinux( } ProxyConfigServiceLinux::ProxyConfigServiceLinux( - base::Environment* env_var_getter, - GConfSettingGetter* gconf_getter) - : delegate_(new Delegate(env_var_getter, gconf_getter)) { + base::Environment* env_var_getter, SettingGetter* setting_getter) + : delegate_(new Delegate(env_var_getter, setting_getter)) { } void ProxyConfigServiceLinux::AddObserver(Observer* observer) { diff --git a/net/proxy/proxy_config_service_linux.h b/net/proxy/proxy_config_service_linux.h index ea76c4d..363363a 100644 --- a/net/proxy/proxy_config_service_linux.h +++ b/net/proxy/proxy_config_service_linux.h @@ -23,36 +23,37 @@ namespace net { // Implementation of ProxyConfigService that retrieves the system proxy -// settings from environment variables or gconf. +// settings from environment variables, gconf, gsettings, or kioslaverc (KDE). class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { public: // Forward declaration of Delegate. class Delegate; - class GConfSettingGetter { + class SettingGetter { public: // Buffer size used in some implementations of this class when reading // files. Defined here so unit tests can construct worst-case inputs. static const size_t BUFFER_SIZE = 512; - GConfSettingGetter() {} - virtual ~GConfSettingGetter() {} + SettingGetter() {} + virtual ~SettingGetter() {} - // 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. + // Initializes the class: obtains a gconf/gsettings 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/gsettings 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 + // Releases the gconf/gsettings client, which clears cached directories and // stops notifications. virtual void ShutDown() = 0; - // Requests notification of gconf setting changes for proxy + // Requests notification of gconf/gsettings changes for proxy // settings. Returns true on success. virtual bool SetUpNotifications(Delegate* delegate) = 0; @@ -61,22 +62,59 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { // 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. VLOG(1) etc.). + // Returns the data source's name (e.g. "gconf", "gsettings", "KDE", + // "test"). Used only for diagnostic purposes (e.g. VLOG(1) 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 - // only be called after a successful call to Init(), and not - // after a failed call to SetUpNotifications() or after calling - // Release(). - virtual bool GetString(const char* key, std::string* result) = 0; + // These are all the values that can be fetched. We used to just use the + // corresponding paths in gconf for these, but gconf is now obsolete and + // in the future we'll be using mostly gsettings/kioslaverc so we + // enumerate them instead to avoid unnecessary string operations. + enum Setting { + PROXY_MODE, // string + PROXY_AUTOCONF_URL, // string + PROXY_USE_HTTP_PROXY, // bool + PROXY_USE_SAME_PROXY, // bool + PROXY_USE_AUTHENTICATION, // bool + PROXY_IGNORE_HOSTS, // string list + PROXY_HTTP_HOST, // string + PROXY_HTTP_PORT, // int + PROXY_HTTPS_HOST, // string + PROXY_HTTPS_PORT, // int + PROXY_FTP_HOST, // string + PROXY_FTP_PORT, // int + PROXY_SOCKS_HOST, // string + PROXY_SOCKS_PORT, // int + }; + + // Given a PROXY_*_HOST value, return the corresponding PROXY_*_PORT value. + static Setting HostSettingToPortSetting(Setting host) { + switch (host) { + case PROXY_HTTP_HOST: + return PROXY_HTTP_PORT; + case PROXY_HTTPS_HOST: + return PROXY_HTTPS_PORT; + case PROXY_FTP_HOST: + return PROXY_FTP_PORT; + case PROXY_SOCKS_HOST: + return PROXY_SOCKS_PORT; + default: + NOTREACHED(); + return host; // placate compiler + } + } + + // Gets a string type value from the data source and stores it in + // |*result|. Returns false if the key is unset or on error. Must only be + // called after a successful call to Init(), and not after a failed call + // to SetUpNotifications() or after calling Release(). + virtual bool GetString(Setting key, std::string* result) = 0; // Same thing for a bool typed value. - virtual bool GetBoolean(const char* key, bool* result) = 0; + virtual bool GetBool(Setting key, bool* result) = 0; // Same for an int typed value. - virtual bool GetInt(const char* key, int* result) = 0; + virtual bool GetInt(Setting key, int* result) = 0; // And for a string list. - virtual bool GetStringList(const char* key, + virtual bool GetStringList(Setting key, std::vector<std::string>* result) = 0; // Returns true if the bypass list should be interpreted as a proxy @@ -88,57 +126,58 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { virtual bool MatchHostsUsingSuffixMatching() = 0; private: - DISALLOW_COPY_AND_ASSIGN(GConfSettingGetter); + DISALLOW_COPY_AND_ASSIGN(SettingGetter); }; // ProxyConfigServiceLinux is created on the UI thread, and - // SetUpAndFetchInitialConfig() is immediately called to - // synchronously fetch the original configuration and set up gconf - // notifications on the UI thread. + // SetUpAndFetchInitialConfig() is immediately called to synchronously + // fetch the original configuration and set up change notifications on + // the UI thread. // // Past that point, it is accessed periodically through the // ProxyConfigService interface (GetLatestProxyConfig, AddObserver, // RemoveObserver) from the IO thread. // - // gconf change notification callbacks can occur at any time and are - // run on the UI thread. The new gconf settings are fetched on the - // UI thread, and the new resulting proxy config is posted to the IO - // thread through Delegate::SetNewProxyConfig(). We then notify - // observers on the IO thread of the configuration change. + // Setting change notification callbacks can occur at any time and are + // run on either the UI thread (gconf/gsettings) or the file thread + // (KDE). The new settings are fetched on that thread, and the resulting + // proxy config is posted to the IO thread through + // Delegate::SetNewProxyConfig(). We then notify observers on the IO + // thread of the configuration change. // // ProxyConfigServiceLinux is deleted from the IO thread. // // The substance of the ProxyConfigServiceLinux implementation is // wrapped in the Delegate ref counted class. On deleting the - // ProxyConfigServiceLinux, Delegate::OnDestroy() is posted to the - // UI thread where gconf notifications will be safely stopped before - // releasing Delegate. + // ProxyConfigServiceLinux, Delegate::OnDestroy() is posted to either + // the UI thread (gconf/gsettings) or the file thread (KDE) where change + // notifications will be safely stopped before releasing Delegate. 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::Environment* env_var_getter); - // Constructor receives gconf and env var getter implementations + // Constructor receives setting and env var getter implementations // to use, and takes ownership of them. Used for testing. - Delegate(base::Environment* env_var_getter, - GConfSettingGetter* gconf_getter); - // Synchronously obtains the proxy configuration. If gconf is - // used, also enables gconf notification for setting - // changes. gconf must only be accessed from the thread running - // 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). The message loop for the file thread is used to - // read any files needed to determine proxy settings. + Delegate(base::Environment* env_var_getter, SettingGetter* setting_getter); + + // Synchronously obtains the proxy configuration. If gconf, + // gsettings, or kioslaverc are used, also enables notifications for + // setting changes. gconf/gsettings must only be accessed from the + // thread running 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). 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, MessageLoopForIO* file_loop); - // Handler for gconf change notifications: fetches a new proxy - // configuration from gconf settings, and if this config is - // different than what we had before, posts a task to have it - // stored in cached_config_. + // Handler for setting change notifications: fetches a new proxy + // configuration from settings, and if this config is different + // than what we had before, posts a task to have it stored in + // cached_config_. // Left public for simplicity. void OnCheckProxyConfigSettings(); @@ -151,7 +190,7 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { // Posts a call to OnDestroy() to the UI thread. Called from // ProxyConfigServiceLinux's destructor. void PostDestroyTask(); - // Safely stops gconf notifications. Posted to the UI thread. + // Safely stops change notifications. Posted to the UI thread. void OnDestroy(); private: @@ -171,14 +210,14 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { // variables were found and the configuration is valid. bool GetConfigFromEnv(ProxyConfig* config); - // Obtains host and port gconf settings and parses a proxy server + // Obtains host and port config settings and parses a proxy server // specification from it and puts it in result. Returns true if the // requested variable is defined and the value valid. - bool GetProxyFromGConf(const char* key_prefix, bool is_socks, - ProxyServer* result_server); - // Fills proxy config from gconf. Returns true if settings were found + bool GetProxyFromSettings(SettingGetter::Setting host_key, + ProxyServer* result_server); + // Fills proxy config from settings. Returns true if settings were found // and the configuration is valid. - bool GetConfigFromGConf(ProxyConfig* config); + bool GetConfigFromSettings(ProxyConfig* config); // This method is posted from the UI thread to the IO thread to // carry the new config information. @@ -188,7 +227,7 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { void SetUpNotifications(); scoped_ptr<base::Environment> env_var_getter_; - scoped_ptr<GConfSettingGetter> gconf_getter_; + scoped_ptr<SettingGetter> setting_getter_; // Cached proxy configuration, to be returned by // GetLatestProxyConfig. Initially populated from the UI thread, but @@ -200,15 +239,14 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { // notification but the config has not actually changed. ProxyConfig reference_config_; - // The MessageLoop for the UI thread, aka main browser thread. This - // thread is where we run the glib main loop (see - // base/message_pump_glib.h). It is the glib default loop in the - // sense that it runs the glib default context: as in the context - // where sources are added by g_timeout_add and g_idle_add, and - // returned by g_main_context_default. gconf uses glib timeouts and - // idles and possibly other callbacks that will all be dispatched on - // this thread. Since gconf is not thread safe, any use of gconf - // must be done on the thread running this loop. + // The MessageLoop for the UI thread, aka main browser thread. This thread + // is where we run the glib main loop (see base/message_pump_glib.h). It is + // the glib default loop in the sense that it runs the glib default context: + // as in the context where sources are added by g_timeout_add and + // g_idle_add, and returned by g_main_context_default. gconf uses glib + // timeouts and idles and possibly other callbacks that will all be + // dispatched on this thread. Since gconf is not thread safe, any use of + // gconf must be done on the thread running this loop. MessageLoop* glib_default_loop_; // MessageLoop for the IO thread. GetLatestProxyConfig() is called from // the thread running this loop. @@ -223,10 +261,10 @@ class BASE_API ProxyConfigServiceLinux : public ProxyConfigService { // Usual constructor ProxyConfigServiceLinux(); - // For testing: take alternate gconf and env var getter implementations. + // For testing: take alternate setting and env var getter implementations. explicit ProxyConfigServiceLinux(base::Environment* env_var_getter); ProxyConfigServiceLinux(base::Environment* env_var_getter, - GConfSettingGetter* gconf_getter); + SettingGetter* setting_getter); virtual ~ProxyConfigServiceLinux(); diff --git a/net/proxy/proxy_config_service_linux_unittest.cc b/net/proxy/proxy_config_service_linux_unittest.cc index 5244b66..8d37e07 100644 --- a/net/proxy/proxy_config_service_linux_unittest.cc +++ b/net/proxy/proxy_config_service_linux_unittest.cc @@ -65,10 +65,11 @@ struct GConfValues { // value (inside a EnvVarValues or GConfValues struct). template<typename value_type> struct SettingsTable { - typedef std::map<std::string, value_type*> map_type; + typedef ProxyConfigServiceLinux::SettingGetter::Setting Setting; + typedef std::map<Setting, value_type*> map_type; // Gets the value from its location - value_type Get(const std::string& key) { + value_type Get(Setting key) { typename map_type::const_iterator it = settings.find(key); // In case there's a typo or the unittest becomes out of sync. CHECK(it != settings.end()) << "key " << key << " not found"; @@ -82,7 +83,7 @@ struct SettingsTable { class MockEnvironment : public base::Environment { public: MockEnvironment() { -#define ENTRY(x) table.settings[#x] = &values.x +#define ENTRY(x) table[#x] = &values.x ENTRY(DESKTOP_SESSION); ENTRY(HOME); ENTRY(KDEHOME); @@ -99,7 +100,7 @@ class MockEnvironment : public base::Environment { Reset(); } - // Zeros all environment values. + // Zeroes all environment values. void Reset() { EnvVarValues zero_values = { 0 }; values = zero_values; @@ -107,10 +108,11 @@ class MockEnvironment : public base::Environment { // Begin base::Environment implementation. virtual bool GetVar(const char* variable_name, std::string* result) { - const char* env_value = table.Get(variable_name); - if (env_value) { + std::map<std::string, const char**>::iterator it = + table.find(variable_name); + if (it != table.end() && *(it->second) != NULL) { // Note that the variable may be defined but empty. - *result = env_value; + *result = *(it->second); return true; } return false; @@ -131,37 +133,38 @@ class MockEnvironment : public base::Environment { EnvVarValues values; private: - SettingsTable<const char*> table; + std::map<std::string, const char**> table; }; -class MockGConfSettingGetter - : public ProxyConfigServiceLinux::GConfSettingGetter { +class MockSettingGetter + : public ProxyConfigServiceLinux::SettingGetter { public: - MockGConfSettingGetter() { + typedef ProxyConfigServiceLinux::SettingGetter SettingGetter; + MockSettingGetter() { #define ENTRY(key, field) \ - strings_table.settings["/system/" key] = &values.field - ENTRY("proxy/mode", mode); - ENTRY("proxy/autoconfig_url", autoconfig_url); - ENTRY("http_proxy/host", http_host); - ENTRY("proxy/secure_host", secure_host); - ENTRY("proxy/ftp_host", ftp_host); - ENTRY("proxy/socks_host", socks_host); + strings_table.settings[SettingGetter::key] = &values.field + ENTRY(PROXY_MODE, mode); + ENTRY(PROXY_AUTOCONF_URL, autoconfig_url); + ENTRY(PROXY_HTTP_HOST, http_host); + ENTRY(PROXY_HTTPS_HOST, secure_host); + ENTRY(PROXY_FTP_HOST, ftp_host); + ENTRY(PROXY_SOCKS_HOST, socks_host); #undef ENTRY #define ENTRY(key, field) \ - ints_table.settings["/system/" key] = &values.field - ENTRY("http_proxy/port", http_port); - ENTRY("proxy/secure_port", secure_port); - ENTRY("proxy/ftp_port", ftp_port); - ENTRY("proxy/socks_port", socks_port); + ints_table.settings[SettingGetter::key] = &values.field + ENTRY(PROXY_HTTP_PORT, http_port); + ENTRY(PROXY_HTTPS_PORT, secure_port); + ENTRY(PROXY_FTP_PORT, ftp_port); + ENTRY(PROXY_SOCKS_PORT, socks_port); #undef ENTRY #define ENTRY(key, field) \ - bools_table.settings["/system/" key] = &values.field - ENTRY("http_proxy/use_http_proxy", use_proxy); - ENTRY("http_proxy/use_same_proxy", same_proxy); - ENTRY("http_proxy/use_authentication", use_auth); + bools_table.settings[SettingGetter::key] = &values.field + ENTRY(PROXY_USE_HTTP_PROXY, use_proxy); + ENTRY(PROXY_USE_SAME_PROXY, same_proxy); + ENTRY(PROXY_USE_AUTHENTICATION, use_auth); #undef ENTRY - string_lists_table.settings["/system/http_proxy/ignore_hosts"] = - &values.ignore_hosts; + string_lists_table.settings[SettingGetter::PROXY_IGNORE_HOSTS] = + &values.ignore_hosts; Reset(); } @@ -190,7 +193,7 @@ class MockGConfSettingGetter return "test"; } - virtual bool GetString(const char* key, std::string* result) { + virtual bool GetString(Setting key, std::string* result) { const char* value = strings_table.Get(key); if (value) { *result = value; @@ -199,13 +202,13 @@ class MockGConfSettingGetter return false; } - virtual bool GetInt(const char* key, int* result) { + virtual bool GetInt(Setting key, int* result) { // We don't bother to distinguish unset keys from 0 values. *result = ints_table.Get(key); return true; } - virtual bool GetBoolean(const char* key, bool* result) { + virtual bool GetBool(Setting key, bool* result) { BoolSettingValue value = bools_table.Get(key); switch (value) { case UNSET: @@ -219,8 +222,7 @@ class MockGConfSettingGetter return true; } - virtual bool GetStringList(const char* key, - std::vector<std::string>* result) { + virtual bool GetStringList(Setting key, std::vector<std::string>* result) { *result = string_lists_table.Get(key); // We don't bother to distinguish unset keys from empty lists. return !result->empty(); @@ -676,11 +678,11 @@ TEST_F(ProxyConfigServiceLinuxTest, BasicGConfTest) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); MockEnvironment* env = new MockEnvironment; - MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter; + MockSettingGetter* setting_getter = new MockSettingGetter; SynchConfigGetter sync_config_getter( - new ProxyConfigServiceLinux(env, gconf_getter)); + new ProxyConfigServiceLinux(env, setting_getter)); ProxyConfig config; - gconf_getter->values = tests[i].values; + setting_getter->values = tests[i].values; sync_config_getter.SetupAndInitialFetch(); ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config); @@ -984,9 +986,9 @@ TEST_F(ProxyConfigServiceLinuxTest, BasicEnvTest) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); MockEnvironment* env = new MockEnvironment; - MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter; + MockSettingGetter* setting_getter = new MockSettingGetter; SynchConfigGetter sync_config_getter( - new ProxyConfigServiceLinux(env, gconf_getter)); + new ProxyConfigServiceLinux(env, setting_getter)); ProxyConfig config; env->values = tests[i].values; sync_config_getter.SetupAndInitialFetch(); @@ -1004,22 +1006,22 @@ TEST_F(ProxyConfigServiceLinuxTest, BasicEnvTest) { TEST_F(ProxyConfigServiceLinuxTest, GconfNotification) { MockEnvironment* env = new MockEnvironment; - MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter; + MockSettingGetter* setting_getter = new MockSettingGetter; ProxyConfigServiceLinux* service = - new ProxyConfigServiceLinux(env, gconf_getter); + new ProxyConfigServiceLinux(env, setting_getter); SynchConfigGetter sync_config_getter(service); ProxyConfig config; // Start with no proxy. - gconf_getter->values.mode = "none"; + setting_getter->values.mode = "none"; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_FALSE(config.auto_detect()); // Now set to auto-detect. - gconf_getter->values.mode = "auto"; - // Simulate gconf notification callback. + setting_getter->values.mode = "auto"; + // Simulate setting change notification callback. service->OnCheckProxyConfigSettings(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); @@ -1030,7 +1032,7 @@ TEST_F(ProxyConfigServiceLinuxTest, KDEConfigParser) { // One of the tests below needs a worst-case long line prefix. We build it // programmatically so that it will always be the right size. std::string long_line; - size_t limit = ProxyConfigServiceLinux::GConfSettingGetter::BUFFER_SIZE - 1; + size_t limit = ProxyConfigServiceLinux::SettingGetter::BUFFER_SIZE - 1; for (size_t i = 0; i < limit; ++i) long_line += "-"; |