6a44233e65f2d316aae496ca1e78e5e5884d6ac8
[WebKit-https.git] / WebCore / platform / network / curl / ResourceHandleManager.cpp
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
55 namespace WebCore {
56
57 const int selectTimeoutMS = 5;
58 const double pollTimeSeconds = 0.05;
59 const int maxRunningJobs = 5;
60
61 static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS");
62
63 ResourceHandleManager::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
75 ResourceHandleManager::~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
84 void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
85 {
86     m_cookieJarFileName = strdup(cookieJarFileName);
87 }
88
89 ResourceHandleManager* 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
98 static 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  */
149 static 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 */
227 size_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
254 void 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     // Retry 'select' if it was interrupted by a process signal.
268     int rc = 0;
269     do {
270         FD_ZERO(&fdread);
271         FD_ZERO(&fdwrite);
272         FD_ZERO(&fdexcep);
273         curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
274         // When the 3 file descriptors are empty, winsock will return -1
275         // and bail out, stopping the file download. So make sure we
276         // have valid file descriptors before calling select.
277         if (maxfd >= 0)
278             rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
279     } while (rc == -1 && errno == EINTR);
280
281     if (-1 == rc) {
282 #ifndef NDEBUG
283         perror("bad: select() returned -1: ");
284 #endif
285         return;
286     }
287
288     int runningHandles = 0;
289     while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { }
290
291     // check the curl messages indicating completed transfers
292     // and free their resources
293     while (true) {
294         int messagesInQueue;
295         CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
296         if (!msg)
297             break;
298
299         // find the node which has same d->m_handle as completed transfer
300         CURL* handle = msg->easy_handle;
301         ASSERT(handle);
302         ResourceHandle* job = 0;
303         CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
304         ASSERT(CURLE_OK == err);
305         ASSERT(job);
306         if (!job)
307             continue;
308         ResourceHandleInternal* d = job->getInternal();
309         ASSERT(d->m_handle == handle);
310
311         if (d->m_cancelled) {
312             removeFromCurl(job);
313             continue;
314         }
315
316         if (CURLMSG_DONE != msg->msg)
317             continue;
318
319         if (CURLE_OK == msg->data.result) {
320             if (d->client())
321                 d->client()->didFinishLoading(job);
322         } else {
323 #ifndef NDEBUG
324             char* url = 0;
325             curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
326             fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
327 #endif
328             if (d->client())
329                 d->client()->didFail(job, ResourceError());
330         }
331
332         removeFromCurl(job);
333     }
334
335     bool started = startScheduledJobs(); // new jobs might have been added in the meantime
336
337     if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
338         m_downloadTimer.startOneShot(pollTimeSeconds);
339 }
340
341 void ResourceHandleManager::removeFromCurl(ResourceHandle* job)
342 {
343     ResourceHandleInternal* d = job->getInternal();
344     ASSERT(d->m_handle);
345     if (!d->m_handle)
346         return;
347     m_runningJobs--;
348     curl_multi_remove_handle(m_curlMultiHandle, d->m_handle);
349     curl_easy_cleanup(d->m_handle);
350     d->m_handle = 0;
351     job->deref();
352 }
353
354 void ResourceHandleManager::setupPUT(ResourceHandle*, struct curl_slist**)
355 {
356     notImplemented();
357 }
358
359 /* Calculate the length of the POST.
360    Force chunked data transfer if size of files can't be obtained.
361  */
362 void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers)
363 {
364     ResourceHandleInternal* d = job->getInternal();
365     curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);
366     curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, 0);
367
368     if (!job->request().httpBody())
369         return;
370
371     Vector<FormDataElement> elements = job->request().httpBody()->elements();
372     size_t numElements = elements.size();
373     if (!numElements)
374         return;
375
376     // Do not stream for simple POST data
377     if (numElements == 1) {
378         job->request().httpBody()->flatten(d->m_postBytes);
379         if (d->m_postBytes.size() != 0) {
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 guesses that we want chunked encoding as long as we specify the header
427     if (chunkedTransfer)
428         *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");
429     else {
430         if (sizeof(long long) == expectedSizeOfCurlOffT)
431           curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (long long)size);
432         else
433           curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (int)size);
434     }
435
436     curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback);
437     curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job);
438 }
439
440 void ResourceHandleManager::add(ResourceHandle* job)
441 {
442     // we can be called from within curl, so to avoid re-entrancy issues
443     // schedule this job to be added the next time we enter curl download loop
444     job->ref();
445     m_resourceHandleList.append(job);
446     if (!m_downloadTimer.isActive())
447         m_downloadTimer.startOneShot(pollTimeSeconds);
448 }
449
450 bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job)
451 {
452     int size = m_resourceHandleList.size();
453     for (int i = 0; i < size; i++) {
454         if (job == m_resourceHandleList[i]) {
455             m_resourceHandleList.remove(i);
456             job->deref();
457             return true;
458         }
459     }
460     return false;
461 }
462
463 bool 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
477 static 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
541 void 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
572 void 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
596 void 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     }
659
660     struct curl_slist* headers = 0;
661     if (job->request().httpHeaderFields().size() > 0) {
662         HTTPHeaderMap customHeaders = job->request().httpHeaderFields();
663         HTTPHeaderMap::const_iterator end = customHeaders.end();
664         for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {
665             String key = it->first;
666             String value = it->second;
667             String headerString(key);
668             headerString.append(": ");
669             headerString.append(value);
670             CString headerLatin1 = headerString.latin1();
671             headers = curl_slist_append(headers, headerLatin1.data());
672         }
673     }
674
675     if ("GET" == job->request().httpMethod())
676         curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE);
677     else if ("POST" == job->request().httpMethod())
678         setupPOST(job, &headers);
679     else if ("PUT" == job->request().httpMethod())
680         setupPUT(job, &headers);
681     else if ("HEAD" == job->request().httpMethod())
682         curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE);
683
684     if (headers) {
685         curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);
686         d->m_customHeaders = headers;
687     }
688 }
689
690 void ResourceHandleManager::cancel(ResourceHandle* job)
691 {
692     if (removeScheduledJob(job))
693         return;
694
695     ResourceHandleInternal* d = job->getInternal();
696     d->m_cancelled = true;
697     if (!m_downloadTimer.isActive())
698         m_downloadTimer.startOneShot(pollTimeSeconds);
699 }
700
701 } // namespace WebCore