ResourceHandleManager.cpp 23.8 KB
Newer Older
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
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (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"
mjs's avatar
mjs committed
33
#include "ResourceHandleManager.h"
34

35
#include "Base64.h"
kjk's avatar
kjk committed
36
#include "CString.h"
37
#include "HTTPParsers.h"
38
#include "MIMETypeRegistry.h"
kevino's avatar
kevino committed
39
#include "NotImplemented.h"
40
#include "ResourceError.h"
mjs's avatar
mjs committed
41
42
#include "ResourceHandle.h"
#include "ResourceHandleInternal.h"
43
#include "TextEncoding.h"
44

45
#include <errno.h>
46
#include <wtf/Vector.h>
47

48
49
50
51
52
53
#if PLATFORM(GTK)
    #if GLIB_CHECK_VERSION(2,12,0)
        #define USE_GLIB_BASE64
    #endif
#endif

54
55
56
57
namespace WebCore {

const int selectTimeoutMS = 5;
const double pollTimeSeconds = 0.05;
58
const int maxRunningJobs = 5;
59

60
61
static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS");

mjs's avatar
mjs committed
62
ResourceHandleManager::ResourceHandleManager()
kjk's avatar
kjk committed
63
64
    : m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback)
    , m_cookieJarFileName(0)
65
    , m_runningJobs(0)
66
67
{
    curl_global_init(CURL_GLOBAL_ALL);
kjk's avatar
kjk committed
68
69
70
71
72
73
    m_curlMultiHandle = curl_multi_init();
    m_curlShareHandle = curl_share_init();
    curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
    curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
}

74
75
76
77
78
79
ResourceHandleManager::~ResourceHandleManager()
{
    curl_multi_cleanup(m_curlMultiHandle);
    curl_share_cleanup(m_curlShareHandle);
    if (m_cookieJarFileName)
        free(m_cookieJarFileName);
80
    curl_global_cleanup();
81
82
}

kjk's avatar
kjk committed
83
void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
84
{
kjk's avatar
kjk committed
85
    m_cookieJarFileName = strdup(cookieJarFileName);
86
87
}

kjk's avatar
kjk committed
88
ResourceHandleManager* ResourceHandleManager::sharedInstance()
89
{
90
    static ResourceHandleManager* sharedInstance = 0;
kjk's avatar
kjk committed
91
92
93
    if (!sharedInstance)
        sharedInstance = new ResourceHandleManager();
    return sharedInstance;
94
95
}

kjk's avatar
kjk committed
96
// called with data after all headers have been processed via headerCallback
97
static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data)
98
{
99
    ResourceHandle* job = static_cast<ResourceHandle*>(data);
mjs's avatar
mjs committed
100
    ResourceHandleInternal* d = job->getInternal();
101
102
    if (d->m_cancelled)
        return 0;
103
104
105
106
107
108

#if LIBCURL_VERSION_NUM > 0x071200
    // We should never be called when deferred loading is activated.
    ASSERT(!d->m_defersLoading);
#endif

109
    size_t totalSize = size * nmemb;
110
111
112
113
114
115
116
117
118
119

    // this shouldn't be necessary but apparently is. CURL writes the data
    // of html page even if it is a redirect that was handled internally
    // can be observed e.g. on gmail.com
    CURL* h = d->m_handle;
    long httpCode = 0;
    CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
    if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)
        return totalSize;

120
121
122
123
124
125
126
127
128
129
130
131
132
133
    // since the code in headerCallback will not have run for local files
    // the code to set the URL and fire didReceiveResponse is never run,
    // which means the ResourceLoader's response does not contain the URL.
    // Run the code here for local files to resolve the issue.
    // TODO: See if there is a better approach for handling this.
    if (!d->m_response.responseFired()) {
        const char* hdr;
        err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
        d->m_response.setUrl(KURL(hdr));
        if (d->client())
            d->client()->didReceiveResponse(job, d->m_response);
        d->m_response.setResponseFired(true);
    }

134
    if (d->client())
kjk's avatar
kjk committed
135
        d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);
136
137
138
    return totalSize;
}

