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