1/*
2 * Copyright (C) 2004, 2006 Apple Inc.  All rights reserved.
3 * Copyright (C) 2005, 2006 Michael Emmel mike.emmel@gmail.com
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "ResourceHandle.h"
30
31#if USE(CURL)
32
33#include "CachedResourceLoader.h"
34#include "CredentialStorage.h"
35#include "FileSystem.h"
36#include "Logging.h"
37#include "NetworkingContext.h"
38#include "NotImplemented.h"
39#include "ResourceHandleInternal.h"
40#include "ResourceHandleManager.h"
41#include "SSLHandle.h"
42
43#if PLATFORM(WIN) && USE(CF)
44#include <wtf/PassRefPtr.h>
45#include <wtf/RetainPtr.h>
46#endif
47
48namespace WebCore {
49
50class WebCoreSynchronousLoader : public ResourceHandleClient {
51public:
52    WebCoreSynchronousLoader();
53
54    virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
55    virtual void didReceiveData(ResourceHandle*, const char*, unsigned, int encodedDataLength);
56    virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
57    virtual void didFail(ResourceHandle*, const ResourceError&);
58
59    ResourceResponse resourceResponse() const { return m_response; }
60    ResourceError resourceError() const { return m_error; }
61    Vector<char> data() const { return m_data; }
62
63private:
64    ResourceResponse m_response;
65    ResourceError m_error;
66    Vector<char> m_data;
67};
68
69WebCoreSynchronousLoader::WebCoreSynchronousLoader()
70{
71}
72
73void WebCoreSynchronousLoader::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
74{
75    m_response = response;
76}
77
78void WebCoreSynchronousLoader::didReceiveData(ResourceHandle*, const char* data, unsigned length, int)
79{
80    m_data.append(data, length);
81}
82
83void WebCoreSynchronousLoader::didFinishLoading(ResourceHandle*, double)
84{
85}
86
87void WebCoreSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
88{
89    m_error = error;
90}
91
92ResourceHandleInternal::~ResourceHandleInternal()
93{
94    fastFree(m_url);
95    if (m_customHeaders)
96        curl_slist_free_all(m_customHeaders);
97}
98
99ResourceHandle::~ResourceHandle()
100{
101    cancel();
102}
103
104bool ResourceHandle::start()
105{
106    // The frame could be null if the ResourceHandle is not associated to any
107    // Frame, e.g. if we are downloading a file.
108    // If the frame is not null but the page is null this must be an attempted
109    // load from an unload handler, so let's just block it.
110    // If both the frame and the page are not null the context is valid.
111    if (d->m_context && !d->m_context->isValid())
112        return false;
113
114    ResourceHandleManager::sharedInstance()->add(this);
115    return true;
116}
117
118void ResourceHandle::cancel()
119{
120    ResourceHandleManager::sharedInstance()->cancel(this);
121}
122
123void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host)
124{
125    allowsAnyHTTPSCertificateHosts(host.lower());
126}
127
128void ResourceHandle::setClientCertificateInfo(const String& host, const String& certificate, const String& key)
129{
130    if (fileExists(certificate))
131        addAllowedClientCertificate(host, certificate, key);
132    else
133        LOG(Network, "Invalid client certificate file: %s!\n", certificate.latin1().data());
134}
135
136#if PLATFORM(WIN) && USE(CF)
137// FIXME:  The CFDataRef will need to be something else when
138// building without
139static HashMap<String, RetainPtr<CFDataRef> >& clientCerts()
140{
141    static HashMap<String, RetainPtr<CFDataRef> > certs;
142    return certs;
143}
144
145void ResourceHandle::setClientCertificate(const String& host, CFDataRef cert)
146{
147    clientCerts().set(host.lower(), cert);
148}
149#endif
150
151void ResourceHandle::platformSetDefersLoading(bool defers)
152{
153    if (!d->m_handle)
154        return;
155
156    if (defers) {
157        CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL);
158        // If we could not defer the handle, so don't do it.
159        if (error != CURLE_OK)
160            return;
161    } else {
162        CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_CONT);
163        if (error != CURLE_OK)
164            // Restarting the handle has failed so just cancel it.
165            cancel();
166    }
167}
168
169bool ResourceHandle::loadsBlocked()
170{
171    notImplemented();
172    return false;
173}
174
175bool ResourceHandle::shouldUseCredentialStorage()
176{
177    return (!client() || client()->shouldUseCredentialStorage(this)) && firstRequest().url().protocolIsInHTTPFamily();
178}
179
180void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
181{
182    WebCoreSynchronousLoader syncLoader;
183    RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &syncLoader, true, false));
184
185    ResourceHandleManager* manager = ResourceHandleManager::sharedInstance();
186
187    manager->dispatchSynchronousJob(handle.get());
188
189    error = syncLoader.resourceError();
190    data = syncLoader.data();
191    response = syncLoader.resourceResponse();
192}
193
194void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
195{
196    if (!d->m_user.isNull() && !d->m_pass.isNull()) {
197        Credential credential(d->m_user, d->m_pass, CredentialPersistenceNone);
198
199        URL urlToStore;
200        if (challenge.failureResponse().httpStatusCode() == 401)
201            urlToStore = challenge.failureResponse().url();
202        CredentialStorage::set(credential, challenge.protectionSpace(), urlToStore);
203
204        String userpass = credential.user() + ":" + credential.password();
205        curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
206
207        d->m_user = String();
208        d->m_pass = String();
209        // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
210        return;
211    }
212
213    if (shouldUseCredentialStorage()) {
214        if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
215            // The stored credential wasn't accepted, stop using it.
216            // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
217            // but the observable effect should be very minor, if any.
218            CredentialStorage::remove(challenge.protectionSpace());
219        }
220
221        if (!challenge.previousFailureCount()) {
222            Credential credential = CredentialStorage::get(challenge.protectionSpace());
223            if (!credential.isEmpty() && credential != d->m_initialCredential) {
224                ASSERT(credential.persistence() == CredentialPersistenceNone);
225                if (challenge.failureResponse().httpStatusCode() == 401) {
226                    // Store the credential back, possibly adding it as a default for this directory.
227                    CredentialStorage::set(credential, challenge.protectionSpace(), challenge.failureResponse().url());
228                }
229                String userpass = credential.user() + ":" + credential.password();
230                curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
231                return;
232            }
233        }
234    }
235
236    d->m_currentWebChallenge = challenge;
237
238    if (client())
239        client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
240}
241
242void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
243{
244    if (challenge != d->m_currentWebChallenge)
245        return;
246
247    if (credential.isEmpty()) {
248        receivedRequestToContinueWithoutCredential(challenge);
249        return;
250    }
251
252    if (shouldUseCredentialStorage()) {
253        if (challenge.failureResponse().httpStatusCode() == 401) {
254            URL urlToStore = challenge.failureResponse().url();
255            CredentialStorage::set(credential, challenge.protectionSpace(), urlToStore);
256        }
257    }
258
259    String userpass = credential.user() + ":" + credential.password();
260    curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
261
262    clearAuthentication();
263}
264
265void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
266{
267    if (challenge != d->m_currentWebChallenge)
268        return;
269
270    String userpass = "";
271    curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
272
273    clearAuthentication();
274}
275
276void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
277{
278    if (challenge != d->m_currentWebChallenge)
279        return;
280
281    if (client())
282        client()->receivedCancellation(this, challenge);
283}
284
285void ResourceHandle::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge&)
286{
287    ASSERT_NOT_REACHED();
288}
289
290void ResourceHandle::receivedChallengeRejection(const AuthenticationChallenge&)
291{
292    ASSERT_NOT_REACHED();
293}
294
295} // namespace WebCore
296
297#endif
298