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