Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(87)

Side by Side Diff: net/proxy/proxy_config_service_linux.cc

Issue 60009: ProxyConfigService for Linux.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Name: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/proxy/proxy_config_service_linux.h"
6
7 #include <gconf/gconf-client.h>
8 #include <gdk/gdk.h>
9 #include <stdlib.h>
10
11 #include "base/logging.h"
12 #include "base/string_tokenizer.h"
13 #include "base/string_util.h"
14 #include "googleurl/src/url_canon.h"
15 #include "net/base/net_errors.h"
16 #include "net/http/http_util.h"
17 #include "net/proxy/proxy_config.h"
18 #include "net/proxy/proxy_server.h"
19
20 namespace net {
21
22 namespace {
23
24 class EnvironmentVariableGetterImpl
25 : public ProxyConfigServiceLinux::EnvironmentVariableGetter {
26 public:
27 virtual bool Getenv(const char* variable_name, std::string* result) {
28 const char* env_value = ::getenv(variable_name);
29 if (env_value) {
30 // Note that the variable may be defined but empty.
31 *result = env_value;
32 return true;
33 }
34 // Some commonly used variable names are uppercase while others
35 // are lowercase, which is inconsistent. Let's try to be helpful
36 // and look for a variable name with the reverse case.
37 char first_char = variable_name[0];
38 std::string alternate_case_var;
39 if (first_char >= 'a' && first_char <= 'z')
40 alternate_case_var = StringToUpperASCII(std::string(variable_name));
41 else if (first_char >= 'A' && first_char <= 'Z')
42 alternate_case_var = StringToLowerASCII(std::string(variable_name));
43 else
44 return false;
45 env_value = ::getenv(alternate_case_var.c_str());
46 if (env_value) {
47 *result = env_value;
48 return true;
49 }
50 return false;
51 }
52 };
53
54 // Given a proxy hostname from a setting, returns that hostname with
55 // an appropriate proxy server scheme prefix.
56 // scheme indicates the desired proxy scheme: usually http, with
57 // socks 4 or 5 as special cases.
58 std::string FixupProxyHostScheme(ProxyServer::Scheme scheme,
59 std::string host) {
60 if (scheme == ProxyServer::SCHEME_SOCKS4 &&
61 StartsWithASCII(host, "socks5://", false)) {
62 // We default to socks 4, but if the user specifically set it to
63 // socks5://, then use that.
64 scheme = ProxyServer::SCHEME_SOCKS5;
65 }
66 // Strip the scheme if any.
67 std::string::size_type colon = host.find("://");
68 if (colon != std::string::npos)
69 host = host.substr(colon + 3);
70 // If a username and perhaps password are specified, give a warning.
71 std::string::size_type at_sign = host.find("@");
72 // Should this be supported?
73 if (at_sign != std::string::npos) {
74 LOG(ERROR) << "ProxyConfigServiceLinux: proxy authentication "
75 "not supported";
76 // Disregard the authentication parameters and continue with this hostname.
77 host = host.substr(at_sign + 1);
78 }
79 // If this is a socks proxy, prepend a scheme so as to tell
80 // ProxyServer. This also allows ProxyServer to choose the right
81 // default port.
82 if (scheme == ProxyServer::SCHEME_SOCKS4)
83 host = "socks4://" + host;
84 else if (scheme == ProxyServer::SCHEME_SOCKS5)
85 host = "socks5://" + host;
86 return host;
87 }
88
89 } // namespace
90
91 bool ProxyConfigServiceLinux::GetProxyFromEnvVarForScheme(
92 const char* variable, ProxyServer::Scheme scheme,
93 ProxyServer* result_server) {
94 std::string env_value;
95 if (env_var_getter_->Getenv(variable, &env_value)) {
96 if (!env_value.empty()) {
97 env_value = FixupProxyHostScheme(scheme, env_value);
98 ProxyServer proxy_server = ProxyServer::FromURI(env_value);
99 if (proxy_server.is_valid() && !proxy_server.is_direct()) {
100 *result_server = proxy_server;
101 return true;
102 } else {
103 LOG(ERROR) << "ProxyConfigServiceLinux: failed to parse "
104 << "environment variable " << variable;
105 }
106 }
107 }
108 return false;
109 }
110
111 bool ProxyConfigServiceLinux::GetProxyFromEnvVar(
112 const char* variable, ProxyServer* result_server) {
113 return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP,
114 result_server);
115 }
116
117 namespace {
118
119 // Returns true if the given string represents an IP address.
120 bool IsIPAddress(const std::string& domain) {
121 // From GURL::HostIsIPAddress()
122 url_canon::RawCanonOutputT<char, 128> ignored_output;
123 url_parse::Component ignored_component;
124 url_parse::Component domain_comp(0, domain.size());
125 return url_canon::CanonicalizeIPAddress(domain.c_str(), domain_comp,
126 &ignored_output,
127 &ignored_component);
128 }
129
130 } // namespace
131
132 void ProxyConfigServiceLinux::ParseNoProxyList(const std::string& no_proxy,
133 ProxyConfig* config) {
134 if (no_proxy.empty())
135 return;
136 // Traditional semantics:
137 // A single "*" is specifically allowed and unproxies anything.
138 // "*" wildcards other than a single "*" entry are not universally
139 // supported. We will support them, as we get * wildcards for free
140 // (see MatchPattern() called from ProxyService::ShouldBypassProxyForURL()).
141 // no_proxy is a comma-separated list of <trailing_domain>[:<port>].
142 // If no port is specified then any port matches.
143 // The historical definition has trailing_domain match using a simple
144 // string "endswith" test, so that the match need not correspond to a
145 // "." boundary. For example: "google.com" matches "igoogle.com" too.
146 // Seems like that could be confusing, but we'll obey tradition.
147 // IP CIDR patterns are supposed to be supported too. We intend
148 // to do this in proxy_service.cc, but it's currently a TODO.
149 // See: http://crbug.com/9835.
150 StringTokenizer no_proxy_list(no_proxy, ",");
151 while (no_proxy_list.GetNext()) {
152 std::string bypass_entry = no_proxy_list.token();
153 TrimWhitespaceASCII(bypass_entry, TRIM_ALL, &bypass_entry);
154 if (bypass_entry.empty())
155 continue;
156 if (bypass_entry.at(0) != '*') {
157 // Insert a wildcard * to obtain an endsWith match, unless the
158 // entry looks like it might be an IP or CIDR.
159 // First look for either a :<port> or CIDR mask length suffix.
160 std::string::const_iterator begin = bypass_entry.begin();
161 std::string::const_iterator scan = bypass_entry.end() - 1;
162 while (scan > begin && IsAsciiDigit(*scan))
163 --scan;
164 std::string potential_ip;
165 if (*scan == '/' || *scan == ':')
166 potential_ip = std::string(begin, scan - 1);
167 else
168 potential_ip = bypass_entry;
169 if (!IsIPAddress(potential_ip)) {
170 // Do insert a wildcard.
171 bypass_entry.insert(0, "*");
172 }
173 // TODO(sdoyon): When CIDR matching is implemented in
174 // proxy_service.cc, consider making config->proxy_bypass more
175 // sophisticated to avoid parsing out the string on every
176 // request.
177 }
178 config->proxy_bypass.push_back(bypass_entry);
179 }
180 }
181
182 bool ProxyConfigServiceLinux::GetConfigFromEnv(ProxyConfig* config) {
183 // Check for automatic configuration first, in
184 // "auto_proxy". Possibly only the "environment_proxy" firefox
185 // extension has ever used this, but it still sounds like a good
186 // idea.
187 std::string auto_proxy;
188 if (env_var_getter_->Getenv("auto_proxy", &auto_proxy)) {
189 if (auto_proxy.empty()) {
190 // Defined and empty => autodetect
191 config->auto_detect = true;
192 } else {
193 // specified autoconfig URL
194 config->pac_url = GURL(auto_proxy);
195 }
196 return true;
197 }
198 // "all_proxy" is a shortcut to avoid defining {http,https,ftp}_proxy.
199 ProxyServer proxy_server;
200 if (GetProxyFromEnvVar("all_proxy", &proxy_server)) {
201 config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
202 config->proxy_rules.single_proxy = proxy_server;
203 } else {
204 bool have_http = GetProxyFromEnvVar("http_proxy", &proxy_server);
205 if (have_http)
206 config->proxy_rules.proxy_for_http = proxy_server;
207 // It would be tempting to let http_proxy apply for all protocols
208 // if https_proxy and ftp_proxy are not defined. Googling turns up
209 // several documents that mention only http_proxy. But then the
210 // user really might not want to proxy https. And it doesn't seem
211 // like other apps do this. So we will refrain.
212 bool have_https = GetProxyFromEnvVar("https_proxy", &proxy_server);
213 if (have_https)
214 config->proxy_rules.proxy_for_https = proxy_server;
215 bool have_ftp = GetProxyFromEnvVar("ftp_proxy", &proxy_server);
216 if (have_ftp)
217 config->proxy_rules.proxy_for_ftp = proxy_server;
218 if (have_http || have_https || have_ftp) {
219 // mustn't change type unless some rules are actually set.
220 config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
221 }
222 }
223 if (config->proxy_rules.empty()) {
224 // If the above were not defined, try for socks.
225 ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS4;
226 std::string env_version;
227 if (env_var_getter_->Getenv("SOCKS_VERSION", &env_version)
228 && env_version.compare("5") == 0)
229 scheme = ProxyServer::SCHEME_SOCKS5;
230 if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) {
231 config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
232 config->proxy_rules.single_proxy = proxy_server;
233 }
234 }
235 // Look for the proxy bypass list.
236 std::string no_proxy;
237 env_var_getter_->Getenv("no_proxy", &no_proxy);
238 if (config->proxy_rules.empty()) {
239 // Having only "no_proxy" set, presumably to "*", makes it
240 // explicit that env vars do specify a configuration: having no
241 // rules specified only means the user explicitly asks for direct
242 // connections.
243 return !no_proxy.empty();
244 }
245 ParseNoProxyList(no_proxy, config);
246 return true;
247 }
248
249 namespace {
250
251 class GConfSettingGetterImpl
252 : public ProxyConfigServiceLinux::GConfSettingGetter {
253 public:
254 GConfSettingGetterImpl() : client_(NULL) {}
255 virtual ~GConfSettingGetterImpl() {
256 if (client_)
257 g_object_unref(client_);
258 }
259
260 virtual void Enter() {
261 gdk_threads_enter();
262 }
263 virtual void Leave() {
264 gdk_threads_leave();
265 }
266
267 virtual bool InitIfNeeded() {
268 if (!client_) {
269 Enter();
270 client_ = gconf_client_get_default();
271 Leave();
272 // It's not clear whether/when this can return NULL.
273 if (!client_)
274 LOG(ERROR) << "ProxyConfigServiceLinux: Unable to create "
275 "a gconf client";
276 }
277 return client_ != NULL;
278 }
279
280 virtual bool GetString(const char* key, std::string* result) {
281 CHECK(client_);
282 GError* error = NULL;
283 gchar* value = gconf_client_get_string(client_, key, &error);
284 if (HandleGError(error, key))
285 return false;
286 if (!value)
287 return false;
288 *result = value;
289 g_free(value);
290 return true;
291 }
292 virtual bool GetBoolean(const char* key, bool* result) {
293 CHECK(client_);
294 GError* error = NULL;
295 // We want to distinguish unset values from values defaulting to
296 // false. For that we need to use the type-generic
297 // gconf_client_get() rather than gconf_client_get_bool().
298 GConfValue* gconf_value = gconf_client_get(client_, key, &error);
299 if (HandleGError(error, key))
300 return false;
301 if (!gconf_value) {
302 // Unset.
303 return false;
304 }
305 if (gconf_value->type != GCONF_VALUE_BOOL) {
306 gconf_value_free(gconf_value);
307 return false;
308 }
309 gboolean bool_value = gconf_value_get_bool(gconf_value);
310 *result = static_cast<bool>(bool_value);
311 gconf_value_free(gconf_value);
312 return true;
313 }
314 virtual bool GetInt(const char* key, int* result) {
315 CHECK(client_);
316 GError* error = NULL;
317 int value = gconf_client_get_int(client_, key, &error);
318 if (HandleGError(error, key))
319 return false;
320 // We don't bother to distinguish an unset value because callers
321 // don't care. 0 is returned if unset.
322 *result = value;
323 return true;
324 }
325 virtual bool GetStringList(const char* key,
326 std::vector<std::string>* result) {
327 CHECK(client_);
328 GError* error = NULL;
329 GSList* list = gconf_client_get_list(client_, key,
330 GCONF_VALUE_STRING, &error);
331 if (HandleGError(error, key))
332 return false;
333 if (!list) {
334 // unset
335 return false;
336 }
337 for (GSList *it = list; it; it = it->next) {
338 result->push_back(static_cast<char*>(it->data));
339 g_free(it->data);
340 }
341 g_slist_free(list);
342 return true;
343 }
344
345 private:
346 // Logs and frees a glib error. Returns false if there was no error
347 // (error is NULL).
348 bool HandleGError(GError* error, const char* key) {
349 if (error != NULL) {
350 LOG(ERROR) << "ProxyConfigServiceLinux: error getting gconf value for "
351 << key << ": " << error->message;
352 g_error_free(error);
353 return true;
354 }
355 return false;
356 }
357
358 GConfClient* client_;
359
360 DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImpl);
361 };
362
363 } // namespace
364
365 bool ProxyConfigServiceLinux::GetProxyFromGConf(
366 const char* key_prefix, bool is_socks, ProxyServer* result_server) {
367 std::string key(key_prefix);
368 std::string host;
369 if (!gconf_getter_->GetString((key + "host").c_str(), &host)
370 || host.empty()) {
371 // Unset or empty.
372 return false;
373 }
374 // Check for an optional port.
375 int port;
376 gconf_getter_->GetInt((key + "port").c_str(), &port);
377 if (port != 0) {
378 // If a port is set and non-zero:
379 host += ":" + IntToString(port);
380 }
381 host = FixupProxyHostScheme(
382 is_socks ? ProxyServer::SCHEME_SOCKS4 : ProxyServer::SCHEME_HTTP,
383 host);
384 ProxyServer proxy_server = ProxyServer::FromURI(host);
385 if (proxy_server.is_valid()) {
386 *result_server = proxy_server;
387 return true;
388 }
389 return false;
390 }
391
392 bool ProxyConfigServiceLinux::GetConfigFromGConf(ProxyConfig* config) {
393 std::string mode;
394 if (!gconf_getter_->GetString("/system/proxy/mode", &mode)) {
395 // We expect this to always be set, so if we don't see it then we
396 // probably have a gconf problem, and so we don't have a valid
397 // proxy config.
398 return false;
399 }
400 if (mode.compare("none") == 0)
401 // Specifically specifies no proxy.
402 return true;
403
404 if (mode.compare("auto") == 0) {
405 // automatic proxy config
406 std::string pac_url_str;
407 if (gconf_getter_->GetString("/system/proxy/autoconfig_url",
408 &pac_url_str)) {
409 if (!pac_url_str.empty()) {
410 GURL pac_url(pac_url_str);
411 if (!pac_url.is_valid())
412 return false;
413 config->pac_url = pac_url;
414 return true;
415 }
416 }
417 config->auto_detect = true;
418 return true;
419 }
420
421 if (mode.compare("manual") != 0) {
422 // Mode is unrecognized.
423 return false;
424 }
425 bool use_http_proxy;
426 if (gconf_getter_->GetBoolean("/system/http_proxy/use_http_proxy",
427 &use_http_proxy)
428 && !use_http_proxy) {
429 // Another master switch for some reason. If set to false, then no
430 // proxy. But we don't panic if the key doesn't exist.
431 return true;
432 }
433
434 bool same_proxy = false;
435 // Indicates to use the http proxy for all protocols. This one may
436 // not exist (presumably on older versions), assume false in that
437 // case.
438 gconf_getter_->GetBoolean("/system/http_proxy/use_same_proxy",
439 &same_proxy);
440
441 ProxyServer proxy_server;
442 if (!same_proxy) {
443 // Try socks.
444 if (GetProxyFromGConf("/system/proxy/socks_", true, &proxy_server)) {
445 // gconf settings do not appear to distinguish between socks
446 // version. We default to version 4.
447 config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
448 config->proxy_rules.single_proxy = proxy_server;
449 }
450 }
451 if (config->proxy_rules.empty()) {
452 bool have_http = GetProxyFromGConf("/system/http_proxy/", false,
453 &proxy_server);
454 if (same_proxy) {
455 if (have_http) {
456 config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
457 config->proxy_rules.single_proxy = proxy_server;
458 }
459 } else {
460 // Protocol specific settings.
461 if (have_http)
462 config->proxy_rules.proxy_for_http = proxy_server;
463 bool have_secure = GetProxyFromGConf("/system/proxy/secure_", false,
464 &proxy_server);
465 if (have_secure)
466 config->proxy_rules.proxy_for_https = proxy_server;
467 bool have_ftp = GetProxyFromGConf("/system/proxy/ftp_", false,
468 &proxy_server);
469 if (have_ftp)
470 config->proxy_rules.proxy_for_ftp = proxy_server;
471 if (have_http || have_secure || have_ftp)
472 config->proxy_rules.type =
473 ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
474 }
475 }
476
477 if (config->proxy_rules.empty()) {
478 // Manual mode but we couldn't parse any rules.
479 return false;
480 }
481
482 // Check for authentication, just so we can warn.
483 bool use_auth;
484 gconf_getter_->GetBoolean("/system/http_proxy/use_authentication",
485 &use_auth);
486 if (use_auth)
487 LOG(ERROR) << "ProxyConfigServiceLinux: proxy authentication "
488 "not supported";
489
490 // Now the bypass list.
491 gconf_getter_->GetStringList("/system/http_proxy/ignore_hosts",
492 &config->proxy_bypass);
493 // Note that there are no settings with semantics corresponding to
494 // config->proxy_bypass_local_names.
495
496 return true;
497 }
498
499 ProxyConfigServiceLinux::ProxyConfigServiceLinux(
500 EnvironmentVariableGetter* env_var_getter,
501 GConfSettingGetter* gconf_getter)
502 : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter) {
503 }
504
505 ProxyConfigServiceLinux::ProxyConfigServiceLinux()
506 : env_var_getter_(new EnvironmentVariableGetterImpl()),
507 gconf_getter_(new GConfSettingGetterImpl()) {
508 }
509
510 int ProxyConfigServiceLinux::GetProxyConfig(ProxyConfig* config) {
511 // GNOME_DESKTOP_SESSION_ID being defined is a good indication that
512 // we are probably running under GNOME.
513 // Note: KDE_FULL_SESSION is a corresponding env var to recognize KDE.
514 std::string dummy, desktop_session;
515 bool ok = false;
516 if (env_var_getter_->Getenv("GNOME_DESKTOP_SESSION_ID", &dummy)
517 || (env_var_getter_->Getenv("DESKTOP_SESSION", &desktop_session)
518 && desktop_session.compare("gnome"))) {
519 // Get settings from gconf.
520 //
521 // I (sdoyon) would have liked to prioritize environment variables
522 // and only fallback to gconf if env vars were unset. But
523 // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
524 // does so even if the proxy mode is set to auto, which would
525 // mislead us.
526 //
527 // We could introduce a CHROME_PROXY_OBEY_ENV_VARS variable...??
528 if (gconf_getter_->InitIfNeeded()) {
529 gconf_getter_->Enter();
530 ok = GetConfigFromGConf(config);
531 gconf_getter_->Leave();
532 if (ok)
533 LOG(INFO) << "ProxyConfigServiceLinux: obtained proxy setting "
534 "from gconf";
535 // If gconf proxy mode is "none", meaning direct, then we take
536 // that to be a valid config and will not check environment variables.
537 // The alternative would have been to look for a proxy whereever
538 // we can find one.
539 //
540 // TODO(sdoyon): Consider wiring in the gconf notification
541 // system. Cache this result config to return on subsequent calls,
542 // and only call GetConfigFromGConf() when we know things have
543 // actually changed.
544 }
545 }
546 // An implementation for KDE settings would be welcome here.
547 if (!ok) {
548 ok = GetConfigFromEnv(config);
549 if (ok)
550 LOG(INFO) << "ProxyConfigServiceLinux: obtained proxy setting "
551 "from environment variables";
552 }
553 return ok ? OK : ERR_FAILED;
554 }
555
556 } // namespace net
OLDNEW
« no previous file with comments | « net/proxy/proxy_config_service_linux.h ('k') | net/proxy/proxy_config_service_linux_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698