/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "XMLHttpRequestMainThread.h"
#include <algorithm>
#ifndef XP_WIN
# include <unistd.h>
#endif
#include "mozilla/AppShutdown.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Components.h"
#include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/DOMString.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/FileCreatorHelper.h"
#include "mozilla/dom/FetchUtil.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/MutableBlobStorage.h"
#include "mozilla/dom/XMLDocument.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/WorkerError.h"
#include "mozilla/Encoding.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/LoadContext.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/net/ContentRange.h"
#include "mozilla/PreloaderBase.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/dom/ProgressEvent.h"
#include "nsDataChannel.h"
#include "nsIBaseChannel.h"
#include "nsIJARChannel.h"
#include "nsIJARURI.h"
#include "nsGlobalWindowInner.h"
#include "nsReadableUtils.h"
#include "nsSandboxFlags.h"
#include "nsIContentPolicy.h"
#include "nsIURI.h"
#include "nsIURIMutator.h"
#include "nsILoadGroup.h"
#include "nsNetUtil.h"
#include "nsStringStream.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsIClassOfService.h"
#include "nsIHttpChannel.h"
#include "nsISupportsPriority.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsIUploadChannel.h"
#include "nsIUploadChannel2.h"
#include "nsXPCOM.h"
#include "nsIDOMEventListener.h"
#include "nsVariant.h"
#include "nsIScriptError.h"
#include "nsICachingChannel.h"
#include "nsICookieJarSettings.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsError.h"
#include "nsIPromptFactory.h"
#include "nsIWindowWatcher.h"
#include "nsIConsoleService.h"
#include "nsAsyncRedirectVerifyHelper.h"
#include "nsIFileChannel.h"
#include "mozilla/Telemetry.h"
#include "js/ArrayBuffer.h" // JS::{Create,Release}MappedArrayBufferContents,New{,Mapped}ArrayBufferWithContents
#include "js/JSON.h" // JS_ParseJSON
#include "js/MemoryFunctions.h"
#include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
#include "js/Value.h" // JS::{,Undefined}Value
#include "jsapi.h" // JS_ClearPendingException
#include "GeckoProfiler.h"
#include "mozilla/dom/XMLHttpRequestBinding.h"
#include "mozilla/Attributes.h"
#include "MultipartBlobImpl.h"
#include "nsIPermissionManager.h"
#include "nsMimeTypes.h"
#include "nsIHttpChannelInternal.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsStreamListenerWrapper.h"
#include "nsITimedChannel.h"
#include "nsWrapperCacheInlines.h"
#include "nsZipArchive.h"
#include "mozilla/Preferences.h"
#include "private/pprio.h"
#include "XMLHttpRequestUpload.h"
// Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
// replaced by FileCreatorHelper#CreateFileW.
#ifdef CreateFile
# undef CreateFile
#endif
extern mozilla::LazyLogModule gXMLHttpRequestLog;
using namespace mozilla::net;
namespace mozilla::dom {
using EventType = XMLHttpRequest::EventType;
using Events = XMLHttpRequest::Events;
// Maximum size that we'll grow an ArrayBuffer instead of doubling,
// once doubling reaches this threshold
const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
// start at 32k to avoid lots of doubling right at the start
const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
// the maximum Content-Length that we'll preallocate. 1GB. Must fit
// in an int32_t!
const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
1 * 1024 * 1024 * 1024LL;
constexpr nsLiteralString kLiteralString_readystatechange =
u
"readystatechange"_ns;
// constexpr nsLiteralString kLiteralString_xmlhttprequest =
// u"xmlhttprequest"_ns;
constexpr nsLiteralString kLiteralString_DOMContentLoaded =
u
"DOMContentLoaded"_ns;
constexpr nsLiteralCString kLiteralString_charset =
"charset"_ns;
constexpr nsLiteralCString kLiteralString_UTF_8 =
"UTF-8"_ns;
#define NS_PROGRESS_EVENT_INTERVAL 50
#define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000
/* 10 secs */
NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
class nsResumeTimeoutsEvent :
public Runnable {
public:
explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
: Runnable(
"dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
NS_IMETHOD Run() override {
mWindow->Resume();
return NS_OK;
}
private:
nsCOMPtr<nsPIDOMWindowInner> mWindow;
};
// This helper function adds the given load flags to the request's existing
// load flags.
static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
nsLoadFlags flags;
request->GetLoadFlags(&flags);
flags |= newFlags;
request->SetLoadFlags(flags);
}
// We are in a sync event loop.
#define NOT_CALLABLE_IN_SYNC_SEND_RV \
if (mFlagSyncLooping || mEventDispatchingSuspended) { \
aRv.
Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
return; \
}
/////////////////////////////////////////////
//
//
/////////////////////////////////////////////
#ifdef DEBUG
// In debug mode, annotate WorkerRefs with the name of the function being
// invoked for increased scrutability. Save the previous value on the stack.
namespace {
struct DebugWorkerRefs {
Mutex& mMutex;
RefPtr<ThreadSafeWorkerRef> mTSWorkerRef;
nsCString mPrev;
DebugWorkerRefs(XMLHttpRequestMainThread& aXHR,
const std::string& aStatus)
: mMutex(aXHR.mTSWorkerRefMutex) {
MutexAutoLock lock(mMutex);
mTSWorkerRef = aXHR.mTSWorkerRef;
if (!mTSWorkerRef) {
return;
}
MOZ_ASSERT(mTSWorkerRef->
Private());
nsCString status(aStatus.c_str());
mPrev = GET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref());
SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), status);
}
~DebugWorkerRefs() {
MutexAutoLock lock(mMutex);
if (!mTSWorkerRef) {
return;
}
MOZ_ASSERT(mTSWorkerRef->
Private());
SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), mPrev);
mTSWorkerRef = nullptr;
}
};
}
// namespace
# define STREAM_STRING(stuff) \
(((
const std::ostringstream&)(std::ostringstream() << stuff)) \
.str())
// NOLINT
# if 1
// Disabling because bug 1855699
# define DEBUG_WORKERREFS
void()
# define DEBUG_WORKERREFS1(x)
void()
# else
# define DEBUG_WORKERREFS \
DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)(*
this, __func__)
# define DEBUG_WORKERREFS1(x) \
DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)( \
*
this, STREAM_STRING(__func__ <<
": " << x))
// NOLINT
# endif
#else
# define DEBUG_WORKERREFS
void()
# define DEBUG_WORKERREFS1(x)
void()
#endif // DEBUG
bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR =
false;
XMLHttpRequestMainThread::XMLHttpRequestMainThread(
nsIGlobalObject* aGlobalObject)
: XMLHttpRequest(aGlobalObject),
#ifdef DEBUG
mTSWorkerRefMutex(
"Debug WorkerRefs"),
#endif
mResponseBodyDecodedPos(0),
mResponseType(XMLHttpRequestResponseType::_empty),
mState(XMLHttpRequest_Binding::UNSENT),
mFlagSynchronous(
false),
mFlagAborted(
false),
mFlagParseBody(
false),
mFlagSyncLooping(
false),
mFlagBackgroundRequest(
false),
mFlagHadUploadListenersOnSend(
false),
mFlagACwithCredentials(
false),
mFlagTimedOut(
false),
mFlagDeleted(
false),
mFlagSend(
false),
mUploadTransferred(0),
mUploadTotal(0),
mUploadComplete(
true),
mProgressSinceLastProgressEvent(
false),
mRequestSentTime(0),
mTimeoutMilliseconds(0),
mErrorLoad(ErrorType::eOK),
mErrorLoadDetail(NS_OK),
mErrorParsingXML(
false),
mWaitingForOnStopRequest(
false),
mProgressTimerIsActive(
false),
mIsHtml(
false),
mWarnAboutSyncHtml(
false),
mLoadTotal(-1),
mLoadTransferred(0),
mIsSystem(
false),
mIsAnon(
false),
mResultJSON(JS::UndefinedValue()),
mArrayBufferBuilder(
new ArrayBufferBuilder()),
mResultArrayBuffer(nullptr),
mIsMappedArrayBuffer(
false),
mXPCOMifier(nullptr),
mEventDispatchingSuspended(
false),
mEofDecoded(
false),
mDelayedDoneNotifier(nullptr) {
DEBUG_WORKERREFS;
mozilla::HoldJSObjects(
this);
}
XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
DEBUG_WORKERREFS;
MOZ_ASSERT(
!mDelayedDoneNotifier,
"How can we have mDelayedDoneNotifier, which owns us, in destructor?");
mFlagDeleted =
true;
if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
mState == XMLHttpRequest_Binding::LOADING) {
Abort();
}
if (mParseEndListener) {
mParseEndListener->SetIsStale();
mParseEndListener = nullptr;
}
MOZ_ASSERT(!mFlagSyncLooping,
"we rather crash than hang");
mFlagSyncLooping =
false;
mozilla::DropJSObjects(
this);
}
void XMLHttpRequestMainThread::Construct(
nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
bool aForWorker, nsIURI* aBaseURI
/* = nullptr */,
nsILoadGroup* aLoadGroup
/* = nullptr */,
PerformanceStorage* aPerformanceStorage
/* = nullptr */,
nsICSPEventListener* aCSPEventListener
/* = nullptr */) {
DEBUG_WORKERREFS;
MOZ_ASSERT(aPrincipal);
mPrincipal = aPrincipal;
mBaseURI = aBaseURI;
mLoadGroup = aLoadGroup;
mCookieJarSettings = aCookieJarSettings;
mForWorker = aForWorker;
mPerformanceStorage = aPerformanceStorage;
mCSPEventListener = aCSPEventListener;
}
void XMLHttpRequestMainThread::InitParameters(
bool aAnon,
bool aSystem) {
DEBUG_WORKERREFS;
if (!aAnon && !aSystem) {
return;
}
// Check for permissions.
// Chrome is always allowed access, so do the permission check only
// for non-chrome pages.
if (!IsSystemXHR() && aSystem) {
nsIGlobalObject* global = GetOwnerGlobal();
if (NS_WARN_IF(!global)) {
SetParameters(aAnon,
false);
return;
}
nsIPrincipal* principal = global->PrincipalOrNull();
if (NS_WARN_IF(!principal)) {
SetParameters(aAnon,
false);
return;
}
nsCOMPtr<nsIPermissionManager> permMgr =
components::PermissionManager::Service();
if (NS_WARN_IF(!permMgr)) {
SetParameters(aAnon,
false);
return;
}
uint32_t permission;
nsresult rv = permMgr->TestPermissionFromPrincipal(
principal,
"systemXHR"_ns, &permission);
if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
SetParameters(aAnon,
false);
return;
}
}
SetParameters(aAnon, aSystem);
}
void XMLHttpRequestMainThread::SetClientInfoAndController(
const ClientInfo& aClientInfo,
const Maybe<ServiceWorkerDescriptor>& aController) {
mClientInfo.emplace(aClientInfo);
mController = aController;
}
void XMLHttpRequestMainThread::ResetResponse() {
mResponseXML = nullptr;
mResponseBody.Truncate();
TruncateResponseText();
mResponseBlobImpl = nullptr;
mResponseBlob = nullptr;
mBlobStorage = nullptr;
mResultArrayBuffer = nullptr;
mArrayBufferBuilder =
new ArrayBufferBuilder();
mResultJSON.setUndefined();
mLoadTransferred = 0;
mResponseBodyDecodedPos = 0;
mEofDecoded =
false;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
XMLHttpRequestEventTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
XMLHttpRequestEventTarget)
tmp->mResultArrayBuffer = nullptr;
tmp->mArrayBufferBuilder = nullptr;
tmp->mResultJSON.setUndefined();
tmp->mResponseBlobImpl = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
XMLHttpRequestEventTarget)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
bool XMLHttpRequestMainThread::IsCertainlyAliveForCC()
const {
return mWaitingForOnStopRequest;
}
// QueryInterface implementation for XMLHttpRequestMainThread
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsINamed)
NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
void XMLHttpRequestMainThread::DisconnectFromOwner() {
XMLHttpRequestEventTarget::DisconnectFromOwner();
// Worker-owned XHRs have their own complicated state machine that does not
// expect Abort() to be called here. The worker state machine cleanup will
// take care of ensuring the XHR is aborted in a timely fashion since the
// worker itself will inherently be canceled at the same time this is
// happening.
if (!mForWorker) {
Abort();
}
}
size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
MallocSizeOf aMallocSizeOf)
const {
size_t n = aMallocSizeOf(
this);
n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
// Why is this safe? Because no-one else will report this string. The
// other possible sharers of this string are as follows.
//
// - The JS engine could hold copies if the JS code holds references, e.g.
// |var text = XHR.responseText|. However, those references will be via JS
// external strings, for which the JS memory reporter does *not* report the
// chars.
//
// - Binary extensions, but they're *extremely* unlikely to do any memory
// reporting.
//
n += mResponseText.SizeOfThis(aMallocSizeOf);
return n;
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - lots
}
static void LogMessage(
const char* aWarning, nsPIDOMWindowInner* aWindow,
const nsTArray<nsString>& aParams = nsTArray<nsString>()) {
nsCOMPtr<Document> doc;
if (aWindow) {
doc = aWindow->GetExtantDoc();
}
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
"DOM"_ns, doc,
nsContentUtils::eDOM_PROPERTIES, aWarning,
aParams);
}
Document* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
if (mResponseType != XMLHttpRequestResponseType::_empty &&
mResponseType != XMLHttpRequestResponseType::Document) {
aRv.ThrowInvalidStateError(
"responseXML is only available if responseType is '' or 'document'.");
return nullptr;
}
if (mWarnAboutSyncHtml) {
mWarnAboutSyncHtml =
false;
LogMessage(
"HTMLSyncXHRWarning", GetOwnerWindow());
}
if (mState != XMLHttpRequest_Binding::DONE) {
return nullptr;
}
return mResponseXML;
}
/*
* This piece copied from XMLDocument, we try to get the charset
* from HTTP headers.
*/
nsresult XMLHttpRequestMainThread::DetectCharset() {
DEBUG_WORKERREFS;
mDecoder = nullptr;
if (mResponseType != XMLHttpRequestResponseType::_empty &&
mResponseType != XMLHttpRequestResponseType::Text &&
mResponseType != XMLHttpRequestResponseType::Json) {
return NS_OK;
}
nsAutoCString charsetVal;
const Encoding* encoding;
bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
(encoding = Encoding::ForLabel(charsetVal));
if (!ok) {
// MS documentation states UTF-8 is default for responseText
encoding = UTF_8_ENCODING;
}
if (mResponseType == XMLHttpRequestResponseType::Json &&
encoding != UTF_8_ENCODING) {
// The XHR spec says only UTF-8 is supported for responseType == "json"
LogMessage(
"JSONCharsetWarning", GetOwnerWindow());
encoding = UTF_8_ENCODING;
}
// Only sniff the BOM for non-JSON responseTypes
if (mResponseType == XMLHttpRequestResponseType::Json) {
mDecoder = encoding->NewDecoderWithBOMRemoval();
}
else {
mDecoder = encoding->NewDecoder();
}
return NS_OK;
}
nsresult XMLHttpRequestMainThread::AppendToResponseText(
Span<
const uint8_t> aBuffer,
bool aLast) {
// Call this with an empty buffer to send the decoder the signal
// that we have hit the end of the stream.
NS_ENSURE_STATE(mDecoder);
CheckedInt<size_t> destBufferLen =
mDecoder->MaxUTF16BufferLength(aBuffer.Length());
{
// scope for holding the mutex that protects mResponseText
XMLHttpRequestStringWriterHelper helper(mResponseText);
uint32_t len = helper.Length();
destBufferLen += len;
if (!destBufferLen.isValid() || destBufferLen.value() > UINT32_MAX) {
return NS_ERROR_OUT_OF_MEMORY;
}
auto handleOrErr = helper.BulkWrite(destBufferLen.value());
if (handleOrErr.isErr()) {
return handleOrErr.unwrapErr();
}
auto handle = handleOrErr.unwrap();
uint32_t result;
size_t read;
size_t written;
std::tie(result, read, written, std::ignore) =
mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
MOZ_ASSERT(result == kInputEmpty);
MOZ_ASSERT(read == aBuffer.Length());
len += written;
MOZ_ASSERT(len <= destBufferLen.value());
handle.Finish(len,
false);
}
// release mutex
if (aLast) {
// Drop the finished decoder to avoid calling into a decoder
// that has finished.
mDecoder = nullptr;
mEofDecoded =
true;
}
return NS_OK;
}
void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
XMLHttpRequestStringSnapshot snapshot;
GetResponseText(snapshot, aRv);
if (aRv.Failed()) {
return;
}
if (!snapshot.GetAsString(aResponseText)) {
aRv.
Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
void XMLHttpRequestMainThread::GetResponseText(
XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
aSnapshot.Reset();
if (mResponseType != XMLHttpRequestResponseType::_empty &&
mResponseType != XMLHttpRequestResponseType::Text) {
aRv.ThrowInvalidStateError(
"responseText is only available if responseType is '' or 'text'.");
return;
}
if (mState != XMLHttpRequest_Binding::LOADING &&
mState != XMLHttpRequest_Binding::DONE) {
return;
}
// Main Fetch step 18 requires to ignore body for head/connect methods.
if (mRequestMethod.EqualsLiteral(
"HEAD") ||
mRequestMethod.EqualsLiteral(
"CONNECT")) {
return;
}
// We only decode text lazily if we're also parsing to a doc.
// Also, if we've decoded all current data already, then no need to decode
// more.
if ((!mResponseXML && !mErrorParsingXML) ||
(mResponseBodyDecodedPos == mResponseBody.Length() &&
(mState != XMLHttpRequest_Binding::DONE || mEofDecoded))) {
mResponseText.CreateSnapshot(aSnapshot);
return;
}
MatchCharsetAndDecoderToResponseDocument();
MOZ_ASSERT(mResponseBodyDecodedPos < mResponseBody.Length() ||
mState == XMLHttpRequest_Binding::DONE,
"Unexpected mResponseBodyDecodedPos");
Span<
const uint8_t> span = mResponseBody;
aRv = AppendToResponseText(span.From(mResponseBodyDecodedPos),
mState == XMLHttpRequest_Binding::DONE);
if (aRv.Failed()) {
return;
}
mResponseBodyDecodedPos = mResponseBody.Length();
if (mEofDecoded) {
// Free memory buffer which we no longer need
mResponseBody.Truncate();
mResponseBodyDecodedPos = 0;
}
mResponseText.CreateSnapshot(aSnapshot);
}
nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
if (!aCx) {
return NS_ERROR_FAILURE;
}
nsAutoString string;
nsresult rv = GetResponseTextForJSON(string);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// The Unicode converter has already zapped the BOM if there was one
JS::Rooted<JS::Value> value(aCx);
if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
return NS_ERROR_FAILURE;
}
mResultJSON = value;
return NS_OK;
}
void XMLHttpRequestMainThread::SetResponseType(
XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
NOT_CALLABLE_IN_SYNC_SEND_RV
if (mState == XMLHttpRequest_Binding::LOADING ||
mState == XMLHttpRequest_Binding::DONE) {
aRv.ThrowInvalidStateError(
"Cannot set 'responseType' property on XMLHttpRequest after 'send()' "
"(when its state is LOADING or DONE).");
return;
}
// sync request is not allowed setting responseType in window context
if (HasOrHasHadOwnerWindow() && mState != XMLHttpRequest_Binding::UNSENT &&
mFlagSynchronous) {
LogMessage(
"ResponseTypeSyncXHRWarning", GetOwnerWindow());
aRv.ThrowInvalidAccessError(
"synchronous XMLHttpRequests do not support timeout and responseType");
return;
}
// Set the responseType attribute's value to the given value.
SetResponseTypeRaw(aResponseType);
}
void XMLHttpRequestMainThread::GetResponse(
JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
switch (mResponseType) {
case XMLHttpRequestResponseType::_empty:
case XMLHttpRequestResponseType::Text: {
DOMString str;
GetResponseText(str, aRv);
if (aRv.Failed()) {
return;
}
if (!xpc::StringToJsval(aCx, str, aResponse)) {
aRv.
Throw(NS_ERROR_OUT_OF_MEMORY);
}
return;
}
case XMLHttpRequestResponseType::Arraybuffer: {
if (mState != XMLHttpRequest_Binding::DONE) {
aResponse.setNull();
return;
}
if (!mResultArrayBuffer) {
mResultArrayBuffer = mArrayBufferBuilder->TakeArrayBuffer(aCx);
if (!mResultArrayBuffer) {
aRv.
Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
aResponse.setObject(*mResultArrayBuffer);
return;
}
case XMLHttpRequestResponseType::Blob: {
if (mState != XMLHttpRequest_Binding::DONE) {
aResponse.setNull();
return;
}
if (!mResponseBlobImpl) {
aResponse.setNull();
return;
}
if (!mResponseBlob) {
mResponseBlob = Blob::Create(GetOwnerGlobal(), mResponseBlobImpl);
}
if (!GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) {
aResponse.setNull();
}
return;
}
case XMLHttpRequestResponseType::Document: {
if (!mResponseXML || mState != XMLHttpRequest_Binding::DONE) {
aResponse.setNull();
return;
}
aRv =
nsContentUtils::WrapNative(aCx, ToSupports(mResponseXML), aResponse);
return;
}
case XMLHttpRequestResponseType::Json: {
if (mState != XMLHttpRequest_Binding::DONE) {
aResponse.setNull();
return;
}
if (mResultJSON.isUndefined()) {
aRv = CreateResponseParsedJSON(aCx);
TruncateResponseText();
if (aRv.Failed()) {
// Per spec, errors aren't propagated. null is returned instead.
aRv = NS_OK;
// It would be nice to log the error to the console. That's hard to
// do without calling window.onerror as a side effect, though.
JS_ClearPendingException(aCx);
mResultJSON.setNull();
}
}
aResponse.set(mResultJSON);
return;
}
default:
NS_ERROR(
"Should not happen");
}
aResponse.setNull();
}
already_AddRefed<BlobImpl> XMLHttpRequestMainThread::GetResponseBlobImpl() {
MOZ_DIAGNOSTIC_ASSERT(mForWorker);
MOZ_DIAGNOSTIC_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
if (mState != XMLHttpRequest_Binding::DONE) {
return nullptr;
}
RefPtr<BlobImpl> blobImpl = mResponseBlobImpl;
return blobImpl.forget();
}
already_AddRefed<ArrayBufferBuilder>
XMLHttpRequestMainThread::GetResponseArrayBufferBuilder() {
MOZ_DIAGNOSTIC_ASSERT(mForWorker);
MOZ_DIAGNOSTIC_ASSERT(mResponseType ==
XMLHttpRequestResponseType::Arraybuffer);
if (mState != XMLHttpRequest_Binding::DONE) {
return nullptr;
}
RefPtr<ArrayBufferBuilder> builder = mArrayBufferBuilder;
return builder.forget();
}
nsresult XMLHttpRequestMainThread::GetResponseTextForJSON(nsAString& aString) {
if (mState != XMLHttpRequest_Binding::DONE) {
aString.SetIsVoid(
true);
return NS_OK;
}
if (!mResponseText.GetAsString(aString)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest()
const {
if (!mChannel) {
return false;
}
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
return loadInfo->GetTainting() == LoadTainting::CORS;
}
bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
if (IsCrossSiteCORSRequest()) {
nsresult rv;
mChannel->GetStatus(&rv);
if (NS_FAILED(rv)) {
return true;
}
}
return false;
}
bool XMLHttpRequestMainThread::BadContentRangeRequested() {
if (!mChannel) {
return false;
}
// Only nsIBaseChannel supports this
nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
if (!baseChan) {
return false;
}
// A bad range was requested if the channel has no content range
// despite the request specifying a range header.
return !baseChan->ContentRange() && mAuthorRequestHeaders.Has(
"range");
}
RefPtr<mozilla::net::ContentRange>
XMLHttpRequestMainThread::GetRequestedContentRange()
const {
MOZ_ASSERT(mChannel);
nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
if (!baseChan) {
return nullptr;
}
return baseChan->ContentRange();
}
void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out)
const {
if (!IsBlobURI(mRequestURL)) {
out.SetIsVoid(
true);
return;
}
RefPtr<mozilla::net::ContentRange> range = GetRequestedContentRange();
if (range) {
range->AsHeader(out);
}
else {
out.SetIsVoid(
true);
}
}
void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
aUrl.Truncate();
if ((mState == XMLHttpRequest_Binding::UNSENT ||
mState == XMLHttpRequest_Binding::OPENED) ||
!mChannel) {
return;
}
// Make sure we don't leak responseURL information from denied cross-site
// requests.
if (IsDeniedCrossSiteCORSRequest()) {
return;
}
nsCOMPtr<nsIURI> responseUrl;
if (NS_FAILED(NS_GetFinalChannelURI(mChannel, getter_AddRefs(responseUrl)))) {
return;
}
nsAutoCString temp;
responseUrl->GetSpecIgnoringRef(temp);
CopyUTF8toUTF16(temp, aUrl);
}
uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
// Make sure we don't leak status information from denied cross-site
// requests.
if (IsDeniedCrossSiteCORSRequest()) {
return 0;
}
if (mState == XMLHttpRequest_Binding::UNSENT ||
mState == XMLHttpRequest_Binding::OPENED) {
return 0;
}
if (mErrorLoad != ErrorType::eOK) {
// Let's simulate the http protocol for jar/app requests:
nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
if (jarChannel) {
nsresult status;
mChannel->GetStatus(&status);
if (status == NS_ERROR_FILE_NOT_FOUND) {
return 404;
// Not Found
}
else {
return 500;
// Internal Error
}
}
return 0;
}
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (!httpChannel) {
// Pretend like we got a 200/206 response, since our load was successful
return GetRequestedContentRange() ? 206 : 200;
}
uint32_t status;
nsresult rv = httpChannel->GetResponseStatus(&status);
if (NS_FAILED(rv)) {
status = 0;
}
return status;
}
void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
ErrorResult& aRv) {
// Return an empty status text on all error loads.
aStatusText.Truncate();
// Make sure we don't leak status information from denied cross-site
// requests.
if (IsDeniedCrossSiteCORSRequest()) {
return;
}
// Check the current XHR state to see if it is valid to obtain the statusText
// value. This check is to prevent the status text for redirects from being
// available before all the redirects have been followed and HTTP headers have
// been received.
if (mState == XMLHttpRequest_Binding::UNSENT ||
mState == XMLHttpRequest_Binding::OPENED) {
return;
}
if (mErrorLoad != ErrorType::eOK) {
return;
}
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (httpChannel) {
Unused << httpChannel->GetResponseStatusText(aStatusText);
}
else {
aStatusText.AssignLiteral(
"OK");
}
}
void XMLHttpRequestMainThread::TerminateOngoingFetch(nsresult detail) {
DEBUG_WORKERREFS;
if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
mState == XMLHttpRequest_Binding::LOADING) {
MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info,
(
"%p TerminateOngoingFetch(0x%" PRIx32
")",
this,
static_cast<uint32_t>(detail)));
CloseRequest(detail);
}
}
void XMLHttpRequestMainThread::CloseRequest(nsresult detail) {
DEBUG_WORKERREFS;
mWaitingForOnStopRequest =
false;
mErrorLoad = ErrorType::eTerminated;
mErrorLoadDetail = detail;
if (mChannel) {
mChannel->CancelWithReason(NS_BINDING_ABORTED,
"XMLHttpRequestMainThread::CloseRequest"_ns);
}
CancelTimeoutTimer();
}
void XMLHttpRequestMainThread::CloseRequestWithError(
const ErrorProgressEventType& aType) {
DEBUG_WORKERREFS;
MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
(
"%p CloseRequestWithError(%s)",
this, aType.cStr));
CloseRequest(aType.errorCode);
ResetResponse();
// If we're in the destructor, don't risk dispatching an event.
if (mFlagDeleted) {
mFlagSyncLooping =
false;
return;
}
if (mState != XMLHttpRequest_Binding::UNSENT &&
!(mState == XMLHttpRequest_Binding::OPENED && !mFlagSend) &&
mState != XMLHttpRequest_Binding::DONE) {
ChangeState(XMLHttpRequest_Binding::DONE,
true);
if (!mFlagSyncLooping) {
if (mUpload && !mUploadComplete) {
mUploadComplete =
true;
DispatchProgressEvent(mUpload, aType, 0, -1);
}
DispatchProgressEvent(
this, aType, 0, -1);
}
}
// The ChangeState call above calls onreadystatechange handlers which
// if they load a new url will cause XMLHttpRequestMainThread::Open to clear
// the abort state bit. If this occurs we're not uninitialized (bug 361773).
if (mFlagAborted) {
ChangeState(XMLHttpRequest_Binding::UNSENT,
false);
// IE seems to do it
}
mFlagSyncLooping =
false;
}
void XMLHttpRequestMainThread::RequestErrorSteps(
const ProgressEventType aEventType,
const nsresult aOptionalException,
ErrorResult& aRv) {
MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
(
"%p RequestErrorSteps(%s,0x%" PRIx32
")",
this, aEventType.cStr,
static_cast<uint32_t>(aOptionalException)));
// Cancel our timers first before setting our state to done, so we don't
// trip any assertions if one fires and asserts that state != done.
CancelTimeoutTimer();
CancelSyncTimeoutTimer();
StopProgressEventTimer();
// Step 1
mState = XMLHttpRequest_Binding::DONE;
// Step 2
mFlagSend =
false;
// Step 3
ResetResponse();
// If we're in the destructor, don't risk dispatching an event.
if (mFlagDeleted) {
mFlagSyncLooping =
false;
return;
}
// Step 4
if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
aRv.
Throw(aOptionalException);
return;
}
// Step 5
FireReadystatechangeEvent();
// Step 6
if (mUpload && !mUploadComplete) {
// Step 6-1
mUploadComplete =
true;
// Step 6-2
if (mFlagHadUploadListenersOnSend) {
// Steps 6-3, 6-4 (loadend is fired for us)
DispatchProgressEvent(mUpload, aEventType, 0, -1);
}
}
// Steps 7 and 8 (loadend is fired for us)
DispatchProgressEvent(
this, aEventType, 0, -1);
}
void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
NOT_CALLABLE_IN_SYNC_SEND_RV
MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, (
"%p Abort()",
this));
AbortInternal(aRv);
}
void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, (
"%p AbortInternal()",
this));
mFlagAborted =
true;
DisconnectDoneNotifier();
// Step 1
TerminateOngoingFetch(NS_ERROR_DOM_ABORT_ERR);
// Step 2
if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
mState == XMLHttpRequest_Binding::LOADING) {
RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, aRv);
}
// Step 3
if (mState == XMLHttpRequest_Binding::DONE) {
ChangeState(XMLHttpRequest_Binding::UNSENT,
false);
// no ReadystateChange event
}
mFlagSyncLooping =
false;
}
/*Method that checks if it is safe to expose a header value to the client.
It is used to check what headers are exposed for CORS requests.*/
bool XMLHttpRequestMainThread::IsSafeHeader(
const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel)
const {
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
NS_WARNING(
"blocked access to response header");
return false;
}
// if this is not a CORS call all headers are safe
if (!IsCrossSiteCORSRequest()) {
return true;
}
// Check for dangerous headers
// Make sure we don't leak header information from denied cross-site
// requests.
if (mChannel) {
nsresult status;
mChannel->GetStatus(&status);
if (NS_FAILED(status)) {
return false;
}
}
const char* kCrossOriginSafeHeaders[] = {
"cache-control",
"content-language",
"content-type",
"content-length",
"expires",
"last-modified",
"pragma"};
for (uint32_t i = 0; i < std::size(kCrossOriginSafeHeaders); ++i) {
if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
return true;
}
}
nsAutoCString headerVal;
// The "Access-Control-Expose-Headers" header contains a comma separated
// list of method names.
Unused << aHttpChannel->GetResponseHeader(
"Access-Control-Expose-Headers"_ns,
headerVal);
bool isSafe =
false;
for (
const nsACString& token :
nsCCharSeparatedTokenizer(headerVal,
',').ToRange()) {
if (token.IsEmpty()) {
continue;
}
if (!NS_IsValidHTTPToken(token)) {
return false;
}
if (token.EqualsLiteral(
"*") && !mFlagACwithCredentials) {
isSafe =
true;
}
else if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator)) {
isSafe =
true;
}
}
return isSafe;
}
bool XMLHttpRequestMainThread::GetContentType(nsACString& aValue)
const {
MOZ_ASSERT(mChannel);
nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
if (baseChan) {
RefPtr<CMimeType> fullMimeType(baseChan->FullMimeType());
if (fullMimeType) {
fullMimeType->Serialize(aValue);
return true;
}
}
if (NS_SUCCEEDED(mChannel->GetContentType(aValue))) {
nsCString value;
if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
aValue.AppendLiteral(
";charset=");
aValue.Append(value);
}
return true;
}
return false;
}
void XMLHttpRequestMainThread::GetAllResponseHeaders(
nsACString& aResponseHeaders, ErrorResult& aRv) {
NOT_CALLABLE_IN_SYNC_SEND_RV
aResponseHeaders.Truncate();
// If the state is UNSENT or OPENED,
// return the empty string and terminate these steps.
if (mState == XMLHttpRequest_Binding::UNSENT ||
mState == XMLHttpRequest_Binding::OPENED) {
return;
}
if (mErrorLoad != ErrorType::eOK) {
return;
}
if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
RefPtr<nsHeaderVisitor> visitor =
new nsHeaderVisitor(*
this, WrapNotNull(httpChannel));
if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
aResponseHeaders = visitor->Headers();
}
return;
}
if (!mChannel) {
return;
}
// Even non-http channels supply content type.
nsAutoCString value;
if (GetContentType(value)) {
aResponseHeaders.AppendLiteral(
"Content-Type: ");
aResponseHeaders.Append(value);
aResponseHeaders.AppendLiteral(
"\r\n");
}
// Don't provide Content-Length for data URIs
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
!uri->SchemeIs(
"data")) {
int64_t length;
if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
aResponseHeaders.AppendLiteral(
"Content-Length: ");
aResponseHeaders.AppendInt(length);
aResponseHeaders.AppendLiteral(
"\r\n");
}
}
// Should set a Content-Range header for blob scheme.
// From https://fetch.spec.whatwg.org/#scheme-fetch 3.blob.9.20:
// "Set response’s header list to «(`Content-Length`, serializedSlicedLength),
// (`Content-Type`, type), (`Content-Range`, contentRange)»."
GetContentRangeHeader(value);
if (!value.IsVoid()) {
aResponseHeaders.AppendLiteral(
"Content-Range: ");
aResponseHeaders.Append(value);
aResponseHeaders.AppendLiteral(
"\r\n");
}
}
void XMLHttpRequestMainThread::GetResponseHeader(
const nsACString& header,
nsACString& _retval,
ErrorResult& aRv) {
NOT_CALLABLE_IN_SYNC_SEND_RV
_retval.SetIsVoid(
true);
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (!httpChannel) {
// If the state is UNSENT or OPENED,
// return null and terminate these steps.
if (mState == XMLHttpRequest_Binding::UNSENT ||
mState == XMLHttpRequest_Binding::OPENED) {
return;
}
// Even non-http channels supply content type and content length.
// Remember we don't leak header information from denied cross-site
// requests. However, we handle file: and blob: URLs for blob response
// types by canceling them with a specific error, so we have to allow
// them to pass through this check.
nsresult status;
if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
(NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
return;
}
// Content Type:
if (header.LowerCaseEqualsASCII(
"content-type")) {
if (!GetContentType(_retval)) {
// Means no content type
_retval.SetIsVoid(
true);
return;
}
}
// Content Length:
else if (header.LowerCaseEqualsASCII(
"content-length")) {
int64_t length;
if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
_retval.AppendInt(length);
}
}
// Content Range:
else if (header.LowerCaseEqualsASCII(
"content-range")) {
GetContentRangeHeader(_retval);
}
return;
}
// Check for dangerous headers
if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
return;
}
aRv = httpChannel->GetResponseHeader(header, _retval);
if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
// Means no header
_retval.SetIsVoid(
true);
aRv.SuppressException();
}
}
already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup()
const {
if (mFlagBackgroundRequest) {
return nullptr;
}
if (mLoadGroup) {
nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
return ref.forget();
}
Document* doc = GetDocumentIfCurrent();
if (doc) {
return doc->GetDocumentLoadGroup();
}
return nullptr;
}
nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
MOZ_ASSERT(mState != XMLHttpRequest_Binding::UNSENT);
RefPtr<Event> event = NS_NewDOMEvent(
this, nullptr, nullptr);
event->InitEvent(kLiteralString_readystatechange,
false,
false);
// We assume anyone who managed to call CreateReadystatechangeEvent is trusted
event->SetTrusted(
true);
DispatchOrStoreEvent(
this, event);
return NS_OK;
}
void XMLHttpRequestMainThread::DispatchProgressEvent(
DOMEventTargetHelper* aTarget,
const ProgressEventType& aType,
int64_t aLoaded, int64_t aTotal) {
DEBUG_WORKERREFS;
NS_ASSERTION(aTarget,
"null target");
if (NS_FAILED(CheckCurrentGlobalCorrectness()) ||
(!AllowUploadProgress() && aTarget == mUpload)) {
return;
}
// If blocked by CORS, zero-out the stats on progress events
// and never fire "progress" or "load" events at all.
if (IsDeniedCrossSiteCORSRequest()) {
if (aType == Events::progress || aType == Events::load) {
return;
}
aLoaded = 0;
aTotal = -1;
}
ProgressEventInit init;
init.mBubbles =
false;
init.mCancelable =
false;
init.mLengthComputable = aTotal != -1;
// XHR spec step 6.1
init.mLoaded = aLoaded;
init.mTotal = (aTotal == -1) ? 0 : aTotal;
RefPtr<ProgressEvent> event =
ProgressEvent::Constructor(aTarget, aType, init);
event->SetTrusted(
true);
MOZ_LOG(
gXMLHttpRequestLog, LogLevel::Debug,
(
"firing %s event (%u,%u,%" PRIu64
",%" PRIu64
")", aType.cStr,
aTarget == mUpload, aTotal != -1, aLoaded, (aTotal == -1) ? 0 : aTotal));
DispatchOrStoreEvent(aTarget, event);
// If we're sending a load, error, timeout or abort event, then
// also dispatch the subsequent loadend event.
if (aType == Events::load || aType == Events::error ||
aType == Events::timeout || aType == Events::abort) {
DispatchProgressEvent(aTarget, Events::loadend, aLoaded, aTotal);
}
}
void XMLHttpRequestMainThread::DispatchOrStoreEvent(
DOMEventTargetHelper* aTarget, Event* aEvent) {
DEBUG_WORKERREFS;
MOZ_ASSERT(aTarget);
MOZ_ASSERT(aEvent);
if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
return;
}
if (mEventDispatchingSuspended) {
PendingEvent* event = mPendingEvents.AppendElement();
event->mTarget = aTarget;
event->mEvent = aEvent;
return;
}
aTarget->DispatchEvent(*aEvent);
}
void XMLHttpRequestMainThread::SuspendEventDispatching() {
MOZ_ASSERT(!mEventDispatchingSuspended);
mEventDispatchingSuspended =
true;
}
void XMLHttpRequestMainThread::ResumeEventDispatching() {
MOZ_ASSERT(mEventDispatchingSuspended);
mEventDispatchingSuspended =
false;
nsTArray<PendingEvent> pendingEvents = std::move(mPendingEvents);
if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
return;
}
for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
pendingEvents[i].mTarget->DispatchEvent(*pendingEvents[i].mEvent);
}
}
already_AddRefed<nsIHttpChannel>
XMLHttpRequestMainThread::GetCurrentHttpChannel() {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
return httpChannel.forget();
}
already_AddRefed<nsIJARChannel>
XMLHttpRequestMainThread::GetCurrentJARChannel() {
nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
return appChannel.forget();
}
bool XMLHttpRequestMainThread::IsSystemXHR()
const {
return mIsSystem || mPrincipal->IsSystemPrincipal();
}
bool XMLHttpRequestMainThread::InUploadPhase()
const {
// We're in the upload phase while our state is OPENED.
return mState == XMLHttpRequest_Binding::OPENED;
}
// This case is hit when the async parameter is outright omitted, which
// should set it to true (and the username and password to null).
void XMLHttpRequestMainThread::Open(
const nsACString& aMethod,
const nsAString& aUrl, ErrorResult& aRv) {
Open(aMethod, aUrl,
true, VoidString(), VoidString(), aRv);
}
// This case is hit when the async parameter is specified, even if the
// JS value was "undefined" (which due to legacy reasons should be
// treated as true, which is how it will already be passed in here).
void XMLHttpRequestMainThread::Open(
const nsACString& aMethod,
const nsAString& aUrl,
bool aAsync,
const nsAString& aUsername,
const nsAString& aPassword,
ErrorResult& aRv) {
Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword, aRv);
}
void XMLHttpRequestMainThread::Open(
const nsACString& aMethod,
const nsACString& aUrl,
bool aAsync,
const nsAString& aUsername,
const nsAString& aPassword,
ErrorResult& aRv) {
DEBUG_WORKERREFS1(aMethod <<
" " << aUrl);
NOT_CALLABLE_IN_SYNC_SEND_RV
// Gecko-specific
if (!aAsync && !DontWarnAboutSyncXHR() && GetOwnerWindow() &&
GetOwnerWindow()->GetExtantDoc()) {
GetOwnerWindow()->GetExtantDoc()->WarnOnceAbout(
DeprecatedOperations::eSyncXMLHttpRequestDeprecated);
}
Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
aAsync ? 0 : 1);
// Step 1
nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
if (!responsibleDocument) {
// This could be because we're no longer current or because we're in some
// non-window context...
if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
aRv.
Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
return;
}
}
if (!mPrincipal) {
aRv.
Throw(NS_ERROR_NOT_INITIALIZED);
return;
}
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
aRv.
Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
return;
}
// Gecko-specific
if (!aAsync && responsibleDocument && GetOwnerWindow()) {
// We have no extant document during unload, so the above general
// syncXHR warning will not display. But we do want to display a
// recommendation to use sendBeacon instead of syncXHR during unload.
nsCOMPtr<nsIDocShell> shell = responsibleDocument->GetDocShell();
if (shell) {
bool inUnload =
false;
shell->GetIsInUnload(&inUnload);
if (inUnload) {
LogMessage(
"UseSendBeaconDuringUnloadAndPagehideWarning",
GetOwnerWindow());
}
}
}
// Steps 2-4
nsAutoCString method;
aRv = FetchUtil::GetValidRequestMethod(aMethod, method);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
// Steps 5-6
nsIURI* baseURI = nullptr;
if (mBaseURI) {
baseURI = mBaseURI;
}
else if (responsibleDocument) {
baseURI = responsibleDocument->GetBaseURI();
}
// Use the responsible document's encoding for the URL if we have one,
// except for dedicated workers. Use UTF-8 otherwise.
NotNull<
const Encoding*> originCharset = UTF_8_ENCODING;
if (responsibleDocument &&
responsibleDocument->NodePrincipal() == mPrincipal) {
originCharset = responsibleDocument->GetDocumentCharacterSet();
}
nsCOMPtr<nsIURI> parsedURL;
nsresult rv =
NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
if (NS_FAILED(rv)) {
aRv.ThrowSyntaxError(
"'"_ns + aUrl +
"' is not a valid URL."_ns);
return;
}
if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
aRv.
Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
return;
}
// Step 7
// This is already handled by the other Open() method, which passes
// username and password in as NullStrings.
// Step 8
nsAutoCString host;
parsedURL->GetHost(host);
if (!host.IsEmpty() && (!aUsername.IsVoid() || !aPassword.IsVoid())) {
auto mutator = NS_MutateURI(parsedURL);
if (!aUsername.IsVoid()) {
mutator.SetUsername(NS_ConvertUTF16toUTF8(aUsername));
}
if (!aPassword.IsVoid()) {
mutator.SetPassword(NS_ConvertUTF16toUTF8(aPassword));
}
Unused << mutator.Finalize(parsedURL);
}
// Step 9
if (!aAsync && HasOrHasHadOwnerWindow() &&
(mTimeoutMilliseconds ||
mResponseType != XMLHttpRequestResponseType::_empty)) {
if (mTimeoutMilliseconds) {
LogMessage(
"TimeoutSyncXHRWarning", GetOwnerWindow());
}
if (mResponseType != XMLHttpRequestResponseType::_empty) {
LogMessage(
"ResponseTypeSyncXHRWarning", GetOwnerWindow());
}
aRv.ThrowInvalidAccessError(
"synchronous XMLHttpRequests do not support timeout and responseType");
return;
}
// Step 10
TerminateOngoingFetch(NS_OK);
// Step 11
// timeouts are handled without a flag
DisconnectDoneNotifier();
mFlagSend =
false;
mRequestMethod.Assign(method);
mRequestURL = parsedURL;
mFlagSynchronous = !aAsync;
mAuthorRequestHeaders.Clear();
ResetResponse();
// Gecko-specific
mFlagHadUploadListenersOnSend =
false;
mFlagAborted =
false;
mFlagTimedOut =
false;
mDecoder = nullptr;
// Per spec we should only create the channel on send(), but we have internal
// code that relies on the channel being created now, and that code is not
// always IsSystemXHR(). However, we're not supposed to throw channel-creation
// errors during open(), so we silently ignore those here.
CreateChannel();
// Step 12
if (mState != XMLHttpRequest_Binding::OPENED) {
mState = XMLHttpRequest_Binding::OPENED;
FireReadystatechangeEvent();
}
}
void XMLHttpRequestMainThread::SetOriginAttributes(
const OriginAttributesDictionary& aAttrs) {
MOZ_ASSERT((mState == XMLHttpRequest_Binding::OPENED) && !mFlagSend);
OriginAttributes attrs(aAttrs);
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
loadInfo->SetOriginAttributes(attrs);
}
/*
* "Copy" from a stream.
*/
nsresult XMLHttpRequestMainThread::StreamReaderFunc(
nsIInputStream* in,
void* closure,
const char* fromRawSegment,
uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
XMLHttpRequestMainThread* xmlHttpRequest =
static_cast<XMLHttpRequestMainThread*>(closure);
if (!xmlHttpRequest || !writeCount) {
NS_WARNING(
"XMLHttpRequest cannot read from stream: no closure or writeCount");
return NS_ERROR_FAILURE;
}
nsresult rv = NS_OK;
if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
xmlHttpRequest->MaybeCreateBlobStorage();
rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
}
else if (xmlHttpRequest->mResponseType ==
XMLHttpRequestResponseType::Arraybuffer &&
!xmlHttpRequest->mIsMappedArrayBuffer) {
// get the initial capacity to something reasonable to avoid a bunch of
// reallocs right at the start
if (xmlHttpRequest->mArrayBufferBuilder->Capacity() == 0)
xmlHttpRequest->mArrayBufferBuilder->SetCapacity(
std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder->Append(
reinterpret_cast<
const uint8_t*>(fromRawSegment), count,
XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
else if (xmlHttpRequest->mResponseType ==
XMLHttpRequestResponseType::_empty &&
xmlHttpRequest->mResponseXML) {
// Copy for our own use
if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
else if (xmlHttpRequest->mResponseType ==
XMLHttpRequestResponseType::_empty ||
xmlHttpRequest->mResponseType ==
XMLHttpRequestResponseType::Text ||
xmlHttpRequest->mResponseType ==
XMLHttpRequestResponseType::Json) {
MOZ_ASSERT(!xmlHttpRequest->mResponseXML,
"We shouldn't be parsing a doc here");
rv = xmlHttpRequest->AppendToResponseText(
AsBytes(Span(fromRawSegment, count)));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
if (xmlHttpRequest->mFlagParseBody) {
// Give the same data to the parser.
// We need to wrap the data in a new lightweight stream and pass that
// to the parser, because calling ReadSegments() recursively on the same
// stream is not supported.
nsCOMPtr<nsIInputStream> copyStream;
rv = NS_NewByteInputStream(getter_AddRefs(copyStream),
Span(fromRawSegment, count),
NS_ASSIGNMENT_DEPEND);
if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
NS_ASSERTION(copyStream,
"NS_NewByteInputStream lied");
nsresult parsingResult =
xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
xmlHttpRequest->mChannel, copyStream, toOffset, count);
// No use to continue parsing if we failed here, but we
// should still finish reading the stream
if (NS_FAILED(parsingResult)) {
xmlHttpRequest->mFlagParseBody =
false;
}
}
}
if (NS_SUCCEEDED(rv)) {
*writeCount = count;
}
else {
*writeCount = 0;
}
return rv;
}
namespace {
void GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI) {
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aURI);
*aURI = nullptr;
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
if (!channel) {
return;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = channel->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return;
}
if (!dom::IsBlobURI(uri)) {
return;
}
uri.forget(aURI);
}
nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aFile);
*aFile = nullptr;
nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
if (!fc) {
return NS_OK;
}
nsCOMPtr<nsIFile> file;
nsresult rv = fc->GetFile(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
file.forget(aFile);
return NS_OK;
}
nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream,
void* aClosure,
const char* aFromRawSegment, uint32_t aToOffset,
uint32_t aCount, uint32_t* aWriteCount) {
*aWriteCount = aCount;
return NS_OK;
}
class FileCreationHandler final :
public PromiseNativeHandler {
public:
NS_DECL_ISUPPORTS
static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
MOZ_ASSERT(aPromise);
RefPtr<FileCreationHandler> handler =
new FileCreationHandler(aXHR);
aPromise->AppendNativeHandler(handler);
}
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override {
if (NS_WARN_IF(!aValue.isObject())) {
mXHR->LocalFileToBlobCompleted(nullptr);
return;
}
RefPtr<Blob> blob;
if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
mXHR->LocalFileToBlobCompleted(nullptr);
return;
}
mXHR->LocalFileToBlobCompleted(blob->Impl());
}
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override {
mXHR->LocalFileToBlobCompleted(nullptr);
}
private:
explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
MOZ_ASSERT(aXHR);
}
~FileCreationHandler() =
default;
RefPtr<XMLHttpRequestMainThread> mXHR;
};
NS_IMPL_ISUPPORTS0(FileCreationHandler)
}
// namespace
void XMLHttpRequestMainThread::LocalFileToBlobCompleted(BlobImpl* aBlobImpl) {
MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
mResponseBlobImpl = aBlobImpl;
mBlobStorage = nullptr;
NS_ASSERTION(mResponseBody.IsEmpty(),
"mResponseBody should be empty");
ChangeStateToDone(mFlagSyncLooping);
}
NS_IMETHODIMP
XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
nsIInputStream* inStr,
uint64_t sourceOffset,
uint32_t count) {
DEBUG_WORKERREFS;
NS_ENSURE_ARG_POINTER(inStr);
mProgressSinceLastProgressEvent =
true;
XMLHttpRequest_Binding::ClearCachedResponseTextValue(
this);
nsresult rv;
if (mResponseType == XMLHttpRequestResponseType::Blob) {
nsCOMPtr<nsIFile> localFile;
nsCOMPtr<nsIURI> blobURI;
GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
if (blobURI) {
RefPtr<BlobImpl> blobImpl;
rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
if (NS_SUCCEEDED(rv)) {
mResponseBlobImpl = blobImpl;
}
}
else {
rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (mResponseBlobImpl || localFile) {
mBlobStorage = nullptr;
NS_ASSERTION(mResponseBody.IsEmpty(),
"mResponseBody should be empty");
// The nsIStreamListener contract mandates us to read from the stream
// before returning.
uint32_t totalRead;
rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
&totalRead);
NS_ENSURE_SUCCESS(rv, rv);
ChangeState(XMLHttpRequest_Binding::LOADING);
// Cancel() must be called with an error. We use
// NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
// just because we can retrieve the File from the channel directly.
return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
}
}
uint32_t totalRead;
rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
(
void*)
this, count, &totalRead);
NS_ENSURE_SUCCESS(rv, rv);
// Fire the first progress event/loading state change
if (mState == XMLHttpRequest_Binding::HEADERS_RECEIVED) {
ChangeState(XMLHttpRequest_Binding::LOADING);
if (!mFlagSynchronous) {
DispatchProgressEvent(
this, Events::progress, mLoadTransferred,
mLoadTotal);
}
mProgressSinceLastProgressEvent =
false;
}
if (!mFlagSynchronous && !mProgressTimerIsActive) {
StartProgressEventTimer();
}
return NS_OK;
}
NS_IMETHODIMP
XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
DEBUG_WORKERREFS;
AUTO_PROFILER_LABEL(
"XMLHttpRequestMainThread::OnStartRequest", NETWORK);
nsresult rv = NS_OK;
if (request != mChannel) {
// Can this still happen?
return NS_OK;
}
// Don't do anything if we have been aborted
if (mState == XMLHttpRequest_Binding::UNSENT) {
return NS_OK;
}
// Don't do anything if we're in mid-abort, but let the request
// know (this can happen due to race conditions in valid XHRs,
// see bz1070763 for info).
if (mFlagAborted) {
return NS_BINDING_ABORTED;
}
// Don't do anything if we have timed out.
if (mFlagTimedOut) {
return NS_OK;
}
// If we were asked for a bad range on a blob URL, but we're async,
// we should throw now in order to fire an error progress event.
if (BadContentRangeRequested()) {
return NS_ERROR_NET_PARTIAL_TRANSFER;
}
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
nsresult status;
request->GetStatus(&status);
if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
mErrorLoad = ErrorType::eRequest;
mErrorLoadDetail = status;
}
// Upload phase is now over. If we were uploading anything,
// stop the timer and fire any final progress events.
if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
!mFlagSynchronous) {
StopProgressEventTimer();
mUploadTransferred = mUploadTotal;
if (mProgressSinceLastProgressEvent) {
DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
mUploadTotal);
mProgressSinceLastProgressEvent =
false;
}
mUploadComplete =
true;
DispatchProgressEvent(mUpload, Events::load, mUploadTotal, mUploadTotal);
}
mFlagParseBody =
true;
if (mErrorLoad == ErrorType::eOK) {
ChangeState(XMLHttpRequest_Binding::HEADERS_RECEIVED);
}
ResetResponse();
if (!mOverrideMimeType.IsEmpty()) {
channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
}
// Fallback to 'application/octet-stream' (leaving data URLs alone)
if (!IsBlobURI(mRequestURL)) {
nsAutoCString type;
channel->GetContentType(type);
if (type.IsEmpty() || type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
}
}
DetectCharset();
// Set up arraybuffer
if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
NS_SUCCEEDED(status)) {
if (mIsMappedArrayBuffer) {
nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
if (jarChannel) {
nsCOMPtr<nsIURI> uri;
rv = channel->GetURI(getter_AddRefs(uri));
if (NS_SUCCEEDED(rv)) {
nsAutoCString file;
nsAutoCString scheme;
uri->GetScheme(scheme);
if (scheme.LowerCaseEqualsLiteral(
"jar")) {
nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
if (jarURI) {
jarURI->GetJAREntry(file);
}
}
nsCOMPtr<nsIFile> jarFile;
jarChannel->GetJarFile(getter_AddRefs(jarFile));
if (!jarFile) {
mIsMappedArrayBuffer =
false;
}
else {
rv = mArrayBufferBuilder->MapToFileInPackage(file, jarFile);
// This can happen legitimately if there are compressed files
// in the jarFile. See bug #1357219. No need to warn on the error.
if (NS_FAILED(rv)) {
mIsMappedArrayBuffer =
false;
}
else {
channel->SetContentType(
"application/mem-mapped"_ns);
}
}
}
}
}
// If memory mapping failed, mIsMappedArrayBuffer would be set to false,
// and we want it fallback to the malloc way.
if (!mIsMappedArrayBuffer) {
int64_t contentLength;
rv = channel->GetContentLength(&contentLength);
if (NS_SUCCEEDED(rv) && contentLength > 0 &&
contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
mArrayBufferBuilder->SetCapacity(
static_cast<int32_t>(contentLength));
}
}
}
// Set up responseXML
// Fetch spec Main Fetch step 21: ignore body for head/connect methods.
bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
mResponseType == XMLHttpRequestResponseType::Document) &&
!(mRequestMethod.EqualsLiteral(
"HEAD") ||
mRequestMethod.EqualsLiteral(
"CONNECT"));
if (parseBody) {
// Do not try to parse documents if content-length = 0
int64_t contentLength;
if (NS_SUCCEEDED(mChannel->GetContentLength(&contentLength)) &&
contentLength == 0) {
parseBody =
false;
}
}
mIsHtml =
false;
mWarnAboutSyncHtml =
false;
if (parseBody && NS_SUCCEEDED(status)) {
// We can gain a huge performance win by not even trying to
// parse non-XML data. This also protects us from the situation
// where we have an XML document and sink, but HTML (or other)
// parser, which can produce unreliable results.
--> --------------------
--> maximum size reached
--> --------------------