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