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.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
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.
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.
36 #include "ResourceHandleManager.h"
38 #include "CredentialStorage.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"
47 #include "WebCoreBundleWin.h"
53 #include <wtf/RetainPtr.h>
55 #include <wtf/Threading.h>
56 #include <wtf/Vector.h>
57 #include <wtf/text/CString.h>
60 #include <sys/param.h>
61 #define MAX_PATH MAXPATHLEN
66 const int selectTimeoutMS = 5;
67 const double pollTimeSeconds = 0.05;
68 const int maxRunningJobs = 5;
70 static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS");
72 static CString certificatePath()
75 CFBundleRef webKitBundleRef = webKitBundle();
76 if (webKitBundleRef) {
77 RetainPtr<CFURLRef> certURLRef = adoptCF(CFBundleCopyResourceURL(webKitBundleRef, CFSTR("cacert"), CFSTR("pem"), CFSTR("certificates")));
80 CFURLGetFileSystemRepresentation(certURLRef.get(), false, reinterpret_cast<UInt8*>(path), MAX_PATH);
85 char* envPath = getenv("CURL_CA_BUNDLE_PATH");
92 static char* cookieJarPath()
94 char* cookieJarPath = getenv("CURL_COOKIE_JAR_PATH");
96 return fastStrDup(cookieJarPath);
98 return fastStrDup("cookies.dat");
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, ());
107 case CURL_LOCK_DATA_COOKIE:
109 case CURL_LOCK_DATA_DNS:
111 case CURL_LOCK_DATA_SHARE:
114 ASSERT_NOT_REACHED();
119 // libcurl does not implement its own thread synchronization primitives.
120 // these two functions provide mutexes for cookies, and for the global DNS
122 static void curl_lock_callback(CURL* /* handle */, curl_lock_data data, curl_lock_access /* access */, void* /* userPtr */)
124 if (Mutex* mutex = sharedResourceMutex(data))
128 static void curl_unlock_callback(CURL* /* handle */, curl_lock_data data, void* /* userPtr */)
130 if (Mutex* mutex = sharedResourceMutex(data))
134 inline static bool isHttpInfo(int statusCode)
136 return 100 <= statusCode && statusCode < 200;
139 inline static bool isHttpRedirect(int statusCode)
141 return 300 <= statusCode && statusCode < 400 && statusCode != 304;
144 inline static bool isHttpAuthentication(int statusCode)
146 return statusCode == 401;
149 ResourceHandleManager::ResourceHandleManager()
150 : m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback)
151 , m_cookieJarFileName(cookieJarPath())
152 , m_certificatePath (certificatePath())
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);
166 ResourceHandleManager::~ResourceHandleManager()
168 curl_multi_cleanup(m_curlMultiHandle);
169 curl_share_cleanup(m_curlShareHandle);
170 if (m_cookieJarFileName)
171 fastFree(m_cookieJarFileName);
172 curl_global_cleanup();
175 CURLSH* ResourceHandleManager::getCurlShareHandle() const
177 return m_curlShareHandle;
180 void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
182 m_cookieJarFileName = fastStrDup(cookieJarFileName);
185 const char* ResourceHandleManager::getCookieJarFileName() const
187 return m_cookieJarFileName;
190 ResourceHandleManager* ResourceHandleManager::sharedInstance()
192 static ResourceHandleManager* sharedInstance = 0;
194 sharedInstance = new ResourceHandleManager();
195 return sharedInstance;
198 static void handleLocalReceiveResponse (CURL* handle, ResourceHandle* job, ResourceHandleInternal* d)
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.
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));
210 d->client()->didReceiveResponse(job, d->m_response);
211 d->m_response.setResponseFired(true);
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)
218 ResourceHandle* job = static_cast<ResourceHandle*>(data);
219 ResourceHandleInternal* d = job->getInternal();
223 // We should never be called when deferred loading is activated.
224 ASSERT(!d->m_defersLoading);
226 size_t totalSize = size * nmemb;
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;
233 CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
234 if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)
237 if (!d->m_response.responseFired()) {
238 handleLocalReceiveResponse(h, job, d);
244 d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);
248 static bool isAppendableHeader(const String &key)
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",
264 "proxy-authenticate",
280 // Custom headers start with 'X-', and need no further checking.
281 if (key.startsWith("x-", /* caseSensitive */ false))
284 for (unsigned i = 0; appendableHeaders[i]; ++i)
285 if (equalIgnoringCase(key, appendableHeaders[i]))
291 static bool getProtectionSpace(CURL* h, const ResourceResponse& response, ProtectionSpace& protectionSpace)
296 err = curl_easy_getinfo(h, CURLINFO_PRIMARY_PORT, &port);
300 long availableAuth = CURLAUTH_NONE;
301 err = curl_easy_getinfo(h, CURLINFO_HTTPAUTH_AVAIL, &availableAuth);
306 err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &url);
310 KURL kurl(ParsedURLString, url);
312 String host = kurl.host();
313 String protocol = kurl.protocol();
317 String authHeader = response.httpHeaderField("WWW-Authenticate");
318 const String realmString = "realm=";
319 int realmPos = authHeader.find(realmString);
321 realm = authHeader.substring(realmPos + realmString.length());
323 ProtectionSpaceServerType serverType = ProtectionSpaceServerHTTP;
324 if (protocol == "https")
325 serverType = ProtectionSpaceServerHTTPS;
327 ProtectionSpaceAuthenticationScheme authScheme = ProtectionSpaceAuthenticationSchemeUnknown;
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;
338 protectionSpace = ProtectionSpace(host, port, serverType, realm, authScheme);
344 * This is being called for each HTTP header in the response. This includes '\r\n'
345 * for the last line of the header.
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.
352 static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
354 ResourceHandle* job = static_cast<ResourceHandle*>(data);
355 ResourceHandleInternal* d = job->getInternal();
359 // We should never be called when deferred loading is activated.
360 ASSERT(!d->m_defersLoading);
362 size_t totalSize = size * nmemb;
363 ResourceHandleClient* client = d->client();
365 String header = String::fromUTF8WithLatin1Fallback(static_cast<const char*>(ptr), totalSize);
368 * a) We can finish and send the ResourceResponse
369 * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse
371 * The HTTP standard requires to use \r\n but for compatibility it recommends to
374 if (header == String("\r\n") || header == String("\n")) {
375 CURL* h = d->m_handle;
378 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
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.
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));
391 curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
392 d->m_response.setURL(KURL(ParsedURLString, hdr));
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")));
400 if (isHttpRedirect(httpCode)) {
401 String location = d->m_response.httpHeaderField("location");
402 if (!location.isEmpty()) {
403 KURL newURL = KURL(job->firstRequest().url(), location);
405 ResourceRequest redirectedRequest = job->firstRequest();
406 redirectedRequest.setURL(newURL);
408 client->willSendRequest(job, redirectedRequest, d->m_response);
410 d->m_firstRequest.setURL(newURL);
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++;
427 client->didReceiveResponse(job, d->m_response);
428 d->m_response.setResponseFired(true);
431 int splitPos = header.find(":");
432 if (splitPos != -1) {
433 String key = header.left(splitPos).stripWhiteSpace();
434 String value = header.substring(splitPos + 1).stripWhiteSpace();
436 if (isAppendableHeader(key))
437 d->m_response.addHTTPHeaderField(key, value);
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.
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.
448 curl_easy_getinfo(d->m_handle, CURLINFO_RESPONSE_CODE, &httpCode);
450 String httpCodeString = String::number(httpCode);
451 int statusCodePos = header.find(httpCodeString);
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());
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.
469 size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data)
471 ResourceHandle* job = static_cast<ResourceHandle*>(data);
472 ResourceHandleInternal* d = job->getInternal();
477 // We should never be called when deferred loading is activated.
478 ASSERT(!d->m_defersLoading);
483 if (!d->m_formDataStream.hasMoreElements())
486 size_t sent = d->m_formDataStream.read(ptr, size, nmemb);
488 // Something went wrong so cancel the job.
495 void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* /* timer */)
497 startScheduledJobs();
504 struct timeval timeout;
506 timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds
508 // Retry 'select' if it was interrupted by a process signal.
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.
519 rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
520 } while (rc == -1 && errno == EINTR);
524 perror("bad: select() returned -1: ");
529 int runningHandles = 0;
530 while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { }
532 // check the curl messages indicating completed transfers
533 // and free their resources
536 CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
540 // find the node which has same d->m_handle as completed transfer
541 CURL* handle = msg->easy_handle;
543 ResourceHandle* job = 0;
544 CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
545 ASSERT_UNUSED(err, CURLE_OK == err);
549 ResourceHandleInternal* d = job->getInternal();
550 ASSERT(d->m_handle == handle);
552 if (d->m_cancelled) {
557 if (CURLMSG_DONE != msg->msg)
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) {
570 d->client()->didFinishLoading(job, 0);
573 curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
575 fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
578 d->client()->didFail(job, ResourceError(String(), msg->data.result, String(url), String(curl_easy_strerror(msg->data.result))));
584 bool started = startScheduledJobs(); // new jobs might have been added in the meantime
586 if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
587 m_downloadTimer.startOneShot(pollTimeSeconds);
590 void ResourceHandleManager::setProxyInfo(const String& host,
593 const String& username,
594 const String& password)
598 if (!host.length()) {
599 m_proxy = emptyString();
602 if (username.length() || password.length())
603 userPass = username + ":" + password + "@";
605 m_proxy = String("http://") + userPass + host + ":" + String::number(port);
609 void ResourceHandleManager::removeFromCurl(ResourceHandle* job)
611 ResourceHandleInternal* d = job->getInternal();
616 curl_multi_remove_handle(m_curlMultiHandle, d->m_handle);
617 curl_easy_cleanup(d->m_handle);
622 static inline size_t getFormElementsCount(ResourceHandle* job)
624 RefPtr<FormData> formData = job->firstRequest().httpBody();
630 // Resolve the blob elements so the formData can correctly report it's size.
631 formData = formData->resolveBlobReferences();
632 job->firstRequest().setHTTPBody(formData);
635 return formData->elements().size();
638 static void setupFormData(ResourceHandle* job, CURLoption sizeOption, struct curl_slist** headers)
640 ResourceHandleInternal* d = job->getInternal();
641 Vector<FormDataElement> elements = job->firstRequest().httpBody()->elements();
642 size_t numElements = elements.size();
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);
652 expectedSizeOfCurlOffT = sizeof(int);
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)
660 static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1;
661 // Obtain the total size of the form data
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;
674 size += fileSizeResult;
676 chunkedTransfer = true;
680 size += elements[i].m_data.size();
683 // cURL guesses that we want chunked encoding as long as we specify the header
685 *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");
687 if (sizeof(long long) == expectedSizeOfCurlOffT)
688 curl_easy_setopt(d->m_handle, sizeOption, (long long)size);
690 curl_easy_setopt(d->m_handle, sizeOption, (int)size);
693 curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback);
694 curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job);
697 void ResourceHandleManager::setupPUT(ResourceHandle* job, struct curl_slist** headers)
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);
703 // Disable the Expect: 100 continue header
704 *headers = curl_slist_append(*headers, "Expect:");
706 size_t numElements = getFormElementsCount(job);
710 setupFormData(job, CURLOPT_INFILESIZE_LARGE, headers);
713 void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers)
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);
719 size_t numElements = getFormElementsCount(job);
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());
733 setupFormData(job, CURLOPT_POSTFIELDSIZE_LARGE, headers);
736 void ResourceHandleManager::add(ResourceHandle* job)
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
741 m_resourceHandleList.append(job);
742 if (!m_downloadTimer.isActive())
743 m_downloadTimer.startOneShot(pollTimeSeconds);
746 bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job)
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);
759 bool ResourceHandleManager::startScheduledJobs()
761 // TODO: Create a separate stack of jobs for each domain.
763 bool started = false;
764 while (!m_resourceHandleList.isEmpty() && m_runningJobs < maxRunningJobs) {
765 ResourceHandle* job = m_resourceHandleList[0];
766 m_resourceHandleList.remove(0);
773 void ResourceHandleManager::dispatchSynchronousJob(ResourceHandle* job)
775 KURL kurl = job->firstRequest().url();
777 if (kurl.protocolIsData()) {
782 ResourceHandleInternal* handle = job->getInternal();
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;
789 initializeHandle(job);
791 // curl_easy_perform blocks until the transfert is finished.
792 CURLcode ret = curl_easy_perform(handle->m_handle);
795 ResourceError error(String(handle->m_url), ret, String(handle->m_url), String(curl_easy_strerror(ret)));
796 handle->client()->didFail(job, error);
799 curl_easy_cleanup(handle->m_handle);
802 void ResourceHandleManager::startJob(ResourceHandle* job)
804 KURL kurl = job->firstRequest().url();
806 if (kurl.protocolIsData()) {
811 initializeHandle(job);
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) {
819 fprintf(stderr, "Error %d starting job %s\n", ret, encodeWithURLEscapeSequences(job->firstRequest().url().string()).latin1().data());
826 void ResourceHandleManager::applyAuthenticationToRequest(ResourceHandle* handle, ResourceRequest& request)
828 // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
829 ResourceHandleInternal* d = handle->getInternal();
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());
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());
845 String user = d->m_user;
846 String password = d->m_pass;
848 if (!d->m_initialCredential.isEmpty()) {
849 user = d->m_initialCredential.user();
850 password = d->m_initialCredential.password();
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.
856 // curl CURLOPT_USERPWD expects username:password
857 String userpass = user + ":" + password;
858 curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());
861 void ResourceHandleManager::initializeHandle(ResourceHandle* job)
863 static const int allowedProtocols = CURLPROTO_FILE | CURLPROTO_FTP | CURLPROTO_FTPS | CURLPROTO_HTTP | CURLPROTO_HTTPS;
864 KURL kurl = job->firstRequest().url();
866 // Remove any fragment part, otherwise curl will send it as part of the request.
867 kurl.removeFragmentIdentifier();
869 ResourceHandleInternal* d = job->getInternal();
870 String url = kurl.string();
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());
879 // Determine the MIME type based on the path.
880 d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));
883 d->m_handle = curl_easy_init();
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);
892 if (getenv("DEBUG_CURL"))
893 curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1);
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.
912 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false);
914 if (!m_certificatePath.isNull())
915 curl_easy_setopt(d->m_handle, CURLOPT_CAINFO, m_certificatePath.data());
917 // enable gzip and deflate through Accept-Encoding:
918 curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, "");
920 // url must remain valid through the request
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);
927 if (m_cookieJarFileName)
928 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName);
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);
939 // Insert the ; to tell curl that this header has an empty value.
940 headerString.append(";");
942 headerString.append(": ");
943 headerString.append(value);
945 CString headerLatin1 = headerString.latin1();
946 headers = curl_slist_append(headers, headerLatin1.data());
950 String method = job->firstRequest().httpMethod();
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);
960 curl_easy_setopt(d->m_handle, CURLOPT_CUSTOMREQUEST, method.latin1().data());
961 setupPUT(job, &headers);
965 curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);
966 d->m_customHeaders = headers;
969 applyAuthenticationToRequest(job, job->firstRequest());
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);
978 void ResourceHandleManager::initCookieSession()
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.
983 CURL* curl = curl_easy_init();
988 curl_easy_setopt(curl, CURLOPT_SHARE, m_curlShareHandle);
990 if (m_cookieJarFileName) {
991 curl_easy_setopt(curl, CURLOPT_COOKIEFILE, m_cookieJarFileName);
992 curl_easy_setopt(curl, CURLOPT_COOKIEJAR, m_cookieJarFileName);
995 curl_easy_setopt(curl, CURLOPT_COOKIESESSION, 1);
997 curl_easy_cleanup(curl);
1000 void ResourceHandleManager::cancel(ResourceHandle* job)
1002 if (removeScheduledJob(job))
1005 ResourceHandleInternal* d = job->getInternal();
1006 d->m_cancelled = true;
1007 if (!m_downloadTimer.isActive())
1008 m_downloadTimer.startOneShot(pollTimeSeconds);
1011 } // namespace WebCore