[curl] Improve detecting and handling of SSL related errors
[WebKit-https.git] / Source / WebCore / platform / network / curl / ResourceHandleCurl.cpp
1 /*
2  * Copyright (C) 2004, 2006 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 #include "CachedResourceLoader.h"
32 #include "CredentialStorage.h"
33 #include "NetworkingContext.h"
34 #include "NotImplemented.h"
35 #include "ResourceHandleInternal.h"
36 #include "ResourceHandleManager.h"
37 #include "SSLHandle.h"
38
39 #if PLATFORM(WIN) && USE(CF)
40 #include <wtf/PassRefPtr.h>
41 #include <wtf/RetainPtr.h>
42 #endif
43
44 namespace WebCore {
45
46 class WebCoreSynchronousLoader : public ResourceHandleClient {
47 public:
48     WebCoreSynchronousLoader();
49
50     virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
51     virtual void didReceiveData(ResourceHandle*, const char*, int, int encodedDataLength);
52     virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
53     virtual void didFail(ResourceHandle*, const ResourceError&);
54
55     ResourceResponse resourceResponse() const { return m_response; }
56     ResourceError resourceError() const { return m_error; }
57     Vector<char> data() const { return m_data; }
58
59 private:
60     ResourceResponse m_response;
61     ResourceError m_error;
62     Vector<char> m_data;
63 };
64
65 WebCoreSynchronousLoader::WebCoreSynchronousLoader()
66 {
67 }
68
69 void WebCoreSynchronousLoader::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
70 {
71     m_response = response;
72 }
73
74 void WebCoreSynchronousLoader::didReceiveData(ResourceHandle*, const char* data, int length, int)
75 {
76     m_data.append(data, length);
77 }
78
79 void WebCoreSynchronousLoader::didFinishLoading(ResourceHandle*, double)
80 {
81 }
82
83 void WebCoreSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
84 {
85     m_error = error;
86 }
87
88 ResourceHandleInternal::~ResourceHandleInternal()
89 {
90     fastFree(m_url);
91     if (m_customHeaders)
92         curl_slist_free_all(m_customHeaders);
93 }
94
95 ResourceHandle::~ResourceHandle()
96 {
97     cancel();
98 }
99
100 bool ResourceHandle::start()
101 {
102     // The frame could be null if the ResourceHandle is not associated to any
103     // Frame, e.g. if we are downloading a file.
104     // If the frame is not null but the page is null this must be an attempted
105     // load from an unload handler, so let's just block it.
106     // If both the frame and the page are not null the context is valid.
107     if (d->m_context && !d->m_context->isValid())
108         return false;
109
110     ResourceHandleManager::sharedInstance()->add(this);
111     return true;
112 }
113
114 void ResourceHandle::cancel()
115 {
116     ResourceHandleManager::sharedInstance()->cancel(this);
117 }
118
119 void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host)
120 {
121     allowsAnyHTTPSCertificateHosts(host.lower());
122 }
123
124 #if PLATFORM(WIN) && USE(CF)
125 // FIXME:  The CFDataRef will need to be something else when
126 // building without 
127 static HashMap<String, RetainPtr<CFDataRef> >& clientCerts()
128 {
129     static HashMap<String, RetainPtr<CFDataRef> > certs;
130     return certs;
131 }
132
133 void ResourceHandle::setClientCertificate(const String& host, CFDataRef cert)
134 {
135     clientCerts().set(host.lower(), cert);
136 }
137 #endif
138
139 void ResourceHandle::platformSetDefersLoading(bool defers)
140 {
141     if (!d->m_handle)
142         return;
143
144     if (defers) {
145         CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL);
146         // If we could not defer the handle, so don't do it.
147         if (error != CURLE_OK)
148             return;
149     } else {
150         CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_CONT);
151         if (error != CURLE_OK)
152             // Restarting the handle has failed so just cancel it.
153             cancel();
154     }
155 }
156
157 bool ResourceHandle::loadsBlocked()
158 {
159     notImplemented();
160     return false;
161 }
162
163 bool ResourceHandle::shouldUseCredentialStorage()
164 {
165     return (!client() || client()->shouldUseCredentialStorage(this)) && firstRequest().url().protocolIsInHTTPFamily();
166 }
167
168 void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
169 {
170     WebCoreSynchronousLoader syncLoader;
171     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &syncLoader, true, false));
172
173     ResourceHandleManager* manager = ResourceHandleManager::sharedInstance();
174
175     manager->dispatchSynchronousJob(handle.get());
176
177     error = syncLoader.resourceError();
178     data = syncLoader.data();
179     response = syncLoader.resourceResponse();
180 }
181
182 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
183 {
184     if (!d->m_user.isNull() && !d->m_pass.isNull()) {
185         Credential credential(d->m_user, d->m_pass, CredentialPersistenceNone);
186
187         URL urlToStore;
188         if (challenge.failureResponse().httpStatusCode() == 401)
189             urlToStore = challenge.failureResponse().url();
190         CredentialStorage::set(credential, challenge.protectionSpace(), urlToStore);
191         
192         String userpass = credential.user() + ":" + credential.password();
193         curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
194
195         d->m_user = String();
196         d->m_pass = String();
197         // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
198         return;
199     }
200
201     if (shouldUseCredentialStorage()) {
202         if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
203             // The stored credential wasn't accepted, stop using it.
204             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
205             // but the observable effect should be very minor, if any.
206             CredentialStorage::remove(challenge.protectionSpace());
207         }
208
209         if (!challenge.previousFailureCount()) {
210             Credential credential = CredentialStorage::get(challenge.protectionSpace());
211             if (!credential.isEmpty() && credential != d->m_initialCredential) {
212                 ASSERT(credential.persistence() == CredentialPersistenceNone);
213                 if (challenge.failureResponse().httpStatusCode() == 401) {
214                     // Store the credential back, possibly adding it as a default for this directory.
215                     CredentialStorage::set(credential, challenge.protectionSpace(), challenge.failureResponse().url());
216                 }
217                 String userpass = credential.user() + ":" + credential.password();
218                 curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
219                 return;
220             }
221         }
222     }
223
224     d->m_currentWebChallenge = challenge;
225     
226     if (client())
227         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
228 }
229
230 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
231 {
232     if (challenge != d->m_currentWebChallenge)
233         return;
234
235     if (credential.isEmpty()) {
236         receivedRequestToContinueWithoutCredential(challenge);
237         return;
238     }
239
240     if (shouldUseCredentialStorage()) {
241         if (challenge.failureResponse().httpStatusCode() == 401) {
242             URL urlToStore = challenge.failureResponse().url();
243             CredentialStorage::set(credential, challenge.protectionSpace(), urlToStore);
244         }
245     }
246
247     String userpass = credential.user() + ":" + credential.password();
248     curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
249
250     clearAuthentication();
251 }
252
253 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
254 {
255     if (challenge != d->m_currentWebChallenge)
256         return;
257
258     String userpass = "";
259     curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
260
261     clearAuthentication();
262 }
263
264 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
265 {
266     if (challenge != d->m_currentWebChallenge)
267         return;
268
269     if (client())
270         client()->receivedCancellation(this, challenge);
271 }
272
273 } // namespace WebCore