[Curl] Move error generation task into ResourceError
[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 "CredentialStorage.h"
36 #include "CurlCacheManager.h"
37 #include "HTTPParsers.h"
38 #include "MIMETypeRegistry.h"
39 #include "MultipartHandle.h"
40 #include "ResourceHandle.h"
41 #include "ResourceHandleInternal.h"
42 #include "TextEncoding.h"
43 #include "ThreadSafeDataBuffer.h"
44 #include "URL.h"
45 #include <wtf/MainThread.h>
46 #include <wtf/text/Base64.h>
47
48 namespace WebCore {
49
50 ResourceHandleCurlDelegate::ResourceHandleCurlDelegate(ResourceHandle* handle)
51     : m_handle(handle)
52     , m_formDataStream(handle)
53     , m_firstRequest(handle->firstRequest().isolatedCopy())
54     , m_customHTTPHeaderFields(m_firstRequest.httpHeaderFields().isolatedCopy())
55     , m_shouldUseCredentialStorage(handle->shouldUseCredentialStorage())
56     , m_user(handle->getInternal()->m_user.isolatedCopy())
57     , m_pass(handle->getInternal()->m_pass.isolatedCopy())
58     , m_initialCredential(handle->getInternal()->m_initialCredential)
59     , m_defersLoading(handle->getInternal()->m_defersLoading)
60 {
61     const URL& url = m_firstRequest.url();
62
63     if (url.isLocalFile()) {
64         // Determine the MIME type based on the path.
65         response().setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));
66     }
67
68     if (m_customHTTPHeaderFields.size()) {
69         auto& cache = CurlCacheManager::getInstance();
70         bool hasCacheHeaders = m_customHTTPHeaderFields.contains(HTTPHeaderName::IfModifiedSince) || m_customHTTPHeaderFields.contains(HTTPHeaderName::IfNoneMatch);
71         if (!hasCacheHeaders && cache.isCached(url)) {
72             cache.addCacheEntryClient(url, m_handle);
73             // append additional cache information
74             for (auto entry : cache.requestHeaders(url))
75                 m_customHTTPHeaderFields.set(entry.key, entry.value);
76             m_addedCacheValidationHeaders = true;
77         }
78     }
79
80     setupAuthentication();
81 }
82
83 ResourceHandleCurlDelegate::~ResourceHandleCurlDelegate()
84 {
85 }
86
87 bool ResourceHandleCurlDelegate::hasHandle() const
88 {
89     return !!m_handle;
90 }
91
92 void ResourceHandleCurlDelegate::releaseHandle()
93 {
94     m_handle = nullptr;
95 }
96
97 bool ResourceHandleCurlDelegate::start()
98 {
99     m_job = CurlJobManager::singleton().add(m_curlHandle, *this);
100     return !!m_job;
101 }
102
103 void ResourceHandleCurlDelegate::cancel()
104 {
105     releaseHandle();
106     CurlJobManager::singleton().cancel(m_job);
107 }
108
109 void ResourceHandleCurlDelegate::setDefersLoading(bool defers)
110 {
111     if (defers == m_defersLoading)
112         return;
113
114     m_defersLoading = defers;
115
116     auto action = [protectedThis = makeRef(*this)]() {
117         if (protectedThis->m_defersLoading) {
118             CURLcode error = protectedThis->m_curlHandle.pause(CURLPAUSE_ALL);
119             // If we could not defer the handle, so don't do it.
120             if (error != CURLE_OK)
121                 return;
122         } else {
123             CURLcode error = protectedThis->m_curlHandle.pause(CURLPAUSE_CONT);
124             if (error != CURLE_OK) {
125                 // Restarting the handle has failed so just cancel it.
126                 protectedThis->m_handle->cancel();
127             }
128         }
129     };
130
131     CurlJobManager::singleton().callOnJobThread(WTFMove(action));
132 }
133
134 void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& pass)
135 {
136     auto action = [protectedThis = makeRef(*this), user = user.isolatedCopy(), pass = pass.isolatedCopy()]() {
137         protectedThis->m_user = user;
138         protectedThis->m_pass = pass;
139         protectedThis->m_curlHandle.setHttpAuthUserPass(user, pass);
140     };
141
142     CurlJobManager::singleton().callOnJobThread(WTFMove(action));
143 }
144
145 void ResourceHandleCurlDelegate::dispatchSynchronousJob()
146 {
147     URL kurl = m_firstRequest.url();
148
149     if (kurl.protocolIsData()) {
150         handleDataURL();
151         return;
152     }
153
154     // If defersLoading is true and we call curl_easy_perform
155     // on a paused handle, libcURL would do the transfert anyway
156     // and we would assert so force defersLoading to be false.
157     m_defersLoading = false;
158
159     setupRequest();
160
161     // curl_easy_perform blocks until the transfer is finished.
162     CURLcode ret = m_curlHandle.perform();
163
164     if (ret != CURLE_OK)
165         notifyFail();
166     else
167         notifyFinish();
168 }
169
170 void ResourceHandleCurlDelegate::retain()
171 {
172     ref();
173 }
174
175 void ResourceHandleCurlDelegate::release()
176 {
177     deref();
178 }
179
180 void ResourceHandleCurlDelegate::setupRequest()
181 {
182     CurlContext& context = CurlContext::singleton();
183
184     URL url = m_firstRequest.url();
185
186     // Remove any fragment part, otherwise curl will send it as part of the request.
187     url.removeFragmentIdentifier();
188
189     String urlString = url.string();
190
191     m_curlHandle.initialize();
192
193     if (url.isLocalFile()) {
194         // Remove any query part sent to a local file.
195         if (!url.query().isEmpty()) {
196             // By setting the query to a null string it'll be removed.
197             url.setQuery(String());
198             urlString = url.string();
199         }
200     }
201
202     if (m_defersLoading) {
203         CURLcode error = m_curlHandle.pause(CURLPAUSE_ALL);
204         // If we did not pause the handle, we would ASSERT in the
205         // header callback. So just assert here.
206         ASSERT_UNUSED(error, error == CURLE_OK);
207     }
208
209 #ifndef NDEBUG
210     m_curlHandle.enableVerboseIfUsed();
211     m_curlHandle.enableStdErrIfUsed();
212 #endif
213
214     auto& sslHandle = CurlContext::singleton().sslHandle();
215
216     m_curlHandle.setSslVerifyPeer(CurlHandle::VerifyPeerEnable);
217     m_curlHandle.setSslVerifyHost(CurlHandle::VerifyHostStrictNameCheck);
218     m_curlHandle.setPrivateData(this);
219     m_curlHandle.setWriteCallbackFunction(didReceiveDataCallback, this);
220     m_curlHandle.setHeaderCallbackFunction(didReceiveHeaderCallback, this);
221     m_curlHandle.enableAutoReferer();
222     m_curlHandle.enableFollowLocation();
223     m_curlHandle.enableHttpAuthentication(CURLAUTH_ANY);
224     m_curlHandle.enableShareHandle();
225     m_curlHandle.enableTimeout();
226     m_curlHandle.enableAllowedProtocols();
227
228     auto sslClientCertificate = sslHandle.getSSLClientCertificate(url.host());
229     if (sslClientCertificate) {
230         m_curlHandle.setSslCert(sslClientCertificate->first.utf8().data());
231         m_curlHandle.setSslCertType("P12");
232         m_curlHandle.setSslKeyPassword(sslClientCertificate->second.utf8().data());
233     }
234
235     if (sslHandle.shouldIgnoreSSLErrors())
236         m_curlHandle.setSslVerifyPeer(CurlHandle::VerifyPeerDisable);
237     else
238         m_curlHandle.setSslCtxCallbackFunction(willSetupSslCtxCallback, this);
239
240     m_curlHandle.setCACertPath(sslHandle.getCACertPath());
241
242     m_curlHandle.enableAcceptEncoding();
243     m_curlHandle.setUrl(urlString);
244     m_curlHandle.enableCookieJarIfExists();
245
246     if (m_customHTTPHeaderFields.size())
247         m_curlHandle.appendRequestHeaders(m_customHTTPHeaderFields);
248
249     String method = m_firstRequest.httpMethod();
250     if ("GET" == method)
251         m_curlHandle.enableHttpGetRequest();
252     else if ("POST" == method)
253         setupPOST();
254     else if ("PUT" == method)
255         setupPUT();
256     else if ("HEAD" == method)
257         m_curlHandle.enableHttpHeadRequest();
258     else {
259         m_curlHandle.setHttpCustomRequest(method);
260         setupPUT();
261     }
262
263     m_curlHandle.enableRequestHeaders();
264
265     applyAuthentication();
266
267     m_curlHandle.enableProxyIfExists();
268 }
269
270 void ResourceHandleCurlDelegate::notifyFinish()
271 {
272     NetworkLoadMetrics networkLoadMetrics = getNetworkLoadMetrics();
273
274     if (isMainThread())
275         didFinish(networkLoadMetrics);
276     else {
277         callOnMainThread([protectedThis = makeRef(*this), metrics = networkLoadMetrics.isolatedCopy()] {
278             if (!protectedThis->m_handle)
279                 return;
280             protectedThis->didFinish(metrics);
281         });
282     }
283 }
284
285 void ResourceHandleCurlDelegate::notifyFail()
286 {
287     ResourceError resourceError = ResourceError::httpError(m_curlHandle.errorCode(), m_firstRequest.url());
288     if (m_sslVerifier.sslErrors())
289         resourceError.setSslErrors(m_sslVerifier.sslErrors());
290
291     if (isMainThread())
292         didFail(resourceError);
293     else {
294         callOnMainThread([protectedThis = makeRef(*this), error = resourceError.isolatedCopy()] {
295             if (!protectedThis->m_handle)
296                 return;
297             protectedThis->didFail(error);
298         });
299     }
300 }
301
302 ResourceResponse& ResourceHandleCurlDelegate::response()
303 {
304     return m_handle->getInternal()->m_response;
305 }
306
307 void ResourceHandleCurlDelegate::setupAuthentication()
308 {
309     // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
310     String partition = m_firstRequest.cachePartition();
311
312     if (m_shouldUseCredentialStorage) {
313         if (m_user.isEmpty() && m_pass.isEmpty()) {
314             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
315             // try and reuse the credential preemptively, as allowed by RFC 2617.
316             m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, m_firstRequest.url());
317         } else {
318             // If there is already a protection space known for the URL, update stored credentials
319             // before sending a request. This makes it possible to implement logout by sending an
320             // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that
321             // an authentication dialog doesn't pop up).
322             CredentialStorage::defaultCredentialStorage().set(partition, Credential(m_user, m_pass, CredentialPersistenceNone), m_firstRequest.url());
323         }
324     }
325 }
326
327 static void removeLeadingAndTrailingQuotes(String& value)
328 {
329     unsigned length = value.length();
330     if (value.startsWith('"') && value.endsWith('"') && length > 1)
331         value = value.substring(1, length - 2);
332 }
333
334 bool ResourceHandleCurlDelegate::getProtectionSpace(const ResourceResponse& response, ProtectionSpace& protectionSpace)
335 {
336     auto port = m_curlHandle.getPrimaryPort();
337     if (!port)
338         return false;
339
340     auto availableAuth = m_curlHandle.getHttpAuthAvail();
341     if (!availableAuth)
342         return false;
343
344     auto url = m_curlHandle.getEffectiveURL();
345     if (!url.isValid())
346         return false;
347
348     String host = url.host();
349     StringView protocol = url.protocol();
350
351     String realm;
352
353     const String authHeader = response.httpHeaderField(HTTPHeaderName::Authorization);
354     const String realmString = "realm=";
355     int realmPos = authHeader.find(realmString);
356     if (realmPos > 0) {
357         realm = authHeader.substring(realmPos + realmString.length());
358         realm = realm.left(realm.find(','));
359         removeLeadingAndTrailingQuotes(realm);
360     }
361
362     ProtectionSpaceServerType serverType = ProtectionSpaceServerHTTP;
363     if (protocol == "https")
364         serverType = ProtectionSpaceServerHTTPS;
365
366     ProtectionSpaceAuthenticationScheme authScheme = ProtectionSpaceAuthenticationSchemeUnknown;
367
368     if (*availableAuth & CURLAUTH_BASIC)
369         authScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
370     if (*availableAuth & CURLAUTH_DIGEST)
371         authScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
372     if (*availableAuth & CURLAUTH_GSSNEGOTIATE)
373         authScheme = ProtectionSpaceAuthenticationSchemeNegotiate;
374     if (*availableAuth & CURLAUTH_NTLM)
375         authScheme = ProtectionSpaceAuthenticationSchemeNTLM;
376
377     protectionSpace = ProtectionSpace(host, *port, serverType, realm, authScheme);
378
379     return true;
380 }
381
382 inline static bool isHttpInfo(int statusCode)
383 {
384     return 100 <= statusCode && statusCode < 200;
385 }
386
387 void ResourceHandleCurlDelegate::didReceiveAllHeaders(long httpCode, long long contentLength)
388 {
389     ASSERT(isMainThread());
390
391     response().setURL(m_curlHandle.getEffectiveURL());
392
393     response().setExpectedContentLength(contentLength);
394     response().setHTTPStatusCode(httpCode);
395     response().setMimeType(extractMIMETypeFromMediaType(response().httpHeaderField(HTTPHeaderName::ContentType)).convertToASCIILowercase());
396     response().setTextEncodingName(extractCharsetFromMediaType(response().httpHeaderField(HTTPHeaderName::ContentType)));
397
398     if (response().isMultipart()) {
399         String boundary;
400         bool parsed = MultipartHandle::extractBoundary(response().httpHeaderField(HTTPHeaderName::ContentType), boundary);
401         if (parsed)
402             m_multipartHandle = std::make_unique<MultipartHandle>(m_handle, boundary);
403     }
404
405     // HTTP redirection
406     if (response().isRedirection()) {
407         String location = response().httpHeaderField(HTTPHeaderName::Location);
408         if (!location.isEmpty()) {
409             URL newURL = URL(m_firstRequest.url(), location);
410
411             ResourceRequest redirectedRequest = m_firstRequest;
412             redirectedRequest.setURL(newURL);
413             ResourceResponse localResponse = response();
414             if (m_handle->client())
415                 m_handle->client()->willSendRequest(m_handle, WTFMove(redirectedRequest), WTFMove(localResponse));
416
417             m_firstRequest.setURL(newURL);
418
419             return;
420         }
421     } else if (response().isUnauthorized()) {
422         ProtectionSpace protectionSpace;
423         if (getProtectionSpace(response(), protectionSpace)) {
424             Credential credential;
425             AuthenticationChallenge challenge(protectionSpace, credential, m_authFailureCount, response(), ResourceError());
426             challenge.setAuthenticationClient(m_handle);
427             m_handle->didReceiveAuthenticationChallenge(challenge);
428             m_authFailureCount++;
429             return;
430         }
431     }
432
433     response().setResponseFired(true);
434
435     if (m_handle->client()) {
436         if (response().isNotModified()) {
437             const String& url = m_firstRequest.url().string();
438             if (CurlCacheManager::getInstance().getCachedResponse(url, response())) {
439                 if (m_addedCacheValidationHeaders) {
440                     response().setHTTPStatusCode(200);
441                     response().setHTTPStatusText("OK");
442                 }
443             }
444         }
445         CurlCacheManager::getInstance().didReceiveResponse(*m_handle, response());
446         m_handle->client()->didReceiveResponse(m_handle, ResourceResponse(response()));
447     }
448 }
449
450 void ResourceHandleCurlDelegate::didReceiveContentData(ThreadSafeDataBuffer buffer)
451 {
452     ASSERT(isMainThread());
453
454     if (!response().responseFired())
455         handleLocalReceiveResponse();
456
457     const char* ptr = reinterpret_cast<const char*>(buffer.data()->begin());
458     size_t size = buffer.size();
459
460     if (m_multipartHandle)
461         m_multipartHandle->contentReceived(ptr, size);
462     else if (m_handle->client()) {
463         CurlCacheManager::getInstance().didReceiveData(*m_handle, ptr, size);
464         m_handle->client()->didReceiveData(m_handle, ptr, size, 0);
465     }
466 }
467
468 void ResourceHandleCurlDelegate::handleLocalReceiveResponse()
469 {
470     ASSERT(isMainThread());
471
472     // since the code in headerCallback will not have run for local files
473     // the code to set the URL and fire didReceiveResponse is never run,
474     // which means the ResourceLoader's response does not contain the URL.
475     // Run the code here for local files to resolve the issue.
476     // TODO: See if there is a better approach for handling this.
477     response().setURL(m_curlHandle.getEffectiveURL());
478     response().setResponseFired(true);
479     if (m_handle->client())
480         m_handle->client()->didReceiveResponse(m_handle, ResourceResponse(response()));
481 }
482
483 void ResourceHandleCurlDelegate::prepareSendData(char* buffer, size_t blockSize, size_t numberOfBlocks)
484 {
485     ASSERT(isMainThread());
486     ASSERT(!m_sendBytes);
487
488     std::unique_lock<Lock> lock(m_workerThreadMutex);
489
490     if (!m_formDataStream.hasMoreElements())
491         return;
492
493     size_t size = m_formDataStream.read(buffer, blockSize, numberOfBlocks);
494     if (!size) {
495         // Something went wrong so cancel the job.
496         m_handle->cancel();
497         return;
498     }
499
500     m_sendBytes = size;
501     m_workerThreadConditionVariable.notifyOne();
502 }
503
504 void ResourceHandleCurlDelegate::didFinish(NetworkLoadMetrics networkLoadMetrics)
505 {
506     response().setDeprecatedNetworkLoadMetrics(networkLoadMetrics);
507
508     if (!m_handle)
509         return;
510
511     if (!response().responseFired()) {
512         handleLocalReceiveResponse();
513         if (!m_handle)
514             return;
515     }
516
517     if (m_multipartHandle)
518         m_multipartHandle->contentEnded();
519
520     if (m_handle->client()) {
521         CurlCacheManager::getInstance().didFinishLoading(*m_handle);
522         m_handle->client()->didFinishLoading(m_handle);
523     }
524 }
525
526 void ResourceHandleCurlDelegate::didFail(const ResourceError& resourceError)
527 {
528     if (!m_handle)
529         return;
530
531     if (m_handle->client()) {
532         CurlCacheManager::getInstance().didFail(*m_handle);
533         m_handle->client()->didFail(m_handle, resourceError);
534     }
535 }
536
537 void ResourceHandleCurlDelegate::handleDataURL()
538 {
539     ASSERT(m_firstRequest.url().protocolIsData());
540     String url = m_firstRequest.url().string();
541
542     ASSERT(m_handle->client());
543
544     int index = url.find(',');
545     if (index == -1) {
546         m_handle->client()->cannotShowURL(m_handle);
547         return;
548     }
549
550     String mediaType = url.substring(5, index - 5);
551     String data = url.substring(index + 1);
552
553     bool base64 = mediaType.endsWith(";base64", false);
554     if (base64)
555         mediaType = mediaType.left(mediaType.length() - 7);
556
557     if (mediaType.isEmpty())
558         mediaType = "text/plain";
559
560     String mimeType = extractMIMETypeFromMediaType(mediaType);
561     String charset = extractCharsetFromMediaType(mediaType);
562
563     if (charset.isEmpty())
564         charset = "US-ASCII";
565
566     ResourceResponse response;
567     response.setMimeType(mimeType);
568     response.setTextEncodingName(charset);
569     response.setURL(m_firstRequest.url());
570
571     if (base64) {
572         data = decodeURLEscapeSequences(data);
573         m_handle->client()->didReceiveResponse(m_handle, WTFMove(response));
574
575         // didReceiveResponse might cause the client to be deleted.
576         if (m_handle->client()) {
577             Vector<char> out;
578             if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
579                 m_handle->client()->didReceiveData(m_handle, out.data(), out.size(), 0);
580         }
581     } else {
582         TextEncoding encoding(charset);
583         data = decodeURLEscapeSequences(data, encoding);
584         m_handle->client()->didReceiveResponse(m_handle, WTFMove(response));
585
586         // didReceiveResponse might cause the client to be deleted.
587         if (m_handle->client()) {
588             CString encodedData = encoding.encode(data, URLEncodedEntitiesForUnencodables);
589             if (encodedData.length())
590                 m_handle->client()->didReceiveData(m_handle, encodedData.data(), encodedData.length(), 0);
591         }
592     }
593
594     if (m_handle->client())
595         m_handle->client()->didFinishLoading(m_handle);
596 }
597
598 void ResourceHandleCurlDelegate::setupPOST()
599 {
600     m_curlHandle.enableHttpPostRequest();
601
602     size_t numElements = getFormElementsCount();
603     if (!numElements)
604         return;
605
606     // Do not stream for simple POST data
607     if (numElements == 1) {
608         m_postBytes = m_firstRequest.httpBody()->flatten();
609         if (m_postBytes.size())
610             m_curlHandle.setPostFields(m_postBytes.data(), m_postBytes.size());
611         return;
612     }
613
614     setupFormData(true);
615 }
616
617 void ResourceHandleCurlDelegate::setupPUT()
618 {
619     m_curlHandle.enableHttpPutRequest();
620
621     // Disable the Expect: 100 continue header
622     m_curlHandle.appendRequestHeader("Expect:");
623
624     size_t numElements = getFormElementsCount();
625     if (!numElements)
626         return;
627
628     setupFormData(false);
629 }
630
631 size_t ResourceHandleCurlDelegate::getFormElementsCount()
632 {
633     RefPtr<FormData> formData = m_firstRequest.httpBody();
634     if (!formData)
635         return 0;
636
637     // Resolve the blob elements so the formData can correctly report it's size.
638     formData = formData->resolveBlobReferences();
639     size_t size = formData->elements().size();
640     m_firstRequest.setHTTPBody(WTFMove(formData));
641     return size;
642 }
643
644 void ResourceHandleCurlDelegate::setupFormData(bool isPostRequest)
645 {
646     Vector<FormDataElement> elements = m_firstRequest.httpBody()->elements();
647     size_t numElements = elements.size();
648
649     static const long long maxCurlOffT = m_curlHandle.maxCurlOffT();
650
651     // Obtain the total size of the form data
652     curl_off_t size = 0;
653     bool chunkedTransfer = false;
654     for (size_t i = 0; i < numElements; i++) {
655         FormDataElement element = elements[i];
656         if (element.m_type == FormDataElement::Type::EncodedFile) {
657             long long fileSizeResult;
658             if (getFileSize(element.m_filename, fileSizeResult)) {
659                 if (fileSizeResult > maxCurlOffT) {
660                     // File size is too big for specifying it to cURL
661                     chunkedTransfer = true;
662                     break;
663                 }
664                 size += fileSizeResult;
665             } else {
666                 chunkedTransfer = true;
667                 break;
668             }
669         } else
670             size += elements[i].m_data.size();
671     }
672
673     // cURL guesses that we want chunked encoding as long as we specify the header
674     if (chunkedTransfer)
675         m_curlHandle.appendRequestHeader("Transfer-Encoding: chunked");
676     else {
677         if (isPostRequest)
678             m_curlHandle.setPostFieldLarge(size);
679         else
680             m_curlHandle.setInFileSizeLarge(size);
681     }
682
683     m_curlHandle.setReadCallbackFunction(willSendDataCallback, this);
684 }
685
686 void ResourceHandleCurlDelegate::applyAuthentication()
687 {
688     String user = m_user;
689     String password = m_pass;
690
691     if (!m_initialCredential.isEmpty()) {
692         user = m_initialCredential.user();
693         password = m_initialCredential.password();
694         m_curlHandle.enableHttpAuthentication(CURLAUTH_BASIC);
695     }
696
697     // It seems we need to set CURLOPT_USERPWD even if username and password is empty.
698     // Otherwise cURL will not automatically continue with a new request after a 401 response.
699
700     // curl CURLOPT_USERPWD expects username:password
701     m_curlHandle.setHttpAuthUserPass(user, password);
702 }
703
704 NetworkLoadMetrics ResourceHandleCurlDelegate::getNetworkLoadMetrics()
705 {
706     NetworkLoadMetrics networkLoadMetrics;
707     if (auto metrics = m_curlHandle.getTimes())
708         networkLoadMetrics = *metrics;
709
710     return networkLoadMetrics;
711 }
712
713 CURLcode ResourceHandleCurlDelegate::willSetupSslCtx(void* sslCtx)
714 {
715     m_sslVerifier.setCurlHandle(&m_curlHandle);
716     m_sslVerifier.setHostName(m_firstRequest.url().host());
717     m_sslVerifier.setSslCtx(sslCtx);
718
719     return CURLE_OK;
720 }
721
722 /*
723 * This is being called for each HTTP header in the response. This includes '\r\n'
724 * for the last line of the header.
725 *
726 * We will add each HTTP Header to the ResourceResponse and on the termination
727 * of the header (\r\n) we will parse Content-Type and Content-Disposition and
728 * update the ResourceResponse and then send it away.
729 *
730 */
731 size_t ResourceHandleCurlDelegate::didReceiveHeader(String&& header)
732 {
733     if (!m_handle)
734         return 0;
735
736     if (m_defersLoading)
737         return 0;
738
739     /*
740     * a) We can finish and send the ResourceResponse
741     * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse
742     *
743     * The HTTP standard requires to use \r\n but for compatibility it recommends to
744     * accept also \n.
745     */
746     if (header == AtomicString("\r\n") || header == AtomicString("\n")) {
747         long httpCode = 0;
748         if (auto code = m_curlHandle.getResponseCode())
749             httpCode = *code;
750
751         if (!httpCode) {
752             // Comes here when receiving 200 Connection Established. Just return.
753             return header.length();
754         }
755         if (isHttpInfo(httpCode)) {
756             // Just return when receiving http info, e.g. HTTP/1.1 100 Continue.
757             // If not, the request might be cancelled, because the MIME type will be empty for this response.
758             return header.length();
759         }
760
761         long long contentLength = 0;
762         if (auto length = m_curlHandle.getContentLenghtDownload())
763             contentLength = *length;
764
765         if (isMainThread())
766             didReceiveAllHeaders(httpCode, contentLength);
767         else {
768             callOnMainThread([protectedThis = makeRef(*this), httpCode, contentLength] {
769                 if (!protectedThis->m_handle)
770                     return;
771                 protectedThis->didReceiveAllHeaders(httpCode, contentLength);
772             });
773         }
774     } else {
775         // If the FOLLOWLOCATION option is enabled for the curl handle then
776         // curl will follow the redirections internally. Thus this header callback
777         // will be called more than one time with the line starting "HTTP" for one job.
778         if (isMainThread())
779             response().appendHTTPHeaderField(header);
780         else {
781             callOnMainThread([protectedThis = makeRef(*this), copyHeader = header.isolatedCopy() ] {
782                 if (!protectedThis->m_handle)
783                     return;
784
785                 protectedThis->response().appendHTTPHeaderField(copyHeader);
786             });
787         }
788     }
789
790     return header.length();
791 }
792
793 // called with data after all headers have been processed via headerCallback
794 size_t ResourceHandleCurlDelegate::didReceiveData(ThreadSafeDataBuffer data)
795 {
796     if (!m_handle)
797         return 0;
798
799     if (m_defersLoading)
800         return 0;
801
802     // this shouldn't be necessary but apparently is. CURL writes the data
803     // of html page even if it is a redirect that was handled internally
804     // can be observed e.g. on gmail.com
805     if (auto httpCode = m_curlHandle.getResponseCode()) {
806         if (*httpCode >= 300 && *httpCode < 400)
807             return data.size();
808     }
809
810     if (!data.size())
811         return 0;
812
813     if (isMainThread())
814         didReceiveContentData(data);
815     else {
816         callOnMainThread([protectedThis = makeRef(*this), data] {
817             if (!protectedThis->m_handle)
818                 return;
819             protectedThis->didReceiveContentData(data);
820         });
821     }
822
823     return data.size();
824 }
825
826 /* This is called to obtain HTTP POST or PUT data.
827 Iterate through FormData elements and upload files.
828 Carefully respect the given buffer blockSize and fill the rest of the data at the next calls.
829 */
830 size_t ResourceHandleCurlDelegate::willSendData(char* buffer, size_t blockSize, size_t numberOfBlocks)
831 {
832     ASSERT(!isMainThread());
833
834     if (!m_handle)
835         return 0;
836
837     if (m_defersLoading)
838         return 0;
839
840     if (!blockSize || !numberOfBlocks)
841         return 0;
842
843     {
844         std::unique_lock<Lock> lock(m_workerThreadMutex);
845
846         m_sendBytes = 0;
847
848         if (isMainThread())
849             prepareSendData(buffer, blockSize, numberOfBlocks);
850         else {
851             callOnMainThread([protectedThis = makeRef(*this), buffer, blockSize, numberOfBlocks] {
852                 if (!protectedThis->m_handle)
853                     return;
854                 protectedThis->prepareSendData(buffer, blockSize, numberOfBlocks);
855             });
856
857             m_workerThreadConditionVariable.wait(lock, [this] {
858                 return m_sendBytes;
859             });
860         }
861     }
862
863     return m_sendBytes;
864 }
865
866 CURLcode ResourceHandleCurlDelegate::willSetupSslCtxCallback(CURL*, void* sslCtx, void* userData)
867 {
868     return static_cast<ResourceHandleCurlDelegate*>(userData)->willSetupSslCtx(sslCtx);
869 }
870
871 size_t ResourceHandleCurlDelegate::didReceiveHeaderCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* data)
872 {
873     return static_cast<ResourceHandleCurlDelegate*>(const_cast<void*>(data))->didReceiveHeader(String(static_cast<const char*>(ptr), blockSize * numberOfBlocks));
874 }
875
876 size_t ResourceHandleCurlDelegate::didReceiveDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* data)
877 {
878     return static_cast<ResourceHandleCurlDelegate*>(const_cast<void*>(data))->didReceiveData(ThreadSafeDataBuffer::copyData(static_cast<const char*>(ptr), blockSize * numberOfBlocks));
879 }
880
881 size_t ResourceHandleCurlDelegate::willSendDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* data)
882 {
883     return static_cast<ResourceHandleCurlDelegate*>(const_cast<void*>(data))->willSendData(ptr, blockSize, numberOfBlocks);
884 }
885
886 } // namespace WebCore
887
888 #endif