b8989afa36fb15a99d511d18a4a30fa217099507
[WebKit-https.git] / Source / WebCore / page / ContentSecurityPolicy.cpp
1 /*
2  * Copyright (C) 2011 Google, Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ContentSecurityPolicy.h"
28
29 #include "Document.h"
30 #include "NotImplemented.h"
31 #include "SecurityOrigin.h"
32
33 namespace WebCore {
34
35 // Normally WebKit uses "static" for internal linkage, but using "static" for
36 // these functions causes a compile error because these functions are used as
37 // template parameters.
38 namespace {
39
40 bool isDirectiveNameCharacter(UChar c)
41 {
42     return isASCIIAlphanumeric(c) || c == '-';
43 }
44
45 bool isDirectiveValueCharacter(UChar c)
46 {
47     return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR
48 }
49
50 bool isSourceCharacter(UChar c)
51 {
52     return !isASCIISpace(c);
53 }
54
55 bool isHostCharacter(UChar c)
56 {
57     return isASCIIAlphanumeric(c) || c == '-';
58 }
59
60 bool isSchemeContinuationCharacter(UChar c)
61 {
62     return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.';
63 }
64
65 } // namespace
66
67 static bool skipExactly(const UChar*& position, const UChar* end, UChar delimiter)
68 {
69     if (position < end && *position == delimiter) {
70         ++position;
71         return true;
72     }
73     return false;
74 }
75
76 template<bool characterPredicate(UChar)>
77 static bool skipExactly(const UChar*& position, const UChar* end)
78 {
79     if (position < end && characterPredicate(*position)) {
80         ++position;
81         return true;
82     }
83     return false;
84 }
85
86 static void skipUtil(const UChar*& position, const UChar* end, UChar delimiter)
87 {
88     while (position < end && *position != delimiter)
89         ++position;
90 }
91
92 template<bool characterPredicate(UChar)>
93 static void skipWhile(const UChar*& position, const UChar* end)
94 {
95     while (position < end && characterPredicate(*position))
96         ++position;
97 }
98
99 class CSPSource {
100 public:
101     CSPSource(const String& scheme, const String& host, int port, bool hostHasWildcard, bool portHasWildcard)
102         : m_scheme(scheme)
103         , m_host(host)
104         , m_port(port)
105         , m_hostHasWildcard(hostHasWildcard)
106         , m_portHasWildcard(portHasWildcard)
107     {
108     }
109
110     bool matches(const KURL& url) const
111     {
112         if (!schemeMatches(url))
113             return false;
114         if (isSchemeOnly())
115             return true;
116         return hostMatches(url) && portMatches(url);
117     }
118
119 private:
120     bool schemeMatches(const KURL& url) const
121     {
122         return equalIgnoringCase(url.protocol(), m_scheme);
123     }
124
125     bool hostMatches(const KURL& url) const
126     {
127         if (m_hostHasWildcard)
128             notImplemented();
129
130         return equalIgnoringCase(url.host(), m_host);
131     }
132
133     bool portMatches(const KURL& url) const
134     {
135         if (m_portHasWildcard)
136             return true;
137
138         // FIXME: Handle explicit default ports correctly.
139         return url.port() == m_port;
140     }
141
142     bool isSchemeOnly() const { return m_host.isEmpty(); }
143
144     String m_scheme;
145     String m_host;
146     int m_port;
147
148     bool m_hostHasWildcard;
149     bool m_portHasWildcard;
150 };
151
152 class CSPSourceList {
153 public:
154     explicit CSPSourceList(SecurityOrigin*);
155
156     void parse(const String&);
157     bool matches(const KURL&);
158
159 private:
160     void parse(const UChar* begin, const UChar* end);
161
162     bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, bool& hostHasWildcard, bool& portHasWildcard);
163     bool parseScheme(const UChar* begin, const UChar* end, String& scheme);
164     bool parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard);
165     bool parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard);
166
167     void addSourceSelf();
168
169     SecurityOrigin* m_origin;
170     Vector<CSPSource> m_list;
171 };
172
173 CSPSourceList::CSPSourceList(SecurityOrigin* origin)
174     : m_origin(origin)
175 {
176 }
177
178 void CSPSourceList::parse(const String& value)
179 {
180     parse(value.characters(), value.characters() + value.length());
181 }
182
183 bool CSPSourceList::matches(const KURL& url)
184 {
185     for (size_t i = 0; i < m_list.size(); ++i) {
186         if (m_list[i].matches(url))
187             return true;
188     }
189     return false;
190 }
191
192 // source-list       = *WSP [ source *( 1*WSP source ) *WSP ]
193 //                   / *WSP "'none'" *WSP
194 //
195 void CSPSourceList::parse(const UChar* begin, const UChar* end)
196 {
197     const UChar* position = begin;
198
199     bool isFirstSourceInList = true;
200     while (position < end) {
201         skipWhile<isASCIISpace>(position, end);
202         const UChar* beginSource = position;
203         skipWhile<isSourceCharacter>(position, end);
204
205         if (isFirstSourceInList && equalIgnoringCase("'none'", beginSource, position - beginSource))
206             return; // We represent 'none' as an empty m_list.
207         isFirstSourceInList = false;
208
209         String scheme, host;
210         int port = 0;
211         bool hostHasWildcard = false;
212         bool portHasWildcard = false;
213
214         if (parseSource(beginSource, position, scheme, host, port, hostHasWildcard, portHasWildcard)) {
215             if (scheme.isEmpty())
216                 scheme = m_origin->protocol();
217             m_list.append(CSPSource(scheme, host, port, hostHasWildcard, portHasWildcard));
218         }
219
220         ASSERT(position == end || isASCIISpace(*position));
221      }
222 }
223
224 // source            = scheme ":"
225 //                   / ( [ scheme "://" ] host [ port ] )
226 //                   / "'self'"
227 //
228 bool CSPSourceList::parseSource(const UChar* begin, const UChar* end,
229                                 String& scheme, String& host, int& port,
230                                 bool& hostHasWildcard, bool& portHasWildcard)
231 {
232     if (begin == end)
233         return false;
234
235     if (equalIgnoringCase("'self'", begin, end - begin)) {
236         addSourceSelf();
237         return false;
238     }
239
240     const UChar* position = begin;
241
242     const UChar* beginHost = begin;
243     skipUtil(position, end, ':');
244
245     if (position == end) {
246         // This must be a host-only source.
247         if (!parseHost(beginHost, position, host, hostHasWildcard))
248             return false;
249         return true;
250     }
251
252     if (end - position == 1) {
253         ASSERT(*position == ':');
254         // This must be a scheme-only source.
255         if (!parseScheme(begin, position, scheme))
256             return false;
257         return true;
258     }
259
260     ASSERT(end - position >= 2);
261     if (position[1] == '/') {
262         if (!parseScheme(begin, position, scheme)
263             || !skipExactly(position, end, ':')
264             || !skipExactly(position, end, '/')
265             || !skipExactly(position, end, '/'))
266             return false;
267         beginHost = position;
268         skipUtil(position, end, ':');
269     }
270
271     if (position == beginHost)
272         return false;
273
274     if (!parseHost(beginHost, position, host, hostHasWildcard))
275         return false;
276
277     if (position == end) {
278         port = 0;
279         return true;
280     }
281
282     if (!skipExactly(position, end, ':'))
283         ASSERT_NOT_REACHED();
284
285     if (!parsePort(position, end, port, portHasWildcard))
286         return false;
287
288     return true;
289 }
290
291 //                     ; <scheme> production from RFC 3986
292 // scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
293 //
294 bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
295 {
296     ASSERT(begin <= end);
297     ASSERT(scheme.isEmpty());
298
299     if (begin == end)
300         return false;
301
302     const UChar* position = begin;
303
304     if (!skipExactly<isASCIIAlpha>(position, end))
305         return false;
306
307     skipWhile<isSchemeContinuationCharacter>(position, end);
308
309     if (position != end)
310         return false;
311
312     scheme = String(begin, end - begin);
313     return true;
314 }
315
316 // host              = [ "*." ] 1*host-char *( "." 1*host-char )
317 //                   / "*"
318 // host-char         = ALPHA / DIGIT / "-"
319 //
320 bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard)
321 {
322     ASSERT(begin <= end);
323     ASSERT(host.isEmpty());
324     ASSERT(!hostHasWildcard);
325
326     if (begin == end)
327         return false;
328
329     const UChar* position = begin;
330
331     if (skipExactly(position, end, '*')) {
332         hostHasWildcard = true;
333
334         if (position == end)
335             return true;
336
337         if (!skipExactly(position, end, '.'))
338             return false;
339     }
340
341     const UChar* hostBegin = position;
342
343     while (position < end) {
344         if (!skipExactly<isHostCharacter>(position, end))
345             return false;
346
347         skipWhile<isHostCharacter>(position, end);
348
349         if (position < end && !skipExactly(position, end, '.'))
350             return false;
351     }
352
353     ASSERT(position == end);
354     host = String(hostBegin, end - hostBegin);
355     return true;
356 }
357
358 // port              = ":" ( 1*DIGIT / "*" )
359 //
360 bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard)
361 {
362     ASSERT(begin <= end);
363     ASSERT(!port);
364     ASSERT(!portHasWildcard);
365
366     if (begin == end)
367         return false;
368
369     if (end - begin == 1 && *begin == '*') {
370         port = 0;
371         portHasWildcard = true;
372         return true;
373     }
374
375     const UChar* position = begin;
376     skipWhile<isASCIIDigit>(position, end);
377
378     if (position != end)
379         return false;
380
381     bool ok;
382     port = charactersToIntStrict(begin, end - begin, &ok);
383     return ok;
384 }
385
386 void CSPSourceList::addSourceSelf()
387 {
388     // FIXME: Inherit the scheme, host, and port from the current URL.
389     notImplemented();
390 }
391
392 class CSPDirective {
393 public:
394     CSPDirective(const String& value, SecurityOrigin* origin)
395         : m_sourceList(origin)
396     {
397         m_sourceList.parse(value);
398     }
399
400     bool allows(const KURL& url)
401     {
402         return m_sourceList.matches(url);
403     }
404
405 private:
406     CSPSourceList m_sourceList;
407 };
408
409 ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin)
410     : m_havePolicy(false)
411     , m_origin(origin)
412 {
413 }
414
415 ContentSecurityPolicy::~ContentSecurityPolicy()
416 {
417 }
418
419 void ContentSecurityPolicy::didReceiveHeader(const String& header)
420 {
421     if (m_havePolicy)
422         return; // The first policy wins.
423
424     parse(header);
425     m_havePolicy = true;
426 }
427
428 bool ContentSecurityPolicy::allowJavaScriptURLs() const
429 {
430     return !m_scriptSrc;
431 }
432
433 bool ContentSecurityPolicy::allowInlineEventHandlers() const
434 {
435     return !m_scriptSrc;
436 }
437
438 bool ContentSecurityPolicy::allowInlineScript() const
439 {
440     return !m_scriptSrc;
441 }
442
443 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
444 {
445     return !m_scriptSrc || m_scriptSrc->allows(url);
446 }
447
448 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url) const
449 {
450     return !m_objectSrc || m_objectSrc->allows(url);
451 }
452
453 // policy            = directive-list
454 // directive-list    = [ directive *( ";" [ directive ] ) ]
455 //
456 void ContentSecurityPolicy::parse(const String& policy)
457 {
458     ASSERT(!m_havePolicy);
459
460     if (policy.isEmpty())
461         return;
462
463     const UChar* position = policy.characters();
464     const UChar* end = position + policy.length();
465
466     while (position < end) {
467         const UChar* directiveBegin = position;
468         skipUtil(position, end, ';');
469
470         String name, value;
471         if (parseDirective(directiveBegin, position, name, value)) {
472             ASSERT(!name.isEmpty());
473             addDirective(name, value);
474         }
475
476         ASSERT(position == end || *position == ';');
477         skipExactly(position, end, ';');
478     }
479 }
480
481 // directive         = *WSP [ directive-name [ WSP directive-value ] ]
482 // directive-name    = 1*( ALPHA / DIGIT / "-" )
483 // directive-value   = *( WSP / <VCHAR except ";"> )
484 //
485 bool ContentSecurityPolicy::parseDirective(const UChar* begin, const UChar* end, String& name, String& value)
486 {
487     ASSERT(name.isEmpty());
488     ASSERT(value.isEmpty());
489
490     const UChar* position = begin;
491     skipWhile<isASCIISpace>(position, end);
492
493     const UChar* nameBegin = position;
494     skipWhile<isDirectiveNameCharacter>(position, end);
495
496     // The directive-name must be non-empty.
497     if (nameBegin == position)
498         return false;
499
500     name = String(nameBegin, position - nameBegin);
501
502     if (position == end)
503         return true;
504
505     if (!skipExactly<isASCIISpace>(position, end))
506         return false;
507
508     skipWhile<isASCIISpace>(position, end);
509
510     const UChar* valueBegin = position;
511     skipWhile<isDirectiveValueCharacter>(position, end);
512
513     if (position != end)
514         return false;
515
516     // The directive-value may be empty.
517     if (valueBegin == position)
518         return true;
519
520     value = String(valueBegin, position - valueBegin);
521     return true;
522 }
523
524 void ContentSecurityPolicy::addDirective(const String& name, const String& value)
525 {
526     DEFINE_STATIC_LOCAL(String, scriptSrc, ("script-src"));
527     DEFINE_STATIC_LOCAL(String, objectSrc, ("object-src"));
528
529     ASSERT(!name.isEmpty());
530
531     if (!m_scriptSrc && equalIgnoringCase(name, scriptSrc))
532         m_scriptSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
533     else if (!m_objectSrc && equalIgnoringCase(name, objectSrc))
534         m_objectSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
535 }
536
537 }