1 // Copyright 2017 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 "services/network/content_security_policy/csp_source.h"
6 #include "services/network/content_security_policy/csp_context.h"
7 #include "testing/gtest/include/gtest/gtest.h"
8 
9 namespace content {
10 
11 namespace {
12 
13 // Allow() is an abbreviation of CSPSource::Allow(). Useful for writing test
14 // expectations on one line.
Allow(const network::mojom::CSPSourcePtr & source,const GURL & url,CSPContext * context,bool is_redirect=false)15 bool Allow(const network::mojom::CSPSourcePtr& source,
16            const GURL& url,
17            CSPContext* context,
18            bool is_redirect = false) {
19   return CheckCSPSource(source, url, context, is_redirect);
20 }
21 
22 }  // namespace
23 
TEST(CSPSourceTest,BasicMatching)24 TEST(CSPSourceTest, BasicMatching) {
25   CSPContext context;
26 
27   auto source = network::mojom::CSPSource::New("http", "example.com", 8000,
28                                                "/foo/", false, false);
29 
30   EXPECT_TRUE(Allow(source, GURL("http://example.com:8000/foo/"), &context));
31   EXPECT_TRUE(Allow(source, GURL("http://example.com:8000/foo/bar"), &context));
32   EXPECT_TRUE(Allow(source, GURL("HTTP://EXAMPLE.com:8000/foo/BAR"), &context));
33 
34   EXPECT_FALSE(Allow(source, GURL("http://example.com:8000/bar/"), &context));
35   EXPECT_FALSE(Allow(source, GURL("https://example.com:8000/bar/"), &context));
36   EXPECT_FALSE(Allow(source, GURL("http://example.com:9000/bar/"), &context));
37   EXPECT_FALSE(
38       Allow(source, GURL("HTTP://example.com:8000/FOO/bar"), &context));
39   EXPECT_FALSE(
40       Allow(source, GURL("HTTP://example.com:8000/FOO/BAR"), &context));
41 }
42 
TEST(CSPSourceTest,AllowScheme)43 TEST(CSPSourceTest, AllowScheme) {
44   CSPContext context;
45 
46   // http -> {http, https}.
47   {
48     auto source = network::mojom::CSPSource::New(
49         "http", "", url::PORT_UNSPECIFIED, "", false, false);
50     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
51     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
52     // This passes because the source is "scheme only" so the upgrade is
53     // allowed.
54     EXPECT_TRUE(Allow(source, GURL("https://a.com:80"), &context));
55     EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context));
56     EXPECT_FALSE(Allow(source, GURL("ws://a.com"), &context));
57     EXPECT_FALSE(Allow(source, GURL("wss://a.com"), &context));
58   }
59 
60   // ws -> {ws, wss}.
61   {
62     auto source = network::mojom::CSPSource::New(
63         "ws", "", url::PORT_UNSPECIFIED, "", false, false);
64     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
65     EXPECT_FALSE(Allow(source, GURL("https://a.com"), &context));
66     EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context));
67     EXPECT_TRUE(Allow(source, GURL("ws://a.com"), &context));
68     EXPECT_TRUE(Allow(source, GURL("wss://a.com"), &context));
69   }
70 
71   // Exact matches required (ftp)
72   {
73     auto source = network::mojom::CSPSource::New(
74         "ftp", "", url::PORT_UNSPECIFIED, "", false, false);
75     EXPECT_TRUE(Allow(source, GURL("ftp://a.com"), &context));
76     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
77   }
78 
79   // Exact matches required (https)
80   {
81     auto source = network::mojom::CSPSource::New(
82         "https", "", url::PORT_UNSPECIFIED, "", false, false);
83     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
84     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
85   }
86 
87   // Exact matches required (wss)
88   {
89     auto source = network::mojom::CSPSource::New(
90         "wss", "", url::PORT_UNSPECIFIED, "", false, false);
91     EXPECT_TRUE(Allow(source, GURL("wss://a.com"), &context));
92     EXPECT_FALSE(Allow(source, GURL("ws://a.com"), &context));
93   }
94 
95   // Scheme is empty (ProtocolMatchesSelf).
96   {
97     auto source = network::mojom::CSPSource::New(
98         "", "a.com", url::PORT_UNSPECIFIED, "", false, false);
99     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
100 
101     // Self's scheme is http.
102     context.SetSelf(url::Origin::Create(GURL("http://a.com")));
103     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
104     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
105     EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context));
106 
107     // Self's is https.
108     context.SetSelf(url::Origin::Create(GURL("https://a.com")));
109     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
110     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
111     EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context));
112 
113     // Self's scheme is not in the http familly.
114     context.SetSelf(url::Origin::Create(GURL("ftp://a.com/")));
115     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
116     EXPECT_TRUE(Allow(source, GURL("ftp://a.com"), &context));
117 
118     // Self's scheme is unique (non standard scheme).
119     context.SetSelf(url::Origin::Create(GURL("non-standard-scheme://a.com")));
120     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
121     EXPECT_FALSE(Allow(source, GURL("non-standard-scheme://a.com"), &context));
122 
123     // Self's scheme is unique (data-url).
124     context.SetSelf(
125         url::Origin::Create(GURL("data:text/html,<iframe src=[...]>")));
126     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
127     EXPECT_FALSE(Allow(source, GURL("data:text/html,hello"), &context));
128   }
129 }
130 
TEST(CSPSourceTest,AllowHost)131 TEST(CSPSourceTest, AllowHost) {
132   CSPContext context;
133   context.SetSelf(url::Origin::Create(GURL("http://example.com")));
134 
135   // Host is * (source-expression = "http://*")
136   {
137     auto source = network::mojom::CSPSource::New(
138         "http", "", url::PORT_UNSPECIFIED, "", true, false);
139     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
140     EXPECT_TRUE(Allow(source, GURL("http://."), &context));
141   }
142 
143   // Host is *.foo.bar
144   {
145     auto source = network::mojom::CSPSource::New(
146         "", "foo.bar", url::PORT_UNSPECIFIED, "", true, false);
147     EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context));
148     EXPECT_FALSE(Allow(source, GURL("http://bar"), &context));
149     EXPECT_FALSE(Allow(source, GURL("http://foo.bar"), &context));
150     EXPECT_FALSE(Allow(source, GURL("http://o.bar"), &context));
151     EXPECT_TRUE(Allow(source, GURL("http://*.foo.bar"), &context));
152     EXPECT_TRUE(Allow(source, GURL("http://sub.foo.bar"), &context));
153     EXPECT_TRUE(Allow(source, GURL("http://sub.sub.foo.bar"), &context));
154     // Please see http://crbug.com/692505
155     EXPECT_TRUE(Allow(source, GURL("http://.foo.bar"), &context));
156   }
157 
158   // Host is exact.
159   {
160     auto source = network::mojom::CSPSource::New(
161         "", "foo.bar", url::PORT_UNSPECIFIED, "", false, false);
162     EXPECT_TRUE(Allow(source, GURL("http://foo.bar"), &context));
163     EXPECT_FALSE(Allow(source, GURL("http://sub.foo.bar"), &context));
164     EXPECT_FALSE(Allow(source, GURL("http://bar"), &context));
165     // Please see http://crbug.com/692505
166     EXPECT_FALSE(Allow(source, GURL("http://.foo.bar"), &context));
167   }
168 }
169 
TEST(CSPSourceTest,AllowPort)170 TEST(CSPSourceTest, AllowPort) {
171   CSPContext context;
172   context.SetSelf(url::Origin::Create(GURL("http://example.com")));
173 
174   // Source's port unspecified.
175   {
176     auto source = network::mojom::CSPSource::New(
177         "", "a.com", url::PORT_UNSPECIFIED, "", false, false);
178     EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context));
179     EXPECT_FALSE(Allow(source, GURL("http://a.com:8080"), &context));
180     EXPECT_FALSE(Allow(source, GURL("http://a.com:443"), &context));
181     EXPECT_FALSE(Allow(source, GURL("https://a.com:80"), &context));
182     EXPECT_FALSE(Allow(source, GURL("https://a.com:8080"), &context));
183     EXPECT_TRUE(Allow(source, GURL("https://a.com:443"), &context));
184     EXPECT_FALSE(Allow(source, GURL("unknown://a.com:80"), &context));
185     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
186     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
187     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
188   }
189 
190   // Source's port is "*".
191   {
192     auto source = network::mojom::CSPSource::New(
193         "", "a.com", url::PORT_UNSPECIFIED, "", false, true);
194     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
195     EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context));
196     EXPECT_TRUE(Allow(source, GURL("http://a.com:8080"), &context));
197     EXPECT_TRUE(Allow(source, GURL("https://a.com:8080"), &context));
198     EXPECT_TRUE(Allow(source, GURL("https://a.com:0"), &context));
199     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
200   }
201 
202   // Source has a port.
203   {
204     auto source =
205         network::mojom::CSPSource::New("", "a.com", 80, "", false, false);
206     EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context));
207     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
208     EXPECT_FALSE(Allow(source, GURL("http://a.com:8080"), &context));
209     EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context));
210   }
211 
212   // Allow upgrade from :80 to :443
213   {
214     auto source =
215         network::mojom::CSPSource::New("", "a.com", 80, "", false, false);
216     EXPECT_TRUE(Allow(source, GURL("https://a.com:443"), &context));
217     // Should not allow scheme upgrades unless both port and scheme are
218     // upgraded.
219     EXPECT_FALSE(Allow(source, GURL("http://a.com:443"), &context));
220   }
221 
222   // Host is * but port is specified
223   {
224     auto source =
225         network::mojom::CSPSource::New("http", "", 111, "", true, false);
226     EXPECT_TRUE(Allow(source, GURL("http://a.com:111"), &context));
227     EXPECT_FALSE(Allow(source, GURL("http://a.com:222"), &context));
228   }
229 }
230 
TEST(CSPSourceTest,AllowPath)231 TEST(CSPSourceTest, AllowPath) {
232   CSPContext context;
233   context.SetSelf(url::Origin::Create(GURL("http://example.com")));
234 
235   // Path to a file
236   {
237     auto source = network::mojom::CSPSource::New(
238         "", "a.com", url::PORT_UNSPECIFIED, "/path/to/file", false, false);
239     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context));
240     EXPECT_FALSE(Allow(source, GURL("http://a.com/path/to/"), &context));
241     EXPECT_FALSE(
242         Allow(source, GURL("http://a.com/path/to/file/subpath"), &context));
243     EXPECT_FALSE(
244         Allow(source, GURL("http://a.com/path/to/something"), &context));
245   }
246 
247   // Path to a directory
248   {
249     auto source = network::mojom::CSPSource::New(
250         "", "a.com", url::PORT_UNSPECIFIED, "/path/to/", false, false);
251     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context));
252     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/"), &context));
253     EXPECT_FALSE(Allow(source, GURL("http://a.com/path/"), &context));
254     EXPECT_FALSE(Allow(source, GURL("http://a.com/path/to"), &context));
255     EXPECT_FALSE(Allow(source, GURL("http://a.com/path/to"), &context));
256   }
257 
258   // Empty path
259   {
260     auto source = network::mojom::CSPSource::New(
261         "", "a.com", url::PORT_UNSPECIFIED, "", false, false);
262     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context));
263     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/"), &context));
264     EXPECT_TRUE(Allow(source, GURL("http://a.com/"), &context));
265     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
266   }
267 
268   // Almost empty path
269   {
270     auto source = network::mojom::CSPSource::New(
271         "", "a.com", url::PORT_UNSPECIFIED, "/", false, false);
272     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context));
273     EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/"), &context));
274     EXPECT_TRUE(Allow(source, GURL("http://a.com/"), &context));
275     EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context));
276   }
277 
278   // Path encoded.
279   {
280     auto source = network::mojom::CSPSource::New(
281         "http", "a.com", url::PORT_UNSPECIFIED, "/Hello Günter", false, false);
282     EXPECT_TRUE(
283         Allow(source, GURL("http://a.com/Hello%20G%C3%BCnter"), &context));
284     EXPECT_TRUE(Allow(source, GURL("http://a.com/Hello Günter"), &context));
285   }
286 
287   // Host is * but path is specified.
288   {
289     auto source = network::mojom::CSPSource::New(
290         "http", "", url::PORT_UNSPECIFIED, "/allowed-path", true, false);
291     EXPECT_TRUE(Allow(source, GURL("http://a.com/allowed-path"), &context));
292     EXPECT_FALSE(Allow(source, GURL("http://a.com/disallowed-path"), &context));
293   }
294 }
295 
TEST(CSPSourceTest,RedirectMatching)296 TEST(CSPSourceTest, RedirectMatching) {
297   CSPContext context;
298   auto source = network::mojom::CSPSource::New("http", "a.com", 8000, "/bar/",
299                                                false, false);
300   EXPECT_TRUE(Allow(source, GURL("http://a.com:8000/"), &context, true));
301   EXPECT_TRUE(Allow(source, GURL("http://a.com:8000/foo"), &context, true));
302   EXPECT_FALSE(Allow(source, GURL("https://a.com:8000/foo"), &context, true));
303   EXPECT_FALSE(
304       Allow(source, GURL("http://not-a.com:8000/foo"), &context, true));
305   EXPECT_FALSE(Allow(source, GURL("http://a.com:9000/foo/"), &context, false));
306 }
307 
TEST(CSPSourceTest,ToString)308 TEST(CSPSourceTest, ToString) {
309   {
310     auto source = network::mojom::CSPSource::New(
311         "http", "", url::PORT_UNSPECIFIED, "", false, false);
312     EXPECT_EQ("http:", ToString(source));
313   }
314   {
315     auto source = network::mojom::CSPSource::New(
316         "http", "a.com", url::PORT_UNSPECIFIED, "", false, false);
317     EXPECT_EQ("http://a.com", ToString(source));
318   }
319   {
320     auto source = network::mojom::CSPSource::New(
321         "", "a.com", url::PORT_UNSPECIFIED, "", false, false);
322     EXPECT_EQ("a.com", ToString(source));
323   }
324   {
325     auto source = network::mojom::CSPSource::New(
326         "", "a.com", url::PORT_UNSPECIFIED, "", true, false);
327     EXPECT_EQ("*.a.com", ToString(source));
328   }
329   {
330     auto source = network::mojom::CSPSource::New("", "", url::PORT_UNSPECIFIED,
331                                                  "", true, false);
332     EXPECT_EQ("*", ToString(source));
333   }
334   {
335     auto source =
336         network::mojom::CSPSource::New("", "a.com", 80, "", false, false);
337     EXPECT_EQ("a.com:80", ToString(source));
338   }
339   {
340     auto source = network::mojom::CSPSource::New(
341         "", "a.com", url::PORT_UNSPECIFIED, "", false, true);
342     EXPECT_EQ("a.com:*", ToString(source));
343   }
344   {
345     auto source = network::mojom::CSPSource::New(
346         "", "a.com", url::PORT_UNSPECIFIED, "/path", false, false);
347     EXPECT_EQ("a.com/path", ToString(source));
348   }
349 }
350 
TEST(CSPSourceTest,UpgradeRequests)351 TEST(CSPSourceTest, UpgradeRequests) {
352   CSPContext context;
353   auto source =
354       network::mojom::CSPSource::New("http", "a.com", 80, "", false, false);
355   EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context, true));
356   EXPECT_FALSE(Allow(source, GURL("https://a.com:80"), &context, true));
357   EXPECT_FALSE(Allow(source, GURL("http://a.com:443"), &context, true));
358   EXPECT_TRUE(Allow(source, GURL("https://a.com:443"), &context, true));
359   EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context, true));
360 }
361 
362 }  // namespace content
363