Use asynchronous ResourceHandleClient calls for WebKit1
[WebKit-https.git] / Source / WebCore / platform / network / curl / ResourceHandleCurlDelegate.cpp
1 /*
2  * Copyright (C) 2004, 2006 Apple Inc.  All rights reserved.
3  * Copyright (C) 2005, 2006 Michael Emmel mike.emmel@gmail.com
4  * Copyright (C) 2017 Sony Interactive Entertainment Inc.
5  * All rights reserved.
6  * Copyright (C) 2017 NAVER Corp. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "ResourceHandleCurlDelegate.h"
32
33 #if USE(CURL)
34
35 #include "AuthenticationChallenge.h"
36 #include "CredentialStorage.h"
37 #include "CurlCacheManager.h"
38 #include "CurlRequest.h"
39 #include "HTTPParsers.h"
40 #include "MultipartHandle.h"
41 #include "ResourceHandleInternal.h"
42 #include "SharedBuffer.h"
43 #include "TextEncoding.h"
44 #include <wtf/text/Base64.h>
45
46 namespace WebCore {
47
48 ResourceHandleCurlDelegate::ResourceHandleCurlDelegate(ResourceHandle* handle)
49     : m_handle(handle)
50     , m_firstRequest(handle->firstRequest().isolatedCopy())
51     , m_currentRequest(handle->firstRequest().isolatedCopy())
52     , m_shouldUseCredentialStorage(handle->shouldUseCredentialStorage())
53     , m_user(handle->getInternal()->m_user.isolatedCopy())
54     , m_pass(handle->getInternal()->m_pass.isolatedCopy())
55     , m_initialCredential(handle->getInternal()->m_initialCredential)
56     , m_defersLoading(handle->getInternal()->m_defersLoading)
57 {
58
59 }
60
61 ResourceHandleCurlDelegate::~ResourceHandleCurlDelegate()
62 {
63     if (m_curlRequest)
64         m_curlRequest->setDelegate(nullptr);
65 }
66
67 bool ResourceHandleCurlDelegate::hasHandle() const
68 {
69     return !!m_handle;
70 }
71
72 void ResourceHandleCurlDelegate::releaseHandle()
73 {
74     m_handle = nullptr;
75 }
76
77 bool ResourceHandleCurlDelegate::start()
78 {
79     ASSERT(isMainThread());
80
81     auto credential = getCredential(m_currentRequest, false);
82
83     m_curlRequest = createCurlRequest(m_currentRequest);
84     m_curlRequest->setUserPass(credential.first, credential.second);
85     m_curlRequest->start();
86
87     return true;
88 }
89
90 void ResourceHandleCurlDelegate::cancel()
91 {
92     ASSERT(isMainThread());
93
94     releaseHandle();
95
96     if (!m_curlRequest)
97         m_curlRequest->cancel();
98 }
99
100 void ResourceHandleCurlDelegate::setDefersLoading(bool defers)
101 {
102     ASSERT(isMainThread());
103
104     if (defers == m_defersLoading)
105         return;
106
107     m_defersLoading = defers;
108
109     if (!m_curlRequest)
110         return;
111
112     if (m_defersLoading)
113         m_curlRequest->suspend();
114     else
115         m_curlRequest->resume();
116 }
117
118 void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& password)
119 {
120     ASSERT(isMainThread());
121
122     if (!m_curlRequest)
123         return;
124
125     bool isSyncRequest = m_curlRequest->isSyncRequest();
126     m_curlRequest->cancel();
127     m_curlRequest->setDelegate(nullptr);
128
129     m_curlRequest = createCurlRequest(m_currentRequest);
130     m_curlRequest->setUserPass(user, password);
131     m_curlRequest->start(isSyncRequest);
132 }
133
134 void ResourceHandleCurlDelegate::dispatchSynchronousJob()
135 {
136     if (m_currentRequest.url().protocolIsData()) {
137         handleDataURL();
138         return;
139     }
140
141     // If defersLoading is true and we call curl_easy_perform
142     // on a paused handle, libcURL would do the transfert anyway
143     // and we would assert so force defersLoading to be false.
144     m_defersLoading = false;
145
146     m_curlRequest = createCurlRequest(m_currentRequest);
147     m_curlRequest->start(true);
148 }
149
150 Ref<CurlRequest> ResourceHandleCurlDelegate::createCurlRequest(ResourceRequest& request)
151 {
152     ASSERT(isMainThread());
153
154     // CurlCache : append additional cache information
155     m_addedCacheValidationHeaders = false;
156
157     bool hasCacheHeaders = request.httpHeaderFields().contains(HTTPHeaderName::IfModifiedSince) || request.httpHeaderFields().contains(HTTPHeaderName::IfNoneMatch);
158     if (!hasCacheHeaders) {
159         auto& cache = CurlCacheManager::getInstance();
160         URL cacheUrl = request.url();
161         cacheUrl.removeFragmentIdentifier();
162
163         if (cache.isCached(cacheUrl)) {
164             cache.addCacheEntryClient(cacheUrl, m_handle);
165
166             for (auto entry : cache.requestHeaders(cacheUrl))
167                 request.addHTTPHeaderField(entry.key, entry.value);
168
169             m_addedCacheValidationHeaders = true;
170         }
171     }
172
173     return CurlRequest::create(request, this, m_defersLoading);
174 }
175
176 bool ResourceHandleCurlDelegate::cancelledOrClientless()
177 {
178     if (!m_handle)
179         return true;
180
181     return !m_handle->client();
182 }
183
184 void ResourceHandleCurlDelegate::curlDidReceiveResponse(const CurlResponse& receivedResponse)
185 {
186     ASSERT(isMainThread());
187     ASSERT(!m_defersLoading);
188
189     if (cancelledOrClientless())
190         return;
191
192     m_handle->getInternal()->m_response = ResourceResponse(receivedResponse);
193
194     if (m_curlRequest)
195         m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
196
197     if (response().isMultipart()) {
198         String boundary;
199         bool parsed = MultipartHandle::extractBoundary(response().httpHeaderField(HTTPHeaderName::ContentType), boundary);
200         if (parsed)
201             m_multipartHandle = std::make_unique<MultipartHandle>(m_handle, boundary);
202     }
203
204     if (response().shouldRedirect()) {
205         willSendRequest();
206         return;
207     }
208
209     if (response().isUnauthorized()) {
210         AuthenticationChallenge challenge(receivedResponse, m_authFailureCount, response(), m_handle);
211         m_handle->didReceiveAuthenticationChallenge(challenge);
212         m_authFailureCount++;
213         return;
214     }
215
216     if (m_handle->client()) {
217         if (response().isNotModified()) {
218             URL cacheUrl = m_currentRequest.url();
219             cacheUrl.removeFragmentIdentifier();
220
221             if (CurlCacheManager::getInstance().getCachedResponse(cacheUrl, response())) {
222                 if (m_addedCacheValidationHeaders) {
223                     response().setHTTPStatusCode(200);
224                     response().setHTTPStatusText("OK");
225                 }
226             }
227         }
228
229         CurlCacheManager::getInstance().didReceiveResponse(*m_handle, response());
230
231         auto protectedThis = makeRef(*m_handle);
232         m_handle->didReceiveResponse(ResourceResponse(response()));
233     }
234 }
235
236 void ResourceHandleCurlDelegate::curlDidReceiveBuffer(Ref<SharedBuffer>&& buffer)
237 {
238     ASSERT(isMainThread());
239
240     if (cancelledOrClientless())
241         return;
242
243     if (m_multipartHandle)
244         m_multipartHandle->contentReceived(buffer->data(), buffer->size());
245     else if (m_handle->client()) {
246         CurlCacheManager::getInstance().didReceiveData(*m_handle, buffer->data(), buffer->size());
247         m_handle->client()->didReceiveBuffer(m_handle, WTFMove(buffer), buffer->size());
248     }
249 }
250
251 void ResourceHandleCurlDelegate::curlDidComplete()
252 {
253     ASSERT(isMainThread());
254
255     if (cancelledOrClientless())
256         return;
257
258     if (m_curlRequest)
259         m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
260
261     if (m_multipartHandle)
262         m_multipartHandle->contentEnded();
263
264     if (m_handle->client()) {
265         CurlCacheManager::getInstance().didFinishLoading(*m_handle);
266         m_handle->client()->didFinishLoading(m_handle);
267     }
268 }
269
270 void ResourceHandleCurlDelegate::curlDidFailWithError(const ResourceError& resourceError)
271 {
272     ASSERT(isMainThread());
273
274     if (cancelledOrClientless())
275         return;
276
277     CurlCacheManager::getInstance().didFail(*m_handle);
278     m_handle->client()->didFail(m_handle, resourceError);
279 }
280
281 void ResourceHandleCurlDelegate::continueDidReceiveResponse()
282 {
283     ASSERT(isMainThread());
284
285     continueAfterDidReceiveResponse();
286 }
287
288 void ResourceHandleCurlDelegate::platformContinueSynchronousDidReceiveResponse()
289 {
290     ASSERT(isMainThread());
291
292     continueAfterDidReceiveResponse();
293 }
294
295 void ResourceHandleCurlDelegate::continueAfterDidReceiveResponse()
296 {
297     ASSERT(isMainThread());
298
299     // continueDidReceiveResponse might cancel the load.
300     if (cancelledOrClientless() || !m_curlRequest)
301         return;
302
303     m_curlRequest->completeDidReceiveResponse();
304 }
305
306 bool ResourceHandleCurlDelegate::shouldRedirectAsGET(const ResourceRequest& request, bool crossOrigin)
307 {
308     if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
309         return false;
310
311     if (!request.url().protocolIsInHTTPFamily())
312         return true;
313
314     if (response().isSeeOther())
315         return true;
316
317     if ((response().isMovedPermanently() || response().isFound()) && (request.httpMethod() == "POST"))
318         return true;
319
320     if (crossOrigin && (request.httpMethod() == "DELETE"))
321         return true;
322
323     return false;
324 }
325
326 void ResourceHandleCurlDelegate::willSendRequest()
327 {
328     ASSERT(isMainThread());
329
330     static const int maxRedirects = 20;
331
332     if (m_redirectCount++ > maxRedirects) {
333         m_handle->client()->didFail(m_handle, ResourceError::httpError(CURLE_TOO_MANY_REDIRECTS, response().url()));
334         return;
335     }
336
337     String location = response().httpHeaderField(HTTPHeaderName::Location);
338     URL newURL = URL(m_firstRequest.url(), location);
339     bool crossOrigin = !protocolHostAndPortAreEqual(m_firstRequest.url(), newURL);
340
341     ResourceRequest newRequest = m_firstRequest;
342     newRequest.setURL(newURL);
343
344     if (shouldRedirectAsGET(newRequest, crossOrigin)) {
345         newRequest.setHTTPMethod("GET");
346         newRequest.setHTTPBody(nullptr);
347         newRequest.clearHTTPContentType();
348     }
349
350     // Should not set Referer after a redirect from a secure resource to non-secure one.
351     if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
352         newRequest.clearHTTPReferrer();
353
354     m_user = newURL.user();
355     m_pass = newURL.pass();
356     newRequest.removeCredentials();
357
358     if (crossOrigin) {
359         // If the network layer carries over authentication headers from the original request
360         // in a cross-origin redirect, we want to clear those headers here. 
361         newRequest.clearHTTPAuthorization();
362         newRequest.clearHTTPOrigin();
363     }
364
365     ResourceResponse responseCopy = response();
366     m_handle->client()->willSendRequestAsync(m_handle, WTFMove(newRequest), WTFMove(responseCopy));
367 }
368
369 void ResourceHandleCurlDelegate::continueWillSendRequest(ResourceRequest&& request)
370 {
371     ASSERT(isMainThread());
372
373     continueAfterWillSendRequest(WTFMove(request));
374 }
375
376 void ResourceHandleCurlDelegate::continueAfterWillSendRequest(ResourceRequest&& request)
377 {
378     ASSERT(isMainThread());
379
380     // willSendRequest might cancel the load.
381     if (cancelledOrClientless() || !m_curlRequest)
382         return;
383
384     m_currentRequest = WTFMove(request);
385
386     bool isSyncRequest = m_curlRequest->isSyncRequest();
387     m_curlRequest->cancel();
388     m_curlRequest->setDelegate(nullptr);
389
390     m_curlRequest = createCurlRequest(m_currentRequest);
391
392     if (protocolHostAndPortAreEqual(m_currentRequest.url(), response().url())) {
393         auto credential = getCredential(m_currentRequest, true);
394         m_curlRequest->setUserPass(credential.first, credential.second);
395     }
396
397     m_curlRequest->start(isSyncRequest);
398 }
399
400 ResourceResponse& ResourceHandleCurlDelegate::response()
401 {
402     return m_handle->getInternal()->m_response;
403 }
404
405 void ResourceHandleCurlDelegate::handleDataURL()
406 {
407     ASSERT(m_firstRequest.url().protocolIsData());
408     String url = m_firstRequest.url().string();
409
410     ASSERT(m_handle->client());
411
412     auto index = url.find(',');
413     if (index == notFound) {
414         m_handle->client()->cannotShowURL(m_handle);
415         return;
416     }
417
418     String mediaType = url.substring(5, index - 5);
419     String data = url.substring(index + 1);
420     auto originalSize = data.length();
421
422     bool base64 = mediaType.endsWith(";base64", false);
423     if (base64)
424         mediaType = mediaType.left(mediaType.length() - 7);
425
426     if (mediaType.isEmpty())
427         mediaType = "text/plain";
428
429     String mimeType = extractMIMETypeFromMediaType(mediaType);
430     String charset = extractCharsetFromMediaType(mediaType);
431
432     if (charset.isEmpty())
433         charset = "US-ASCII";
434
435     ResourceResponse response;
436     response.setMimeType(mimeType);
437     response.setTextEncodingName(charset);
438     response.setURL(m_firstRequest.url());
439
440     if (base64) {
441         data = decodeURLEscapeSequences(data);
442         m_handle->didReceiveResponse(WTFMove(response));
443
444         // didReceiveResponse might cause the client to be deleted.
445         if (m_handle->client()) {
446             Vector<char> out;
447             if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
448                 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(out.data(), out.size()), originalSize);
449         }
450     } else {
451         TextEncoding encoding(charset);
452         data = decodeURLEscapeSequences(data, encoding);
453         m_handle->didReceiveResponse(WTFMove(response));
454
455         // didReceiveResponse might cause the client to be deleted.
456         if (m_handle->client()) {
457             CString encodedData = encoding.encode(data, URLEncodedEntitiesForUnencodables);
458             if (encodedData.length())
459                 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(encodedData.data(), encodedData.length()), originalSize);
460         }
461     }
462
463     if (m_handle->client())
464         m_handle->client()->didFinishLoading(m_handle);
465 }
466
467 std::pair<String, String> ResourceHandleCurlDelegate::getCredential(ResourceRequest& request, bool redirect)
468 {
469     // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
470     String partition = request.cachePartition();
471
472     if (m_shouldUseCredentialStorage) {
473         if (m_user.isEmpty() && m_pass.isEmpty()) {
474             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
475             // try and reuse the credential preemptively, as allowed by RFC 2617.
476             m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());
477         } else if (!redirect) {
478             // If there is already a protection space known for the URL, update stored credentials
479             // before sending a request. This makes it possible to implement logout by sending an
480             // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that
481             // an authentication dialog doesn't pop up).
482             CredentialStorage::defaultCredentialStorage().set(partition, Credential(m_user, m_pass, CredentialPersistenceNone), request.url());
483         }
484     }
485
486     String user = m_user;
487     String password = m_pass;
488
489     if (!m_initialCredential.isEmpty()) {
490         user = m_initialCredential.user();
491         password = m_initialCredential.password();
492     }
493
494     if (user.isEmpty() && password.isEmpty())
495         return std::pair<String, String>("", "");
496
497     return std::pair<String, String>(user, password);
498 }
499
500 } // namespace WebCore
501
502 #endif