514931614cde3c45cb3e492468e90d3ce795b080
[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) 2018 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 "ResourceHandleInternal.h"
41 #include "SharedBuffer.h"
42 #include "TextEncoding.h"
43 #include <wtf/CompletionHandler.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->setClient(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 void 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
88 void ResourceHandleCurlDelegate::cancel()
89 {
90     ASSERT(isMainThread());
91
92     releaseHandle();
93
94     if (!m_curlRequest)
95         m_curlRequest->cancel();
96 }
97
98 void ResourceHandleCurlDelegate::setDefersLoading(bool defers)
99 {
100     ASSERT(isMainThread());
101
102     if (defers == m_defersLoading)
103         return;
104
105     m_defersLoading = defers;
106
107     if (!m_curlRequest)
108         return;
109
110     if (m_defersLoading)
111         m_curlRequest->suspend();
112     else
113         m_curlRequest->resume();
114 }
115
116 void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& password)
117 {
118     ASSERT(isMainThread());
119
120     if (!m_curlRequest)
121         return;
122
123     bool isSyncRequest = m_curlRequest->isSyncRequest();
124     m_curlRequest->cancel();
125     m_curlRequest->setClient(nullptr);
126
127     m_curlRequest = createCurlRequest(m_currentRequest);
128     m_curlRequest->setUserPass(user, password);
129     m_curlRequest->start(isSyncRequest);
130 }
131
132 void ResourceHandleCurlDelegate::dispatchSynchronousJob()
133 {
134     if (m_currentRequest.url().protocolIsData()) {
135         handleDataURL();
136         return;
137     }
138
139     // If defersLoading is true and we call curl_easy_perform
140     // on a paused handle, libcURL would do the transfert anyway
141     // and we would assert so force defersLoading to be false.
142     m_defersLoading = false;
143
144     m_curlRequest = createCurlRequest(m_currentRequest);
145     m_curlRequest->start(true);
146 }
147
148 Ref<CurlRequest> ResourceHandleCurlDelegate::createCurlRequest(ResourceRequest& request)
149 {
150     ASSERT(isMainThread());
151
152     // CurlCache : append additional cache information
153     m_addedCacheValidationHeaders = false;
154
155     bool hasCacheHeaders = request.httpHeaderFields().contains(HTTPHeaderName::IfModifiedSince) || request.httpHeaderFields().contains(HTTPHeaderName::IfNoneMatch);
156     if (!hasCacheHeaders) {
157         auto& cache = CurlCacheManager::singleton();
158         URL cacheUrl = request.url();
159         cacheUrl.removeFragmentIdentifier();
160
161         if (cache.isCached(cacheUrl)) {
162             cache.addCacheEntryClient(cacheUrl, m_handle);
163
164             for (const auto& entry : cache.requestHeaders(cacheUrl))
165                 request.addHTTPHeaderField(entry.key, entry.value);
166
167             m_addedCacheValidationHeaders = true;
168         }
169     }
170
171     CurlRequest::ShouldSuspend shouldSuspend = m_defersLoading ? CurlRequest::ShouldSuspend::Yes : CurlRequest::ShouldSuspend::No;
172     return CurlRequest::create(request, this, shouldSuspend, CurlRequest::EnableMultipart::Yes);
173 }
174
175 bool ResourceHandleCurlDelegate::cancelledOrClientless()
176 {
177     if (!m_handle)
178         return true;
179
180     return !m_handle->client();
181 }
182
183 void ResourceHandleCurlDelegate::curlDidSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
184 {
185     ASSERT(isMainThread());
186
187     if (cancelledOrClientless())
188         return;
189
190     m_handle->client()->didSendData(m_handle, bytesSent, totalBytesToBeSent);
191 }
192
193 void ResourceHandleCurlDelegate::curlDidReceiveResponse(const CurlResponse& receivedResponse)
194 {
195     ASSERT(isMainThread());
196     ASSERT(!m_defersLoading);
197
198     if (cancelledOrClientless())
199         return;
200
201     m_handle->getInternal()->m_response = ResourceResponse(receivedResponse);
202
203     if (m_curlRequest)
204         m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
205
206     if (response().shouldRedirect()) {
207         willSendRequest();
208         return;
209     }
210
211     if (response().isUnauthorized()) {
212         AuthenticationChallenge challenge(receivedResponse, m_authFailureCount, response(), m_handle);
213         m_handle->didReceiveAuthenticationChallenge(challenge);
214         m_authFailureCount++;
215         return;
216     }
217
218     if (m_handle->client()) {
219         if (response().isNotModified()) {
220             URL cacheUrl = m_currentRequest.url();
221             cacheUrl.removeFragmentIdentifier();
222
223             if (CurlCacheManager::singleton().getCachedResponse(cacheUrl, response())) {
224                 if (m_addedCacheValidationHeaders) {
225                     response().setHTTPStatusCode(200);
226                     response().setHTTPStatusText("OK");
227                 }
228             }
229         }
230
231         CurlCacheManager::singleton().didReceiveResponse(*m_handle, response());
232
233         auto protectedThis = makeRef(*m_handle);
234         m_handle->didReceiveResponse(ResourceResponse(response()), [this, protectedThis = makeRef(*this)] {
235             continueAfterDidReceiveResponse();
236         });
237     }
238 }
239
240 void ResourceHandleCurlDelegate::curlDidReceiveBuffer(Ref<SharedBuffer>&& buffer)
241 {
242     ASSERT(isMainThread());
243
244     if (cancelledOrClientless())
245         return;
246
247     CurlCacheManager::singleton().didReceiveData(*m_handle, buffer->data(), buffer->size());
248     m_handle->client()->didReceiveBuffer(m_handle, WTFMove(buffer), buffer->size());
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_handle->client()) {
262         CurlCacheManager::singleton().didFinishLoading(*m_handle);
263         m_handle->client()->didFinishLoading(m_handle);
264     }
265 }
266
267 void ResourceHandleCurlDelegate::curlDidFailWithError(const ResourceError& resourceError)
268 {
269     ASSERT(isMainThread());
270
271     if (cancelledOrClientless())
272         return;
273
274     CurlCacheManager::singleton().didFail(*m_handle);
275     m_handle->client()->didFail(m_handle, resourceError);
276 }
277
278 void ResourceHandleCurlDelegate::continueDidReceiveResponse()
279 {
280     ASSERT(isMainThread());
281
282     continueAfterDidReceiveResponse();
283 }
284
285 void ResourceHandleCurlDelegate::platformContinueSynchronousDidReceiveResponse()
286 {
287     ASSERT(isMainThread());
288
289     continueAfterDidReceiveResponse();
290 }
291
292 void ResourceHandleCurlDelegate::continueAfterDidReceiveResponse()
293 {
294     ASSERT(isMainThread());
295
296     // continueDidReceiveResponse might cancel the load.
297     if (cancelledOrClientless() || !m_curlRequest)
298         return;
299
300     m_curlRequest->completeDidReceiveResponse();
301 }
302
303 bool ResourceHandleCurlDelegate::shouldRedirectAsGET(const ResourceRequest& request, bool crossOrigin)
304 {
305     if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
306         return false;
307
308     if (!request.url().protocolIsInHTTPFamily())
309         return true;
310
311     if (response().isSeeOther())
312         return true;
313
314     if ((response().isMovedPermanently() || response().isFound()) && (request.httpMethod() == "POST"))
315         return true;
316
317     if (crossOrigin && (request.httpMethod() == "DELETE"))
318         return true;
319
320     return false;
321 }
322
323 void ResourceHandleCurlDelegate::willSendRequest()
324 {
325     ASSERT(isMainThread());
326
327     static const int maxRedirects = 20;
328
329     if (m_redirectCount++ > maxRedirects) {
330         m_handle->client()->didFail(m_handle, ResourceError::httpError(CURLE_TOO_MANY_REDIRECTS, response().url()));
331         return;
332     }
333
334     String location = response().httpHeaderField(HTTPHeaderName::Location);
335     URL newURL = URL(m_handle->getInternal()->m_response.url(), location);
336     bool crossOrigin = !protocolHostAndPortAreEqual(m_firstRequest.url(), newURL);
337
338     ResourceRequest newRequest = m_firstRequest;
339     newRequest.setURL(newURL);
340
341     if (shouldRedirectAsGET(newRequest, crossOrigin)) {
342         newRequest.setHTTPMethod("GET");
343         newRequest.setHTTPBody(nullptr);
344         newRequest.clearHTTPContentType();
345     }
346
347     // Should not set Referer after a redirect from a secure resource to non-secure one.
348     if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
349         newRequest.clearHTTPReferrer();
350
351     m_user = newURL.user();
352     m_pass = newURL.pass();
353     newRequest.removeCredentials();
354
355     if (crossOrigin) {
356         // If the network layer carries over authentication headers from the original request
357         // in a cross-origin redirect, we want to clear those headers here. 
358         newRequest.clearHTTPAuthorization();
359         newRequest.clearHTTPOrigin();
360     }
361
362     ResourceResponse responseCopy = response();
363     m_handle->client()->willSendRequestAsync(m_handle, WTFMove(newRequest), WTFMove(responseCopy), [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) {
364         continueWillSendRequest(WTFMove(request));
365     });
366 }
367
368 void ResourceHandleCurlDelegate::continueWillSendRequest(ResourceRequest&& request)
369 {
370     ASSERT(isMainThread());
371
372     continueAfterWillSendRequest(WTFMove(request));
373 }
374
375 void ResourceHandleCurlDelegate::continueAfterWillSendRequest(ResourceRequest&& request)
376 {
377     ASSERT(isMainThread());
378
379     // willSendRequest might cancel the load.
380     if (cancelledOrClientless() || !m_curlRequest)
381         return;
382
383     m_currentRequest = WTFMove(request);
384
385     bool isSyncRequest = m_curlRequest->isSyncRequest();
386     m_curlRequest->cancel();
387     m_curlRequest->setClient(nullptr);
388
389     m_curlRequest = createCurlRequest(m_currentRequest);
390
391     if (protocolHostAndPortAreEqual(m_currentRequest.url(), response().url())) {
392         auto credential = getCredential(m_currentRequest, true);
393         m_curlRequest->setUserPass(credential.first, credential.second);
394     }
395
396     m_curlRequest->start(isSyncRequest);
397 }
398
399 ResourceResponse& ResourceHandleCurlDelegate::response()
400 {
401     return m_handle->getInternal()->m_response;
402 }
403
404 void ResourceHandleCurlDelegate::handleDataURL()
405 {
406     ASSERT(m_firstRequest.url().protocolIsData());
407     String url = m_firstRequest.url().string();
408
409     ASSERT(m_handle->client());
410
411     auto index = url.find(',');
412     if (index == notFound) {
413         m_handle->client()->cannotShowURL(m_handle);
414         return;
415     }
416
417     String mediaType = url.substring(5, index - 5);
418     String data = url.substring(index + 1);
419     auto originalSize = data.length();
420
421     bool base64 = mediaType.endsWithIgnoringASCIICase(";base64");
422     if (base64)
423         mediaType = mediaType.left(mediaType.length() - 7);
424
425     if (mediaType.isEmpty())
426         mediaType = "text/plain";
427
428     String mimeType = extractMIMETypeFromMediaType(mediaType);
429     String charset = extractCharsetFromMediaType(mediaType);
430
431     if (charset.isEmpty())
432         charset = "US-ASCII";
433
434     ResourceResponse response;
435     response.setMimeType(mimeType);
436     response.setTextEncodingName(charset);
437     response.setURL(m_firstRequest.url());
438
439     if (base64) {
440         data = decodeURLEscapeSequences(data);
441         m_handle->didReceiveResponse(WTFMove(response), [this, protectedThis = makeRef(*this)] {
442             continueAfterDidReceiveResponse();
443         });
444
445         // didReceiveResponse might cause the client to be deleted.
446         if (m_handle->client()) {
447             Vector<char> out;
448             if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
449                 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(out.data(), out.size()), originalSize);
450         }
451     } else {
452         TextEncoding encoding(charset);
453         data = decodeURLEscapeSequences(data, encoding);
454         m_handle->didReceiveResponse(WTFMove(response), [this, protectedThis = makeRef(*this)] {
455             continueAfterDidReceiveResponse();
456         });
457
458         // didReceiveResponse might cause the client to be deleted.
459         if (m_handle->client()) {
460             auto encodedData = encoding.encode(data, UnencodableHandling::URLEncodedEntities);
461             if (encodedData.size())
462                 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(WTFMove(encodedData)), originalSize);
463         }
464     }
465
466     if (m_handle->client())
467         m_handle->client()->didFinishLoading(m_handle);
468 }
469
470 std::pair<String, String> ResourceHandleCurlDelegate::getCredential(ResourceRequest& request, bool redirect)
471 {
472     // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
473     String partition = request.cachePartition();
474
475     if (m_shouldUseCredentialStorage) {
476         if (m_user.isEmpty() && m_pass.isEmpty()) {
477             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
478             // try and reuse the credential preemptively, as allowed by RFC 2617.
479             m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());
480         } else if (!redirect) {
481             // If there is already a protection space known for the URL, update stored credentials
482             // before sending a request. This makes it possible to implement logout by sending an
483             // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that
484             // an authentication dialog doesn't pop up).
485             CredentialStorage::defaultCredentialStorage().set(partition, Credential(m_user, m_pass, CredentialPersistenceNone), request.url());
486         }
487     }
488
489     String user = m_user;
490     String password = m_pass;
491
492     if (!m_initialCredential.isEmpty()) {
493         user = m_initialCredential.user();
494         password = m_initialCredential.password();
495     }
496
497     if (user.isEmpty() && password.isEmpty())
498         return std::pair<String, String>("", "");
499
500     return std::pair<String, String>(user, password);
501 }
502
503 } // namespace WebCore
504
505 #endif