[Curl] Extract multipart handling from ResourceHandle to CurlRequest.
[WebKit-https.git] / Source / WebCore / platform / network / curl / CurlRequest.cpp
1 /*
2  * Copyright (C) 2017 Sony Interactive Entertainment Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CurlRequest.h"
28
29 #if USE(CURL)
30
31 #include "CurlRequestClient.h"
32 #include "CurlRequestScheduler.h"
33 #include "MIMETypeRegistry.h"
34 #include "ResourceError.h"
35 #include "SharedBuffer.h"
36 #include <wtf/MainThread.h>
37
38 namespace WebCore {
39
40 CurlRequest::CurlRequest(const ResourceRequest&request, CurlRequestClient* client, bool shouldSuspend, bool enableMultipart)
41     : m_request(request.isolatedCopy())
42     , m_shouldSuspend(shouldSuspend)
43     , m_enableMultipart(enableMultipart)
44     , m_formDataStream(m_request.httpBody())
45 {
46     ASSERT(isMainThread());
47
48     setClient(client);
49 }
50
51 void CurlRequest::setUserPass(const String& user, const String& password)
52 {
53     ASSERT(isMainThread());
54
55     m_user = user.isolatedCopy();
56     m_password = password.isolatedCopy();
57 }
58
59 void CurlRequest::start(bool isSyncRequest)
60 {
61     // The pausing of transfer does not work with protocols, like file://.
62     // Therefore, PAUSE can not be done in didReceiveData().
63     // It means that the same logic as http:// can not be used.
64     // In the file scheme, invokeDidReceiveResponse() is done first. 
65     // Then StartWithJobManager is called with completeDidReceiveResponse and start transfer with libcurl.
66
67     // http : didReceiveHeader => didReceiveData[PAUSE] => invokeDidReceiveResponse => (MainThread)curlDidReceiveResponse => completeDidReceiveResponse[RESUME] => didReceiveData
68     // file : invokeDidReceiveResponseForFile => (MainThread)curlDidReceiveResponse => completeDidReceiveResponse => didReceiveData
69
70     ASSERT(isMainThread());
71
72     m_isSyncRequest = isSyncRequest;
73
74     auto url = m_request.url().isolatedCopy();
75
76     if (!m_isSyncRequest) {
77         // For asynchronous, use CurlRequestScheduler. Curl processes runs on sub thread.
78         if (url.isLocalFile())
79             invokeDidReceiveResponseForFile(url);
80         else
81             startWithJobManager();
82     } else {
83         // For synchronous, does not use CurlRequestScheduler. Curl processes runs on main thread.
84         // curl_easy_perform blocks until the transfer is finished.
85         retain();
86         if (url.isLocalFile())
87             invokeDidReceiveResponseForFile(url);
88
89         setupTransfer();
90         CURLcode resultCode = m_curlHandle->perform();
91         didCompleteTransfer(resultCode);
92         release();
93     }
94 }
95
96 void CurlRequest::startWithJobManager()
97 {
98     ASSERT(isMainThread());
99
100     CurlRequestScheduler::singleton().add(this);
101 }
102
103 void CurlRequest::cancel()
104 {
105     ASSERT(isMainThread());
106
107     if (isCompletedOrCancelled())
108         return;
109
110     m_cancelled = true;
111
112     if (!m_isSyncRequest) {
113         auto& scheduler = CurlRequestScheduler::singleton();
114
115         if (needToInvokeDidCancelTransfer()) {
116             scheduler.callOnWorkerThread([protectedThis = makeRef(*this)]() {
117                 protectedThis->didCancelTransfer();
118             });
119         } else
120             scheduler.cancel(this);
121     } else {
122         if (needToInvokeDidCancelTransfer())
123             didCancelTransfer();
124     }
125
126     setRequestPaused(false);
127     setCallbackPaused(false);
128 }
129
130 void CurlRequest::suspend()
131 {
132     ASSERT(isMainThread());
133
134     setRequestPaused(true);
135 }
136
137 void CurlRequest::resume()
138 {
139     ASSERT(isMainThread());
140
141     setRequestPaused(false);
142 }
143
144 /* `this` is protected inside this method. */
145 void CurlRequest::callClient(WTF::Function<void(CurlRequestClient*)> task)
146 {
147     if (isMainThread()) {
148         if (CurlRequestClient* client = m_client)
149             task(client);
150     } else {
151         callOnMainThread([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable {
152             if (CurlRequestClient* client = protectedThis->m_client)
153                 task(client);
154         });
155     }
156 }
157
158 CURL* CurlRequest::setupTransfer()
159 {
160     auto& sslHandle = CurlContext::singleton().sslHandle();
161
162     m_curlHandle = std::make_unique<CurlHandle>();
163
164     m_curlHandle->initialize();
165     m_curlHandle->setUrl(m_request.url());
166     m_curlHandle->appendRequestHeaders(m_request.httpHeaderFields());
167
168     const auto& method = m_request.httpMethod();
169     if (method == "GET")
170         m_curlHandle->enableHttpGetRequest();
171     else if (method == "POST")
172         setupPOST(m_request);
173     else if (method == "PUT")
174         setupPUT(m_request);
175     else if (method == "HEAD")
176         m_curlHandle->enableHttpHeadRequest();
177     else {
178         m_curlHandle->setHttpCustomRequest(method);
179         setupPUT(m_request);
180     }
181
182     if (!m_user.isEmpty() || !m_password.isEmpty()) {
183         m_curlHandle->enableHttpAuthentication(CURLAUTH_ANY);
184         m_curlHandle->setHttpAuthUserPass(m_user, m_password);
185     }
186
187     m_curlHandle->setHeaderCallbackFunction(didReceiveHeaderCallback, this);
188     m_curlHandle->setWriteCallbackFunction(didReceiveDataCallback, this);
189
190     m_curlHandle->enableShareHandle();
191     m_curlHandle->enableAllowedProtocols();
192     m_curlHandle->enableAcceptEncoding();
193     m_curlHandle->enableTimeout();
194
195     m_curlHandle->enableProxyIfExists();
196     m_curlHandle->enableCookieJarIfExists();
197
198     m_curlHandle->setSslVerifyPeer(CurlHandle::VerifyPeer::Enable);
199     m_curlHandle->setSslVerifyHost(CurlHandle::VerifyHost::StrictNameCheck);
200
201     auto sslClientCertificate = sslHandle.getSSLClientCertificate(m_request.url().host());
202     if (sslClientCertificate) {
203         m_curlHandle->setSslCert(sslClientCertificate->first.utf8().data());
204         m_curlHandle->setSslCertType("P12");
205         m_curlHandle->setSslKeyPassword(sslClientCertificate->second.utf8().data());
206     }
207
208     if (sslHandle.shouldIgnoreSSLErrors())
209         m_curlHandle->setSslVerifyPeer(CurlHandle::VerifyPeer::Disable);
210     else
211         m_curlHandle->setSslCtxCallbackFunction(willSetupSslCtxCallback, this);
212
213     m_curlHandle->setCACertPath(sslHandle.getCACertPath());
214
215     if (m_shouldSuspend)
216         suspend();
217
218 #ifndef NDEBUG
219     m_curlHandle->enableVerboseIfUsed();
220     m_curlHandle->enableStdErrIfUsed();
221 #endif
222
223     return m_curlHandle->handle();
224 }
225
226 CURLcode CurlRequest::willSetupSslCtx(void* sslCtx)
227 {
228     m_sslVerifier.setCurlHandle(m_curlHandle.get());
229     m_sslVerifier.setHostName(m_request.url().host());
230     m_sslVerifier.setSslCtx(sslCtx);
231
232     return CURLE_OK;
233 }
234
235 // This is called to obtain HTTP POST or PUT data.
236 // Iterate through FormData elements and upload files.
237 // Carefully respect the given buffer size and fill the rest of the data at the next calls.
238
239 size_t CurlRequest::willSendData(char* buffer, size_t blockSize, size_t numberOfBlocks)
240 {
241     if (isCompletedOrCancelled())
242         return CURL_READFUNC_ABORT;
243
244     if (!blockSize || !numberOfBlocks)
245         return CURL_READFUNC_ABORT;
246
247     // Check for overflow.
248     if (blockSize > (std::numeric_limits<size_t>::max() / numberOfBlocks))
249         return CURL_READFUNC_ABORT;
250
251     size_t bufferSize = blockSize * numberOfBlocks;
252     auto sendBytes = m_formDataStream.read(buffer, bufferSize);
253     if (!sendBytes) {
254         // Something went wrong so error the job.
255         return CURL_READFUNC_ABORT;
256     }
257
258     return *sendBytes;
259 }
260
261 // This is being called for each HTTP header in the response. This includes '\r\n'
262 // for the last line of the header.
263
264 size_t CurlRequest::didReceiveHeader(String&& header)
265 {
266     static const auto emptyLineCRLF = "\r\n";
267     static const auto emptyLineLF = "\n";
268
269     if (isCompletedOrCancelled())
270         return 0;
271
272     // libcurl sends all headers that libcurl received to application.
273     // So, in digest authentication, a block of response headers are received twice consecutively from libcurl.
274     // For example, when authentication succeeds, the first block is "401 Authorization", and the second block is "200 OK".
275     // Also, "100 Continue" and "200 Connection Established" do the same behavior.
276     // In this process, deletes the first block to send a correct headers to WebCore.
277     if (m_didReceiveResponse) {
278         m_didReceiveResponse = false;
279         m_response = CurlResponse { };
280         m_multipartHandle = nullptr;
281     }
282
283     auto receiveBytes = static_cast<size_t>(header.length());
284
285     // The HTTP standard requires to use \r\n but for compatibility it recommends to accept also \n.
286     if ((header != emptyLineCRLF) && (header != emptyLineLF)) {
287         m_response.headers.append(WTFMove(header));
288         return receiveBytes;
289     }
290
291     long statusCode = 0;
292     if (auto code = m_curlHandle->getResponseCode())
293         statusCode = *code;
294
295     long httpConnectCode = 0;
296     if (auto code = m_curlHandle->getHttpConnectCode())
297         httpConnectCode = *code;
298
299     m_didReceiveResponse = true;
300
301     m_response.url = m_request.url();
302     m_response.statusCode = statusCode;
303
304     if (auto length = m_curlHandle->getContentLength())
305         m_response.expectedContentLength = *length;
306
307     if (auto port = m_curlHandle->getPrimaryPort())
308         m_response.connectPort = *port;
309
310     if (auto auth = m_curlHandle->getHttpAuthAvail())
311         m_response.availableHttpAuth = *auth;
312
313     if (auto version = m_curlHandle->getHttpVersion())
314         m_response.httpVersion = *version;
315
316     if (auto metrics = m_curlHandle->getNetworkLoadMetrics())
317         m_networkLoadMetrics = *metrics;
318
319     if (m_enableMultipart)
320         m_multipartHandle = CurlMultipartHandle::createIfNeeded(*this, m_response);
321
322     // Response will send at didReceiveData() or didCompleteTransfer()
323     // to receive continueDidRceiveResponse() for asynchronously.
324
325     return receiveBytes;
326 }
327
328 // called with data after all headers have been processed via headerCallback
329
330 size_t CurlRequest::didReceiveData(Ref<SharedBuffer>&& buffer)
331 {
332     if (isCompletedOrCancelled())
333         return 0;
334
335     if (needToInvokeDidReceiveResponse()) {
336         if (!m_isSyncRequest) {
337             // For asynchronous, pause until completeDidReceiveResponse() is called.
338             setCallbackPaused(true);
339             invokeDidReceiveResponse(m_response, Action::ReceiveData);
340             return CURL_WRITEFUNC_PAUSE;
341         }
342
343         // For synchronous, completeDidReceiveResponse() is called in invokeDidReceiveResponse().
344         // In this case, pause is unnecessary.
345         invokeDidReceiveResponse(m_response, Action::None);
346     }
347
348     auto receiveBytes = buffer->size();
349
350     writeDataToDownloadFileIfEnabled(buffer);
351
352     if (receiveBytes) {
353         if (m_multipartHandle)
354             m_multipartHandle->didReceiveData(buffer);
355         else {
356             callClient([this, buffer = WTFMove(buffer)](CurlRequestClient* client) mutable {
357                 if (client)
358                     client->curlDidReceiveBuffer(WTFMove(buffer));
359             });
360         }
361     }
362
363     return receiveBytes;
364 }
365
366 void CurlRequest::didReceiveHeaderFromMultipart(const Vector<String>& headers)
367 {
368     if (isCompletedOrCancelled())
369         return;
370
371     CurlResponse response = m_response.isolatedCopy();
372     response.expectedContentLength = 0;
373     response.headers.clear();
374
375     for (auto header : headers)
376         response.headers.append(header);
377
378     invokeDidReceiveResponse(response, Action::None);
379 }
380
381 void CurlRequest::didReceiveDataFromMultipart(Ref<SharedBuffer>&& buffer)
382 {
383     if (isCompletedOrCancelled())
384         return;
385
386     auto receiveBytes = buffer->size();
387
388     if (receiveBytes) {
389         callClient([this, buffer = WTFMove(buffer)](CurlRequestClient* client) mutable {
390             if (client)
391                 client->curlDidReceiveBuffer(WTFMove(buffer));
392         });
393     }
394 }
395
396 void CurlRequest::didCompleteTransfer(CURLcode result)
397 {
398     if (m_cancelled) {
399         m_curlHandle = nullptr;
400         return;
401     }
402
403     if (result == CURLE_OK) {
404         if (needToInvokeDidReceiveResponse()) {
405             // Processing of didReceiveResponse() has not been completed. (For example, HEAD method)
406             // When completeDidReceiveResponse() is called, didCompleteTransfer() will be called again.
407
408             m_finishedResultCode = result;
409             invokeDidReceiveResponse(m_response, Action::FinishTransfer);
410         } else {
411             if (m_multipartHandle)
412                 m_multipartHandle->didComplete();
413
414             if (auto metrics = m_curlHandle->getNetworkLoadMetrics())
415                 m_networkLoadMetrics = *metrics;
416
417             finalizeTransfer();
418             callClient([this](CurlRequestClient* client) {
419                 if (client)
420                     client->curlDidComplete();
421             });
422         }
423     } else {
424         auto resourceError = ResourceError::httpError(result, m_request.url());
425         if (m_sslVerifier.sslErrors())
426             resourceError.setSslErrors(m_sslVerifier.sslErrors());
427
428         finalizeTransfer();
429         callClient([this, error = resourceError.isolatedCopy()](CurlRequestClient* client) {
430             if (client)
431                 client->curlDidFailWithError(error);
432         });
433     }
434 }
435
436 void CurlRequest::didCancelTransfer()
437 {
438     finalizeTransfer();
439     cleanupDownloadFile();
440 }
441
442 void CurlRequest::finalizeTransfer()
443 {
444     closeDownloadFile();
445     m_formDataStream.clean();
446     m_multipartHandle = nullptr;
447     m_curlHandle = nullptr;
448 }
449
450 void CurlRequest::setupPUT(ResourceRequest& request)
451 {
452     m_curlHandle->enableHttpPutRequest();
453
454     // Disable the Expect: 100 continue header
455     m_curlHandle->removeRequestHeader("Expect");
456
457     auto elementSize = m_formDataStream.elementSize();
458     if (!elementSize)
459         return;
460
461     setupSendData(true);
462 }
463
464 void CurlRequest::setupPOST(ResourceRequest& request)
465 {
466     m_curlHandle->enableHttpPostRequest();
467
468     auto elementSize = m_formDataStream.elementSize();
469     if (!elementSize)
470         return;
471
472     // Do not stream for simple POST data
473     if (elementSize == 1) {
474         auto postData = m_formDataStream.getPostData();
475         if (postData && postData->size())
476             m_curlHandle->setPostFields(postData->data(), postData->size());
477     } else
478         setupSendData(false);
479 }
480
481 void CurlRequest::setupSendData(bool forPutMethod)
482 {
483     // curl guesses that we want chunked encoding as long as we specify the header
484     if (m_formDataStream.shouldUseChunkTransfer())
485         m_curlHandle->appendRequestHeader("Transfer-Encoding: chunked");
486     else {
487         if (forPutMethod)
488             m_curlHandle->setInFileSizeLarge(static_cast<curl_off_t>(m_formDataStream.totalSize()));
489         else
490             m_curlHandle->setPostFieldLarge(static_cast<curl_off_t>(m_formDataStream.totalSize()));
491     }
492
493     m_curlHandle->setReadCallbackFunction(willSendDataCallback, this);
494 }
495
496 void CurlRequest::invokeDidReceiveResponseForFile(URL& url)
497 {
498     // Since the code in didReceiveHeader() will not have run for local files
499     // the code to set the URL and fire didReceiveResponse is never run,
500     // which means the ResourceLoader's response does not contain the URL.
501     // Run the code here for local files to resolve the issue.
502
503     ASSERT(isMainThread());
504     ASSERT(url.isLocalFile());
505
506     m_response.url = url;
507     m_response.statusCode = 200;
508
509     // Determine the MIME type based on the path.
510     m_response.headers.append(String("Content-Type: " + MIMETypeRegistry::getMIMETypeForPath(m_response.url.path())));
511
512     if (!m_isSyncRequest) {
513         // DidReceiveResponse must not be called immediately
514         CurlRequestScheduler::singleton().callOnWorkerThread([protectedThis = makeRef(*this)]() {
515             protectedThis->invokeDidReceiveResponse(protectedThis->m_response, Action::StartTransfer);
516         });
517     } else {
518         // For synchronous, completeDidReceiveResponse() is called in platformContinueSynchronousDidReceiveResponse().
519         invokeDidReceiveResponse(m_response, Action::None);
520     }
521 }
522
523 void CurlRequest::invokeDidReceiveResponse(const CurlResponse& response, Action behaviorAfterInvoke)
524 {
525     ASSERT(!m_didNotifyResponse || m_multipartHandle);
526
527     m_didNotifyResponse = true;
528     m_actionAfterInvoke = behaviorAfterInvoke;
529
530     callClient([this, response = response.isolatedCopy()](CurlRequestClient* client) {
531         if (client)
532             client->curlDidReceiveResponse(response);
533     });
534 }
535
536 void CurlRequest::completeDidReceiveResponse()
537 {
538     ASSERT(isMainThread());
539     ASSERT(m_didNotifyResponse);
540     ASSERT(!m_didReturnFromNotify || m_multipartHandle);
541
542     if (isCancelled())
543         return;
544
545     if (m_actionAfterInvoke != Action::StartTransfer && isCompleted())
546         return;
547
548     m_didReturnFromNotify = true;
549
550     if (m_actionAfterInvoke == Action::ReceiveData) {
551         // Resume transfer
552         setCallbackPaused(false);
553     } else if (m_actionAfterInvoke == Action::StartTransfer) {
554         // Start transfer for file scheme
555         startWithJobManager();
556     } else if (m_actionAfterInvoke == Action::FinishTransfer) {
557         if (!m_isSyncRequest) {
558             CurlRequestScheduler::singleton().callOnWorkerThread([protectedThis = makeRef(*this), finishedResultCode = m_finishedResultCode]() {
559                 protectedThis->didCompleteTransfer(finishedResultCode);
560             });
561         } else
562             didCompleteTransfer(m_finishedResultCode);
563     }
564 }
565
566 void CurlRequest::setRequestPaused(bool paused)
567 {
568     auto wasPaused = isPaused();
569
570     m_isPausedOfRequest = paused;
571
572     if (isPaused() == wasPaused)
573         return;
574
575     pausedStatusChanged();
576 }
577
578 void CurlRequest::setCallbackPaused(bool paused)
579 {
580     auto wasPaused = isPaused();
581
582     m_isPausedOfCallback = paused;
583
584     if (isPaused() == wasPaused)
585         return;
586
587     // In this case, PAUSE will be executed within didReceiveData(). Change pause state and return.
588     if (paused)
589         return;
590
591     pausedStatusChanged();
592 }
593
594 void CurlRequest::pausedStatusChanged()
595 {
596     if (isCompletedOrCancelled())
597         return;
598
599     if (!m_isSyncRequest && isMainThread()) {
600         CurlRequestScheduler::singleton().callOnWorkerThread([protectedThis = makeRef(*this), paused = isPaused()]() {
601             if (protectedThis->isCompletedOrCancelled())
602                 return;
603
604             auto error = protectedThis->m_curlHandle->pause(paused ? CURLPAUSE_ALL : CURLPAUSE_CONT);
605             if ((error != CURLE_OK) && !paused) {
606                 // Restarting the handle has failed so just cancel it.
607                 callOnMainThread([protectedThis = makeRef(protectedThis.get())]() {
608                     protectedThis->cancel();
609                 });
610             }
611         });
612     } else {
613         auto error = m_curlHandle->pause(isPaused() ? CURLPAUSE_ALL : CURLPAUSE_CONT);
614         if ((error != CURLE_OK) && !isPaused())
615             cancel();
616     }
617 }
618
619 void CurlRequest::enableDownloadToFile()
620 {
621     LockHolder locker(m_downloadMutex);
622     m_isEnabledDownloadToFile = true;
623 }
624
625 const String& CurlRequest::getDownloadedFilePath()
626 {
627     LockHolder locker(m_downloadMutex);
628     return m_downloadFilePath;
629 }
630
631 void CurlRequest::writeDataToDownloadFileIfEnabled(const SharedBuffer& buffer)
632 {
633     {
634         LockHolder locker(m_downloadMutex);
635
636         if (!m_isEnabledDownloadToFile)
637             return;
638
639         if (m_downloadFilePath.isEmpty())
640             m_downloadFilePath = FileSystem::openTemporaryFile("download", m_downloadFileHandle);
641     }
642
643     if (m_downloadFileHandle != FileSystem::invalidPlatformFileHandle)
644         FileSystem::writeToFile(m_downloadFileHandle, buffer.data(), buffer.size());
645 }
646
647 void CurlRequest::closeDownloadFile()
648 {
649     LockHolder locker(m_downloadMutex);
650
651     if (m_downloadFileHandle == FileSystem::invalidPlatformFileHandle)
652         return;
653
654     FileSystem::closeFile(m_downloadFileHandle);
655     m_downloadFileHandle = FileSystem::invalidPlatformFileHandle;
656 }
657
658 void CurlRequest::cleanupDownloadFile()
659 {
660     LockHolder locker(m_downloadMutex);
661
662     if (!m_downloadFilePath.isEmpty()) {
663         FileSystem::deleteFile(m_downloadFilePath);
664         m_downloadFilePath = String();
665     }
666 }
667
668 CURLcode CurlRequest::willSetupSslCtxCallback(CURL*, void* sslCtx, void* userData)
669 {
670     return static_cast<CurlRequest*>(userData)->willSetupSslCtx(sslCtx);
671 }
672
673 size_t CurlRequest::willSendDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* userData)
674 {
675     return static_cast<CurlRequest*>(userData)->willSendData(ptr, blockSize, numberOfBlocks);
676 }
677
678 size_t CurlRequest::didReceiveHeaderCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* userData)
679 {
680     return static_cast<CurlRequest*>(userData)->didReceiveHeader(String(ptr, blockSize * numberOfBlocks));
681 }
682
683 size_t CurlRequest::didReceiveDataCallback(char* ptr, size_t blockSize, size_t numberOfBlocks, void* userData)
684 {
685     return static_cast<CurlRequest*>(userData)->didReceiveData(SharedBuffer::create(ptr, blockSize * numberOfBlocks));
686 }
687
688 }
689
690 #endif