1/* 2 * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org> 4 * Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org> 5 * Copyright (C) 2008, 2011 Google Inc. All rights reserved. 6 * Copyright (C) 2012 Intel Corporation 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Lesser General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public 19 * License along with this library; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 */ 22 23#include "config.h" 24#include "XMLHttpRequest.h" 25 26#include "Blob.h" 27#include "ContentSecurityPolicy.h" 28#include "CrossOriginAccessControl.h" 29#include "DOMFormData.h" 30#include "DOMImplementation.h" 31#include "Event.h" 32#include "EventException.h" 33#include "ExceptionCode.h" 34#include "File.h" 35#include "HTMLDocument.h" 36#include "HTTPHeaderNames.h" 37#include "HTTPParsers.h" 38#include "InspectorInstrumentation.h" 39#include "JSDOMBinding.h" 40#include "JSDOMWindow.h" 41#include "MemoryCache.h" 42#include "ParsedContentType.h" 43#include "ResourceError.h" 44#include "ResourceRequest.h" 45#include "ScriptController.h" 46#include "Settings.h" 47#include "SharedBuffer.h" 48#include "TextResourceDecoder.h" 49#include "ThreadableLoader.h" 50#include "XMLHttpRequestException.h" 51#include "XMLHttpRequestProgressEvent.h" 52#include "XMLHttpRequestUpload.h" 53#include "markup.h" 54#include <heap/Strong.h> 55#include <mutex> 56#include <runtime/ArrayBuffer.h> 57#include <runtime/ArrayBufferView.h> 58#include <runtime/JSCInlines.h> 59#include <runtime/JSLock.h> 60#include <wtf/NeverDestroyed.h> 61#include <wtf/Ref.h> 62#include <wtf/RefCountedLeakCounter.h> 63#include <wtf/StdLibExtras.h> 64#include <wtf/text/CString.h> 65 66#if ENABLE(RESOURCE_TIMING) 67#include "CachedResourceRequestInitiators.h" 68#endif 69 70namespace WebCore { 71 72DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, xmlHttpRequestCounter, ("XMLHttpRequest")); 73 74// Histogram enum to see when we can deprecate xhr.send(ArrayBuffer). 75enum XMLHttpRequestSendArrayBufferOrView { 76 XMLHttpRequestSendArrayBuffer, 77 XMLHttpRequestSendArrayBufferView, 78 XMLHttpRequestSendArrayBufferOrViewMax, 79}; 80 81static bool isSetCookieHeader(const String& name) 82{ 83 return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2"); 84} 85 86static void replaceCharsetInMediaType(String& mediaType, const String& charsetValue) 87{ 88 unsigned int pos = 0, len = 0; 89 90 findCharsetInMediaType(mediaType, pos, len); 91 92 if (!len) { 93 // When no charset found, do nothing. 94 return; 95 } 96 97 // Found at least one existing charset, replace all occurrences with new charset. 98 while (len) { 99 mediaType.replace(pos, len, charsetValue); 100 unsigned int start = pos + charsetValue.length(); 101 findCharsetInMediaType(mediaType, pos, len, start); 102 } 103} 104 105static void logConsoleError(ScriptExecutionContext* context, const String& message) 106{ 107 if (!context) 108 return; 109 // FIXME: It's not good to report the bad usage without indicating what source line it came from. 110 // We should pass additional parameters so we can tell the console where the mistake occurred. 111 context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, message); 112} 113 114PassRefPtr<XMLHttpRequest> XMLHttpRequest::create(ScriptExecutionContext& context) 115{ 116 RefPtr<XMLHttpRequest> xmlHttpRequest(adoptRef(new XMLHttpRequest(context))); 117 xmlHttpRequest->suspendIfNeeded(); 118 119 return xmlHttpRequest.release(); 120} 121 122XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext& context) 123 : ActiveDOMObject(&context) 124 , m_async(true) 125 , m_includeCredentials(false) 126#if ENABLE(XHR_TIMEOUT) 127 , m_timeoutMilliseconds(0) 128#endif 129 , m_state(UNSENT) 130 , m_createdDocument(false) 131 , m_error(false) 132 , m_uploadEventsAllowed(true) 133 , m_uploadComplete(false) 134 , m_sameOriginRequest(true) 135 , m_receivedLength(0) 136 , m_lastSendLineNumber(0) 137 , m_lastSendColumnNumber(0) 138 , m_exceptionCode(0) 139 , m_progressEventThrottle(this) 140 , m_responseTypeCode(ResponseTypeDefault) 141 , m_responseCacheIsValid(false) 142{ 143#ifndef NDEBUG 144 xmlHttpRequestCounter.increment(); 145#endif 146} 147 148XMLHttpRequest::~XMLHttpRequest() 149{ 150#ifndef NDEBUG 151 xmlHttpRequestCounter.decrement(); 152#endif 153} 154 155Document* XMLHttpRequest::document() const 156{ 157 ASSERT_WITH_SECURITY_IMPLICATION(scriptExecutionContext()->isDocument()); 158 return static_cast<Document*>(scriptExecutionContext()); 159} 160 161SecurityOrigin* XMLHttpRequest::securityOrigin() const 162{ 163 return scriptExecutionContext()->securityOrigin(); 164} 165 166#if ENABLE(DASHBOARD_SUPPORT) 167bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const 168{ 169 if (scriptExecutionContext()->isWorkerGlobalScope()) 170 return false; 171 Settings* settings = document()->settings(); 172 return settings && settings->usesDashboardBackwardCompatibilityMode(); 173} 174#endif 175 176XMLHttpRequest::State XMLHttpRequest::readyState() const 177{ 178 return m_state; 179} 180 181String XMLHttpRequest::responseText(ExceptionCode& ec) 182{ 183 if (m_responseTypeCode != ResponseTypeDefault && m_responseTypeCode != ResponseTypeText) { 184 ec = INVALID_STATE_ERR; 185 return ""; 186 } 187 return responseTextIgnoringResponseType(); 188} 189 190void XMLHttpRequest::didCacheResponseJSON() 191{ 192 ASSERT(m_responseTypeCode == ResponseTypeJSON); 193 ASSERT(doneWithoutErrors()); 194 m_responseCacheIsValid = true; 195 m_responseBuilder.clear(); 196} 197 198Document* XMLHttpRequest::responseXML(ExceptionCode& ec) 199{ 200 if (m_responseTypeCode != ResponseTypeDefault && m_responseTypeCode != ResponseTypeDocument) { 201 ec = INVALID_STATE_ERR; 202 return nullptr; 203 } 204 205 if (!doneWithoutErrors()) 206 return nullptr; 207 208 if (!m_createdDocument) { 209 bool isHTML = equalIgnoringCase(responseMIMEType(), "text/html"); 210 211 // The W3C spec requires the final MIME type to be some valid XML type, or text/html. 212 // If it is text/html, then the responseType of "document" must have been supplied explicitly. 213 if ((m_response.isHTTP() && !responseIsXML() && !isHTML) 214 || (isHTML && m_responseTypeCode == ResponseTypeDefault) 215 || scriptExecutionContext()->isWorkerGlobalScope()) { 216 m_responseDocument = 0; 217 } else { 218 if (isHTML) 219 m_responseDocument = HTMLDocument::create(0, m_url); 220 else 221 m_responseDocument = Document::create(0, m_url); 222 // FIXME: Set Last-Modified. 223 m_responseDocument->setContent(m_responseBuilder.toStringPreserveCapacity()); 224 m_responseDocument->setSecurityOrigin(securityOrigin()); 225 if (!m_responseDocument->wellFormed()) 226 m_responseDocument = 0; 227 } 228 m_createdDocument = true; 229 } 230 231 return m_responseDocument.get(); 232} 233 234Blob* XMLHttpRequest::responseBlob() 235{ 236 ASSERT(m_responseTypeCode == ResponseTypeBlob); 237 ASSERT(doneWithoutErrors()); 238 239 if (!m_responseBlob) { 240 if (m_binaryResponseBuilder) { 241 // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient. 242 Vector<char> data; 243 data.append(m_binaryResponseBuilder->data(), m_binaryResponseBuilder->size()); 244 String normalizedContentType = Blob::normalizedContentType(responseMIMEType()); // responseMIMEType defaults to text/xml which may be incorrect. 245 m_responseBlob = Blob::create(WTF::move(data), normalizedContentType); 246 m_binaryResponseBuilder.clear(); 247 } else { 248 // If we errored out or got no data, we still return a blob, just an empty one. 249 m_responseBlob = Blob::create(); 250 } 251 } 252 253 return m_responseBlob.get(); 254} 255 256ArrayBuffer* XMLHttpRequest::responseArrayBuffer() 257{ 258 ASSERT(m_responseTypeCode == ResponseTypeArrayBuffer); 259 ASSERT(doneWithoutErrors()); 260 261 if (!m_responseArrayBuffer) { 262 if (m_binaryResponseBuilder) 263 m_responseArrayBuffer = m_binaryResponseBuilder->createArrayBuffer(); 264 else 265 m_responseArrayBuffer = ArrayBuffer::create(nullptr, 0); 266 m_binaryResponseBuilder.clear(); 267 } 268 269 return m_responseArrayBuffer.get(); 270} 271 272#if ENABLE(XHR_TIMEOUT) 273void XMLHttpRequest::setTimeout(unsigned long timeout, ExceptionCode& ec) 274{ 275 // FIXME: Need to trigger or update the timeout Timer here, if needed. http://webkit.org/b/98156 276 // XHR2 spec, 4.7.3. "This implies that the timeout attribute can be set while fetching is in progress. If that occurs it will still be measured relative to the start of fetching." 277 if (scriptExecutionContext()->isDocument() && !m_async) { 278 logConsoleError(scriptExecutionContext(), "XMLHttpRequest.timeout cannot be set for synchronous HTTP(S) requests made from the window context."); 279 ec = INVALID_ACCESS_ERR; 280 return; 281 } 282 m_timeoutMilliseconds = timeout; 283} 284#endif 285 286void XMLHttpRequest::setResponseType(const String& responseType, ExceptionCode& ec) 287{ 288 if (m_state >= LOADING) { 289 ec = INVALID_STATE_ERR; 290 return; 291 } 292 293 // Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated 294 // attempt to discourage synchronous XHR use. responseType is one such piece of functionality. 295 // We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols 296 // such as file: and data: still make sense to allow. 297 if (!m_async && scriptExecutionContext()->isDocument() && m_url.protocolIsInHTTPFamily()) { 298 logConsoleError(scriptExecutionContext(), "XMLHttpRequest.responseType cannot be changed for synchronous HTTP(S) requests made from the window context."); 299 ec = INVALID_ACCESS_ERR; 300 return; 301 } 302 303 if (responseType == "") 304 m_responseTypeCode = ResponseTypeDefault; 305 else if (responseType == "text") 306 m_responseTypeCode = ResponseTypeText; 307 else if (responseType == "json") 308 m_responseTypeCode = ResponseTypeJSON; 309 else if (responseType == "document") 310 m_responseTypeCode = ResponseTypeDocument; 311 else if (responseType == "blob") 312 m_responseTypeCode = ResponseTypeBlob; 313 else if (responseType == "arraybuffer") 314 m_responseTypeCode = ResponseTypeArrayBuffer; 315 else 316 ASSERT_NOT_REACHED(); 317} 318 319String XMLHttpRequest::responseType() 320{ 321 switch (m_responseTypeCode) { 322 case ResponseTypeDefault: 323 return ""; 324 case ResponseTypeText: 325 return "text"; 326 case ResponseTypeJSON: 327 return "json"; 328 case ResponseTypeDocument: 329 return "document"; 330 case ResponseTypeBlob: 331 return "blob"; 332 case ResponseTypeArrayBuffer: 333 return "arraybuffer"; 334 } 335 return ""; 336} 337 338void XMLHttpRequest::setLastSendLineAndColumnNumber(unsigned lineNumber, unsigned columnNumber) 339{ 340 m_lastSendLineNumber = lineNumber; 341 m_lastSendColumnNumber = columnNumber; 342} 343 344XMLHttpRequestUpload* XMLHttpRequest::upload() 345{ 346 if (!m_upload) 347 m_upload = std::make_unique<XMLHttpRequestUpload>(this); 348 return m_upload.get(); 349} 350 351void XMLHttpRequest::changeState(State newState) 352{ 353 if (m_state != newState) { 354 m_state = newState; 355 callReadyStateChangeListener(); 356 } 357} 358 359void XMLHttpRequest::callReadyStateChangeListener() 360{ 361 if (!scriptExecutionContext()) 362 return; 363 364 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchXHRReadyStateChangeEvent(scriptExecutionContext(), this); 365 366 if (m_async || (m_state <= OPENED || m_state == DONE)) 367 m_progressEventThrottle.dispatchReadyStateChangeEvent(Event::create(eventNames().readystatechangeEvent, false, false), m_state == DONE ? FlushProgressEvent : DoNotFlushProgressEvent); 368 369 InspectorInstrumentation::didDispatchXHRReadyStateChangeEvent(cookie); 370 if (m_state == DONE && !m_error) { 371 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchXHRLoadEvent(scriptExecutionContext(), this); 372 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadEvent); 373 InspectorInstrumentation::didDispatchXHRLoadEvent(cookie); 374 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadendEvent); 375 } 376} 377 378void XMLHttpRequest::setWithCredentials(bool value, ExceptionCode& ec) 379{ 380 if (m_state > OPENED || m_loader) { 381 ec = INVALID_STATE_ERR; 382 return; 383 } 384 385 m_includeCredentials = value; 386} 387 388bool XMLHttpRequest::isAllowedHTTPMethod(const String& method) 389{ 390 return !equalIgnoringCase(method, "TRACE") 391 && !equalIgnoringCase(method, "TRACK") 392 && !equalIgnoringCase(method, "CONNECT"); 393} 394 395String XMLHttpRequest::uppercaseKnownHTTPMethod(const String& method) 396{ 397 const char* const methods[] = { "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT" }; 398 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(methods); ++i) { 399 if (equalIgnoringCase(method, methods[i])) { 400 // Don't bother allocating a new string if it's already all uppercase. 401 if (method == methods[i]) 402 break; 403 return ASCIILiteral(methods[i]); 404 } 405 } 406 return method; 407} 408 409static bool isForbiddenRequestHeader(const String& name) 410{ 411 HTTPHeaderName headerName; 412 if (!findHTTPHeaderName(name, headerName)) 413 return false; 414 415 switch (headerName) { 416 case HTTPHeaderName::AcceptCharset: 417 case HTTPHeaderName::AcceptEncoding: 418 case HTTPHeaderName::AccessControlRequestHeaders: 419 case HTTPHeaderName::AccessControlRequestMethod: 420 case HTTPHeaderName::Connection: 421 case HTTPHeaderName::ContentLength: 422 case HTTPHeaderName::ContentTransferEncoding: 423 case HTTPHeaderName::Cookie: 424 case HTTPHeaderName::Cookie2: 425 case HTTPHeaderName::Date: 426 case HTTPHeaderName::DNT: 427 case HTTPHeaderName::Expect: 428 case HTTPHeaderName::Host: 429 case HTTPHeaderName::KeepAlive: 430 case HTTPHeaderName::Origin: 431 case HTTPHeaderName::Referer: 432 case HTTPHeaderName::TE: 433 case HTTPHeaderName::Trailer: 434 case HTTPHeaderName::TransferEncoding: 435 case HTTPHeaderName::Upgrade: 436 case HTTPHeaderName::UserAgent: 437 case HTTPHeaderName::Via: 438 return true; 439 440 default: 441 return false; 442 } 443} 444 445bool XMLHttpRequest::isAllowedHTTPHeader(const String& name) 446{ 447 if (isForbiddenRequestHeader(name)) 448 return false; 449 450 if (name.startsWith("proxy-", false)) 451 return false; 452 453 if (name.startsWith("sec-", false)) 454 return false; 455 456 return true; 457} 458 459void XMLHttpRequest::open(const String& method, const URL& url, ExceptionCode& ec) 460{ 461 open(method, url, true, ec); 462} 463 464void XMLHttpRequest::open(const String& method, const URL& url, bool async, ExceptionCode& ec) 465{ 466 internalAbort(); 467 State previousState = m_state; 468 m_state = UNSENT; 469 m_error = false; 470 m_uploadComplete = false; 471 472 // clear stuff from possible previous load 473 clearResponse(); 474 clearRequest(); 475 476 ASSERT(m_state == UNSENT); 477 478 if (!isValidHTTPToken(method)) { 479 ec = SYNTAX_ERR; 480 return; 481 } 482 483 if (!isAllowedHTTPMethod(method)) { 484 ec = SECURITY_ERR; 485 return; 486 } 487 488 // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. 489 bool shouldBypassMainWorldContentSecurityPolicy = false; 490 if (scriptExecutionContext()->isDocument()) { 491 Document* document = static_cast<Document*>(scriptExecutionContext()); 492 if (document->frame()) 493 shouldBypassMainWorldContentSecurityPolicy = document->frame()->script().shouldBypassMainWorldContentSecurityPolicy(); 494 } 495 if (!shouldBypassMainWorldContentSecurityPolicy && !scriptExecutionContext()->contentSecurityPolicy()->allowConnectToSource(url)) { 496 // FIXME: Should this be throwing an exception? 497 ec = SECURITY_ERR; 498 return; 499 } 500 501 if (!async && scriptExecutionContext()->isDocument()) { 502 if (document()->settings() && !document()->settings()->syncXHRInDocumentsEnabled()) { 503 logConsoleError(scriptExecutionContext(), "Synchronous XMLHttpRequests are disabled for this page."); 504 ec = INVALID_ACCESS_ERR; 505 return; 506 } 507 508 // Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated 509 // attempt to discourage synchronous XHR use. responseType is one such piece of functionality. 510 // We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols 511 // such as file: and data: still make sense to allow. 512 if (url.protocolIsInHTTPFamily() && m_responseTypeCode != ResponseTypeDefault) { 513 logConsoleError(scriptExecutionContext(), "Synchronous HTTP(S) requests made from the window context cannot have XMLHttpRequest.responseType set."); 514 ec = INVALID_ACCESS_ERR; 515 return; 516 } 517 518#if ENABLE(XHR_TIMEOUT) 519 // Similarly, timeouts are disabled for synchronous requests as well. 520 if (m_timeoutMilliseconds > 0) { 521 logConsoleError(scriptExecutionContext(), "Synchronous XMLHttpRequests must not have a timeout value set."); 522 ec = INVALID_ACCESS_ERR; 523 return; 524 } 525#endif 526 } 527 528 m_method = uppercaseKnownHTTPMethod(method); 529 530 m_url = url; 531 532 m_async = async; 533 534 ASSERT(!m_loader); 535 536 // Check previous state to avoid dispatching readyState event 537 // when calling open several times in a row. 538 if (previousState != OPENED) 539 changeState(OPENED); 540 else 541 m_state = OPENED; 542} 543 544void XMLHttpRequest::open(const String& method, const URL& url, bool async, const String& user, ExceptionCode& ec) 545{ 546 URL urlWithCredentials(url); 547 urlWithCredentials.setUser(user); 548 549 open(method, urlWithCredentials, async, ec); 550} 551 552void XMLHttpRequest::open(const String& method, const URL& url, bool async, const String& user, const String& password, ExceptionCode& ec) 553{ 554 URL urlWithCredentials(url); 555 urlWithCredentials.setUser(user); 556 urlWithCredentials.setPass(password); 557 558 open(method, urlWithCredentials, async, ec); 559} 560 561bool XMLHttpRequest::initSend(ExceptionCode& ec) 562{ 563 if (!scriptExecutionContext()) 564 return false; 565 566 if (m_state != OPENED || m_loader) { 567 ec = INVALID_STATE_ERR; 568 return false; 569 } 570 571 m_error = false; 572 return true; 573} 574 575void XMLHttpRequest::send(ExceptionCode& ec) 576{ 577 send(String(), ec); 578} 579 580void XMLHttpRequest::send(Document* document, ExceptionCode& ec) 581{ 582 ASSERT(document); 583 584 if (!initSend(ec)) 585 return; 586 587 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) { 588 String contentType = getRequestHeader("Content-Type"); 589 if (contentType.isEmpty()) { 590#if ENABLE(DASHBOARD_SUPPORT) 591 if (usesDashboardBackwardCompatibilityMode()) 592 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded"); 593 else 594#endif 595 // FIXME: this should include the charset used for encoding. 596 setRequestHeaderInternal("Content-Type", "application/xml"); 597 } 598 599 // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm 600 // from the HTML5 specification to serialize the document. 601 String body = createMarkup(*document); 602 603 // FIXME: this should use value of document.inputEncoding to determine the encoding to use. 604 TextEncoding encoding = UTF8Encoding(); 605 m_requestEntityBody = FormData::create(encoding.encode(body, EntitiesForUnencodables)); 606 if (m_upload) 607 m_requestEntityBody->setAlwaysStream(true); 608 } 609 610 createRequest(ec); 611} 612 613void XMLHttpRequest::send(const String& body, ExceptionCode& ec) 614{ 615 if (!initSend(ec)) 616 return; 617 618 if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) { 619 String contentType = getRequestHeader("Content-Type"); 620 if (contentType.isEmpty()) { 621#if ENABLE(DASHBOARD_SUPPORT) 622 if (usesDashboardBackwardCompatibilityMode()) 623 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded"); 624 else 625#endif 626 setRequestHeaderInternal("Content-Type", "application/xml"); 627 } else { 628 replaceCharsetInMediaType(contentType, "UTF-8"); 629 m_requestHeaders.set(HTTPHeaderName::ContentType, contentType); 630 } 631 632 m_requestEntityBody = FormData::create(UTF8Encoding().encode(body, EntitiesForUnencodables)); 633 if (m_upload) 634 m_requestEntityBody->setAlwaysStream(true); 635 } 636 637 createRequest(ec); 638} 639 640void XMLHttpRequest::send(Blob* body, ExceptionCode& ec) 641{ 642 if (!initSend(ec)) 643 return; 644 645 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) { 646 const String& contentType = getRequestHeader("Content-Type"); 647 if (contentType.isEmpty()) { 648 const String& blobType = body->type(); 649 if (!blobType.isEmpty() && isValidContentType(blobType)) 650 setRequestHeaderInternal("Content-Type", blobType); 651 else { 652 // From FileAPI spec, whenever media type cannot be determined, empty string must be returned. 653 setRequestHeaderInternal("Content-Type", ""); 654 } 655 } 656 657 m_requestEntityBody = FormData::create(); 658 m_requestEntityBody->appendBlob(body->url()); 659 } 660 661 createRequest(ec); 662} 663 664void XMLHttpRequest::send(DOMFormData* body, ExceptionCode& ec) 665{ 666 if (!initSend(ec)) 667 return; 668 669 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) { 670 m_requestEntityBody = FormData::createMultiPart(*(static_cast<FormDataList*>(body)), body->encoding(), document()); 671 672 m_requestEntityBody->generateFiles(document()); 673 674 String contentType = getRequestHeader("Content-Type"); 675 if (contentType.isEmpty()) { 676 contentType = makeString("multipart/form-data; boundary=", m_requestEntityBody->boundary().data()); 677 setRequestHeaderInternal("Content-Type", contentType); 678 } 679 } 680 681 createRequest(ec); 682} 683 684void XMLHttpRequest::send(ArrayBuffer* body, ExceptionCode& ec) 685{ 686 String consoleMessage("ArrayBuffer is deprecated in XMLHttpRequest.send(). Use ArrayBufferView instead."); 687 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, consoleMessage); 688 689 sendBytesData(body->data(), body->byteLength(), ec); 690} 691 692void XMLHttpRequest::send(ArrayBufferView* body, ExceptionCode& ec) 693{ 694 sendBytesData(body->baseAddress(), body->byteLength(), ec); 695} 696 697void XMLHttpRequest::sendBytesData(const void* data, size_t length, ExceptionCode& ec) 698{ 699 if (!initSend(ec)) 700 return; 701 702 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) { 703 m_requestEntityBody = FormData::create(data, length); 704 if (m_upload) 705 m_requestEntityBody->setAlwaysStream(true); 706 } 707 708 createRequest(ec); 709} 710 711void XMLHttpRequest::sendForInspectorXHRReplay(PassRefPtr<FormData> formData, ExceptionCode& ec) 712{ 713 m_requestEntityBody = formData ? formData->deepCopy() : 0; 714 createRequest(ec); 715 m_exceptionCode = ec; 716} 717 718void XMLHttpRequest::createRequest(ExceptionCode& ec) 719{ 720 // Only GET request is supported for blob URL. 721 if (m_url.protocolIs("blob") && m_method != "GET") { 722 ec = XMLHttpRequestException::NETWORK_ERR; 723 return; 724 } 725 726 // The presence of upload event listeners forces us to use preflighting because POSTing to an URL that does not 727 // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all. 728 // Also, only async requests support upload progress events. 729 bool uploadEvents = false; 730 if (m_async) { 731 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadstartEvent); 732 if (m_requestEntityBody && m_upload) { 733 uploadEvents = m_upload->hasEventListeners(); 734 m_upload->dispatchProgressEvent(eventNames().loadstartEvent); 735 } 736 } 737 738 m_sameOriginRequest = securityOrigin()->canRequest(m_url); 739 740 // We also remember whether upload events should be allowed for this request in case the upload listeners are 741 // added after the request is started. 742 m_uploadEventsAllowed = m_sameOriginRequest || uploadEvents || !isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders); 743 744 ResourceRequest request(m_url); 745 request.setHTTPMethod(m_method); 746 747 InspectorInstrumentation::willLoadXHR(scriptExecutionContext(), this, m_method, m_url, m_async, m_requestEntityBody ? m_requestEntityBody->deepCopy() : 0, m_requestHeaders, m_includeCredentials); 748 749 if (m_requestEntityBody) { 750 ASSERT(m_method != "GET"); 751 ASSERT(m_method != "HEAD"); 752 request.setHTTPBody(m_requestEntityBody.release()); 753 } 754 755 if (!m_requestHeaders.isEmpty()) 756 request.setHTTPHeaderFields(m_requestHeaders); 757 758 ThreadableLoaderOptions options; 759 options.setSendLoadCallbacks(SendCallbacks); 760 options.setSniffContent(DoNotSniffContent); 761 options.preflightPolicy = uploadEvents ? ForcePreflight : ConsiderPreflight; 762 options.setAllowCredentials((m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials); 763 options.crossOriginRequestPolicy = UseAccessControl; 764 options.securityOrigin = securityOrigin(); 765#if ENABLE(RESOURCE_TIMING) 766 options.initiator = cachedResourceRequestInitiators().xmlhttprequest; 767#endif 768 769#if ENABLE(XHR_TIMEOUT) 770 if (m_timeoutMilliseconds) 771 request.setTimeoutInterval(m_timeoutMilliseconds / 1000.0); 772#endif 773 774 m_exceptionCode = 0; 775 m_error = false; 776 777 if (m_async) { 778 if (m_upload) 779 request.setReportUploadProgress(true); 780 781 // ThreadableLoader::create can return null here, for example if we're no longer attached to a page. 782 // This is true while running onunload handlers. 783 // FIXME: Maybe we need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>. 784 // FIXME: Maybe create() can return null for other reasons too? 785 m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options); 786 if (m_loader) { 787 // Neither this object nor the JavaScript wrapper should be deleted while 788 // a request is in progress because we need to keep the listeners alive, 789 // and they are referenced by the JavaScript wrapper. 790 setPendingActivity(this); 791 } 792 } else { 793 InspectorInstrumentation::willLoadXHRSynchronously(scriptExecutionContext()); 794 ThreadableLoader::loadResourceSynchronously(scriptExecutionContext(), request, *this, options); 795 InspectorInstrumentation::didLoadXHRSynchronously(scriptExecutionContext()); 796 } 797 798 if (!m_exceptionCode && m_error) 799 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR; 800 ec = m_exceptionCode; 801} 802 803void XMLHttpRequest::abort() 804{ 805 // internalAbort() calls dropProtection(), which may release the last reference. 806 Ref<XMLHttpRequest> protect(*this); 807 808 bool sendFlag = m_loader; 809 810 internalAbort(); 811 812 clearResponseBuffers(); 813 814 // Clear headers as required by the spec 815 m_requestHeaders.clear(); 816 817 if ((m_state <= OPENED && !sendFlag) || m_state == DONE) 818 m_state = UNSENT; 819 else { 820 ASSERT(!m_loader); 821 changeState(DONE); 822 m_state = UNSENT; 823 } 824 825 dispatchErrorEvents(eventNames().abortEvent); 826} 827 828void XMLHttpRequest::internalAbort() 829{ 830 bool hadLoader = m_loader; 831 832 m_error = true; 833 834 // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization. 835 m_receivedLength = 0; 836 837 if (hadLoader) { 838 m_loader->cancel(); 839 m_loader = 0; 840 } 841 842 m_decoder = 0; 843 844 InspectorInstrumentation::didFailXHRLoading(scriptExecutionContext(), this); 845 846 if (hadLoader) 847 dropProtection(); 848} 849 850void XMLHttpRequest::clearResponse() 851{ 852 m_response = ResourceResponse(); 853 clearResponseBuffers(); 854} 855 856void XMLHttpRequest::clearResponseBuffers() 857{ 858 m_responseBuilder.clear(); 859 m_responseEncoding = String(); 860 m_createdDocument = false; 861 m_responseDocument = 0; 862 m_responseBlob = 0; 863 m_binaryResponseBuilder.clear(); 864 m_responseArrayBuffer.clear(); 865 m_responseCacheIsValid = false; 866} 867 868void XMLHttpRequest::clearRequest() 869{ 870 m_requestHeaders.clear(); 871 m_requestEntityBody = 0; 872} 873 874void XMLHttpRequest::genericError() 875{ 876 clearResponse(); 877 clearRequest(); 878 m_error = true; 879 880 changeState(DONE); 881} 882 883void XMLHttpRequest::networkError() 884{ 885 genericError(); 886 dispatchErrorEvents(eventNames().errorEvent); 887 internalAbort(); 888} 889 890void XMLHttpRequest::abortError() 891{ 892 genericError(); 893 dispatchErrorEvents(eventNames().abortEvent); 894} 895 896void XMLHttpRequest::dropProtection() 897{ 898 // The XHR object itself holds on to the responseText, and 899 // thus has extra cost even independent of any 900 // responseText or responseXML objects it has handed 901 // out. But it is protected from GC while loading, so this 902 // can't be recouped until the load is done, so only 903 // report the extra cost at that point. 904 JSC::VM& vm = scriptExecutionContext()->vm(); 905 JSC::JSLockHolder lock(vm); 906 vm.heap.reportExtraMemoryCost(m_responseBuilder.length() * 2); 907 908 unsetPendingActivity(this); 909} 910 911void XMLHttpRequest::overrideMimeType(const String& override) 912{ 913 m_mimeTypeOverride = override; 914} 915 916void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec) 917{ 918 if (m_state != OPENED || m_loader) { 919#if ENABLE(DASHBOARD_SUPPORT) 920 if (usesDashboardBackwardCompatibilityMode()) 921 return; 922#endif 923 924 ec = INVALID_STATE_ERR; 925 return; 926 } 927 928 if (!isValidHTTPToken(name) || !isValidHTTPHeaderValue(value)) { 929 ec = SYNTAX_ERR; 930 return; 931 } 932 933 // A privileged script (e.g. a Dashboard widget) can set any headers. 934 if (!securityOrigin()->canLoadLocalResources() && !isAllowedHTTPHeader(name)) { 935 logConsoleError(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\""); 936 return; 937 } 938 939 setRequestHeaderInternal(name, value); 940} 941 942void XMLHttpRequest::setRequestHeaderInternal(const String& name, const String& value) 943{ 944 m_requestHeaders.add(name, value); 945} 946 947String XMLHttpRequest::getRequestHeader(const String& name) const 948{ 949 return m_requestHeaders.get(name); 950} 951 952String XMLHttpRequest::getAllResponseHeaders() const 953{ 954 if (m_state < HEADERS_RECEIVED || m_error) 955 return ""; 956 957 StringBuilder stringBuilder; 958 959 HTTPHeaderSet accessControlExposeHeaderSet; 960 parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet); 961 962 for (const auto& header : m_response.httpHeaderFields()) { 963 // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons: 964 // 1) If the client did have access to the fields, then it could read HTTP-only 965 // cookies; those cookies are supposed to be hidden from scripts. 966 // 2) There's no known harm in hiding Set-Cookie header fields entirely; we don't 967 // know any widely used technique that requires access to them. 968 // 3) Firefox has implemented this policy. 969 if (isSetCookieHeader(header.key) && !securityOrigin()->canLoadLocalResources()) 970 continue; 971 972 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(header.key) && !accessControlExposeHeaderSet.contains(header.key)) 973 continue; 974 975 stringBuilder.append(header.key); 976 stringBuilder.append(':'); 977 stringBuilder.append(' '); 978 stringBuilder.append(header.value); 979 stringBuilder.append('\r'); 980 stringBuilder.append('\n'); 981 } 982 983 return stringBuilder.toString(); 984} 985 986String XMLHttpRequest::getResponseHeader(const String& name) const 987{ 988 if (m_state < HEADERS_RECEIVED || m_error) 989 return String(); 990 991 // See comment in getAllResponseHeaders above. 992 if (isSetCookieHeader(name) && !securityOrigin()->canLoadLocalResources()) { 993 logConsoleError(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\""); 994 return String(); 995 } 996 997 HTTPHeaderSet accessControlExposeHeaderSet; 998 parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet); 999 1000 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name) && !accessControlExposeHeaderSet.contains(name)) { 1001 logConsoleError(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\""); 1002 return String(); 1003 } 1004 return m_response.httpHeaderField(name); 1005} 1006 1007String XMLHttpRequest::responseMIMEType() const 1008{ 1009 String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride); 1010 if (mimeType.isEmpty()) { 1011 if (m_response.isHTTP()) 1012 mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType)); 1013 else 1014 mimeType = m_response.mimeType(); 1015 } 1016 if (mimeType.isEmpty()) 1017 mimeType = "text/xml"; 1018 1019 return mimeType; 1020} 1021 1022bool XMLHttpRequest::responseIsXML() const 1023{ 1024 // FIXME: Remove the lower() call when DOMImplementation.isXMLMIMEType() is modified 1025 // to do case insensitive MIME type matching. 1026 return DOMImplementation::isXMLMIMEType(responseMIMEType().lower()); 1027} 1028 1029int XMLHttpRequest::status() const 1030{ 1031 if (m_state == UNSENT || m_state == OPENED || m_error) 1032 return 0; 1033 1034 if (m_response.httpStatusCode()) 1035 return m_response.httpStatusCode(); 1036 1037 return 0; 1038} 1039 1040String XMLHttpRequest::statusText() const 1041{ 1042 if (m_state == UNSENT || m_state == OPENED || m_error) 1043 return String(); 1044 1045 if (!m_response.httpStatusText().isNull()) 1046 return m_response.httpStatusText(); 1047 1048 return String(); 1049} 1050 1051void XMLHttpRequest::didFail(const ResourceError& error) 1052{ 1053 1054 // If we are already in an error state, for instance we called abort(), bail out early. 1055 if (m_error) 1056 return; 1057 1058 if (error.isCancellation()) { 1059 m_exceptionCode = XMLHttpRequestException::ABORT_ERR; 1060 abortError(); 1061 return; 1062 } 1063 1064#if ENABLE(XHR_TIMEOUT) 1065 if (error.isTimeout()) { 1066 didTimeout(); 1067 return; 1068 } 1069#endif 1070 1071 // Network failures are already reported to Web Inspector by ResourceLoader. 1072 if (error.domain() == errorDomainWebKitInternal) 1073 logConsoleError(scriptExecutionContext(), "XMLHttpRequest cannot load " + error.failingURL() + ". " + error.localizedDescription()); 1074 1075 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR; 1076 networkError(); 1077} 1078 1079void XMLHttpRequest::didFailRedirectCheck() 1080{ 1081 networkError(); 1082} 1083 1084void XMLHttpRequest::didFinishLoading(unsigned long identifier, double) 1085{ 1086 if (m_error) 1087 return; 1088 1089 if (m_state < HEADERS_RECEIVED) 1090 changeState(HEADERS_RECEIVED); 1091 1092 if (m_decoder) 1093 m_responseBuilder.append(m_decoder->flush()); 1094 1095 m_responseBuilder.shrinkToFit(); 1096 1097 InspectorInstrumentation::didFinishXHRLoading(scriptExecutionContext(), this, identifier, m_responseBuilder.toStringPreserveCapacity(), m_url, m_lastSendURL, m_lastSendLineNumber, m_lastSendColumnNumber); 1098 1099 bool hadLoader = m_loader; 1100 m_loader = 0; 1101 1102 changeState(DONE); 1103 m_responseEncoding = String(); 1104 m_decoder = 0; 1105 1106 if (hadLoader) 1107 dropProtection(); 1108} 1109 1110void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) 1111{ 1112 if (!m_upload) 1113 return; 1114 1115 if (m_uploadEventsAllowed) 1116 m_upload->dispatchThrottledProgressEvent(true, bytesSent, totalBytesToBeSent); 1117 if (bytesSent == totalBytesToBeSent && !m_uploadComplete) { 1118 m_uploadComplete = true; 1119 if (m_uploadEventsAllowed) { 1120 m_upload->dispatchProgressEvent(eventNames().loadEvent); 1121 m_upload->dispatchProgressEvent(eventNames().loadendEvent); 1122 } 1123 } 1124} 1125 1126void XMLHttpRequest::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) 1127{ 1128 InspectorInstrumentation::didReceiveXHRResponse(scriptExecutionContext(), identifier); 1129 1130 m_response = response; 1131 if (!m_mimeTypeOverride.isEmpty()) 1132 m_response.setHTTPHeaderField(HTTPHeaderName::ContentType, m_mimeTypeOverride); 1133} 1134 1135void XMLHttpRequest::didReceiveData(const char* data, int len) 1136{ 1137 if (m_error) 1138 return; 1139 1140 if (m_state < HEADERS_RECEIVED) 1141 changeState(HEADERS_RECEIVED); 1142 1143 // FIXME: Should we update "Content-Type" header field with m_mimeTypeOverride value in case it has changed since didReceiveResponse? 1144 if (!m_mimeTypeOverride.isEmpty()) 1145 m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride); 1146 if (m_responseEncoding.isEmpty()) 1147 m_responseEncoding = m_response.textEncodingName(); 1148 1149 bool useDecoder = shouldDecodeResponse(); 1150 1151 if (useDecoder && !m_decoder) { 1152 if (!m_responseEncoding.isEmpty()) 1153 m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding); 1154 // allow TextResourceDecoder to look inside the m_response if it's XML or HTML 1155 else if (responseIsXML()) { 1156 m_decoder = TextResourceDecoder::create("application/xml"); 1157 // Don't stop on encoding errors, unlike it is done for other kinds of XML resources. This matches the behavior of previous WebKit versions, Firefox and Opera. 1158 m_decoder->useLenientXMLDecoding(); 1159 } else if (equalIgnoringCase(responseMIMEType(), "text/html")) 1160 m_decoder = TextResourceDecoder::create("text/html", "UTF-8"); 1161 else 1162 m_decoder = TextResourceDecoder::create("text/plain", "UTF-8"); 1163 } 1164 1165 if (!len) 1166 return; 1167 1168 if (len == -1) 1169 len = strlen(data); 1170 1171 if (useDecoder) 1172 m_responseBuilder.append(m_decoder->decode(data, len)); 1173 else if (m_responseTypeCode == ResponseTypeArrayBuffer || m_responseTypeCode == ResponseTypeBlob) { 1174 // Buffer binary data. 1175 if (!m_binaryResponseBuilder) 1176 m_binaryResponseBuilder = SharedBuffer::create(); 1177 m_binaryResponseBuilder->append(data, len); 1178 } 1179 1180 if (!m_error) { 1181 m_receivedLength += len; 1182 1183 if (m_async) { 1184 long long expectedLength = m_response.expectedContentLength(); 1185 bool lengthComputable = expectedLength > 0 && m_receivedLength <= expectedLength; 1186 unsigned long long total = lengthComputable ? expectedLength : 0; 1187 m_progressEventThrottle.dispatchThrottledProgressEvent(lengthComputable, m_receivedLength, total); 1188 } 1189 1190 if (m_state != LOADING) 1191 changeState(LOADING); 1192 else 1193 // Firefox calls readyStateChanged every time it receives data, 4449442 1194 callReadyStateChangeListener(); 1195 } 1196} 1197 1198void XMLHttpRequest::dispatchErrorEvents(const AtomicString& type) 1199{ 1200 if (!m_uploadComplete) { 1201 m_uploadComplete = true; 1202 if (m_upload && m_uploadEventsAllowed) { 1203 m_upload->dispatchProgressEvent(eventNames().progressEvent); 1204 m_upload->dispatchProgressEvent(type); 1205 m_upload->dispatchProgressEvent(eventNames().loadendEvent); 1206 } 1207 } 1208 m_progressEventThrottle.dispatchProgressEvent(eventNames().progressEvent); 1209 m_progressEventThrottle.dispatchProgressEvent(type); 1210 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadendEvent); 1211} 1212 1213#if ENABLE(XHR_TIMEOUT) 1214void XMLHttpRequest::didTimeout() 1215{ 1216 // internalAbort() calls dropProtection(), which may release the last reference. 1217 Ref<XMLHttpRequest> protect(*this); 1218 internalAbort(); 1219 1220 clearResponse(); 1221 clearRequest(); 1222 1223 m_error = true; 1224 m_exceptionCode = XMLHttpRequestException::TIMEOUT_ERR; 1225 1226 if (!m_async) { 1227 m_state = DONE; 1228 m_exceptionCode = TIMEOUT_ERR; 1229 return; 1230 } 1231 1232 changeState(DONE); 1233 1234 dispatchErrorEvents(eventNames().timeoutEvent); 1235} 1236#endif 1237 1238bool XMLHttpRequest::canSuspend() const 1239{ 1240 return !m_loader; 1241} 1242 1243void XMLHttpRequest::suspend(ReasonForSuspension) 1244{ 1245 m_progressEventThrottle.suspend(); 1246} 1247 1248void XMLHttpRequest::resume() 1249{ 1250 m_progressEventThrottle.resume(); 1251} 1252 1253void XMLHttpRequest::stop() 1254{ 1255 internalAbort(); 1256} 1257 1258void XMLHttpRequest::contextDestroyed() 1259{ 1260 ASSERT(!m_loader); 1261 ActiveDOMObject::contextDestroyed(); 1262} 1263 1264} // namespace WebCore 1265