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