1 // Copyright 2015 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 "content/browser/browsing_data/browsing_data_filter_builder_impl.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <string>
10 #include <vector>
11 
12 #include "base/callback.h"
13 #include "base/optional.h"
14 #include "net/cookies/canonical_cookie.h"
15 #include "net/cookies/cookie_deletion_info.h"
16 #include "services/network/cookie_manager.h"
17 #include "testing/gmock/include/gmock/gmock.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "url/gurl.h"
20 #include "url/origin.h"
21 
22 using CookieDeletionInfo = net::CookieDeletionInfo;
23 
24 namespace content {
25 
26 namespace {
27 
28 const char kGoogleDomain[] = "google.com";
29 // sp.nom.br is an eTLD, so this is a regular valid registrable domain, just
30 // like google.com.
31 const char kLongETLDDomain[] = "website.sp.nom.br";
32 // This domain will also not be found in registries, and since it has only
33 // one component, it will not be recognized as a valid registrable domain.
34 const char kInternalHostname[] = "fileserver";
35 // This domain will not be found in registries. It will be assumed that
36 // it belongs to an unknown registry, and since it has two components,
37 // they will be treated as the second level domain and TLD. Most importantly,
38 // it will NOT be treated as a subdomain of "fileserver".
39 const char kUnknownRegistryDomain[] = "second-level-domain.fileserver";
40 // IP addresses are supported.
41 const char kIPAddress[] = "192.168.1.1";
42 
43 struct TestCase {
44   std::string url;
45   bool should_match;
46 };
47 
RunTestCase(TestCase test_case,const base::RepeatingCallback<bool (const GURL &)> & filter)48 void RunTestCase(TestCase test_case,
49                  const base::RepeatingCallback<bool(const GURL&)>& filter) {
50   GURL url(test_case.url);
51   EXPECT_TRUE(url.is_valid()) << test_case.url << " is not valid.";
52   EXPECT_EQ(test_case.should_match, filter.Run(GURL(test_case.url)))
53       << test_case.url;
54 }
55 
RunTestCase(TestCase test_case,network::mojom::CookieDeletionFilterPtr deletion_filter)56 void RunTestCase(TestCase test_case,
57                  network::mojom::CookieDeletionFilterPtr deletion_filter) {
58   // Test with regular cookie, http only, domain, and secure.
59   CookieDeletionInfo delete_info =
60       network::DeletionFilterToInfo(std::move(deletion_filter));
61   std::string cookie_line = "A=2";
62   GURL test_url(test_case.url);
63   EXPECT_TRUE(test_url.is_valid()) << test_case.url;
64   std::unique_ptr<net::CanonicalCookie> cookie =
65       net::CanonicalCookie::Create(test_url, cookie_line, base::Time::Now(),
66                                    base::nullopt /* server_time */);
67   EXPECT_TRUE(cookie) << cookie_line << " from " << test_case.url
68                       << " is not a valid cookie";
69   if (cookie)
70     EXPECT_EQ(test_case.should_match, delete_info.Matches(*cookie))
71         << cookie->DebugString();
72 
73   cookie_line = std::string("A=2;domain=") + test_url.host();
74   cookie =
75       net::CanonicalCookie::Create(test_url, cookie_line, base::Time::Now(),
76                                    base::nullopt /* server_time */);
77   if (cookie)
78     EXPECT_EQ(test_case.should_match, delete_info.Matches(*cookie))
79         << cookie->DebugString();
80 
81   cookie_line = std::string("A=2; HttpOnly;") + test_url.host();
82   cookie =
83       net::CanonicalCookie::Create(test_url, cookie_line, base::Time::Now(),
84                                    base::nullopt /* server_time */);
85   if (cookie)
86     EXPECT_EQ(test_case.should_match, delete_info.Matches(*cookie))
87         << cookie->DebugString();
88 
89   cookie_line = std::string("A=2; HttpOnly; Secure;") + test_url.host();
90   cookie =
91       net::CanonicalCookie::Create(test_url, cookie_line, base::Time::Now(),
92                                    base::nullopt /* server_time */);
93   if (cookie)
94     EXPECT_EQ(test_case.should_match, delete_info.Matches(*cookie))
95         << cookie->DebugString();
96 }
97 
RunTestCase(TestCase test_case,const base::RepeatingCallback<bool (const std::string &)> & filter)98 void RunTestCase(
99     TestCase test_case,
100     const base::RepeatingCallback<bool(const std::string&)>& filter) {
101   std::string channel_id_server_id = test_case.url;
102   EXPECT_EQ(test_case.should_match, filter.Run(channel_id_server_id))
103       << channel_id_server_id << " should "
104       << (test_case.should_match ? "" : "NOT ") << "be matched by the filter.";
105 }
106 
107 }  // namespace
108 
TEST(BrowsingDataFilterBuilderImplTest,Noop)109 TEST(BrowsingDataFilterBuilderImplTest, Noop) {
110   // An no-op filter matches everything.
111   base::RepeatingCallback<bool(const GURL&)> filter =
112       BrowsingDataFilterBuilder::BuildNoopFilter();
113 
114   TestCase test_cases[] = {
115       {"https://www.google.com", true},
116       {"https://www.chrome.com", true},
117       {"http://www.google.com/foo/bar", true},
118       {"https://website.sp.nom.br", true},
119   };
120 
121   for (TestCase test_case : test_cases)
122     RunTestCase(test_case, filter);
123 }
124 
TEST(BrowsingDataFilterBuilderImplTest,RegistrableDomainGURLWhitelist)125 TEST(BrowsingDataFilterBuilderImplTest,
126      RegistrableDomainGURLWhitelist) {
127   BrowsingDataFilterBuilderImpl builder(
128       BrowsingDataFilterBuilderImpl::WHITELIST);
129   builder.AddRegisterableDomain(std::string(kGoogleDomain));
130   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
131   builder.AddRegisterableDomain(std::string(kIPAddress));
132   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
133   builder.AddRegisterableDomain(std::string(kInternalHostname));
134   base::RepeatingCallback<bool(const GURL&)> filter =
135       builder.BuildGeneralFilter();
136 
137   TestCase test_cases[] = {
138       // We match any URL on the specified domains.
139       {"http://www.google.com/foo/bar", true},
140       {"https://www.sub.google.com/foo/bar", true},
141       {"https://sub.google.com", true},
142       {"http://www.sub.google.com:8000/foo/bar", true},
143       {"https://website.sp.nom.br", true},
144       {"https://www.website.sp.nom.br", true},
145       {"http://192.168.1.1", true},
146       {"http://192.168.1.1:80", true},
147 
148       // Internal hostnames do not have subdomains.
149       {"http://fileserver", true },
150       {"http://fileserver/foo/bar", true },
151       {"http://website.fileserver/foo/bar", false },
152 
153       // This is a valid registrable domain with the TLD "fileserver", which
154       // is unrelated to the internal hostname "fileserver".
155       {"http://second-level-domain.fileserver/foo", true},
156       {"http://www.second-level-domain.fileserver/index.html", true},
157 
158       // Different domains.
159       {"https://www.youtube.com", false},
160       {"https://www.google.net", false},
161       {"http://192.168.1.2", false},
162 
163       // Check both a bare eTLD.
164       {"https://sp.nom.br", false},
165   };
166 
167   for (TestCase test_case : test_cases)
168     RunTestCase(test_case, filter);
169 }
170 
TEST(BrowsingDataFilterBuilderImplTest,RegistrableDomainGURLBlacklist)171 TEST(BrowsingDataFilterBuilderImplTest,
172      RegistrableDomainGURLBlacklist) {
173   BrowsingDataFilterBuilderImpl builder(
174       BrowsingDataFilterBuilderImpl::BLACKLIST);
175   builder.AddRegisterableDomain(std::string(kGoogleDomain));
176   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
177   builder.AddRegisterableDomain(std::string(kIPAddress));
178   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
179   builder.AddRegisterableDomain(std::string(kInternalHostname));
180   base::RepeatingCallback<bool(const GURL&)> filter =
181       builder.BuildGeneralFilter();
182 
183   TestCase test_cases[] = {
184       // We match any URL that are not on the specified domains.
185       {"http://www.google.com/foo/bar", false},
186       {"https://www.sub.google.com/foo/bar", false},
187       {"https://sub.google.com", false},
188       {"http://www.sub.google.com:8000/foo/bar", false},
189       {"https://website.sp.nom.br", false},
190       {"https://www.website.sp.nom.br", false},
191       {"http://192.168.1.1", false},
192       {"http://192.168.1.1:80", false},
193 
194       // Internal hostnames do not have subdomains.
195       {"http://fileserver", false },
196       {"http://fileserver/foo/bar", false },
197       {"http://website.fileserver/foo/bar", true },
198 
199       // This is a valid registrable domain with the TLD "fileserver", which
200       // is unrelated to the internal hostname "fileserver".
201       {"http://second-level-domain.fileserver/foo", false},
202       {"http://www.second-level-domain.fileserver/index.html", false},
203 
204       // Different domains.
205       {"https://www.youtube.com", true},
206       {"https://www.google.net", true},
207       {"http://192.168.1.2", true},
208 
209       // Check our bare eTLD.
210       {"https://sp.nom.br", true},
211   };
212 
213   for (TestCase test_case : test_cases)
214     RunTestCase(test_case, filter);
215 }
216 
TEST(BrowsingDataFilterBuilderImplTest,RegistrableDomainMatchesCookiesWhitelist)217 TEST(BrowsingDataFilterBuilderImplTest,
218      RegistrableDomainMatchesCookiesWhitelist) {
219   BrowsingDataFilterBuilderImpl builder(
220       BrowsingDataFilterBuilderImpl::WHITELIST);
221   builder.AddRegisterableDomain(std::string(kGoogleDomain));
222   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
223   builder.AddRegisterableDomain(std::string(kIPAddress));
224   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
225   builder.AddRegisterableDomain(std::string(kInternalHostname));
226 
227   TestCase test_cases[] = {
228       // Any cookie with the same registerable domain as the origins is matched.
229       {"https://www.google.com", true},
230       {"http://www.google.com", true},
231       {"http://www.google.com:300", true},
232       {"https://mail.google.com", true},
233       {"http://mail.google.com", true},
234       {"http://google.com", true},
235       {"https://website.sp.nom.br", true},
236       {"https://sub.website.sp.nom.br", true},
237       {"http://192.168.1.1", true},
238       {"http://192.168.1.1:10", true},
239 
240       // Different eTLDs.
241       {"https://www.google.org", false},
242       {"https://www.google.co.uk", false},
243 
244       // We treat eTLD+1 and bare eTLDs as different domains.
245       {"https://www.sp.nom.br", false},
246       {"https://sp.nom.br", false},
247 
248       // Different hosts in general.
249       {"https://www.chrome.com", false},
250       {"http://192.168.2.1", false},
251 
252       // Internal hostnames do not have subdomains.
253       {"https://fileserver", true },
254       {"http://fileserver/foo/bar", true },
255       {"http://website.fileserver", false },
256 
257       // This is a valid registrable domain with the TLD "fileserver", which
258       // is unrelated to the internal hostname "fileserver".
259       {"http://second-level-domain.fileserver", true},
260       {"https://subdomain.second-level-domain.fileserver", true},
261   };
262 
263   for (TestCase test_case : test_cases)
264     RunTestCase(test_case, builder.BuildCookieDeletionFilter());
265 }
266 
TEST(BrowsingDataFilterBuilderImplTest,RegistrableDomainMatchesCookiesBlacklist)267 TEST(BrowsingDataFilterBuilderImplTest,
268      RegistrableDomainMatchesCookiesBlacklist) {
269   BrowsingDataFilterBuilderImpl builder(
270       BrowsingDataFilterBuilderImpl::BLACKLIST);
271   builder.AddRegisterableDomain(std::string(kGoogleDomain));
272   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
273   builder.AddRegisterableDomain(std::string(kIPAddress));
274   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
275   builder.AddRegisterableDomain(std::string(kInternalHostname));
276 
277   TestCase test_cases[] = {
278       // Any cookie that doesn't have the same registerable domain is matched.
279       {"https://www.google.com", false},
280       {"http://www.google.com", false},
281       {"http://www.google.com:300", false},
282       {"https://mail.google.com", false},
283       {"http://mail.google.com", false},
284       {"http://google.com", false},
285       {"https://website.sp.nom.br", false},
286       {"https://sub.website.sp.nom.br", false},
287       {"http://192.168.1.1", false},
288       {"http://192.168.1.1:10", false},
289 
290       // Different eTLDs.
291       {"https://www.google.org", true},
292       {"https://www.google.co.uk", true},
293 
294       // We treat eTLD+1 and bare eTLDs as different domains.
295       {"https://www.sp.nom.br", true},
296       {"https://sp.nom.br", true},
297 
298       // Different hosts in general.
299       {"https://www.chrome.com", true},
300       {"http://192.168.2.1", true},
301 
302       // Internal hostnames do not have subdomains.
303       {"https://fileserver", false },
304       {"http://fileserver/foo/bar", false },
305       {"http://website.fileserver", true },
306 
307       // This is a valid registrable domain with the TLD "fileserver", which
308       // is unrelated to the internal hostname "fileserver".
309       {"http://second-level-domain.fileserver", false},
310       {"https://subdomain.second-level-domain.fileserver", false},
311   };
312 
313   for (TestCase test_case : test_cases)
314     RunTestCase(test_case, builder.BuildCookieDeletionFilter());
315 }
316 
TEST(BrowsingDataFilterBuilderImplTest,NetworkServiceFilterWhitelist)317 TEST(BrowsingDataFilterBuilderImplTest, NetworkServiceFilterWhitelist) {
318   BrowsingDataFilterBuilderImpl builder(
319       BrowsingDataFilterBuilderImpl::WHITELIST);
320   ASSERT_EQ(BrowsingDataFilterBuilderImpl::WHITELIST, builder.GetMode());
321   builder.AddRegisterableDomain(std::string(kGoogleDomain));
322   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
323   builder.AddRegisterableDomain(std::string(kIPAddress));
324   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
325   builder.AddRegisterableDomain(std::string(kInternalHostname));
326   network::mojom::ClearDataFilterPtr filter =
327       builder.BuildNetworkServiceFilter();
328 
329   EXPECT_EQ(network::mojom::ClearDataFilter_Type::DELETE_MATCHES, filter->type);
330   EXPECT_THAT(filter->domains, testing::UnorderedElementsAre(
331                                    kGoogleDomain, kLongETLDDomain, kIPAddress,
332                                    kUnknownRegistryDomain, kInternalHostname));
333   EXPECT_TRUE(filter->origins.empty());
334 }
335 
TEST(BrowsingDataFilterBuilderImplTest,NetworkServiceFilterBlacklist)336 TEST(BrowsingDataFilterBuilderImplTest, NetworkServiceFilterBlacklist) {
337   BrowsingDataFilterBuilderImpl builder(
338       BrowsingDataFilterBuilderImpl::BLACKLIST);
339   ASSERT_EQ(BrowsingDataFilterBuilderImpl::BLACKLIST, builder.GetMode());
340   builder.AddRegisterableDomain(std::string(kGoogleDomain));
341   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
342   builder.AddRegisterableDomain(std::string(kIPAddress));
343   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
344   builder.AddRegisterableDomain(std::string(kInternalHostname));
345   network::mojom::ClearDataFilterPtr filter =
346       builder.BuildNetworkServiceFilter();
347 
348   EXPECT_EQ(network::mojom::ClearDataFilter_Type::KEEP_MATCHES, filter->type);
349   EXPECT_THAT(filter->domains, testing::UnorderedElementsAre(
350                                    kGoogleDomain, kLongETLDDomain, kIPAddress,
351                                    kUnknownRegistryDomain, kInternalHostname));
352   EXPECT_TRUE(filter->origins.empty());
353 }
354 
TEST(BrowsingDataFilterBuilderImplTest,RegistrableDomainMatchesPluginSitesWhitelist)355 TEST(BrowsingDataFilterBuilderImplTest,
356      RegistrableDomainMatchesPluginSitesWhitelist) {
357   BrowsingDataFilterBuilderImpl builder(
358       BrowsingDataFilterBuilderImpl::WHITELIST);
359   builder.AddRegisterableDomain(std::string(kGoogleDomain));
360   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
361   builder.AddRegisterableDomain(std::string(kIPAddress));
362   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
363   builder.AddRegisterableDomain(std::string(kInternalHostname));
364   base::RepeatingCallback<bool(const std::string&)> filter =
365       builder.BuildPluginFilter();
366 
367   TestCase test_cases[] = {
368       // Plugin sites can be domains, ...
369       {"google.com", true},
370       {"www.google.com", true},
371       {"website.sp.nom.br", true},
372       {"www.website.sp.nom.br", true},
373       {"second-level-domain.fileserver", true},
374       {"foo.bar.second-level-domain.fileserver", true},
375 
376       // ... IP addresses, or internal hostnames.
377       {"192.168.1.1", true},
378       {"fileserver", true},
379 
380       // Sites not in the whitelist are not matched.
381       {"example.com", false},
382       {"192.168.1.2", false},
383       {"website.fileserver", false},
384   };
385 
386   for (TestCase test_case : test_cases)
387     RunTestCase(test_case, filter);
388 }
389 
TEST(BrowsingDataFilterBuilderImplTest,RegistrableDomainMatchesPluginSitesBlacklist)390 TEST(BrowsingDataFilterBuilderImplTest,
391      RegistrableDomainMatchesPluginSitesBlacklist) {
392   BrowsingDataFilterBuilderImpl builder(
393       BrowsingDataFilterBuilderImpl::BLACKLIST);
394   builder.AddRegisterableDomain(std::string(kGoogleDomain));
395   builder.AddRegisterableDomain(std::string(kLongETLDDomain));
396   builder.AddRegisterableDomain(std::string(kIPAddress));
397   builder.AddRegisterableDomain(std::string(kUnknownRegistryDomain));
398   builder.AddRegisterableDomain(std::string(kInternalHostname));
399   base::RepeatingCallback<bool(const std::string&)> filter =
400       builder.BuildPluginFilter();
401 
402   TestCase test_cases[] = {
403       // Plugin sites can be domains, ...
404       {"google.com", false},
405       {"www.google.com", false},
406       {"website.sp.nom.br", false},
407       {"www.website.sp.nom.br", false},
408       {"second-level-domain.fileserver", false},
409       {"foo.bar.second-level-domain.fileserver", false},
410 
411       // ... IP addresses, or internal hostnames.
412       {"192.168.1.1", false},
413       {"fileserver", false},
414 
415       // Sites not in the blacklist are matched.
416       {"example.com", true},
417       {"192.168.1.2", true},
418       {"website.fileserver", true},
419   };
420 
421   for (TestCase test_case : test_cases)
422     RunTestCase(test_case, filter);
423 }
424 
TEST(BrowsingDataFilterBuilderImplTest,OriginWhitelist)425 TEST(BrowsingDataFilterBuilderImplTest, OriginWhitelist) {
426   BrowsingDataFilterBuilderImpl builder(
427       BrowsingDataFilterBuilderImpl::WHITELIST);
428   builder.AddOrigin(url::Origin::Create(GURL("https://www.google.com")));
429   builder.AddOrigin(url::Origin::Create(GURL("http://www.example.com")));
430   base::RepeatingCallback<bool(const GURL&)> filter =
431       builder.BuildGeneralFilter();
432 
433   TestCase test_cases[] = {
434       // Whitelist matches any URL on the specified origins.
435       { "https://www.google.com", true },
436       { "https://www.google.com/?q=test", true },
437       { "http://www.example.com", true },
438       { "http://www.example.com/index.html", true },
439       { "http://www.example.com/foo/bar", true },
440 
441       // Subdomains are different origins.
442       { "https://test.www.google.com", false },
443 
444       // Different scheme or port is a different origin.
445       { "https://www.google.com:8000", false },
446       { "https://www.example.com/index.html", false },
447 
448       // Different host is a different origin.
449       { "https://www.youtube.com", false },
450       { "https://www.chromium.org", false },
451   };
452 
453   for (TestCase test_case : test_cases)
454     RunTestCase(test_case, filter);
455 }
456 
TEST(BrowsingDataFilterBuilderImplTest,OriginBlacklist)457 TEST(BrowsingDataFilterBuilderImplTest, OriginBlacklist) {
458   BrowsingDataFilterBuilderImpl builder(
459       BrowsingDataFilterBuilderImpl::BLACKLIST);
460   builder.AddOrigin(url::Origin::Create(GURL("https://www.google.com")));
461   builder.AddOrigin(url::Origin::Create(GURL("http://www.example.com")));
462   base::RepeatingCallback<bool(const GURL&)> filter =
463       builder.BuildGeneralFilter();
464 
465   TestCase test_cases[] = {
466       // URLS on explicitly specified origins are not matched.
467       { "https://www.google.com", false },
468       { "https://www.google.com/?q=test", false },
469       { "http://www.example.com", false },
470       { "http://www.example.com/index.html", false },
471       { "http://www.example.com/foo/bar", false },
472 
473       // Subdomains are different origins.
474       { "https://test.www.google.com", true },
475 
476       // The same hosts but with different schemes and ports
477       // are not blacklisted.
478       { "https://www.google.com:8000", true },
479       { "https://www.example.com/index.html", true },
480 
481       // Different hosts are not blacklisted.
482       { "https://www.chrome.com", true },
483       { "https://www.youtube.com", true },
484   };
485 
486   for (TestCase test_case : test_cases)
487     RunTestCase(test_case, filter);
488 }
489 
TEST(BrowsingDataFilterBuilderImplTest,CombinedWhitelist)490 TEST(BrowsingDataFilterBuilderImplTest, CombinedWhitelist) {
491   BrowsingDataFilterBuilderImpl builder(
492       BrowsingDataFilterBuilderImpl::WHITELIST);
493   builder.AddOrigin(url::Origin::Create(GURL("https://google.com")));
494   builder.AddRegisterableDomain("example.com");
495   base::RepeatingCallback<bool(const GURL&)> filter =
496       builder.BuildGeneralFilter();
497 
498   TestCase test_cases[] = {
499       // Whitelist matches any URL on the specified origins.
500       { "https://google.com/foo/bar", true },
501       { "https://example.com/?q=test", true },
502 
503       // Since www.google.com was added as an origin, its subdomains are not
504       // matched. However, example.com was added as a registrable domain,
505       // so its subdomains are matched.
506       { "https://www.google.com/foo/bar", false },
507       { "https://www.example.com/?q=test", true },
508   };
509 
510   for (TestCase test_case : test_cases)
511     RunTestCase(test_case, filter);
512 }
513 
TEST(BrowsingDataFilterBuilderImplTest,CombinedBlacklist)514 TEST(BrowsingDataFilterBuilderImplTest, CombinedBlacklist) {
515   BrowsingDataFilterBuilderImpl builder(
516       BrowsingDataFilterBuilderImpl::BLACKLIST);
517   builder.AddOrigin(url::Origin::Create(GURL("https://google.com")));
518   builder.AddRegisterableDomain("example.com");
519   base::RepeatingCallback<bool(const GURL&)> filter =
520       builder.BuildGeneralFilter();
521 
522   TestCase test_cases[] = {
523       // URLS on explicitly specified origins are not matched.
524       { "https://google.com/foo/bar", false },
525       { "https://example.com/?q=test", false },
526 
527       // Since www.google.com was added as an origin, its subdomains are
528       // not in the blacklist. However, example.com was added as a registrable
529       // domain, so its subdomains are also blacklisted.
530       { "https://www.google.com/foo/bar", true },
531       { "https://www.example.com/?q=test", false },
532   };
533 
534   for (TestCase test_case : test_cases)
535     RunTestCase(test_case, filter);
536 }
537 
538 }  // namespace content
539