CSP: Extract helper classes into their own files
[WebKit-https.git] / Source / WebCore / page / csp / ContentSecurityPolicySourceList.cpp
1 /*
2  * Copyright (C) 2011 Google, Inc. All rights reserved.
3  * Copyright (C) 2016 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "ContentSecurityPolicySourceList.h"
29
30 #include "ContentSecurityPolicy.h"
31 #include "ContentSecurityPolicyDirectiveList.h"
32 #include "ParsingUtilities.h"
33 #include "SecurityOrigin.h"
34 #include "URL.h"
35 #include <wtf/ASCIICType.h>
36
37 namespace WebCore {
38
39 static bool isSourceCharacter(UChar c)
40 {
41     return !isASCIISpace(c);
42 }
43
44 static bool isHostCharacter(UChar c)
45 {
46     return isASCIIAlphanumeric(c) || c == '-';
47 }
48
49 static bool isPathComponentCharacter(UChar c)
50 {
51     return c != '?' && c != '#';
52 }
53
54 static bool isSchemeContinuationCharacter(UChar c)
55 {
56     return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.';
57 }
58
59 static bool isNotColonOrSlash(UChar c)
60 {
61     return c != ':' && c != '/';
62 }
63
64 static bool isSourceListNone(const String& value)
65 {
66     auto characters = StringView(value).upconvertedCharacters();
67     const UChar* begin = characters;
68     const UChar* end = characters + value.length();
69     skipWhile<UChar, isASCIISpace>(begin, end);
70
71     const UChar* position = begin;
72     skipWhile<UChar, isSourceCharacter>(position, end);
73     if (!equalLettersIgnoringASCIICase(begin, position - begin, "'none'"))
74         return false;
75
76     skipWhile<UChar, isASCIISpace>(position, end);
77     if (position != end)
78         return false;
79     
80     return true;
81 }
82
83 ContentSecurityPolicySourceList::ContentSecurityPolicySourceList(const ContentSecurityPolicy& policy, const String& directiveName)
84     : m_policy(policy)
85     , m_directiveName(directiveName)
86 {
87 }
88
89 void ContentSecurityPolicySourceList::parse(const String& value)
90 {
91     // We represent 'none' as an empty m_list.
92     if (isSourceListNone(value))
93         return;
94     auto characters = StringView(value).upconvertedCharacters();
95     parse(characters, characters + value.length());
96 }
97
98 bool ContentSecurityPolicySourceList::matches(const URL& url)
99 {
100     if (m_allowStar)
101         return true;
102
103     URL effectiveURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url;
104
105     if (m_allowSelf && m_policy.urlMatchesSelf(effectiveURL))
106         return true;
107
108     for (auto& entry : m_list) {
109         if (entry.matches(effectiveURL))
110             return true;
111     }
112
113     return false;
114 }
115
116 // source-list       = *WSP [ source *( 1*WSP source ) *WSP ]
117 //                   / *WSP "'none'" *WSP
118 //
119 void ContentSecurityPolicySourceList::parse(const UChar* begin, const UChar* end)
120 {
121     const UChar* position = begin;
122
123     while (position < end) {
124         skipWhile<UChar, isASCIISpace>(position, end);
125         if (position == end)
126             return;
127
128         const UChar* beginSource = position;
129         skipWhile<UChar, isSourceCharacter>(position, end);
130
131         String scheme, host, path;
132         int port = 0;
133         bool hostHasWildcard = false;
134         bool portHasWildcard = false;
135
136         if (parseSource(beginSource, position, scheme, host, port, path, hostHasWildcard, portHasWildcard)) {
137             // Wildcard hosts and keyword sources ('self', 'unsafe-inline',
138             // etc.) aren't stored in m_list, but as attributes on the source
139             // list itself.
140             if (scheme.isEmpty() && host.isEmpty())
141                 continue;
142             if (isCSPDirectiveName(host))
143                 m_policy.reportDirectiveAsSourceExpression(m_directiveName, host);
144             m_list.append(ContentSecurityPolicySource(m_policy, scheme, host, port, path, hostHasWildcard, portHasWildcard));
145         } else
146             m_policy.reportInvalidSourceExpression(m_directiveName, String(beginSource, position - beginSource));
147
148         ASSERT(position == end || isASCIISpace(*position));
149     }
150 }
151
152 // source            = scheme ":"
153 //                   / ( [ scheme "://" ] host [ port ] [ path ] )
154 //                   / "'self'"
155 //
156 bool ContentSecurityPolicySourceList::parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, bool& hostHasWildcard, bool& portHasWildcard)
157 {
158     if (begin == end)
159         return false;
160
161     if (equalLettersIgnoringASCIICase(begin, end - begin, "'none'"))
162         return false;
163
164     if (end - begin == 1 && *begin == '*') {
165         m_allowStar = true;
166         return true;
167     }
168
169     if (equalLettersIgnoringASCIICase(begin, end - begin, "'self'")) {
170         m_allowSelf = true;
171         return true;
172     }
173
174     if (equalLettersIgnoringASCIICase(begin, end - begin, "'unsafe-inline'")) {
175         m_allowInline = true;
176         return true;
177     }
178
179     if (equalLettersIgnoringASCIICase(begin, end - begin, "'unsafe-eval'")) {
180         m_allowEval = true;
181         return true;
182     }
183
184     const UChar* position = begin;
185     const UChar* beginHost = begin;
186     const UChar* beginPath = end;
187     const UChar* beginPort = nullptr;
188
189     skipWhile<UChar, isNotColonOrSlash>(position, end);
190
191     if (position == end) {
192         // host
193         //     ^
194         return parseHost(beginHost, position, host, hostHasWildcard);
195     }
196
197     if (position < end && *position == '/') {
198         // host/path || host/ || /
199         //     ^            ^    ^
200         if (!parseHost(beginHost, position, host, hostHasWildcard)
201             || !parsePath(position, end, path)
202             || position != end)
203             return false;
204         return true;
205     }
206
207     if (position < end && *position == ':') {
208         if (end - position == 1) {
209             // scheme:
210             //       ^
211             return parseScheme(begin, position, scheme);
212         }
213
214         if (position[1] == '/') {
215             // scheme://host || scheme://
216             //       ^                ^
217             if (!parseScheme(begin, position, scheme)
218                 || !skipExactly<UChar>(position, end, ':')
219                 || !skipExactly<UChar>(position, end, '/')
220                 || !skipExactly<UChar>(position, end, '/'))
221                 return false;
222             if (position == end)
223                 return true;
224             beginHost = position;
225             skipWhile<UChar, isNotColonOrSlash>(position, end);
226         }
227
228         if (position < end && *position == ':') {
229             // host:port || scheme://host:port
230             //     ^                     ^
231             beginPort = position;
232             skipUntil<UChar>(position, end, '/');
233         }
234     }
235
236     if (position < end && *position == '/') {
237         // scheme://host/path || scheme://host:port/path
238         //              ^                          ^
239         if (position == beginHost)
240             return false;
241
242         beginPath = position;
243     }
244
245     if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, hostHasWildcard))
246         return false;
247
248     if (!beginPort)
249         port = 0;
250     else {
251         if (!parsePort(beginPort, beginPath, port, portHasWildcard))
252             return false;
253     }
254
255     if (beginPath != end) {
256         if (!parsePath(beginPath, end, path))
257             return false;
258     }
259
260     return true;
261 }
262
263 //                     ; <scheme> production from RFC 3986
264 // scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
265 //
266 bool ContentSecurityPolicySourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
267 {
268     ASSERT(begin <= end);
269     ASSERT(scheme.isEmpty());
270
271     if (begin == end)
272         return false;
273
274     const UChar* position = begin;
275
276     if (!skipExactly<UChar, isASCIIAlpha>(position, end))
277         return false;
278
279     skipWhile<UChar, isSchemeContinuationCharacter>(position, end);
280
281     if (position != end)
282         return false;
283
284     scheme = String(begin, end - begin);
285     return true;
286 }
287
288 // host              = [ "*." ] 1*host-char *( "." 1*host-char )
289 //                   / "*"
290 // host-char         = ALPHA / DIGIT / "-"
291 //
292 bool ContentSecurityPolicySourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard)
293 {
294     ASSERT(begin <= end);
295     ASSERT(host.isEmpty());
296     ASSERT(!hostHasWildcard);
297
298     if (begin == end)
299         return false;
300
301     const UChar* position = begin;
302
303     if (skipExactly<UChar>(position, end, '*')) {
304         hostHasWildcard = true;
305
306         if (position == end)
307             return true;
308
309         if (!skipExactly<UChar>(position, end, '.'))
310             return false;
311     }
312
313     const UChar* hostBegin = position;
314
315     while (position < end) {
316         if (!skipExactly<UChar, isHostCharacter>(position, end))
317             return false;
318
319         skipWhile<UChar, isHostCharacter>(position, end);
320
321         if (position < end && !skipExactly<UChar>(position, end, '.'))
322             return false;
323     }
324
325     ASSERT(position == end);
326     host = String(hostBegin, end - hostBegin);
327     return true;
328 }
329
330 bool ContentSecurityPolicySourceList::parsePath(const UChar* begin, const UChar* end, String& path)
331 {
332     ASSERT(begin <= end);
333     ASSERT(path.isEmpty());
334     
335     const UChar* position = begin;
336     skipWhile<UChar, isPathComponentCharacter>(position, end);
337     // path/to/file.js?query=string || path/to/file.js#anchor
338     //                ^                               ^
339     if (position < end)
340         m_policy.reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position);
341     
342     path = decodeURLEscapeSequences(String(begin, position - begin));
343     
344     ASSERT(position <= end);
345     ASSERT(position == end || (*position == '#' || *position == '?'));
346     return true;
347 }
348
349 // port              = ":" ( 1*DIGIT / "*" )
350 //
351 bool ContentSecurityPolicySourceList::parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard)
352 {
353     ASSERT(begin <= end);
354     ASSERT(!port);
355     ASSERT(!portHasWildcard);
356     
357     if (!skipExactly<UChar>(begin, end, ':'))
358         ASSERT_NOT_REACHED();
359     
360     if (begin == end)
361         return false;
362     
363     if (end - begin == 1 && *begin == '*') {
364         port = 0;
365         portHasWildcard = true;
366         return true;
367     }
368     
369     const UChar* position = begin;
370     skipWhile<UChar, isASCIIDigit>(position, end);
371     
372     if (position != end)
373         return false;
374     
375     bool ok;
376     port = charactersToIntStrict(begin, end - begin, &ok);
377     return ok;
378 }
379
380 } // namespace WebCore