root/trunk/WebCore/platform/network/curl/ResourceHandleManager.cpp

Revision 38455, 23.9 KB (checked in by zecke@webkit.org, 2 weeks ago)

Do not spit Curl errors into the stdout as this is used by DRT

We have failing tests results due the curl errors printed to stdout,
move them to stderr to be able to pass those tests.

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