139
140
141
142
143
144
145
146
147
/*
 * This is being called for each HTTP header in the response. This includes '\r\n'
 * for the last line of the header.
 *
 * We will add each HTTP Header to the ResourceResponse and on the termination
 * of the header (\r\n) we will parse Content-Type and Content-Disposition and
 * update the ResourceResponse and then send it away.
 *
 */
148
149
static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
{
150
151
    ResourceHandle* job = static_cast<ResourceHandle*>(data);
    ResourceHandleInternal* d = job->getInternal();
152
153
    if (d->m_cancelled)
        return 0;
154
155
156
157
158
159

#if LIBCURL_VERSION_NUM > 0x071200
    // We should never be called when deferred loading is activated.
    ASSERT(!d->m_defersLoading);
#endif

160
    size_t totalSize = size * nmemb;
161
162
163
164
165
166
    ResourceHandleClient* client = d->client();

    String header(static_cast<const char*>(ptr), totalSize);

    /*
     * a) We can finish and send the ResourceResponse
167
     * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse
168
169
170
     *
     * The HTTP standard requires to use \r\n but for compatibility it recommends to
     * accept also \n.
171
     */
172
    if (header == String("\r\n") || header == String("\n")) {
173
174
175
176
177
        CURL* h = d->m_handle;
        CURLcode err;

        double contentLength = 0;
        err = curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength);
178
        d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength));
179
180
181
182
183
184
185
186

        const char* hdr;
        err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
        d->m_response.setUrl(KURL(hdr));

        long httpCode = 0;
        err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
        d->m_response.setHTTPStatusCode(httpCode);
187

188
189
190
191
        d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField("Content-Type")));
        d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField("Content-Type")));
        d->m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(d->m_response.httpHeaderField("Content-Disposition")));

192
193
194
195
        // HTTP redirection
        if (httpCode >= 300 && httpCode < 400) {
            String location = d->m_response.httpHeaderField("location");
            if (!location.isEmpty()) {
darin@apple.com's avatar
darin@apple.com committed
196
                KURL newURL = KURL(job->request().url(), location);
197
198
199
200
201
202
203
204
205
206
207
208
209
210

                ResourceRequest redirectedRequest = job->request();
                redirectedRequest.setURL(newURL);
                if (client)
                    client->willSendRequest(job, redirectedRequest, d->m_response);

                d->m_request.setURL(newURL);

                return totalSize;
            }
        }

        if (client)
            client->didReceiveResponse(job, d->m_response);
211
212
        d->m_response.setResponseFired(true);

213
214
215
216
217
218
    } else {
        int splitPos = header.find(":");
        if (splitPos != -1)
            d->m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace());
    }

219
220
221
    return totalSize;
}

222
223
224
225
226
227
228
229
230
231
232
/* This is called to obtain HTTP POST or PUT data.
   Iterate through FormData elements and upload files.
   Carefully respect the given buffer size and fill the rest of the data at the next calls.
*/
size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data)
{
    ResourceHandle* job = static_cast<ResourceHandle*>(data);
    ResourceHandleInternal* d = job->getInternal();
    if (d->m_cancelled)
        return 0;

233
234
235
236
237
#if LIBCURL_VERSION_NUM > 0x071200
    // We should never be called when deferred loading is activated.
    ASSERT(!d->m_defersLoading);
#endif

238
    if (!size || !nmemb)
239
240
        return 0;

241
242
243
    if (!d->m_formDataStream.hasMoreElements())
        return 0;

244
    size_t sent = d->m_formDataStream.read(ptr, size, nmemb);
zecke@webkit.org's avatar
zecke@webkit.org committed
245

246
247
248
    // Something went wrong so cancel the job.
    if (!sent)
        job->cancel();
249
250
251
252

    return sent;
}

