| // Copyright 2015 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 <utility> |
| #include <vector> |
| |
| #include "base/strings/string_piece.h" |
| #include "content/common/cross_site_document_classifier.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::StringPiece; |
| using Result = content::CrossSiteDocumentClassifier::Result; |
| |
| namespace content { |
| |
| TEST(CrossSiteDocumentClassifierTest, IsBlockableScheme) { |
| GURL data_url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA=="); |
| GURL ftp_url("ftp://google.com"); |
| GURL mailto_url("mailto:[email protected]"); |
| GURL about_url("about:chrome"); |
| GURL http_url("http://google.com"); |
| GURL https_url("https://google.com"); |
| |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsBlockableScheme(data_url)); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsBlockableScheme(ftp_url)); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsBlockableScheme(mailto_url)); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsBlockableScheme(about_url)); |
| EXPECT_TRUE(CrossSiteDocumentClassifier::IsBlockableScheme(http_url)); |
| EXPECT_TRUE(CrossSiteDocumentClassifier::IsBlockableScheme(https_url)); |
| } |
| |
| TEST(CrossSiteDocumentClassifierTest, IsValidCorsHeaderSet) { |
| url::Origin frame_origin = url::Origin::Create(GURL("http://www.google.com")); |
| |
| EXPECT_TRUE( |
| CrossSiteDocumentClassifier::IsValidCorsHeaderSet(frame_origin, "*")); |
| EXPECT_FALSE( |
| CrossSiteDocumentClassifier::IsValidCorsHeaderSet(frame_origin, "\"*\"")); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| frame_origin, "http://mail.google.com")); |
| EXPECT_TRUE(CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| frame_origin, "http://www.google.com")); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| frame_origin, "https://www.google.com")); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| frame_origin, "http://yahoo.com")); |
| EXPECT_FALSE(CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| frame_origin, "www.google.com")); |
| } |
| |
| TEST(CrossSiteDocumentClassifierTest, SniffForHTML) { |
| StringPiece html_data(" \t\r\n <HtMladfokadfkado"); |
| StringPiece comment_html_data(" <!-- this is comment --> <html><body>"); |
| StringPiece two_comments_html_data( |
| "<!-- this is comment -->\n<!-- this is comment --><html><body>"); |
| StringPiece commented_out_html_tag_data("<!-- <html> <?xml> \n<html>--><b"); |
| StringPiece mixed_comments_html_data( |
| "<!-- this is comment <!-- --> <script></script>"); |
| StringPiece non_html_data(" var name=window.location;\nadfadf"); |
| StringPiece comment_js_data( |
| " <!-- this is comment\n document.write(1);\n// -->window.open()"); |
| StringPiece empty_data(""); |
| |
| EXPECT_EQ(Result::kYes, CrossSiteDocumentClassifier::SniffForHTML(html_data)); |
| EXPECT_EQ(Result::kYes, |
| CrossSiteDocumentClassifier::SniffForHTML(comment_html_data)); |
| EXPECT_EQ(Result::kYes, |
| CrossSiteDocumentClassifier::SniffForHTML(two_comments_html_data)); |
| EXPECT_EQ(Result::kYes, CrossSiteDocumentClassifier::SniffForHTML( |
| commented_out_html_tag_data)); |
| EXPECT_EQ(Result::kYes, CrossSiteDocumentClassifier::SniffForHTML( |
| mixed_comments_html_data)); |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForHTML(non_html_data)); |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForHTML(comment_js_data)); |
| |
| // Prefixes of |commented_out_html_tag_data| should be indeterminate. |
| StringPiece almost_html = commented_out_html_tag_data; |
| while (!almost_html.empty()) { |
| almost_html.remove_suffix(1); |
| EXPECT_EQ(Result::kMaybe, |
| CrossSiteDocumentClassifier::SniffForHTML(almost_html)) |
| << almost_html; |
| } |
| } |
| |
| TEST(CrossSiteDocumentClassifierTest, SniffForXML) { |
| StringPiece xml_data(" \t \r \n <?xml version=\"1.0\"?>\n <catalog"); |
| StringPiece non_xml_data(" var name=window.location;\nadfadf"); |
| StringPiece empty_data(""); |
| |
| EXPECT_EQ(Result::kYes, CrossSiteDocumentClassifier::SniffForXML(xml_data)); |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForXML(non_xml_data)); |
| |
| // Empty string should be indeterminate. |
| EXPECT_EQ(Result::kMaybe, |
| CrossSiteDocumentClassifier::SniffForXML(empty_data)); |
| } |
| |
| TEST(CrossSiteDocumentClassifierTest, SniffForJSON) { |
| StringPiece json_data("\t\t\r\n { \"name\" : \"chrome\", "); |
| StringPiece json_corrupt_after_first_key( |
| "\t\t\r\n { \"name\" :^^^^!!@#\1\", "); |
| StringPiece json_data2("{ \"key \\\" \" \t\t\r\n:"); |
| StringPiece non_json_data0("\t\t\r\n { name : \"chrome\", "); |
| StringPiece non_json_data1("\t\t\r\n foo({ \"name\" : \"chrome\", "); |
| StringPiece empty_data(""); |
| |
| EXPECT_EQ(Result::kYes, CrossSiteDocumentClassifier::SniffForJSON(json_data)); |
| EXPECT_EQ(Result::kYes, CrossSiteDocumentClassifier::SniffForJSON( |
| json_corrupt_after_first_key)); |
| |
| EXPECT_EQ(Result::kYes, |
| CrossSiteDocumentClassifier::SniffForJSON(json_data2)); |
| |
| // All prefixes prefixes of |json_data2| ought to be indeterminate. |
| StringPiece almost_json = json_data2; |
| while (!almost_json.empty()) { |
| almost_json.remove_suffix(1); |
| EXPECT_EQ(Result::kMaybe, |
| CrossSiteDocumentClassifier::SniffForJSON(almost_json)) |
| << almost_json; |
| } |
| |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForJSON(non_json_data0)); |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForJSON(non_json_data1)); |
| |
| EXPECT_EQ(Result::kYes, |
| CrossSiteDocumentClassifier::SniffForJSON(R"({"" : 1})")) |
| << "Empty strings are accepted"; |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForJSON(R"({'' : 1})")) |
| << "Single quotes are not accepted"; |
| EXPECT_EQ(Result::kYes, |
| CrossSiteDocumentClassifier::SniffForJSON("{\"\\\"\" : 1}")) |
| << "Escaped quotes are recognized"; |
| EXPECT_EQ(Result::kYes, |
| CrossSiteDocumentClassifier::SniffForJSON(R"({"\\\u000a" : 1})")) |
| << "Escaped control characters are recognized"; |
| EXPECT_EQ(Result::kMaybe, |
| CrossSiteDocumentClassifier::SniffForJSON(R"({"\\\u00)")) |
| << "Incomplete escape results in maybe"; |
| EXPECT_EQ(Result::kMaybe, CrossSiteDocumentClassifier::SniffForJSON("{\"\\")) |
| << "Incomplete escape results in maybe"; |
| EXPECT_EQ(Result::kMaybe, |
| CrossSiteDocumentClassifier::SniffForJSON("{\"\\\"")) |
| << "Incomplete escape results in maybe"; |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForJSON("{\"\n\" : true}")) |
| << "Unescaped control characters are rejected"; |
| EXPECT_EQ(Result::kNo, CrossSiteDocumentClassifier::SniffForJSON("{}")) |
| << "Empty dictionary is not recognized (since it's valid JS too)"; |
| EXPECT_EQ(Result::kNo, |
| CrossSiteDocumentClassifier::SniffForJSON("[true, false, 1, 2]")) |
| << "Lists dictionary are not recognized (since they're valid JS too)"; |
| EXPECT_EQ(Result::kNo, CrossSiteDocumentClassifier::SniffForJSON(R"({":"})")) |
| << "A colon character inside a string does not trigger a match"; |
| } |
| |
| TEST(CrossSiteDocumentClassifierTest, GetCanonicalMimeType) { |
| std::vector<std::pair<const char*, CrossSiteDocumentMimeType>> tests = { |
| // Basic tests for things in the original implementation: |
| {"text/html", CROSS_SITE_DOCUMENT_MIME_TYPE_HTML}, |
| {"text/xml", CROSS_SITE_DOCUMENT_MIME_TYPE_XML}, |
| {"application/rss+xml", CROSS_SITE_DOCUMENT_MIME_TYPE_XML}, |
| {"application/xml", CROSS_SITE_DOCUMENT_MIME_TYPE_XML}, |
| {"application/json", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"text/json", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"text/x-json", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"text/plain", CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN}, |
| |
| // Other mime types: |
| {"application/foobar", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| |
| // Regression tests for https://crbug.com/799155 (prefix/suffix matching): |
| {"application/json+protobuf", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"text/json+protobuf", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"application/activity+json", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"text/foobar+xml", CROSS_SITE_DOCUMENT_MIME_TYPE_XML}, |
| // No match without a '+' character: |
| {"application/jsonfoobar", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| {"application/foobarjson", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| {"application/xmlfoobar", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| {"application/foobarxml", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| |
| // Case-insensitive comparison: |
| {"APPLICATION/JSON", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"APPLICATION/JSON+PROTOBUF", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| {"APPLICATION/ACTIVITY+JSON", CROSS_SITE_DOCUMENT_MIME_TYPE_JSON}, |
| |
| // Images are allowed cross-site, and SVG is an image, so we should |
| // classify SVG as "other" instead of "xml" (even though it technically is |
| // an xml document). |
| {"image/svg+xml", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| |
| // Javascript should not be blocked. |
| {"application/javascript", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| {"application/jsonp", CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS}, |
| }; |
| |
| for (const auto& test : tests) { |
| const char* input = test.first; // e.g. "text/html" |
| CrossSiteDocumentMimeType expected = test.second; |
| CrossSiteDocumentMimeType actual = |
| CrossSiteDocumentClassifier::GetCanonicalMimeType(input); |
| EXPECT_EQ(expected, actual) |
| << "when testing with the following input: " << input; |
| } |
| } |
| |
| } // namespace content |