1 /* 2 * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. 3 * Copyright (C) 2006 Michael Emmel mike.emmel (at) gmail.com 4 * Copyright (C) 2007 Alp Toker <alp (at) 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 (at) webkit.org> 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 29 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include "config.h" 35 #include "ResourceHandleManager.h" 36 37 #include "Base64.h" 38 #include "CString.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 "TextEncoding.h" 46 47 #include <errno.h> 48 #include <stdio.h> 49 #include <wtf/Threading.h> 50 #include <wtf/Vector.h> 51 52 #if !OS(WINDOWS) 53 #include <sys/param.h> 54 #define MAX_PATH MAXPATHLEN 55 #endif 56 57 namespace WebCore { 58 59 const int selectTimeoutMS = 5; 60 const double pollTimeSeconds = 0.05; 61 const int maxRunningJobs = 5; 62 63 static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS"); 64 65 static CString certificatePath() 66 { 67 #if PLATFORM(CF) 68 CFBundleRef webKitBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebKit")); 69 if (webKitBundle) { 70 RetainPtr<CFURLRef> certURLRef(AdoptCF, CFBundleCopyResourceURL(webKitBundle, CFSTR("cacert"), CFSTR("pem"), CFSTR("certificates"))); 71 if (certURLRef) { 72 char path[MAX_PATH]; 73 CFURLGetFileSystemRepresentation(certURLRef.get(), false, reinterpret_cast<UInt8*>(path), MAX_PATH); 74 return path; 75 } 76 } 77 #endif 78 char* envPath = getenv("CURL_CA_BUNDLE_PATH"); 79 if (envPath) 80 return envPath; 81 82 return CString(); 83 } 84 85 static Mutex* sharedResourceMutex(curl_lock_data data) { 86 DEFINE_STATIC_LOCAL(Mutex, cookieMutex, ()); 87 DEFINE_STATIC_LOCAL(Mutex, dnsMutex, ()); 88 DEFINE_STATIC_LOCAL(Mutex, shareMutex, ()); 89 90 switch (data) { 91 case CURL_LOCK_DATA_COOKIE: 92 return &cookieMutex; 93 case CURL_LOCK_DATA_DNS: 94 return &dnsMutex; 95 case CURL_LOCK_DATA_SHARE: 96 return &shareMutex; 97 default: 98 ASSERT_NOT_REACHED(); 99 return NULL; 100 } 101 } 102 103 // libcurl does not implement its own thread synchronization primitives. 104 // these two functions provide mutexes for cookies, and for the global DNS 105 // cache. 106 static void curl_lock_callback(CURL* handle, curl_lock_data data, curl_lock_access access, void* userPtr) 107 { 108 if (Mutex* mutex = sharedResourceMutex(data)) 109 mutex->lock(); 110 } 111 112 static void curl_unlock_callback(CURL* handle, curl_lock_data data, void* userPtr) 113 { 114 if (Mutex* mutex = sharedResourceMutex(data)) 115 mutex->unlock(); 116 } 117 118 ResourceHandleManager::ResourceHandleManager() 119 : m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback) 120 , m_cookieJarFileName(0) 121 , m_runningJobs(0) 122 , m_certificatePath (certificatePath()) 123 { 124 curl_global_init(CURL_GLOBAL_ALL); 125 m_curlMultiHandle = curl_multi_init(); 126 m_curlShareHandle = curl_share_init(); 127 curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); 128 curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); 129 curl_share_setopt(m_curlShareHandle, CURLSHOPT_LOCKFUNC, curl_lock_callback); 130 curl_share_setopt(m_curlShareHandle, CURLSHOPT_UNLOCKFUNC, curl_unlock_callback); 131 } 132 133 ResourceHandleManager::~ResourceHandleManager() 134 { 135 curl_multi_cleanup(m_curlMultiHandle); 136 curl_share_cleanup(m_curlShareHandle); 137 if (m_cookieJarFileName) 138 fastFree(m_cookieJarFileName); 139 curl_global_cleanup(); 140 } 141 142 void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName) 143 { 144 m_cookieJarFileName = fastStrDup(cookieJarFileName); 145 } 146 147 ResourceHandleManager* ResourceHandleManager::sharedInstance() 148 { 149 static ResourceHandleManager* sharedInstance = 0; 150 if (!sharedInstance) 151 sharedInstance = new ResourceHandleManager(); 152 return sharedInstance; 153 } 154 155 static void handleLocalReceiveResponse (CURL* handle, ResourceHandle* job, ResourceHandleInternal* d) 156 { 157 // since the code in headerCallback will not have run for local files 158 // the code to set the URL and fire didReceiveResponse is never run, 159 // which means the ResourceLoader's response does not contain the URL. 160 // Run the code here for local files to resolve the issue. 161 // TODO: See if there is a better approach for handling this. 162 const char* hdr; 163 CURLcode err = curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &hdr); 164 ASSERT(CURLE_OK == err); 165 d->m_response.setURL(KURL(ParsedURLString, hdr)); 166 if (d->client()) 167 d->client()->didReceiveResponse(job, d->m_response); 168 d->m_response.setResponseFired(true); 169 } 170 171 172 // called with data after all headers have been processed via headerCallback 173 static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data) 174 { 175 ResourceHandle* job = static_cast<ResourceHandle*>(data); 176 ResourceHandleInternal* d = job->getInternal(); 177 if (d->m_cancelled) 178 return 0; 179 180 #if LIBCURL_VERSION_NUM > 0x071200 181 // We should never be called when deferred loading is activated. 182 ASSERT(!d->m_defersLoading); 183 #endif 184 185 size_t totalSize = size * nmemb; 186 187 // this shouldn't be necessary but apparently is. CURL writes the data 188 // of html page even if it is a redirect that was handled internally 189 // can be observed e.g. on gmail.com 190 CURL* h = d->m_handle; 191 long httpCode = 0; 192 CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode); 193 if (CURLE_OK == err && httpCode >= 300 && httpCode < 400) 194 return totalSize; 195 196 if (!d->m_response.responseFired()) { 197 handleLocalReceiveResponse(h, job, d); 198 if (d->m_cancelled) 199 return 0; 200 } 201 202 if (d->client()) 203 d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0); 204 return totalSize; 205 } 206 207 /* 208 * This is being called for each HTTP header in the response. This includes '\r\n' 209 * for the last line of the header. 210 * 211 * We will add each HTTP Header to the ResourceResponse and on the termination 212 * of the header (\r\n) we will parse Content-Type and Content-Disposition and 213 * update the ResourceResponse and then send it away. 214 * 215 */ 216 static size_t headerCallback(char* 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 #if LIBCURL_VERSION_NUM > 0x071200 224 // We should never be called when deferred loading is activated. 225 ASSERT(!d->m_defersLoading); 226 #endif 227 228 size_t totalSize = size * nmemb; 229 ResourceHandleClient* client = d->client(); 230 231 String header(static_cast<const char*>(ptr), totalSize); 232 233 /* 234 * a) We can finish and send the ResourceResponse 235 * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse 236 * 237 * The HTTP standard requires to use \r\n but for compatibility it recommends to 238 * accept also \n. 239 */ 240 if (header == String("\r\n") || header == String("\n")) { 241 CURL* h = d->m_handle; 242 CURLcode err; 243 244 double contentLength = 0; 245 err = curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength); 246 d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength)); 247 248 const char* hdr; 249 err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr); 250 d->m_response.setURL(KURL(ParsedURLString, hdr)); 251 252 long httpCode = 0; 253 err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode); 254 d->m_response.setHTTPStatusCode(httpCode); 255 256 d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField("Content-Type"))); 257 d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField("Content-Type"))); 258 d->m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(d->m_response.httpHeaderField("Content-Disposition"))); 259 260 // HTTP redirection 261 if (httpCode >= 300 && httpCode < 400) { 262 String location = d->m_response.httpHeaderField("location"); 263 if (!location.isEmpty()) { 264 KURL newURL = KURL(job->request().url(), location); 265 266 ResourceRequest redirectedRequest = job->request(); 267 redirectedRequest.setURL(newURL); 268 if (client) 269 client->willSendRequest(job, redirectedRequest, d->m_response); 270 271 d->m_request.setURL(newURL); 272 273 return totalSize; 274 } 275 } 276 277 if (client) 278 client->didReceiveResponse(job, d->m_response); 279 d->m_response.setResponseFired(true); 280 281 } else { 282 int splitPos = header.find(":"); 283 if (splitPos != -1) 284 d->m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace()); 285 } 286 287 return totalSize; 288 } 289 290 /* This is called to obtain HTTP POST or PUT data. 291 Iterate through FormData elements and upload files. 292 Carefully respect the given buffer size and fill the rest of the data at the next calls. 293 */ 294 size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data) 295 { 296 ResourceHandle* job = static_cast<ResourceHandle*>(data); 297 ResourceHandleInternal* d = job->getInternal(); 298 299 if (d->m_cancelled) 300 return 0; 301 302 #if LIBCURL_VERSION_NUM > 0x071200 303 // We should never be called when deferred loading is activated. 304 ASSERT(!d->m_defersLoading); 305 #endif 306 307 if (!size || !nmemb) 308 return 0; 309 310 if (!d->m_formDataStream.hasMoreElements()) 311 return 0; 312 313 size_t sent = d->m_formDataStream.read(ptr, size, nmemb); 314 315 // Something went wrong so cancel the job. 316 if (!sent) 317 job->cancel(); 318 319 return sent; 320 } 321 322 void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer) 323 { 324 startScheduledJobs(); 325 326 fd_set fdread; 327 fd_set fdwrite; 328 fd_set fdexcep; 329 int maxfd = 0; 330 331 struct timeval timeout; 332 timeout.tv_sec = 0; 333 timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds 334 335 // Retry 'select' if it was interrupted by a process signal. 336 int rc = 0; 337 do { 338 FD_ZERO(&fdread); 339 FD_ZERO(&fdwrite); 340 FD_ZERO(&fdexcep); 341 curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd); 342 // When the 3 file descriptors are empty, winsock will return -1 343 // and bail out, stopping the file download. So make sure we 344 // have valid file descriptors before calling select. 345 if (maxfd >= 0) 346 rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); 347 } while (rc == -1 && errno == EINTR); 348 349 if (-1 == rc) { 350 #ifndef NDEBUG 351 perror("bad: select() returned -1: "); 352 #endif 353 return; 354 } 355 356 int runningHandles = 0; 357 while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { } 358 359 // check the curl messages indicating completed transfers 360 // and free their resources 361 while (true) { 362 int messagesInQueue; 363 CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue); 364 if (!msg) 365 break; 366 367 // find the node which has same d->m_handle as completed transfer 368 CURL* handle = msg->easy_handle; 369 ASSERT(handle); 370 ResourceHandle* job = 0; 371 CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job); 372 ASSERT(CURLE_OK == err); 373 ASSERT(job); 374 if (!job) 375 continue; 376 ResourceHandleInternal* d = job->getInternal(); 377 ASSERT(d->m_handle == handle); 378 379 if (d->m_cancelled) { 380 removeFromCurl(job); 381 continue; 382 } 383 384 if (CURLMSG_DONE != msg->msg) 385 continue; 386 387 if (CURLE_OK == msg->data.result) { 388 if (!d->m_response.responseFired()) { 389 handleLocalReceiveResponse(d->m_handle, job, d); 390 if (d->m_cancelled) { 391 removeFromCurl(job); 392 continue; 393 } 394 } 395 396 if (d->client()) 397 d->client()->didFinishLoading(job); 398 } else { 399 char* url = 0; 400 curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url); 401 #ifndef NDEBUG 402 fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result)); 403 #endif 404 if (d->client()) 405 d->client()->didFail(job, ResourceError(String(), msg->data.result, String(url), String(curl_easy_strerror(msg->data.result)))); 406 } 407 408 removeFromCurl(job); 409 } 410 411 bool started = startScheduledJobs(); // new jobs might have been added in the meantime 412 413 if (!m_downloadTimer.isActive() && (started || (runningHandles > 0))) 414 m_downloadTimer.startOneShot(pollTimeSeconds); 415 } 416 417 void ResourceHandleManager::setProxyInfo(const String& host, 418 unsigned long port, 419 ProxyType type, 420 const String& username, 421 const String& password) 422 { 423 m_proxyType = type; 424 425 if (!host.length()) { 426 m_proxy = String(""); 427 } else { 428 String userPass; 429 if (username.length() || password.length()) 430 userPass = username + ":" + password + "@"; 431 432 m_proxy = String("http://") + userPass + host + ":" + String::number(port); 433 } 434 } 435 436 void ResourceHandleManager::removeFromCurl(ResourceHandle* job) 437 { 438 ResourceHandleInternal* d = job->getInternal(); 439 ASSERT(d->m_handle); 440 if (!d->m_handle) 441 return; 442 m_runningJobs--; 443 curl_multi_remove_handle(m_curlMultiHandle, d->m_handle); 444 curl_easy_cleanup(d->m_handle); 445 d->m_handle = 0; 446 job->deref(); 447 } 448 449 void ResourceHandleManager::setupPUT(ResourceHandle*, struct curl_slist**) 450 { 451 notImplemented(); 452 } 453 454 /* Calculate the length of the POST. 455 Force chunked data transfer if size of files can't be obtained. 456 */ 457 void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers) 458 { 459 ResourceHandleInternal* d = job->getInternal(); 460 curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE); 461 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, 0); 462 463 if (!job->request().httpBody()) 464 return; 465 466 Vector<FormDataElement> elements = job->request().httpBody()->elements(); 467 size_t numElements = elements.size(); 468 if (!numElements) 469 return; 470 471 // Do not stream for simple POST data 472 if (numElements == 1) { 473 job->request().httpBody()->flatten(d->m_postBytes); 474 if (d->m_postBytes.size() != 0) { 475 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size()); 476 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data()); 477 } 478 return; 479 } 480 481 // Obtain the total size of the POST 482 // The size of a curl_off_t could be different in WebKit and in cURL depending on 483 // compilation flags of both. For CURLOPT_POSTFIELDSIZE_LARGE we have to pass the 484 // right size or random data will be used as the size. 485 static int expectedSizeOfCurlOffT = 0; 486 if (!expectedSizeOfCurlOffT) { 487 curl_version_info_data *infoData = curl_version_info(CURLVERSION_NOW); 488 if (infoData->features & CURL_VERSION_LARGEFILE) 489 expectedSizeOfCurlOffT = sizeof(long long); 490 else 491 expectedSizeOfCurlOffT = sizeof(int); 492 } 493 494 #if COMPILER(MSVC) 495 // work around compiler error in Visual Studio 2005. It can't properly 496 // handle math with 64-bit constant declarations. 497 #pragma warning(disable: 4307) 498 #endif 499 static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1; 500 curl_off_t size = 0; 501 bool chunkedTransfer = false; 502 for (size_t i = 0; i < numElements; i++) { 503 FormDataElement element = elements[i]; 504 if (element.m_type == FormDataElement::encodedFile) { 505 long long fileSizeResult; 506 if (getFileSize(element.m_filename, fileSizeResult)) { 507 if (fileSizeResult > maxCurlOffT) { 508 // File size is too big for specifying it to cURL 509 chunkedTransfer = true; 510 break; 511 } 512 size += fileSizeResult; 513 } else { 514 chunkedTransfer = true; 515 break; 516 } 517 } else 518 size += elements[i].m_data.size(); 519 } 520 521 // cURL guesses that we want chunked encoding as long as we specify the header 522 if (chunkedTransfer) 523 *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked"); 524 else { 525 if (sizeof(long long) == expectedSizeOfCurlOffT) 526 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (long long)size); 527 else 528 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (int)size); 529 } 530 531 curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback); 532 curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job); 533 } 534 535 void ResourceHandleManager::add(ResourceHandle* job) 536 { 537 // we can be called from within curl, so to avoid re-entrancy issues 538 // schedule this job to be added the next time we enter curl download loop 539 job->ref(); 540 m_resourceHandleList.append(job); 541 if (!m_downloadTimer.isActive()) 542 m_downloadTimer.startOneShot(pollTimeSeconds); 543 } 544 545 bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job) 546 { 547 int size = m_resourceHandleList.size(); 548 for (int i = 0; i < size; i++) { 549 if (job == m_resourceHandleList[i]) { 550 m_resourceHandleList.remove(i); 551 job->deref(); 552 return true; 553 } 554 } 555 return false; 556 } 557 558 bool ResourceHandleManager::startScheduledJobs() 559 { 560 // TODO: Create a separate stack of jobs for each domain. 561 562 bool started = false; 563 while (!m_resourceHandleList.isEmpty() && m_runningJobs < maxRunningJobs) { 564 ResourceHandle* job = m_resourceHandleList[0]; 565 m_resourceHandleList.remove(0); 566 startJob(job); 567 started = true; 568 } 569 return started; 570 } 571 572 static void parseDataUrl(ResourceHandle* handle) 573 { 574 ResourceHandleClient* client = handle->client(); 575 576 ASSERT(client); 577 if (!client) 578 return; 579 580 String url = handle->request().url().string(); 581 ASSERT(url.startsWith("data:", false)); 582 583 int index = url.find(','); 584 if (index == -1) { 585 client->cannotShowURL(handle); 586 return; 587 } 588 589 String mediaType = url.substring(5, index - 5); 590 String data = url.substring(index + 1); 591 592 bool base64 = mediaType.endsWith(";base64", false); 593 if (base64) 594 mediaType = mediaType.left(mediaType.length() - 7); 595 596 if (mediaType.isEmpty()) 597 mediaType = "text/plain;charset=US-ASCII"; 598 599 String mimeType = extractMIMETypeFromMediaType(mediaType); 600 String charset = extractCharsetFromMediaType(mediaType); 601 602 ResourceResponse response; 603 response.setMimeType(mimeType); 604 605 if (base64) { 606 data = decodeURLEscapeSequences(data); 607 response.setTextEncodingName(charset); 608 client->didReceiveResponse(handle, response); 609 610 // WebCore's decoder fails on Acid3 test 97 (whitespace). 611 Vector<char> out; 612 if (base64Decode(data.latin1().data(), data.latin1().length(), out) && out.size() > 0) 613 client->didReceiveData(handle, out.data(), out.size(), 0); 614 } else { 615 // We have to convert to UTF-16 early due to limitations in KURL 616 data = decodeURLEscapeSequences(data, TextEncoding(charset)); 617 response.setTextEncodingName("UTF-16"); 618 client->didReceiveResponse(handle, response); 619 if (data.length() > 0) 620 client->didReceiveData(handle, reinterpret_cast<const char*>(data.characters()), data.length() * sizeof(UChar), 0); 621 } 622 623 client->didFinishLoading(handle); 624 } 625 626 void ResourceHandleManager::dispatchSynchronousJob(ResourceHandle* job) 627 { 628 KURL kurl = job->request().url(); 629 630 if (kurl.protocolIs("data")) { 631 parseDataUrl(job); 632 return; 633 } 634 635 ResourceHandleInternal* handle = job->getInternal(); 636 637 #if LIBCURL_VERSION_NUM > 0x071200 638 // If defersLoading is true and we call curl_easy_perform 639 // on a paused handle, libcURL would do the transfert anyway 640 // and we would assert so force defersLoading to be false. 641 handle->m_defersLoading = false; 642 #endif 643 644 initializeHandle(job); 645 646 // curl_easy_perform blocks until the transfert is finished. 647 CURLcode ret = curl_easy_perform(handle->m_handle); 648 649 if (ret != 0) { 650 ResourceError error(String(handle->m_url), ret, String(handle->m_url), String(curl_easy_strerror(ret))); 651 handle->client()->didFail(job, error); 652 } 653 654 curl_easy_cleanup(handle->m_handle); 655 } 656 657 void ResourceHandleManager::startJob(ResourceHandle* job) 658 { 659 KURL kurl = job->request().url(); 660 661 if (kurl.protocolIs("data")) { 662 parseDataUrl(job); 663 return; 664 } 665 666 initializeHandle(job); 667 668 m_runningJobs++; 669 CURLMcode ret = curl_multi_add_handle(m_curlMultiHandle, job->getInternal()->m_handle); 670 // don't call perform, because events must be async 671 // timeout will occur and do curl_multi_perform 672 if (ret && ret != CURLM_CALL_MULTI_PERFORM) { 673 #ifndef NDEBUG 674 fprintf(stderr, "Error %d starting job %s\n", ret, encodeWithURLEscapeSequences(job->request().url().string()).latin1().data()); 675 #endif 676 job->cancel(); 677 return; 678 } 679 } 680 681 void ResourceHandleManager::initializeHandle(ResourceHandle* job) 682 { 683 KURL kurl = job->request().url(); 684 685 // Remove any fragment part, otherwise curl will send it as part of the request. 686 kurl.removeFragmentIdentifier(); 687 688 ResourceHandleInternal* d = job->getInternal(); 689 String url = kurl.string(); 690 691 if (kurl.isLocalFile()) { 692 String query = kurl.query(); 693 // Remove any query part sent to a local file. 694 if (!query.isEmpty()) { 695 int queryIndex = url.find(query); 696 if (queryIndex != -1) 697 url = url.left(queryIndex - 1); 698 } 699 // Determine the MIME type based on the path. 700 d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url)); 701 } 702 703 d->m_handle = curl_easy_init(); 704 705 #if LIBCURL_VERSION_NUM > 0x071200 706 if (d->m_defersLoading) { 707 CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL); 708 // If we did not pause the handle, we would ASSERT in the 709 // header callback. So just assert here. 710 ASSERT(error == CURLE_OK); 711 } 712 #endif 713 #ifndef NDEBUG 714 if (getenv("DEBUG_CURL")) 715 curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1); 716 #endif 717 curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job); 718 curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer); 719 curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback); 720 curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job); 721 curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback); 722 curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job); 723 curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1); 724 curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1); 725 curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10); 726 curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 727 curl_easy_setopt(d->m_handle, CURLOPT_SHARE, m_curlShareHandle); 728 curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes 729 // FIXME: Enable SSL verification when we have a way of shipping certs 730 // and/or reporting SSL errors to the user. 731 if (ignoreSSLErrors) 732 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false); 733 734 if (!m_certificatePath.isNull()) 735 curl_easy_setopt(d->m_handle, CURLOPT_CAINFO, m_certificatePath.data()); 736 737 // enable gzip and deflate through Accept-Encoding: 738 curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, ""); 739 740 // url must remain valid through the request 741 ASSERT(!d->m_url); 742 743 // url is in ASCII so latin1() will only convert it to char* without character translation. 744 d->m_url = fastStrDup(url.latin1().data()); 745 curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url); 746 747 if (m_cookieJarFileName) { 748 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEFILE, m_cookieJarFileName); 749 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName); 750 } 751 752 struct curl_slist* headers = 0; 753 if (job->request().httpHeaderFields().size() > 0) { 754 HTTPHeaderMap customHeaders = job->request().httpHeaderFields(); 755 HTTPHeaderMap::const_iterator end = customHeaders.end(); 756 for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) { 757 String key = it->first; 758 String value = it->second; 759 String headerString(key); 760 headerString.append(": "); 761 headerString.append(value); 762 CString headerLatin1 = headerString.latin1(); 763 headers = curl_slist_append(headers, headerLatin1.data()); 764 } 765 } 766 767 if ("GET" == job->request().httpMethod()) 768 curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE); 769 else if ("POST" == job->request().httpMethod()) 770 setupPOST(job, &headers); 771 else if ("PUT" == job->request().httpMethod()) 772 setupPUT(job, &headers); 773 else if ("HEAD" == job->request().httpMethod()) 774 curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE); 775 776 if (headers) { 777 curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers); 778 d->m_customHeaders = headers; 779 } 780 // curl CURLOPT_USERPWD expects username:password 781 if (d->m_user.length() || d->m_pass.length()) { 782 String userpass = d->m_user + ":" + d->m_pass; 783 curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data()); 784 } 785 786 // Set proxy options if we have them. 787 if (m_proxy.length()) { 788 curl_easy_setopt(d->m_handle, CURLOPT_PROXY, m_proxy.utf8().data()); 789 curl_easy_setopt(d->m_handle, CURLOPT_PROXYTYPE, m_proxyType); 790 } 791 } 792 793 void ResourceHandleManager::cancel(ResourceHandle* job) 794 { 795 if (removeScheduledJob(job)) 796 return; 797 798 ResourceHandleInternal* d = job->getInternal(); 799 d->m_cancelled = true; 800 if (!m_downloadTimer.isActive()) 801 m_downloadTimer.startOneShot(pollTimeSeconds); 802 } 803 804 } // namespace WebCore 805