forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			4152 lines
		
	
	
	
		
			129 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			4152 lines
		
	
	
	
		
			129 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/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/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/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/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 "nsIJARChannel.h"
 | |
| #include "nsIJARURI.h"
 | |
| #include "nsLayoutCID.h"
 | |
| #include "nsReadableUtils.h"
 | |
| #include "nsSandboxFlags.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 "nsStringBuffer.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 "nsIClassOfService.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
 | |
| 
 | |
| using namespace mozilla::net;
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace dom {
 | |
| 
 | |
| // 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;
 | |
| 
 | |
| namespace {
 | |
| const nsLiteralString ProgressEventTypeStrings[] = {
 | |
|     u"loadstart"_ns, u"progress"_ns, u"error"_ns,  u"abort"_ns,
 | |
|     u"timeout"_ns,   u"load"_ns,     u"loadend"_ns};
 | |
| static_assert(MOZ_ARRAY_LENGTH(ProgressEventTypeStrings) ==
 | |
|                   size_t(XMLHttpRequestMainThread::ProgressEventType::ENUM_MAX),
 | |
|               "Mismatched lengths for ProgressEventTypeStrings and "
 | |
|               "ProgressEventType enums");
 | |
| 
 | |
| const nsString kLiteralString_readystatechange = u"readystatechange"_ns;
 | |
| const nsString kLiteralString_xmlhttprequest = u"xmlhttprequest"_ns;
 | |
| const nsString kLiteralString_DOMContentLoaded = u"DOMContentLoaded"_ns;
 | |
| const nsCString kLiteralString_charset = "charset"_ns;
 | |
| const nsCString kLiteralString_UTF_8 = "UTF-8"_ns;
 | |
| }  // namespace
 | |
| 
 | |
| #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;                                                        \
 | |
|   }
 | |
| 
 | |
| /////////////////////////////////////////////
 | |
| //
 | |
| //
 | |
| /////////////////////////////////////////////
 | |
| 
 | |
| bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
 | |
| 
 | |
| XMLHttpRequestMainThread::XMLHttpRequestMainThread(
 | |
|     nsIGlobalObject* aGlobalObject)
 | |
|     : XMLHttpRequest(aGlobalObject),
 | |
|       mResponseBodyDecodedPos(0),
 | |
|       mResponseType(XMLHttpRequestResponseType::_empty),
 | |
|       mRequestObserver(nullptr),
 | |
|       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),
 | |
|       mErrorParsingXML(false),
 | |
|       mWaitingForOnStopRequest(false),
 | |
|       mProgressTimerIsActive(false),
 | |
|       mIsHtml(false),
 | |
|       mWarnAboutSyncHtml(false),
 | |
|       mLoadTotal(-1),
 | |
|       mLoadTransferred(0),
 | |
|       mIsSystem(false),
 | |
|       mIsAnon(false),
 | |
|       mFirstStartRequestSeen(false),
 | |
|       mInLoadProgressEvent(false),
 | |
|       mResultJSON(JS::UndefinedValue()),
 | |
|       mArrayBufferBuilder(new ArrayBufferBuilder()),
 | |
|       mResultArrayBuffer(nullptr),
 | |
|       mIsMappedArrayBuffer(false),
 | |
|       mXPCOMifier(nullptr),
 | |
|       mEventDispatchingSuspended(false),
 | |
|       mEofDecoded(false),
 | |
|       mDelayedDoneNotifier(nullptr) {
 | |
|   mozilla::HoldJSObjects(this);
 | |
| }
 | |
| 
 | |
| XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
 | |
|   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 */) {
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
|   mPrincipal = aPrincipal;
 | |
|   mBaseURI = aBaseURI;
 | |
|   mLoadGroup = aLoadGroup;
 | |
|   mCookieJarSettings = aCookieJarSettings;
 | |
|   mForWorker = aForWorker;
 | |
|   mPerformanceStorage = aPerformanceStorage;
 | |
|   mCSPEventListener = aCSPEventListener;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
 | |
|   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;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetRequestObserver(
 | |
|     nsIRequestObserver* aObserver) {
 | |
|   mRequestObserver = aObserver;
 | |
| }
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_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();
 | |
|   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", GetOwner());
 | |
|   }
 | |
