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