mjs's avatar
mjs committed
253
void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer)
254
{
255
    startScheduledJobs();
kjk's avatar
kjk committed
256

257
258
259
260
    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd = 0;
kjk's avatar
kjk committed
261
262
263
264
265

    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = selectTimeoutMS * 1000;       // select waits microseconds

266
267
    // Temporarily disable timers since signals may interrupt select(), raising EINTR errors on some platforms
    setDeferringTimers(true);
268
    int rc = 0;
269
270
271
272
273
    do {
        FD_ZERO(&fdread);
        FD_ZERO(&fdwrite);
        FD_ZERO(&fdexcep);
        curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
274
275
276
277
278
        // When the 3 file descriptors are empty, winsock will return -1
        // and bail out, stopping the file download. So make sure we
        // have valid file descriptors before calling select.
        if (maxfd >= 0)
            rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
279
    } while (rc == -1 && errno == EINTR);
280
    setDeferringTimers(false);
kjk's avatar
kjk committed
281
282
283

    if (-1 == rc) {
#ifndef NDEBUG
284
        perror("bad: select() returned -1: ");
kjk's avatar
kjk committed
285
#endif
286
287
        return;
    }
kjk's avatar
kjk committed
288
289

    int runningHandles = 0;
290
    while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { }
kjk's avatar
kjk committed
291
292
293
294
295
296
297
298
299
300
301
302

    // check the curl messages indicating completed transfers
    // and free their resources
    while (true) {
        int messagesInQueue;
        CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
        if (!msg)
            break;

        // find the node which has same d->m_handle as completed transfer
        CURL* handle = msg->easy_handle;
        ASSERT(handle);
303
304
305
        ResourceHandle* job = 0;
        CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
        ASSERT(CURLE_OK == err);
kjk's avatar
kjk committed
306
307
308
309
        ASSERT(job);
        if (!job)
            continue;
        ResourceHandleInternal* d = job->getInternal();
310
        ASSERT(d->m_handle == handle);
311
312
313
314
315
316
317
318
319

        if (d->m_cancelled) {
            removeFromCurl(job);
            continue;
        }

        if (CURLMSG_DONE != msg->msg)
            continue;

kjk's avatar
kjk committed
320
321
        if (CURLE_OK == msg->data.result) {
            if (d->client())
322
                d->client()->didFinishLoading(job);
kjk's avatar
kjk committed
323
        } else {
ggaren's avatar
ggaren committed
324
#ifndef NDEBUG
kjk's avatar
kjk committed
325
326
327
            char* url = 0;
            curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
            printf("Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
ggaren's avatar
ggaren committed
328
#endif
kjk's avatar
kjk committed
329
330
            if (d->client())
                d->client()->didFail(job, ResourceError());
331
332
        }

kjk's avatar
kjk committed
333
        removeFromCurl(job);
334
    }
kjk's avatar
kjk committed
335

336
337
338
    bool started = startScheduledJobs(); // new jobs might have been added in the meantime

    if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
ggaren's avatar
ggaren committed
339
        m_downloadTimer.startOneShot(pollTimeSeconds);
340
341
}

kjk's avatar
kjk committed
342
void ResourceHandleManager::removeFromCurl(ResourceHandle* job)
343
{
mjs's avatar
mjs committed
344
    ResourceHandleInternal* d = job->getInternal();
kjk's avatar
kjk committed
345
    ASSERT(d->m_handle);
346
347
    if (!d->m_handle)
        return;
348
    m_runningJobs--;
kjk's avatar
kjk committed
349
350
351
352
353
    curl_multi_remove_handle(m_curlMultiHandle, d->m_handle);
    curl_easy_cleanup(d->m_handle);
    d->m_handle = 0;
}

354
void ResourceHandleManager::setupPUT(ResourceHandle*, struct curl_slist**)
kjk's avatar
kjk committed
355
{
kevino's avatar
kevino committed
356
    notImplemented();
kjk's avatar
kjk committed
357
358
}

359
360
361
362
/* Calculate the length of the POST.
   Force chunked data transfer if size of files can't be obtained.
 */
void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers)
kjk's avatar
kjk committed
363
{
364
    ResourceHandleInternal* d = job->getInternal();
zecke@webkit.org's avatar
zecke@webkit.org committed
365
366
367
368
    Vector<FormDataElement> elements;
    // Fix crash when httpBody is null (see bug #16906).
    if (job->request().httpBody())
        elements = job->request().httpBody()->elements();
369
    size_t numElements = elements.size();
370

371
372
    if (!numElements)
        return;
373

374
375
376
377
378
379
    // Do not stream for simple POST data
    if (numElements == 1) {
        job->request().httpBody()->flatten(d->m_postBytes);
        if (d->m_postBytes.size() != 0) {
            curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);
            curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size());
380
            curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data());
