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