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