381
382
383
        }
        return;
    }
384

385
    // Obtain the total size of the POST
386
387
388
389
390
391
392
393
394
395
396
397
    // The size of a curl_off_t could be different in WebKit and in cURL depending on
    // compilation flags of both. For CURLOPT_POSTFIELDSIZE_LARGE we have to pass the
    // right size or random data will be used as the size.
    static int expectedSizeOfCurlOffT = 0;
    if (!expectedSizeOfCurlOffT) {
        curl_version_info_data *infoData = curl_version_info(CURLVERSION_NOW);
        if (infoData->features & CURL_VERSION_LARGEFILE)
            expectedSizeOfCurlOffT = sizeof(long long);
        else
            expectedSizeOfCurlOffT = sizeof(int);
    }

398
399
400
401
402
#if COMPILER(MSVC)
    // work around compiler error in Visual Studio 2005.  It can't properly
    // handle math with 64-bit constant declarations.
#pragma warning(disable: 4307)
#endif
403
    static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1;
404
405
406
407
408
409
    curl_off_t size = 0;
    bool chunkedTransfer = false;
    for (size_t i = 0; i < numElements; i++) {
        FormDataElement element = elements[i];
        if (element.m_type == FormDataElement::encodedFile) {
            long long fileSizeResult;
410
            if (getFileSize(element.m_filename, fileSizeResult)) {
411
412
413
414
415
416
417
418
419
420
421
422
                if (fileSizeResult > maxCurlOffT) {
                    // File size is too big for specifying it to cURL
                    chunkedTransfer = true;
                    break;
                }
                size += fileSizeResult;
            } else {
                chunkedTransfer = true;
                break;
            }
        } else
            size += elements[i].m_data.size();
423
424
    }

425
426
427
428
429
    curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);

    // cURL guesses that we want chunked encoding as long as we specify the header
    if (chunkedTransfer)
        *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");
430
431
432
433
434
435
    else {
        if (sizeof(long long) == expectedSizeOfCurlOffT)
          curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (long long)size);
        else
          curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (int)size);
    }
436
437
438

    curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback);
    curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job);
439
440
}

mjs's avatar
mjs committed
441
void ResourceHandleManager::add(ResourceHandle* job)
442
443
444
{
    // we can be called from within curl, so to avoid re-entrancy issues
    // schedule this job to be added the next time we enter curl download loop
445
    m_resourceHandleList.append(job);
446
447
448
449
450
451
    if (!m_downloadTimer.isActive())
        m_downloadTimer.startOneShot(pollTimeSeconds);
}

bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job)
{
452
    int size = m_resourceHandleList.size();
453
    for (int i = 0; i < size; i++) {
454
455
        if (job == m_resourceHandleList[i]) {
            m_resourceHandleList.remove(i);
456
457
458
459
460
461
462
463
            return true;
        }
    }
    return false;
}

bool ResourceHandleManager::startScheduledJobs()
{
464
465
    // TODO: Create a separate stack of jobs for each domain.

466
    bool started = false;
467
468
469
    while (!m_resourceHandleList.isEmpty() && m_runningJobs < maxRunningJobs) {
        ResourceHandle* job = m_resourceHandleList[0];
        m_resourceHandleList.remove(0);
470
        startJob(job);
471
        started = true;
472
473
474
475
    }
    return started;
}

