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