• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008, 2009 Apple 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 APPLE COMPUTER, 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 
27 #include "config.h"
28 #include "CrossOriginPreflightResultCache.h"
29 
30 #include "CrossOriginAccessControl.h"
31 #include "ResourceResponse.h"
32 #include <wtf/CurrentTime.h>
33 
34 namespace WebCore {
35 
36 // These values are at the discretion of the user agent.
37 static const unsigned defaultPreflightCacheTimeoutSeconds = 5;
38 static const unsigned maxPreflightCacheTimeoutSeconds = 600; // Should be short enough to minimize the risk of using a poisoned cache after switching to a secure network.
39 
parseAccessControlMaxAge(const String & string,unsigned & expiryDelta)40 static bool parseAccessControlMaxAge(const String& string, unsigned& expiryDelta)
41 {
42     // FIXME: this will not do the correct thing for a number starting with a '+'
43     bool ok = false;
44     expiryDelta = string.toUIntStrict(&ok);
45     return ok;
46 }
47 
48 template<class HashType>
addToAccessControlAllowList(const String & string,unsigned start,unsigned end,HashSet<String,HashType> & set)49 static void addToAccessControlAllowList(const String& string, unsigned start, unsigned end, HashSet<String, HashType>& set)
50 {
51     StringImpl* stringImpl = string.impl();
52     if (!stringImpl)
53         return;
54 
55     // Skip white space from start.
56     while (start <= end && isSpaceOrNewline((*stringImpl)[start]))
57         ++start;
58 
59     // only white space
60     if (start > end)
61         return;
62 
63     // Skip white space from end.
64     while (end && isSpaceOrNewline((*stringImpl)[end]))
65         --end;
66 
67     // substringCopy() is called on the strings because the cache is accessed on multiple threads.
68     set.add(string.substringCopy(start, end - start + 1));
69 }
70 
71 template<class HashType>
parseAccessControlAllowList(const String & string,HashSet<String,HashType> & set)72 static bool parseAccessControlAllowList(const String& string, HashSet<String, HashType>& set)
73 {
74     int start = 0;
75     int end;
76     while ((end = string.find(',', start)) != -1) {
77         if (start == end)
78             return false;
79 
80         addToAccessControlAllowList(string, start, end - 1, set);
81         start = end + 1;
82     }
83     if (start != static_cast<int>(string.length()))
84         addToAccessControlAllowList(string, start, string.length() - 1, set);
85 
86     return true;
87 }
88 
parse(const ResourceResponse & response)89 bool CrossOriginPreflightResultCacheItem::parse(const ResourceResponse& response)
90 {
91     m_methods.clear();
92     if (!parseAccessControlAllowList(response.httpHeaderField("Access-Control-Allow-Methods"), m_methods))
93         return false;
94 
95     m_headers.clear();
96     if (!parseAccessControlAllowList(response.httpHeaderField("Access-Control-Allow-Headers"), m_headers))
97         return false;
98 
99     unsigned expiryDelta;
100     if (parseAccessControlMaxAge(response.httpHeaderField("Access-Control-Max-Age"), expiryDelta)) {
101         if (expiryDelta > maxPreflightCacheTimeoutSeconds)
102             expiryDelta = maxPreflightCacheTimeoutSeconds;
103     } else
104         expiryDelta = defaultPreflightCacheTimeoutSeconds;
105 
106     m_absoluteExpiryTime = currentTime() + expiryDelta;
107     return true;
108 }
109 
allowsCrossOriginMethod(const String & method) const110 bool CrossOriginPreflightResultCacheItem::allowsCrossOriginMethod(const String& method) const
111 {
112     return m_methods.contains(method) || isOnAccessControlSimpleRequestMethodWhitelist(method);
113 }
114 
allowsCrossOriginHeaders(const HTTPHeaderMap & requestHeaders) const115 bool CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders(const HTTPHeaderMap& requestHeaders) const
116 {
117     HTTPHeaderMap::const_iterator end = requestHeaders.end();
118     for (HTTPHeaderMap::const_iterator it = requestHeaders.begin(); it != end; ++it) {
119         if (!m_headers.contains(it->first) && !isOnAccessControlSimpleRequestHeaderWhitelist(it->first, it->second))
120             return false;
121     }
122     return true;
123 }
124 
allowsRequest(bool includeCredentials,const String & method,const HTTPHeaderMap & requestHeaders) const125 bool CrossOriginPreflightResultCacheItem::allowsRequest(bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders) const
126 {
127     if (m_absoluteExpiryTime < currentTime())
128         return false;
129     if (includeCredentials && !m_credentials)
130         return false;
131     if (!allowsCrossOriginMethod(method))
132         return false;
133     if (!allowsCrossOriginHeaders(requestHeaders))
134         return false;
135     return true;
136 }
137 
shared()138 CrossOriginPreflightResultCache& CrossOriginPreflightResultCache::shared()
139 {
140     AtomicallyInitializedStatic(CrossOriginPreflightResultCache&, cache = *new CrossOriginPreflightResultCache);
141     return cache;
142 }
143 
appendEntry(const String & origin,const KURL & url,CrossOriginPreflightResultCacheItem * preflightResult)144 void CrossOriginPreflightResultCache::appendEntry(const String& origin, const KURL& url, CrossOriginPreflightResultCacheItem* preflightResult)
145 {
146     MutexLocker lock(m_mutex);
147     // Note that the entry may already be present in the HashMap if another thread is accessing the same location.
148     m_preflightHashMap.set(std::make_pair(origin.copy(), url.copy()), preflightResult);
149 }
150 
canSkipPreflight(const String & origin,const KURL & url,bool includeCredentials,const String & method,const HTTPHeaderMap & requestHeaders)151 bool CrossOriginPreflightResultCache::canSkipPreflight(const String& origin, const KURL& url, bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders)
152 {
153     MutexLocker lock(m_mutex);
154     CrossOriginPreflightResultHashMap::iterator cacheIt = m_preflightHashMap.find(std::make_pair(origin, url));
155     if (cacheIt == m_preflightHashMap.end())
156         return false;
157 
158     if (cacheIt->second->allowsRequest(includeCredentials, method, requestHeaders))
159         return true;
160 
161     delete cacheIt->second;
162     m_preflightHashMap.remove(cacheIt);
163     return false;
164 }
165 
empty()166 void CrossOriginPreflightResultCache::empty()
167 {
168     MutexLocker lock(m_mutex);
169     deleteAllValues(m_preflightHashMap);
170     m_preflightHashMap.clear();
171 }
172 
173 } // namespace WebCore
174