alp's avatar
alp committed
476
477
static void parseDataUrl(ResourceHandle* handle)
{
478
    ResourceHandleClient* client = handle->client();
alp's avatar
alp committed
479

480
481
482
    ASSERT(client);
    if (!client)
        return;
alp's avatar
alp committed
483

484
485
    String url = handle->request().url().string();
    ASSERT(url.startsWith("data:", false));
alp's avatar
alp committed
486

487
488
489
490
    int index = url.find(',');
    if (index == -1) {
        client->cannotShowURL(handle);
        return;
alp's avatar
alp committed
491
492
    }

493
494
    String mediaType = url.substring(5, index - 5);
    String data = url.substring(index + 1);
alp's avatar
alp committed
495

496
497
498
    bool base64 = mediaType.endsWith(";base64", false);
    if (base64)
        mediaType = mediaType.left(mediaType.length() - 7);
alp's avatar
alp committed
499

500
501
    if (mediaType.isEmpty())
        mediaType = "text/plain;charset=US-ASCII";
alp's avatar
alp committed
502

503
504
    String mimeType = extractMIMETypeFromMediaType(mediaType);
    String charset = extractCharsetFromMediaType(mediaType);
alp's avatar
alp committed
505

506
507
    ResourceResponse response;
    response.setMimeType(mimeType);
alp's avatar
alp committed
508

509
510
511
512
    if (base64) {
        data = decodeURLEscapeSequences(data);
        response.setTextEncodingName(charset);
        client->didReceiveResponse(handle, response);
alp's avatar
alp committed
513

514
515
        // Use the GLib Base64 if available, since WebCore's decoder isn't
        // general-purpose and fails on Acid3 test 97 (whitespace).
516
#ifdef USE_GLIB_BASE64
517
518
519
        size_t outLength = 0;
        char* outData = 0;
        outData = reinterpret_cast<char*>(g_base64_decode(data.utf8().data(), &outLength));
520
        if (outData && outLength > 0)
521
522
523
524
            client->didReceiveData(handle, outData, outLength, 0);
        g_free(outData);
#else
        Vector<char> out;
525
        if (base64Decode(data.latin1().data(), data.latin1().length(), out) && out.size() > 0)
526
            client->didReceiveData(handle, out.data(), out.size(), 0);
527
#endif
528
529
530
531
532
    } else {
        // We have to convert to UTF-16 early due to limitations in KURL
        data = decodeURLEscapeSequences(data, TextEncoding(charset));
        response.setTextEncodingName("UTF-16");
        client->didReceiveResponse(handle, response);
533
534
        if (data.length() > 0)
            client->didReceiveData(handle, reinterpret_cast<const char*>(data.characters()), data.length() * sizeof(UChar), 0);
535
    }
536

alp's avatar
alp committed
537
538
539
    client->didFinishLoading(handle);
}

540
541
542
543
544
545
546
547
548
549
550
void ResourceHandleManager::dispatchSynchronousJob(ResourceHandle* job)
{
    KURL kurl = job->request().url();

    if (kurl.protocolIs("data")) {
        parseDataUrl(job);
        return;
    }

    ResourceHandleInternal* handle = job->getInternal();

551
552
553
554
555
556
557
558
559
#if LIBCURL_VERSION_NUM > 0x071200
    // If defersLoading is true and we call curl_easy_perform
    // on a paused handle, libcURL would do the transfert anyway
    // and we would assert so force defersLoading to be false.
    handle->m_defersLoading = false;
#endif

    initializeHandle(job);

560
561
562
563
564
565
566
567
568
569
570
    // curl_easy_perform blocks until the transfert is finished.
    CURLcode ret =  curl_easy_perform(handle->m_handle);

    if (ret != 0) {
        ResourceError error(String(handle->m_url), ret, String(handle->m_url), String(curl_easy_strerror(ret)));
        handle->client()->didFail(job, error);
    }

    curl_easy_cleanup(handle->m_handle);
}

571
void ResourceHandleManager::startJob(ResourceHandle* job)
572
{
alp's avatar
alp committed
573
574
    KURL kurl = job->request().url();

darin@apple.com's avatar
darin@apple.com committed
575
    if (kurl.protocolIs("data")) {
alp's avatar
alp committed
576
577
578
579
        parseDataUrl(job);
        return;
    }

580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
    initializeHandle(job);

    m_runningJobs++;
    CURLMcode ret = curl_multi_add_handle(m_curlMultiHandle, job->getInternal()->m_handle);
    // don't call perform, because events must be async
    // timeout will occur and do curl_multi_perform
    if (ret && ret != CURLM_CALL_MULTI_PERFORM) {
#ifndef NDEBUG
        printf("Error %d starting job %s\n", ret, encodeWithURLEscapeSequences(job->request().url().string()).latin1().data());
#endif
        job->cancel();
        return;
    }
}

