| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <string> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "base/stl_util.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "content/browser/appcache/appcache_manifest_parser.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| class AppCacheManifestParserTest : public testing::Test { |
| }; |
| |
| TEST(AppCacheManifestParserTest, NoData) { |
| const GURL url("http://localhost"); |
| const std::string scope = url.GetWithoutFilename().path(); |
| AppCacheManifest manifest; |
| |
| EXPECT_FALSE(ParseManifest( |
| url, scope, "", 0, PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, manifest)); |
| |
| manifest = AppCacheManifest(); |
| EXPECT_FALSE(ParseManifest(url, scope, "CACHE MANIFEST\r", 0, // Len is 0. |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| } |
| |
| TEST(AppCacheManifestParserTest, CheckSignature) { |
| const GURL url("http://localhost"); |
| const std::string scope = url.GetWithoutFilename().path(); |
| |
| const std::string kBadSignatures[] = { |
| "foo", |
| "CACHE MANIFEST;V2\r", // not followed by whitespace |
| "CACHE MANIFEST#bad\r", // no whitespace before comment |
| "cache manifest ", // wrong case |
| "#CACHE MANIFEST\r", // comment |
| "xCACHE MANIFEST\n", // bad first char |
| " CACHE MANIFEST\r", // begins with whitespace |
| "\xEF\xBE\xBF" "CACHE MANIFEST\r", // bad UTF-8 BOM value |
| }; |
| |
| for (const std::string& bad_signature : kBadSignatures) { |
| AppCacheManifest manifest; |
| EXPECT_FALSE( |
| ParseManifest(url, scope, bad_signature.c_str(), bad_signature.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, manifest)); |
| } |
| |
| const std::string kGoodSignatures[] = { |
| "CACHE MANIFEST", |
| "CACHE MANIFEST ", |
| "CACHE MANIFEST\r", |
| "CACHE MANIFEST\n", |
| "CACHE MANIFEST\r\n", |
| "CACHE MANIFEST\t# ignore me\r", |
| "CACHE MANIFEST ignore\r\n", |
| "CHROMIUM CACHE MANIFEST\r\n", |
| "\xEF\xBB\xBF" "CACHE MANIFEST \r\n", // BOM present |
| }; |
| |
| for (const std::string& good_signature : kGoodSignatures) { |
| AppCacheManifest manifest; |
| EXPECT_TRUE(ParseManifest( |
| url, scope, good_signature.c_str(), good_signature.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, manifest)); |
| } |
| } |
| |
| TEST(AppCacheManifestParserTest, HeaderMetrics) { |
| const GURL url("http://localhost"); |
| const std::string scope = url.GetWithoutFilename().path(); |
| |
| struct TestCase { |
| std::string manifest; |
| int expected_false_count; |
| int expected_true_count; |
| } test_cases[] = { |
| {"CACHE MANIFEST\r\n", 1, 0}, |
| {"CHROMIUM CACHE MANIFEST\r\n", 0, 1}, |
| {"CACHE MANIFEST#bad\r\n", 0, 0}, |
| }; |
| |
| for (const auto& test_case : test_cases) { |
| AppCacheManifest manifest; |
| base::HistogramTester tester; |
| |
| ParseManifest(url, scope, test_case.manifest.c_str(), |
| test_case.manifest.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, manifest); |
| tester.ExpectBucketCount("appcache.Manifest.ChromeHeader", 0, |
| test_case.expected_false_count); |
| tester.ExpectBucketCount("appcache.Manifest.ChromeHeader", 1, |
| test_case.expected_true_count); |
| } |
| } |
| |
| TEST(AppCacheManifestParserTest, DangerousModeMetrics) { |
| const GURL url("http://localhost"); |
| const std::string scope = url.GetWithoutFilename().path(); |
| |
| struct TestCase { |
| std::string manifest; |
| ParseMode parse_mode; |
| int expected_false_count; |
| int expected_true_count; |
| } test_cases[] = { |
| {"CACHE MANIFEST\r\n", PARSE_MANIFEST_PER_STANDARD, 1, 0}, |
| {"CACHE MANIFEST\r\n", PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, 0, 1}, |
| {"CACHE MANIFEST#bad\r\n", PARSE_MANIFEST_PER_STANDARD, 0, 0}, |
| {"CACHE MANIFEST#bad\r\n", PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, 0, |
| 0}, |
| }; |
| |
| for (const auto& test_case : test_cases) { |
| AppCacheManifest manifest; |
| base::HistogramTester tester; |
| |
| ParseManifest(url, scope, test_case.manifest.c_str(), |
| test_case.manifest.length(), test_case.parse_mode, manifest); |
| tester.ExpectBucketCount("appcache.Manifest.DangerousMode", 0, |
| test_case.expected_false_count); |
| tester.ExpectBucketCount("appcache.Manifest.DangerousMode", 1, |
| test_case.expected_true_count); |
| } |
| } |
| |
| TEST(AppCacheManifestParserTest, NoManifestUrl) { |
| base::HistogramTester tester; |
| AppCacheManifest manifest; |
| const std::string kData("CACHE MANIFEST\r" |
| "relative/tobase.com\r" |
| "http://absolute.com/addme.com"); |
| const GURL kUrl; |
| const std::string kScope("/"); |
| EXPECT_FALSE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_EQ(manifest.parser_version, -1); |
| |
| // Verify UMA values show the invalid manifest URL. |
| int invalid_count = 1; |
| int valid_count = 0; |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 0, |
| invalid_count); |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 1, |
| valid_count); |
| } |
| |
| TEST(AppCacheManifestParserTest, NoManifestScope) { |
| base::HistogramTester tester; |
| AppCacheManifest manifest; |
| const std::string kData( |
| "CACHE MANIFEST\r" |
| "relative/tobase.com\r" |
| "http://absolute.com/addme.com"); |
| const GURL kUrl("http://localhost"); |
| const std::string kScope; |
| EXPECT_FALSE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_EQ(manifest.parser_version, -1); |
| |
| // Verify UMA values show neither a valid nor invalid manifest URL since |
| // metrics weren't recorded. |
| int invalid_count = 0; |
| int valid_count = 0; |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 0, |
| invalid_count); |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 1, |
| valid_count); |
| } |
| |
| TEST(AppCacheManifestParserTest, NoManifestUrlAndScope) { |
| base::HistogramTester tester; |
| AppCacheManifest manifest; |
| const std::string kData( |
| "CACHE MANIFEST\r" |
| "relative/tobase.com\r" |
| "http://absolute.com/addme.com"); |
| const GURL kUrl; |
| const std::string kScope; |
| EXPECT_FALSE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_EQ(manifest.parser_version, -1); |
| |
| // Verify UMA values show the invalid manifest URL. |
| int invalid_count = 1; |
| int valid_count = 0; |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 0, |
| invalid_count); |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 1, |
| valid_count); |
| } |
| |
| TEST(AppCacheManifestParserTest, SimpleManifest) { |
| base::HistogramTester tester; |
| AppCacheManifest manifest; |
| const std::string kData( |
| "CACHE MANIFEST\r" |
| "relative/tobase.com\r" |
| "http://absolute.com/addme.com"); |
| const GURL kUrl("http://localhost"); |
| const std::string kScope = "/"; |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| const size_t kExpected = 2; |
| EXPECT_EQ(manifest.explicit_urls.size(), kExpected); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_EQ(manifest.parser_version, 1); |
| |
| // Verify UMA values show neither the valid or invalid manifest URL since |
| // metrics weren't recorded. |
| int invalid_count = 0; |
| int valid_count = 1; |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 0, |
| invalid_count); |
| tester.ExpectBucketCount("appcache.Manifest.ValidManifestURL", 1, |
| valid_count); |
| } |
| |
| TEST(AppCacheManifestParserTest, ExplicitUrls) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://www.foo.com"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "relative/one\r" |
| "# some comment\r" |
| "http://www.foo.com/two#strip\r\n" |
| "NETWORK:\r" |
| " \t CACHE:\r" |
| "HTTP://www.diff.com/three\r" |
| "FALLBACK:\r" |
| " \t # another comment with leading whitespace\n" |
| "IGNORE:\r" |
| "http://www.foo.com/ignore\r" |
| "CACHE: \r" |
| "garbage:#!@\r" |
| "https://www.foo.com/diffscheme \t \r" |
| " \t relative/four#stripme\n\r" |
| "*\r"); |
| |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| EXPECT_EQ(manifest.parser_version, 1); |
| |
| std::unordered_set<std::string> urls = manifest.explicit_urls; |
| const size_t kExpected = 5; |
| ASSERT_EQ(kExpected, urls.size()); |
| EXPECT_TRUE(urls.find("http://www.foo.com/relative/one") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.foo.com/two") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.diff.com/three") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.foo.com/relative/four") != urls.end()); |
| |
| // Wildcard is treated as a relative URL in explicit section. |
| EXPECT_TRUE(urls.find("http://www.foo.com/*") != urls.end()); |
| |
| // We should get the same results with dangerous features disallowed. |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_PER_STANDARD, manifest)); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| |
| urls = manifest.explicit_urls; |
| ASSERT_EQ(kExpected, urls.size()); |
| EXPECT_TRUE(urls.find("http://www.foo.com/relative/one") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.foo.com/two") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.diff.com/three") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.foo.com/relative/four") != urls.end()); |
| |
| // Wildcard is treated as a relative URL in explicit section. |
| EXPECT_TRUE(urls.find("http://www.foo.com/*") != urls.end()); |
| } |
| |
| TEST(AppCacheManifestParserTest, WhitelistUrls) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://www.bar.com"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "NETWORK:\r" |
| "relative/one\r" |
| "# a comment\r" |
| "http://www.bar.com/two\r" |
| "HTTP://www.diff.com/three#strip\n\r" |
| "FALLBACK:\r" |
| "garbage\r" |
| "UNKNOWN:\r" |
| "http://www.bar.com/ignore\r" |
| "CACHE:\r" |
| "NETWORK:\r" |
| "https://www.wrongscheme.com\n" |
| "relative/four#stripref \t \r" |
| "http://www.five.com\r\n" |
| "*foo\r"); |
| |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.intercept_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| |
| const std::vector<AppCacheNamespace>& online = |
| manifest.online_whitelist_namespaces; |
| const size_t kExpected = 6; |
| ASSERT_EQ(kExpected, online.size()); |
| EXPECT_EQ(APPCACHE_NETWORK_NAMESPACE, online[0].type); |
| EXPECT_TRUE(online[0].target_url.is_empty()); |
| EXPECT_EQ(GURL("http://www.bar.com/relative/one"), online[0].namespace_url); |
| EXPECT_EQ(GURL("http://www.bar.com/two"), online[1].namespace_url); |
| EXPECT_EQ(GURL("http://www.diff.com/three"), online[2].namespace_url); |
| EXPECT_EQ(GURL("http://www.bar.com/relative/four"), online[3].namespace_url); |
| EXPECT_EQ(GURL("http://www.five.com"), online[4].namespace_url); |
| EXPECT_EQ(GURL("http://www.bar.com/*foo"), online[5].namespace_url); |
| } |
| |
| TEST(AppCacheManifestParserTest, FallbackUrls) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://glorp.com"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "# a comment\r" |
| "CACHE:\r" |
| "NETWORK:\r" |
| "UNKNOWN:\r" |
| "FALLBACK:\r" |
| "relative/one \t \t http://glorp.com/onefb \t \r" |
| "*\r" |
| "https://glorp.com/wrong http://glorp.com/wrongfb\r" |
| "http://glorp.com/two#strip relative/twofb\r" |
| "HTTP://glorp.com/three relative/threefb#strip\n" |
| "http://glorp.com/three http://glorp.com/three-dup\r" |
| "http://glorp.com/solo \t \r\n" |
| "http://diff.com/ignore http://glorp.com/wronghost\r" |
| "http://glorp.com/wronghost http://diff.com/ohwell\r" |
| "relative/badscheme ftp://glorp.com/ignored\r" |
| "garbage\r\n" |
| "CACHE:\r" |
| "# only fallback urls in this test\r" |
| "FALLBACK:\n" |
| "relative/four#strip relative/fourfb#strip\r" |
| "http://www.glorp.com/notsame relative/skipped\r"); |
| |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| |
| const std::vector<AppCacheNamespace>& fallbacks = |
| manifest.fallback_namespaces; |
| const size_t kExpected = 5; |
| ASSERT_EQ(kExpected, fallbacks.size()); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[0].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[1].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[2].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[3].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[4].type); |
| EXPECT_EQ(GURL("http://glorp.com/relative/one"), |
| fallbacks[0].namespace_url); |
| EXPECT_EQ(GURL("http://glorp.com/onefb"), |
| fallbacks[0].target_url); |
| EXPECT_EQ(GURL("http://glorp.com/two"), |
| fallbacks[1].namespace_url); |
| EXPECT_EQ(GURL("http://glorp.com/relative/twofb"), |
| fallbacks[1].target_url); |
| EXPECT_EQ(GURL("http://glorp.com/three"), |
| fallbacks[2].namespace_url); |
| EXPECT_EQ(GURL("http://glorp.com/relative/threefb"), |
| fallbacks[2].target_url); |
| EXPECT_EQ(GURL("http://glorp.com/three"), // duplicates are stored |
| fallbacks[3].namespace_url); |
| EXPECT_EQ(GURL("http://glorp.com/three-dup"), |
| fallbacks[3].target_url); |
| EXPECT_EQ(GURL("http://glorp.com/relative/four"), |
| fallbacks[4].namespace_url); |
| EXPECT_EQ(GURL("http://glorp.com/relative/fourfb"), |
| fallbacks[4].target_url); |
| EXPECT_TRUE(manifest.intercept_namespaces.empty()); |
| |
| // Nothing should be ignored since all namespaces are in scope. |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_PER_STANDARD, manifest)); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| } |
| |
| TEST(AppCacheManifestParserTest, FallbackUrlsWithPort) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://www.portme.com:1234"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "FALLBACK:\r" |
| "http://www.portme.com:1234/one relative/onefb\r" |
| "HTTP://www.portme.com:9876/wrong http://www.portme.com:1234/ignore\r" |
| "http://www.portme.com:1234/stillwrong http://www.portme.com:42/boo\r" |
| "relative/two relative/twofb\r" |
| "http://www.portme.com:1234/three HTTP://www.portme.com:1234/threefb\r" |
| "http://www.portme.com/noport http://www.portme.com:1234/skipped\r" |
| "http://www.portme.com:1234/skipme http://www.portme.com/noport\r"); |
| |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| |
| const std::vector<AppCacheNamespace>& fallbacks = |
| manifest.fallback_namespaces; |
| const size_t kExpected = 3; |
| ASSERT_EQ(kExpected, fallbacks.size()); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[0].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[1].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[2].type); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/one"), |
| fallbacks[0].namespace_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/relative/onefb"), |
| fallbacks[0].target_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/relative/two"), |
| fallbacks[1].namespace_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/relative/twofb"), |
| fallbacks[1].target_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/three"), |
| fallbacks[2].namespace_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/threefb"), |
| fallbacks[2].target_url); |
| EXPECT_TRUE(manifest.intercept_namespaces.empty()); |
| |
| // Nothing should be ignored since all namespaces are in scope. |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_PER_STANDARD, manifest)); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| } |
| |
| TEST(AppCacheManifestParserTest, InterceptUrls) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://www.portme.com:1234"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CHROMIUM CACHE MANIFEST\r" |
| "CHROMIUM-INTERCEPT:\r" |
| "http://www.portme.com:1234/one return relative/int1\r" |
| "HTTP://www.portme.com:9/wrong return http://www.portme.com:1234/ignore\r" |
| "http://www.portme.com:1234/wrong return http://www.portme.com:9/boo\r" |
| "relative/two return relative/int2\r" |
| "relative/three wrong relative/threefb\r" |
| "http://www.portme.com:1234/three return HTTP://www.portme.com:1234/int3\r" |
| "http://www.portme.com/noport return http://www.portme.com:1234/skipped\r" |
| "http://www.portme.com:1234/skipme return http://www.portme.com/noport\r" |
| "relative/wrong/again missing/intercept_type\r"); |
| |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_FALSE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| |
| const std::vector<AppCacheNamespace>& intercepts = |
| manifest.intercept_namespaces; |
| const size_t kExpected = 3; |
| ASSERT_EQ(kExpected, intercepts.size()); |
| EXPECT_EQ(APPCACHE_INTERCEPT_NAMESPACE, intercepts[0].type); |
| EXPECT_EQ(APPCACHE_INTERCEPT_NAMESPACE, intercepts[1].type); |
| EXPECT_EQ(APPCACHE_INTERCEPT_NAMESPACE, intercepts[2].type); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/one"), |
| intercepts[0].namespace_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/relative/int1"), |
| intercepts[0].target_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/relative/two"), |
| intercepts[1].namespace_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/relative/int2"), |
| intercepts[1].target_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/three"), |
| intercepts[2].namespace_url); |
| EXPECT_EQ(GURL("http://www.portme.com:1234/int3"), |
| intercepts[2].target_url); |
| |
| // Disallow intercepts this time. |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_PER_STANDARD, manifest)); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.explicit_urls.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| EXPECT_TRUE(manifest.intercept_namespaces.empty()); |
| EXPECT_FALSE(manifest.online_whitelist_all); |
| EXPECT_TRUE(manifest.did_ignore_intercept_namespaces); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| } |
| |
| TEST(AppCacheManifestParserTest, ComboUrls) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://combo.com:42"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "relative/explicit-1\r" |
| "# some comment\r" |
| "http://combo.com:99/explicit-2#strip\r" |
| "NETWORK:\r" |
| "http://combo.com/whitelist-1\r" |
| "HTTP://www.diff.com/whitelist-2#strip\r" |
| "*\r" |
| "CACHE:\n\r" |
| "http://www.diff.com/explicit-3\r" |
| "FALLBACK:\r" |
| "http://combo.com:42/fallback-1 http://combo.com:42/fallback-1b\r" |
| "relative/fallback-2 relative/fallback-2b\r" |
| "UNKNOWN:\r\n" |
| "http://combo.com/ignoreme\r" |
| "relative/still-ignored\r" |
| "NETWORK:\r\n" |
| "relative/whitelist-3#strip\r" |
| "http://combo.com:99/whitelist-4\r"); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.online_whitelist_all); |
| |
| std::unordered_set<std::string> urls = manifest.explicit_urls; |
| size_t expected = 3; |
| ASSERT_EQ(expected, urls.size()); |
| EXPECT_TRUE(urls.find("http://combo.com:42/relative/explicit-1") != |
| urls.end()); |
| EXPECT_TRUE(urls.find("http://combo.com:99/explicit-2") != urls.end()); |
| EXPECT_TRUE(urls.find("http://www.diff.com/explicit-3") != urls.end()); |
| |
| const std::vector<AppCacheNamespace>& online = |
| manifest.online_whitelist_namespaces; |
| expected = 4; |
| ASSERT_EQ(expected, online.size()); |
| EXPECT_EQ(GURL("http://combo.com/whitelist-1"), |
| online[0].namespace_url); |
| EXPECT_EQ(GURL("http://www.diff.com/whitelist-2"), |
| online[1].namespace_url); |
| EXPECT_EQ(GURL("http://combo.com:42/relative/whitelist-3"), |
| online[2].namespace_url); |
| EXPECT_EQ(GURL("http://combo.com:99/whitelist-4"), |
| online[3].namespace_url); |
| |
| const std::vector<AppCacheNamespace>& fallbacks = |
| manifest.fallback_namespaces; |
| expected = 2; |
| ASSERT_EQ(expected, fallbacks.size()); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[0].type); |
| EXPECT_EQ(APPCACHE_FALLBACK_NAMESPACE, fallbacks[1].type); |
| EXPECT_EQ(GURL("http://combo.com:42/fallback-1"), |
| fallbacks[0].namespace_url); |
| EXPECT_EQ(GURL("http://combo.com:42/fallback-1b"), |
| fallbacks[0].target_url); |
| EXPECT_EQ(GURL("http://combo.com:42/relative/fallback-2"), |
| fallbacks[1].namespace_url); |
| EXPECT_EQ(GURL("http://combo.com:42/relative/fallback-2b"), |
| fallbacks[1].target_url); |
| |
| EXPECT_TRUE(manifest.intercept_namespaces.empty()); |
| } |
| |
| TEST(AppCacheManifestParserTest, UnusualUtf8) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://bad.com"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "\xC0" "invalidutf8\r" |
| "nonbmp" "\xF1\x84\xAB\xBC\r"); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| std::unordered_set<std::string> urls = manifest.explicit_urls; |
| EXPECT_TRUE(urls.find("http://bad.com/%EF%BF%BDinvalidutf8") != urls.end()) |
| << "manifest byte stream was passed through, not UTF-8-decoded"; |
| EXPECT_TRUE(urls.find("http://bad.com/nonbmp%F1%84%AB%BC") != urls.end()); |
| } |
| |
| TEST(AppCacheManifestParserTest, IgnoreAfterSpace) { |
| AppCacheManifest manifest; |
| const GURL kUrl("http://smorg.borg"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData( |
| "CACHE MANIFEST\r" |
| "resource.txt this stuff after the white space should be ignored\r"); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| |
| std::unordered_set<std::string> urls = manifest.explicit_urls; |
| EXPECT_TRUE(urls.find("http://smorg.borg/resource.txt") != urls.end()); |
| } |
| |
| TEST(AppCacheManifestParserTest, DifferentOriginUrlWithSecureScheme) { |
| AppCacheManifest manifest; |
| const GURL kUrl("https://www.foo.com"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData("CACHE MANIFEST\r" |
| "CACHE: \r" |
| "relative/secureschemesameorigin\r" |
| "https://www.foo.com/secureschemesameorigin\r" |
| "http://www.xyz.com/secureschemedifforigin\r" |
| "https://www.xyz.com/secureschemedifforigin\r"); |
| |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_TRUE(manifest.fallback_namespaces.empty()); |
| EXPECT_TRUE(manifest.online_whitelist_namespaces.empty()); |
| |
| std::unordered_set<std::string> urls = manifest.explicit_urls; |
| const size_t kExpected = 3; |
| ASSERT_EQ(kExpected, urls.size()); |
| EXPECT_TRUE(urls.find("https://www.foo.com/relative/secureschemesameorigin") |
| != urls.end()); |
| EXPECT_TRUE(urls.find("https://www.foo.com/secureschemesameorigin") != |
| urls.end()); |
| EXPECT_FALSE(urls.find("http://www.xyz.com/secureschemedifforigin") != |
| urls.end()); |
| EXPECT_TRUE(urls.find("https://www.xyz.com/secureschemedifforigin") != |
| urls.end()); |
| } |
| |
| TEST(AppCacheManifestParserTest, IgnoreDangerousFallbacksWithGlobalScope) { |
| const GURL kUrl("http://foo.com/scope/manifest?with_query_args"); |
| const std::string kScope = kUrl.GetWithEmptyPath().path(); |
| const std::string kData( |
| "CACHE MANIFEST\r" |
| "FALLBACK:\r" |
| "http://foo.com/scope/ fallback_url\r" |
| "http://foo.com/out_of_scope/ fallback_url\r"); |
| |
| // Scope matching depends on resolving "." as a relative url. |
| EXPECT_EQ(kUrl.GetWithoutFilename().spec(), |
| std::string("http://foo.com/scope/")); |
| |
| AppCacheManifest manifest; |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| EXPECT_EQ(2u, manifest.fallback_namespaces.size()); |
| |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_PER_STANDARD, manifest)); |
| EXPECT_TRUE(manifest.did_ignore_fallback_namespaces); |
| EXPECT_EQ(1u, manifest.fallback_namespaces.size()); |
| EXPECT_EQ(GURL("http://foo.com/scope/"), |
| manifest.fallback_namespaces[0].namespace_url); |
| } |
| |
| TEST(AppCacheManifestParserTest, IgnoreDangerousFallbacksWithDefaultScope) { |
| const GURL kUrl("http://foo.com/scope/manifest?with_query_args"); |
| const std::string kScope = kUrl.GetWithoutFilename().path(); |
| const std::string kData( |
| "CACHE MANIFEST\r" |
| "FALLBACK:\r" |
| "http://foo.com/scope/ fallback_url\r" |
| "http://foo.com/out_of_scope/ fallback_url\r"); |
| |
| // Scope matching depends on resolving "." as a relative url. |
| EXPECT_EQ(kUrl.GetWithoutFilename().spec(), |
| std::string("http://foo.com/scope/")); |
| |
| AppCacheManifest manifest; |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, |
| manifest)); |
| EXPECT_EQ(manifest.parser_version, 1); |
| EXPECT_FALSE(manifest.did_ignore_fallback_namespaces); |
| EXPECT_EQ(1u, manifest.fallback_namespaces.size()); |
| |
| manifest = AppCacheManifest(); |
| EXPECT_TRUE(ParseManifest(kUrl, kScope, kData.c_str(), kData.length(), |
| PARSE_MANIFEST_PER_STANDARD, manifest)); |
| EXPECT_TRUE(manifest.did_ignore_fallback_namespaces); |
| EXPECT_EQ(1u, manifest.fallback_namespaces.size()); |
| EXPECT_EQ(GURL("http://foo.com/scope/"), |
| manifest.fallback_namespaces[0].namespace_url); |
| } |
| |
| TEST(AppCacheManifestParserTest, InterceptUsageMetricsWithGlobalScope) { |
| const GURL url("http://foo.com/scope/manifest?with_query_args"); |
| const std::string scope = url.GetWithEmptyPath().path(); |
| |
| struct TestCase { |
| std::string manifest; |
| int expected_none_count; |
| int expected_exact_count; |
| } test_cases[] = { |
| {"", 1, 0}, |
| {"FALLBACK:\rhttp://foo.com/fallback /url\r", 1, 0}, |
| {"FALLBACK:\rhttp://foo.com/fallback_pattern* /pattern isPattern\r", 1, |
| 0}, |
| {"NETWORK:\r*\r", 1, 0}, |
| {"NETWORK:\rhttp://foo.com/network\r", 1, 0}, |
| {"NETWORK:\rhttp://foo.com/network_pattern* isPattern\r", 1, 0}, |
| {"CHROMIUM-INTERCEPT:\rhttp://foo.com/intercept return /url\r", 0, 1}, |
| {"CHROMIUM-INTERCEPT:\r" |
| "http://foo.com/intercept* return /pattern isPattern\r", |
| 0, 1}, |
| }; |
| |
| for (const auto& test_case : test_cases) { |
| AppCacheManifest manifest; |
| base::HistogramTester tester; |
| std::string manifest_text = |
| std::string("CACHE MANIFEST\r") + test_case.manifest; |
| |
| ParseManifest(url, scope, manifest_text.c_str(), manifest_text.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, manifest); |
| tester.ExpectBucketCount("appcache.Manifest.InterceptUsage", 0, |
| test_case.expected_none_count); |
| tester.ExpectBucketCount("appcache.Manifest.InterceptUsage", 1, |
| test_case.expected_exact_count); |
| tester.ExpectBucketCount("appcache.Manifest.InterceptUsage", 2, 0); |
| } |
| } |
| |
| TEST(AppCacheManifestParserTest, InterceptUsageMetricsWithDefaultScope) { |
| const GURL url("http://foo.com/scope/manifest?with_query_args"); |
| const std::string scope = url.GetWithoutFilename().path(); |
| |
| struct TestCase { |
| std::string manifest; |
| int expected_none_count; |
| int expected_exact_count; |
| } test_cases[] = { |
| {"", 1, 0}, |
| {"FALLBACK:\rhttp://foo.com/fallback /url\r", 1, 0}, |
| {"FALLBACK:\rhttp://foo.com/fallback_pattern* /pattern isPattern\r", 1, |
| 0}, |
| {"NETWORK:\r*\r", 1, 0}, |
| {"NETWORK:\rhttp://foo.com/network\r", 1, 0}, |
| {"NETWORK:\rhttp://foo.com/network_pattern* isPattern\r", 1, 0}, |
| {"CHROMIUM-INTERCEPT:\rhttp://foo.com/intercept return /url\r", 1, 0}, |
| {"CHROMIUM-INTERCEPT:\r" |
| "http://foo.com/intercept* return /pattern isPattern\r", |
| 1, 0}, |
| {"CHROMIUM-INTERCEPT:\r" |
| "http://foo.com/scope/intercept return /url\r", |
| 0, 1}, |
| {"CHROMIUM-INTERCEPT:\r" |
| "http://foo.com/scope/intercept* return /pattern isPattern\r", |
| 0, 1}, |
| }; |
| |
| for (const auto& test_case : test_cases) { |
| AppCacheManifest manifest; |
| base::HistogramTester tester; |
| std::string manifest_text = |
| std::string("CACHE MANIFEST\r") + test_case.manifest; |
| |
| ParseManifest(url, scope, manifest_text.c_str(), manifest_text.length(), |
| PARSE_MANIFEST_ALLOWING_DANGEROUS_FEATURES, manifest); |
| tester.ExpectBucketCount("appcache.Manifest.InterceptUsage", 0, |
| test_case.expected_none_count); |
| tester.ExpectBucketCount("appcache.Manifest.InterceptUsage", 1, |
| test_case.expected_exact_count); |
| tester.ExpectBucketCount("appcache.Manifest.InterceptUsage", 2, 0); |
| } |
| } |
| |
| } // namespace content |