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