void ResourceHandleManager::initializeHandle(ResourceHandle* job)
{
    KURL kurl = job->request().url();

599
    // Remove any fragment part, otherwise curl will send it as part of the request.
600
    kurl.removeRef();
601

mjs's avatar
mjs committed
602
    ResourceHandleInternal* d = job->getInternal();
darin@apple.com's avatar
darin@apple.com committed
603
    String url = kurl.string();
604
605

    if (kurl.isLocalFile()) {
darin@apple.com's avatar
darin@apple.com committed
606
        String query = kurl.query();
607
608
609
610
        // Remove any query part sent to a local file.
        if (!query.isEmpty())
            url = url.left(url.find(query));
        // Determine the MIME type based on the path.
darin@apple.com's avatar
darin@apple.com committed
611
        d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));
612
613
    }

614
    d->m_handle = curl_easy_init();
615
616
617
618
619
620
621
622
623

#if LIBCURL_VERSION_NUM > 0x071200
    if (d->m_defersLoading) {
        CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL);
        // If we did not pause the handle, we would ASSERT in the
        // header callback. So just assert here.
        ASSERT(error == CURLE_OK);
    }
#endif
624
625
626
627
#ifndef NDEBUG
    if (getenv("DEBUG_CURL"))
        curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1);
#endif
628
    curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job);
kjk's avatar
kjk committed
629
    curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer);
630
631
632
633
    curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback);
    curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job);
    curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback);
    curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job);
634
    curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1);
635
636
637
    curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10);
    curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
kjk's avatar
kjk committed
638
    curl_easy_setopt(d->m_handle, CURLOPT_SHARE, m_curlShareHandle);
639
    curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes
640
641
    // FIXME: Enable SSL verification when we have a way of shipping certs
    // and/or reporting SSL errors to the user.
642
643
    if (ignoreSSLErrors)
        curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false);
kjk's avatar
kjk committed
644
645
646
647
648
    // enable gzip and deflate through Accept-Encoding:
    curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, "");

    // url must remain valid through the request
    ASSERT(!d->m_url);
649
650

    // url is in ASCII so latin1() will only convert it to char* without character translation.
darin@apple.com's avatar
darin@apple.com committed
651
    d->m_url = strdup(url.latin1().data());
kjk's avatar
kjk committed
652
653
654
655
656
657
658
    curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url);

    if (m_cookieJarFileName) {
        curl_easy_setopt(d->m_handle, CURLOPT_COOKIEFILE, m_cookieJarFileName);
        curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName);
    }

659
    struct curl_slist* headers = 0;
660
661
    if (job->request().httpHeaderFields().size() > 0) {
        HTTPHeaderMap customHeaders = job->request().httpHeaderFields();
kjk's avatar
kjk committed
662
663
664
665
        HTTPHeaderMap::const_iterator end = customHeaders.end();
        for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {
            String key = it->first;
            String value = it->second;
666
667
668
669
670
            String headerString(key);
            headerString.append(": ");
            headerString.append(value);
            CString headerLatin1 = headerString.latin1();
            headers = curl_slist_append(headers, headerLatin1.data());
kjk's avatar
kjk committed
671
672
673
        }
    }

674
    if ("GET" == job->request().httpMethod())
675
        curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE);
676
    else if ("POST" == job->request().httpMethod())
677
        setupPOST(job, &headers);
678
    else if ("PUT" == job->request().httpMethod())
679
        setupPUT(job, &headers);
680
    else if ("HEAD" == job->request().httpMethod())
kjk's avatar
kjk committed
681
682
        curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE);

683
684
685
686
    if (headers) {
        curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);
        d->m_customHeaders = headers;
    }
687
688
}

mjs's avatar
mjs committed
689
void ResourceHandleManager::cancel(ResourceHandle* job)
690
{
691
692
    if (removeScheduledJob(job))
        return;
693

694
695
696
697
    ResourceHandleInternal* d = job->getInternal();
    d->m_cancelled = true;
    if (!m_downloadTimer.isActive())
        m_downloadTimer.startOneShot(pollTimeSeconds);
698
699
700
}

} // namespace WebCore