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
59namespace WebCore {
60
61const int selectTimeoutMS = 5;
62const double pollTimeSeconds = 0.05;
63const int maxRunningJobs = 5;
64
65static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS");
66
67static 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
87static 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.
108static 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
114static void curl_unlock_callback(CURL* handle, curl_lock_data data, void* userPtr)
115{
116    if (Mutex* mutex = sharedResourceMutex(data))
117        mutex->unlock();
118}
119
120ResourceHandleManager::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
136ResourceHandleManager::~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
145void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
146{
147    m_cookieJarFileName = fastStrDup(cookieJarFileName);
148}
149
150ResourceHandleManager* ResourceHandleManager::sharedInstance()
151{
152    static ResourceHandleManager* sharedInstance = 0;
153    if (!sharedInstance)
154        sharedInstance = new ResourceHandleManager();
155    return sharedInstance;
156}
157
158static 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
176static 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 */
219static 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*/
297size_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
325void 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
420void 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
439void 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
452void 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 */
460void 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
538void 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
548bool 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
561bool 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
575void 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
606void 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
630void 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
742void 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