Modernize some aspects of text codecs, eliminate WebKit use of strcasecmp
[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 bool 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     return true;
89 }
90
91 void ResourceHandleCurlDelegate::cancel()
92 {
93     ASSERT(isMainThread());
94
95     releaseHandle();
96
97     if (!m_curlRequest)
98         m_curlRequest->cancel();
99 }
100
101 void ResourceHandleCurlDelegate::setDefersLoading(bool defers)
102 {
103     ASSERT(isMainThread());
104
105     if (defers == m_defersLoading)
106         return;
107
108     m_defersLoading = defers;
109
110     if (!m_curlRequest)
111         return;
112
113     if (m_defersLoading)
114         m_curlRequest->suspend();
115     else
116         m_curlRequest->resume();
117 }
118
119 void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& password)
120 {
121     ASSERT(isMainThread());
122
123     if (!m_curlRequest)
124         return;
125
126     bool isSyncRequest = m_curlRequest->isSyncRequest();
127     m_curlRequest->cancel();
128     m_curlRequest->setClient(nullptr);
129
130     m_curlRequest = createCurlRequest(m_currentRequest);
131     m_curlRequest->setUserPass(user, password);
132     m_curlRequest->start(isSyncRequest);
133 }
134
135 void ResourceHandleCurlDelegate::dispatchSynchronousJob()
136 {
137     if (m_currentRequest.url().protocolIsData()) {
138         handleDataURL();
139         return;
140     }
141
142     // If defersLoading is true and we call curl_easy_perform
143     // on a paused handle, libcURL would do the transfert anyway
144     // and we would assert so force defersLoading to be false.
145     m_defersLoading = false;
146
147     m_curlRequest = createCurlRequest(m_currentRequest);
148     m_curlRequest->start(true);
149 }
150
151 Ref<CurlRequest> ResourceHandleCurlDelegate::createCurlRequest(ResourceRequest& request)
152 {
153     ASSERT(isMainThread());
154
155     // CurlCache : append additional cache information
156     m_addedCacheValidationHeaders = false;
157
158     bool hasCacheHeaders = request.httpHeaderFields().contains(HTTPHeaderName::IfModifiedSince) || request.httpHeaderFields().contains(HTTPHeaderName::IfNoneMatch);
159     if (!hasCacheHeaders) {
160         auto& cache = CurlCacheManager::singleton();
161         URL cacheUrl = request.url();
162         cacheUrl.removeFragmentIdentifier();
163
164         if (cache.isCached(cacheUrl)) {
165             cache.addCacheEntryClient(cacheUrl, m_handle);
166
167             for (const auto& entry : cache.requestHeaders(cacheUrl))
168                 request.addHTTPHeaderField(entry.key, entry.value);
169
170             m_addedCacheValidationHeaders = true;
171         }
172     }
173
174     return CurlRequest::create(request, this, m_defersLoading);
175 }
176
177 bool ResourceHandleCurlDelegate::cancelledOrClientless()
178 {
179     if (!m_handle)
180         return true;
181
182     return !m_handle->client();
183 }
184
185 void ResourceHandleCurlDelegate::curlDidReceiveResponse(const CurlResponse& receivedResponse)
186 {
187     ASSERT(isMainThread());
188     ASSERT(!m_defersLoading);
189
190     if (cancelledOrClientless())
191         return;
192
193     m_handle->getInternal()->m_response = ResourceResponse(receivedResponse);
194
195     if (m_curlRequest)
196         m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
197
198     if (response().isMultipart()) {
199         String boundary;
200         bool parsed = MultipartHandle::extractBoundary(response().httpHeaderField(HTTPHeaderName::ContentType), boundary);
201         if (parsed)
202             m_multipartHandle = std::make_unique<MultipartHandle>(m_handle, boundary);
203     }
204
205     if (response().shouldRedirect()) {
206         willSendRequest();
207         return;
208     }
209
210     if (response().isUnauthorized()) {
211         AuthenticationChallenge challenge(receivedResponse, m_authFailureCount, response(), m_handle);
212         m_handle->didReceiveAuthenticationChallenge(challenge);
213         m_authFailureCount++;
214         return;
215     }
216
217     if (m_handle->client()) {
218         if (response().isNotModified()) {
219             URL cacheUrl = m_currentRequest.url();
220             cacheUrl.removeFragmentIdentifier();
221
222             if (CurlCacheManager::singleton().getCachedResponse(cacheUrl, response())) {
223                 if (m_addedCacheValidationHeaders) {
224                     response().setHTTPStatusCode(200);
225                     response().setHTTPStatusText("OK");
226                 }
227             }
228         }
229
230         CurlCacheManager::singleton().didReceiveResponse(*m_handle, response());
231
232         auto protectedThis = makeRef(*m_handle);
233         m_handle->didReceiveResponse(ResourceResponse(response()));
234     }
235 }
236
237 void ResourceHandleCurlDelegate::curlDidReceiveBuffer(Ref<SharedBuffer>&& buffer)
238 {
239     ASSERT(isMainThread());
240
241     if (cancelledOrClientless())
242         return;
243
244     if (m_multipartHandle)
245         m_multipartHandle->contentReceived(buffer->data(), buffer->size());
246     else if (m_handle->client()) {
247         CurlCacheManager::singleton().didReceiveData(*m_handle, buffer->data(), buffer->size());
248         m_handle->client()->didReceiveBuffer(m_handle, WTFMove(buffer), buffer->size());
249     }
250 }
251
252 void ResourceHandleCurlDelegate::curlDidComplete()
253 {
254     ASSERT(isMainThread());
255
256     if (cancelledOrClientless())
257         return;
258
259     if (m_curlRequest)
260         m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
261
262     if (m_multipartHandle)
263         m_multipartHandle->contentEnded();
264
265     if (m_handle->client()) {
266         CurlCacheManager::singleton().didFinishLoading(*m_handle);
267         m_handle->client()->didFinishLoading(m_handle);
268     }
269 }
270
271 void ResourceHandleCurlDelegate::curlDidFailWithError(const ResourceError& resourceError)
272 {
273     ASSERT(isMainThread());
274
275     if (cancelledOrClientless())
276         return;
277
278     CurlCacheManager::singleton().didFail(*m_handle);
279     m_handle->client()->didFail(m_handle, resourceError);
280 }
281
282 void ResourceHandleCurlDelegate::continueDidReceiveResponse()
283 {
284     ASSERT(isMainThread());
285
286     continueAfterDidReceiveResponse();
287 }
288
289 void ResourceHandleCurlDelegate::platformContinueSynchronousDidReceiveResponse()
290 {
291     ASSERT(isMainThread());
292
293     continueAfterDidReceiveResponse();
294 }
295
296 void ResourceHandleCurlDelegate::continueAfterDidReceiveResponse()
297 {
298     ASSERT(isMainThread());
299
300     // continueDidReceiveResponse might cancel the load.
301     if (cancelledOrClientless() || !m_curlRequest)
302         return;
303
304     m_curlRequest->completeDidReceiveResponse();
305 }
306
307 bool ResourceHandleCurlDelegate::shouldRedirectAsGET(const ResourceRequest& request, bool crossOrigin)
308 {
309     if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
310         return false;
311
312     if (!request.url().protocolIsInHTTPFamily())
313         return true;
314
315     if (response().isSeeOther())
316         return true;
317
318     if ((response().isMovedPermanently() || response().isFound()) && (request.httpMethod() == "POST"))
319         return true;
320
321     if (crossOrigin && (request.httpMethod() == "DELETE"))
322         return true;
323
324     return false;
325 }
326
327 void ResourceHandleCurlDelegate::willSendRequest()
328 {
329     ASSERT(isMainThread());
330
331     static const int maxRedirects = 20;
332
333     if (m_redirectCount++ > maxRedirects) {
334         m_handle->client()->didFail(m_handle, ResourceError::httpError(CURLE_TOO_MANY_REDIRECTS, response().url()));
335         return;
336     }
337
338     String location = response().httpHeaderField(HTTPHeaderName::Location);
339     URL newURL = URL(m_firstRequest.url(), location);
340     bool crossOrigin = !protocolHostAndPortAreEqual(m_firstRequest.url(), newURL);
341
342     ResourceRequest newRequest = m_firstRequest;
343     newRequest.setURL(newURL);
344
345     if (shouldRedirectAsGET(newRequest, crossOrigin)) {
346         newRequest.setHTTPMethod("GET");
347         newRequest.setHTTPBody(nullptr);
348         newRequest.clearHTTPContentType();
349     }
350
351     // Should not set Referer after a redirect from a secure resource to non-secure one.
352     if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
353         newRequest.clearHTTPReferrer();
354
355     m_user = newURL.user();
356     m_pass = newURL.pass();
357     newRequest.removeCredentials();
358
359     if (crossOrigin) {
360         // If the network layer carries over authentication headers from the original request
361         // in a cross-origin redirect, we want to clear those headers here. 
362         newRequest.clearHTTPAuthorization();
363         newRequest.clearHTTPOrigin();
364     }
365
366     ResourceResponse responseCopy = response();
367     m_handle->client()->willSendRequestAsync(m_handle, WTFMove(newRequest), WTFMove(responseCopy), [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) {
368         continueWillSendRequest(WTFMove(request));
369     });
370 }
371
372 void ResourceHandleCurlDelegate::continueWillSendRequest(ResourceRequest&& request)
373 {
374     ASSERT(isMainThread());
375
376     continueAfterWillSendRequest(WTFMove(request));
377 }
378
379 void ResourceHandleCurlDelegate::continueAfterWillSendRequest(ResourceRequest&& request)
380 {
381     ASSERT(isMainThread());
382
383     // willSendRequest might cancel the load.
384     if (cancelledOrClientless() || !m_curlRequest)
385         return;
386
387     m_currentRequest = WTFMove(request);
388
389     bool isSyncRequest = m_curlRequest->isSyncRequest();
390     m_curlRequest->cancel();
391     m_curlRequest->setClient(nullptr);
392
393     m_curlRequest = createCurlRequest(m_currentRequest);
394
395     if (protocolHostAndPortAreEqual(m_currentRequest.url(), response().url())) {
396         auto credential = getCredential(m_currentRequest, true);
397         m_curlRequest->setUserPass(credential.first, credential.second);
398     }
399
400     m_curlRequest->start(isSyncRequest);
401 }
402
403 ResourceResponse& ResourceHandleCurlDelegate::response()
404 {
405     return m_handle->getInternal()->m_response;
406 }
407
408 void ResourceHandleCurlDelegate::handleDataURL()
409 {
410     ASSERT(m_firstRequest.url().protocolIsData());
411     String url = m_firstRequest.url().string();
412
413     ASSERT(m_handle->client());
414
415     auto index = url.find(',');
416     if (index == notFound) {
417         m_handle->client()->cannotShowURL(m_handle);
418         return;
419     }
420
421     String mediaType = url.substring(5, index - 5);
422     String data = url.substring(index + 1);
423     auto originalSize = data.length();
424
425     bool base64 = mediaType.endsWithIgnoringASCIICase(";base64");
426     if (base64)
427         mediaType = mediaType.left(mediaType.length() - 7);
428
429     if (mediaType.isEmpty())
430         mediaType = "text/plain";
431
432     String mimeType = extractMIMETypeFromMediaType(mediaType);
433     String charset = extractCharsetFromMediaType(mediaType);
434
435     if (charset.isEmpty())
436         charset = "US-ASCII";
437
438     ResourceResponse response;
439     response.setMimeType(mimeType);
440     response.setTextEncodingName(charset);
441     response.setURL(m_firstRequest.url());
442
443     if (base64) {
444         data = decodeURLEscapeSequences(data);
445         m_handle->didReceiveResponse(WTFMove(response));
446
447         // didReceiveResponse might cause the client to be deleted.
448         if (m_handle->client()) {
449             Vector<char> out;
450             if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
451                 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(out.data(), out.size()), originalSize);
452         }
453     } else {
454         TextEncoding encoding(charset);
455         data = decodeURLEscapeSequences(data, encoding);
456         m_handle->didReceiveResponse(WTFMove(response));
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