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