Changeset 218962 in webkit
- Timestamp:
- Jun 29, 2017 2:58:55 PM (7 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r218961 r218962 1 2017-06-29 Basuke Suzuki <Basuke.Suzuki@sony.com> 2 3 [Curl] ResourceHandleManager violate the class responsibility of ResourceHandle 4 https://bugs.webkit.org/show_bug.cgi?id=173630 5 6 Reviewed by Alex Christensen. 7 8 * platform/network/ResourceHandle.h: 9 * platform/network/curl/ResourceHandleCurl.cpp: 10 (WebCore::ResourceHandle::platformLoadResourceSynchronously): 11 (WebCore::calculateWebTimingInformations): 12 (WebCore::handleLocalReceiveResponse): 13 (WebCore::writeCallback): 14 (WebCore::isHttpInfo): 15 (WebCore::isHttpRedirect): 16 (WebCore::isHttpAuthentication): 17 (WebCore::isHttpNotModified): 18 (WebCore::isAppendableHeader): 19 (WebCore::removeLeadingAndTrailingQuotes): 20 (WebCore::getProtectionSpace): 21 (WebCore::headerCallback): 22 (WebCore::readCallback): 23 (WebCore::getFormElementsCount): 24 (WebCore::setupFormData): 25 (WebCore::ResourceHandle::setupPUT): 26 (WebCore::ResourceHandle::setupPOST): 27 (WebCore::ResourceHandle::handleDataURL): 28 (WebCore::ResourceHandle::dispatchSynchronousJob): 29 (WebCore::ResourceHandle::applyAuthentication): 30 (WebCore::ResourceHandle::initialize): 31 (WebCore::ResourceHandle::handleCurlMsg): 32 * platform/network/curl/ResourceHandleManager.cpp: 33 (WebCore::ResourceHandleManager::downloadTimerCallback): 34 (WebCore::ResourceHandleManager::startJob): 35 (WebCore::calculateWebTimingInformations): Deleted. 36 (WebCore::isHttpInfo): Deleted. 37 (WebCore::isHttpRedirect): Deleted. 38 (WebCore::isHttpAuthentication): Deleted. 39 (WebCore::isHttpNotModified): Deleted. 40 (WebCore::handleLocalReceiveResponse): Deleted. 41 (WebCore::writeCallback): Deleted. 42 (WebCore::isAppendableHeader): Deleted. 43 (WebCore::removeLeadingAndTrailingQuotes): Deleted. 44 (WebCore::getProtectionSpace): Deleted. 45 (WebCore::headerCallback): Deleted. 46 (WebCore::readCallback): Deleted. 47 (WebCore::getFormElementsCount): Deleted. 48 (WebCore::setupFormData): Deleted. 49 (WebCore::ResourceHandleManager::setupPUT): Deleted. 50 (WebCore::ResourceHandleManager::setupPOST): Deleted. 51 (WebCore::handleDataURL): Deleted. 52 (WebCore::ResourceHandleManager::dispatchSynchronousJob): Deleted. 53 (WebCore::ResourceHandleManager::applyAuthenticationToRequest): Deleted. 54 (WebCore::ResourceHandleManager::initializeHandle): Deleted. 55 * platform/network/curl/ResourceHandleManager.h: 56 1 57 2017-06-29 Said Abou-Hallawa <sabouhallawa@apple.com> 2 58 -
trunk/Source/WebCore/platform/network/ResourceHandle.h
r218799 r218962 69 69 #endif 70 70 71 #if USE(CURL) 72 #include "CurlJobManager.h" 73 #endif 74 71 75 namespace WTF { 72 76 class SchedulePair; … … 153 157 #endif 154 158 155 #if PLATFORM(WIN) && USE(CURL)159 #if OS(WINDOWS) && USE(CURL) 156 160 static void setHostAllowsAnyHTTPSCertificate(const String&); 157 161 static void setClientCertificateInfo(const String&, const String&, const String&); 158 162 #endif 159 163 160 #if PLATFORM(WIN) && USE(CURL) && USE(CF)164 #if OS(WINDOWS) && USE(CURL) && USE(CF) 161 165 static void setClientCertificate(const String& host, CFDataRef); 162 166 #endif … … 182 186 #endif 183 187 188 #if USE(CURL) 189 void initialize(); 190 void handleDataURL(); 191 void handleCurlMsg(CURLMsg*); 192 #endif 193 184 194 bool hasAuthenticationChallenge() const; 185 195 void clearAuthentication(); … … 283 293 #endif 284 294 295 #if USE(CURL) 296 void dispatchSynchronousJob(); 297 298 void setupPOST(struct curl_slist**); 299 void setupPUT(struct curl_slist**); 300 301 void applyAuthentication(); 302 303 char m_curlErrorBuffer[CURL_ERROR_SIZE]; 304 #endif 305 285 306 friend class ResourceHandleInternal; 286 307 std::unique_ptr<ResourceHandleInternal> d; -
trunk/Source/WebCore/platform/network/curl/ResourceHandleCurl.cpp
r218142 r218962 33 33 #include "CachedResourceLoader.h" 34 34 #include "CredentialStorage.h" 35 #include "CurlCacheManager.h" 36 #include "CurlContext.h" 35 37 #include "FileSystem.h" 36 38 #include "Logging.h" 39 #include "MIMETypeRegistry.h" 37 40 #include "NetworkingContext.h" 38 41 #include "NotImplemented.h" … … 41 44 #include "SSLHandle.h" 42 45 #include "SynchronousLoaderClient.h" 46 #include <wtf/text/Base64.h> 43 47 44 48 namespace WebCore { … … 75 79 } 76 80 77 #if PLATFORM(WIN)81 #if OS(WINDOWS) 78 82 79 83 void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host) … … 92 96 #endif 93 97 94 #if PLATFORM(WIN) && USE(CF)98 #if OS(WINDOWS) && USE(CF) 95 99 96 100 void ResourceHandle::setClientCertificate(const String&, CFDataRef) … … 128 132 RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &client, false, false)); 129 133 130 ResourceHandleManager* manager = ResourceHandleManager::sharedInstance(); 131 132 manager->dispatchSynchronousJob(handle.get()); 134 handle.get()->dispatchSynchronousJob(); 133 135 134 136 error = client.error(); … … 242 244 } 243 245 246 const char* const errorDomainCurl = "CurlErrorDomain"; 247 248 #if ENABLE(WEB_TIMING) 249 static void calculateWebTimingInformations(ResourceHandleInternal* d) 250 { 251 double preTransferTime = 0; 252 double dnslookupTime = 0; 253 double connectTime = 0; 254 double appConnectTime = 0; 255 256 curl_easy_getinfo(d->m_handle, CURLINFO_NAMELOOKUP_TIME, &dnslookupTime); 257 curl_easy_getinfo(d->m_handle, CURLINFO_CONNECT_TIME, &connectTime); 258 curl_easy_getinfo(d->m_handle, CURLINFO_APPCONNECT_TIME, &appConnectTime); 259 curl_easy_getinfo(d->m_handle, CURLINFO_PRETRANSFER_TIME, &preTransferTime); 260 261 d->m_response.deprecatedNetworkLoadMetrics().domainLookupStart = Seconds(0); 262 d->m_response.deprecatedNetworkLoadMetrics().domainLookupEnd = Seconds(dnslookupTime); 263 264 d->m_response.deprecatedNetworkLoadMetrics().connectStart = Seconds(dnslookupTime); 265 d->m_response.deprecatedNetworkLoadMetrics().connectEnd = Seconds(connectTime); 266 267 d->m_response.deprecatedNetworkLoadMetrics().requestStart = Seconds(connectTime); 268 d->m_response.deprecatedNetworkLoadMetrics().responseStart = Seconds(preTransferTime); 269 270 if (appConnectTime) 271 d->m_response.deprecatedNetworkLoadMetrics().secureConnectionStart = Seconds(connectTime); 272 } 273 #endif 274 275 static void handleLocalReceiveResponse(CURL* handle, ResourceHandle* job, ResourceHandleInternal* d) 276 { 277 // since the code in headerCallback will not have run for local files 278 // the code to set the URL and fire didReceiveResponse is never run, 279 // which means the ResourceLoader's response does not contain the URL. 280 // Run the code here for local files to resolve the issue. 281 // TODO: See if there is a better approach for handling this. 282 URL url = CurlContext::singleton().getEffectiveURL(handle); 283 ASSERT(url.isValid()); 284 d->m_response.setURL(url); 285 if (d->client()) 286 d->client()->didReceiveResponse(job, ResourceResponse(d->m_response)); 287 d->m_response.setResponseFired(true); 288 } 289 290 291 // called with data after all headers have been processed via headerCallback 292 static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data) 293 { 294 ResourceHandle* job = static_cast<ResourceHandle*>(data); 295 ResourceHandleInternal* d = job->getInternal(); 296 if (d->m_cancelled) 297 return 0; 298 299 // We should never be called when deferred loading is activated. 300 ASSERT(!d->m_defersLoading); 301 302 size_t totalSize = size * nmemb; 303 304 // this shouldn't be necessary but apparently is. CURL writes the data 305 // of html page even if it is a redirect that was handled internally 306 // can be observed e.g. on gmail.com 307 CURL* h = d->m_handle; 308 long httpCode = 0; 309 CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode); 310 if (CURLE_OK == err && httpCode >= 300 && httpCode < 400) 311 return totalSize; 312 313 if (!d->m_response.responseFired()) { 314 handleLocalReceiveResponse(h, job, d); 315 if (d->m_cancelled) 316 return 0; 317 } 318 319 if (d->m_multipartHandle) 320 d->m_multipartHandle->contentReceived(static_cast<const char*>(ptr), totalSize); 321 else if (d->client()) { 322 d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0); 323 CurlCacheManager::getInstance().didReceiveData(*job, static_cast<char*>(ptr), totalSize); 324 } 325 326 return totalSize; 327 } 328 329 inline static bool isHttpInfo(int statusCode) 330 { 331 return 100 <= statusCode && statusCode < 200; 332 } 333 334 inline static bool isHttpRedirect(int statusCode) 335 { 336 return 300 <= statusCode && statusCode < 400 && statusCode != 304; 337 } 338 339 inline static bool isHttpAuthentication(int statusCode) 340 { 341 return statusCode == 401; 342 } 343 344 inline static bool isHttpNotModified(int statusCode) 345 { 346 return statusCode == 304; 347 } 348 349 static bool isAppendableHeader(const String &key) 350 { 351 static const char* appendableHeaders[] = { 352 "access-control-allow-headers", 353 "access-control-allow-methods", 354 "access-control-allow-origin", 355 "access-control-expose-headers", 356 "allow", 357 "cache-control", 358 "connection", 359 "content-encoding", 360 "content-language", 361 "if-match", 362 "if-none-match", 363 "keep-alive", 364 "pragma", 365 "proxy-authenticate", 366 "public", 367 "server", 368 "set-cookie", 369 "te", 370 "trailer", 371 "transfer-encoding", 372 "upgrade", 373 "user-agent", 374 "vary", 375 "via", 376 "warning", 377 "www-authenticate" 378 }; 379 380 // Custom headers start with 'X-', and need no further checking. 381 if (key.startsWith("x-", /* caseSensitive */ false)) 382 return true; 383 384 for (auto& header : appendableHeaders) { 385 if (equalIgnoringASCIICase(key, header)) 386 return true; 387 } 388 389 return false; 390 } 391 392 static void removeLeadingAndTrailingQuotes(String& value) 393 { 394 unsigned length = value.length(); 395 if (value.startsWith('"') && value.endsWith('"') && length > 1) 396 value = value.substring(1, length - 2); 397 } 398 399 static bool getProtectionSpace(CURL* h, const ResourceResponse& response, ProtectionSpace& protectionSpace) 400 { 401 CURLcode err; 402 403 long port = 0; 404 err = curl_easy_getinfo(h, CURLINFO_PRIMARY_PORT, &port); 405 if (err != CURLE_OK) 406 return false; 407 408 long availableAuth = CURLAUTH_NONE; 409 err = curl_easy_getinfo(h, CURLINFO_HTTPAUTH_AVAIL, &availableAuth); 410 if (err != CURLE_OK) 411 return false; 412 413 URL url = CurlContext::singleton().getEffectiveURL(h); 414 if (!url.isValid()) 415 return false; 416 417 String host = url.host(); 418 StringView protocol = url.protocol(); 419 420 String realm; 421 422 const String authHeader = response.httpHeaderField(HTTPHeaderName::Authorization); 423 const String realmString = "realm="; 424 int realmPos = authHeader.find(realmString); 425 if (realmPos > 0) { 426 realm = authHeader.substring(realmPos + realmString.length()); 427 realm = realm.left(realm.find(',')); 428 removeLeadingAndTrailingQuotes(realm); 429 } 430 431 ProtectionSpaceServerType serverType = ProtectionSpaceServerHTTP; 432 if (protocol == "https") 433 serverType = ProtectionSpaceServerHTTPS; 434 435 ProtectionSpaceAuthenticationScheme authScheme = ProtectionSpaceAuthenticationSchemeUnknown; 436 437 if (availableAuth & CURLAUTH_BASIC) 438 authScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic; 439 if (availableAuth & CURLAUTH_DIGEST) 440 authScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest; 441 if (availableAuth & CURLAUTH_GSSNEGOTIATE) 442 authScheme = ProtectionSpaceAuthenticationSchemeNegotiate; 443 if (availableAuth & CURLAUTH_NTLM) 444 authScheme = ProtectionSpaceAuthenticationSchemeNTLM; 445 446 protectionSpace = ProtectionSpace(host, port, serverType, realm, authScheme); 447 448 return true; 449 } 450 451 /* 452 * This is being called for each HTTP header in the response. This includes '\r\n' 453 * for the last line of the header. 454 * 455 * We will add each HTTP Header to the ResourceResponse and on the termination 456 * of the header (\r\n) we will parse Content-Type and Content-Disposition and 457 * update the ResourceResponse and then send it away. 458 * 459 */ 460 static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data) 461 { 462 ResourceHandle* job = static_cast<ResourceHandle*>(data); 463 ResourceHandleInternal* d = job->getInternal(); 464 if (d->m_cancelled) 465 return 0; 466 467 // We should never be called when deferred loading is activated. 468 ASSERT(!d->m_defersLoading); 469 470 size_t totalSize = size * nmemb; 471 ResourceHandleClient* client = d->client(); 472 473 String header(static_cast<const char*>(ptr), totalSize); 474 475 /* 476 * a) We can finish and send the ResourceResponse 477 * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse 478 * 479 * The HTTP standard requires to use \r\n but for compatibility it recommends to 480 * accept also \n. 481 */ 482 if (header == String("\r\n") || header == String("\n")) { 483 CURL* h = d->m_handle; 484 485 long httpCode = 0; 486 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode); 487 488 if (!httpCode) { 489 // Comes here when receiving 200 Connection Established. Just return. 490 return totalSize; 491 } 492 if (isHttpInfo(httpCode)) { 493 // Just return when receiving http info, e.g. HTTP/1.1 100 Continue. 494 // If not, the request might be cancelled, because the MIME type will be empty for this response. 495 return totalSize; 496 } 497 498 double contentLength = 0; 499 curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength); 500 d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength)); 501 502 d->m_response.setURL(CurlContext::singleton().getEffectiveURL(h)); 503 504 d->m_response.setHTTPStatusCode(httpCode); 505 d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField(HTTPHeaderName::ContentType)).convertToASCIILowercase()); 506 d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField(HTTPHeaderName::ContentType))); 507 508 if (d->m_response.isMultipart()) { 509 String boundary; 510 bool parsed = MultipartHandle::extractBoundary(d->m_response.httpHeaderField(HTTPHeaderName::ContentType), boundary); 511 if (parsed) 512 d->m_multipartHandle = std::make_unique<MultipartHandle>(job, boundary); 513 } 514 515 // HTTP redirection 516 if (isHttpRedirect(httpCode)) { 517 String location = d->m_response.httpHeaderField(HTTPHeaderName::Location); 518 if (!location.isEmpty()) { 519 URL newURL = URL(job->firstRequest().url(), location); 520 521 ResourceRequest redirectedRequest = job->firstRequest(); 522 redirectedRequest.setURL(newURL); 523 ResourceResponse response = d->m_response; 524 if (client) 525 client->willSendRequest(job, WTFMove(redirectedRequest), WTFMove(response)); 526 527 d->m_firstRequest.setURL(newURL); 528 529 return totalSize; 530 } 531 } else if (isHttpAuthentication(httpCode)) { 532 ProtectionSpace protectionSpace; 533 if (getProtectionSpace(d->m_handle, d->m_response, protectionSpace)) { 534 Credential credential; 535 AuthenticationChallenge challenge(protectionSpace, credential, d->m_authFailureCount, d->m_response, ResourceError()); 536 challenge.setAuthenticationClient(job); 537 job->didReceiveAuthenticationChallenge(challenge); 538 d->m_authFailureCount++; 539 return totalSize; 540 } 541 } 542 543 if (client) { 544 if (isHttpNotModified(httpCode)) { 545 const String& url = job->firstRequest().url().string(); 546 if (CurlCacheManager::getInstance().getCachedResponse(url, d->m_response)) { 547 if (d->m_addedCacheValidationHeaders) { 548 d->m_response.setHTTPStatusCode(200); 549 d->m_response.setHTTPStatusText("OK"); 550 } 551 } 552 } 553 client->didReceiveResponse(job, ResourceResponse(d->m_response)); 554 CurlCacheManager::getInstance().didReceiveResponse(*job, d->m_response); 555 } 556 d->m_response.setResponseFired(true); 557 558 } else { 559 int splitPos = header.find(":"); 560 if (splitPos != -1) { 561 String key = header.left(splitPos).stripWhiteSpace(); 562 String value = header.substring(splitPos + 1).stripWhiteSpace(); 563 564 if (isAppendableHeader(key)) 565 d->m_response.addHTTPHeaderField(key, value); 566 else 567 d->m_response.setHTTPHeaderField(key, value); 568 } else if (header.startsWith("HTTP", false)) { 569 // This is the first line of the response. 570 // Extract the http status text from this. 571 // 572 // If the FOLLOWLOCATION option is enabled for the curl handle then 573 // curl will follow the redirections internally. Thus this header callback 574 // will be called more than one time with the line starting "HTTP" for one job. 575 long httpCode = 0; 576 curl_easy_getinfo(d->m_handle, CURLINFO_RESPONSE_CODE, &httpCode); 577 578 String httpCodeString = String::number(httpCode); 579 int statusCodePos = header.find(httpCodeString); 580 581 if (statusCodePos != -1) { 582 // The status text is after the status code. 583 String status = header.substring(statusCodePos + httpCodeString.length()); 584 d->m_response.setHTTPStatusText(status.stripWhiteSpace()); 585 } 586 587 } 588 } 589 590 return totalSize; 591 } 592 593 /* This is called to obtain HTTP POST or PUT data. 594 Iterate through FormData elements and upload files. 595 Carefully respect the given buffer size and fill the rest of the data at the next calls. 596 */ 597 size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data) 598 { 599 ResourceHandle* job = static_cast<ResourceHandle*>(data); 600 ResourceHandleInternal* d = job->getInternal(); 601 602 if (d->m_cancelled) 603 return 0; 604 605 // We should never be called when deferred loading is activated. 606 ASSERT(!d->m_defersLoading); 607 608 if (!size || !nmemb) 609 return 0; 610 611 if (!d->m_formDataStream.hasMoreElements()) 612 return 0; 613 614 size_t sent = d->m_formDataStream.read(ptr, size, nmemb); 615 616 // Something went wrong so cancel the job. 617 if (!sent) 618 job->cancel(); 619 620 return sent; 621 } 622 623 static inline size_t getFormElementsCount(ResourceHandle* job) 624 { 625 RefPtr<FormData> formData = job->firstRequest().httpBody(); 626 627 if (!formData) 628 return 0; 629 630 // Resolve the blob elements so the formData can correctly report it's size. 631 formData = formData->resolveBlobReferences(); 632 size_t size = formData->elements().size(); 633 job->firstRequest().setHTTPBody(WTFMove(formData)); 634 635 return size; 636 } 637 638 static void setupFormData(ResourceHandle* job, CURLoption sizeOption, struct curl_slist** headers) 639 { 640 ResourceHandleInternal* d = job->getInternal(); 641 Vector<FormDataElement> elements = job->firstRequest().httpBody()->elements(); 642 size_t numElements = elements.size(); 643 644 // The size of a curl_off_t could be different in WebKit and in cURL depending on 645 // compilation flags of both. 646 static int expectedSizeOfCurlOffT = 0; 647 if (!expectedSizeOfCurlOffT) { 648 curl_version_info_data* infoData = curl_version_info(CURLVERSION_NOW); 649 if (infoData->features & CURL_VERSION_LARGEFILE) 650 expectedSizeOfCurlOffT = sizeof(long long); 651 else 652 expectedSizeOfCurlOffT = sizeof(int); 653 } 654 655 static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1; 656 // Obtain the total size of the form data 657 curl_off_t size = 0; 658 bool chunkedTransfer = false; 659 for (size_t i = 0; i < numElements; i++) { 660 FormDataElement element = elements[i]; 661 if (element.m_type == FormDataElement::Type::EncodedFile) { 662 long long fileSizeResult; 663 if (getFileSize(element.m_filename, fileSizeResult)) { 664 if (fileSizeResult > maxCurlOffT) { 665 // File size is too big for specifying it to cURL 666 chunkedTransfer = true; 667 break; 668 } 669 size += fileSizeResult; 670 } else { 671 chunkedTransfer = true; 672 break; 673 } 674 } else 675 size += elements[i].m_data.size(); 676 } 677 678 // cURL guesses that we want chunked encoding as long as we specify the header 679 if (chunkedTransfer) 680 *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked"); 681 else { 682 if (sizeof(long long) == expectedSizeOfCurlOffT) 683 curl_easy_setopt(d->m_handle, sizeOption, (long long)size); 684 else 685 curl_easy_setopt(d->m_handle, sizeOption, (int)size); 686 } 687 688 curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback); 689 curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job); 690 } 691 692 void ResourceHandle::setupPUT(struct curl_slist** headers) 693 { 694 ResourceHandleInternal* d = getInternal(); 695 curl_easy_setopt(d->m_handle, CURLOPT_UPLOAD, TRUE); 696 curl_easy_setopt(d->m_handle, CURLOPT_INFILESIZE, 0); 697 698 // Disable the Expect: 100 continue header 699 *headers = curl_slist_append(*headers, "Expect:"); 700 701 size_t numElements = getFormElementsCount(this); 702 if (!numElements) 703 return; 704 705 setupFormData(this, CURLOPT_INFILESIZE_LARGE, headers); 706 } 707 708 void ResourceHandle::setupPOST(struct curl_slist** headers) 709 { 710 ResourceHandleInternal* d = getInternal(); 711 curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE); 712 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, 0); 713 714 size_t numElements = getFormElementsCount(this); 715 if (!numElements) 716 return; 717 718 // Do not stream for simple POST data 719 if (numElements == 1) { 720 firstRequest().httpBody()->flatten(d->m_postBytes); 721 if (d->m_postBytes.size()) { 722 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size()); 723 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data()); 724 } 725 return; 726 } 727 728 setupFormData(this, CURLOPT_POSTFIELDSIZE_LARGE, headers); 729 } 730 731 void ResourceHandle::handleDataURL() 732 { 733 ASSERT(firstRequest().url().protocolIsData()); 734 String url = firstRequest().url().string(); 735 736 ASSERT(client()); 737 738 int index = url.find(','); 739 if (index == -1) { 740 client()->cannotShowURL(this); 741 return; 742 } 743 744 String mediaType = url.substring(5, index - 5); 745 String data = url.substring(index + 1); 746 747 bool base64 = mediaType.endsWith(";base64", false); 748 if (base64) 749 mediaType = mediaType.left(mediaType.length() - 7); 750 751 if (mediaType.isEmpty()) 752 mediaType = "text/plain"; 753 754 String mimeType = extractMIMETypeFromMediaType(mediaType); 755 String charset = extractCharsetFromMediaType(mediaType); 756 757 if (charset.isEmpty()) 758 charset = "US-ASCII"; 759 760 ResourceResponse response; 761 response.setMimeType(mimeType); 762 response.setTextEncodingName(charset); 763 response.setURL(firstRequest().url()); 764 765 if (base64) { 766 data = decodeURLEscapeSequences(data); 767 client()->didReceiveResponse(this, WTFMove(response)); 768 769 // didReceiveResponse might cause the client to be deleted. 770 if (client()) { 771 Vector<char> out; 772 if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0) 773 client()->didReceiveData(this, out.data(), out.size(), 0); 774 } 775 } else { 776 TextEncoding encoding(charset); 777 data = decodeURLEscapeSequences(data, encoding); 778 client()->didReceiveResponse(this, WTFMove(response)); 779 780 // didReceiveResponse might cause the client to be deleted. 781 if (client()) { 782 CString encodedData = encoding.encode(data, URLEncodedEntitiesForUnencodables); 783 if (encodedData.length()) 784 client()->didReceiveData(this, encodedData.data(), encodedData.length(), 0); 785 } 786 } 787 788 if (client()) 789 client()->didFinishLoading(this); 790 } 791 792 void ResourceHandle::dispatchSynchronousJob() 793 { 794 URL kurl = firstRequest().url(); 795 796 if (kurl.protocolIsData()) { 797 handleDataURL(); 798 return; 799 } 800 801 ResourceHandleInternal* d = getInternal(); 802 803 // If defersLoading is true and we call curl_easy_perform 804 // on a paused handle, libcURL would do the transfert anyway 805 // and we would assert so force defersLoading to be false. 806 d->m_defersLoading = false; 807 808 initialize(); 809 810 // curl_easy_perform blocks until the transfert is finished. 811 CURLcode ret = curl_easy_perform(d->m_handle); 812 813 if (ret != CURLE_OK) { 814 ResourceError error(ASCIILiteral(errorDomainCurl), ret, kurl, String(curl_easy_strerror(ret))); 815 error.setSSLErrors(d->m_sslErrors); 816 d->client()->didFail(this, error); 817 } else { 818 if (d->client()) 819 d->client()->didReceiveResponse(this, ResourceResponse(d->m_response)); 820 } 821 822 #if ENABLE(WEB_TIMING) 823 calculateWebTimingInformations(d); 824 #endif 825 826 curl_easy_cleanup(d->m_handle); 827 } 828 829 void ResourceHandle::applyAuthentication() 830 { 831 ResourceRequest& request = firstRequest(); 832 // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open(). 833 ResourceHandleInternal* d = getInternal(); 834 835 String partition = request.cachePartition(); 836 837 if (shouldUseCredentialStorage()) { 838 if (d->m_user.isEmpty() && d->m_pass.isEmpty()) { 839 // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 840 // try and reuse the credential preemptively, as allowed by RFC 2617. 841 d->m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url()); 842 } else { 843 // If there is already a protection space known for the URL, update stored credentials 844 // before sending a request. This makes it possible to implement logout by sending an 845 // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that 846 // an authentication dialog doesn't pop up). 847 CredentialStorage::defaultCredentialStorage().set(partition, Credential(d->m_user, d->m_pass, CredentialPersistenceNone), request.url()); 848 } 849 } 850 851 String user = d->m_user; 852 String password = d->m_pass; 853 854 if (!d->m_initialCredential.isEmpty()) { 855 user = d->m_initialCredential.user(); 856 password = d->m_initialCredential.password(); 857 curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 858 } 859 860 // It seems we need to set CURLOPT_USERPWD even if username and password is empty. 861 // Otherwise cURL will not automatically continue with a new request after a 401 response. 862 863 // curl CURLOPT_USERPWD expects username:password 864 String userpass = user + ":" + password; 865 curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data()); 866 } 867 868 void ResourceHandle::initialize() 869 { 870 CurlContext& context = CurlContext::singleton(); 871 872 static const int allowedProtocols = CURLPROTO_FILE | CURLPROTO_FTP | CURLPROTO_FTPS | CURLPROTO_HTTP | CURLPROTO_HTTPS; 873 URL url = firstRequest().url(); 874 875 // Remove any fragment part, otherwise curl will send it as part of the request. 876 url.removeFragmentIdentifier(); 877 878 ResourceHandleInternal* d = getInternal(); 879 String urlString = url.string(); 880 881 if (url.isLocalFile()) { 882 // Remove any query part sent to a local file. 883 if (!url.query().isEmpty()) { 884 // By setting the query to a null string it'll be removed. 885 url.setQuery(String()); 886 urlString = url.string(); 887 } 888 // Determine the MIME type based on the path. 889 d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url)); 890 } 891 892 d->m_handle = curl_easy_init(); 893 894 if (d->m_defersLoading) { 895 CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL); 896 // If we did not pause the handle, we would ASSERT in the 897 // header callback. So just assert here. 898 ASSERT_UNUSED(error, error == CURLE_OK); 899 } 900 #ifndef NDEBUG 901 if (context.isVerbose()) 902 curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1); 903 if (context.getLogFile()) 904 curl_easy_setopt(d->m_handle, CURLOPT_STDERR, context.getLogFile()); 905 #endif 906 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, 1L); 907 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYHOST, 2L); 908 curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, this); 909 curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer); 910 curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback); 911 curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, this); 912 curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback); 913 curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, this); 914 curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1); 915 curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1); 916 curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10); 917 curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 918 curl_easy_setopt(d->m_handle, CURLOPT_SHARE, CurlContext::singleton().curlShareHandle()); 919 curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes 920 curl_easy_setopt(d->m_handle, CURLOPT_PROTOCOLS, allowedProtocols); 921 curl_easy_setopt(d->m_handle, CURLOPT_REDIR_PROTOCOLS, allowedProtocols); 922 setSSLClientCertificate(this); 923 924 if (context.shouldIgnoreSSLErrors()) 925 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false); 926 else 927 setSSLVerifyOptions(this); 928 929 const char* certificate = context.getCertificatePath(); 930 if (certificate) 931 curl_easy_setopt(d->m_handle, CURLOPT_CAINFO, certificate); 932 933 // enable gzip and deflate through Accept-Encoding: 934 curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, ""); 935 936 // url must remain valid through the request 937 ASSERT(!d->m_url); 938 939 // url is in ASCII so latin1() will only convert it to char* without character translation. 940 d->m_url = fastStrDup(urlString.latin1().data()); 941 curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url); 942 943 const char* cookieJar = context.getCookieJarFileName(); 944 if (cookieJar) 945 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, cookieJar); 946 947 struct curl_slist* headers = 0; 948 if (firstRequest().httpHeaderFields().size() > 0) { 949 HTTPHeaderMap customHeaders = firstRequest().httpHeaderFields(); 950 951 bool hasCacheHeaders = customHeaders.contains(HTTPHeaderName::IfModifiedSince) || customHeaders.contains(HTTPHeaderName::IfNoneMatch); 952 if (!hasCacheHeaders && CurlCacheManager::getInstance().isCached(url)) { 953 CurlCacheManager::getInstance().addCacheEntryClient(url, this); 954 HTTPHeaderMap& requestHeaders = CurlCacheManager::getInstance().requestHeaders(url); 955 956 // append additional cache information 957 HTTPHeaderMap::const_iterator it = requestHeaders.begin(); 958 HTTPHeaderMap::const_iterator end = requestHeaders.end(); 959 while (it != end) { 960 customHeaders.set(it->key, it->value); 961 ++it; 962 } 963 d->m_addedCacheValidationHeaders = true; 964 } 965 966 HTTPHeaderMap::const_iterator end = customHeaders.end(); 967 for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) { 968 String key = it->key; 969 String value = it->value; 970 String headerString(key); 971 if (value.isEmpty()) { 972 // Insert the ; to tell curl that this header has an empty value. 973 headerString.append(";"); 974 } else { 975 headerString.append(": "); 976 headerString.append(value); 977 } 978 CString headerLatin1 = headerString.latin1(); 979 headers = curl_slist_append(headers, headerLatin1.data()); 980 } 981 } 982 983 String method = firstRequest().httpMethod(); 984 if ("GET" == method) 985 curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE); 986 else if ("POST" == method) 987 setupPOST(&headers); 988 else if ("PUT" == method) 989 setupPUT(&headers); 990 else if ("HEAD" == method) 991 curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE); 992 else { 993 curl_easy_setopt(d->m_handle, CURLOPT_CUSTOMREQUEST, method.ascii().data()); 994 setupPUT(&headers); 995 } 996 997 if (headers) { 998 curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers); 999 d->m_customHeaders = headers; 1000 } 1001 1002 applyAuthentication(); 1003 1004 // Set proxy options if we have them. 1005 auto& proxy = CurlContext::singleton().proxyInfo(); 1006 if (proxy.type != CurlProxyType::Invalid) { 1007 curl_easy_setopt(d->m_handle, CURLOPT_PROXY, proxy.url().utf8().data()); 1008 curl_easy_setopt(d->m_handle, CURLOPT_PROXYTYPE, proxy.type); 1009 } 1010 } 1011 1012 void ResourceHandle::handleCurlMsg(CURLMsg* msg) 1013 { 1014 ResourceHandleInternal* d = getInternal(); 1015 1016 if (CURLE_OK == msg->data.result) { 1017 #if ENABLE(WEB_TIMING) 1018 calculateWebTimingInformations(d); 1019 #endif 1020 if (!d->m_response.responseFired()) { 1021 handleLocalReceiveResponse(d->m_handle, this, d); 1022 if (d->m_cancelled) 1023 return; 1024 } 1025 1026 if (d->m_multipartHandle) 1027 d->m_multipartHandle->contentEnded(); 1028 1029 if (d->client()) { 1030 d->client()->didFinishLoading(this); 1031 CurlCacheManager::getInstance().didFinishLoading(*this); 1032 } 1033 } else { 1034 URL url = CurlContext::singleton().getEffectiveURL(d->m_handle); 1035 #ifndef NDEBUG 1036 fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url.string().utf8().data(), curl_easy_strerror(msg->data.result)); 1037 #endif 1038 if (d->client()) { 1039 ResourceError resourceError(ASCIILiteral(errorDomainCurl), msg->data.result, url, String(curl_easy_strerror(msg->data.result))); 1040 resourceError.setSSLErrors(d->m_sslErrors); 1041 d->client()->didFail(this, resourceError); 1042 CurlCacheManager::getInstance().didFail(*this); 1043 } 1044 } 1045 } 1046 244 1047 } // namespace WebCore 245 1048 -
trunk/Source/WebCore/platform/network/curl/ResourceHandleManager.cpp
r218947 r218962 42 42 43 43 #include "CredentialStorage.h" 44 #include "CurlCacheManager.h"45 44 #include "CurlContext.h" 46 45 #include "HTTPHeaderNames.h" 47 46 #include "HTTPParsers.h" 48 #include "MIMETypeRegistry.h"49 47 #include "MultipartHandle.h" 50 48 #include "ResourceError.h" … … 56 54 #include "SSLHandle.h" 57 55 #include "TextEncoding.h" 58 #include <wtf/text/Base64.h>59 56 #include <wtf/text/CString.h> 60 57 #include <wtf/text/StringView.h> … … 80 77 static const Seconds pollTime { 50_ms }; 81 78 const int maxRunningJobs = 128; 82 const char* const errorDomainCurl = "CurlErrorDomain";83 84 #if ENABLE(WEB_TIMING)85 static void calculateWebTimingInformations(ResourceHandleInternal* d)86 {87 double preTransferTime = 0;88 double dnslookupTime = 0;89 double connectTime = 0;90 double appConnectTime = 0;91 92 curl_easy_getinfo(d->m_handle, CURLINFO_NAMELOOKUP_TIME, &dnslookupTime);93 curl_easy_getinfo(d->m_handle, CURLINFO_CONNECT_TIME, &connectTime);94 curl_easy_getinfo(d->m_handle, CURLINFO_APPCONNECT_TIME, &appConnectTime);95 curl_easy_getinfo(d->m_handle, CURLINFO_PRETRANSFER_TIME, &preTransferTime);96 97 d->m_response.deprecatedNetworkLoadMetrics().domainLookupStart = Seconds(0);98 d->m_response.deprecatedNetworkLoadMetrics().domainLookupEnd = Seconds(dnslookupTime);99 100 d->m_response.deprecatedNetworkLoadMetrics().connectStart = Seconds(dnslookupTime);101 d->m_response.deprecatedNetworkLoadMetrics().connectEnd = Seconds(connectTime);102 103 d->m_response.deprecatedNetworkLoadMetrics().requestStart = Seconds(connectTime);104 d->m_response.deprecatedNetworkLoadMetrics().responseStart = Seconds(preTransferTime);105 106 if (appConnectTime)107 d->m_response.deprecatedNetworkLoadMetrics().secureConnectionStart = Seconds(connectTime);108 }109 #endif110 111 inline static bool isHttpInfo(int statusCode)112 {113 return 100 <= statusCode && statusCode < 200;114 }115 116 inline static bool isHttpRedirect(int statusCode)117 {118 return 300 <= statusCode && statusCode < 400 && statusCode != 304;119 }120 121 inline static bool isHttpAuthentication(int statusCode)122 {123 return statusCode == 401;124 }125 126 inline static bool isHttpNotModified(int statusCode)127 {128 return statusCode == 304;129 }130 79 131 80 ResourceHandleManager::ResourceHandleManager() … … 147 96 sharedInstance = new ResourceHandleManager(); 148 97 return sharedInstance; 149 }150 151 static void handleLocalReceiveResponse (CURL* handle, ResourceHandle* job, ResourceHandleInternal* d)152 {153 // since the code in headerCallback will not have run for local files154 // the code to set the URL and fire didReceiveResponse is never run,155 // which means the ResourceLoader's response does not contain the URL.156 // Run the code here for local files to resolve the issue.157 // TODO: See if there is a better approach for handling this.158 URL url = CurlContext::singleton().getEffectiveURL(handle);159 ASSERT(url.isValid());160 d->m_response.setURL(url);161 if (d->client())162 d->client()->didReceiveResponse(job, ResourceResponse(d->m_response));163 d->m_response.setResponseFired(true);164 }165 166 167 // called with data after all headers have been processed via headerCallback168 static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data)169 {170 ResourceHandle* job = static_cast<ResourceHandle*>(data);171 ResourceHandleInternal* d = job->getInternal();172 if (d->m_cancelled)173 return 0;174 175 // We should never be called when deferred loading is activated.176 ASSERT(!d->m_defersLoading);177 178 size_t totalSize = size * nmemb;179 180 // this shouldn't be necessary but apparently is. CURL writes the data181 // of html page even if it is a redirect that was handled internally182 // can be observed e.g. on gmail.com183 CURL* h = d->m_handle;184 long httpCode = 0;185 CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);186 if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)187 return totalSize;188 189 if (!d->m_response.responseFired()) {190 handleLocalReceiveResponse(h, job, d);191 if (d->m_cancelled)192 return 0;193 }194 195 if (d->m_multipartHandle)196 d->m_multipartHandle->contentReceived(static_cast<const char*>(ptr), totalSize);197 else if (d->client()) {198 d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);199 CurlCacheManager::getInstance().didReceiveData(*job, static_cast<char*>(ptr), totalSize);200 }201 202 return totalSize;203 }204 205 static bool isAppendableHeader(const String &key)206 {207 static const char* appendableHeaders[] = {208 "access-control-allow-headers",209 "access-control-allow-methods",210 "access-control-allow-origin",211 "access-control-expose-headers",212 "allow",213 "cache-control",214 "connection",215 "content-encoding",216 "content-language",217 "if-match",218 "if-none-match",219 "keep-alive",220 "pragma",221 "proxy-authenticate",222 "public",223 "server",224 "set-cookie",225 "te",226 "trailer",227 "transfer-encoding",228 "upgrade",229 "user-agent",230 "vary",231 "via",232 "warning",233 "www-authenticate"234 };235 236 // Custom headers start with 'X-', and need no further checking.237 if (key.startsWith("x-", /* caseSensitive */ false))238 return true;239 240 for (auto& header : appendableHeaders) {241 if (equalIgnoringASCIICase(key, header))242 return true;243 }244 245 return false;246 }247 248 static void removeLeadingAndTrailingQuotes(String& value)249 {250 unsigned length = value.length();251 if (value.startsWith('"') && value.endsWith('"') && length > 1)252 value = value.substring(1, length-2);253 }254 255 static bool getProtectionSpace(CURL* h, const ResourceResponse& response, ProtectionSpace& protectionSpace)256 {257 CURLcode err;258 259 long port = 0;260 err = curl_easy_getinfo(h, CURLINFO_PRIMARY_PORT, &port);261 if (err != CURLE_OK)262 return false;263 264 long availableAuth = CURLAUTH_NONE;265 err = curl_easy_getinfo(h, CURLINFO_HTTPAUTH_AVAIL, &availableAuth);266 if (err != CURLE_OK)267 return false;268 269 URL url = CurlContext::singleton().getEffectiveURL(h);270 if (!url.isValid())271 return false;272 273 String host = url.host();274 StringView protocol = url.protocol();275 276 String realm;277 278 const String authHeader = response.httpHeaderField(HTTPHeaderName::Authorization);279 const String realmString = "realm=";280 int realmPos = authHeader.find(realmString);281 if (realmPos > 0) {282 realm = authHeader.substring(realmPos + realmString.length());283 realm = realm.left(realm.find(','));284 removeLeadingAndTrailingQuotes(realm);285 }286 287 ProtectionSpaceServerType serverType = ProtectionSpaceServerHTTP;288 if (protocol == "https")289 serverType = ProtectionSpaceServerHTTPS;290 291 ProtectionSpaceAuthenticationScheme authScheme = ProtectionSpaceAuthenticationSchemeUnknown;292 293 if (availableAuth & CURLAUTH_BASIC)294 authScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;295 if (availableAuth & CURLAUTH_DIGEST)296 authScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;297 if (availableAuth & CURLAUTH_GSSNEGOTIATE)298 authScheme = ProtectionSpaceAuthenticationSchemeNegotiate;299 if (availableAuth & CURLAUTH_NTLM)300 authScheme = ProtectionSpaceAuthenticationSchemeNTLM;301 302 protectionSpace = ProtectionSpace(host, port, serverType, realm, authScheme);303 304 return true;305 }306 307 /*308 * This is being called for each HTTP header in the response. This includes '\r\n'309 * for the last line of the header.310 *311 * We will add each HTTP Header to the ResourceResponse and on the termination312 * of the header (\r\n) we will parse Content-Type and Content-Disposition and313 * update the ResourceResponse and then send it away.314 *315 */316 static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)317 {318 ResourceHandle* job = static_cast<ResourceHandle*>(data);319 ResourceHandleInternal* d = job->getInternal();320 if (d->m_cancelled)321 return 0;322 323 // We should never be called when deferred loading is activated.324 ASSERT(!d->m_defersLoading);325 326 size_t totalSize = size * nmemb;327 ResourceHandleClient* client = d->client();328 329 String header(static_cast<const char*>(ptr), totalSize);330 331 /*332 * a) We can finish and send the ResourceResponse333 * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse334 *335 * The HTTP standard requires to use \r\n but for compatibility it recommends to336 * accept also \n.337 */338 if (header == String("\r\n") || header == String("\n")) {339 CURL* h = d->m_handle;340 341 long httpCode = 0;342 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);343 344 if (!httpCode) {345 // Comes here when receiving 200 Connection Established. Just return.346 return totalSize;347 }348 if (isHttpInfo(httpCode)) {349 // Just return when receiving http info, e.g. HTTP/1.1 100 Continue.350 // If not, the request might be cancelled, because the MIME type will be empty for this response.351 return totalSize;352 }353 354 double contentLength = 0;355 curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength);356 d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength));357 358 d->m_response.setURL(CurlContext::singleton().getEffectiveURL(h));359 360 d->m_response.setHTTPStatusCode(httpCode);361 d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField(HTTPHeaderName::ContentType)).convertToASCIILowercase());362 d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField(HTTPHeaderName::ContentType)));363 364 if (d->m_response.isMultipart()) {365 String boundary;366 bool parsed = MultipartHandle::extractBoundary(d->m_response.httpHeaderField(HTTPHeaderName::ContentType), boundary);367 if (parsed)368 d->m_multipartHandle = std::make_unique<MultipartHandle>(job, boundary);369 }370 371 // HTTP redirection372 if (isHttpRedirect(httpCode)) {373 String location = d->m_response.httpHeaderField(HTTPHeaderName::Location);374 if (!location.isEmpty()) {375 URL newURL = URL(job->firstRequest().url(), location);376 377 ResourceRequest redirectedRequest = job->firstRequest();378 redirectedRequest.setURL(newURL);379 ResourceResponse response = d->m_response;380 if (client)381 client->willSendRequest(job, WTFMove(redirectedRequest), WTFMove(response));382 383 d->m_firstRequest.setURL(newURL);384 385 return totalSize;386 }387 } else if (isHttpAuthentication(httpCode)) {388 ProtectionSpace protectionSpace;389 if (getProtectionSpace(d->m_handle, d->m_response, protectionSpace)) {390 Credential credential;391 AuthenticationChallenge challenge(protectionSpace, credential, d->m_authFailureCount, d->m_response, ResourceError());392 challenge.setAuthenticationClient(job);393 job->didReceiveAuthenticationChallenge(challenge);394 d->m_authFailureCount++;395 return totalSize;396 }397 }398 399 if (client) {400 if (isHttpNotModified(httpCode)) {401 const String& url = job->firstRequest().url().string();402 if (CurlCacheManager::getInstance().getCachedResponse(url, d->m_response)) {403 if (d->m_addedCacheValidationHeaders) {404 d->m_response.setHTTPStatusCode(200);405 d->m_response.setHTTPStatusText("OK");406 }407 }408 }409 client->didReceiveResponse(job, ResourceResponse(d->m_response));410 CurlCacheManager::getInstance().didReceiveResponse(*job, d->m_response);411 }412 d->m_response.setResponseFired(true);413 414 } else {415 int splitPos = header.find(":");416 if (splitPos != -1) {417 String key = header.left(splitPos).stripWhiteSpace();418 String value = header.substring(splitPos + 1).stripWhiteSpace();419 420 if (isAppendableHeader(key))421 d->m_response.addHTTPHeaderField(key, value);422 else423 d->m_response.setHTTPHeaderField(key, value);424 } else if (header.startsWith("HTTP", false)) {425 // This is the first line of the response.426 // Extract the http status text from this.427 //428 // If the FOLLOWLOCATION option is enabled for the curl handle then429 // curl will follow the redirections internally. Thus this header callback430 // will be called more than one time with the line starting "HTTP" for one job.431 long httpCode = 0;432 curl_easy_getinfo(d->m_handle, CURLINFO_RESPONSE_CODE, &httpCode);433 434 String httpCodeString = String::number(httpCode);435 int statusCodePos = header.find(httpCodeString);436 437 if (statusCodePos != -1) {438 // The status text is after the status code.439 String status = header.substring(statusCodePos + httpCodeString.length());440 d->m_response.setHTTPStatusText(status.stripWhiteSpace());441 }442 443 }444 }445 446 return totalSize;447 }448 449 /* This is called to obtain HTTP POST or PUT data.450 Iterate through FormData elements and upload files.451 Carefully respect the given buffer size and fill the rest of the data at the next calls.452 */453 size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data)454 {455 ResourceHandle* job = static_cast<ResourceHandle*>(data);456 ResourceHandleInternal* d = job->getInternal();457 458 if (d->m_cancelled)459 return 0;460 461 // We should never be called when deferred loading is activated.462 ASSERT(!d->m_defersLoading);463 464 if (!size || !nmemb)465 return 0;466 467 if (!d->m_formDataStream.hasMoreElements())468 return 0;469 470 size_t sent = d->m_formDataStream.read(ptr, size, nmemb);471 472 // Something went wrong so cancel the job.473 if (!sent)474 job->cancel();475 476 return sent;477 98 } 478 99 … … 542 163 continue; 543 164 544 545 if (CURLE_OK == msg->data.result) { 546 #if ENABLE(WEB_TIMING) 547 calculateWebTimingInformations(d); 548 #endif 549 if (!d->m_response.responseFired()) { 550 handleLocalReceiveResponse(d->m_handle, job, d); 551 if (d->m_cancelled) { 552 removeFromCurl(job); 553 continue; 554 } 555 } 556 557 if (d->m_multipartHandle) 558 d->m_multipartHandle->contentEnded(); 559 560 if (d->client()) { 561 d->client()->didFinishLoading(job); 562 CurlCacheManager::getInstance().didFinishLoading(*job); 563 } 564 } else { 565 URL url = CurlContext::singleton().getEffectiveURL(d->m_handle); 566 #ifndef NDEBUG 567 fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url.string().utf8().data(), curl_easy_strerror(msg->data.result)); 568 #endif 569 if (d->client()) { 570 ResourceError resourceError(ASCIILiteral(errorDomainCurl), msg->data.result, url, String(curl_easy_strerror(msg->data.result))); 571 resourceError.setSSLErrors(d->m_sslErrors); 572 d->client()->didFail(job, resourceError); 573 CurlCacheManager::getInstance().didFail(*job); 574 } 575 } 576 165 job->handleCurlMsg(msg); 577 166 removeFromCurl(job); 578 167 } … … 595 184 d->m_handle = 0; 596 185 job->deref(); 597 }598 599 static inline size_t getFormElementsCount(ResourceHandle* job)600 {601 RefPtr<FormData> formData = job->firstRequest().httpBody();602 603 if (!formData)604 return 0;605 606 // Resolve the blob elements so the formData can correctly report it's size.607 formData = formData->resolveBlobReferences();608 size_t size = formData->elements().size();609 job->firstRequest().setHTTPBody(WTFMove(formData));610 611 return size;612 }613 614 static void setupFormData(ResourceHandle* job, CURLoption sizeOption, struct curl_slist** headers)615 {616 ResourceHandleInternal* d = job->getInternal();617 Vector<FormDataElement> elements = job->firstRequest().httpBody()->elements();618 size_t numElements = elements.size();619 620 // The size of a curl_off_t could be different in WebKit and in cURL depending on621 // compilation flags of both.622 static int expectedSizeOfCurlOffT = 0;623 if (!expectedSizeOfCurlOffT) {624 curl_version_info_data *infoData = curl_version_info(CURLVERSION_NOW);625 if (infoData->features & CURL_VERSION_LARGEFILE)626 expectedSizeOfCurlOffT = sizeof(long long);627 else628 expectedSizeOfCurlOffT = sizeof(int);629 }630 631 static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1;632 // Obtain the total size of the form data633 curl_off_t size = 0;634 bool chunkedTransfer = false;635 for (size_t i = 0; i < numElements; i++) {636 FormDataElement element = elements[i];637 if (element.m_type == FormDataElement::Type::EncodedFile) {638 long long fileSizeResult;639 if (getFileSize(element.m_filename, fileSizeResult)) {640 if (fileSizeResult > maxCurlOffT) {641 // File size is too big for specifying it to cURL642 chunkedTransfer = true;643 break;644 }645 size += fileSizeResult;646 } else {647 chunkedTransfer = true;648 break;649 }650 } else651 size += elements[i].m_data.size();652 }653 654 // cURL guesses that we want chunked encoding as long as we specify the header655 if (chunkedTransfer)656 *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked");657 else {658 if (sizeof(long long) == expectedSizeOfCurlOffT)659 curl_easy_setopt(d->m_handle, sizeOption, (long long)size);660 else661 curl_easy_setopt(d->m_handle, sizeOption, (int)size);662 }663 664 curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback);665 curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job);666 }667 668 void ResourceHandleManager::setupPUT(ResourceHandle* job, struct curl_slist** headers)669 {670 ResourceHandleInternal* d = job->getInternal();671 curl_easy_setopt(d->m_handle, CURLOPT_UPLOAD, TRUE);672 curl_easy_setopt(d->m_handle, CURLOPT_INFILESIZE, 0);673 674 // Disable the Expect: 100 continue header675 *headers = curl_slist_append(*headers, "Expect:");676 677 size_t numElements = getFormElementsCount(job);678 if (!numElements)679 return;680 681 setupFormData(job, CURLOPT_INFILESIZE_LARGE, headers);682 }683 684 void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers)685 {686 ResourceHandleInternal* d = job->getInternal();687 curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);688 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, 0);689 690 size_t numElements = getFormElementsCount(job);691 if (!numElements)692 return;693 694 // Do not stream for simple POST data695 if (numElements == 1) {696 job->firstRequest().httpBody()->flatten(d->m_postBytes);697 if (d->m_postBytes.size()) {698 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size());699 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data());700 }701 return;702 }703 704 setupFormData(job, CURLOPT_POSTFIELDSIZE_LARGE, headers);705 186 } 706 187 … … 742 223 } 743 224 744 static void handleDataURL(ResourceHandle* handle)745 {746 ASSERT(handle->firstRequest().url().protocolIsData());747 String url = handle->firstRequest().url().string();748 749 ASSERT(handle);750 ASSERT(handle->client());751 752 int index = url.find(',');753 if (index == -1) {754 handle->client()->cannotShowURL(handle);755 return;756 }757 758 String mediaType = url.substring(5, index - 5);759 String data = url.substring(index + 1);760 761 bool base64 = mediaType.endsWith(";base64", false);762 if (base64)763 mediaType = mediaType.left(mediaType.length() - 7);764 765 if (mediaType.isEmpty())766 mediaType = "text/plain";767 768 String mimeType = extractMIMETypeFromMediaType(mediaType);769 String charset = extractCharsetFromMediaType(mediaType);770 771 if (charset.isEmpty())772 charset = "US-ASCII";773 774 ResourceResponse response;775 response.setMimeType(mimeType);776 response.setTextEncodingName(charset);777 response.setURL(handle->firstRequest().url());778 779 if (base64) {780 data = decodeURLEscapeSequences(data);781 handle->client()->didReceiveResponse(handle, WTFMove(response));782 783 // didReceiveResponse might cause the client to be deleted.784 if (handle->client()) {785 Vector<char> out;786 if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)787 handle->client()->didReceiveData(handle, out.data(), out.size(), 0);788 }789 } else {790 TextEncoding encoding(charset);791 data = decodeURLEscapeSequences(data, encoding);792 handle->client()->didReceiveResponse(handle, WTFMove(response));793 794 // didReceiveResponse might cause the client to be deleted.795 if (handle->client()) {796 CString encodedData = encoding.encode(data, URLEncodedEntitiesForUnencodables);797 if (encodedData.length())798 handle->client()->didReceiveData(handle, encodedData.data(), encodedData.length(), 0);799 }800 }801 802 if (handle->client())803 handle->client()->didFinishLoading(handle);804 }805 806 void ResourceHandleManager::dispatchSynchronousJob(ResourceHandle* job)807 {808 URL kurl = job->firstRequest().url();809 810 if (kurl.protocolIsData()) {811 handleDataURL(job);812 return;813 }814 815 ResourceHandleInternal* handle = job->getInternal();816 817 // If defersLoading is true and we call curl_easy_perform818 // on a paused handle, libcURL would do the transfert anyway819 // and we would assert so force defersLoading to be false.820 handle->m_defersLoading = false;821 822 initializeHandle(job);823 824 // curl_easy_perform blocks until the transfert is finished.825 CURLcode ret = curl_easy_perform(handle->m_handle);826 827 if (ret != CURLE_OK) {828 ResourceError error(ASCIILiteral(errorDomainCurl), ret, kurl, String(curl_easy_strerror(ret)));829 error.setSSLErrors(handle->m_sslErrors);830 handle->client()->didFail(job, error);831 } else {832 if (handle->client())833 handle->client()->didReceiveResponse(job, ResourceResponse(handle->m_response));834 }835 836 #if ENABLE(WEB_TIMING)837 calculateWebTimingInformations(handle);838 #endif839 840 curl_easy_cleanup(handle->m_handle);841 }842 843 225 void ResourceHandleManager::startJob(ResourceHandle* job) 844 226 { … … 846 228 847 229 if (url.protocolIsData()) { 848 handleDataURL(job);230 job->handleDataURL(); 849 231 job->deref(); 850 232 return; 851 233 } 852 234 853 initializeHandle(job);235 job->initialize(); 854 236 855 237 m_runningJobs++; … … 866 248 } 867 249 868 void ResourceHandleManager::applyAuthenticationToRequest(ResourceHandle* handle, ResourceRequest& request)869 {870 // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().871 ResourceHandleInternal* d = handle->getInternal();872 873 String partition = handle->firstRequest().cachePartition();874 875 if (handle->shouldUseCredentialStorage()) {876 if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {877 // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication,878 // try and reuse the credential preemptively, as allowed by RFC 2617.879 d->m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());880 } else {881 // If there is already a protection space known for the URL, update stored credentials882 // before sending a request. This makes it possible to implement logout by sending an883 // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that884 // an authentication dialog doesn't pop up).885 CredentialStorage::defaultCredentialStorage().set(partition, Credential(d->m_user, d->m_pass, CredentialPersistenceNone), request.url());886 }887 }888 889 String user = d->m_user;890 String password = d->m_pass;891 892 if (!d->m_initialCredential.isEmpty()) {893 user = d->m_initialCredential.user();894 password = d->m_initialCredential.password();895 curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);896 }897 898 // It seems we need to set CURLOPT_USERPWD even if username and password is empty.899 // Otherwise cURL will not automatically continue with a new request after a 401 response.900 901 // curl CURLOPT_USERPWD expects username:password902 String userpass = user + ":" + password;903 curl_easy_setopt(d->m_handle, CURLOPT_USERPWD, userpass.utf8().data());904 }905 906 void ResourceHandleManager::initializeHandle(ResourceHandle* job)907 {908 CurlContext& context = CurlContext::singleton();909 910 static const int allowedProtocols = CURLPROTO_FILE | CURLPROTO_FTP | CURLPROTO_FTPS | CURLPROTO_HTTP | CURLPROTO_HTTPS;911 URL url = job->firstRequest().url();912 913 // Remove any fragment part, otherwise curl will send it as part of the request.914 url.removeFragmentIdentifier();915 916 ResourceHandleInternal* d = job->getInternal();917 String urlString = url.string();918 919 if (url.isLocalFile()) {920 // Remove any query part sent to a local file.921 if (!url.query().isEmpty()) {922 // By setting the query to a null string it'll be removed.923 url.setQuery(String());924 urlString = url.string();925 }926 // Determine the MIME type based on the path.927 d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url));928 }929 930 d->m_handle = curl_easy_init();931 932 if (d->m_defersLoading) {933 CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL);934 // If we did not pause the handle, we would ASSERT in the935 // header callback. So just assert here.936 ASSERT_UNUSED(error, error == CURLE_OK);937 }938 #ifndef NDEBUG939 if (context.isVerbose())940 curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1);941 if (context.getLogFile())942 curl_easy_setopt(d->m_handle, CURLOPT_STDERR, context.getLogFile());943 #endif944 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, 1L);945 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYHOST, 2L);946 curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job);947 curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer);948 curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback);949 curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job);950 curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback);951 curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job);952 curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1);953 curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1);954 curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10);955 curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);956 curl_easy_setopt(d->m_handle, CURLOPT_SHARE, CurlContext::singleton().curlShareHandle());957 curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes958 curl_easy_setopt(d->m_handle, CURLOPT_PROTOCOLS, allowedProtocols);959 curl_easy_setopt(d->m_handle, CURLOPT_REDIR_PROTOCOLS, allowedProtocols);960 setSSLClientCertificate(job);961 962 if (context.shouldIgnoreSSLErrors())963 curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false);964 else965 setSSLVerifyOptions(job);966 967 const char* certificate = context.getCertificatePath();968 if (certificate)969 curl_easy_setopt(d->m_handle, CURLOPT_CAINFO, certificate);970 971 // enable gzip and deflate through Accept-Encoding:972 curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, "");973 974 // url must remain valid through the request975 ASSERT(!d->m_url);976 977 // url is in ASCII so latin1() will only convert it to char* without character translation.978 d->m_url = fastStrDup(urlString.latin1().data());979 curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url);980 981 const char* cookieJar = context.getCookieJarFileName();982 if (cookieJar)983 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, cookieJar);984 985 struct curl_slist* headers = 0;986 if (job->firstRequest().httpHeaderFields().size() > 0) {987 HTTPHeaderMap customHeaders = job->firstRequest().httpHeaderFields();988 989 bool hasCacheHeaders = customHeaders.contains(HTTPHeaderName::IfModifiedSince) || customHeaders.contains(HTTPHeaderName::IfNoneMatch);990 if (!hasCacheHeaders && CurlCacheManager::getInstance().isCached(url)) {991 CurlCacheManager::getInstance().addCacheEntryClient(url, job);992 HTTPHeaderMap& requestHeaders = CurlCacheManager::getInstance().requestHeaders(url);993 994 // append additional cache information995 HTTPHeaderMap::const_iterator it = requestHeaders.begin();996 HTTPHeaderMap::const_iterator end = requestHeaders.end();997 while (it != end) {998 customHeaders.set(it->key, it->value);999 ++it;1000 }1001 d->m_addedCacheValidationHeaders = true;1002 }1003 1004 HTTPHeaderMap::const_iterator end = customHeaders.end();1005 for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {1006 String key = it->key;1007 String value = it->value;1008 String headerString(key);1009 if (value.isEmpty())1010 // Insert the ; to tell curl that this header has an empty value.1011 headerString.append(";");1012 else {1013 headerString.append(": ");1014 headerString.append(value);1015 }1016 CString headerLatin1 = headerString.latin1();1017 headers = curl_slist_append(headers, headerLatin1.data());1018 }1019 }1020 1021 String method = job->firstRequest().httpMethod();1022 if ("GET" == method)1023 curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE);1024 else if ("POST" == method)1025 setupPOST(job, &headers);1026 else if ("PUT" == method)1027 setupPUT(job, &headers);1028 else if ("HEAD" == method)1029 curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE);1030 else {1031 curl_easy_setopt(d->m_handle, CURLOPT_CUSTOMREQUEST, method.ascii().data());1032 setupPUT(job, &headers);1033 }1034 1035 if (headers) {1036 curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);1037 d->m_customHeaders = headers;1038 }1039 1040 applyAuthenticationToRequest(job, job->firstRequest());1041 1042 // Set proxy options if we have them.1043 auto& proxy = CurlContext::singleton().proxyInfo();1044 if (proxy.type != CurlProxyType::Invalid) {1045 curl_easy_setopt(d->m_handle, CURLOPT_PROXY, proxy.url().utf8().data());1046 curl_easy_setopt(d->m_handle, CURLOPT_PROXYTYPE, proxy.type);1047 }1048 }1049 1050 250 void ResourceHandleManager::cancel(ResourceHandle* job) 1051 251 { -
trunk/Source/WebCore/platform/network/curl/ResourceHandleManager.h
r218947 r218962 51 51 void cancel(ResourceHandle*); 52 52 53 void dispatchSynchronousJob(ResourceHandle*);54 55 void setupPOST(ResourceHandle*, struct curl_slist**);56 void setupPUT(ResourceHandle*, struct curl_slist**);57 58 53 private: 59 54 ResourceHandleManager(); … … 64 59 void startJob(ResourceHandle*); 65 60 bool startScheduledJobs(); 66 void applyAuthenticationToRequest(ResourceHandle*, ResourceRequest&);67 68 void initializeHandle(ResourceHandle*);69 61 70 62 Timer m_downloadTimer; 71 63 CURLM* m_curlMultiHandle; 72 char m_curlErrorBuffer[CURL_ERROR_SIZE];73 64 Vector<ResourceHandle*> m_resourceHandleList; 74 65 int m_runningJobs;
Note: See TracChangeset
for help on using the changeset viewer.