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