blob: 5b7b28333e808c8074e3271fd940cc1c730e2efb [file] [log] [blame]
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: morlovich@google.com (Maksim Orlovich)
#include "net/instaweb/rewriter/public/csp.h"
#include <iostream>
#include <memory>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace net_instaweb {
// Help gTest printing.
::std::ostream& operator<<(::std::ostream& os,
const CspSourceExpression& expr) {
return os << expr.DebugString();
}
::std::ostream& operator<<(::std::ostream& os,
const CspSourceExpression::UrlData& url_data) {
return os << url_data.DebugString();
}
namespace {
TEST(CspParseSourceTest, Quoted) {
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kSelf),
CspSourceExpression::Parse("'self' "));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kSelf),
CspSourceExpression::Parse(" 'sElf' "));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kStrictDynamic),
CspSourceExpression::Parse(" \t 'strict-dynamic' "));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse(" \t 'strictly-unknown' "));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnsafeInline),
CspSourceExpression::Parse("'unsafe-inline'"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnsafeEval),
CspSourceExpression::Parse("'unsafe-eval'"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("'unsafe-eviiiiiil'"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnsafeHashedAttributes),
CspSourceExpression::Parse("'unsafe-hashed-attribUtes'"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("'nonce-qwertyu12345'"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("''"));
}
TEST(CspParseSourceTest, NonQuoted) {
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse(" "));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kSchemeSource,
CspSourceExpression::UrlData("https", "", "", "")),
CspSourceExpression::Parse(" https:"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kSchemeSource,
CspSourceExpression::UrlData("weird-scheme+-1.0", "", "", "")),
CspSourceExpression::Parse("weird-scheme+-1.0:"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("", "*.example.com", "", "")),
CspSourceExpression::Parse("*.example.com"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("*example.com"));
// w/o a colon this is a hostname, not a scheme.
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("", "http", "", "")),
CspSourceExpression::Parse("http"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("http", "www.example.com", "",
"/dir")),
CspSourceExpression::Parse("http://www.example.com/dir"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("http", "www.example.com", "",
"/dir/file.js")),
CspSourceExpression::Parse("http://www.example.com/dir/file.js"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("", "www.example.com", "",
"/dir/file.js")),
CspSourceExpression::Parse("www.example.com/dir/file.js"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("", "*", "", "")),
CspSourceExpression::Parse("*"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("http:!/example.com"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("http://"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("http:/"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("http:/example.com"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("?example.com/dir/file.js"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("http:///dir/file.js"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("https", "*", "*", "/foo.js")),
CspSourceExpression::Parse("https://*:*/foo.js"));
// Test for no port after :. Note that this needs an explicit scheme, since
// www.example.com: would be a valid scheme-source!
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("http://www.example.com:"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("www.example.com:/foo"));
EXPECT_EQ(
CspSourceExpression(
CspSourceExpression::kHostSource,
CspSourceExpression::UrlData("https", "*", "443", "/foo.js")),
CspSourceExpression::Parse("https://*:443/foo.js"));
EXPECT_EQ(
CspSourceExpression(CspSourceExpression::kUnknown),
CspSourceExpression::Parse("https://*:443?foo.js"));
}
TEST(CspParseSourceListTest, None) {
// Special keyword "none", semantically equivalent to an empty
// expressions list.
std::unique_ptr<CspSourceList> n1(CspSourceList::Parse(" 'None' "));
std::unique_ptr<CspSourceList> n2(CspSourceList::Parse("'none'"));
ASSERT_TRUE(n1 != nullptr);
ASSERT_TRUE(n2 != nullptr);
EXPECT_TRUE(n1->expressions().empty());
EXPECT_TRUE(n2->expressions().empty());
}
TEST(CspParseTest, Empty) {
std::unique_ptr<CspPolicy> policy(CspPolicy::Parse(" "));
EXPECT_EQ(policy, nullptr);
}
TEST(CspParseTest, Basic) {
std::unique_ptr<CspPolicy> policy(CspPolicy::Parse(
"default-src *; script-src 'unsafe-inline' 'unsafe-eval'"));
ASSERT_TRUE(policy != nullptr);
ASSERT_TRUE(policy->SourceListFor(CspDirective::kDefaultSrc) != nullptr);
const std::vector<CspSourceExpression>& default_src =
policy->SourceListFor(CspDirective::kDefaultSrc)->expressions();
ASSERT_EQ(1, default_src.size());
EXPECT_EQ(CspSourceExpression::kHostSource, default_src[0].kind());
EXPECT_EQ(CspSourceExpression::UrlData("", "*", "", ""),
default_src[0].url_data());
ASSERT_TRUE(policy->SourceListFor(CspDirective::kScriptSrc) != nullptr);
const std::vector<CspSourceExpression>& script_src =
policy->SourceListFor(CspDirective::kScriptSrc)->expressions();
ASSERT_EQ(2, script_src.size());
EXPECT_EQ(CspSourceExpression::kUnsafeInline, script_src[0].kind());
EXPECT_EQ(CspSourceExpression::kUnsafeEval, script_src[1].kind());
}
TEST(CspParseTest, Repeated) {
// Repeating within same policy doesn't do anything.
std::unique_ptr<CspPolicy> policy(CspPolicy::Parse(
"script-src 'unsafe-inline' 'unsafe-eval'; script-src 'strict-dynamic'"));
ASSERT_TRUE(policy != nullptr);
ASSERT_TRUE(policy->SourceListFor(CspDirective::kScriptSrc) != nullptr);
const std::vector<CspSourceExpression>& script_src =
policy->SourceListFor(CspDirective::kScriptSrc)->expressions();
ASSERT_EQ(2, script_src.size());
EXPECT_EQ(CspSourceExpression::kUnsafeInline, script_src[0].kind());
EXPECT_EQ(CspSourceExpression::kUnsafeEval, script_src[1].kind());
}
} // namespace
} // namespace net_instaweb