• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2004, 2006 Apple Computer, Inc.  All rights reserved.
3  * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
4  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
5  * Copyright (C) 2007 Holger Hans Peter Freyther
6  * Copyright (C) 2008 Collabora Ltd.
7  * Copyright (C) 2008 Nuanti Ltd.
8  * Copyright (C) 2009 Appcelerator Inc.
9  * Copyright (C) 2009 Brent Fulgham <bfulgham@webkit.org>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
29  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include "config.h"
35 #include "ResourceHandleManager.h"
36 
37 #include "DataURL.h"
38 #include "HTTPParsers.h"
39 #include "MIMETypeRegistry.h"
40 #include "NotImplemented.h"
41 #include "ResourceError.h"
42 #include "ResourceHandle.h"
43 #include "ResourceHandleInternal.h"
44 
45 #include <errno.h>
46 #include <stdio.h>
47 #if USE(CF)
48 #include <wtf/RetainPtr.h>
49 #endif
50 #include <wtf/Threading.h>
51 #include <wtf/Vector.h>
52 #include <wtf/text/CString.h>
53 
54 #if !OS(WINDOWS)
55 #include <sys/param.h>
56 #define MAX_PATH MAXPATHLEN
57 #endif
58 
59 namespace WebCore {
60 
61 const int selectTimeoutMS = 5;
62 const double pollTimeSeconds = 0.05;
63 const int maxRunningJobs = 5;
64 
65 static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS");
66 
certificatePath()67 static CString certificatePath()
68 {
69 #if USE(CF)
70     CFBundleRef webKitBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebKit"));
71     if (webKitBundle) {
72         RetainPtr<CFURLRef> certURLRef(AdoptCF, CFBundleCopyResourceURL(webKitBundle, CFSTR("cacert"), CFSTR("pem"), CFSTR("certificates")));
73         if (certURLRef) {
74             char path[MAX_PATH];
75             CFURLGetFileSystemRepresentation(certURLRef.get(), false, reinterpret_cast<UInt8*>(path), MAX_PATH);
76             return path;
77         }
78     }
79 #endif
80     char* envPath = getenv("CURL_CA_BUNDLE_PATH");
81     if (envPath)
82        return envPath;
83 
84     return CString();
85 }
86 
sharedResourceMutex(curl_lock_data data)87 static Mutex* sharedResourceMutex(curl_lock_data data) {
88     DEFINE_STATIC_LOCAL(Mutex, cookieMutex, ());
89     DEFINE_STATIC_LOCAL(Mutex, dnsMutex, ());
90     DEFINE_STATIC_LOCAL(Mutex, shareMutex, ());
91 
92     switch (data) {
93         case CURL_LOCK_DATA_COOKIE:
94             return &cookieMutex;
95         case CURL_LOCK_DATA_DNS:
96             return &dnsMutex;
97         case CURL_LOCK_DATA_SHARE:
98             return &shareMutex;
99         default:
100             ASSERT_NOT_REACHED();
101             return NULL;
102     }
103 }
104 
105 // libcurl does not implement its own thread synchronization primitives.
106 // these two functions provide mutexes for cookies, and for the global DNS
107 // cache.
curl_lock_callback(CURL * handle,curl_lock_data data,curl_lock_access access,void * userPtr)108 static void curl_lock_callback(CURL* handle, curl_lock_data data, curl_lock_access access, void* userPtr)
109 {
110     if (Mutex* mutex = sharedResourceMutex(data))
111         mutex->lock();
112 }
113 
curl_unlock_callback(CURL * handle,curl_lock_data data,void * userPtr)114 static void curl_unlock_callback(CURL* handle, curl_lock_data data, void* userPtr)
115 {
116     if (Mutex* mutex = sharedResourceMutex(data))
117         mutex->unlock();
118 }
119 
ResourceHandleManager()120 ResourceHandleManager::ResourceHandleManager()
121     : m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback)
122     , m_cookieJarFileName(0)
123     , m_certificatePath (certificatePath())
124     , m_runningJobs(0)
125 
126 {
127     curl_global_init(CURL_GLOBAL_ALL);
128     m_curlMultiHandle = curl_multi_init();
129     m_curlShareHandle = curl_share_init();
130     curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
131     curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
132     curl_share_setopt(m_curlShareHandle, CURLSHOPT_LOCKFUNC, curl_lock_callback);
133     curl_share_setopt(m_curlShareHandle, CURLSHOPT_UNLOCKFUNC, curl_unlock_callback);
134 }
135 
~ResourceHandleManager()136 ResourceHandleManager::~ResourceHandleManager()
137 {
138     curl_multi_cleanup(m_curlMultiHandle);
139     curl_share_cleanup(m_curlShareHandle);
140     if (m_cookieJarFileName)
141         fastFree(m_cookieJarFileName);
142     curl_global_cleanup();
143 }
144 
setCookieJarFileName(const char * cookieJarFileName)145 void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
146 {
147     m_cookieJarFileName = fastStrDup(cookieJarFileName);
148 }
149 
sharedInstance()150 ResourceHandleManager* ResourceHandleManager::sharedInstance()
151 {
152     static ResourceHandleManager* sharedInstance = 0;
153     if (!sharedInstance)
154         sharedInstance = new ResourceHandleManager();
155     return sharedInstance;
156 }
157 
handleLocalReceiveResponse(CURL * handle,ResourceHandle * job,ResourceHandleInternal * d)158 static void handleLocalReceiveResponse (CURL* handle, ResourceHandle* job, ResourceHandleInternal* d)
159 {
160     // since the code in headerCallback will not have run for local files
161     // the code to set the URL and fire didReceiveResponse is never run,
162     // which means the ResourceLoader's response does not contain the URL.
163     // Run the code here for local files to resolve the issue.
164     // TODO: See if there is a better approach for handling this.
165      const char* hdr;
166      CURLcode err = curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &hdr);
167      ASSERT_UNUSED(err, CURLE_OK == err);
168      d->m_response.setURL(KURL(ParsedURLString, hdr));
169      if (d->client())
170          d->client()->didReceiveResponse(job, d->m_response);
171      d->m_response.setResponseFired(true);
172 }
173 
174 
175 // called with data after all headers have been processed via headerCallback
writeCallback(void * ptr,size_t size,size_t nmemb,void * data)176 static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data)
177 {
178     ResourceHandle* job = static_cast<ResourceHandle*>(data);
179     ResourceHandleInternal* d = job->getInternal();
180     if (d->m_cancelled)
181         return 0;
182 
183 #if LIBCURL_VERSION_NUM > 0x071200
184     // We should never be called when deferred loading is activated.
185     ASSERT(!d->m_defersLoading);
186 #endif
187 
188     size_t totalSize = size * nmemb;
189 
190     // this shouldn't be necessary but apparently is. CURL writes the data
191     // of html page even if it is a redirect that was handled internally
192     // can be observed e.g. on gmail.com
193     CURL* h = d->m_handle;
194     long httpCode = 0;
195     CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
196     if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)
197         return totalSize;
198 
199     if (!d->m_response.responseFired()) {
200         handleLocalReceiveResponse(h, job, d);
201         if (d->m_cancelled)
202             return 0;
203     }
204 
205     if (d->client())
206         d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);
207     return totalSize;
208 }
209 
210 /*
211  * This is being called for each HTTP header in the response. This includes '\r\n'
212  * for the last line of the header.
213  *
214  * We will add each HTTP Header to the ResourceResponse and on the termination
215  * of the header (\r\n) we will parse Content-Type and Content-Disposition and
216  * update the ResourceResponse and then send it away.
217  *
218  */
headerCallback(char * ptr,size_t size,size_t nmemb,void * data)219 static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
220 {
221     ResourceHandle* job = static_cast<ResourceHandle*>(data);
222     ResourceHandleInternal* d = job->getInternal();
223     if (d->m_cancelled)
224         return 0;
225 
226 #if LIBCURL_VERSION_NUM > 0x071200
227     // We should never be called when deferred loading is activated.
228     ASSERT(!d->m_defersLoading);
229 #endif
230 
231     size_t totalSize = size * nmemb;
232     ResourceHandleClient* client = d->client();
233 
234     String header(static_cast<const char*>(ptr), totalSize);
235 
236     /*
237      * a) We can finish and send the ResourceResponse
238      * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse
239      *
240      * The HTTP standard requires to use \r\n but for compatibility it recommends to
241      * accept also \n.
242      */
243     if (header == String("\r\n") || header == String("\n")) {
244         CURL* h = d->m_handle;
245         CURLcode err;
246 
247         double contentLength = 0;
248         err = curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength);
249         d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength));
250 
251         const char* hdr;
252         err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
253         d->m_response.setURL(KURL(ParsedURLString, hdr));
254 
255         long httpCode = 0;
256         err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
257         d->m_response.setHTTPStatusCode(httpCode);
258 
259         d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField("Content-Type")));
260         d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField("Content-Type")));
261         d->m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(d->m_response.httpHeaderField("Content-Disposition")));
262 
263         // HTTP redirection
264         if (httpCode >= 300 && httpCode < 400) {
265             String location = d->m_response.httpHeaderField("location");
266             if (!location.isEmpty()) {
267                 KURL newURL = KURL(job->firstRequest().url(), location);
268 
269                 ResourceRequest redirectedRequest = job->firstRequest();
270                 redirectedRequest.setURL(newURL);
271                 if (client)
272                     client->willSendRequest(job, redirectedRequest, d->m_response);
273 
274                 d->m_firstRequest.setURL(newURL);
275 
276                 return totalSize;
277             }
278         }
279 
280         if (client)
281             client->didReceiveResponse(job, d->m_response);
282         d->m_response.setResponseFired(true);
283 
284     } else {
285         int splitPos = header.find(":");
286         if (splitPos != -1)
287             d->m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace());
288     }
289 
290     return totalSize;
291 }
292 
293 /* This is called to obtain HTTP POST or PUT data.
294    Iterate through FormData elements and upload files.
295    Carefully respect the given buffer size and fill the rest of the data at the next calls.
296 */
readCallback(void * ptr,size_t size,size_t nmemb,void * data)297 size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data)
298 {
299     ResourceHandle* job = static_cast<ResourceHandle*>(data);
300     ResourceHandleInternal* d = job->getInternal();
301 
302     if (d->m_cancelled)
303         return 0;
304 
305 #if LIBCURL_VERSION_NUM > 0x071200
306     // We should never be called when deferred loading is activated.
307     ASSERT(!d->m_defersLoading);
308 #endif
309 
310     if (!size || !nmemb)
311         return 0;
312 
313     if (!d->m_formDataStream.hasMoreElements())
314         return 0;
315 
316     size_t sent = d->m_formDataStream.read(ptr, size, nmemb);
317 
318     // Something went wrong so cancel the job.
319     if (!sent)
320         job->cancel();
321 
322     return sent;
323 }
324 
downloadTimerCallback(Timer<ResourceHandleManager> * timer)325 void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer)
326 {
327     startScheduledJobs();
328 
329     fd_set fdread;
330     fd_set fdwrite;
331     fd_set fdexcep;
332     int maxfd = 0;
333 
334     struct timeval timeout;
335     timeout.tv_sec = 0;
336     timeout.tv_usec = selectTimeoutMS * 1000;       // select waits microseconds
337 
338     // Retry 'select' if it was interrupted by a process signal.
339     int rc = 0;
340     do {
341         FD_ZERO(&fdread);
342         FD_ZERO(&fdwrite);
343         FD_ZERO(&fdexcep);
344         curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
345         // When the 3 file descriptors are empty, winsock will return -1
346         // and bail out, stopping the file download. So make sure we
347         // have valid file descriptors before calling select.
348         if (maxfd >= 0)
349             rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
350     } while (rc == -1 && errno == EINTR);
351 
352     if (-1 == rc) {
353 #ifndef NDEBUG
354         perror("bad: select() returned -1: ");
355 #endif
356         return;
357     }
358 
359     int runningHandles = 0;
360     while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { }
361 
362     // check the curl messages indicating completed transfers
363     // and free their resources
364     while (true) {
365         int messagesInQueue;
366         CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
367         if (!msg)
368             break;
369 
370         // find the node which has same d->m_handle as completed transfer
371         CURL* handle = msg->easy_handle;
372         ASSERT(handle);
373         ResourceHandle* job = 0;
374         CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
375         ASSERT_UNUSED(err, CURLE_OK == err);
376         ASSERT(job);
377         if (!job)
378             continue;
379         ResourceHandleInternal* d = job->getInternal();
380         ASSERT(d->m_handle == handle);
381 
382         if (d->m_cancelled) {
383             removeFromCurl(job);
384             continue;
385         }
386 
387         if (CURLMSG_DONE != msg->msg)
388             continue;
389 
390         if (CURLE_OK == msg->data.result) {
391             if (!d->m_response.responseFired()) {
392                 handleLocalReceiveResponse(d->m_handle, job, d);
393                 if (d->m_cancelled) {
394                     removeFromCurl(job);
395                     continue;
396                 }
397             }
398 
399             if (d->client())
400                 d->client()->didFinishLoading(job, 0);
401         } else {
402             char* url = 0;
403             curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
404 #ifndef NDEBUG
405             fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
406 #endif
407             if (d->client())
408                 d->client()->didFail(job, ResourceError(String(), msg->data.result, String(url), String(curl_easy_strerror(msg->data.result))));
409         }
410 
411         removeFromCurl(job);
412     }
413 
414     bool started = startScheduledJobs(); // new jobs might have been added in the meantime
415 
416     if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
417         m_downloadTimer.startOneShot(pollTimeSeconds);
418 }
419 
setProxyInfo(const String & host,unsigned long port,ProxyType type,const String & username,const String & password)420 void ResourceHandleManager::setProxyInfo(const String& host,
421                                          unsigned long port,
422                                          ProxyType type,
423                                          const String& username,
424                                          const String& password)
425 {
426     m_proxyType = type;
427 
428     if (!host.length()) {
429         m_proxy = String("");
430     } else {
431         String userPass;
432         if (username.length() || password.length())
433             userPass = username + ":" + password + "@";
434 
435         m_proxy = String("http://") + userPass + host + ":" + String::number(port);
436     }
437 }
438 
removeFromCurl(ResourceHandle * job)439 void ResourceHandleManager::removeFromCurl(ResourceHandle* job)
440 {
441     ResourceHandleInternal* d = job->getInternal();
442     ASSERT(d->m_handle);
443     if (!d->m_handle)
444         return;
445     m_runningJobs--;
446     curl_multi_remove_handle(m_curlMultiHandle, d->m_handle);
447     curl_easy_cleanup(d->m_handle);
448     d->m_handle = 0;
449     job->deref();
450 }
451 
setupPUT(ResourceHandle *,struct curl_slist **)452 void ResourceHandleManager::setupPUT(ResourceHandle*, struct curl_slist**)
453 {
454     notImplemented();
455 }
456 
457 /* Calculate the length of the POST.
458    Force chunked data transfer if size of files can't be obtained.
459  */
setupPOST(ResourceHandle * job,struct curl_slist ** headers)460 void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers)
461 {
462     ResourceHandleInternal* d = job->getInternal();
463     curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);
464     curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, 0);
465 
466     if (!job->firstRequest().httpBody())
467         return;
468 
469     Vector<FormDataElement> elements = job->firstRequest().httpBody()->elements();
470     size_t numElements = elements.size();
471     if (!numElements)
472         return;
473 
474     // Do not stream for simple POST data
475     if (numElements == 1) {
476         job->firstRequest().httpBody()->flatten(d->m_postBytes);
477         if (d->m_postBytes.size() != 0) {
478             curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size());
479             curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data());
480         }
481         return;
482     }
483 
484     // Obtain the total size of the POST
485     // The size of a curl_off_t could be different in WebKit and in cURL depending on
486     // compilation flags of both. For CURLOPT_POSTFIELDSIZE_LARGE we have to pass the
487     // right size or random data will be used as the size.
488     static int expectedSizeOfCurlOffT = 0;
489     if (!expectedSizeOfCurlOffT) {
490         curl_version_info_data *infoData = curl_version_info(CURLVERSION_NOW);
491         if (infoData->features & CURL_VERSION_LARGEFILE)
492             expectedSizeOfCurlOffT = sizeof(long long);
493         else
494             expectedSizeOfCurlOffT = sizeof(int);
495     }
496 
497 #if COMPILER(MSVC)
498     // work around compiler error in Visual Studio 2005.  It can't properly
499     // handle math with 64-bit constant declarations.
500 #pragma warning(disable: 4307)
501 #endif
502     static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1;
503     curl_off_t size = 0;
504     bool chunkedTransfer = false;
505     for (size_t i = 0; i < numElements; i++) {
506         FormDataElement element = elements[i];
507         if (element.m_type == FormDataElement::encodedFile) {
508             long long fileSizeResult;
509             if (getFileSize(element.m_filename, fileSizeResult)) {
510                 if (fileSizeResult > maxCurlOffT) {
511                     // File size is too big for specifying it to cURL
512                     chunkedTransfer = true;
513                     break;
514                 }
515                 size += fileSizeResult;
516             } else {
517                 chunkedTransfer = true;
518                 break;
519             }
520         } else
521             size += elements[i].m_data.size();
522     }
523 
524     // cURL guesses that we want chunked encoding as long as we specify the header
525     if (chunkedTransfer)
526         *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");
527     else {
528         if (sizeof(long long) == expectedSizeOfCurlOffT)
529           curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (long long)size);
530         else
531           curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (int)size);
532     }
533 
534     curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback);
535     curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job);
536 }
537 
add(ResourceHandle * job)538 void ResourceHandleManager::add(ResourceHandle* job)
539 {
540     // we can be called from within curl, so to avoid re-entrancy issues
541     // schedule this job to be added the next time we enter curl download loop
542     job->ref();
543     m_resourceHandleList.append(job);
544     if (!m_downloadTimer.isActive())
545         m_downloadTimer.startOneShot(pollTimeSeconds);
546 }
547 
removeScheduledJob(ResourceHandle * job)548 bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job)
549 {
550     int size = m_resourceHandleList.size();
551     for (int i = 0; i < size; i++) {
552         if (job == m_resourceHandleList[i]) {
553             m_resourceHandleList.remove(i);
554             job->deref();
555             return true;
556         }
557     }
558     return false;
559 }
560 
startScheduledJobs()561 bool ResourceHandleManager::startScheduledJobs()
562 {
563     // TODO: Create a separate stack of jobs for each domain.
564 
565     bool started = false;
566     while (!m_resourceHandleList.isEmpty() && m_runningJobs < maxRunningJobs) {
567         ResourceHandle* job = m_resourceHandleList[0];
568         m_resourceHandleList.remove(0);
569         startJob(job);
570         started = true;
571     }
572     return started;
573 }
574 
dispatchSynchronousJob(ResourceHandle * job)575 void ResourceHandleManager::dispatchSynchronousJob(ResourceHandle* job)
576 {
577     KURL kurl = job->firstRequest().url();
578 
579     if (kurl.protocolIsData()) {
580         handleDataURL(job);
581         return;
582     }
583 
584     ResourceHandleInternal* handle = job->getInternal();
585 
586 #if LIBCURL_VERSION_NUM > 0x071200
587     // If defersLoading is true and we call curl_easy_perform
588     // on a paused handle, libcURL would do the transfert anyway
589     // and we would assert so force defersLoading to be false.
590     handle->m_defersLoading = false;
591 #endif
592 
593     initializeHandle(job);
594 
595     // curl_easy_perform blocks until the transfert is finished.
596     CURLcode ret =  curl_easy_perform(handle->m_handle);
597 
598     if (ret != 0) {
599         ResourceError error(String(handle->m_url), ret, String(handle->m_url), String(curl_easy_strerror(ret)));
600         handle->client()->didFail(job, error);
601     }
602 
603     curl_easy_cleanup(handle->m_handle);
604 }
605 
startJob(ResourceHandle * job)606 void ResourceHandleManager::startJob(ResourceHandle* job)
607 {
608     KURL kurl = job->firstRequest().url();
609 
610     if (kurl.protocolIsData()) {
611         handleDataURL(job);
612         return;
613     }
614 
615     initializeHandle(job);
616 
617     m_runningJobs++;
618     CURLMcode ret = curl_multi_add_handle(m_curlMultiHandle, job->getInternal()->m_handle);
619     // don't call perform, because events must be async
620     // timeout will occur and do curl_multi_perform
621     if (ret && ret != CURLM_CALL_MULTI_PERFORM) {
622 #ifndef NDEBUG
623         fprintf(stderr, "Error %d starting job %s\n", ret, encodeWithURLEscapeSequences(job->firstRequest().url().string()).latin1().data());
624 #endif
625         job->cancel();
626         return;
627     }
628 }
629 
initializeHandle(ResourceHandle * job)630 void ResourceHandleManager::initializeHandle(ResourceHandle* job)
631 {
632     KURL kurl = job->firstRequest().url();
633 
634     // Remove any fragment part, otherwise curl will send it as part of the request.
635     kurl.removeFragmentIdentifier();
636 
637     ResourceHandleInternal* d = job->getInternal();
638     String url = kurl.string();
639 
640     if (kurl.isLocalFile()) {
641         String query = kurl.query();
642         // Remove any query part sent to a local file.
643         if (!query.isEmpty()) {
644             int queryIndex = url.find(query);
645             if (queryIndex != -1)
646                 url = url.left(queryIndex - 1);
647         }
648         // Determine the MIME type based on the path.
649         d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));
650     }
651 
652     d->m_handle = curl_easy_init();
653 
654 #if LIBCURL_VERSION_NUM > 0x071200
655     if (d->m_defersLoading) {
656         CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL);
657         // If we did not pause the handle, we would ASSERT in the
658         // header callback. So just assert here.
659         ASSERT_UNUSED(error, error == CURLE_OK);
660     }
661 #endif
662 #ifndef NDEBUG
663     if (getenv("DEBUG_CURL"))
664         curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1);
665 #endif
666     curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job);
667     curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer);
668     curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback);
669     curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job);
670     curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback);
671     curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job);
672     curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1);
673     curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1);
674     curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10);
675     curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
676     curl_easy_setopt(d->m_handle, CURLOPT_SHARE, m_curlShareHandle);
677     curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes
678     // FIXME: Enable SSL verification when we have a way of shipping certs
679     // and/or reporting SSL errors to the user.
680     if (ignoreSSLErrors)
681         curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false);
682 
683     if (!m_certificatePath.isNull())
684        curl_easy_setopt(d->m_handle, CURLOPT_CAINFO, m_certificatePath.data());
685 
686     // enable gzip and deflate through Accept-Encoding:
687     curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, "");
688 
689     // url must remain valid through the request
690     ASSERT(!d->m_url);
691 
692     // url is in ASCII so latin1() will only convert it to char* without character translation.
693     d->m_url = fastStrDup(url.latin1().data());
694     curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url);
695 
696     if (m_cookieJarFileName) {
697         curl_easy_setopt(d->m_handle, CURLOPT_COOKIEFILE, m_cookieJarFileName);
698         curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName);
699     }
700 
701     struct curl_slist* headers = 0;
702     if (job->firstRequest().httpHeaderFields().size() > 0) {
703         HTTPHeaderMap customHeaders = job->firstRequest().httpHeaderFields();
704         HTTPHeaderMap::const_iterator end = customHeaders.end();
705         for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {
706             String key = it->first;
707             String value = it->second;
708             String headerString(key);
709             headerString.append(": ");
710             headerString.append(value);
711             CString headerLatin1 = headerString.latin1();
712             headers = curl_slist_append(headers, headerLatin1.data());
713         }
714     }
715 
716     if ("GET" == job->firstRequest().httpMethod())
717         curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE);
718     else if ("POST" == job->firstRequest().httpMethod())
719         setupPOST(job, &headers);
720     else if ("PUT" == job->firstRequest().httpMethod())
721         setupPUT(job, &headers);
722     else if ("HEAD" == job->firstRequest().httpMethod())
723         curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE);
724 
725     if (headers) {
726         curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);
727         d->m_customHeaders = headers;
728     }
729     // curl CURLOPT_USERPWD expects username:password
730     if (d->m_user.length() || d->m_pass.length()) {
731         String userpass = d->m_user + ":" + d->m_pass;
732         curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
733     }
734 
735     // Set proxy options if we have them.
736     if (m_proxy.length()) {
737         curl_easy_setopt(d->m_handle, CURLOPT_PROXY, m_proxy.utf8().data());
738         curl_easy_setopt(d->m_handle, CURLOPT_PROXYTYPE, m_proxyType);
739     }
740 }
741 
cancel(ResourceHandle * job)742 void ResourceHandleManager::cancel(ResourceHandle* job)
743 {
744     if (removeScheduledJob(job))
745         return;
746 
747     ResourceHandleInternal* d = job->getInternal();
748     d->m_cancelled = true;
749     if (!m_downloadTimer.isActive())
750         m_downloadTimer.startOneShot(pollTimeSeconds);
751 }
752 
753 } // namespace WebCore
754