2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "CurlDownload.h"
32 #include "HTTPHeaderNames.h"
33 #include "HTTPParsers.h"
34 #include "ResourceHandleManager.h"
35 #include "ResourceRequest.h"
36 #include <wtf/MainThread.h>
37 #include <wtf/text/CString.h>
39 using namespace WebCore;
43 // CurlDownloadManager -------------------------------------------------------------------
45 CurlDownloadManager::CurlDownloadManager()
47 , m_curlMultiHandle(0)
50 curl_global_init(CURL_GLOBAL_ALL);
51 m_curlMultiHandle = curl_multi_init();
54 CurlDownloadManager::~CurlDownloadManager()
57 curl_multi_cleanup(m_curlMultiHandle);
58 curl_global_cleanup();
61 bool CurlDownloadManager::add(CURL* curlHandle)
63 DeprecatedMutexLocker locker(m_mutex);
65 m_pendingHandleList.append(curlHandle);
66 startThreadIfNeeded();
71 bool CurlDownloadManager::remove(CURL* curlHandle)
73 DeprecatedMutexLocker locker(m_mutex);
75 m_removedHandleList.append(curlHandle);
80 int CurlDownloadManager::getActiveDownloadCount() const
82 DeprecatedMutexLocker locker(m_mutex);
83 return m_activeHandleList.size();
86 int CurlDownloadManager::getPendingDownloadCount() const
88 DeprecatedMutexLocker locker(m_mutex);
89 return m_pendingHandleList.size();
92 void CurlDownloadManager::startThreadIfNeeded()
96 waitForThreadCompletion(m_threadId);
98 m_threadId = createThread(downloadThread, this, "downloadThread");
102 void CurlDownloadManager::stopThread()
107 waitForThreadCompletion(m_threadId);
112 void CurlDownloadManager::stopThreadIfIdle()
114 DeprecatedMutexLocker locker(m_mutex);
116 if (!getActiveDownloadCount() && !getPendingDownloadCount())
120 void CurlDownloadManager::updateHandleList()
122 DeprecatedMutexLocker locker(m_mutex);
124 // Remove curl easy handles from multi list
125 int size = m_removedHandleList.size();
126 for (int i = 0; i < size; i++) {
127 removeFromCurl(m_removedHandleList[0]);
128 m_removedHandleList.remove(0);
131 // Add pending curl easy handles to multi list
132 size = m_pendingHandleList.size();
133 for (int i = 0; i < size; i++) {
134 addToCurl(m_pendingHandleList[0]);
135 m_pendingHandleList.remove(0);
139 bool CurlDownloadManager::addToCurl(CURL* curlHandle)
141 CURLMcode retval = curl_multi_add_handle(m_curlMultiHandle, curlHandle);
142 if (retval == CURLM_OK) {
143 m_activeHandleList.append(curlHandle);
149 bool CurlDownloadManager::removeFromCurl(CURL* curlHandle)
151 int handlePos = m_activeHandleList.find(curlHandle);
156 CURLMcode retval = curl_multi_remove_handle(m_curlMultiHandle, curlHandle);
157 if (retval == CURLM_OK) {
158 m_activeHandleList.remove(handlePos);
159 curl_easy_cleanup(curlHandle);
165 void CurlDownloadManager::downloadThread(void* data)
167 CurlDownloadManager* downloadManager = reinterpret_cast<CurlDownloadManager*>(data);
169 while (downloadManager->runThread()) {
171 downloadManager->updateHandleList();
173 // Retry 'select' if it was interrupted by a process signal.
182 const int selectTimeoutMS = 5;
184 struct timeval timeout;
186 timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds
191 curl_multi_fdset(downloadManager->getMultiHandle(), &fdread, &fdwrite, &fdexcep, &maxfd);
192 // When the 3 file descriptors are empty, winsock will return -1
193 // and bail out, stopping the file download. So make sure we
194 // have valid file descriptors before calling select.
196 rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
197 } while (rc == -1 && errno == EINTR);
199 int activeDownloadCount = 0;
200 while (curl_multi_perform(downloadManager->getMultiHandle(), &activeDownloadCount) == CURLM_CALL_MULTI_PERFORM) { }
202 int messagesInQueue = 0;
203 CURLMsg* msg = curl_multi_info_read(downloadManager->getMultiHandle(), &messagesInQueue);
208 CurlDownload* download = 0;
209 CURLcode err = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &download);
211 if (msg->msg == CURLMSG_DONE) {
213 if (msg->data.result == CURLE_OK) {
214 callOnMainThread([download] {
215 download->didFinish();
216 download->deref(); // This matches the ref() in CurlDownload::start().
219 callOnMainThread([download] {
221 download->deref(); // This matches the ref() in CurlDownload::start().
225 downloadManager->removeFromCurl(msg->easy_handle);
228 downloadManager->stopThreadIfIdle();
232 // CurlDownload --------------------------------------------------------------------------
234 CurlDownloadManager CurlDownload::m_downloadManager;
236 CurlDownload::CurlDownload()
237 : m_curlHandle(nullptr)
238 , m_customHeaders(nullptr)
240 , m_tempHandle(invalidPlatformFileHandle)
241 , m_deletesFileUponFailure(false)
242 , m_listener(nullptr)
246 CurlDownload::~CurlDownload()
248 DeprecatedMutexLocker locker(m_mutex);
254 curl_slist_free_all(m_customHeaders);
257 moveFileToDestination();
260 void CurlDownload::init(CurlDownloadListener* listener, const URL& url)
265 DeprecatedMutexLocker locker(m_mutex);
267 m_curlHandle = curl_easy_init();
269 String urlStr = url.string();
270 m_url = fastStrDup(urlStr.latin1().data());
272 curl_easy_setopt(m_curlHandle, CURLOPT_URL, m_url);
273 curl_easy_setopt(m_curlHandle, CURLOPT_PRIVATE, this);
274 curl_easy_setopt(m_curlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
275 curl_easy_setopt(m_curlHandle, CURLOPT_WRITEDATA, this);
276 curl_easy_setopt(m_curlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
277 curl_easy_setopt(m_curlHandle, CURLOPT_WRITEHEADER, this);
278 curl_easy_setopt(m_curlHandle, CURLOPT_FOLLOWLOCATION, 1);
279 curl_easy_setopt(m_curlHandle, CURLOPT_MAXREDIRS, 10);
280 curl_easy_setopt(m_curlHandle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
282 const char* certPath = getenv("CURL_CA_BUNDLE_PATH");
284 curl_easy_setopt(m_curlHandle, CURLOPT_CAINFO, certPath);
286 CURLSH* curlsh = ResourceHandleManager::sharedInstance()->getCurlShareHandle();
288 curl_easy_setopt(m_curlHandle, CURLOPT_SHARE, curlsh);
290 m_listener = listener;
293 void CurlDownload::init(CurlDownloadListener* listener, ResourceHandle*, const ResourceRequest& request, const ResourceResponse&)
298 DeprecatedMutexLocker locker(m_mutex);
300 URL url(ParsedURLString, request.url());
307 bool CurlDownload::start()
309 ref(); // CurlDownloadManager::downloadThread will call deref when the download has finished.
310 return m_downloadManager.add(m_curlHandle);
313 bool CurlDownload::cancel()
315 return m_downloadManager.remove(m_curlHandle);
318 String CurlDownload::getTempPath() const
320 DeprecatedMutexLocker locker(m_mutex);
324 String CurlDownload::getUrl() const
326 DeprecatedMutexLocker locker(m_mutex);
327 return String(m_url);
330 ResourceResponse CurlDownload::getResponse() const
332 DeprecatedMutexLocker locker(m_mutex);
336 void CurlDownload::closeFile()
338 DeprecatedMutexLocker locker(m_mutex);
340 if (m_tempHandle != invalidPlatformFileHandle) {
341 WebCore::closeFile(m_tempHandle);
342 m_tempHandle = invalidPlatformFileHandle;
346 void CurlDownload::moveFileToDestination()
348 if (m_destination.isEmpty())
351 ::MoveFileEx(m_tempPath.charactersWithNullTermination().data(), m_destination.charactersWithNullTermination().data(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
354 void CurlDownload::writeDataToFile(const char* data, int size)
356 if (m_tempPath.isEmpty())
357 m_tempPath = openTemporaryFile("download", m_tempHandle);
359 if (m_tempHandle != invalidPlatformFileHandle)
360 writeToFile(m_tempHandle, data, size);
363 void CurlDownload::addHeaders(const ResourceRequest& request)
365 if (request.httpHeaderFields().size() > 0) {
366 struct curl_slist* headers = 0;
368 HTTPHeaderMap customHeaders = request.httpHeaderFields();
369 HTTPHeaderMap::const_iterator end = customHeaders.end();
370 for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {
371 const String& value = it->value;
372 String headerString(it->key);
374 // Insert the ; to tell curl that this header has an empty value.
375 headerString.append(";");
377 headerString.append(": ");
378 headerString.append(value);
380 CString headerLatin1 = headerString.latin1();
381 headers = curl_slist_append(headers, headerLatin1.data());
385 curl_easy_setopt(m_curlHandle, CURLOPT_HTTPHEADER, headers);
386 m_customHeaders = headers;
391 void CurlDownload::didReceiveHeader(const String& header)
393 DeprecatedMutexLocker locker(m_mutex);
395 if (header == "\r\n" || header == "\n") {
398 CURLcode err = curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &httpCode);
400 if (httpCode >= 200 && httpCode < 300) {
402 err = curl_easy_getinfo(m_curlHandle, CURLINFO_EFFECTIVE_URL, &url);
405 StringCapture capturedUrl(strUrl);
407 RefPtr<CurlDownload> protectedDownload(this);
409 callOnMainThread([this, capturedUrl, protectedDownload] {
410 m_response.setURL(URL(ParsedURLString, capturedUrl.string()));
412 m_response.setMimeType(extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType)));
413 m_response.setTextEncodingName(extractCharsetFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType)));
415 didReceiveResponse();
419 StringCapture capturedHeader(header);
421 RefPtr<CurlDownload> protectedDownload(this);
423 callOnMainThread([this, capturedHeader, protectedDownload] {
424 int splitPos = capturedHeader.string().find(":");
426 m_response.setHTTPHeaderField(capturedHeader.string().left(splitPos), capturedHeader.string().substring(splitPos + 1).stripWhiteSpace());
431 void CurlDownload::didReceiveData(void* data, int size)
433 DeprecatedMutexLocker locker(m_mutex);
435 RefPtr<CurlDownload> protectedDownload(this);
437 callOnMainThread([this, size, protectedDownload] {
438 didReceiveDataOfLength(size);
441 writeDataToFile(static_cast<const char*>(data), size);
444 void CurlDownload::didReceiveResponse()
447 m_listener->didReceiveResponse();
450 void CurlDownload::didReceiveDataOfLength(int size)
453 m_listener->didReceiveDataOfLength(size);
456 void CurlDownload::didFinish()
459 moveFileToDestination();
462 m_listener->didFinish();
465 void CurlDownload::didFail()
467 DeprecatedMutexLocker locker(m_mutex);
471 if (m_deletesFileUponFailure)
472 deleteFile(m_tempPath);
475 m_listener->didFail();
478 size_t CurlDownload::writeCallback(void* ptr, size_t size, size_t nmemb, void* data)
480 size_t totalSize = size * nmemb;
481 CurlDownload* download = reinterpret_cast<CurlDownload*>(data);
484 download->didReceiveData(ptr, totalSize);
489 size_t CurlDownload::headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
491 size_t totalSize = size * nmemb;
492 CurlDownload* download = reinterpret_cast<CurlDownload*>(data);
494 String header(static_cast<const char*>(ptr), totalSize);
497 download->didReceiveHeader(header);
502 void CurlDownload::downloadFinishedCallback(CurlDownload* download)
505 download->didFinish();
508 void CurlDownload::downloadFailedCallback(CurlDownload* download)
514 void CurlDownload::receivedDataCallback(CurlDownload* download, int size)
517 download->didReceiveDataOfLength(size);
520 void CurlDownload::receivedResponseCallback(CurlDownload* download)
523 download->didReceiveResponse();