Home | History | Annotate | Download | only in curl
      1 /*
      2  * Copyright (C) 2004, 2006 Apple Computer, Inc.  All rights reserved.
      3  * Copyright (C) 2006 Michael Emmel mike.emmel (at) gmail.com
      4  * Copyright (C) 2007 Alp Toker <alp (at) 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 (at) 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 
     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 
     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.
    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 
    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 
    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 
    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 
    145 void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
    146 {
    147     m_cookieJarFileName = fastStrDup(cookieJarFileName);
    148 }
    149 
    150 ResourceHandleManager* ResourceHandleManager::sharedInstance()
    151 {
    152     static ResourceHandleManager* sharedInstance = 0;
    153     if (!sharedInstance)
    154         sharedInstance = new ResourceHandleManager();
    155     return sharedInstance;
    156 }
    157 
    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
    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  */
    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 */
    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 
    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 
    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 
    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 
    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  */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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