1/*
2 * Copyright (C) 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 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 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 "CredentialStorage.h"
28
29#include "NetworkStorageSession.h"
30#include <wtf/URL.h>
31
32#if PLATFORM(IOS_FAMILY)
33#include "WebCoreThread.h"
34#endif
35
36namespace WebCore {
37
38static String originStringFromURL(const URL& url)
39{
40 return makeString(url.protocol(), "://", url.hostAndPort(), '/');
41}
42
43static String protectionSpaceMapKeyFromURL(const URL& url)
44{
45 ASSERT(url.isValid());
46
47 // Remove the last path component that is not a directory to determine the subtree for which credentials will apply.
48 // We keep a leading slash, but remove a trailing one.
49 String directoryURL = url.string().substring(0, url.pathEnd());
50 unsigned directoryURLPathStart = url.pathStart();
51 ASSERT(directoryURL[directoryURLPathStart] == '/');
52 if (directoryURL.length() > directoryURLPathStart + 1) {
53 size_t index = directoryURL.reverseFind('/');
54 ASSERT(index != notFound);
55 directoryURL = directoryURL.substring(0, (index != directoryURLPathStart) ? index : directoryURLPathStart + 1);
56 }
57
58 return directoryURL;
59}
60
61void CredentialStorage::set(const String& partitionName, const Credential& credential, const ProtectionSpace& protectionSpace, const URL& url)
62{
63 ASSERT(protectionSpace.isProxy() || protectionSpace.authenticationScheme() == ProtectionSpaceAuthenticationSchemeClientCertificateRequested || url.protocolIsInHTTPFamily());
64 ASSERT(protectionSpace.isProxy() || protectionSpace.authenticationScheme() == ProtectionSpaceAuthenticationSchemeClientCertificateRequested || url.isValid());
65
66 m_protectionSpaceToCredentialMap.set(std::make_pair(partitionName, protectionSpace), credential);
67
68 if (!protectionSpace.isProxy() && protectionSpace.authenticationScheme() != ProtectionSpaceAuthenticationSchemeClientCertificateRequested) {
69 m_originsWithCredentials.add(originStringFromURL(url));
70
71 ProtectionSpaceAuthenticationScheme scheme = protectionSpace.authenticationScheme();
72 if (scheme == ProtectionSpaceAuthenticationSchemeHTTPBasic || scheme == ProtectionSpaceAuthenticationSchemeDefault) {
73 // The map can contain both a path and its subpath - while redundant, this makes lookups faster.
74 m_pathToDefaultProtectionSpaceMap.set(protectionSpaceMapKeyFromURL(url), protectionSpace);
75 }
76 }
77}
78
79Credential CredentialStorage::get(const String& partitionName, const ProtectionSpace& protectionSpace)
80{
81 return m_protectionSpaceToCredentialMap.get(std::make_pair(partitionName, protectionSpace));
82}
83
84void CredentialStorage::remove(const String& partitionName, const ProtectionSpace& protectionSpace)
85{
86 m_protectionSpaceToCredentialMap.remove(std::make_pair(partitionName, protectionSpace));
87}
88
89void CredentialStorage::removeCredentialsWithOrigin(const SecurityOriginData& origin)
90{
91 Vector<std::pair<String, ProtectionSpace>> keysToRemove;
92 for (auto& keyValuePair : m_protectionSpaceToCredentialMap) {
93 auto& protectionSpace = keyValuePair.key.second;
94 if (protectionSpace.host() == origin.host
95 && ((origin.port && protectionSpace.port() == *origin.port)
96 || (!origin.port && protectionSpace.port() == 80))
97 && ((protectionSpace.serverType() == ProtectionSpaceServerHTTP && origin.protocol == "http"_s)
98 || (protectionSpace.serverType() == ProtectionSpaceServerHTTPS && origin.protocol == "https"_s)))
99 keysToRemove.append(keyValuePair.key);
100 }
101 for (auto& key : keysToRemove)
102 remove(key.first, key.second);
103}
104
105Vector<SecurityOriginData> CredentialStorage::originsWithCredentials() const
106{
107 Vector<SecurityOriginData> origins;
108 for (auto& keyValuePair : m_protectionSpaceToCredentialMap) {
109 auto& protectionSpace = keyValuePair.key.second;
110 if (protectionSpace.isProxy())
111 continue;
112 String protocol;
113 switch (protectionSpace.serverType()) {
114 case ProtectionSpaceServerHTTP:
115 protocol = "http"_s;
116 break;
117 case ProtectionSpaceServerHTTPS:
118 protocol = "https"_s;
119 break;
120 case ProtectionSpaceServerFTP:
121 protocol = "ftp"_s;
122 break;
123 case ProtectionSpaceServerFTPS:
124 protocol = "ftps"_s;
125 break;
126 default:
127 ASSERT_NOT_REACHED();
128 continue;
129 }
130
131 SecurityOriginData origin { protocol, protectionSpace.host(), static_cast<uint16_t>(protectionSpace.port())};
132 origins.append(WTFMove(origin));
133 }
134 return origins;
135}
136
137HashMap<String, ProtectionSpace>::iterator CredentialStorage::findDefaultProtectionSpaceForURL(const URL& url)
138{
139 ASSERT(url.protocolIsInHTTPFamily());
140 ASSERT(url.isValid());
141
142 // Don't spend time iterating the path for origins that don't have any credentials.
143 if (!m_originsWithCredentials.contains(originStringFromURL(url)))
144 return m_pathToDefaultProtectionSpaceMap.end();
145
146 String directoryURL = protectionSpaceMapKeyFromURL(url);
147 unsigned directoryURLPathStart = url.pathStart();
148 while (true) {
149 PathToDefaultProtectionSpaceMap::iterator iter = m_pathToDefaultProtectionSpaceMap.find(directoryURL);
150 if (iter != m_pathToDefaultProtectionSpaceMap.end())
151 return iter;
152
153 if (directoryURL.length() == directoryURLPathStart + 1) // path is "/" already, cannot shorten it any more
154 return m_pathToDefaultProtectionSpaceMap.end();
155
156 size_t index = directoryURL.reverseFind('/', directoryURL.length() - 2);
157 ASSERT(index != notFound);
158 directoryURL = directoryURL.substring(0, (index == directoryURLPathStart) ? index + 1 : index);
159 ASSERT(directoryURL.length() > directoryURLPathStart);
160 }
161}
162
163bool CredentialStorage::set(const String& partitionName, const Credential& credential, const URL& url)
164{
165 ASSERT(url.protocolIsInHTTPFamily());
166 ASSERT(url.isValid());
167 PathToDefaultProtectionSpaceMap::iterator iter = findDefaultProtectionSpaceForURL(url);
168 if (iter == m_pathToDefaultProtectionSpaceMap.end())
169 return false;
170 ASSERT(m_originsWithCredentials.contains(originStringFromURL(url)));
171 m_protectionSpaceToCredentialMap.set(std::make_pair(partitionName, iter->value), credential);
172 return true;
173}
174
175Credential CredentialStorage::get(const String& partitionName, const URL& url)
176{
177 PathToDefaultProtectionSpaceMap::iterator iter = findDefaultProtectionSpaceForURL(url);
178 if (iter == m_pathToDefaultProtectionSpaceMap.end())
179 return Credential();
180 return m_protectionSpaceToCredentialMap.get(std::make_pair(partitionName, iter->value));
181}
182
183void CredentialStorage::clearCredentials()
184{
185 m_protectionSpaceToCredentialMap.clear();
186 m_originsWithCredentials.clear();
187 m_pathToDefaultProtectionSpaceMap.clear();
188}
189
190} // namespace WebCore
191