|   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() {
 | |
|   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", GetOwner());
 | |
|     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;
 | |
|     bool hadErrors;
 | |
|     Tie(result, read, written, hadErrors) =
 | |
|         mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
 | |
|     MOZ_ASSERT(result == kInputEmpty);
 | |
|     MOZ_ASSERT(read == aBuffer.Length());
 | |
|     len += written;
 | |
|     MOZ_ASSERT(len <= destBufferLen.value());
 | |
|     Unused << hadErrors;
 | |
|     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 (HasOrHasHadOwner() && mState != XMLHttpRequest_Binding::UNSENT &&
 | |
|       mFlagSynchronous) {
 | |
|     LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
 | |
|     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;
 | |
| }
 | |
| 
 | |
| 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 response, since our load was successful
 | |
|     return 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() {
 | |
|   if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
 | |
|       mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
 | |
|       mState == XMLHttpRequest_Binding::LOADING) {
 | |
|     CloseRequest();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::CloseRequest() {
 | |
|   mWaitingForOnStopRequest = false;
 | |
|   mErrorLoad = ErrorType::eTerminated;
 | |
|   if (mChannel) {
 | |
|     mChannel->Cancel(NS_BINDING_ABORTED);
 | |
|   }
 | |
|   if (mTimeoutTimer) {
 | |
|     mTimeoutTimer->Cancel();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::CloseRequestWithError(
 | |
|     const ProgressEventType aType) {
 | |
|   CloseRequest();
 | |
| 
 | |
|   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) {
 | |
|   // Step 1
 | |
|   mState = XMLHttpRequest_Binding::DONE;
 | |
| 
 | |
|   StopProgressEventTimer();
 | |
| 
 | |
|   // 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
 | |
|   AbortInternal(aRv);
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
 | |
|   mFlagAborted = true;
 | |
|   DisconnectDoneNotifier();
 | |
| 
 | |
|   // Step 1
 | |
|   TerminateOngoingFetch();
 | |
| 
 | |
|   // Step 2
 | |
|   if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
 | |
|       mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
 | |
|       mState == XMLHttpRequest_Binding::LOADING) {
 | |
|     RequestErrorSteps(ProgressEventType::abort, NS_OK, 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 < ArrayLength(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;
 | |
| }
 | |
| 
 | |
| 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 (NS_SUCCEEDED(mChannel->GetContentType(value))) {
 | |
|     aResponseHeaders.AppendLiteral("Content-Type: ");
 | |
|     aResponseHeaders.Append(value);
 | |
|     if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
 | |
|       aResponseHeaders.AppendLiteral(";charset=");
 | |
|       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");
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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 (NS_FAILED(mChannel->GetContentType(_retval))) {
 | |
|         // Means no content type
 | |
|         _retval.SetIsVoid(true);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       nsCString value;
 | |
|       if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) &&
 | |
|           !value.IsEmpty()) {
 | |
|         _retval.AppendLiteral(";charset=");
 | |
|         _retval.Append(value);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Content Length:
 | |
|     else if (header.LowerCaseEqualsASCII("content-length")) {
 | |
|       int64_t length;
 | |
|       if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
 | |
|         _retval.AppendInt(length);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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) {
 | |
|   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 == ProgressEventType::progress ||
 | |
|         aType == ProgressEventType::load) {
 | |
|       return;
 | |
|     }
 | |
|     aLoaded = 0;
 | |
|     aTotal = -1;
 | |
|   }
 | |
| 
 | |
|   if (aType == ProgressEventType::progress) {
 | |
|     mInLoadProgressEvent = true;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
| 
 | |
|   const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
 | |
|   RefPtr<ProgressEvent> event =
 | |
|       ProgressEvent::Constructor(aTarget, typeString, init);
 | |
|   event->SetTrusted(true);
 | |
| 
 | |
|   DispatchOrStoreEvent(aTarget, event);
 | |
| 
 | |
|   if (aType == ProgressEventType::progress) {
 | |
|     mInLoadProgressEvent = false;
 | |
|   }
 | |
| 
 | |
|   // If we're sending a load, error, timeout or abort event, then
 | |
|   // also dispatch the subsequent loadend event.
 | |
|   if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
 | |
|       aType == ProgressEventType::timeout ||
 | |
|       aType == ProgressEventType::abort) {
 | |
|     DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::DispatchOrStoreEvent(
 | |
|     DOMEventTargetHelper* aTarget, Event* aEvent) {
 | |
|   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) {
 | |
|   NOT_CALLABLE_IN_SYNC_SEND_RV
 | |
| 
 | |
|   // Gecko-specific
 | |
|   if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
 | |
|       GetOwner()->GetExtantDoc()) {
 | |
|     GetOwner()->GetExtantDoc()->WarnOnceAbout(
 | |
|         DeprecatedOperations::eSyncXMLHttpRequest);
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   // Gecko-specific
 | |
|   if (!aAsync && responsibleDocument && GetOwner()) {
 | |
|     // 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", GetOwner());
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // 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)) {
 | |
|     if (rv == NS_ERROR_MALFORMED_URI) {
 | |
|       aRv.Throw(NS_ERROR_DOM_MALFORMED_URI);
 | |
|       return;
 | |
|     }
 | |
|     aRv.Throw(rv);
 | |
|     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 && HasOrHasHadOwner() &&
 | |
|       (mTimeoutMilliseconds ||
 | |
|        mResponseType != XMLHttpRequestResponseType::_empty)) {
 | |
|     if (mTimeoutMilliseconds) {
 | |
|       LogMessage("TimeoutSyncXHRWarning", GetOwner());
 | |
|     }
 | |
|     if (mResponseType != XMLHttpRequestResponseType::_empty) {
 | |
|       LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
 | |
|     }
 | |
|     aRv.ThrowInvalidAccessError(
 | |
|         "synchronous XMLHttpRequests do not support timeout and responseType");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 10
 | |
|   TerminateOngoingFetch();
 | |
| 
 | |
|   // 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) 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) 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) {
 | |
|   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, ProgressEventType::progress, mLoadTransferred,
 | |
|                             mLoadTotal);
 | |
|     }
 | |
|     mProgressSinceLastProgressEvent = false;
 | |
|   }
 | |
| 
 | |
|   if (!mFlagSynchronous && !mProgressTimerIsActive) {
 | |
|     StartProgressEventTimer();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
 | |
|   AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
|   if (!mFirstStartRequestSeen && mRequestObserver) {
 | |
|     mFirstStartRequestSeen = true;
 | |
|     mRequestObserver->OnStartRequest(request);
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   // 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, ProgressEventType::progress,
 | |
|                             mUploadTransferred, mUploadTotal);
 | |
|       mProgressSinceLastProgressEvent = false;
 | |
|     }
 | |
| 
 | |
|     mUploadComplete = true;
 | |
|     DispatchProgressEvent(mUpload, ProgressEventType::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'
 | |
|   nsAutoCString type;
 | |
|   channel->GetContentType(type);
 | |
|   if (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
 | |
|   // Note: Main Fetch step 18 requires to 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.
 | |
|     nsAutoCString type;
 | |
|     channel->GetContentType(type);
 | |
| 
 | |
|     if ((mResponseType == XMLHttpRequestResponseType::Document) &&
 | |
|         type.EqualsLiteral("text/html")) {
 | |
|       // HTML parsing is only supported for responseType == "document" to
 | |
|       // avoid running the parser and, worse, populating responseXML for
 | |
|       // legacy users of XHR who use responseType == "" for retrieving the
 | |
|       // responseText of text/html resources. This legacy case is so common
 | |
|       // that it's not useful to emit a warning about it.
 | |
|       if (mFlagSynchronous) {
 | |
|         // We don't make cool new features available in the bad synchronous
 | |
|         // mode. The synchronous mode is for legacy only.
 | |
|         mWarnAboutSyncHtml = true;
 | |
|         mFlagParseBody = false;
 | |
|       } else {
 | |
|         mIsHtml = true;
 | |
|       }
 | |
|     } else if (!(type.EqualsLiteral("text/xml") ||
 | |
|                  type.EqualsLiteral("application/xml") ||
 | |
|                  type.RFind("+xml", true, -1, 4) != kNotFound)) {
 | |
|       // Follow https://xhr.spec.whatwg.org/
 | |
|       // If final MIME type is not null, text/html, text/xml, application/xml,
 | |
|       // or does not end in +xml, return null.
 | |
|       mFlagParseBody = false;
 | |
|     }
 | |
|   } else {
 | |
|     // The request failed, so we shouldn't be parsing anyway
 | |
|     mFlagParseBody = false;
 | |
|   }
 | |
| 
 | |
|   if (mFlagParseBody) {
 | |
|     nsCOMPtr<nsIURI> baseURI, docURI;
 | |
|     rv = mChannel->GetURI(getter_AddRefs(docURI));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|     baseURI = docURI;
 | |
| 
 | |
|     nsCOMPtr<Document> doc = GetDocumentIfCurrent();
 | |
|     nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
 | |
|     if (doc) {
 | |
|       chromeXHRDocURI = doc->GetDocumentURI();
 | |
|       chromeXHRDocBaseURI = doc->GetBaseURI();
 | |
|     } else {
 | |
|       // If we're no longer current, just kill the load, though it really should
 | |
|       // have been killed already.
 | |
|       if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
 | |
|         return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Create an empty document from it.
 | |
|     const auto& emptyStr = u""_ns;
 | |
|     nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
 | |
| 
 | |
|     nsCOMPtr<nsIPrincipal> requestingPrincipal;
 | |
|     rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
 | |
|         channel, getter_AddRefs(requestingPrincipal));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     rv = NS_NewDOMDocument(
 | |
|         getter_AddRefs(mResponseXML), emptyStr, emptyStr, nullptr, docURI,
 | |
|         baseURI, requestingPrincipal, true, global,
 | |
|         mIsHtml ? DocumentFlavorHTML : DocumentFlavorLegacyGuess);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|     mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
 | |
|     mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
 | |
| 
 | |
|     // suppress parsing failure messages to console for statuses which
 | |
|     // can have empty bodies (see bug 884693).
 | |
|     IgnoredErrorResult rv2;
 | |
|     uint32_t responseStatus = GetStatus(rv2);
 | |
|     if (!rv2.Failed() && (responseStatus == 201 || responseStatus == 202 ||
 | |
|                           responseStatus == 204 || responseStatus == 205 ||
 | |
|                           responseStatus == 304)) {
 | |
|       mResponseXML->SetSuppressParserErrorConsoleMessages(true);
 | |
|     }
 | |
| 
 | |
|     if (mPrincipal->IsSystemPrincipal()) {
 | |
|       mResponseXML->ForceEnableXULXBL();
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
 | |
|     bool isCrossSite = false;
 | |
|     isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
 | |
| 
 | |
|     if (isCrossSite) {
 | |
|       mResponseXML->DisableCookieAccess();
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIStreamListener> listener;
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup;
 | |
|     channel->GetLoadGroup(getter_AddRefs(loadGroup));
 | |
| 
 | |
|     // suppress <parsererror> nodes on XML document parse failure, but only
 | |
|     // for non-privileged code (including Web Extensions). See bug 289714.
 | |
|     if (!IsSystemXHR()) {
 | |
|       mResponseXML->SetSuppressParserErrorElement(true);
 | |
|     }
 | |
| 
 | |
|     rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
 | |
|                                          nullptr, getter_AddRefs(listener),
 | |
|                                          !isCrossSite);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     // the spec requires the response document.referrer to be the empty string
 | |
|     nsCOMPtr<nsIReferrerInfo> referrerInfo =
 | |
|         new ReferrerInfo(nullptr, mResponseXML->ReferrerPolicy());
 | |
|     mResponseXML->SetReferrerInfo(referrerInfo);
 | |
| 
 | |
|     mXMLParserStreamListener = listener;
 | |
|     rv = mXMLParserStreamListener->OnStartRequest(request);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // Download phase beginning; start the progress event timer if necessary.
 | |
|   if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
 | |
|     StartProgressEventTimer();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) {
 | |
|   AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK);
 | |
| 
 | |
|   if (request != mChannel) {
 | |
|     // Can this still happen?
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Send the decoder the signal that we've hit the end of the stream,
 | |
|   // but only when decoding text eagerly.
 | |
|   if (mDecoder && ((mResponseType == XMLHttpRequestResponseType::Text) ||
 | |
|                    (mResponseType == XMLHttpRequestResponseType::Json) ||
 | |
|                    (mResponseType == XMLHttpRequestResponseType::_empty &&
 | |
|                     !mResponseXML))) {
 | |
|     AppendToResponseText(Span<const uint8_t>(), true);
 | |
|   }
 | |
| 
 | |
|   mWaitingForOnStopRequest = false;
 | |
| 
 | |
|   if (mRequestObserver) {
 | |
|     NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!");
 | |
|     mFirstStartRequestSeen = false;
 | |
|     mRequestObserver->OnStopRequest(request, status);
 | |
|   }
 | |
| 
 | |
|   // make sure to notify the listener if we were aborted
 | |
|   // XXX in fact, why don't we do the cleanup below in this case??
 | |
|   // UNSENT is for abort calls.  See OnStartRequest above.
 | |
|   if (mState == XMLHttpRequest_Binding::UNSENT || mFlagTimedOut) {
 | |
|     if (mXMLParserStreamListener)
 | |
|       (void)mXMLParserStreamListener->OnStopRequest(request, status);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Is this good enough here?
 | |
|   if (mXMLParserStreamListener && mFlagParseBody) {
 | |
|     mXMLParserStreamListener->OnStopRequest(request, status);
 | |
|   }
 | |
| 
 | |
|   mXMLParserStreamListener = nullptr;
 | |
|   mContext = nullptr;
 | |
| 
 | |
|   // If window.stop() or other aborts were issued, handle as an abort
 | |
|   if (status == NS_BINDING_ABORTED) {
 | |
|     mFlagParseBody = false;
 | |
|     IgnoredErrorResult rv;
 | |
|     RequestErrorSteps(ProgressEventType::abort, NS_OK, rv);
 | |
|     ChangeState(XMLHttpRequest_Binding::UNSENT, false);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // If we were just reading a blob URL, we're already done
 | |
|   if (status == NS_ERROR_FILE_ALREADY_EXISTS && mResponseBlobImpl) {
 | |
|     ChangeStateToDone(mFlagSyncLooping);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   bool waitingForBlobCreation = false;
 | |
| 
 | |
|   // If we have this error, we have to deal with a file: URL + responseType =
 | |
|   // blob. We have this error because we canceled the channel. The status will
 | |
|   // be set to NS_OK.
 | |
|   if (!mResponseBlobImpl && status == NS_ERROR_FILE_ALREADY_EXISTS &&
 | |
|       mResponseType == XMLHttpRequestResponseType::Blob) {
 | |
|     nsCOMPtr<nsIFile> file;
 | |
|     nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     if (file) {
 | |
|       nsAutoCString contentType;
 | |
|       rv = mChannel->GetContentType(contentType);
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         return rv;
 | |
|       }
 | |
| 
 | |
|       ChromeFilePropertyBag bag;
 | |
|       CopyUTF8toUTF16(contentType, bag.mType);
 | |
| 
 | |
|       nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
 | |
| 
 | |
|       ErrorResult error;
 | |
|       RefPtr<Promise> promise =
 | |
|           FileCreatorHelper::CreateFile(global, file, bag, true, error);
 | |
|       if (NS_WARN_IF(error.Failed())) {
 | |
|         return error.StealNSResult();
 | |
|       }
 | |
| 
 | |
|       FileCreationHandler::Create(promise, this);
 | |
|       waitingForBlobCreation = true;
 | |
|       status = NS_OK;
 | |
| 
 | |
|       NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
 | |
|       NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (NS_SUCCEEDED(status) &&
 | |
|       mResponseType == XMLHttpRequestResponseType::Blob &&
 | |
|       !waitingForBlobCreation) {
 | |
|     // Smaller files may be written in cache map instead of separate files.
 | |
|     // Also, no-store response cannot be written in persistent cache.
 | |
|     nsAutoCString contentType;
 | |
|     if (!mOverrideMimeType.IsEmpty()) {
 | |
|       contentType.Assign(NS_ConvertUTF16toUTF8(mOverrideMimeType));
 | |
|     } else {
 | |
|       mChannel->GetContentType(contentType);
 | |
|     }
 | |
| 
 | |
|     // mBlobStorage can be null if the channel is non-file non-cacheable
 | |
|     // and if the response length is zero.
 | |
|     MaybeCreateBlobStorage();
 | |
|     mBlobStorage->GetBlobImplWhenReady(contentType, this);
 | |
|     waitingForBlobCreation = true;
 | |
| 
 | |
|     NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
 | |
|     NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
 | |
|   } else if (NS_SUCCEEDED(status) && !mIsMappedArrayBuffer &&
 | |
|              mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
 | |
|     // set the capacity down to the actual length, to realloc back
 | |
|     // down to the actual size
 | |
|     if (!mArrayBufferBuilder->SetCapacity(mArrayBufferBuilder->Length())) {
 | |
|       // this should never happen!
 | |
|       status = NS_ERROR_UNEXPECTED;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
 | |
|   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   channel->SetNotificationCallbacks(nullptr);
 | |
|   mNotificationCallbacks = nullptr;
 | |
|   mChannelEventSink = nullptr;
 | |
|   mProgressEventSink = nullptr;
 | |
| 
 | |
|   bool wasSync = mFlagSyncLooping;
 | |
|   mFlagSyncLooping = false;
 | |
|   mRequestSentTime = 0;
 | |
| 
 | |
|   // update our charset and decoder to match mResponseXML,
 | |
|   // before it is possibly nulled out
 | |
|   MatchCharsetAndDecoderToResponseDocument();
 | |
| 
 | |
|   if (NS_FAILED(status)) {
 | |
|     // This can happen if the server is unreachable. Other possible
 | |
|     // reasons are that the user leaves the page or hits the ESC key.
 | |
| 
 | |
|     mErrorLoad = ErrorType::eUnreachable;
 | |
|     mResponseXML = nullptr;
 | |
|   }
 | |
| 
 | |
|   // If we're uninitialized at this point, we encountered an error
 | |
|   // earlier and listeners have already been notified. Also we do
 | |
|   // not want to do this if we already completed.
 | |
|   if (mState == XMLHttpRequest_Binding::UNSENT ||
 | |
|       mState == XMLHttpRequest_Binding::DONE) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!mResponseXML) {
 | |
|     mFlagParseBody = false;
 | |
| 
 | |
|     // We postpone the 'done' until the creation of the Blob is completed.
 | |
|     if (!waitingForBlobCreation) {
 | |
|       ChangeStateToDone(wasSync);
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (mIsHtml) {
 | |
|     NS_ASSERTION(!mFlagSyncLooping,
 | |
|                  "We weren't supposed to support HTML parsing with XHR!");
 | |
|     mParseEndListener = new nsXHRParseEndListener(this);
 | |
|     RefPtr<EventTarget> eventTarget = mResponseXML;
 | |
|     EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
 | |
|     manager->AddEventListenerByType(mParseEndListener,
 | |
|                                     kLiteralString_DOMContentLoaded,
 | |
|                                     TrustedEventsAtSystemGroupBubble());
 | |
|     return NS_OK;
 | |
|   } else {
 | |
|     mFlagParseBody = false;
 | |
|   }
 | |
| 
 | |
|   // We might have been sent non-XML data. If that was the case,
 | |
|   // we should null out the document member. The idea in this
 | |
|   // check here is that if there is no document element it is not
 | |
|   // an XML document. We might need a fancier check...
 | |
|   if (!mResponseXML->GetRootElement()) {
 | |
|     mErrorParsingXML = true;
 | |
|     mResponseXML = nullptr;
 | |
|   }
 | |
|   ChangeStateToDone(wasSync);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::OnBodyParseEnd() {
 | |
|   mFlagParseBody = false;
 | |
|   mParseEndListener = nullptr;
 | |
|   ChangeStateToDone(mFlagSyncLooping);
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument() {
 | |
|   if (mResponseXML &&
 | |
|       (!mDecoder ||
 | |
|        mDecoder->Encoding() != mResponseXML->GetDocumentCharacterSet())) {
 | |
|     TruncateResponseText();
 | |
|     mResponseBodyDecodedPos = 0;
 | |
|     mEofDecoded = false;
 | |
|     mDecoder = mResponseXML->GetDocumentCharacterSet()->NewDecoder();
 | |
|   }
 | |
| }
 | |
| void XMLHttpRequestMainThread::DisconnectDoneNotifier() {
 | |
|   if (mDelayedDoneNotifier) {
 | |
|     // Disconnect may release the last reference to 'this'.
 | |
|     RefPtr<XMLHttpRequestMainThread> kungfuDeathGrip = this;
 | |
|     mDelayedDoneNotifier->Disconnect();
 | |
|     mDelayedDoneNotifier = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::ChangeStateToDone(bool aWasSync) {
 | |
|   DisconnectDoneNotifier();
 | |
| 
 | |
|   if (!mForWorker && !aWasSync && mChannel) {
 | |
|     // If the top level page is loading, try to postpone the handling of the
 | |
|     // final events.
 | |
|     nsLoadFlags loadFlags = 0;
 | |
|     mChannel->GetLoadFlags(&loadFlags);
 | |
|     if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
 | |
|       nsPIDOMWindowInner* owner = GetOwner();
 | |
|       BrowsingContext* bc = owner ? owner->GetBrowsingContext() : nullptr;
 | |
|       bc = bc ? bc->Top() : nullptr;
 | |
|       if (bc && bc->IsLoading()) {
 | |
|         MOZ_ASSERT(!mDelayedDoneNotifier);
 | |
|         RefPtr<XMLHttpRequestDoneNotifier> notifier =
 | |
|             new XMLHttpRequestDoneNotifier(this);
 | |
|         mDelayedDoneNotifier = notifier;
 | |
|         bc->AddDeprioritizedLoadRunner(notifier);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ChangeStateToDoneInternal();
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::ChangeStateToDoneInternal() {
 | |
|   DisconnectDoneNotifier();
 | |
|   StopProgressEventTimer();
 | |
| 
 | |
|   MOZ_ASSERT(!mFlagParseBody,
 | |
|              "ChangeStateToDone() called before async HTML parsing is done.");
 | |
| 
 | |
|   mFlagSend = false;
 | |
| 
 | |
|   if (mTimeoutTimer) {
 | |
|     mTimeoutTimer->Cancel();
 | |
|   }
 | |
| 
 | |
|   // Per spec, fire the last download progress event, if any,
 | |
|   // before readystatechange=4/done. (Note that 0-sized responses
 | |
|   // will have not sent a progress event yet, so one must be sent here).
 | |
|   if (!mFlagSynchronous &&
 | |
|       (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
 | |
|     DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
 | |
|                           mLoadTotal);
 | |
|     mProgressSinceLastProgressEvent = false;
 | |
|   }
 | |
| 
 | |
|   // Notify the document when an XHR request completes successfully.
 | |
|   // This is used by the password manager as a hint to observe DOM mutations.
 | |
|   // Call this prior to changing state to DONE to ensure we set up the
 | |
|   // observer before mutations occur.
 | |
|   if (mErrorLoad == ErrorType::eOK) {
 | |
|     Document* doc = GetDocumentIfCurrent();
 | |
|     if (doc) {
 | |
|       doc->NotifyFetchOrXHRSuccess();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Per spec, fire readystatechange=4/done before final error events.
 | |
|   ChangeState(XMLHttpRequest_Binding::DONE, true);
 | |
| 
 | |
|   // Per spec, if we failed in the upload phase, fire a final error
 | |
|   // and loadend events for the upload after readystatechange=4/done.
 | |
|   if (!mFlagSynchronous && mUpload && !mUploadComplete) {
 | |
|     DispatchProgressEvent(mUpload, ProgressEventType::error, 0, -1);
 | |
|   }
 | |
| 
 | |
|   // Per spec, fire download's load/error and loadend events after
 | |
|   // readystatechange=4/done (and of course all upload events).
 | |
|   if (mErrorLoad != ErrorType::eOK) {
 | |
|     DispatchProgressEvent(this, ProgressEventType::error, 0, -1);
 | |
|   } else {
 | |
|     DispatchProgressEvent(this, ProgressEventType::load, mLoadTransferred,
 | |
|                           mLoadTotal);
 | |
|   }
 | |
| 
 | |
|   if (mErrorLoad != ErrorType::eOK) {
 | |
|     // By nulling out channel here we make it so that Send() can test
 | |
|     // for that and throw. Also calling the various status
 | |
|     // methods/members will not throw.
 | |
|     // This matches what IE does.
 | |
|     mChannel = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult XMLHttpRequestMainThread::CreateChannel() {
 | |
|   // When we are called from JS we can find the load group for the page,
 | |
|   // and add ourselves to it. This way any pending requests
 | |
|   // will be automatically aborted if the user leaves the page.
 | |
|   nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
 | |
| 
 | |
|   nsSecurityFlags secFlags;
 | |
|   nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
 | |
|   uint32_t sandboxFlags = 0;
 | |
|   if (mPrincipal->IsSystemPrincipal()) {
 | |
|     // When chrome is loading we want to make sure to sandbox any potential
 | |
|     // result document. We also want to allow cross-origin loads.
 | |
|     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
 | |
|     sandboxFlags = SANDBOXED_ORIGIN;
 | |
|   } else if (IsSystemXHR()) {
 | |
|     // For pages that have appropriate permissions, we want to still allow
 | |
|     // cross-origin loads, but make sure that the any potential result
 | |
|     // documents get the same principal as the loader.
 | |
|     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
 | |
|                nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
 | |
|     loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
 | |
|   } else {
 | |
|     // Otherwise use CORS. Again, make sure that potential result documents
 | |
|     // use the same principal as the loader.
 | |
|     secFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
 | |
|                nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
 | |
|   }
 | |
| 
 | |
|   if (mIsAnon) {
 | |
|     secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
 | |
|   }
 | |
| 
 | |
|   // Use the responsibleDocument if we have it, except for dedicated workers
 | |
|   // where it will be the parent document, which is not the one we want to use.
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
 | |
|   if (responsibleDocument &&
 | |
|       responsibleDocument->NodePrincipal() == mPrincipal) {
 | |
|     rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL,
 | |
|                        responsibleDocument, secFlags,
 | |
|                        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
 | |
|                        nullptr,  // aPerformanceStorage
 | |
|                        loadGroup,
 | |
|                        nullptr,  // aCallbacks
 | |
|                        loadFlags, nullptr, sandboxFlags);
 | |
|   } else if (mClientInfo.isSome()) {
 | |
|     rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
 | |
|                        mClientInfo.ref(), mController, secFlags,
 | |
|                        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
 | |
|                        mCookieJarSettings,
 | |
|                        mPerformanceStorage,  // aPerformanceStorage
 | |
|                        loadGroup,
 | |
|                        nullptr,  // aCallbacks
 | |
|                        loadFlags, nullptr, sandboxFlags);
 | |
|   } else {
 | |
|     // Otherwise use the principal.
 | |
|     rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
 | |
|                        secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
 | |
|                        mCookieJarSettings,
 | |
|                        mPerformanceStorage,  // aPerformanceStorage
 | |
|                        loadGroup,
 | |
|                        nullptr,  // aCallbacks
 | |
|                        loadFlags, nullptr, sandboxFlags);
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (mCSPEventListener) {
 | |
|     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
 | |
|     rv = loadInfo->SetCspEventListener(mCSPEventListener);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
 | |
|   if (httpChannel) {
 | |
|     rv = httpChannel->SetRequestMethod(mRequestMethod);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     httpChannel->SetSource(profiler_capture_backtrace());
 | |
| 
 | |
|     // Set the initiator type
 | |
|     nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
 | |
|     if (timedChannel) {
 | |
|       timedChannel->SetInitiatorType(u"xmlhttprequest"_ns);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::MaybeLowerChannelPriority() {
 | |
|   nsCOMPtr<Document> doc = GetDocumentIfCurrent();
 | |
|   if (!doc) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoJSAPI jsapi;
 | |
|   if (!jsapi.Init(GetOwnerGlobal())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   JSContext* cx = jsapi.cx();
 | |
| 
 | |
|   if (!doc->IsScriptTracking(cx)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::network_http_tailing_enabled()) {
 | |
|     nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
 | |
|     if (cos) {
 | |
|       // Adding TailAllowed to overrule the Unblocked flag, but to preserve
 | |
|       // the effect of Unblocked when tailing is off.
 | |
|       cos->AddClassFlags(nsIClassOfService::Throttleable |
 | |
|                          nsIClassOfService::Tail |
 | |
|                          nsIClassOfService::TailAllowed);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
 | |
|   if (p) {
 | |
|     p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult XMLHttpRequestMainThread::InitiateFetch(
 | |
|     already_AddRefed<nsIInputStream> aUploadStream, int64_t aUploadLength,
 | |
|     nsACString& aUploadContentType) {
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIInputStream> uploadStream = std::move(aUploadStream);
 | |
| 
 | |
|   if (!uploadStream) {
 | |
|     RefPtr<PreloaderBase> preload = FindPreload();
 | |
|     if (preload) {
 | |
|       // Because of bug 682305, we can't let listener be the XHR object itself
 | |
|       // because JS wouldn't be able to use it. So create a listener around
 | |
|       // 'this'. Make sure to hold a strong reference so that we don't leak the
 | |
|       // wrapper.
 | |
|       nsCOMPtr<nsIStreamListener> listener =
 | |
|           new net::nsStreamListenerWrapper(this);
 | |
|       rv = preload->AsyncConsume(listener);
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         mFromPreload = true;
 | |
| 
 | |
|         // May be null when the preload has already finished, but the XHR code
 | |
|         // is safe to live with it.
 | |
|         mChannel = preload->Channel();
 | |
|         MOZ_ASSERT(mChannel);
 | |
|         EnsureChannelContentType();
 | |
|         return NS_OK;
 | |
|       }
 | |
| 
 | |
|       preload = nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
 | |
|   // in turn keeps STOP button from becoming active.  If the consumer passed in
 | |
|   // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
 | |
|   // necko won't generate any progress notifications.
 | |
|   if (HasListenersFor(nsGkAtoms::onprogress) ||
 | |
|       (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
 | |
|     nsLoadFlags loadFlags;
 | |
|     mChannel->GetLoadFlags(&loadFlags);
 | |
|     loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
 | |
|     loadFlags |= nsIRequest::LOAD_NORMAL;
 | |
|     mChannel->SetLoadFlags(loadFlags);
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
 | |
|   if (httpChannel) {
 | |
|     // If the user hasn't overridden the Accept header, set it to */* per spec.
 | |
|     if (!mAuthorRequestHeaders.Has("accept")) {
 | |
|       mAuthorRequestHeaders.Set("accept", "*/*"_ns);
 | |
|     }
 | |
| 
 | |
|     mAuthorRequestHeaders.ApplyToChannel(httpChannel, false);
 | |
| 
 | |
|     if (!IsSystemXHR()) {
 | |
|       nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
 | |
|       nsCOMPtr<Document> doc = owner ? owner->GetExtantDoc() : nullptr;
 | |
|       nsCOMPtr<nsIReferrerInfo> referrerInfo =
 | |
|           ReferrerInfo::CreateForFetch(mPrincipal, doc);
 | |
|       Unused << httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
 | |
|     }
 | |
| 
 | |
|     // Some extensions override the http protocol handler and provide their own
 | |
|     // implementation. The channels returned from that implementation don't
 | |
|     // always seem to implement the nsIUploadChannel2 interface, presumably
 | |
|     // because it's a new interface. Eventually we should remove this and simply
 | |
|     // require that http channels implement the new interface (see bug 529041).
 | |
|     nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
 | |
|     if (!uploadChannel2) {
 | |
|       nsCOMPtr<nsIConsoleService> consoleService =
 | |
|           do_GetService(NS_CONSOLESERVICE_CONTRACTID);
 | |
|       if (consoleService) {
 | |
|         consoleService->LogStringMessage(
 | |
|             u"Http channel implementation doesn't support nsIUploadChannel2. "
 | |
|             "An extension has supplied a non-functional http protocol handler. "
 | |
|             "This will break behavior and in future releases not work at all.");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (uploadStream) {
 | |
|       // If necessary, wrap the stream in a buffered stream so as to guarantee
 | |
|       // support for our upload when calling ExplicitSetUploadStream.
 | |
|       if (!NS_InputStreamIsBuffered(uploadStream)) {
 | |
|         nsCOMPtr<nsIInputStream> bufferedStream;
 | |
|         rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
 | |
|                                        uploadStream.forget(), 4096);
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         uploadStream = bufferedStream;
 | |
|       }
 | |
| 
 | |
|       // We want to use a newer version of the upload channel that won't
 | |
|       // ignore the necessary headers for an empty Content-Type.
 | |
|       nsCOMPtr<nsIUploadChannel2> uploadChannel2(
 | |
|           do_QueryInterface(httpChannel));
 | |
|       // This assertion will fire if buggy extensions are installed
 | |
|       NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
 | |
|       if (uploadChannel2) {
 | |
|         uploadChannel2->ExplicitSetUploadStream(
 | |
|             uploadStream, aUploadContentType, mUploadTotal, mRequestMethod,
 | |
|             false);
 | |
|       } else {
 | |
|         // The http channel doesn't support the new nsIUploadChannel2.
 | |
|         // Emulate it as best we can using nsIUploadChannel.
 | |
|         if (aUploadContentType.IsEmpty()) {
 | |
|           aUploadContentType.AssignLiteral("application/octet-stream");
 | |
|         }
 | |
|         nsCOMPtr<nsIUploadChannel> uploadChannel =
 | |
|             do_QueryInterface(httpChannel);
 | |
|         uploadChannel->SetUploadStream(uploadStream, aUploadContentType,
 | |
|                                        mUploadTotal);
 | |
|         // Reset the method to its original value
 | |
|         rv = httpChannel->SetRequestMethod(mRequestMethod);
 | |
|         MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Due to the chrome-only XHR.channel API, we need a hacky way to set the
 | |
|   // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
 | |
|   // .withCredentials can be called after open() is called.
 | |
|   // Not doing this for privileged system XHRs since those don't use CORS.
 | |
|   if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
 | |
|     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
 | |
|     static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
 | |
|   }
 | |
| 
 | |
|   // We never let XHR be blocked by head CSS/JS loads to avoid potential
 | |
|   // deadlock where server generation of CSS/JS requires an XHR signal.
 | |
|   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
 | |
|   if (cos) {
 | |
|     cos->AddClassFlags(nsIClassOfService::Unblocked);
 | |
| 
 | |
|     // Mark channel as urgent-start if the XHR is triggered by user input
 | |
|     // events.
 | |
|     if (UserActivation::IsHandlingUserInput()) {
 | |
|       cos->AddClassFlags(nsIClassOfService::UrgentStart);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Disable Necko-internal response timeouts.
 | |
|   nsCOMPtr<nsIHttpChannelInternal> internalHttpChannel(
 | |
|       do_QueryInterface(mChannel));
 | |
|   if (internalHttpChannel) {
 | |
|     rv = internalHttpChannel->SetResponseTimeoutEnabled(false);
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|   }
 | |
| 
 | |
|   if (!mIsAnon) {
 | |
|     AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
 | |
|   }
 | |
| 
 | |
|   // Bypass the network cache in cases where it makes no sense:
 | |
|   // POST responses are always unique, and we provide no API that would
 | |
|   // allow our consumers to specify a "cache key" to access old POST
 | |
|   // responses, so they are not worth caching.
 | |
|   if (mRequestMethod.EqualsLiteral("POST")) {
 | |
|     AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
 | |
|                                nsIRequest::INHIBIT_CACHING);
 | |
|   } else {
 | |
|     // When we are sync loading, we need to bypass the local cache when it would
 | |
|     // otherwise block us waiting for exclusive access to the cache.  If we
 | |
|     // don't do this, then we could dead lock in some cases (see bug 309424).
 | |
|     //
 | |
|     // Also don't block on the cache entry on async if it is busy - favoring
 | |
|     // parallelism over cache hit rate for xhr. This does not disable the cache
 | |
|     // everywhere - only in cases where more than one channel for the same URI
 | |
|     // is accessed simultanously.
 | |
|     AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
 | |
|   }
 | |
| 
 | |
|   EnsureChannelContentType();
 | |
| 
 | |
|   // Set up the preflight if needed
 | |
|   if (!IsSystemXHR()) {
 | |
|     nsTArray<nsCString> CORSUnsafeHeaders;
 | |
|     mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
 | |
|     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
 | |
|     loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
 | |
|                                    mFlagHadUploadListenersOnSend);
 | |
|   }
 | |
| 
 | |
|   // Hook us up to listen to redirects and the like. Only do this very late
 | |
|   // since this creates a cycle between the channel and us. This cycle has
 | |
|   // to be manually broken if anything below fails.
 | |
|   mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
 | |
|   mChannel->SetNotificationCallbacks(this);
 | |
| 
 | |
|   if (internalHttpChannel) {
 | |
|     internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
 | |
|   }
 | |
| 
 | |
|   // Because of bug 682305, we can't let listener be the XHR object itself
 | |
|   // because JS wouldn't be able to use it. So create a listener around 'this'.
 | |
|   // Make sure to hold a strong reference so that we don't leak the wrapper.
 | |
|   nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
 | |
| 
 | |
|   // Check if this XHR is created from a tracking script.
 | |
|   // If yes, lower the channel's priority.
 | |
|   if (StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
 | |
|     MaybeLowerChannelPriority();
 | |
|   }
 | |
| 
 | |
|   // Associate any originating stack with the channel.
 | |
|   NotifyNetworkMonitorAlternateStack(mChannel, std::move(mOriginStack));
 | |
| 
 | |
|   // Start reading from the channel
 | |
|   rv = mChannel->AsyncOpen(listener);
 | |
|   listener = nullptr;
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     // Drop our ref to the channel to avoid cycles. Also drop channel's
 | |
|     // ref to us to be extra safe.
 | |
|     mChannel->SetNotificationCallbacks(mNotificationCallbacks);
 | |
|     mChannel = nullptr;
 | |
| 
 | |
|     mErrorLoad = ErrorType::eChannelOpen;
 | |
| 
 | |
|     // Per spec, we throw on sync errors, but not async.
 | |
|     if (mFlagSynchronous) {
 | |
|       mState = XMLHttpRequest_Binding::DONE;
 | |
|       return NS_ERROR_DOM_NETWORK_ERR;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
 | |
|   Document* doc = GetDocumentIfCurrent();
 | |
|   if (!doc) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (mPrincipal->IsSystemPrincipal() || IsSystemXHR()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (!mRequestMethod.EqualsLiteral("GET")) {
 | |
|     // Preload can only do GET.
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (!mAuthorRequestHeaders.IsEmpty()) {
 | |
|     // Preload can't set headers.
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // mIsAnon overrules mFlagACwithCredentials.
 | |
|   CORSMode cors = (mIsAnon || !mFlagACwithCredentials)
 | |
|                       ? CORSMode::CORS_ANONYMOUS
 | |
|                       : CORSMode::CORS_USE_CREDENTIALS;
 | |
|   nsCOMPtr<nsIReferrerInfo> referrerInfo =
 | |
|       ReferrerInfo::CreateForFetch(mPrincipal, doc);
 | |
|   auto key = PreloadHashKey::CreateAsFetch(mRequestURL, cors);
 | |
|   RefPtr<PreloaderBase> preload = doc->Preloads().LookupPreload(key);
 | |
|   if (!preload) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   preload->RemoveSelf(doc);
 | |
|   preload->NotifyUsage(PreloaderBase::LoadBackground::Keep);
 | |
| 
 | |
|   return preload.forget();
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::EnsureChannelContentType() {
 | |
|   MOZ_ASSERT(mChannel);
 | |
| 
 | |
|   // Since we expect XML data, set the type hint accordingly
 | |
|   // if the channel doesn't know any content type.
 | |
|   // This means that we always try to parse local files as XML
 | |
|   // ignoring return value, as this is not critical. Use text/xml as fallback
 | |
|   // MIME type.
 | |
|   nsAutoCString contentType;
 | |
|   if (NS_FAILED(mChannel->GetContentType(contentType)) ||
 | |
|       contentType.IsEmpty() ||
 | |
|       contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
 | |
|     mChannel->SetContentType("text/xml"_ns);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::ResumeTimeout() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(mFlagSynchronous);
 | |
| 
 | |
|   if (mResumeTimeoutRunnable) {
 | |
|     DispatchToMainThread(mResumeTimeoutRunnable.forget());
 | |
|     mResumeTimeoutRunnable = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::Send(
 | |
|     const Nullable<
 | |
|         DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
 | |
|         aData,
 | |
|     ErrorResult& aRv) {
 | |
|   NOT_CALLABLE_IN_SYNC_SEND_RV
 | |
| 
 | |
|   if (aData.IsNull()) {
 | |
|     SendInternal(nullptr, false, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsDocument()) {
 | |
|     BodyExtractor<Document> body(&aData.Value().GetAsDocument());
 | |
|     SendInternal(&body, true, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsBlob()) {
 | |
|     BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
 | |
|     SendInternal(&body, false, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsArrayBuffer()) {
 | |
|     BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
 | |
|     SendInternal(&body, false, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsArrayBufferView()) {
 | |
|     BodyExtractor<const ArrayBufferView> body(
 | |
|         &aData.Value().GetAsArrayBufferView());
 | |
|     SendInternal(&body, false, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsFormData()) {
 | |
|     BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
 | |
|     SendInternal(&body, false, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsURLSearchParams()) {
 | |
|     BodyExtractor<const URLSearchParams> body(
 | |
|         &aData.Value().GetAsURLSearchParams());
 | |
|     SendInternal(&body, false, aRv);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aData.Value().IsUSVString()) {
 | |
|     BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
 | |
|     SendInternal(&body, true, aRv);
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult XMLHttpRequestMainThread::MaybeSilentSendFailure(nsresult aRv) {
 | |
|   // Per spec, silently fail on async request failures; throw for sync.
 | |
|   if (mFlagSynchronous) {
 | |
|     mState = XMLHttpRequest_Binding::DONE;
 | |
|     return NS_ERROR_DOM_NETWORK_ERR;
 | |
|   }
 | |
| 
 | |
|   // Defer the actual sending of async events just in case listeners
 | |
|   // are attached after the send() method is called.
 | |
|   Unused << NS_WARN_IF(
 | |
|       NS_FAILED(DispatchToMainThread(NewRunnableMethod<ProgressEventType>(
 | |
|           "dom::XMLHttpRequestMainThread::CloseRequestWithError", this,
 | |
|           &XMLHttpRequestMainThread::CloseRequestWithError,
 | |
|           ProgressEventType::error))));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
 | |
|                                             bool aBodyIsDocumentOrString,
 | |
|                                             ErrorResult& aRv) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (!mPrincipal) {
 | |
|     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 1
 | |
|   if (mState != XMLHttpRequest_Binding::OPENED) {
 | |
|     aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 2
 | |
|   if (mFlagSend) {
 | |
|     aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
 | |
|     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If open() failed to create the channel, then throw a network error
 | |
|   // as per spec. We really should create the channel here in send(), but
 | |
|   // we have internal code relying on the channel being created in open().
 | |
|   if (!mChannel) {
 | |
|     mFlagSend = true;  // so CloseRequestWithError sets us to DONE.
 | |
|     aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // non-GET requests aren't allowed for blob.
 | |
|   if (IsBlobURI(mRequestURL) && !mRequestMethod.EqualsLiteral("GET")) {
 | |
|     mFlagSend = true;  // so CloseRequestWithError sets us to DONE.
 | |
|     aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // XXX We should probably send a warning to the JS console
 | |
|   //     if there are no event listeners set and we are doing
 | |
|   //     an asynchronous call.
 | |
| 
 | |
|   mUploadTransferred = 0;
 | |
|   mUploadTotal = 0;
 | |
|   // By default we don't have any upload, so mark upload complete.
 | |
|   mUploadComplete = true;
 | |
|   mErrorLoad = ErrorType::eOK;
 | |
|   mLoadTotal = -1;
 | |
|   nsCOMPtr<nsIInputStream> uploadStream;
 | |
|   nsAutoCString uploadContentType;
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
 | |
|   if (aBody && httpChannel && !mRequestMethod.EqualsLiteral("GET") &&
 | |
|       !mRequestMethod.EqualsLiteral("HEAD")) {
 | |
|     nsAutoCString charset;
 | |
|     nsAutoCString defaultContentType;
 | |
|     uint64_t size_u64;
 | |
|     aRv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64,
 | |
|                              defaultContentType, charset);
 | |
|     if (aRv.Failed()) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // make sure it fits within js MAX_SAFE_INTEGER
 | |
|     mUploadTotal =
 | |
|         net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
 | |
| 
 | |
|     if (uploadStream) {
 | |
|       // If author set no Content-Type, use the default from GetAsStream().
 | |
|       mAuthorRequestHeaders.Get("content-type", uploadContentType);
 | |
|       if (uploadContentType.IsVoid()) {
 | |
|         uploadContentType = defaultContentType;
 | |
|       } else if (aBodyIsDocumentOrString &&
 | |
|                  StaticPrefs::dom_xhr_standard_content_type_normalization()) {
 | |
|         UniquePtr<CMimeType> contentTypeRecord =
 | |
|             CMimeType::Parse(uploadContentType);
 | |
|         nsAutoCString charset;
 | |
|         if (contentTypeRecord &&
 | |
|             contentTypeRecord->GetParameterValue(kLiteralString_charset,
 | |
|                                                  charset) &&
 | |
|             !charset.EqualsIgnoreCase("utf-8")) {
 | |
|           contentTypeRecord->SetParameterValue(kLiteralString_charset,
 | |
|                                                kLiteralString_UTF_8);
 | |
|           contentTypeRecord->Serialize(uploadContentType);
 | |
|         }
 | |
|       } else if (!charset.IsEmpty()) {
 | |
|         // We don't want to set a charset for streams.
 | |
|         // Replace all case-insensitive matches of the charset in the
 | |
|         // content-type with the correct case.
 | |
|         RequestHeaders::CharsetIterator iter(uploadContentType);
 | |
|         while (iter.Next()) {
 | |
|           if (!iter.Equals(charset, nsCaseInsensitiveCStringComparator)) {
 | |
|             iter.Replace(charset);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       mUploadComplete = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ResetResponse();
 | |
| 
 | |
|   // Check if we should enable cross-origin upload listeners.
 | |
|   if (mUpload && mUpload->HasListeners()) {
 | |
|     mFlagHadUploadListenersOnSend = true;
 | |
|   }
 | |
| 
 | |
|   mIsMappedArrayBuffer = false;
 | |
|   if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
 | |
|       StaticPrefs::dom_mapped_arraybuffer_enabled()) {
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     nsAutoCString scheme;
 | |
| 
 | |
|     aRv = mChannel->GetURI(getter_AddRefs(uri));
 | |
|     if (!aRv.Failed()) {
 | |
|       uri->GetScheme(scheme);
 | |
|       if (scheme.LowerCaseEqualsLiteral("jar")) {
 | |
|         mIsMappedArrayBuffer = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   aRv = InitiateFetch(uploadStream.forget(), mUploadTotal, uploadContentType);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Start our timeout
 | |
|   mRequestSentTime = PR_Now();
 | |
|   StartTimeoutTimer();
 | |
| 
 | |
|   mWaitingForOnStopRequest = true;
 | |
| 
 | |
|   // Step 8
 | |
|   mFlagSend = true;
 | |
| 
 | |
|   // If we're synchronous, spin an event loop here and wait
 | |
|   RefPtr<Document> suspendedDoc;
 | |
|   if (mFlagSynchronous) {
 | |
|     auto scopeExit = MakeScopeExit([&] {
 | |
|       CancelSyncTimeoutTimer();
 | |
|       ResumeTimeout();
 | |
|       ResumeEventDispatching();
 | |
|     });
 | |
|     Maybe<AutoSuppressEventHandling> autoSuppress;
 | |
| 
 | |
|     mFlagSyncLooping = true;
 | |
| 
 | |
|     if (GetOwner()) {
 | |
|       if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
 | |
|               GetOwner()->GetOuterWindow()->GetInProcessTop()) {
 | |
|         if (nsCOMPtr<nsPIDOMWindowInner> topInner =
 | |
|                 topWindow->GetCurrentInnerWindow()) {
 | |
|           suspendedDoc = topWindow->GetExtantDoc();
 | |
|           autoSuppress.emplace(topWindow->GetBrowsingContext());
 | |
|           topInner->Suspend();
 | |
|           mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     SuspendEventDispatching();
 | |
|     StopProgressEventTimer();
 | |
| 
 | |
|     SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
 | |
|     if (syncTimeoutType == eErrorOrExpired) {
 | |
|       Abort();
 | |
|       aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsAutoSyncOperation sync(suspendedDoc,
 | |
|                              SyncOperationBehavior::eSuspendInput);
 | |
|     if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
 | |
|       aRv.Throw(NS_ERROR_UNEXPECTED);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Time expired... We should throw.
 | |
|     if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
 | |
|       aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
 | |
|       return;
 | |
|     }
 | |
|   } else {
 | |
|     // Now that we've successfully opened the channel, we can change state. Note
 | |
|     // that this needs to come after the AsyncOpen() and rv check, because this
 | |
|     // can run script that would try to restart this request, and that could end
 | |
|     // up doing our AsyncOpen on a null channel if the reentered AsyncOpen
 | |
|     // fails.
 | |
|     StopProgressEventTimer();
 | |
| 
 | |
|     // Upload phase beginning; start the progress event timer if necessary.
 | |
|     if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
 | |
|       StartProgressEventTimer();
 | |
|     }
 | |
|     // Dispatch loadstart events
 | |
|     DispatchProgressEvent(this, ProgressEventType::loadstart, 0, -1);
 | |
|     if (mUpload && !mUploadComplete) {
 | |
|       DispatchProgressEvent(mUpload, ProgressEventType::loadstart, 0,
 | |
|                             mUploadTotal);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mChannel) {
 | |
|     aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
 | |
| void XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
 | |
|                                                 const nsACString& aValue,
 | |
|                                                 ErrorResult& aRv) {
 | |
|   NOT_CALLABLE_IN_SYNC_SEND_RV
 | |
| 
 | |
|   // Step 1
 | |
|   if (mState != XMLHttpRequest_Binding::OPENED) {
 | |
|     aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 2
 | |
|   if (mFlagSend) {
 | |
|     aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 3
 | |
|   nsAutoCString value;
 | |
|   NS_TrimHTTPWhitespace(aValue, value);
 | |
| 
 | |
|   // Step 4
 | |
|   if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
 | |
|     aRv.Throw(NS_ERROR_DOM_INVALID_HEADER_NAME);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 5
 | |
|   bool isPrivilegedCaller = IsSystemXHR();
 | |
|   bool isForbiddenHeader = nsContentUtils::IsForbiddenRequestHeader(aName);
 | |
|   if (!isPrivilegedCaller && isForbiddenHeader) {
 | |
|     AutoTArray<nsString, 1> params;
 | |
|     CopyUTF8toUTF16(aName, *params.AppendElement());
 | |
|     LogMessage("ForbiddenHeaderWarning", GetOwner(), params);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 6.1
 | |
|   // Skipping for now, as normalizing the case of header names may not be
 | |
|   // web-compatible. See bug 1285036.
 | |
| 
 | |
|   // Step 6.2-6.3
 | |
|   // Gecko-specific: invalid headers can be set by privileged
 | |
|   //                 callers, but will not merge.
 | |
|   if (isPrivilegedCaller && isForbiddenHeader) {
 | |
|     mAuthorRequestHeaders.Set(aName, value);
 | |
|   } else {
 | |
|     mAuthorRequestHeaders.MergeOrSet(aName, value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) {
 | |
|   NOT_CALLABLE_IN_SYNC_SEND_RV
 | |
| 
 | |
|   if (mFlagSynchronous && mState != XMLHttpRequest_Binding::UNSENT &&
 | |
|       HasOrHasHadOwner()) {
 | |
|     /* Timeout is not supported for synchronous requests with an owning window,
 | |
|        per XHR2 spec. */
 | |
|     LogMessage("TimeoutSyncXHRWarning", GetOwner());
 | |
|     aRv.ThrowInvalidAccessError(
 | |
|         "synchronous XMLHttpRequests do not support timeout and responseType");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mTimeoutMilliseconds = aTimeout;
 | |
|   if (mRequestSentTime) {
 | |
|     StartTimeoutTimer();
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsIEventTarget* XMLHttpRequestMainThread::GetTimerEventTarget() {
 | |
|   if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
 | |
|     return global->EventTargetFor(TaskCategory::Other);
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsresult XMLHttpRequestMainThread::DispatchToMainThread(
 | |
|     already_AddRefed<nsIRunnable> aRunnable) {
 | |
|   if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
 | |
|     nsCOMPtr<nsIEventTarget> target =
 | |
|         global->EventTargetFor(TaskCategory::Other);
 | |
|     MOZ_ASSERT(target);
 | |
| 
 | |
|     return target->Dispatch(std::move(aRunnable), NS_DISPATCH_NORMAL);
 | |
|   }
 | |
| 
 | |
|   return NS_DispatchToMainThread(std::move(aRunnable));
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::StartTimeoutTimer() {
 | |
|   MOZ_ASSERT(
 | |
|       mRequestSentTime,
 | |
|       "StartTimeoutTimer mustn't be called before the request was sent!");
 | |
|   if (mState == XMLHttpRequest_Binding::DONE) {
 | |
|     // do nothing!
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mTimeoutTimer) {
 | |
|     mTimeoutTimer->Cancel();
 | |
|   }
 | |
| 
 | |
|   if (!mTimeoutMilliseconds) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mTimeoutTimer) {
 | |
|     mTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
 | |
|   }
 | |
|   uint32_t elapsed =
 | |
|       (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
 | |
|   mTimeoutTimer->InitWithCallback(
 | |
|       this, mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
 | |
|       nsITimer::TYPE_ONE_SHOT);
 | |
| }
 | |
| 
 | |
| uint16_t XMLHttpRequestMainThread::ReadyState() const { return mState; }
 | |
| 
 | |
| void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType,
 | |
|                                                 ErrorResult& aRv) {
 | |
|   NOT_CALLABLE_IN_SYNC_SEND_RV
 | |
| 
 | |
|   if (mState == XMLHttpRequest_Binding::LOADING ||
 | |
|       mState == XMLHttpRequest_Binding::DONE) {
 | |
|     aRv.ThrowInvalidStateError(
 | |
|         "Cannot call 'overrideMimeType()' on XMLHttpRequest after 'send()' "
 | |
|         "(when its state is LOADING or DONE).");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   UniquePtr<MimeType> parsed = MimeType::Parse(aMimeType);
 | |
|   if (parsed) {
 | |
|     parsed->Serialize(mOverrideMimeType);
 | |
|   } else {
 | |
|     mOverrideMimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool XMLHttpRequestMainThread::MozBackgroundRequest() const {
 | |
|   return mFlagBackgroundRequest;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetMozBackgroundRequestExternal(
 | |
|     bool aMozBackgroundRequest, ErrorResult& aRv) {
 | |
|   if (!IsSystemXHR()) {
 | |
|     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mState != XMLHttpRequest_Binding::UNSENT) {
 | |
|     // Can't change this while we're in the middle of something.
 | |
|     aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mFlagBackgroundRequest = aMozBackgroundRequest;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetMozBackgroundRequest(
 | |
|     bool aMozBackgroundRequest, ErrorResult& aRv) {
 | |
|   // No errors for this webIDL method on main-thread.
 | |
|   SetMozBackgroundRequestExternal(aMozBackgroundRequest, IgnoreErrors());
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetOriginStack(
 | |
|     UniquePtr<SerializedStackHolder> aOriginStack) {
 | |
|   mOriginStack = std::move(aOriginStack);
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetSource(
 | |
|     UniquePtr<ProfileChunkedBuffer> aSource) {
 | |
|   if (!mChannel) {
 | |
|     return;
 | |
|   }
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
 | |
| 
 | |
|   if (httpChannel) {
 | |
|     httpChannel->SetSource(std::move(aSource));
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool XMLHttpRequestMainThread::WithCredentials() const {
 | |
|   return mFlagACwithCredentials;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
 | |
|                                                   ErrorResult& aRv) {
 | |
|   NOT_CALLABLE_IN_SYNC_SEND_RV
 | |
| 
 | |
|   // Return error if we're already processing a request.  Note that we can't use
 | |
|   // ReadyState() here, because it can't differentiate between "opened" and
 | |
|   // "sent", so we use mState directly.
 | |
| 
 | |
|   if ((mState != XMLHttpRequest_Binding::UNSENT &&
 | |
|        mState != XMLHttpRequest_Binding::OPENED) ||
 | |
|       mFlagSend || mIsAnon) {
 | |
|     aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mFlagACwithCredentials = aWithCredentials;
 | |
| }
 | |
| 
 | |
| nsresult XMLHttpRequestMainThread::ChangeState(uint16_t aState,
 | |
|                                                bool aBroadcast) {
 | |
|   mState = aState;
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   if (aState != XMLHttpRequest_Binding::HEADERS_RECEIVED &&
 | |
|       aState != XMLHttpRequest_Binding::LOADING) {
 | |
|     StopProgressEventTimer();
 | |
|   }
 | |
| 
 | |
|   if (aBroadcast &&
 | |
|       (!mFlagSynchronous || aState == XMLHttpRequest_Binding::OPENED ||
 | |
|        aState == XMLHttpRequest_Binding::DONE)) {
 | |
|     rv = FireReadystatechangeEvent();
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| /////////////////////////////////////////////////////
 | |
| // nsIChannelEventSink methods:
 | |
| //
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::AsyncOnChannelRedirect(
 | |
|     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
 | |
|     nsIAsyncVerifyRedirectCallback* callback) {
 | |
|   MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
 | |
| 
 | |
|   // Prepare to receive callback
 | |
|   mRedirectCallback = callback;
 | |
|   mNewRedirectChannel = aNewChannel;
 | |
| 
 | |
|   if (mChannelEventSink) {
 | |
|     nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd = EnsureXPCOMifier();
 | |
| 
 | |
|     nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(
 | |
|         aOldChannel, aNewChannel, aFlags, fwd);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       mRedirectCallback = nullptr;
 | |
|       mNewRedirectChannel = nullptr;
 | |
|     }
 | |
|     return rv;
 | |
|   }
 | |
|   OnRedirectVerifyCallback(NS_OK);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result) {
 | |
|   NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
 | |
|   NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
 | |
| 
 | |
|   if (NS_SUCCEEDED(result)) {
 | |
|     bool rewriteToGET = false;
 | |
|     nsCOMPtr<nsIHttpChannel> oldHttpChannel = GetCurrentHttpChannel();
 | |
|     // Fetch 4.4.11
 | |
|     Unused << oldHttpChannel->ShouldStripRequestBodyHeader(mRequestMethod,
 | |
|                                                            &rewriteToGET);
 | |
| 
 | |
|     mChannel = mNewRedirectChannel;
 | |
| 
 | |
|     nsCOMPtr<nsIHttpChannel> newHttpChannel(do_QueryInterface(mChannel));
 | |
|     if (newHttpChannel) {
 | |
|       // Ensure all original headers are duplicated for the new channel (bug
 | |
|       // #553888)
 | |
|       mAuthorRequestHeaders.ApplyToChannel(newHttpChannel, rewriteToGET);
 | |
|     }
 | |
|   } else {
 | |
|     mErrorLoad = ErrorType::eRedirect;
 | |
|   }
 | |
| 
 | |
|   mNewRedirectChannel = nullptr;
 | |
| 
 | |
|   mRedirectCallback->OnRedirectVerifyCallback(result);
 | |
|   mRedirectCallback = nullptr;
 | |
| 
 | |
|   // It's important that we return success here. If we return the result code
 | |
|   // that we were passed, JavaScript callers who cancel the redirect will wind
 | |
|   // up throwing an exception in the process.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /////////////////////////////////////////////////////
 | |
| // nsIProgressEventSink methods:
 | |
| //
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::OnProgress(nsIRequest* aRequest, int64_t aProgress,
 | |
|                                      int64_t aProgressMax) {
 | |
|   // When uploading, OnProgress reports also headers in aProgress and
 | |
|   // aProgressMax. So, try to remove the headers, if possible.
 | |
|   bool lengthComputable = (aProgressMax != -1);
 | |
|   if (InUploadPhase()) {
 | |
|     int64_t loaded = aProgress;
 | |
|     if (lengthComputable) {
 | |
|       int64_t headerSize = aProgressMax - mUploadTotal;
 | |
|       loaded -= headerSize;
 | |
|     }
 | |
|     mUploadTransferred = loaded;
 | |
|     mProgressSinceLastProgressEvent = true;
 | |
| 
 | |
|     if (!mFlagSynchronous && !mProgressTimerIsActive) {
 | |
|       StartProgressEventTimer();
 | |
|     }
 | |
|   } else {
 | |
|     mLoadTotal = aProgressMax;
 | |
|     mLoadTransferred = aProgress;
 | |
|     // OnDataAvailable() handles mProgressSinceLastProgressEvent
 | |
|     // for the download phase.
 | |
|   }
 | |
| 
 | |
|   if (mProgressEventSink) {
 | |
|     mProgressEventSink->OnProgress(aRequest, aProgress, aProgressMax);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::OnStatus(nsIRequest* aRequest, nsresult aStatus,
 | |
|                                    const char16_t* aStatusArg) {
 | |
|   if (mProgressEventSink) {
 | |
|     mProgressEventSink->OnStatus(aRequest, aStatus, aStatusArg);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool XMLHttpRequestMainThread::AllowUploadProgress() {
 | |
|   return !IsCrossSiteCORSRequest() || mFlagHadUploadListenersOnSend;
 | |
| }
 | |
| 
 | |
| /////////////////////////////////////////////////////
 | |
| // nsIInterfaceRequestor methods:
 | |
| //
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::GetInterface(const nsIID& aIID, void** aResult) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // Make sure to return ourselves for the channel event sink interface and
 | |
|   // progress event sink interface, no matter what.  We can forward these to
 | |
|   // mNotificationCallbacks if it wants to get notifications for them.  But we
 | |
|   // need to see these notifications for proper functioning.
 | |
|   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
 | |
|     mChannelEventSink = do_GetInterface(mNotificationCallbacks);
 | |
|     *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
 | |
|     return NS_OK;
 | |
|   } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
 | |
|     mProgressEventSink = do_GetInterface(mNotificationCallbacks);
 | |
|     *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Now give mNotificationCallbacks (if non-null) a chance to return the
 | |
|   // desired interface.
 | |
|   if (mNotificationCallbacks) {
 | |
|     rv = mNotificationCallbacks->GetInterface(aIID, aResult);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mFlagBackgroundRequest && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
 | |
|                                   aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
 | |
|     nsCOMPtr<nsIPromptFactory> wwatch =
 | |
|         do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     // Get the an auth prompter for our window so that the parenting
 | |
|     // of the dialogs works as it should when using tabs.
 | |
|     nsCOMPtr<nsPIDOMWindowOuter> window;
 | |
|     if (GetOwner()) {
 | |
|       window = GetOwner()->GetOuterWindow();
 | |
|     }
 | |
|     return wwatch->GetPrompt(window, aIID, reinterpret_cast<void**>(aResult));
 | |
|   }
 | |
| 
 | |
|   // Now check for the various XHR non-DOM interfaces, except
 | |
|   // nsIProgressEventSink and nsIChannelEventSink which we already
 | |
|   // handled above.
 | |
|   if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
 | |
|     *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
 | |
|     return NS_OK;
 | |
|   }
 | |
|   if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
 | |
|     *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
 | |
|     return NS_OK;
 | |
|   }
 | |
|   if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
 | |
|     *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   return QueryInterface(aIID, aResult);
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::GetInterface(
 | |
|     JSContext* aCx, JS::Handle<JS::Value> aIID,
 | |
|     JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
 | |
|   dom::GetInterface(aCx, this, aIID, aRetval, aRv);
 | |
| }
 | |
| 
 | |
| XMLHttpRequestUpload* XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv) {
 | |
|   if (!mUpload) {
 | |
|     mUpload = new XMLHttpRequestUpload(this);
 | |
|   }
 | |
|   return mUpload;
 | |
| }
 | |
| 
 | |
| bool XMLHttpRequestMainThread::MozAnon() const { return mIsAnon; }
 | |
| 
 | |
| bool XMLHttpRequestMainThread::MozSystem() const { return IsSystemXHR(); }
 | |
| 
 | |
| void XMLHttpRequestMainThread::HandleTimeoutCallback() {
 | |
|   if (mState == XMLHttpRequest_Binding::DONE) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "XMLHttpRequestMainThread::HandleTimeoutCallback "
 | |
|         "with completed request");
 | |
|     // do nothing!
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mFlagTimedOut = true;
 | |
|   CloseRequestWithError(ProgressEventType::timeout);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::Notify(nsITimer* aTimer) {
 | |
|   if (mProgressNotifier == aTimer) {
 | |
|     HandleProgressTimerCallback();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (mTimeoutTimer == aTimer) {
 | |
|     HandleTimeoutCallback();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (mSyncTimeoutTimer == aTimer) {
 | |
|     HandleSyncTimeoutTimer();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Just in case some JS user wants to QI to nsITimerCallback and play with
 | |
|   // us...
 | |
|   NS_WARNING("Unexpected timer!");
 | |
|   return NS_ERROR_INVALID_POINTER;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::HandleProgressTimerCallback() {
 | |
|   // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
 | |
|   if (!mLoadTotal && mLoadTransferred) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mProgressTimerIsActive = false;
 | |
| 
 | |
|   if (!mProgressSinceLastProgressEvent || mErrorLoad != ErrorType::eOK) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (InUploadPhase()) {
 | |
|     if (mUpload && !mUploadComplete && mFlagHadUploadListenersOnSend) {
 | |
|       DispatchProgressEvent(mUpload, ProgressEventType::progress,
 | |
|                             mUploadTransferred, mUploadTotal);
 | |
|     }
 | |
|   } else {
 | |
|     FireReadystatechangeEvent();
 | |
|     DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
 | |
|                           mLoadTotal);
 | |
|   }
 | |
| 
 | |
|   mProgressSinceLastProgressEvent = false;
 | |
| 
 | |
|   StartProgressEventTimer();
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::StopProgressEventTimer() {
 | |
|   if (mProgressNotifier) {
 | |
|     mProgressTimerIsActive = false;
 | |
|     mProgressNotifier->Cancel();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::StartProgressEventTimer() {
 | |
|   if (!mProgressNotifier) {
 | |
|     mProgressNotifier = NS_NewTimer(GetTimerEventTarget());
 | |
|   }
 | |
|   if (mProgressNotifier) {
 | |
|     mProgressTimerIsActive = true;
 | |
|     mProgressNotifier->Cancel();
 | |
|     mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
 | |
|                                         nsITimer::TYPE_ONE_SHOT);
 | |
|   }
 | |
| }
 | |
| 
 | |
| XMLHttpRequestMainThread::SyncTimeoutType
 | |
| XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer() {
 | |
|   MOZ_ASSERT(mFlagSynchronous);
 | |
| 
 | |
|   Document* doc = GetDocumentIfCurrent();
 | |
|   if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
 | |
|     return eNoTimerNeeded;
 | |
|   }
 | |
| 
 | |
|   // If we are in a beforeunload or a unload event, we must force a timeout.
 | |
|   TimeDuration diff =
 | |
|       (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
 | |
|   if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
 | |
|     return eErrorOrExpired;
 | |
|   }
 | |
| 
 | |
|   mSyncTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
 | |
|   if (!mSyncTimeoutTimer) {
 | |
|     return eErrorOrExpired;
 | |
|   }
 | |
| 
 | |
|   uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
 | |
|   nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
 | |
|                                                     nsITimer::TYPE_ONE_SHOT);
 | |
|   return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::HandleSyncTimeoutTimer() {
 | |
|   MOZ_ASSERT(mSyncTimeoutTimer);
 | |
|   MOZ_ASSERT(mFlagSyncLooping);
 | |
| 
 | |
|   CancelSyncTimeoutTimer();
 | |
|   Abort();
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::CancelSyncTimeoutTimer() {
 | |
|   if (mSyncTimeoutTimer) {
 | |
|     mSyncTimeoutTimer->Cancel();
 | |
|     mSyncTimeoutTimer = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsXMLHttpRequestXPCOMifier>
 | |
| XMLHttpRequestMainThread::EnsureXPCOMifier() {
 | |
|   if (!mXPCOMifier) {
 | |
|     mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
 | |
|   }
 | |
|   RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
 | |
|   return newRef.forget();
 | |
| }
 | |
| 
 | |
| bool XMLHttpRequestMainThread::ShouldBlockAuthPrompt() {
 | |
|   // Verify that it's ok to prompt for credentials here, per spec
 | |
|   // http://xhr.spec.whatwg.org/#the-send%28%29-method
 | |
| 
 | |
|   if (mAuthorRequestHeaders.Has("authorization")) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Also skip if a username and/or password is provided in the URI.
 | |
|   nsCString username;
 | |
|   rv = uri->GetUsername(username);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCString password;
 | |
|   rv = uri->GetPassword(password);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!username.IsEmpty() || !password.IsEmpty()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::TruncateResponseText() {
 | |
|   mResponseText.Truncate();
 | |
|   XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,
 | |
|                   nsIHttpHeaderVisitor)
 | |
| 
 | |
| NS_IMETHODIMP XMLHttpRequestMainThread::nsHeaderVisitor::VisitHeader(
 | |
|     const nsACString& header, const nsACString& value) {
 | |
|   if (mXHR.IsSafeHeader(header, mHttpChannel)) {
 | |
|     nsAutoCString lowerHeader(header);
 | |
|     ToLowerCase(lowerHeader);
 | |
|     if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
 | |
|                                          fallible)) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| XMLHttpRequestMainThread::nsHeaderVisitor::nsHeaderVisitor(
 | |
|     const XMLHttpRequestMainThread& aXMLHttpRequest,
 | |
|     NotNull<nsIHttpChannel*> aHttpChannel)
 | |
|     : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
 | |
| 
 | |
| XMLHttpRequestMainThread::nsHeaderVisitor::~nsHeaderVisitor() = default;
 | |
| 
 | |
| void XMLHttpRequestMainThread::MaybeCreateBlobStorage() {
 | |
|   MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
 | |
| 
 | |
|   if (mBlobStorage) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MutableBlobStorage::MutableBlobStorageType storageType =
 | |
|       BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
 | |
|           ? MutableBlobStorage::eCouldBeInTemporaryFile
 | |
|           : MutableBlobStorage::eOnlyInMemory;
 | |
| 
 | |
|   nsCOMPtr<nsIEventTarget> eventTarget;
 | |
|   if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
 | |
|     eventTarget = global->EventTargetFor(TaskCategory::Other);
 | |
|   }
 | |
| 
 | |
|   mBlobStorage = new MutableBlobStorage(storageType, eventTarget);
 | |
| }
 | |
| 
 | |
| void XMLHttpRequestMainThread::BlobStoreCompleted(
 | |
|     MutableBlobStorage* aBlobStorage, BlobImpl* aBlobImpl, nsresult aRv) {
 | |
|   // Ok, the state is changed...
 | |
|   if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
 | |
| 
 | |
|   mResponseBlobImpl = aBlobImpl;
 | |
|   mBlobStorage = nullptr;
 | |
| 
 | |
|   ChangeStateToDone(mFlagSyncLooping);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| XMLHttpRequestMainThread::GetName(nsACString& aName) {
 | |
|   aName.AssignLiteral("XMLHttpRequest");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // nsXMLHttpRequestXPCOMifier implementation
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsINamed)
 | |
|   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
 | |
| 
 | |
| // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
 | |
| // inheritance from nsISupports.
 | |
| NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
 | |
|   if (tmp->mXHR) {
 | |
|     tmp->mXHR->mXPCOMifier = nullptr;
 | |
|   }
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
 | |
|   // Return ourselves for the things we implement (except
 | |
|   // nsIInterfaceRequestor) and the XHR for the rest.
 | |
|   if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
 | |
|     nsresult rv = QueryInterface(aIID, aResult);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return mXHR->GetInterface(aIID, aResult);
 | |
| }
 | |
| 
 | |
| ArrayBufferBuilder::ArrayBufferBuilder()
 | |
|     : mMutex("ArrayBufferBuilder"),
 | |
|       mDataPtr(nullptr),
 | |
|       mCapacity(0),
 | |
|       mLength(0),
 | |
|       mMapPtr(nullptr),
 | |
|       mNeutered(false) {}
 | |
| 
 | |
| ArrayBufferBuilder::~ArrayBufferBuilder() {
 | |
|   if (mDataPtr) {
 | |
|     JS_free(nullptr, mDataPtr);
 | |
|   }
 | |
| 
 | |
|   if (mMapPtr) {
 | |
|     JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
 | |
|     mMapPtr = nullptr;
 | |
|   }
 | |
| 
 | |
|   mDataPtr = nullptr;
 | |
|   mCapacity = mLength = 0;
 | |
| }
 | |
| 
 | |
| bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return SetCapacityInternal(aNewCap, lock);
 | |
| }
 | |
| 
 | |
| bool ArrayBufferBuilder::SetCapacityInternal(
 | |
|     uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
 | |
|   MOZ_ASSERT(!mMapPtr);
 | |
|   MOZ_ASSERT(!mNeutered);
 | |
| 
 | |
|   // To ensure that realloc won't free mDataPtr, use a size of 1
 | |
|   // instead of 0.
 | |
|   uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
 | |
| 
 | |
|   if (!newdata) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (aNewCap > mCapacity) {
 | |
|     memset(newdata + mCapacity, 0, aNewCap - mCapacity);
 | |
|   }
 | |
| 
 | |
|   mDataPtr = newdata;
 | |
|   mCapacity = aNewCap;
 | |
|   if (mLength > aNewCap) {
 | |
|     mLength = aNewCap;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
 | |
|                                 uint32_t aMaxGrowth) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(!mMapPtr);
 | |
|   MOZ_ASSERT(!mNeutered);
 | |
| 
 | |
|   CheckedUint32 neededCapacity = mLength;
 | |
|   neededCapacity += aDataLen;
 | |
|   if (!neededCapacity.isValid()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (mLength + aDataLen > mCapacity) {
 | |
|     CheckedUint32 newcap = mCapacity;
 | |
|     // Double while under aMaxGrowth or if not specified.
 | |
|     if (!aMaxGrowth || mCapacity < aMaxGrowth) {
 | |
|       newcap *= 2;
 | |
|     } else {
 | |
|       newcap += aMaxGrowth;
 | |
|     }
 | |
| 
 | |
|     if (!newcap.isValid()) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // But make sure there's always enough to satisfy our request.
 | |
|     if (newcap.value() < neededCapacity.value()) {
 | |
|       newcap = neededCapacity;
 | |
|     }
 | |
| 
 | |
|     if (!SetCapacityInternal(newcap.value(), lock)) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Assert that the region isn't overlapping so we can memcpy.
 | |
|   MOZ_ASSERT(
 | |
|       !AreOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
 | |
| 
 | |
|   memcpy(mDataPtr + mLength, aNewData, aDataLen);
 | |
|   mLength += aDataLen;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| uint32_t ArrayBufferBuilder::Length() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(!mNeutered);
 | |
|   return mLength;
 | |
| }
 | |
| 
 | |
| uint32_t ArrayBufferBuilder::Capacity() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(!mNeutered);
 | |
|   return mCapacity;
 | |
| }
 | |
| 
 | |
| JSObject* ArrayBufferBuilder::TakeArrayBuffer(JSContext* aCx) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mNeutered);
 | |
| 
 | |
|   if (mMapPtr) {
 | |
|     JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
 | |
|     if (!obj) {
 | |
|       JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
 | |
|     }
 | |
| 
 | |
|     mMapPtr = nullptr;
 | |
|     mNeutered = true;
 | |
| 
 | |
|     // The memory-mapped contents will be released when the ArrayBuffer becomes
 | |
|     // detached or is GC'd.
 | |
|     return obj;
 | |
|   }
 | |
| 
 | |
|   // we need to check for mLength == 0, because nothing may have been
 | |
|   // added
 | |
|   if (mCapacity > mLength || mLength == 0) {
 | |
|     if (!SetCapacityInternal(mLength, lock)) {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   JSObject* obj = JS::NewArrayBufferWithContents(aCx, mLength, mDataPtr);
 | |
|   if (!obj) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   mDataPtr = nullptr;
 | |
|   mCapacity = mLength = 0;
 | |
| 
 | |
|   mNeutered = true;
 | |
|   return obj;
 | |
| }
 | |
| 
 | |
| nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
 | |
|                                                 nsIFile* aJarFile) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(!mNeutered);
 | |
| 
 | |
|   nsresult rv;
 | |
| 
 | |
|   // Open Jar file to get related attributes of target file.
 | |
|   RefPtr<nsZipArchive> zip = new nsZipArchive();
 | |
|   rv = zip->OpenArchive(aJarFile);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   nsZipItem* zipItem = zip->GetItem(aFile.get());
 | |
|   if (!zipItem) {
 | |
|     return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
 | |
|   }
 | |
| 
 | |
|   // If file was added to the package as stored(uncompressed), map to the
 | |
|   // offset of file in zip package.
 | |
|   if (!zipItem->Compression()) {
 | |
|     uint32_t offset = zip->GetDataOffset(zipItem);
 | |
|     uint32_t size = zipItem->RealSize();
 | |
|     mozilla::AutoFDClose pr_fd;
 | |
|     rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, &pr_fd.rwget());
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
|     mMapPtr = JS::CreateMappedArrayBufferContents(
 | |
|         PR_FileDesc2NativeHandle(pr_fd), offset, size);
 | |
|     if (mMapPtr) {
 | |
|       mLength = size;
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool ArrayBufferBuilder::AreOverlappingRegions(const uint8_t* aStart1,
 | |
|                                                uint32_t aLength1,
 | |
|                                                const uint8_t* aStart2,
 | |
|                                                uint32_t aLength2) {
 | |
|   const uint8_t* end1 = aStart1 + aLength1;
 | |
|   const uint8_t* end2 = aStart2 + aLength2;
 | |
| 
 | |
|   const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
 | |
|   const uint8_t* min_end = end1 < end2 ? end1 : end2;
 | |
| 
 | |
|   return max_start < min_end;
 | |
| }
 | |
| 
 | |
| RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
 | |
|   for (RequestHeaders::RequestHeader& header : mHeaders) {
 | |
|     if (header.mName.Equals(aName, nsCaseInsensitiveCStringComparator)) {
 | |
|       return &header;
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool RequestHeaders::IsEmpty() const { return mHeaders.IsEmpty(); }
 | |
| 
 | |
| bool RequestHeaders::Has(const char* aName) {
 | |
|   return Has(nsDependentCString(aName));
 | |
| }
 | |
| 
 | |
| bool RequestHeaders::Has(const nsACString& aName) { return !!Find(aName); }
 | |
| 
 | |
| void RequestHeaders::Get(const char* aName, nsACString& aValue) {
 | |
|   Get(nsDependentCString(aName), aValue);
 | |
| }
 | |
| 
 | |
| void RequestHeaders::Get(const nsACString& aName, nsACString& aValue) {
 | |
|   RequestHeader* header = Find(aName);
 | |
|   if (header) {
 | |
|     aValue = header->mValue;
 | |
|   } else {
 | |
|     aValue.SetIsVoid(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RequestHeaders::Set(const char* aName, const nsACString& aValue) {
 | |
|   Set(nsDependentCString(aName), aValue);
 | |
| }
 | |
| 
 | |
| void RequestHeaders::Set(const nsACString& aName, const nsACString& aValue) {
 | |
|   RequestHeader* header = Find(aName);
 | |
|   if (header) {
 | |
|     header->mValue.Assign(aValue);
 | |
|   } else {
 | |
|     RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
 | |
|     mHeaders.AppendElement(newHeader);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue) {
 | |
|   MergeOrSet(nsDependentCString(aName), aValue);
 | |
| }
 | |
| 
 | |
| void RequestHeaders::MergeOrSet(const nsACString& aName,
 | |
|                                 const nsACString& aValue) {
 | |
|   RequestHeader* header = Find(aName);
 | |
|   if (header) {
 | |
|     header->mValue.AppendLiteral(", ");
 | |
|     header->mValue.Append(aValue);
 | |
|   } else {
 | |
|     RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
 | |
|     mHeaders.AppendElement(newHeader);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RequestHeaders::Clear() { mHeaders.Clear(); }
 | |
| 
 | |
| void RequestHeaders::ApplyToChannel(nsIHttpChannel* aChannel,
 | |
|                                     bool aStripRequestBodyHeader) const {
 | |
|   for (const RequestHeader& header : mHeaders) {
 | |
|     if (aStripRequestBodyHeader &&
 | |
|         (header.mName.LowerCaseEqualsASCII("content-type") ||
 | |
|          header.mName.LowerCaseEqualsASCII("content-encoding") ||
 | |
|          header.mName.LowerCaseEqualsASCII("content-language") ||
 | |
|          header.mName.LowerCaseEqualsASCII("content-location"))) {
 | |
|       continue;
 | |
|     }
 | |
|     // Update referrerInfo to override referrer header in system privileged.
 | |
|     if (header.mName.LowerCaseEqualsASCII("referer")) {
 | |
|       DebugOnly<nsresult> rv = aChannel->SetNewReferrerInfo(
 | |
|           header.mValue, nsIReferrerInfo::ReferrerPolicyIDL::UNSAFE_URL, true);
 | |
|       MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|     }
 | |
|     if (header.mValue.IsEmpty()) {
 | |
|       DebugOnly<nsresult> rv = aChannel->SetEmptyRequestHeader(header.mName);
 | |
|       MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|     } else {
 | |
|       DebugOnly<nsresult> rv =
 | |
|           aChannel->SetRequestHeader(header.mName, header.mValue, false);
 | |
|       MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const {
 | |
|   for (const RequestHeader& header : mHeaders) {
 | |
|     if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName,
 | |
|                                                        header.mValue)) {
 | |
|       aArray.AppendElement(header.mName);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource)
 | |
|     : mValid(false),
 | |
|       mCurPos(-1),
 | |
|       mCurLen(-1),
 | |
|       mCutoff(aSource.Length()),
 | |
|       mSource(aSource) {}
 | |
| 
 | |
| bool RequestHeaders::CharsetIterator::Equals(
 | |
|     const nsACString& aOther, const nsCStringComparator& aCmp) const {
 | |
|   if (mValid) {
 | |
|     return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement) {
 | |
|   if (mValid) {
 | |
|     mSource.Replace(mCurPos, mCurLen, aReplacement);
 | |
|     mCurLen = aReplacement.Length();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool RequestHeaders::CharsetIterator::Next() {
 | |
|   int32_t start, end;
 | |
|   nsAutoCString charset;
 | |
| 
 | |
|   // Look for another charset declaration in the string, limiting the
 | |
|   // search to only the characters before the parts we've already searched
 | |
|   // (before mCutoff), so that we don't find the same charset twice.
 | |
|   NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff), charset,
 | |
|                                    &mValid, &start, &end);
 | |
| 
 | |
|   if (!mValid) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Everything after the = sign is the part of the charset we want.
 | |
|   mCurPos = mSource.FindChar('=', start) + 1;
 | |
|   mCurLen = end - mCurPos;
 | |
| 
 | |
|   // Special case: the extracted charset is quoted with single quotes.
 | |
|   // For the purpose of preserving what was set we want to handle them
 | |
|   // as delimiters (although they aren't really).
 | |
|   if (charset.Length() >= 2 && charset.First() == '\'' &&
 | |
|       charset.Last() == '\'') {
 | |
|     ++mCurPos;
 | |
|     mCurLen -= 2;
 | |
|   }
 | |
| 
 | |
|   mCutoff = start;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| }  // namespace dom
 | |
| }  // namespace mozilla
 | 
