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