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