1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 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. ``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. 24 */ 25 26#include "config.h" 27 28#if USE(CURL) 29 30#include "CurlDownload.h" 31 32#include "HTTPHeaderNames.h" 33#include "HTTPParsers.h" 34#include "MainThreadTask.h" 35#include "ResourceHandleManager.h" 36#include "ResourceRequest.h" 37#include <wtf/MainThread.h> 38#include <wtf/text/CString.h> 39 40using namespace WebCore; 41 42template<> struct CrossThreadCopierBase<false, false, CurlDownload*> : public CrossThreadCopierPassThrough<CurlDownload*> { 43}; 44 45namespace WebCore { 46 47// CurlDownloadManager ------------------------------------------------------------------- 48 49CurlDownloadManager::CurlDownloadManager() 50: m_threadId(0) 51, m_curlMultiHandle(0) 52, m_runThread(false) 53{ 54 curl_global_init(CURL_GLOBAL_ALL); 55 m_curlMultiHandle = curl_multi_init(); 56} 57 58CurlDownloadManager::~CurlDownloadManager() 59{ 60 stopThread(); 61 curl_multi_cleanup(m_curlMultiHandle); 62 curl_global_cleanup(); 63} 64 65bool CurlDownloadManager::add(CURL* curlHandle) 66{ 67 MutexLocker locker(m_mutex); 68 69 m_pendingHandleList.append(curlHandle); 70 startThreadIfNeeded(); 71 72 return true; 73} 74 75bool CurlDownloadManager::remove(CURL* curlHandle) 76{ 77 MutexLocker locker(m_mutex); 78 79 m_removedHandleList.append(curlHandle); 80 81 return true; 82} 83 84int CurlDownloadManager::getActiveDownloadCount() const 85{ 86 MutexLocker locker(m_mutex); 87 return m_activeHandleList.size(); 88} 89 90int CurlDownloadManager::getPendingDownloadCount() const 91{ 92 MutexLocker locker(m_mutex); 93 return m_pendingHandleList.size(); 94} 95 96void CurlDownloadManager::startThreadIfNeeded() 97{ 98 if (!m_runThread) { 99 if (m_threadId) 100 waitForThreadCompletion(m_threadId); 101 m_runThread = true; 102 m_threadId = createThread(downloadThread, this, "downloadThread"); 103 } 104} 105 106void CurlDownloadManager::stopThread() 107{ 108 m_runThread = false; 109 110 if (m_threadId) { 111 waitForThreadCompletion(m_threadId); 112 m_threadId = 0; 113 } 114} 115 116void CurlDownloadManager::stopThreadIfIdle() 117{ 118 MutexLocker locker(m_mutex); 119 120 if (!getActiveDownloadCount() && !getPendingDownloadCount()) 121 setRunThread(false); 122} 123 124void CurlDownloadManager::updateHandleList() 125{ 126 MutexLocker locker(m_mutex); 127 128 // Remove curl easy handles from multi list 129 int size = m_removedHandleList.size(); 130 for (int i = 0; i < size; i++) { 131 removeFromCurl(m_removedHandleList[0]); 132 m_removedHandleList.remove(0); 133 } 134 135 // Add pending curl easy handles to multi list 136 size = m_pendingHandleList.size(); 137 for (int i = 0; i < size; i++) { 138 addToCurl(m_pendingHandleList[0]); 139 m_pendingHandleList.remove(0); 140 } 141} 142 143bool CurlDownloadManager::addToCurl(CURL* curlHandle) 144{ 145 CURLMcode retval = curl_multi_add_handle(m_curlMultiHandle, curlHandle); 146 if (retval == CURLM_OK) { 147 m_activeHandleList.append(curlHandle); 148 return true; 149 } 150 return false; 151} 152 153bool CurlDownloadManager::removeFromCurl(CURL* curlHandle) 154{ 155 int handlePos = m_activeHandleList.find(curlHandle); 156 157 if (handlePos < 0) 158 return true; 159 160 CURLMcode retval = curl_multi_remove_handle(m_curlMultiHandle, curlHandle); 161 if (retval == CURLM_OK) { 162 m_activeHandleList.remove(handlePos); 163 curl_easy_cleanup(curlHandle); 164 return true; 165 } 166 return false; 167} 168 169void CurlDownloadManager::downloadThread(void* data) 170{ 171 CurlDownloadManager* downloadManager = reinterpret_cast<CurlDownloadManager*>(data); 172 173 while (downloadManager->runThread()) { 174 175 downloadManager->updateHandleList(); 176 177 // Retry 'select' if it was interrupted by a process signal. 178 int rc = 0; 179 do { 180 fd_set fdread; 181 fd_set fdwrite; 182 fd_set fdexcep; 183 184 int maxfd = 0; 185 186 const int selectTimeoutMS = 5; 187 188 struct timeval timeout; 189 timeout.tv_sec = 0; 190 timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds 191 192 FD_ZERO(&fdread); 193 FD_ZERO(&fdwrite); 194 FD_ZERO(&fdexcep); 195 curl_multi_fdset(downloadManager->getMultiHandle(), &fdread, &fdwrite, &fdexcep, &maxfd); 196 // When the 3 file descriptors are empty, winsock will return -1 197 // and bail out, stopping the file download. So make sure we 198 // have valid file descriptors before calling select. 199 if (maxfd >= 0) 200 rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); 201 } while (rc == -1 && errno == EINTR); 202 203 int activeDownloadCount = 0; 204 while (curl_multi_perform(downloadManager->getMultiHandle(), &activeDownloadCount) == CURLM_CALL_MULTI_PERFORM) { } 205 206 int messagesInQueue = 0; 207 CURLMsg* msg = curl_multi_info_read(downloadManager->getMultiHandle(), &messagesInQueue); 208 209 if (!msg) 210 continue; 211 212 CurlDownload* download = 0; 213 CURLcode err = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &download); 214 215 if (msg->msg == CURLMSG_DONE) { 216 if (msg->data.result == CURLE_OK) 217 callOnMainThread(MainThreadTask(CurlDownload::downloadFinishedCallback, download)); 218 else 219 callOnMainThread(MainThreadTask(CurlDownload::downloadFailedCallback, download)); 220 221 downloadManager->removeFromCurl(msg->easy_handle); 222 } 223 224 downloadManager->stopThreadIfIdle(); 225 } 226} 227 228// CurlDownload -------------------------------------------------------------------------- 229 230CurlDownloadManager CurlDownload::m_downloadManager; 231 232CurlDownload::CurlDownload() 233: m_curlHandle(0) 234, m_customHeaders(0) 235, m_url(0) 236, m_tempHandle(invalidPlatformFileHandle) 237, m_deletesFileUponFailure(false) 238, m_listener(0) 239{ 240} 241 242CurlDownload::~CurlDownload() 243{ 244 MutexLocker locker(m_mutex); 245 246 if (m_url) 247 fastFree(m_url); 248 249 if (m_customHeaders) 250 curl_slist_free_all(m_customHeaders); 251 252 closeFile(); 253 moveFileToDestination(); 254} 255 256void CurlDownload::init(CurlDownloadListener* listener, const URL& url) 257{ 258 if (!listener) 259 return; 260 261 MutexLocker locker(m_mutex); 262 263 m_curlHandle = curl_easy_init(); 264 265 String urlStr = url.string(); 266 m_url = fastStrDup(urlStr.latin1().data()); 267 268 curl_easy_setopt(m_curlHandle, CURLOPT_URL, m_url); 269 curl_easy_setopt(m_curlHandle, CURLOPT_PRIVATE, this); 270 curl_easy_setopt(m_curlHandle, CURLOPT_WRITEFUNCTION, writeCallback); 271 curl_easy_setopt(m_curlHandle, CURLOPT_WRITEDATA, this); 272 curl_easy_setopt(m_curlHandle, CURLOPT_HEADERFUNCTION, headerCallback); 273 curl_easy_setopt(m_curlHandle, CURLOPT_WRITEHEADER, this); 274 curl_easy_setopt(m_curlHandle, CURLOPT_FOLLOWLOCATION, 1); 275 curl_easy_setopt(m_curlHandle, CURLOPT_MAXREDIRS, 10); 276 curl_easy_setopt(m_curlHandle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 277 278 const char* certPath = getenv("CURL_CA_BUNDLE_PATH"); 279 if (certPath) 280 curl_easy_setopt(m_curlHandle, CURLOPT_CAINFO, certPath); 281 282 CURLSH* curlsh = ResourceHandleManager::sharedInstance()->getCurlShareHandle(); 283 if (curlsh) 284 curl_easy_setopt(m_curlHandle, CURLOPT_SHARE, curlsh); 285 286 m_listener = listener; 287} 288 289void CurlDownload::init(CurlDownloadListener* listener, ResourceHandle*, const ResourceRequest& request, const ResourceResponse&) 290{ 291 if (!listener) 292 return; 293 294 MutexLocker locker(m_mutex); 295 296 URL url(ParsedURLString, request.url()); 297 298 init(listener, url); 299 300 addHeaders(request); 301} 302 303bool CurlDownload::start() 304{ 305 return m_downloadManager.add(m_curlHandle); 306} 307 308bool CurlDownload::cancel() 309{ 310 return m_downloadManager.remove(m_curlHandle); 311} 312 313String CurlDownload::getTempPath() const 314{ 315 MutexLocker locker(m_mutex); 316 return m_tempPath; 317} 318 319String CurlDownload::getUrl() const 320{ 321 MutexLocker locker(m_mutex); 322 return String(m_url); 323} 324 325ResourceResponse CurlDownload::getResponse() const 326{ 327 MutexLocker locker(m_mutex); 328 return m_response; 329} 330 331void CurlDownload::closeFile() 332{ 333 MutexLocker locker(m_mutex); 334 335 if (m_tempHandle != invalidPlatformFileHandle) { 336 WebCore::closeFile(m_tempHandle); 337 m_tempHandle = invalidPlatformFileHandle; 338 } 339} 340 341void CurlDownload::moveFileToDestination() 342{ 343 if (m_destination.isEmpty()) 344 return; 345 346 ::MoveFileEx(m_tempPath.charactersWithNullTermination().data(), m_destination.charactersWithNullTermination().data(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING); 347} 348 349void CurlDownload::writeDataToFile(const char* data, int size) 350{ 351 if (m_tempPath.isEmpty()) 352 m_tempPath = openTemporaryFile("download", m_tempHandle); 353 354 if (m_tempHandle != invalidPlatformFileHandle) 355 writeToFile(m_tempHandle, data, size); 356} 357 358void CurlDownload::addHeaders(const ResourceRequest& request) 359{ 360 if (request.httpHeaderFields().size() > 0) { 361 struct curl_slist* headers = 0; 362 363 HTTPHeaderMap customHeaders = request.httpHeaderFields(); 364 HTTPHeaderMap::const_iterator end = customHeaders.end(); 365 for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) { 366 const String& value = it->value; 367 String headerString(it->key); 368 if (value.isEmpty()) 369 // Insert the ; to tell curl that this header has an empty value. 370 headerString.append(";"); 371 else { 372 headerString.append(": "); 373 headerString.append(value); 374 } 375 CString headerLatin1 = headerString.latin1(); 376 headers = curl_slist_append(headers, headerLatin1.data()); 377 } 378 379 if (headers) { 380 curl_easy_setopt(m_curlHandle, CURLOPT_HTTPHEADER, headers); 381 m_customHeaders = headers; 382 } 383 } 384} 385 386void CurlDownload::didReceiveHeader(const String& header) 387{ 388 MutexLocker locker(m_mutex); 389 390 if (header == "\r\n" || header == "\n") { 391 392 long httpCode = 0; 393 CURLcode err = curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &httpCode); 394 395 if (httpCode >= 200 && httpCode < 300) { 396 const char* url = 0; 397 err = curl_easy_getinfo(m_curlHandle, CURLINFO_EFFECTIVE_URL, &url); 398 m_response.setURL(URL(ParsedURLString, url)); 399 400 m_response.setMimeType(extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType))); 401 m_response.setTextEncodingName(extractCharsetFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType))); 402 m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(m_response.httpHeaderField(HTTPHeaderName::ContentDisposition))); 403 404 callOnMainThread(MainThreadTask(receivedResponseCallback, this)); 405 } 406 } else { 407 int splitPos = header.find(":"); 408 if (splitPos != -1) 409 m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace()); 410 } 411} 412 413void CurlDownload::didReceiveData(void* data, int size) 414{ 415 MutexLocker locker(m_mutex); 416 417 callOnMainThread(MainThreadTask(receivedDataCallback, this, size)); 418 419 writeDataToFile(static_cast<const char*>(data), size); 420} 421 422void CurlDownload::didReceiveResponse() 423{ 424 if (m_listener) 425 m_listener->didReceiveResponse(); 426} 427 428void CurlDownload::didReceiveDataOfLength(int size) 429{ 430 if (m_listener) 431 m_listener->didReceiveDataOfLength(size); 432} 433 434void CurlDownload::didFinish() 435{ 436 closeFile(); 437 moveFileToDestination(); 438 439 if (m_listener) 440 m_listener->didFinish(); 441} 442 443void CurlDownload::didFail() 444{ 445 MutexLocker locker(m_mutex); 446 447 closeFile(); 448 449 if (m_deletesFileUponFailure) 450 deleteFile(m_tempPath); 451 452 if (m_listener) 453 m_listener->didFail(); 454} 455 456size_t CurlDownload::writeCallback(void* ptr, size_t size, size_t nmemb, void* data) 457{ 458 size_t totalSize = size * nmemb; 459 CurlDownload* download = reinterpret_cast<CurlDownload*>(data); 460 461 if (download) 462 download->didReceiveData(ptr, totalSize); 463 464 return totalSize; 465} 466 467size_t CurlDownload::headerCallback(char* ptr, size_t size, size_t nmemb, void* data) 468{ 469 size_t totalSize = size * nmemb; 470 CurlDownload* download = reinterpret_cast<CurlDownload*>(data); 471 472 String header = String::fromUTF8WithLatin1Fallback(static_cast<const char*>(ptr), totalSize); 473 474 if (download) 475 download->didReceiveHeader(header); 476 477 return totalSize; 478} 479 480void CurlDownload::downloadFinishedCallback(CurlDownload* download) 481{ 482 if (download) 483 download->didFinish(); 484} 485 486void CurlDownload::downloadFailedCallback(CurlDownload* download) 487{ 488 if (download) 489 download->didFail(); 490} 491 492void CurlDownload::receivedDataCallback(CurlDownload* download, int size) 493{ 494 if (download) 495 download->didReceiveDataOfLength(size); 496} 497 498void CurlDownload::receivedResponseCallback(CurlDownload* download) 499{ 500 if (download) 501 download->didReceiveResponse(); 502} 503 504} 505 506#endif 507