1 // Copyright 2012 The Chromium Authors
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_resolution/proxy_config.h"
6
7 #include "base/json/json_writer.h"
8 #include "base/values.h"
9 #include "net/base/proxy_string_util.h"
10 #include "net/proxy_resolution/proxy_config_service_common_unittest.h"
11 #include "net/proxy_resolution/proxy_info.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13
14 namespace net {
15 namespace {
16
ExpectProxyServerEquals(const char * expectation,const ProxyList & proxy_list)17 void ExpectProxyServerEquals(const char* expectation,
18 const ProxyList& proxy_list) {
19 if (expectation == nullptr) {
20 EXPECT_TRUE(proxy_list.IsEmpty());
21 } else {
22 EXPECT_EQ(expectation, proxy_list.ToDebugString());
23 }
24 }
25
TEST(ProxyConfigTest,Equals)26 TEST(ProxyConfigTest, Equals) {
27 // Test |ProxyConfig::auto_detect|.
28
29 ProxyConfig config1;
30 config1.set_auto_detect(true);
31
32 ProxyConfig config2;
33 config2.set_auto_detect(false);
34
35 EXPECT_FALSE(config1.Equals(config2));
36 EXPECT_FALSE(config2.Equals(config1));
37
38 config2.set_auto_detect(true);
39
40 EXPECT_TRUE(config1.Equals(config2));
41 EXPECT_TRUE(config2.Equals(config1));
42
43 // Test |ProxyConfig::pac_url|.
44
45 config2.set_pac_url(GURL("http://wpad/wpad.dat"));
46
47 EXPECT_FALSE(config1.Equals(config2));
48 EXPECT_FALSE(config2.Equals(config1));
49
50 config1.set_pac_url(GURL("http://wpad/wpad.dat"));
51
52 EXPECT_TRUE(config1.Equals(config2));
53 EXPECT_TRUE(config2.Equals(config1));
54
55 // Test |ProxyConfig::proxy_rules|.
56
57 config2.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
58 config2.proxy_rules().single_proxies.SetSingleProxyServer(
59 ProxyUriToProxyServer("myproxy:80", ProxyServer::SCHEME_HTTP));
60
61 EXPECT_FALSE(config1.Equals(config2));
62 EXPECT_FALSE(config2.Equals(config1));
63
64 config1.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
65 config1.proxy_rules().single_proxies.SetSingleProxyServer(
66 ProxyUriToProxyServer("myproxy:100", ProxyServer::SCHEME_HTTP));
67
68 EXPECT_FALSE(config1.Equals(config2));
69 EXPECT_FALSE(config2.Equals(config1));
70
71 config1.proxy_rules().single_proxies.SetSingleProxyServer(
72 ProxyUriToProxyServer("myproxy", ProxyServer::SCHEME_HTTP));
73
74 EXPECT_TRUE(config1.Equals(config2));
75 EXPECT_TRUE(config2.Equals(config1));
76
77 // Test |ProxyConfig::bypass_rules|.
78
79 config2.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
80
81 EXPECT_FALSE(config1.Equals(config2));
82 EXPECT_FALSE(config2.Equals(config1));
83
84 config1.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
85
86 EXPECT_TRUE(config1.Equals(config2));
87 EXPECT_TRUE(config2.Equals(config1));
88
89 // Test |ProxyConfig::proxy_rules.reverse_bypass|.
90
91 config2.proxy_rules().reverse_bypass = true;
92
93 EXPECT_FALSE(config1.Equals(config2));
94 EXPECT_FALSE(config2.Equals(config1));
95
96 config1.proxy_rules().reverse_bypass = true;
97
98 EXPECT_TRUE(config1.Equals(config2));
99 EXPECT_TRUE(config2.Equals(config1));
100 }
101
102 struct ProxyConfigToValueTestCase {
103 ProxyConfig config;
104 const char* expected_value_json;
105 };
106
107 class ProxyConfigToValueTest
108 : public ::testing::TestWithParam<ProxyConfigToValueTestCase> {};
109
TEST_P(ProxyConfigToValueTest,ToValueJSON)110 TEST_P(ProxyConfigToValueTest, ToValueJSON) {
111 const ProxyConfigToValueTestCase& test_case = GetParam();
112
113 base::Value value = test_case.config.ToValue();
114
115 std::string json_string;
116 ASSERT_TRUE(base::JSONWriter::Write(value, &json_string));
117
118 EXPECT_EQ(std::string(test_case.expected_value_json), json_string);
119 }
120
GetTestCaseDirect()121 ProxyConfigToValueTestCase GetTestCaseDirect() {
122 return {ProxyConfig::CreateDirect(), "{}"};
123 }
124
GetTestCaseAutoDetect()125 ProxyConfigToValueTestCase GetTestCaseAutoDetect() {
126 return {ProxyConfig::CreateAutoDetect(), "{\"auto_detect\":true}"};
127 }
128
GetTestCasePacUrl()129 ProxyConfigToValueTestCase GetTestCasePacUrl() {
130 ProxyConfig config;
131 config.set_pac_url(GURL("http://www.example.com/test.pac"));
132
133 return {std::move(config),
134 "{\"pac_url\":\"http://www.example.com/test.pac\"}"};
135 }
136
GetTestCasePacUrlMandatory()137 ProxyConfigToValueTestCase GetTestCasePacUrlMandatory() {
138 ProxyConfig config;
139 config.set_pac_url(GURL("http://www.example.com/test.pac"));
140 config.set_pac_mandatory(true);
141
142 return {std::move(config),
143 "{\"pac_mandatory\":true,\"pac_url\":\"http://www.example.com/"
144 "test.pac\"}"};
145 }
146
GetTestCasePacUrlAndAutoDetect()147 ProxyConfigToValueTestCase GetTestCasePacUrlAndAutoDetect() {
148 ProxyConfig config = ProxyConfig::CreateAutoDetect();
149 config.set_pac_url(GURL("http://www.example.com/test.pac"));
150
151 return {
152 std::move(config),
153 "{\"auto_detect\":true,\"pac_url\":\"http://www.example.com/test.pac\"}"};
154 }
155
GetTestCaseSingleProxy()156 ProxyConfigToValueTestCase GetTestCaseSingleProxy() {
157 ProxyConfig config;
158 config.proxy_rules().ParseFromString("https://proxy1:8080");
159
160 return {std::move(config), "{\"single_proxy\":[\"[https://proxy1:8080]\"]}"};
161 }
162
GetTestCaseSingleProxyWithBypass()163 ProxyConfigToValueTestCase GetTestCaseSingleProxyWithBypass() {
164 ProxyConfig config;
165 config.proxy_rules().ParseFromString("https://proxy1:8080");
166 config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
167 config.proxy_rules().bypass_rules.AddRuleFromString("192.168.0.1/16");
168
169 return {std::move(config),
170 "{\"bypass_list\":[\"*.google.com\",\"192.168.0.1/"
171 "16\"],\"single_proxy\":[\"[https://proxy1:8080]\"]}"};
172 }
173
GetTestCaseSingleProxyWithReversedBypass()174 ProxyConfigToValueTestCase GetTestCaseSingleProxyWithReversedBypass() {
175 ProxyConfig config;
176 config.proxy_rules().ParseFromString("https://proxy1:8080");
177 config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
178 config.proxy_rules().reverse_bypass = true;
179
180 return {std::move(config),
181 "{\"bypass_list\":[\"*.google.com\"],\"reverse_bypass\":true,"
182 "\"single_proxy\":[\"[https://proxy1:8080]\"]}"};
183 }
184
GetTestCaseProxyPerScheme()185 ProxyConfigToValueTestCase GetTestCaseProxyPerScheme() {
186 ProxyConfig config;
187 config.proxy_rules().ParseFromString(
188 "http=https://proxy1:8080;https=socks5://proxy2");
189 config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
190 config.set_pac_url(GURL("http://wpad/wpad.dat"));
191 config.set_auto_detect(true);
192
193 return {
194 std::move(config),
195 "{\"auto_detect\":true,\"bypass_list\":[\"*.google.com\"],\"pac_url\":"
196 "\"http://wpad/wpad.dat\",\"proxy_per_scheme\":{\"http\":[\"[https://"
197 "proxy1:8080]\"],\"https\":[\"[socks5://proxy2:1080]\"]}}"};
198 }
199
GetTestCaseSingleProxyList()200 ProxyConfigToValueTestCase GetTestCaseSingleProxyList() {
201 ProxyConfig config;
202 config.proxy_rules().ParseFromString(
203 "https://proxy1:8080,http://proxy2,direct://");
204
205 return {
206 std::move(config),
207 "{\"single_proxy\":[\"[https://proxy1:8080]\",\"[proxy2:80]\",\"direct://"
208 "\"]}"};
209 }
210
211 INSTANTIATE_TEST_SUITE_P(
212 All,
213 ProxyConfigToValueTest,
214 testing::Values(GetTestCaseDirect(),
215 GetTestCaseAutoDetect(),
216 GetTestCasePacUrl(),
217 GetTestCasePacUrlMandatory(),
218 GetTestCasePacUrlAndAutoDetect(),
219 GetTestCaseSingleProxy(),
220 GetTestCaseSingleProxyWithBypass(),
221 GetTestCaseSingleProxyWithReversedBypass(),
222 GetTestCaseProxyPerScheme(),
223 GetTestCaseSingleProxyList()));
224
TEST(ProxyConfigTest,ParseProxyRules)225 TEST(ProxyConfigTest, ParseProxyRules) {
226 const struct {
227 const char* proxy_rules;
228
229 ProxyConfig::ProxyRules::Type type;
230 // These will be PAC-stle strings, eg 'PROXY foo.com'
231 const char* single_proxy;
232 const char* proxy_for_http;
233 const char* proxy_for_https;
234 const char* proxy_for_ftp;
235 const char* fallback_proxy;
236 } tests[] = {
237 // One HTTP proxy for all schemes.
238 {
239 "myproxy:80",
240
241 ProxyConfig::ProxyRules::Type::PROXY_LIST,
242 "PROXY myproxy:80",
243 nullptr,
244 nullptr,
245 nullptr,
246 nullptr,
247 },
248
249 // Multiple HTTP proxies for all schemes.
250 {
251 "myproxy:80,https://myotherproxy",
252
253 ProxyConfig::ProxyRules::Type::PROXY_LIST,
254 "PROXY myproxy:80;HTTPS myotherproxy:443",
255 nullptr,
256 nullptr,
257 nullptr,
258 nullptr,
259 },
260
261 // Only specify a proxy server for "http://" urls.
262 {
263 "http=myproxy:80",
264
265 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
266 nullptr,
267 "PROXY myproxy:80",
268 nullptr,
269 nullptr,
270 nullptr,
271 },
272
273 // Specify an HTTP proxy for "ftp://" and a SOCKS proxy for "https://"
274 // urls.
275 {
276 "ftp=ftp-proxy ; https=socks4://foopy",
277
278 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
279 nullptr,
280 nullptr,
281 "SOCKS foopy:1080",
282 "PROXY ftp-proxy:80",
283 nullptr,
284 },
285
286 // Give a scheme-specific proxy as well as a non-scheme specific.
287 // The first entry "foopy" takes precedance marking this list as
288 // Type::PROXY_LIST.
289 {
290 "foopy ; ftp=ftp-proxy",
291
292 ProxyConfig::ProxyRules::Type::PROXY_LIST,
293 "PROXY foopy:80",
294 nullptr,
295 nullptr,
296 nullptr,
297 nullptr,
298 },
299
300 // Give a scheme-specific proxy as well as a non-scheme specific.
301 // The first entry "ftp=ftp-proxy" takes precedance marking this list as
302 // Type::PROXY_LIST_PER_SCHEME.
303 {
304 "ftp=ftp-proxy ; foopy",
305
306 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
307 nullptr,
308 nullptr,
309 nullptr,
310 "PROXY ftp-proxy:80",
311 nullptr,
312 },
313
314 // Include a list of entries for a single scheme.
315 {
316 "ftp=ftp1,ftp2,ftp3",
317
318 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
319 nullptr,
320 nullptr,
321 nullptr,
322 "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80",
323 nullptr,
324 },
325
326 // Include multiple entries for the same scheme -- they accumulate.
327 {
328 "http=http1,http2; http=http3",
329
330 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
331 nullptr,
332 "PROXY http1:80;PROXY http2:80;PROXY http3:80",
333 nullptr,
334 nullptr,
335 nullptr,
336 },
337
338 // Include lists of entries for multiple schemes.
339 {
340 "ftp=ftp1,ftp2,ftp3 ; http=http1,http2; ",
341
342 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
343 nullptr,
344 "PROXY http1:80;PROXY http2:80",
345 nullptr,
346 "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80",
347 nullptr,
348 },
349
350 // Include non-default proxy schemes.
351 {
352 "http=https://secure_proxy; ftp=socks4://socks_proxy; "
353 "https=socks://foo",
354
355 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
356 nullptr,
357 "HTTPS secure_proxy:443",
358 "SOCKS5 foo:1080",
359 "SOCKS socks_proxy:1080",
360 nullptr,
361 },
362
363 // Only SOCKS proxy present, others being blank.
364 {
365 "socks=foopy",
366
367 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
368 nullptr,
369 nullptr,
370 nullptr,
371 nullptr,
372 "SOCKS foopy:1080",
373 },
374
375 // SOCKS proxy present along with other proxies too
376 {
377 "http=httpproxy ; https=httpsproxy ; ftp=ftpproxy ; socks=foopy ",
378
379 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
380 nullptr,
381 "PROXY httpproxy:80",
382 "PROXY httpsproxy:80",
383 "PROXY ftpproxy:80",
384 "SOCKS foopy:1080",
385 },
386
387 // SOCKS proxy (with modifier) present along with some proxies
388 // (FTP being blank)
389 {
390 "http=httpproxy ; https=httpsproxy ; socks=socks5://foopy ",
391
392 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
393 nullptr,
394 "PROXY httpproxy:80",
395 "PROXY httpsproxy:80",
396 nullptr,
397 "SOCKS5 foopy:1080",
398 },
399
400 // Include unsupported schemes -- they are discarded.
401 {
402 "crazy=foopy ; foo=bar ; https=myhttpsproxy",
403
404 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
405 nullptr,
406 nullptr,
407 "PROXY myhttpsproxy:80",
408 nullptr,
409 nullptr,
410 },
411
412 // direct:// as first option for a scheme.
413 {
414 "http=direct://,myhttpproxy; https=direct://",
415
416 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
417 nullptr,
418 "DIRECT;PROXY myhttpproxy:80",
419 "DIRECT",
420 nullptr,
421 nullptr,
422 },
423
424 // direct:// as a second option for a scheme.
425 {
426 "http=myhttpproxy,direct://",
427
428 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
429 nullptr,
430 "PROXY myhttpproxy:80;DIRECT",
431 nullptr,
432 nullptr,
433 nullptr,
434 },
435
436 };
437
438 ProxyConfig config;
439
440 for (const auto& test : tests) {
441 config.proxy_rules().ParseFromString(test.proxy_rules);
442
443 EXPECT_EQ(test.type, config.proxy_rules().type);
444 ExpectProxyServerEquals(test.single_proxy,
445 config.proxy_rules().single_proxies);
446 ExpectProxyServerEquals(test.proxy_for_http,
447 config.proxy_rules().proxies_for_http);
448 ExpectProxyServerEquals(test.proxy_for_https,
449 config.proxy_rules().proxies_for_https);
450 ExpectProxyServerEquals(test.proxy_for_ftp,
451 config.proxy_rules().proxies_for_ftp);
452 ExpectProxyServerEquals(test.fallback_proxy,
453 config.proxy_rules().fallback_proxies);
454 }
455 }
456
TEST(ProxyConfigTest,ProxyRulesSetBypassFlag)457 TEST(ProxyConfigTest, ProxyRulesSetBypassFlag) {
458 // Test whether the did_bypass_proxy() flag is set in proxy info correctly.
459 ProxyConfig::ProxyRules rules;
460 ProxyInfo result;
461
462 rules.ParseFromString("http=httpproxy:80");
463 rules.bypass_rules.AddRuleFromString(".com");
464
465 rules.Apply(GURL("http://example.com"), &result);
466 EXPECT_TRUE(result.is_direct_only());
467 EXPECT_TRUE(result.did_bypass_proxy());
468
469 rules.Apply(GURL("http://example.org"), &result);
470 EXPECT_FALSE(result.is_direct());
471 EXPECT_FALSE(result.did_bypass_proxy());
472
473 // Try with reversed bypass rules.
474 rules.reverse_bypass = true;
475
476 rules.Apply(GURL("http://example.org"), &result);
477 EXPECT_TRUE(result.is_direct_only());
478 EXPECT_TRUE(result.did_bypass_proxy());
479
480 rules.Apply(GURL("http://example.com"), &result);
481 EXPECT_FALSE(result.is_direct());
482 EXPECT_FALSE(result.did_bypass_proxy());
483 }
484
485 static const char kWsUrl[] = "ws://example.com/echo";
486 static const char kWssUrl[] = "wss://example.com/echo";
487
488 class ProxyConfigWebSocketTest : public ::testing::Test {
489 protected:
ParseFromString(const std::string & rules)490 void ParseFromString(const std::string& rules) {
491 rules_.ParseFromString(rules);
492 }
Apply(const GURL & gurl)493 void Apply(const GURL& gurl) { rules_.Apply(gurl, &info_); }
ToDebugString() const494 std::string ToDebugString() const { return info_.ToDebugString(); }
495
WsUrl()496 static GURL WsUrl() { return GURL(kWsUrl); }
WssUrl()497 static GURL WssUrl() { return GURL(kWssUrl); }
498
499 ProxyConfig::ProxyRules rules_;
500 ProxyInfo info_;
501 };
502
503 // If a single proxy is set for all protocols, WebSocket uses it.
TEST_F(ProxyConfigWebSocketTest,UsesProxy)504 TEST_F(ProxyConfigWebSocketTest, UsesProxy) {
505 ParseFromString("proxy:3128");
506 Apply(WsUrl());
507 EXPECT_EQ("PROXY proxy:3128", ToDebugString());
508 }
509
510 // See RFC6455 Section 4.1. item 3, "_Proxy Usage_". Note that this favors a
511 // SOCKSv4 proxy (although technically the spec only notes SOCKSv5).
TEST_F(ProxyConfigWebSocketTest,PrefersSocksV4)512 TEST_F(ProxyConfigWebSocketTest, PrefersSocksV4) {
513 ParseFromString(
514 "http=proxy:3128 ; https=sslproxy:3128 ; socks=socksproxy:1080");
515 Apply(WsUrl());
516 EXPECT_EQ("SOCKS socksproxy:1080", ToDebugString());
517 }
518
519 // See RFC6455 Section 4.1. item 3, "_Proxy Usage_".
TEST_F(ProxyConfigWebSocketTest,PrefersSocksV5)520 TEST_F(ProxyConfigWebSocketTest, PrefersSocksV5) {
521 ParseFromString(
522 "http=proxy:3128 ; https=sslproxy:3128 ; socks=socks5://socksproxy:1080");
523 Apply(WsUrl());
524 EXPECT_EQ("SOCKS5 socksproxy:1080", ToDebugString());
525 }
526
TEST_F(ProxyConfigWebSocketTest,PrefersHttpsToHttp)527 TEST_F(ProxyConfigWebSocketTest, PrefersHttpsToHttp) {
528 ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
529 Apply(WssUrl());
530 EXPECT_EQ("PROXY sslproxy:3128", ToDebugString());
531 }
532
533 // Tests when a proxy-per-url-scheme configuration was used, and proxies are
534 // specified for http://, https://, and a fallback proxy (non-SOCKS).
535 // Even though the fallback proxy is not SOCKS, it is still favored over the
536 // proxy for http://* and https://*.
TEST_F(ProxyConfigWebSocketTest,PrefersNonSocksFallbackOverHttps)537 TEST_F(ProxyConfigWebSocketTest, PrefersNonSocksFallbackOverHttps) {
538 // The notation for "socks=" is abused to set the "fallback proxy".
539 ParseFromString(
540 "http=proxy:3128 ; https=sslproxy:3128; socks=https://httpsproxy");
541 EXPECT_EQ("HTTPS httpsproxy:443", rules_.fallback_proxies.ToDebugString());
542 Apply(WssUrl());
543 EXPECT_EQ("HTTPS httpsproxy:443", ToDebugString());
544 }
545
546 // Tests when a proxy-per-url-scheme configuration was used, and the fallback
547 // proxy is a non-SOCKS proxy, and no proxy was given for https://* or
548 // http://*. The fallback proxy is used.
TEST_F(ProxyConfigWebSocketTest,UsesNonSocksFallbackProxy)549 TEST_F(ProxyConfigWebSocketTest, UsesNonSocksFallbackProxy) {
550 // The notation for "socks=" is abused to set the "fallback proxy".
551 ParseFromString("ftp=ftpproxy:3128; socks=https://httpsproxy");
552 EXPECT_EQ("HTTPS httpsproxy:443", rules_.fallback_proxies.ToDebugString());
553 Apply(WssUrl());
554 EXPECT_EQ("HTTPS httpsproxy:443", ToDebugString());
555 }
556
TEST_F(ProxyConfigWebSocketTest,PrefersHttpsEvenForWs)557 TEST_F(ProxyConfigWebSocketTest, PrefersHttpsEvenForWs) {
558 ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
559 Apply(WsUrl());
560 EXPECT_EQ("PROXY sslproxy:3128", ToDebugString());
561 }
562
TEST_F(ProxyConfigWebSocketTest,PrefersHttpToDirect)563 TEST_F(ProxyConfigWebSocketTest, PrefersHttpToDirect) {
564 ParseFromString("http=proxy:3128");
565 Apply(WssUrl());
566 EXPECT_EQ("PROXY proxy:3128", ToDebugString());
567 }
568
TEST_F(ProxyConfigWebSocketTest,IgnoresFtpProxy)569 TEST_F(ProxyConfigWebSocketTest, IgnoresFtpProxy) {
570 ParseFromString("ftp=ftpproxy:3128");
571 Apply(WssUrl());
572 EXPECT_EQ("DIRECT", ToDebugString());
573 }
574
TEST_F(ProxyConfigWebSocketTest,ObeysBypassRules)575 TEST_F(ProxyConfigWebSocketTest, ObeysBypassRules) {
576 ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
577 rules_.bypass_rules.AddRuleFromString(".chromium.org");
578 Apply(GURL("wss://codereview.chromium.org/feed"));
579 EXPECT_EQ("DIRECT", ToDebugString());
580 }
581
TEST_F(ProxyConfigWebSocketTest,ObeysLocalBypass)582 TEST_F(ProxyConfigWebSocketTest, ObeysLocalBypass) {
583 ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
584 rules_.bypass_rules.AddRuleFromString("<local>");
585 Apply(GURL("ws://localhost/feed"));
586 EXPECT_EQ("DIRECT", ToDebugString());
587 }
588
589 } // namespace
590 } // namespace net
591