/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gtest/gtest.h" #include "mozilla/dom/IntegrityPolicyWAICT.h" #include "mozilla/gtest/MozAssertions.h" #include "nsString.h" using namespace mozilla::dom; // Tests for IntegrityPolicyWAICT::ParseHeader, which parses the value of the // Integrity-Policy-WAICT-v1 HTTP response header. // // Websites signal that they want user-agents to enforce WAICT through the use // of the HTTP response header: Integrity-Policy-WAICT-v1. // // The header is a structured response header (Dictionary type per RFC 9651). // The following key-value pairs MUST be present: // max-age - An sf-integer that MUST be >= 0. How long (in seconds) // user-agents MUST enforce WAICT after seeing this header. // mode - An sf-token containing either "enforce" or "report". In enforce // mode, subresources that fail integrity checks are blocked. In report // mode, failures are reported but resources are still loaded. // manifest - An sf-string containing a URL where the user-agent can fetch // the WAICT manifest. The URL MAY be relative. // blocked-destinations - An sf-inner-list of one or more sf-tokens // indicating the destination types (e.g., script, style) to which // integrity checks apply. Unrecognized tokens MUST be ignored. // // If one or more mandatory keys is missing or invalid, the entire header MUST // be ignored. The following key-value pairs are optional: // preload - An sf-boolean. Not used directly by user-agents. // endpoints - Indicates endpoint(s) for submitting violations. // // Any other keys MUST be ignored. // // Example: // Integrity-Policy-WAICT-v1: max-age=90, mode=report, // blocked-destinations=(script style), preload=?0, // endpoints=(foo-reports), // manifest="/.well-known/waict/manifests/1.json" // // Tests are organized by field. For each field we test valid values, missing // (required vs optional), and invalid values where applicable. class WAICTHeaderParsingTest : public ::testing::Test { protected: nsresult ParseHeader(const nsACString& aHeader) { RefPtr policy = new IntegrityPolicyWAICT(nullptr); return policy->ParseHeader(aHeader); } }; // Manifest: // An sf-string containing a URL where the user-agent can fetch the // WAICT manifest. The URL MAY be relative, in which case it is resolved // against the origin. TEST_F(WAICTHeaderParsingTest, Manifest_RelativeURL) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Manifest_AbsoluteURL) { EXPECT_NS_SUCCEEDED(ParseHeader( "manifest=\"https://example.com/waict-manifest.json\", max-age=0, " "mode=enforce, blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Manifest_Missing) { EXPECT_NS_FAILED(ParseHeader( "max-age=86400, mode=enforce, blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Manifest_Empty) { EXPECT_NS_FAILED( ParseHeader("manifest=\"\", max-age=86400, mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Manifest_AsToken) { EXPECT_NS_FAILED( ParseHeader("manifest=waict-manifest.json, max-age=86400, mode=enforce, " "blocked-destinations=(script)"_ns)); } // max-age - An sf-integer indicating the number of seconds the policy is // valid. Must be a non-negative integer. TEST_F(WAICTHeaderParsingTest, MaxAge_Valid) { EXPECT_NS_SUCCEEDED(ParseHeader( "manifest=\"waict-manifest.json\", max-age=86400, mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, MaxAge_Zero) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, MaxAge_Missing) { EXPECT_NS_FAILED( ParseHeader("manifest=\"waict-manifest.json\", mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, MaxAge_Negative) { EXPECT_NS_FAILED( ParseHeader("manifest=\"waict-manifest.json\", max-age=-1, mode=enforce, " "blocked-destinations=(script)"_ns)); } // mode - An sf-token with value "enforce" or "report". TEST_F(WAICTHeaderParsingTest, Mode_Enforce) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Mode_Report) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=report, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Mode_Missing) { EXPECT_NS_FAILED( ParseHeader("manifest=\"waict-manifest.json\", max-age=86400, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, Mode_Invalid) { EXPECT_NS_FAILED(ParseHeader( "manifest=\"waict-manifest.json\", max-age=86400, mode=block, " "blocked-destinations=(script)"_ns)); } // mode must be an SFV token (unquoted), not a string. TEST_F(WAICTHeaderParsingTest, Mode_AsString) { EXPECT_NS_FAILED(ParseHeader( "manifest=\"waict-manifest.json\", max-age=86400, mode=\"enforce\", " "blocked-destinations=(script)"_ns)); } // blocked-destinations - An sf-inner-list of one or more sf-tokens indicating // the destination types to which integrity checks apply. Unrecognised tokens // MUST be ignored. TEST_F(WAICTHeaderParsingTest, BlockedDestinations_Script) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script)"_ns)); } TEST_F(WAICTHeaderParsingTest, BlockedDestinations_Multiple) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script image)"_ns)); } TEST_F(WAICTHeaderParsingTest, BlockedDestinations_ScriptAndUnrecognised) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script cat)"_ns)); } TEST_F(WAICTHeaderParsingTest, BlockedDestinations_Empty) { EXPECT_NS_SUCCEEDED(ParseHeader( "manifest=\"waict-manifest.json\", max-age=86400, mode=enforce, " "blocked-destinations=()"_ns)); } TEST_F(WAICTHeaderParsingTest, BlockedDestinations_Missing) { EXPECT_NS_FAILED(ParseHeader( "manifest=\"waict-manifest.json\", max-age=86400, mode=enforce"_ns)); } // endpoints - Optional. Indicates endpoint(s) for submitting violations. TEST_F(WAICTHeaderParsingTest, Endpoints_Present) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script), endpoints=(default)"_ns)); } TEST_F(WAICTHeaderParsingTest, Endpoints_Multiple) { EXPECT_NS_SUCCEEDED(ParseHeader( "manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script), endpoints=(default other)"_ns)); } TEST_F(WAICTHeaderParsingTest, Endpoints_Zero) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script), endpoints=()"_ns)); } TEST_F(WAICTHeaderParsingTest, Endpoints_Missing) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script)"_ns)); } // preload - Optional. An sf-boolean indicating the site wants to enforce WAICT // via a preload list. Not used directly by user-agents. TEST_F(WAICTHeaderParsingTest, Preload_Missing) { EXPECT_NS_SUCCEEDED( ParseHeader("manifest=\"waict-manifest.json\", max-age=0, mode=enforce, " "blocked-destinations=(script)"_ns)); } // General // The entire header must be rejected if it is not valid SFV. TEST_F(WAICTHeaderParsingTest, MalformedSFV) { EXPECT_NS_FAILED(ParseHeader("not valid sfv !!!"_ns)); } TEST_F(WAICTHeaderParsingTest, EmptyHeader) { EXPECT_NS_FAILED(ParseHeader(""_ns)); }