blob: d77154d32ca8238686c0f9932ad7f055b5d2bf68 [file] [log] [blame]
/*
* Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org>
* Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org>
* Copyright (C) 2008, 2011 Google Inc. All rights reserved.
* Copyright (C) 2012 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "core/xmlhttprequest/XMLHttpRequest.h"
#include <memory>
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/array_buffer_or_array_buffer_view_or_blob_or_document_or_string_or_form_data_or_url_search_params.h"
#include "bindings/core/v8/array_buffer_or_array_buffer_view_or_blob_or_usv_string.h"
#include "core/dom/DOMException.h"
#include "core/dom/DOMImplementation.h"
#include "core/dom/DocumentInit.h"
#include "core/dom/DocumentParser.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/dom/XMLDocument.h"
#include "core/dom/events/Event.h"
#include "core/editing/serializers/Serialization.h"
#include "core/events/ProgressEvent.h"
#include "core/fileapi/Blob.h"
#include "core/fileapi/File.h"
#include "core/fileapi/FileReaderLoader.h"
#include "core/fileapi/FileReaderLoaderClient.h"
#include "core/frame/Deprecation.h"
#include "core/frame/Frame.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLDocument.h"
#include "core/html/forms/FormData.h"
#include "core/html/parser/TextResourceDecoder.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "core/loader/ThreadableLoader.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/probe/CoreProbes.h"
#include "core/typed_arrays/DOMArrayBuffer.h"
#include "core/typed_arrays/DOMArrayBufferView.h"
#include "core/typed_arrays/DOMTypedArray.h"
#include "core/url/URLSearchParams.h"
#include "core/xmlhttprequest/XMLHttpRequestUpload.h"
#include "platform/FileMetadata.h"
#include "platform/Histogram.h"
#include "platform/SharedBuffer.h"
#include "platform/bindings/DOMWrapperWorld.h"
#include "platform/bindings/ScriptState.h"
#include "platform/blob/BlobData.h"
#include "platform/exported/WrappedResourceResponse.h"
#include "platform/feature_policy/FeaturePolicy.h"
#include "platform/loader/fetch/FetchUtils.h"
#include "platform/loader/fetch/ResourceError.h"
#include "platform/loader/fetch/ResourceLoaderOptions.h"
#include "platform/loader/fetch/ResourceRequest.h"
#include "platform/loader/fetch/TextResourceDecoderOptions.h"
#include "platform/loader/fetch/fetch_initiator_type_names.h"
#include "platform/network/HTTPParsers.h"
#include "platform/network/NetworkLog.h"
#include "platform/network/ParsedContentType.h"
#include "platform/network/http_names.h"
#include "platform/runtime_enabled_features.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/weborigin/SecurityPolicy.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/AutoReset.h"
#include "platform/wtf/StdLibExtras.h"
#include "platform/wtf/text/CString.h"
#include "public/platform/WebCORS.h"
#include "public/platform/WebURLRequest.h"
#include "third_party/WebKit/common/feature_policy/feature_policy_feature.h"
namespace blink {
namespace {
// This class protects the wrapper of the associated XMLHttpRequest object
// via hasPendingActivity method which returns true if
// m_eventDispatchRecursionLevel is positive.
class ScopedEventDispatchProtect final {
public:
explicit ScopedEventDispatchProtect(int* level) : level_(level) { ++*level_; }
~ScopedEventDispatchProtect() {
DCHECK_GT(*level_, 0);
--*level_;
}
private:
int* const level_;
};
// These methods were placed in HTTPParsers.h. Since these methods don't
// perform ABNF validation but loosely look for the part that is likely to be
// indicating the charset parameter, new code should use
// HttpUtil::ParseContentType() than these. To discourage use of these methods,
// moved from HTTPParser.h to the only user XMLHttpRequest.cpp.
//
// TODO(tyoshino): Switch XHR to use HttpUtil. See crbug.com/743311.
void FindCharsetInMediaType(const String& media_type,
unsigned& charset_pos,
unsigned& charset_len) {
charset_len = 0;
size_t pos = charset_pos;
unsigned length = media_type.length();
while (pos < length) {
pos = media_type.FindIgnoringASCIICase("charset", pos);
if (pos == kNotFound)
return;
// Give up if we find "charset" at the head.
if (!pos)
return;
// Now check that "charset" is not a substring of some longer word.
if (media_type[pos - 1] > ' ' && media_type[pos - 1] != ';') {
pos += 7;
continue;
}
pos += 7;
while (pos < length && media_type[pos] <= ' ')
++pos;
// Treat this as a charset parameter.
if (media_type[pos++] == '=')
break;
}
while (pos < length && (media_type[pos] <= ' ' || media_type[pos] == '"' ||
media_type[pos] == '\''))
++pos;
charset_pos = pos;
// we don't handle spaces within quoted parameter values, because charset
// names cannot have any
while (pos < length && media_type[pos] > ' ' && media_type[pos] != '"' &&
media_type[pos] != '\'' && media_type[pos] != ';')
++pos;
charset_len = pos - charset_pos;
}
String ExtractCharsetFromMediaType(const String& media_type) {
unsigned pos = 0;
unsigned len = 0;
FindCharsetInMediaType(media_type, pos, len);
return media_type.Substring(pos, len);
}
void ReplaceCharsetInMediaType(String& media_type,
const String& charset_value) {
unsigned pos = 0;
while (true) {
unsigned len = 0;
FindCharsetInMediaType(media_type, pos, len);
if (!len)
return;
media_type.replace(pos, len, charset_value);
pos += charset_value.length();
}
}
void LogConsoleError(ExecutionContext* context, const String& message) {
if (!context)
return;
// FIXME: It's not good to report the bad usage without indicating what source
// line it came from. We should pass additional parameters so we can tell the
// console where the mistake occurred.
ConsoleMessage* console_message =
ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message);
context->AddConsoleMessage(console_message);
}
bool ValidateOpenArguments(const AtomicString& method,
const KURL& url,
ExceptionState& exception_state) {
if (!IsValidHTTPToken(method)) {
exception_state.ThrowDOMException(
kSyntaxError, "'" + method + "' is not a valid HTTP method.");
return false;
}
if (FetchUtils::IsForbiddenMethod(method)) {
exception_state.ThrowSecurityError("'" + method +
"' HTTP method is unsupported.");
return false;
}
if (!url.IsValid()) {
exception_state.ThrowDOMException(kSyntaxError, "Invalid URL");
return false;
}
return true;
}
} // namespace
class XMLHttpRequest::BlobLoader final
: public GarbageCollectedFinalized<XMLHttpRequest::BlobLoader>,
public FileReaderLoaderClient {
public:
static BlobLoader* Create(XMLHttpRequest* xhr,
scoped_refptr<BlobDataHandle> handle) {
return new BlobLoader(xhr, std::move(handle));
}
// FileReaderLoaderClient functions.
void DidStartLoading() override {}
void DidReceiveDataForClient(const char* data, unsigned length) override {
DCHECK_LE(length, static_cast<unsigned>(INT_MAX));
xhr_->DidReceiveData(data, length);
}
void DidFinishLoading() override { xhr_->DidFinishLoadingFromBlob(); }
void DidFail(FileError::ErrorCode error) override {
xhr_->DidFailLoadingFromBlob();
}
void Cancel() { loader_->Cancel(); }
void Trace(blink::Visitor* visitor) { visitor->Trace(xhr_); }
private:
BlobLoader(XMLHttpRequest* xhr, scoped_refptr<BlobDataHandle> handle)
: xhr_(xhr),
loader_(
FileReaderLoader::Create(FileReaderLoader::kReadByClient, this)) {
loader_->Start(xhr_->GetExecutionContext(), std::move(handle));
}
Member<XMLHttpRequest> xhr_;
std::unique_ptr<FileReaderLoader> loader_;
};
XMLHttpRequest* XMLHttpRequest::Create(ScriptState* script_state) {
ExecutionContext* context = ExecutionContext::From(script_state);
DOMWrapperWorld& world = script_state->World();
v8::Isolate* isolate = script_state->GetIsolate();
XMLHttpRequest* xml_http_request =
world.IsIsolatedWorld()
? new XMLHttpRequest(context, isolate, true,
world.IsolatedWorldSecurityOrigin())
: new XMLHttpRequest(context, isolate, false, nullptr);
xml_http_request->PauseIfNeeded();
return xml_http_request;
}
XMLHttpRequest* XMLHttpRequest::Create(ExecutionContext* context) {
v8::Isolate* isolate = ToIsolate(context);
CHECK(isolate);
XMLHttpRequest* xml_http_request =
new XMLHttpRequest(context, isolate, false, nullptr);
xml_http_request->PauseIfNeeded();
return xml_http_request;
}
XMLHttpRequest::XMLHttpRequest(
ExecutionContext* context,
v8::Isolate* isolate,
bool is_isolated_world,
scoped_refptr<SecurityOrigin> isolated_world_security_origin)
: PausableObject(context),
timeout_milliseconds_(0),
state_(kUnsent),
length_downloaded_to_file_(0),
received_length_(0),
exception_code_(0),
progress_event_throttle_(
XMLHttpRequestProgressEventThrottle::Create(this)),
response_type_code_(kResponseTypeDefault),
isolate_(isolate),
is_isolated_world_(is_isolated_world),
isolated_world_security_origin_(
std::move(isolated_world_security_origin)),
event_dispatch_recursion_level_(0),
async_(true),
with_credentials_(false),
parsed_response_(false),
error_(false),
upload_events_allowed_(true),
upload_complete_(false),
same_origin_request_(true),
downloading_to_file_(false),
response_text_overflow_(false),
send_flag_(false),
response_array_buffer_failure_(false) {}
XMLHttpRequest::~XMLHttpRequest() {
binary_response_builder_ = nullptr;
length_downloaded_to_file_ = 0;
ReportMemoryUsageToV8();
}
Document* XMLHttpRequest::GetDocument() const {
DCHECK(GetExecutionContext()->IsDocument());
return ToDocument(GetExecutionContext());
}
const SecurityOrigin* XMLHttpRequest::GetSecurityOrigin() const {
return isolated_world_security_origin_
? isolated_world_security_origin_.get()
: GetExecutionContext()->GetSecurityOrigin();
}
SecurityOrigin* XMLHttpRequest::GetMutableSecurityOrigin() {
return isolated_world_security_origin_
? isolated_world_security_origin_.get()
: GetExecutionContext()->GetMutableSecurityOrigin();
}
XMLHttpRequest::State XMLHttpRequest::readyState() const {
return state_;
}
ScriptString XMLHttpRequest::responseText(ExceptionState& exception_state) {
if (response_type_code_ != kResponseTypeDefault &&
response_type_code_ != kResponseTypeText) {
exception_state.ThrowDOMException(kInvalidStateError,
"The value is only accessible if the "
"object's 'responseType' is '' or 'text' "
"(was '" +
responseType() + "').");
return ScriptString();
}
if (error_ || (state_ != kLoading && state_ != kDone))
return ScriptString();
return response_text_;
}
ScriptString XMLHttpRequest::ResponseJSONSource() {
DCHECK_EQ(response_type_code_, kResponseTypeJSON);
if (error_ || state_ != kDone)
return ScriptString();
return response_text_;
}
void XMLHttpRequest::InitResponseDocument() {
// The W3C spec requires the final MIME type to be some valid XML type, or
// text/html. If it is text/html, then the responseType of "document" must
// have been supplied explicitly.
bool is_html = ResponseIsHTML();
if ((response_.IsHTTP() && !ResponseIsXML() && !is_html) ||
(is_html && response_type_code_ == kResponseTypeDefault) ||
!GetExecutionContext() || GetExecutionContext()->IsWorkerGlobalScope()) {
response_document_ = nullptr;
return;
}
DocumentInit init = DocumentInit::Create()
.WithContextDocument(GetDocument()->ContextDocument())
.WithURL(response_.Url());
if (is_html)
response_document_ = HTMLDocument::Create(init);
else
response_document_ = XMLDocument::Create(init);
// FIXME: Set Last-Modified.
response_document_->SetSecurityOrigin(GetMutableSecurityOrigin());
response_document_->SetContextFeatures(GetDocument()->GetContextFeatures());
response_document_->SetMimeType(FinalResponseMIMETypeWithFallback());
}
Document* XMLHttpRequest::responseXML(ExceptionState& exception_state) {
if (response_type_code_ != kResponseTypeDefault &&
response_type_code_ != kResponseTypeDocument) {
exception_state.ThrowDOMException(kInvalidStateError,
"The value is only accessible if the "
"object's 'responseType' is '' or "
"'document' (was '" +
responseType() + "').");
return nullptr;
}
if (error_ || state_ != kDone)
return nullptr;
if (!parsed_response_) {
InitResponseDocument();
if (!response_document_)
return nullptr;
response_document_->SetContent(response_text_.FlattenToString());
if (!response_document_->WellFormed())
response_document_ = nullptr;
parsed_response_ = true;
}
return response_document_;
}
Blob* XMLHttpRequest::ResponseBlob() {
DCHECK_EQ(response_type_code_, kResponseTypeBlob);
// We always return null before kDone.
if (error_ || state_ != kDone)
return nullptr;
if (!response_blob_) {
if (downloading_to_file_) {
DCHECK(!binary_response_builder_);
// When responseType is set to "blob", we redirect the downloaded
// data to a file-handle directly in the browser process. We get
// the file-path from the ResourceResponse directly instead of
// copying the bytes between the browser and the renderer.
response_blob_ = Blob::Create(CreateBlobDataHandleFromResponse());
} else {
std::unique_ptr<BlobData> blob_data = BlobData::Create();
size_t size = 0;
if (binary_response_builder_ && binary_response_builder_->size()) {
binary_response_builder_->ForEachSegment(
[&blob_data](const char* segment, size_t segment_size,
size_t segment_offset) -> bool {
blob_data->AppendBytes(segment, segment_size);
return true;
});
size = binary_response_builder_->size();
blob_data->SetContentType(
FinalResponseMIMETypeWithFallback().LowerASCII());
binary_response_builder_ = nullptr;
ReportMemoryUsageToV8();
}
response_blob_ =
Blob::Create(BlobDataHandle::Create(std::move(blob_data), size));
}
}
return response_blob_;
}
DOMArrayBuffer* XMLHttpRequest::ResponseArrayBuffer() {
DCHECK_EQ(response_type_code_, kResponseTypeArrayBuffer);
if (error_ || state_ != kDone)
return nullptr;
if (!response_array_buffer_ && !response_array_buffer_failure_) {
if (binary_response_builder_ && binary_response_builder_->size()) {
DOMArrayBuffer* buffer = DOMArrayBuffer::CreateUninitializedOrNull(
binary_response_builder_->size(), 1);
if (buffer) {
bool result = binary_response_builder_->GetBytes(
buffer->Data(), static_cast<size_t>(buffer->ByteLength()));
DCHECK(result);
response_array_buffer_ = buffer;
}
// https://xhr.spec.whatwg.org/#arraybuffer-response allows clearing
// of the 'received bytes' payload when the response buffer allocation
// fails.
binary_response_builder_ = nullptr;
ReportMemoryUsageToV8();
// Mark allocation as failed; subsequent calls to the accessor must
// continue to report |null|.
//
response_array_buffer_failure_ = !buffer;
} else {
response_array_buffer_ = DOMArrayBuffer::Create(nullptr, 0);
}
}
return response_array_buffer_;
}
void XMLHttpRequest::setTimeout(unsigned timeout,
ExceptionState& exception_state) {
// FIXME: Need to trigger or update the timeout Timer here, if needed.
// http://webkit.org/b/98156
// 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."
if (GetExecutionContext() && GetExecutionContext()->IsDocument() && !async_) {
exception_state.ThrowDOMException(kInvalidAccessError,
"Timeouts cannot be set for synchronous "
"requests made from a document.");
return;
}
timeout_milliseconds_ = timeout;
// From http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute:
// Note: 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.
//
// The timeout may be overridden after send.
if (loader_)
loader_->OverrideTimeout(timeout);
}
void XMLHttpRequest::setResponseType(const String& response_type,
ExceptionState& exception_state) {
if (state_ >= kLoading) {
exception_state.ThrowDOMException(kInvalidStateError,
"The response type cannot be set if the "
"object's state is LOADING or DONE.");
return;
}
// Newer functionality is not available to synchronous requests in window
// contexts, as a spec-mandated attempt to discourage synchronous XHR use.
// responseType is one such piece of functionality.
if (GetExecutionContext() && GetExecutionContext()->IsDocument() && !async_) {
exception_state.ThrowDOMException(kInvalidAccessError,
"The response type cannot be changed for "
"synchronous requests made from a "
"document.");
return;
}
if (response_type == "") {
response_type_code_ = kResponseTypeDefault;
} else if (response_type == "text") {
response_type_code_ = kResponseTypeText;
} else if (response_type == "json") {
response_type_code_ = kResponseTypeJSON;
} else if (response_type == "document") {
response_type_code_ = kResponseTypeDocument;
} else if (response_type == "blob") {
response_type_code_ = kResponseTypeBlob;
} else if (response_type == "arraybuffer") {
response_type_code_ = kResponseTypeArrayBuffer;
} else {
NOTREACHED();
}
}
String XMLHttpRequest::responseType() {
switch (response_type_code_) {
case kResponseTypeDefault:
return "";
case kResponseTypeText:
return "text";
case kResponseTypeJSON:
return "json";
case kResponseTypeDocument:
return "document";
case kResponseTypeBlob:
return "blob";
case kResponseTypeArrayBuffer:
return "arraybuffer";
}
return "";
}
String XMLHttpRequest::responseURL() {
KURL response_url(response_.Url());
if (!response_url.IsNull())
response_url.RemoveFragmentIdentifier();
return response_url.GetString();
}
XMLHttpRequestUpload* XMLHttpRequest::upload() {
if (!upload_)
upload_ = XMLHttpRequestUpload::Create(this);
return upload_;
}
void XMLHttpRequest::TrackProgress(long long length) {
received_length_ += length;
ChangeState(kLoading);
if (async_) {
// readyStateChange event is fired as well.
DispatchProgressEventFromSnapshot(EventTypeNames::progress);
}
}
void XMLHttpRequest::ChangeState(State new_state) {
if (state_ != new_state) {
state_ = new_state;
DispatchReadyStateChangeEvent();
}
}
void XMLHttpRequest::DispatchReadyStateChangeEvent() {
if (!GetExecutionContext())
return;
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
if (async_ || (state_ <= kOpened || state_ == kDone)) {
TRACE_EVENT1(
"devtools.timeline", "XHRReadyStateChange", "data",
InspectorXhrReadyStateChangeEvent::Data(GetExecutionContext(), this));
XMLHttpRequestProgressEventThrottle::DeferredEventAction action =
XMLHttpRequestProgressEventThrottle::kIgnore;
if (state_ == kDone) {
if (error_)
action = XMLHttpRequestProgressEventThrottle::kClear;
else
action = XMLHttpRequestProgressEventThrottle::kFlush;
}
progress_event_throttle_->DispatchReadyStateChangeEvent(
Event::Create(EventTypeNames::readystatechange), action);
}
if (state_ == kDone && !error_) {
TRACE_EVENT1("devtools.timeline", "XHRLoad", "data",
InspectorXhrLoadEvent::Data(GetExecutionContext(), this));
DispatchProgressEventFromSnapshot(EventTypeNames::load);
DispatchProgressEventFromSnapshot(EventTypeNames::loadend);
}
}
void XMLHttpRequest::setWithCredentials(bool value,
ExceptionState& exception_state) {
if (state_ > kOpened || send_flag_) {
exception_state.ThrowDOMException(
kInvalidStateError,
"The value may only be set if the object's state is UNSENT or OPENED.");
return;
}
with_credentials_ = value;
}
void XMLHttpRequest::open(const AtomicString& method,
const String& url_string,
ExceptionState& exception_state) {
if (!GetExecutionContext())
return;
KURL url(GetExecutionContext()->CompleteURL(url_string));
if (!ValidateOpenArguments(method, url, exception_state))
return;
open(method, url, true, exception_state);
}
void XMLHttpRequest::open(const AtomicString& method,
const String& url_string,
bool async,
const String& username,
const String& password,
ExceptionState& exception_state) {
if (!GetExecutionContext())
return;
KURL url(GetExecutionContext()->CompleteURL(url_string));
if (!ValidateOpenArguments(method, url, exception_state))
return;
if (!username.IsNull())
url.SetUser(username);
if (!password.IsNull())
url.SetPass(password);
open(method, url, async, exception_state);
}
void XMLHttpRequest::open(const AtomicString& method,
const KURL& url,
bool async,
ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " open(" << method << ", " << url.ElidedString()
<< ", " << async << ")";
DCHECK(ValidateOpenArguments(method, url, exception_state));
if (!InternalAbort())
return;
State previous_state = state_;
state_ = kUnsent;
error_ = false;
upload_complete_ = false;
if (!async && GetExecutionContext()->IsDocument()) {
if (GetDocument()->GetSettings() &&
!GetDocument()->GetSettings()->GetSyncXHRInDocumentsEnabled()) {
exception_state.ThrowDOMException(
kInvalidAccessError,
"Synchronous requests are disabled for this page.");
return;
}
// Newer functionality is not available to synchronous requests in window
// contexts, as a spec-mandated attempt to discourage synchronous XHR use.
// responseType is one such piece of functionality.
if (response_type_code_ != kResponseTypeDefault) {
exception_state.ThrowDOMException(
kInvalidAccessError,
"Synchronous requests from a document must not set a response type.");
return;
}
// Similarly, timeouts are disabled for synchronous requests as well.
if (timeout_milliseconds_ > 0) {
exception_state.ThrowDOMException(
kInvalidAccessError, "Synchronous requests must not set a timeout.");
return;
}
// Here we just warn that firing sync XHR's may affect responsiveness.
// Eventually sync xhr will be deprecated and an "InvalidAccessError"
// exception thrown.
// Refer : https://xhr.spec.whatwg.org/#sync-warning
// Use count for XHR synchronous requests on main thread only.
if (!GetDocument()->ProcessingBeforeUnload()) {
Deprecation::CountDeprecation(
GetExecutionContext(),
WebFeature::kXMLHttpRequestSynchronousInNonWorkerOutsideBeforeUnload);
}
}
method_ = FetchUtils::NormalizeMethod(method);
url_ = url;
async_ = async;
DCHECK(!loader_);
send_flag_ = false;
// Check previous state to avoid dispatching readyState event
// when calling open several times in a row.
if (previous_state != kOpened)
ChangeState(kOpened);
else
state_ = kOpened;
}
bool XMLHttpRequest::InitSend(ExceptionState& exception_state) {
// We need to check ContextDestroyed because it is possible to create a
// XMLHttpRequest with already detached document.
// TODO(yhirano): Fix this.
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
HandleNetworkError();
ThrowForLoadFailureIfNeeded(exception_state,
"Document is already detached.");
return false;
}
if (state_ != kOpened || send_flag_) {
exception_state.ThrowDOMException(kInvalidStateError,
"The object's state must be OPENED.");
return false;
}
if (!async_) {
if (GetExecutionContext()->IsDocument() &&
IsSupportedInFeaturePolicy(FeaturePolicyFeature::kSyncXHR) &&
!GetDocument()->GetFrame()->IsFeatureEnabled(
FeaturePolicyFeature::kSyncXHR)) {
LogConsoleError(GetExecutionContext(),
"Synchronous requests are disabled by Feature Policy.");
HandleNetworkError();
ThrowForLoadFailureIfNeeded(exception_state, String());
return false;
}
v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (isolate && v8::MicrotasksScope::IsRunningMicrotasks(isolate)) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kDuring_Microtask_SyncXHR);
}
}
error_ = false;
return true;
}
void XMLHttpRequest::send(
const ArrayBufferOrArrayBufferViewOrBlobOrDocumentOrStringOrFormDataOrURLSearchParams&
body,
ExceptionState& exception_state) {
probe::willSendXMLHttpOrFetchNetworkRequest(GetExecutionContext(), Url());
if (body.IsNull()) {
send(String(), exception_state);
return;
}
if (body.IsArrayBuffer()) {
send(body.GetAsArrayBuffer(), exception_state);
return;
}
if (body.IsArrayBufferView()) {
send(body.GetAsArrayBufferView().View(), exception_state);
return;
}
if (body.IsBlob()) {
send(body.GetAsBlob(), exception_state);
return;
}
if (body.IsDocument()) {
send(body.GetAsDocument(), exception_state);
return;
}
if (body.IsFormData()) {
send(body.GetAsFormData(), exception_state);
return;
}
if (body.IsURLSearchParams()) {
send(body.GetAsURLSearchParams(), exception_state);
return;
}
DCHECK(body.IsString());
send(body.GetAsString(), exception_state);
}
bool XMLHttpRequest::AreMethodAndURLValidForSend() {
return method_ != HTTPNames::GET && method_ != HTTPNames::HEAD &&
url_.ProtocolIsInHTTPFamily();
}
void XMLHttpRequest::send(Document* document, ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() Document "
<< static_cast<void*>(document);
DCHECK(document);
if (!InitSend(exception_state))
return;
scoped_refptr<EncodedFormData> http_body;
if (AreMethodAndURLValidForSend()) {
// FIXME: Per https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send the
// Content-Type header and whether to serialize as HTML or XML should
// depend on |document->isHTMLDocument()|.
if (!HasContentTypeRequestHeader())
SetRequestHeaderInternal(HTTPNames::Content_Type,
"application/xml;charset=UTF-8");
String body = CreateMarkup(document);
http_body = EncodedFormData::Create(
UTF8Encoding().Encode(body, WTF::kEntitiesForUnencodables));
}
CreateRequest(std::move(http_body), exception_state);
}
void XMLHttpRequest::send(const String& body, ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() String " << body;
if (!InitSend(exception_state))
return;
scoped_refptr<EncodedFormData> http_body;
if (!body.IsNull() && AreMethodAndURLValidForSend()) {
http_body = EncodedFormData::Create(
UTF8Encoding().Encode(body, WTF::kEntitiesForUnencodables));
UpdateContentTypeAndCharset("text/plain;charset=UTF-8", "UTF-8");
}
CreateRequest(std::move(http_body), exception_state);
}
void XMLHttpRequest::send(Blob* body, ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() Blob " << body->Uuid();
if (!InitSend(exception_state))
return;
scoped_refptr<EncodedFormData> http_body;
if (AreMethodAndURLValidForSend()) {
if (!HasContentTypeRequestHeader()) {
const String& blob_type = FetchUtils::NormalizeHeaderValue(body->type());
if (!blob_type.IsEmpty() && ParsedContentType(blob_type).IsValid()) {
SetRequestHeaderInternal(HTTPNames::Content_Type,
AtomicString(blob_type));
}
}
// FIXME: add support for uploading bundles.
http_body = EncodedFormData::Create();
if (body->HasBackingFile()) {
File* file = ToFile(body);
if (!file->GetPath().IsEmpty())
http_body->AppendFile(file->GetPath());
else
NOTREACHED();
} else {
http_body->AppendBlob(body->Uuid(), body->GetBlobDataHandle());
}
}
CreateRequest(std::move(http_body), exception_state);
}
void XMLHttpRequest::send(FormData* body, ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() FormData " << body;
if (!InitSend(exception_state))
return;
scoped_refptr<EncodedFormData> http_body;
if (AreMethodAndURLValidForSend()) {
http_body = body->EncodeMultiPartFormData();
// TODO (sof): override any author-provided charset= in the
// content type value to UTF-8 ?
if (!HasContentTypeRequestHeader()) {
AtomicString content_type =
AtomicString("multipart/form-data; boundary=") +
FetchUtils::NormalizeHeaderValue(http_body->Boundary().data());
SetRequestHeaderInternal(HTTPNames::Content_Type, content_type);
}
}
CreateRequest(std::move(http_body), exception_state);
}
void XMLHttpRequest::send(URLSearchParams* body,
ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() URLSearchParams " << body;
if (!InitSend(exception_state))
return;
scoped_refptr<EncodedFormData> http_body;
if (AreMethodAndURLValidForSend()) {
http_body = body->ToEncodedFormData();
UpdateContentTypeAndCharset(
"application/x-www-form-urlencoded;charset=UTF-8", "UTF-8");
}
CreateRequest(std::move(http_body), exception_state);
}
void XMLHttpRequest::send(DOMArrayBuffer* body,
ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() ArrayBuffer " << body;
SendBytesData(body->Data(), body->ByteLength(), exception_state);
}
void XMLHttpRequest::send(DOMArrayBufferView* body,
ExceptionState& exception_state) {
NETWORK_DVLOG(1) << this << " send() ArrayBufferView " << body;
SendBytesData(body->BaseAddress(), body->byteLength(), exception_state);
}
void XMLHttpRequest::SendBytesData(const void* data,
size_t length,
ExceptionState& exception_state) {
if (!InitSend(exception_state))
return;
scoped_refptr<EncodedFormData> http_body;
if (AreMethodAndURLValidForSend()) {
http_body = EncodedFormData::Create(data, length);
}
CreateRequest(std::move(http_body), exception_state);
}
void XMLHttpRequest::SendForInspectorXHRReplay(
scoped_refptr<EncodedFormData> form_data,
ExceptionState& exception_state) {
CreateRequest(form_data ? form_data->DeepCopy() : nullptr, exception_state);
exception_code_ = exception_state.Code();
}
void XMLHttpRequest::ThrowForLoadFailureIfNeeded(
ExceptionState& exception_state,
const String& reason) {
if (error_ && !exception_code_)
exception_code_ = kNetworkError;
if (!exception_code_)
return;
String message = "Failed to load '" + url_.ElidedString() + "'";
if (reason.IsNull()) {
message.append('.');
} else {
message.append(": ");
message.append(reason);
}
exception_state.ThrowDOMException(exception_code_, message);
}
void XMLHttpRequest::CreateRequest(scoped_refptr<EncodedFormData> http_body,
ExceptionState& exception_state) {
// Only GET request is supported for blob URL.
if (url_.ProtocolIs("blob") && method_ != HTTPNames::GET) {
HandleNetworkError();
if (!async_) {
ThrowForLoadFailureIfNeeded(
exception_state,
"'GET' is the only method allowed for 'blob:' URLs.");
}
return;
}
DCHECK(GetExecutionContext());
ExecutionContext& execution_context = *GetExecutionContext();
send_flag_ = true;
// The presence of upload event listeners forces us to use preflighting
// because POSTing to an URL that does not permit cross origin requests should
// look exactly like POSTing to an URL that does not respond at all.
// Also, only async requests support upload progress events.
bool upload_events = false;
if (async_) {
probe::AsyncTaskScheduled(&execution_context, "XMLHttpRequest.send", this);
DispatchProgressEvent(EventTypeNames::loadstart, 0, 0);
// Event handler could have invalidated this send operation,
// (re)setting the send flag and/or initiating another send
// operation; leave quietly if so.
if (!send_flag_ || loader_)
return;
if (http_body && upload_) {
upload_events = upload_->HasEventListeners();
upload_->DispatchEvent(
ProgressEvent::Create(EventTypeNames::loadstart, false, 0, 0));
// See above.
if (!send_flag_ || loader_)
return;
}
}
same_origin_request_ = GetSecurityOrigin()->CanRequestNoSuborigin(url_);
if (!same_origin_request_ && with_credentials_) {
UseCounter::Count(&execution_context,
WebFeature::kXMLHttpRequestCrossOriginWithCredentials);
}
// We also remember whether upload events should be allowed for this request
// in case the upload listeners are added after the request is started.
upload_events_allowed_ =
same_origin_request_ || upload_events ||
!FetchUtils::IsCORSSafelistedMethod(method_) ||
!FetchUtils::ContainsOnlyCORSSafelistedHeaders(request_headers_);
ResourceRequest request(url_);
request.SetHTTPMethod(method_);
request.SetRequestContext(WebURLRequest::kRequestContextXMLHttpRequest);
request.SetFetchRequestMode(
upload_events ? network::mojom::FetchRequestMode::kCORSWithForcedPreflight
: network::mojom::FetchRequestMode::kCORS);
request.SetFetchCredentialsMode(
with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude
: network::mojom::FetchCredentialsMode::kSameOrigin);
request.SetServiceWorkerMode(is_isolated_world_
? WebURLRequest::ServiceWorkerMode::kNone
: WebURLRequest::ServiceWorkerMode::kAll);
request.SetExternalRequestStateFromRequestorAddressSpace(
execution_context.GetSecurityContext().AddressSpace());
probe::willLoadXHR(&execution_context, this, this, method_, url_, async_,
request_headers_, with_credentials_);
if (http_body) {
DCHECK_NE(method_, HTTPNames::GET);
DCHECK_NE(method_, HTTPNames::HEAD);
request.SetHTTPBody(std::move(http_body));
}
if (request_headers_.size() > 0)
request.AddHTTPHeaderFields(request_headers_);
ThreadableLoaderOptions options;
options.timeout_milliseconds = timeout_milliseconds_;
ResourceLoaderOptions resource_loader_options;
resource_loader_options.security_origin = GetSecurityOrigin();
resource_loader_options.initiator_info.name =
FetchInitiatorTypeNames::xmlhttprequest;
// When responseType is set to "blob", we redirect the downloaded data to a
// file-handle directly.
// TODO: implement this for network service code path. http://crbug.com/754493
if (!RuntimeEnabledFeatures::NetworkServiceEnabled())
downloading_to_file_ = GetResponseTypeCode() == kResponseTypeBlob;
if (downloading_to_file_) {
request.SetDownloadToFile(true);
resource_loader_options.data_buffering_policy = kDoNotBufferData;
}
if (async_) {
resource_loader_options.data_buffering_policy = kDoNotBufferData;
}
exception_code_ = 0;
error_ = false;
if (async_) {
UseCounter::Count(&execution_context,
WebFeature::kXMLHttpRequestAsynchronous);
if (GetExecutionContext()->IsDocument()) {
// Update histogram for usage of async xhr within pagedismissal.
auto pagedismissal = GetDocument()->PageDismissalEventBeingDispatched();
if (pagedismissal != Document::kNoDismissal) {
UseCounter::Count(GetDocument(), WebFeature::kAsyncXhrInPageDismissal);
DEFINE_STATIC_LOCAL(EnumerationHistogram,
asyncxhr_pagedismissal_histogram,
("XHR.Async.PageDismissal", 5));
asyncxhr_pagedismissal_histogram.Count(pagedismissal);
}
}
if (upload_)
request.SetReportUploadProgress(true);
DCHECK(!loader_);
DCHECK(send_flag_);
loader_ = ThreadableLoader::Create(execution_context, this, options,
resource_loader_options);
loader_->Start(request);
return;
}
// Use count for XHR synchronous requests.
UseCounter::Count(&execution_context, WebFeature::kXMLHttpRequestSynchronous);
if (GetExecutionContext()->IsDocument()) {
// Update histogram for usage of sync xhr within pagedismissal.
auto pagedismissal = GetDocument()->PageDismissalEventBeingDispatched();
if (pagedismissal != Document::kNoDismissal) {
UseCounter::Count(GetDocument(), WebFeature::kSyncXhrInPageDismissal);
DEFINE_STATIC_LOCAL(EnumerationHistogram, syncxhr_pagedismissal_histogram,
("XHR.Sync.PageDismissal", 5));
syncxhr_pagedismissal_histogram.Count(pagedismissal);
}
}
ThreadableLoader::LoadResourceSynchronously(execution_context, request, *this,
options, resource_loader_options);
ThrowForLoadFailureIfNeeded(exception_state, String());
}
void XMLHttpRequest::abort() {
NETWORK_DVLOG(1) << this << " abort()";
// internalAbort() clears the response. Save the data needed for
// dispatching ProgressEvents.
long long expected_length = response_.ExpectedContentLength();
long long received_length = received_length_;
if (!InternalAbort())
return;
// The script never gets any chance to call abort() on a sync XHR between
// send() call and transition to the DONE state. It's because a sync XHR
// doesn't dispatch any event between them. So, if |m_async| is false, we
// can skip the "request error steps" (defined in the XHR spec) without any
// state check.
//
// FIXME: It's possible open() is invoked in internalAbort() and |m_async|
// becomes true by that. We should implement more reliable treatment for
// nested method invocations at some point.
if (async_) {
if ((state_ == kOpened && send_flag_) || state_ == kHeadersReceived ||
state_ == kLoading) {
DCHECK(!loader_);
HandleRequestError(0, EventTypeNames::abort, received_length,
expected_length);
}
}
if (state_ == kDone)
state_ = kUnsent;
}
void XMLHttpRequest::Dispose() {
probe::detachClientRequest(GetExecutionContext(), this);
progress_event_throttle_->Stop();
InternalAbort();
}
void XMLHttpRequest::ClearVariablesForLoading() {
if (blob_loader_) {
blob_loader_->Cancel();
blob_loader_ = nullptr;
}
decoder_.reset();
if (response_document_parser_) {
response_document_parser_->RemoveClient(this);
response_document_parser_->Detach();
response_document_parser_ = nullptr;
}
}
bool XMLHttpRequest::InternalAbort() {
// Fast path for repeated internalAbort()s; this
// will happen if an XHR object is notified of context
// destruction followed by finalization.
if (error_ && !loader_)
return true;
error_ = true;
if (response_document_parser_ && !response_document_parser_->IsStopped())
response_document_parser_->StopParsing();
ClearVariablesForLoading();
ClearResponse();
ClearRequest();
if (!loader_)
return true;
// Cancelling the ThreadableLoader m_loader may result in calling
// window.onload synchronously. If such an onload handler contains open()
// call on the same XMLHttpRequest object, reentry happens.
//
// If, window.onload contains open() and send(), m_loader will be set to
// non 0 value. So, we cannot continue the outer open(). In such case,
// just abort the outer open() by returning false.
ThreadableLoader* loader = loader_.Release();
loader->Cancel();
// If abort() called internalAbort() and a nested open() ended up
// clearing the error flag, but didn't send(), make sure the error
// flag is still set.
bool new_load_started = loader_;
if (!new_load_started)
error_ = true;
return !new_load_started;
}
void XMLHttpRequest::ClearResponse() {
// FIXME: when we add the support for multi-part XHR, we will have to
// be careful with this initialization.
received_length_ = 0;
response_ = ResourceResponse();
response_text_.Clear();
parsed_response_ = false;
response_document_ = nullptr;
response_blob_ = nullptr;
downloading_to_file_ = false;
length_downloaded_to_file_ = 0;
// These variables may referred by the response accessors. So, we can clear
// this only when we clear the response holder variables above.
binary_response_builder_ = nullptr;
response_array_buffer_.Clear();
response_array_buffer_failure_ = false;
ReportMemoryUsageToV8();
}
void XMLHttpRequest::ClearRequest() {
request_headers_.Clear();
}
void XMLHttpRequest::DispatchProgressEvent(const AtomicString& type,
long long received_length,
long long expected_length) {
bool length_computable =
expected_length > 0 && received_length <= expected_length;
unsigned long long loaded =
received_length >= 0 ? static_cast<unsigned long long>(received_length)
: 0;
unsigned long long total =
length_computable ? static_cast<unsigned long long>(expected_length) : 0;
ExecutionContext* context = GetExecutionContext();
probe::AsyncTask async_task(
context, this, type == EventTypeNames::loadend ? nullptr : "progress",
async_);
progress_event_throttle_->DispatchProgressEvent(type, length_computable,
loaded, total);
}
void XMLHttpRequest::DispatchProgressEventFromSnapshot(
const AtomicString& type) {
DispatchProgressEvent(type, received_length_,
response_.ExpectedContentLength());
}
void XMLHttpRequest::HandleNetworkError() {
NETWORK_DVLOG(1) << this << " handleNetworkError()";
// Response is cleared next, save needed progress event data.
long long expected_length = response_.ExpectedContentLength();
long long received_length = received_length_;
if (!InternalAbort())
return;
HandleRequestError(kNetworkError, EventTypeNames::error, received_length,
expected_length);
}
void XMLHttpRequest::HandleDidCancel() {
NETWORK_DVLOG(1) << this << " handleDidCancel()";
// Response is cleared next, save needed progress event data.
long long expected_length = response_.ExpectedContentLength();
long long received_length = received_length_;
if (!InternalAbort())
return;
HandleRequestError(kAbortError, EventTypeNames::abort, received_length,
expected_length);
}
void XMLHttpRequest::HandleRequestError(ExceptionCode exception_code,
const AtomicString& type,
long long received_length,
long long expected_length) {
NETWORK_DVLOG(1) << this << " handleRequestError()";
probe::didFailXHRLoading(GetExecutionContext(), this, this, method_, url_);
send_flag_ = false;
if (!async_) {
DCHECK(exception_code);
state_ = kDone;
exception_code_ = exception_code;
return;
}
// With m_error set, the state change steps are minimal: any pending
// progress event is flushed + a readystatechange is dispatched.
// No new progress events dispatched; as required, that happens at
// the end here.
DCHECK(error_);
ChangeState(kDone);
if (!upload_complete_) {
upload_complete_ = true;
if (upload_ && upload_events_allowed_)
upload_->HandleRequestError(type);
}
// Note: The below event dispatch may be called while |hasPendingActivity() ==
// false|, when |handleRequestError| is called after |internalAbort()|. This
// is safe, however, as |this| will be kept alive from a strong ref
// |Event::m_target|.
DispatchProgressEvent(EventTypeNames::progress, received_length,
expected_length);
DispatchProgressEvent(type, received_length, expected_length);
DispatchProgressEvent(EventTypeNames::loadend, received_length,
expected_length);
}
// https://xhr.spec.whatwg.org/#the-overridemimetype()-method
void XMLHttpRequest::overrideMimeType(const AtomicString& mime_type,
ExceptionState& exception_state) {
if (state_ == kLoading || state_ == kDone) {
exception_state.ThrowDOMException(
kInvalidStateError,
"MimeType cannot be overridden when the state is LOADING or DONE.");
return;
}
mime_type_override_ = "application/octet-stream";
if (ParsedContentType(mime_type).IsValid())
mime_type_override_ = mime_type;
}
// https://xhr.spec.whatwg.org/#the-setrequestheader()-method
void XMLHttpRequest::setRequestHeader(const AtomicString& name,
const AtomicString& value,
ExceptionState& exception_state) {
// "1. If |state| is not "opened", throw an InvalidStateError exception.
// 2. If the send() flag is set, throw an InvalidStateError exception."
if (state_ != kOpened || send_flag_) {
exception_state.ThrowDOMException(kInvalidStateError,
"The object's state must be OPENED.");
return;
}
// "3. Normalize |value|."
const String normalized_value = FetchUtils::NormalizeHeaderValue(value);
// "4. If |name| is not a name or |value| is not a value, throw a SyntaxError
// exception."
if (!IsValidHTTPToken(name)) {
exception_state.ThrowDOMException(
kSyntaxError, "'" + name + "' is not a valid HTTP header field name.");
return;
}
if (!IsValidHTTPHeaderValue(normalized_value)) {
exception_state.ThrowDOMException(
kSyntaxError,
"'" + normalized_value + "' is not a valid HTTP header field value.");
return;
}
// "5. Terminate these steps if |name| is a forbidden header name."
// No script (privileged or not) can set unsafe headers.
if (FetchUtils::IsForbiddenHeaderName(name)) {
LogConsoleError(GetExecutionContext(),
"Refused to set unsafe header \"" + name + "\"");
return;
}
// "6. Combine |name|/|value| in author request headers."
SetRequestHeaderInternal(name, AtomicString(normalized_value));
}
void XMLHttpRequest::SetRequestHeaderInternal(const AtomicString& name,
const AtomicString& value) {
DCHECK_EQ(value, FetchUtils::NormalizeHeaderValue(value))
<< "Header values must be normalized";
HTTPHeaderMap::AddResult result = request_headers_.Add(name, value);
if (!result.is_new_entry) {
AtomicString new_value = result.stored_value->value + ", " + value;
result.stored_value->value = new_value;
}
}
bool XMLHttpRequest::HasContentTypeRequestHeader() const {
return request_headers_.Find(HTTPNames::Content_Type) !=
request_headers_.end();
}
String XMLHttpRequest::getAllResponseHeaders() const {
if (state_ < kHeadersReceived || error_)
return "";
StringBuilder string_builder;
WebHTTPHeaderSet access_control_expose_header_set =
WebCORS::ExtractCorsExposedHeaderNamesList(
with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude
: network::mojom::FetchCredentialsMode::kSameOrigin,
WrappedResourceResponse(response_));
HTTPHeaderMap::const_iterator end = response_.HttpHeaderFields().end();
for (HTTPHeaderMap::const_iterator it = response_.HttpHeaderFields().begin();
it != end; ++it) {
// Hide any headers whose name is a forbidden response-header name.
// This is required for all kinds of filtered responses.
//
// TODO: Consider removing canLoadLocalResources() call.
// crbug.com/567527
if (FetchUtils::IsForbiddenResponseHeaderName(it->key) &&
!GetSecurityOrigin()->CanLoadLocalResources())
continue;
if (!same_origin_request_ &&
!WebCORS::IsOnAccessControlResponseHeaderWhitelist(it->key) &&
access_control_expose_header_set.find(it->key.Ascii().data()) ==
access_control_expose_header_set.end())
continue;
string_builder.Append(it->key.LowerASCII());
string_builder.Append(':');
string_builder.Append(' ');
string_builder.Append(it->value);
string_builder.Append('\r');
string_builder.Append('\n');
}
return string_builder.ToString();
}
const AtomicString& XMLHttpRequest::getResponseHeader(
const AtomicString& name) const {
if (state_ < kHeadersReceived || error_)
return g_null_atom;
// See comment in getAllResponseHeaders above.
if (FetchUtils::IsForbiddenResponseHeaderName(name) &&
!GetSecurityOrigin()->CanLoadLocalResources()) {
LogConsoleError(GetExecutionContext(),
"Refused to get unsafe header \"" + name + "\"");
return g_null_atom;
}
WebHTTPHeaderSet access_control_expose_header_set =
WebCORS::ExtractCorsExposedHeaderNamesList(
with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude
: network::mojom::FetchCredentialsMode::kSameOrigin,
WrappedResourceResponse(response_));
if (!same_origin_request_ &&
!WebCORS::IsOnAccessControlResponseHeaderWhitelist(name) &&
access_control_expose_header_set.find(name.Ascii().data()) ==
access_control_expose_header_set.end()) {
LogConsoleError(GetExecutionContext(),
"Refused to get unsafe header \"" + name + "\"");
return g_null_atom;
}
return response_.HttpHeaderField(name);
}
AtomicString XMLHttpRequest::FinalResponseMIMEType() const {
AtomicString overridden_type =
ExtractMIMETypeFromMediaType(mime_type_override_);
if (!overridden_type.IsEmpty())
return overridden_type;
if (response_.IsHTTP())
return ExtractMIMETypeFromMediaType(
response_.HttpHeaderField(HTTPNames::Content_Type));
return response_.MimeType();
}
AtomicString XMLHttpRequest::FinalResponseMIMETypeWithFallback() const {
AtomicString final_type = FinalResponseMIMEType();
if (!final_type.IsEmpty())
return final_type;
return AtomicString("text/xml");
}
String XMLHttpRequest::FinalResponseCharset() const {
String override_response_charset =
ExtractCharsetFromMediaType(mime_type_override_);
if (!override_response_charset.IsEmpty())
return override_response_charset;
return response_.TextEncodingName();
}
void XMLHttpRequest::UpdateContentTypeAndCharset(
const AtomicString& default_content_type,
const String& charset) {
// http://xhr.spec.whatwg.org/#the-send()-method step 4's concilliation of
// "charset=" in any author-provided Content-Type: request header.
String content_type = request_headers_.Get(HTTPNames::Content_Type);
if (content_type.IsEmpty()) {
SetRequestHeaderInternal(HTTPNames::Content_Type, default_content_type);
return;
}
String original_content_type = content_type;
ReplaceCharsetInMediaType(content_type, charset);
request_headers_.Set(HTTPNames::Content_Type, AtomicString(content_type));
if (original_content_type != content_type)
UseCounter::Count(GetExecutionContext(), WebFeature::kReplaceCharsetInXHR);
}
bool XMLHttpRequest::ResponseIsXML() const {
return DOMImplementation::IsXMLMIMEType(FinalResponseMIMETypeWithFallback());
}
bool XMLHttpRequest::ResponseIsHTML() const {
return EqualIgnoringASCIICase(FinalResponseMIMEType(), "text/html");
}
int XMLHttpRequest::status() const {
if (state_ == kUnsent || state_ == kOpened || error_)
return 0;
if (response_.HttpStatusCode())
return response_.HttpStatusCode();
return 0;
}
String XMLHttpRequest::statusText() const {
if (state_ == kUnsent || state_ == kOpened || error_)
return String();
if (!response_.HttpStatusText().IsNull())
return response_.HttpStatusText();
return String();
}
void XMLHttpRequest::DidFail(const ResourceError& error) {
NETWORK_DVLOG(1) << this << " didFail()";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
// If we are already in an error state, for instance we called abort(), bail
// out early.
if (error_)
return;
// Internally, access check violations are considered `cancellations`, but
// at least the mixed-content and CSP specs require them to be surfaced as
// network errors to the page. See:
// [1] https://www.w3.org/TR/mixed-content/#algorithms,
// [2] https://www.w3.org/TR/CSP3/#fetch-integration.
if (error.IsCancellation() && !error.IsAccessCheck()) {
HandleDidCancel();
return;
}
if (error.IsTimeout()) {
HandleDidTimeout();
return;
}
HandleNetworkError();
}
void XMLHttpRequest::DidFailRedirectCheck() {
NETWORK_DVLOG(1) << this << " didFailRedirectCheck()";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
HandleNetworkError();
}
void XMLHttpRequest::DidFinishLoading(unsigned long identifier, double) {
NETWORK_DVLOG(1) << this << " didFinishLoading(" << identifier << ")";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
if (error_)
return;
if (state_ < kHeadersReceived)
ChangeState(kHeadersReceived);
if (downloading_to_file_ && response_type_code_ != kResponseTypeBlob &&
length_downloaded_to_file_) {
DCHECK_EQ(kLoading, state_);
// In this case, we have sent the request with DownloadToFile true,
// but the user changed the response type after that. Hence we need to
// read the response data and provide it to this object.
blob_loader_ = BlobLoader::Create(this, CreateBlobDataHandleFromResponse());
} else {
DidFinishLoadingInternal();
}
}
void XMLHttpRequest::DidFinishLoadingInternal() {
if (response_document_parser_) {
// |DocumentParser::finish()| tells the parser that we have reached end of
// the data. When using |HTMLDocumentParser|, which works asynchronously,
// we do not have the complete document just after the
// |DocumentParser::finish()| call. Wait for the parser to call us back in
// |notifyParserStopped| to progress state.
response_document_parser_->Finish();
DCHECK(response_document_);
return;
}
if (decoder_) {
auto text = decoder_->Flush();
if (!text.IsEmpty() && !response_text_overflow_) {
response_text_ = response_text_.ConcatenateWith(text);
response_text_overflow_ = response_text_.IsEmpty();
}
}
ClearVariablesForLoading();
EndLoading();
}
void XMLHttpRequest::DidFinishLoadingFromBlob() {
NETWORK_DVLOG(1) << this << " didFinishLoadingFromBlob";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
DidFinishLoadingInternal();
}
void XMLHttpRequest::DidFailLoadingFromBlob() {
NETWORK_DVLOG(1) << this << " didFailLoadingFromBlob()";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
if (error_)
return;
HandleNetworkError();
}
scoped_refptr<BlobDataHandle>
XMLHttpRequest::CreateBlobDataHandleFromResponse() {
DCHECK(downloading_to_file_);
std::unique_ptr<BlobData> blob_data = BlobData::Create();
String file_path = response_.DownloadedFilePath();
// If we errored out or got no data, we return an empty handle.
if (!file_path.IsEmpty() && length_downloaded_to_file_) {
blob_data->AppendFile(file_path, 0, length_downloaded_to_file_,
InvalidFileTime());
}
blob_data->SetContentType(FinalResponseMIMETypeWithFallback().LowerASCII());
return BlobDataHandle::Create(std::move(blob_data),
length_downloaded_to_file_);
}
void XMLHttpRequest::NotifyParserStopped() {
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
// This should only be called when response document is parsed asynchronously.
DCHECK(response_document_parser_);
DCHECK(!response_document_parser_->IsParsing());
// Do nothing if we are called from |internalAbort()|.
if (error_)
return;
ClearVariablesForLoading();
if (!response_document_->WellFormed())
response_document_ = nullptr;
parsed_response_ = true;
EndLoading();
}
void XMLHttpRequest::EndLoading() {
probe::didFinishXHRLoading(GetExecutionContext(), this, this, method_, url_);
if (loader_) {
// Set |m_error| in order to suppress the cancel notification (see
// XMLHttpRequest::didFail).
AutoReset<bool> scope(&error_, true);
loader_.Release()->Cancel();
}
send_flag_ = false;
ChangeState(kDone);
if (!GetExecutionContext() || !GetExecutionContext()->IsDocument())
return;
if (GetDocument() && GetDocument()->GetFrame() &&
GetDocument()->GetFrame()->GetPage() && FetchUtils::IsOkStatus(status()))
GetDocument()->GetFrame()->GetPage()->GetChromeClient().AjaxSucceeded(
GetDocument()->GetFrame());
}
void XMLHttpRequest::DidSendData(unsigned long long bytes_sent,
unsigned long long total_bytes_to_be_sent) {
NETWORK_DVLOG(1) << this << " didSendData(" << bytes_sent << ", "
<< total_bytes_to_be_sent << ")";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
if (!upload_)
return;
if (upload_events_allowed_)
upload_->DispatchProgressEvent(bytes_sent, total_bytes_to_be_sent);
if (bytes_sent == total_bytes_to_be_sent && !upload_complete_) {
upload_complete_ = true;
if (upload_events_allowed_)
upload_->DispatchEventAndLoadEnd(EventTypeNames::load, true, bytes_sent,
total_bytes_to_be_sent);
}
}
void XMLHttpRequest::DidReceiveResponse(
unsigned long identifier,
const ResourceResponse& response,
std::unique_ptr<WebDataConsumerHandle> handle) {
ALLOW_UNUSED_LOCAL(handle);
DCHECK(!handle);
NETWORK_DVLOG(1) << this << " didReceiveResponse(" << identifier << ")";
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
response_ = response;
}
void XMLHttpRequest::ParseDocumentChunk(const char* data, unsigned len) {
if (!response_document_parser_) {
DCHECK(!response_document_);
InitResponseDocument();
if (!response_document_)
return;
response_document_parser_ =
response_document_->ImplicitOpen(kAllowAsynchronousParsing);
response_document_parser_->AddClient(this);
}
DCHECK(response_document_parser_);
if (response_document_parser_->NeedsDecoder())
response_document_parser_->SetDecoder(CreateDecoder());
response_document_parser_->AppendBytes(data, len);
}
std::unique_ptr<TextResourceDecoder> XMLHttpRequest::CreateDecoder() const {
const TextResourceDecoderOptions decoder_options_for_utf8_plain_text(
TextResourceDecoderOptions::kPlainTextContent, UTF8Encoding());
if (response_type_code_ == kResponseTypeJSON)
return TextResourceDecoder::Create(decoder_options_for_utf8_plain_text);
String final_response_charset = FinalResponseCharset();
if (!final_response_charset.IsEmpty()) {
// If the final charset is given, use the charset without sniffing the
// content.
return TextResourceDecoder::Create(TextResourceDecoderOptions(
TextResourceDecoderOptions::kPlainTextContent,
WTF::TextEncoding(final_response_charset)));
}
TextResourceDecoderOptions decoder_options_for_xml(
TextResourceDecoderOptions::kXMLContent);
// 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.
decoder_options_for_xml.SetUseLenientXMLDecoding();
switch (response_type_code_) {
case kResponseTypeDefault:
if (ResponseIsXML())
return TextResourceDecoder::Create(decoder_options_for_xml);
// fall through
case kResponseTypeText:
return TextResourceDecoder::Create(decoder_options_for_utf8_plain_text);
case kResponseTypeDocument:
if (ResponseIsHTML()) {
return TextResourceDecoder::Create(TextResourceDecoderOptions(
TextResourceDecoderOptions::kHTMLContent, UTF8Encoding()));
}
return TextResourceDecoder::Create(decoder_options_for_xml);
case kResponseTypeJSON:
case kResponseTypeBlob:
case kResponseTypeArrayBuffer:
NOTREACHED();
break;
}
NOTREACHED();
return nullptr;
}
void XMLHttpRequest::DidReceiveData(const char* data, unsigned len) {
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
if (error_)
return;
if (state_ < kHeadersReceived)
ChangeState(kHeadersReceived);
// We need to check for |m_error| again, because |changeState| may trigger
// readystatechange, and user javascript can cause |abort()|.
if (error_)
return;
if (!len)
return;
if (response_type_code_ == kResponseTypeDocument && ResponseIsHTML()) {
ParseDocumentChunk(data, len);
} else if (response_type_code_ == kResponseTypeDefault ||
response_type_code_ == kResponseTypeText ||
response_type_code_ == kResponseTypeJSON ||
response_type_code_ == kResponseTypeDocument) {
if (!decoder_)
decoder_ = CreateDecoder();
auto text = decoder_->Decode(data, len);
if (!text.IsEmpty() && !response_text_overflow_) {
response_text_ = response_text_.ConcatenateWith(text);
response_text_overflow_ = response_text_.IsEmpty();
}
} else if (response_type_code_ == kResponseTypeArrayBuffer ||
response_type_code_ == kResponseTypeBlob) {
// Buffer binary data.
if (!binary_response_builder_)
binary_response_builder_ = SharedBuffer::Create();
binary_response_builder_->Append(data, len);
ReportMemoryUsageToV8();
}
if (blob_loader_) {
// In this case, the data is provided by m_blobLoader. As progress
// events are already fired, we should return here.
return;
}
TrackProgress(len);
}
void XMLHttpRequest::DidDownloadData(int data_length) {
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
if (error_)
return;
DCHECK(downloading_to_file_);
if (state_ < kHeadersReceived)
ChangeState(kHeadersReceived);
if (!data_length)
return;
// readystatechange event handler may do something to put this XHR in error
// state. We need to check m_error again here.
if (error_)
return;
length_downloaded_to_file_ += data_length;
ReportMemoryUsageToV8();
TrackProgress(data_length);
}
void XMLHttpRequest::HandleDidTimeout() {
NETWORK_DVLOG(1) << this << " handleDidTimeout()";
// Response is cleared next, save needed progress event data.
long long expected_length = response_.ExpectedContentLength();
long long received_length = received_length_;
if (!InternalAbort())
return;
HandleRequestError(kTimeoutError, EventTypeNames::timeout, received_length,
expected_length);
}
void XMLHttpRequest::Pause() {
progress_event_throttle_->Pause();
}
void XMLHttpRequest::Unpause() {
progress_event_throttle_->Unpause();
}
void XMLHttpRequest::ContextDestroyed(ExecutionContext*) {
Dispose();
// In case we are in the middle of send() function, unset the send flag to
// stop the operation.
send_flag_ = false;
}
bool XMLHttpRequest::HasPendingActivity() const {
// Neither this object nor the JavaScript wrapper should be deleted while
// a request is in progress because we need to keep the listeners alive,
// and they are referenced by the JavaScript wrapper.
// |m_loader| is non-null while request is active and ThreadableLoaderClient
// callbacks may be called, and |m_responseDocumentParser| is non-null while
// DocumentParserClient callbacks may be called.
if (loader_ || response_document_parser_)
return true;
return event_dispatch_recursion_level_ > 0;
}
const AtomicString& XMLHttpRequest::InterfaceName() const {
return EventTargetNames::XMLHttpRequest;
}
ExecutionContext* XMLHttpRequest::GetExecutionContext() const {
return PausableObject::GetExecutionContext();
}
void XMLHttpRequest::ReportMemoryUsageToV8() {
// binary_response_builder_
size_t size = binary_response_builder_ ? binary_response_builder_->size() : 0;
int64_t diff =
static_cast<int64_t>(size) -
static_cast<int64_t>(binary_response_builder_last_reported_size_);
binary_response_builder_last_reported_size_ = size;
// Blob (downloading_to_file_, length_downloaded_to_file_)
diff += static_cast<int64_t>(length_downloaded_to_file_) -
static_cast<int64_t>(length_downloaded_to_file_last_reported_);
length_downloaded_to_file_last_reported_ = length_downloaded_to_file_;
if (diff)
isolate_->AdjustAmountOfExternalAllocatedMemory(diff);
}
void XMLHttpRequest::Trace(blink::Visitor* visitor) {
visitor->Trace(response_blob_);
visitor->Trace(loader_);
visitor->Trace(response_document_);
visitor->Trace(response_document_parser_);
visitor->Trace(response_array_buffer_);
visitor->Trace(progress_event_throttle_);
visitor->Trace(upload_);
visitor->Trace(blob_loader_);
XMLHttpRequestEventTarget::Trace(visitor);
DocumentParserClient::Trace(visitor);
PausableObject::Trace(visitor);
}
void XMLHttpRequest::TraceWrappers(
const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(response_blob_);
visitor->TraceWrappers(response_document_);
visitor->TraceWrappers(response_array_buffer_);
XMLHttpRequestEventTarget::TraceWrappers(visitor);
}
std::ostream& operator<<(std::ostream& ostream, const XMLHttpRequest* xhr) {
return ostream << "XMLHttpRequest " << static_cast<const void*>(xhr);
}
} // namespace blink