Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsISystemProxySettings.h"
7 #include "mozilla/ModuleUtils.h"
8 #include "nsIServiceManager.h"
9 #include "nsIGConfService.h"
10 #include "nsIURI.h"
11 #include "nsReadableUtils.h"
12 #include "nsArrayUtils.h"
13 #include "prnetdb.h"
14 #include "prenv.h"
15 #include "nsPrintfCString.h"
16 #include "nsNetUtil.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsIGSettingsService.h"
19 #include "nsInterfaceHashtable.h"
20 #include "mozilla/Attributes.h"
21 #include "nsIURI.h"
23 class nsUnixSystemProxySettings MOZ_FINAL : public nsISystemProxySettings {
24 public:
25 NS_DECL_ISUPPORTS
26 NS_DECL_NSISYSTEMPROXYSETTINGS
28 nsUnixSystemProxySettings()
29 : mSchemeProxySettings(5)
30 {
31 }
32 nsresult Init();
34 private:
35 ~nsUnixSystemProxySettings() {}
37 nsCOMPtr<nsIGConfService> mGConf;
38 nsCOMPtr<nsIGSettingsService> mGSettings;
39 nsCOMPtr<nsIGSettingsCollection> mProxySettings;
40 nsInterfaceHashtable<nsCStringHashKey, nsIGSettingsCollection> mSchemeProxySettings;
41 bool IsProxyMode(const char* aMode);
42 nsresult SetProxyResultFromGConf(const char* aKeyBase, const char* aType, nsACString& aResult);
43 nsresult GetProxyFromGConf(const nsACString& aScheme, const nsACString& aHost, int32_t aPort, nsACString& aResult);
44 nsresult GetProxyFromGSettings(const nsACString& aScheme, const nsACString& aHost, int32_t aPort, nsACString& aResult);
45 nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType, nsACString& aResult);
46 };
48 NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
50 NS_IMETHODIMP
51 nsUnixSystemProxySettings::GetMainThreadOnly(bool *aMainThreadOnly)
52 {
53 // dbus prevents us from being threadsafe, but this routine should not block anyhow
54 *aMainThreadOnly = true;
55 return NS_OK;
56 }
58 nsresult
59 nsUnixSystemProxySettings::Init()
60 {
61 mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
62 if (mGSettings) {
63 mGSettings->GetCollectionForSchema(NS_LITERAL_CSTRING("org.gnome.system.proxy"),
64 getter_AddRefs(mProxySettings));
65 }
66 if (!mProxySettings) {
67 mGConf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
68 }
70 return NS_OK;
71 }
73 bool
74 nsUnixSystemProxySettings::IsProxyMode(const char* aMode)
75 {
76 nsAutoCString mode;
77 return NS_SUCCEEDED(mGConf->GetString(NS_LITERAL_CSTRING("/system/proxy/mode"), mode)) &&
78 mode.EqualsASCII(aMode);
79 }
81 nsresult
82 nsUnixSystemProxySettings::GetPACURI(nsACString& aResult)
83 {
84 if (mProxySettings) {
85 nsCString proxyMode;
86 // Check if mode is auto
87 nsresult rv = mProxySettings->GetString(NS_LITERAL_CSTRING("mode"), proxyMode);
88 if (rv == NS_OK && proxyMode.Equals("auto")) {
89 return mProxySettings->GetString(NS_LITERAL_CSTRING("autoconfig-url"), aResult);
90 }
91 /* The org.gnome.system.proxy schema has been found, but auto mode is not set.
92 * Don't try the GConf and return empty string. */
93 aResult.Truncate();
94 return NS_OK;
95 }
97 if (mGConf && IsProxyMode("auto")) {
98 return mGConf->GetString(NS_LITERAL_CSTRING("/system/proxy/autoconfig_url"),
99 aResult);
100 }
101 // Return an empty string when auto mode is not set.
102 aResult.Truncate();
103 return NS_OK;
104 }
106 static bool
107 IsInNoProxyList(const nsACString& aHost, int32_t aPort, const char* noProxyVal)
108 {
109 NS_ASSERTION(aPort >= 0, "Negative port?");
111 nsAutoCString noProxy(noProxyVal);
112 if (noProxy.EqualsLiteral("*"))
113 return true;
115 noProxy.StripWhitespace();
117 nsReadingIterator<char> pos;
118 nsReadingIterator<char> end;
119 noProxy.BeginReading(pos);
120 noProxy.EndReading(end);
121 while (pos != end) {
122 nsReadingIterator<char> last = pos;
123 nsReadingIterator<char> nextPos;
124 if (FindCharInReadable(',', last, end)) {
125 nextPos = last;
126 ++nextPos;
127 } else {
128 last = end;
129 nextPos = end;
130 }
132 nsReadingIterator<char> colon = pos;
133 int32_t port = -1;
134 if (FindCharInReadable(':', colon, last)) {
135 ++colon;
136 nsDependentCSubstring portStr(colon, last);
137 nsAutoCString portStr2(portStr); // We need this for ToInteger. String API's suck.
138 nsresult err;
139 port = portStr2.ToInteger(&err);
140 if (NS_FAILED(err)) {
141 port = -2; // don't match any port, so we ignore this pattern
142 }
143 --colon;
144 } else {
145 colon = last;
146 }
148 if (port == -1 || port == aPort) {
149 nsDependentCSubstring hostStr(pos, colon);
150 // By using StringEndsWith instead of an equality comparator, we can include sub-domains
151 if (StringEndsWith(aHost, hostStr, nsCaseInsensitiveCStringComparator()))
152 return true;
153 }
155 pos = nextPos;
156 }
158 return false;
159 }
161 static void SetProxyResult(const char* aType, const nsACString& aHost,
162 int32_t aPort, nsACString& aResult)
163 {
164 aResult.AppendASCII(aType);
165 aResult.Append(' ');
166 aResult.Append(aHost);
167 if (aPort > 0) {
168 aResult.Append(':');
169 aResult.Append(nsPrintfCString("%d", aPort));
170 }
171 }
173 static nsresult
174 GetProxyFromEnvironment(const nsACString& aScheme,
175 const nsACString& aHost,
176 int32_t aPort,
177 nsACString& aResult)
178 {
179 nsAutoCString envVar;
180 envVar.Append(aScheme);
181 envVar.AppendLiteral("_proxy");
182 const char* proxyVal = PR_GetEnv(envVar.get());
183 if (!proxyVal) {
184 proxyVal = PR_GetEnv("all_proxy");
185 if (!proxyVal) {
186 // Return failure so that the caller can detect the failure and
187 // fall back to other proxy detection (e.g., WPAD)
188 return NS_ERROR_FAILURE;
189 }
190 }
192 const char* noProxyVal = PR_GetEnv("no_proxy");
193 if (noProxyVal && IsInNoProxyList(aHost, aPort, noProxyVal)) {
194 aResult.AppendLiteral("DIRECT");
195 return NS_OK;
196 }
198 // Use our URI parser to crack the proxy URI
199 nsCOMPtr<nsIURI> proxyURI;
200 nsresult rv = NS_NewURI(getter_AddRefs(proxyURI), proxyVal);
201 NS_ENSURE_SUCCESS(rv, rv);
203 // Is there a way to specify "socks://" or something in these environment
204 // variables? I can't find any documentation.
205 bool isHTTP;
206 rv = proxyURI->SchemeIs("http", &isHTTP);
207 NS_ENSURE_SUCCESS(rv, rv);
208 if (!isHTTP)
209 return NS_ERROR_UNKNOWN_PROTOCOL;
211 nsAutoCString proxyHost;
212 rv = proxyURI->GetHost(proxyHost);
213 NS_ENSURE_SUCCESS(rv, rv);
215 int32_t proxyPort;
216 rv = proxyURI->GetPort(&proxyPort);
217 NS_ENSURE_SUCCESS(rv, rv);
219 SetProxyResult("PROXY", proxyHost, proxyPort, aResult);
220 return NS_OK;
221 }
223 nsresult
224 nsUnixSystemProxySettings::SetProxyResultFromGConf(const char* aKeyBase, const char* aType,
225 nsACString& aResult)
226 {
227 nsAutoCString hostKey;
228 hostKey.AppendASCII(aKeyBase);
229 hostKey.AppendLiteral("host");
230 nsAutoCString host;
231 nsresult rv = mGConf->GetString(hostKey, host);
232 NS_ENSURE_SUCCESS(rv, rv);
233 if (host.IsEmpty())
234 return NS_ERROR_FAILURE;
236 nsAutoCString portKey;
237 portKey.AppendASCII(aKeyBase);
238 portKey.AppendLiteral("port");
239 int32_t port;
240 rv = mGConf->GetInt(portKey, &port);
241 NS_ENSURE_SUCCESS(rv, rv);
243 /* When port is 0, proxy is not considered as enabled even if host is set. */
244 if (port == 0)
245 return NS_ERROR_FAILURE;
247 SetProxyResult(aType, host, port, aResult);
248 return NS_OK;
249 }
251 nsresult
252 nsUnixSystemProxySettings::SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
253 nsACString& aResult)
254 {
255 nsDependentCString key(aKeyBase);
257 nsCOMPtr<nsIGSettingsCollection> proxy_settings = mSchemeProxySettings.Get(key);
258 nsresult rv;
259 if (!proxy_settings) {
260 rv = mGSettings->GetCollectionForSchema(key, getter_AddRefs(proxy_settings));
261 NS_ENSURE_SUCCESS(rv, rv);
263 mSchemeProxySettings.Put(key, proxy_settings);
264 }
266 nsAutoCString host;
267 rv = proxy_settings->GetString(NS_LITERAL_CSTRING("host"), host);
268 NS_ENSURE_SUCCESS(rv, rv);
269 if (host.IsEmpty())
270 return NS_ERROR_FAILURE;
272 int32_t port;
273 rv = proxy_settings->GetInt(NS_LITERAL_CSTRING("port"), &port);
274 NS_ENSURE_SUCCESS(rv, rv);
276 /* When port is 0, proxy is not considered as enabled even if host is set. */
277 if (port == 0)
278 return NS_ERROR_FAILURE;
280 SetProxyResult(aType, host, port, aResult);
281 return NS_OK;
282 }
284 /* copied from nsProtocolProxyService.cpp --- we should share this! */
285 static void
286 proxy_MaskIPv6Addr(PRIPv6Addr &addr, uint16_t mask_len)
287 {
288 if (mask_len == 128)
289 return;
291 if (mask_len > 96) {
292 addr.pr_s6_addr32[3] = PR_htonl(
293 PR_ntohl(addr.pr_s6_addr32[3]) & (~0L << (128 - mask_len)));
294 }
295 else if (mask_len > 64) {
296 addr.pr_s6_addr32[3] = 0;
297 addr.pr_s6_addr32[2] = PR_htonl(
298 PR_ntohl(addr.pr_s6_addr32[2]) & (~0L << (96 - mask_len)));
299 }
300 else if (mask_len > 32) {
301 addr.pr_s6_addr32[3] = 0;
302 addr.pr_s6_addr32[2] = 0;
303 addr.pr_s6_addr32[1] = PR_htonl(
304 PR_ntohl(addr.pr_s6_addr32[1]) & (~0L << (64 - mask_len)));
305 }
306 else {
307 addr.pr_s6_addr32[3] = 0;
308 addr.pr_s6_addr32[2] = 0;
309 addr.pr_s6_addr32[1] = 0;
310 addr.pr_s6_addr32[0] = PR_htonl(
311 PR_ntohl(addr.pr_s6_addr32[0]) & (~0L << (32 - mask_len)));
312 }
313 }
315 static bool ConvertToIPV6Addr(const nsACString& aName,
316 PRIPv6Addr* aAddr, int32_t* aMask)
317 {
318 PRNetAddr addr;
319 // try to convert hostname to IP
320 if (PR_StringToNetAddr(PromiseFlatCString(aName).get(), &addr) != PR_SUCCESS)
321 return false;
323 // convert parsed address to IPv6
324 if (addr.raw.family == PR_AF_INET) {
325 // convert to IPv4-mapped address
326 PR_ConvertIPv4AddrToIPv6(addr.inet.ip, aAddr);
327 if (aMask) {
328 if (*aMask <= 32)
329 *aMask += 96;
330 else
331 return false;
332 }
333 } else if (addr.raw.family == PR_AF_INET6) {
334 // copy the address
335 memcpy(aAddr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
336 } else {
337 return false;
338 }
340 return true;
341 }
343 static bool HostIgnoredByProxy(const nsACString& aIgnore,
344 const nsACString& aHost)
345 {
346 if (aIgnore.Equals(aHost, nsCaseInsensitiveCStringComparator()))
347 return true;
349 if (aIgnore.First() == '*' &&
350 StringEndsWith(aHost, nsDependentCSubstring(aIgnore, 1),
351 nsCaseInsensitiveCStringComparator()))
352 return true;
354 int32_t mask = 128;
355 nsReadingIterator<char> start;
356 nsReadingIterator<char> slash;
357 nsReadingIterator<char> end;
358 aIgnore.BeginReading(start);
359 aIgnore.BeginReading(slash);
360 aIgnore.EndReading(end);
361 if (FindCharInReadable('/', slash, end)) {
362 ++slash;
363 nsDependentCSubstring maskStr(slash, end);
364 nsAutoCString maskStr2(maskStr);
365 nsresult err;
366 mask = maskStr2.ToInteger(&err);
367 if (NS_FAILED(err)) {
368 mask = 128;
369 }
370 --slash;
371 } else {
372 slash = end;
373 }
375 nsDependentCSubstring ignoreStripped(start, slash);
376 PRIPv6Addr ignoreAddr, hostAddr;
377 if (!ConvertToIPV6Addr(ignoreStripped, &ignoreAddr, &mask) ||
378 !ConvertToIPV6Addr(aHost, &hostAddr, nullptr))
379 return false;
381 proxy_MaskIPv6Addr(ignoreAddr, mask);
382 proxy_MaskIPv6Addr(hostAddr, mask);
384 return memcmp(&ignoreAddr, &hostAddr, sizeof(PRIPv6Addr)) == 0;
385 }
387 nsresult
388 nsUnixSystemProxySettings::GetProxyFromGConf(const nsACString& aScheme,
389 const nsACString& aHost,
390 int32_t aPort,
391 nsACString& aResult)
392 {
393 bool masterProxySwitch = false;
394 mGConf->GetBool(NS_LITERAL_CSTRING("/system/http_proxy/use_http_proxy"), &masterProxySwitch);
395 // if no proxy is set in GConf return NS_ERROR_FAILURE
396 if (!(IsProxyMode("manual") || masterProxySwitch)) {
397 return NS_ERROR_FAILURE;
398 }
400 nsCOMPtr<nsIArray> ignoreList;
401 if (NS_SUCCEEDED(mGConf->GetStringList(NS_LITERAL_CSTRING("/system/http_proxy/ignore_hosts"),
402 getter_AddRefs(ignoreList))) && ignoreList) {
403 uint32_t len = 0;
404 ignoreList->GetLength(&len);
405 for (uint32_t i = 0; i < len; ++i) {
406 nsCOMPtr<nsISupportsString> str = do_QueryElementAt(ignoreList, i);
407 if (str) {
408 nsAutoString s;
409 if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
410 if (HostIgnoredByProxy(NS_ConvertUTF16toUTF8(s), aHost)) {
411 aResult.AppendLiteral("DIRECT");
412 return NS_OK;
413 }
414 }
415 }
416 }
417 }
419 bool useHttpProxyForAll = false;
420 // This setting sometimes doesn't exist, don't bail on failure
421 mGConf->GetBool(NS_LITERAL_CSTRING("/system/http_proxy/use_same_proxy"), &useHttpProxyForAll);
423 nsresult rv;
424 if (!useHttpProxyForAll) {
425 rv = SetProxyResultFromGConf("/system/proxy/socks_", "SOCKS", aResult);
426 if (NS_SUCCEEDED(rv))
427 return rv;
428 }
430 if (aScheme.LowerCaseEqualsLiteral("http") || useHttpProxyForAll) {
431 rv = SetProxyResultFromGConf("/system/http_proxy/", "PROXY", aResult);
432 } else if (aScheme.LowerCaseEqualsLiteral("https")) {
433 rv = SetProxyResultFromGConf("/system/proxy/secure_", "PROXY", aResult);
434 } else if (aScheme.LowerCaseEqualsLiteral("ftp")) {
435 rv = SetProxyResultFromGConf("/system/proxy/ftp_", "PROXY", aResult);
436 } else {
437 rv = NS_ERROR_FAILURE;
438 }
440 return rv;
441 }
443 nsresult
444 nsUnixSystemProxySettings::GetProxyFromGSettings(const nsACString& aScheme,
445 const nsACString& aHost,
446 int32_t aPort,
447 nsACString& aResult)
448 {
449 nsCString proxyMode;
450 nsresult rv = mProxySettings->GetString(NS_LITERAL_CSTRING("mode"), proxyMode);
451 NS_ENSURE_SUCCESS(rv, rv);
453 // return NS_ERROR_FAILURE when no proxy is set
454 if (!proxyMode.Equals("manual")) {
455 return NS_ERROR_FAILURE;
456 }
458 nsCOMPtr<nsIArray> ignoreList;
459 if (NS_SUCCEEDED(mProxySettings->GetStringList(NS_LITERAL_CSTRING("ignore-hosts"),
460 getter_AddRefs(ignoreList))) && ignoreList) {
461 uint32_t len = 0;
462 ignoreList->GetLength(&len);
463 for (uint32_t i = 0; i < len; ++i) {
464 nsCOMPtr<nsISupportsCString> str = do_QueryElementAt(ignoreList, i);
465 if (str) {
466 nsCString s;
467 if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
468 if (HostIgnoredByProxy(s, aHost)) {
469 aResult.AppendLiteral("DIRECT");
470 return NS_OK;
471 }
472 }
473 }
474 }
475 }
477 if (aScheme.LowerCaseEqualsLiteral("http")) {
478 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY", aResult);
479 } else if (aScheme.LowerCaseEqualsLiteral("https")) {
480 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.https", "PROXY", aResult);
481 /* Try to use HTTP proxy when HTTPS proxy is not explicitly defined */
482 if (rv != NS_OK)
483 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY", aResult);
484 } else if (aScheme.LowerCaseEqualsLiteral("ftp")) {
485 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.ftp", "PROXY", aResult);
486 } else {
487 rv = NS_ERROR_FAILURE;
488 }
489 if (rv != NS_OK) {
490 /* If proxy for scheme is not specified, use SOCKS proxy for all schemes */
491 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.socks", "SOCKS", aResult);
492 }
494 if (NS_FAILED(rv)) {
495 aResult.AppendLiteral("DIRECT");
496 }
498 return NS_OK;
499 }
501 nsresult
502 nsUnixSystemProxySettings::GetProxyForURI(const nsACString & aSpec,
503 const nsACString & aScheme,
504 const nsACString & aHost,
505 const int32_t aPort,
506 nsACString & aResult)
507 {
508 if (mProxySettings) {
509 nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult);
510 if (NS_SUCCEEDED(rv))
511 return rv;
512 }
513 if (mGConf)
514 return GetProxyFromGConf(aScheme, aHost, aPort, aResult);
516 return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
517 }
519 #define NS_UNIXSYSTEMPROXYSERVICE_CID /* 0fa3158c-d5a7-43de-9181-a285e74cf1d4 */\
520 { 0x0fa3158c, 0xd5a7, 0x43de, \
521 {0x91, 0x81, 0xa2, 0x85, 0xe7, 0x4c, 0xf1, 0xd4 } }
523 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUnixSystemProxySettings, Init)
524 NS_DEFINE_NAMED_CID(NS_UNIXSYSTEMPROXYSERVICE_CID);
526 static const mozilla::Module::CIDEntry kUnixProxyCIDs[] = {
527 { &kNS_UNIXSYSTEMPROXYSERVICE_CID, false, nullptr, nsUnixSystemProxySettingsConstructor },
528 { nullptr }
529 };
531 static const mozilla::Module::ContractIDEntry kUnixProxyContracts[] = {
532 { NS_SYSTEMPROXYSETTINGS_CONTRACTID, &kNS_UNIXSYSTEMPROXYSERVICE_CID },
533 { nullptr }
534 };
536 static const mozilla::Module kUnixProxyModule = {
537 mozilla::Module::kVersion,
538 kUnixProxyCIDs,
539 kUnixProxyContracts
540 };
542 NSMODULE_DEFN(nsUnixProxyModule) = &kUnixProxyModule;