michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsXMLHttpRequest.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/dom/XMLHttpRequestUploadBinding.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/EventListenerManager.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "nsDOMBlobBuilder.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMProgressEvent.h" michael@0: #include "nsIJARChannel.h" michael@0: #include "nsLayoutCID.h" michael@0: #include "nsReadableUtils.h" michael@0: michael@0: #include "nsIURI.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIUploadChannel.h" michael@0: #include "nsIUploadChannel2.h" michael@0: #include "nsIDOMSerializer.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIVariant.h" michael@0: #include "nsVariant.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsIContentPolicy.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsCrossSiteListenerProxy.h" michael@0: #include "nsIHTMLDocument.h" michael@0: #include "nsIStorageStream.h" michael@0: #include "nsIPromptFactory.h" michael@0: #include "nsIWindowWatcher.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIChannelPolicy.h" michael@0: #include "nsChannelPolicy.h" michael@0: #include "nsIContentSecurityPolicy.h" michael@0: #include "nsAsyncRedirectVerifyHelper.h" michael@0: #include "nsStringBuffer.h" michael@0: #include "nsDOMFile.h" michael@0: #include "nsIFileChannel.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "jsfriendapi.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "mozilla/dom/EncodingUtils.h" michael@0: #include "nsIUnicodeDecoder.h" michael@0: #include "mozilla/dom/XMLHttpRequestBinding.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include "nsFormData.h" michael@0: #include "nsStreamListenerWrapper.h" michael@0: #include "xpcjsid.h" michael@0: #include "nsITimedChannel.h" michael@0: michael@0: #include "nsWrapperCacheInlines.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: // Maximum size that we'll grow an ArrayBuffer instead of doubling, michael@0: // once doubling reaches this threshold michael@0: #define XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH (32*1024*1024) michael@0: // start at 32k to avoid lots of doubling right at the start michael@0: #define XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE (32*1024) michael@0: // the maximum Content-Length that we'll preallocate. 1GB. Must fit michael@0: // in an int32_t! michael@0: #define XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE (1*1024*1024*1024LL) michael@0: michael@0: #define LOAD_STR "load" michael@0: #define ERROR_STR "error" michael@0: #define ABORT_STR "abort" michael@0: #define TIMEOUT_STR "timeout" michael@0: #define LOADSTART_STR "loadstart" michael@0: #define PROGRESS_STR "progress" michael@0: #define READYSTATE_STR "readystatechange" michael@0: #define LOADEND_STR "loadend" michael@0: michael@0: // CIDs michael@0: michael@0: // State michael@0: #define XML_HTTP_REQUEST_UNSENT (1 << 0) // 0 UNSENT michael@0: #define XML_HTTP_REQUEST_OPENED (1 << 1) // 1 OPENED michael@0: #define XML_HTTP_REQUEST_HEADERS_RECEIVED (1 << 2) // 2 HEADERS_RECEIVED michael@0: #define XML_HTTP_REQUEST_LOADING (1 << 3) // 3 LOADING michael@0: #define XML_HTTP_REQUEST_DONE (1 << 4) // 4 DONE michael@0: #define XML_HTTP_REQUEST_SENT (1 << 5) // Internal, OPENED in IE and external view michael@0: // The above states are mutually exclusive, change with ChangeState() only. michael@0: // The states below can be combined. michael@0: #define XML_HTTP_REQUEST_ABORTED (1 << 7) // Internal michael@0: #define XML_HTTP_REQUEST_ASYNC (1 << 8) // Internal michael@0: #define XML_HTTP_REQUEST_PARSEBODY (1 << 9) // Internal michael@0: #define XML_HTTP_REQUEST_SYNCLOOPING (1 << 10) // Internal michael@0: #define XML_HTTP_REQUEST_BACKGROUND (1 << 13) // Internal michael@0: #define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 14) // Internal michael@0: #define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT (1 << 15) // Internal michael@0: #define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 16) // Internal michael@0: #define XML_HTTP_REQUEST_TIMED_OUT (1 << 17) // Internal michael@0: #define XML_HTTP_REQUEST_DELETED (1 << 18) // Internal michael@0: michael@0: #define XML_HTTP_REQUEST_LOADSTATES \ michael@0: (XML_HTTP_REQUEST_UNSENT | \ michael@0: XML_HTTP_REQUEST_OPENED | \ michael@0: XML_HTTP_REQUEST_HEADERS_RECEIVED | \ michael@0: XML_HTTP_REQUEST_LOADING | \ michael@0: XML_HTTP_REQUEST_DONE | \ michael@0: XML_HTTP_REQUEST_SENT) michael@0: michael@0: #define NS_BADCERTHANDLER_CONTRACTID \ michael@0: "@mozilla.org/content/xmlhttprequest-bad-cert-handler;1" michael@0: michael@0: #define NS_PROGRESS_EVENT_INTERVAL 50 michael@0: michael@0: #define IMPL_CSTRING_GETTER(_name) \ michael@0: NS_IMETHODIMP \ michael@0: nsXMLHttpRequest::_name(nsACString& aOut) \ michael@0: { \ michael@0: nsCString tmp; \ michael@0: _name(tmp); \ michael@0: aOut = tmp; \ michael@0: return NS_OK; \ michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener) michael@0: michael@0: class nsResumeTimeoutsEvent : public nsRunnable michael@0: { michael@0: public: michael@0: nsResumeTimeoutsEvent(nsPIDOMWindow* aWindow) : mWindow(aWindow) {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mWindow->ResumeTimeouts(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mWindow; michael@0: }; michael@0: michael@0: michael@0: // This helper function adds the given load flags to the request's existing michael@0: // load flags. michael@0: static void AddLoadFlags(nsIRequest *request, nsLoadFlags newFlags) michael@0: { michael@0: nsLoadFlags flags; michael@0: request->GetLoadFlags(&flags); michael@0: flags |= newFlags; michael@0: request->SetLoadFlags(flags); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // XMLHttpRequestAuthPrompt michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class XMLHttpRequestAuthPrompt : public nsIAuthPrompt michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIAUTHPROMPT michael@0: michael@0: XMLHttpRequestAuthPrompt(); michael@0: virtual ~XMLHttpRequestAuthPrompt(); michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(XMLHttpRequestAuthPrompt, nsIAuthPrompt) michael@0: michael@0: XMLHttpRequestAuthPrompt::XMLHttpRequestAuthPrompt() michael@0: { michael@0: MOZ_COUNT_CTOR(XMLHttpRequestAuthPrompt); michael@0: } michael@0: michael@0: XMLHttpRequestAuthPrompt::~XMLHttpRequestAuthPrompt() michael@0: { michael@0: MOZ_COUNT_DTOR(XMLHttpRequestAuthPrompt); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XMLHttpRequestAuthPrompt::Prompt(const char16_t* aDialogTitle, michael@0: const char16_t* aText, michael@0: const char16_t* aPasswordRealm, michael@0: uint32_t aSavePassword, michael@0: const char16_t* aDefaultText, michael@0: char16_t** aResult, michael@0: bool* aRetval) michael@0: { michael@0: *aRetval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XMLHttpRequestAuthPrompt::PromptUsernameAndPassword(const char16_t* aDialogTitle, michael@0: const char16_t* aDialogText, michael@0: const char16_t* aPasswordRealm, michael@0: uint32_t aSavePassword, michael@0: char16_t** aUser, michael@0: char16_t** aPwd, michael@0: bool* aRetval) michael@0: { michael@0: *aRetval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XMLHttpRequestAuthPrompt::PromptPassword(const char16_t* aDialogTitle, michael@0: const char16_t* aText, michael@0: const char16_t* aPasswordRealm, michael@0: uint32_t aSavePassword, michael@0: char16_t** aPwd, michael@0: bool* aRetval) michael@0: { michael@0: *aRetval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: ///////////////////////////////////////////// michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsXHREventTarget) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXHREventTarget, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXHREventTarget, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXHREventTarget) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestEventTarget) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsXHREventTarget, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(nsXHREventTarget, DOMEventTargetHelper) michael@0: michael@0: void michael@0: nsXHREventTarget::DisconnectFromOwner() michael@0: { michael@0: DOMEventTargetHelper::DisconnectFromOwner(); michael@0: } michael@0: michael@0: ///////////////////////////////////////////// michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsXMLHttpRequestUpload) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestUpload) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget) michael@0: NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget) michael@0: michael@0: /* virtual */ JSObject* michael@0: nsXMLHttpRequestUpload::WrapObject(JSContext* aCx) michael@0: { michael@0: return XMLHttpRequestUploadBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: ///////////////////////////////////////////// michael@0: // michael@0: // michael@0: ///////////////////////////////////////////// michael@0: michael@0: bool michael@0: nsXMLHttpRequest::sDontWarnAboutSyncXHR = false; michael@0: michael@0: nsXMLHttpRequest::nsXMLHttpRequest() michael@0: : mResponseBodyDecodedPos(0), michael@0: mResponseType(XML_HTTP_RESPONSE_TYPE_DEFAULT), michael@0: mRequestObserver(nullptr), mState(XML_HTTP_REQUEST_UNSENT), michael@0: mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true), michael@0: mProgressSinceLastProgressEvent(false), michael@0: mRequestSentTime(0), mTimeoutMilliseconds(0), michael@0: mErrorLoad(false), mWaitingForOnStopRequest(false), michael@0: mProgressTimerIsActive(false), michael@0: mIsHtml(false), michael@0: mWarnAboutSyncHtml(false), michael@0: mLoadLengthComputable(false), mLoadTotal(0), michael@0: mIsSystem(false), michael@0: mIsAnon(false), michael@0: mFirstStartRequestSeen(false), michael@0: mInLoadProgressEvent(false), michael@0: mResultJSON(JSVAL_VOID), michael@0: mResultArrayBuffer(nullptr), michael@0: mXPCOMifier(nullptr) michael@0: { michael@0: SetIsDOMBinding(); michael@0: #ifdef DEBUG michael@0: StaticAssertions(); michael@0: #endif michael@0: } michael@0: michael@0: nsXMLHttpRequest::~nsXMLHttpRequest() michael@0: { michael@0: mState |= XML_HTTP_REQUEST_DELETED; michael@0: michael@0: if (mState & (XML_HTTP_REQUEST_SENT | michael@0: XML_HTTP_REQUEST_LOADING)) { michael@0: Abort(); michael@0: } michael@0: michael@0: NS_ABORT_IF_FALSE(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), "we rather crash than hang"); michael@0: mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; michael@0: michael@0: mResultJSON = JSVAL_VOID; michael@0: mResultArrayBuffer = nullptr; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::RootJSResultObjects() michael@0: { michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: /** michael@0: * This Init method is called from the factory constructor. michael@0: */ michael@0: nsresult michael@0: nsXMLHttpRequest::Init() michael@0: { michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: nsCOMPtr subjectPrincipal; michael@0: if (secMan) { michael@0: secMan->GetSystemPrincipal(getter_AddRefs(subjectPrincipal)); michael@0: } michael@0: NS_ENSURE_STATE(subjectPrincipal); michael@0: michael@0: // Instead of grabbing some random global from the context stack, michael@0: // let's use the default one (junk scope) for now. michael@0: // We should move away from this Init... michael@0: nsCOMPtr global = xpc::GetJunkScopeGlobal(); michael@0: NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); michael@0: Construct(subjectPrincipal, global); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * This Init method should only be called by C++ consumers. michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::Init(nsIPrincipal* aPrincipal, michael@0: nsIScriptContext* aScriptContext, michael@0: nsIGlobalObject* aGlobalObject, michael@0: nsIURI* aBaseURI) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aPrincipal); michael@0: michael@0: if (nsCOMPtr win = do_QueryInterface(aGlobalObject)) { michael@0: if (win->IsOuterWindow()) { michael@0: // Must be bound to inner window, innerize if necessary. michael@0: nsCOMPtr inner = do_QueryInterface( michael@0: win->GetCurrentInnerWindow()); michael@0: aGlobalObject = inner.get(); michael@0: } michael@0: } michael@0: michael@0: Construct(aPrincipal, aGlobalObject, aBaseURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::InitParameters(bool aAnon, bool aSystem) michael@0: { michael@0: if (!aAnon && !aSystem) { michael@0: return; michael@0: } michael@0: michael@0: // Check for permissions. michael@0: nsCOMPtr window = do_QueryInterface(GetOwner()); michael@0: if (!window || !window->GetDocShell()) { michael@0: return; michael@0: } michael@0: michael@0: // Chrome is always allowed access, so do the permission check only michael@0: // for non-chrome pages. michael@0: if (!IsSystemXHR() && aSystem) { michael@0: nsCOMPtr doc = window->GetExtantDoc(); michael@0: if (!doc) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr principal = doc->NodePrincipal(); michael@0: nsCOMPtr permMgr = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: if (!permMgr) michael@0: return; michael@0: michael@0: uint32_t permission; michael@0: nsresult rv = michael@0: permMgr->TestPermissionFromPrincipal(principal, "systemXHR", &permission); michael@0: if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: SetParameters(aAnon, aSystem); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::ResetResponse() michael@0: { michael@0: mResponseXML = nullptr; michael@0: mResponseBody.Truncate(); michael@0: mResponseText.Truncate(); michael@0: mResponseBlob = nullptr; michael@0: mDOMFile = nullptr; michael@0: mBlobSet = nullptr; michael@0: mResultArrayBuffer = nullptr; michael@0: mArrayBufferBuilder.reset(); michael@0: mResultJSON = JSVAL_VOID; michael@0: mLoadTransferred = 0; michael@0: mResponseBodyDecodedPos = 0; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SetRequestObserver(nsIRequestObserver* aObserver) michael@0: { michael@0: mRequestObserver = aObserver; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequest) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXMLHttpRequest) michael@0: bool isBlack = tmp->IsBlack(); michael@0: if (isBlack || tmp->mWaitingForOnStopRequest) { michael@0: if (tmp->mListenerManager) { michael@0: tmp->mListenerManager->MarkForCC(); michael@0: } michael@0: if (!isBlack && tmp->PreservingWrapper()) { michael@0: // This marks the wrapper black. michael@0: tmp->GetWrapper(); michael@0: } michael@0: return true; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXMLHttpRequest) michael@0: return tmp-> michael@0: IsBlackAndDoesNotNeedTracing(static_cast(tmp)); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXMLHttpRequest) michael@0: return tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXMLHttpRequest, michael@0: nsXHREventTarget) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCORSPreflightChannel) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXMLHttpRequest, michael@0: nsXHREventTarget) michael@0: tmp->mResultArrayBuffer = nullptr; michael@0: tmp->mArrayBufferBuilder.reset(); michael@0: tmp->mResultJSON = JSVAL_VOID; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCORSPreflightChannel) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsXMLHttpRequest, michael@0: nsXHREventTarget) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResultJSON) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: // QueryInterface implementation for nsXMLHttpRequest michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXMLHttpRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIJSXMLHttpRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsITimerCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequest, nsXHREventTarget) michael@0: NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequest, nsXHREventTarget) michael@0: michael@0: NS_IMPL_EVENT_HANDLER(nsXMLHttpRequest, readystatechange) michael@0: michael@0: void michael@0: nsXMLHttpRequest::DisconnectFromOwner() michael@0: { michael@0: nsXHREventTarget::DisconnectFromOwner(); michael@0: Abort(); michael@0: } michael@0: michael@0: size_t michael@0: nsXMLHttpRequest::SizeOfEventTargetIncludingThis( michael@0: MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = aMallocSizeOf(this); michael@0: n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: michael@0: // Why is this safe? Because no-one else will report this string. The michael@0: // other possible sharers of this string are as follows. michael@0: // michael@0: // - The JS engine could hold copies if the JS code holds references, e.g. michael@0: // |var text = XHR.responseText|. However, those references will be via JS michael@0: // external strings, for which the JS memory reporter does *not* report the michael@0: // chars. michael@0: // michael@0: // - Binary extensions, but they're *extremely* unlikely to do any memory michael@0: // reporting. michael@0: // michael@0: n += mResponseText.SizeOfExcludingThisEvenIfShared(aMallocSizeOf); michael@0: michael@0: return n; michael@0: michael@0: // Measurement of the following members may be added later if DMD finds it is michael@0: // worthwhile: michael@0: // - lots michael@0: } michael@0: michael@0: /* readonly attribute nsIChannel channel; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetChannel(nsIChannel **aChannel) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aChannel); michael@0: NS_IF_ADDREF(*aChannel = mChannel); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void LogMessage(const char* aWarning, nsPIDOMWindow* aWindow) michael@0: { michael@0: nsCOMPtr doc; michael@0: if (aWindow) { michael@0: doc = aWindow->GetExtantDoc(); michael@0: } michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("DOM"), doc, michael@0: nsContentUtils::eDOM_PROPERTIES, michael@0: aWarning); michael@0: } michael@0: michael@0: /* readonly attribute nsIDOMDocument responseXML; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetResponseXML(nsIDOMDocument **aResponseXML) michael@0: { michael@0: ErrorResult rv; michael@0: nsIDocument* responseXML = GetResponseXML(rv); michael@0: if (rv.Failed()) { michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: if (!responseXML) { michael@0: *aResponseXML = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: return CallQueryInterface(responseXML, aResponseXML); michael@0: } michael@0: michael@0: nsIDocument* michael@0: nsXMLHttpRequest::GetResponseXML(ErrorResult& aRv) michael@0: { michael@0: if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT && michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_DOCUMENT) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return nullptr; michael@0: } michael@0: if (mWarnAboutSyncHtml) { michael@0: mWarnAboutSyncHtml = false; michael@0: LogMessage("HTMLSyncXHRWarning", GetOwner()); michael@0: } michael@0: return (XML_HTTP_REQUEST_DONE & mState) ? mResponseXML : nullptr; michael@0: } michael@0: michael@0: /* michael@0: * This piece copied from XMLDocument, we try to get the charset michael@0: * from HTTP headers. michael@0: */ michael@0: nsresult michael@0: nsXMLHttpRequest::DetectCharset() michael@0: { michael@0: mResponseCharset.Truncate(); michael@0: mDecoder = nullptr; michael@0: michael@0: if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT && michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_TEXT && michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_JSON && michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString charsetVal; michael@0: bool ok = mChannel && michael@0: NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) && michael@0: EncodingUtils::FindEncodingForLabel(charsetVal, mResponseCharset); michael@0: if (!ok || mResponseCharset.IsEmpty()) { michael@0: // MS documentation states UTF-8 is default for responseText michael@0: mResponseCharset.AssignLiteral("UTF-8"); michael@0: } michael@0: michael@0: if (mResponseType == XML_HTTP_RESPONSE_TYPE_JSON && michael@0: !mResponseCharset.EqualsLiteral("UTF-8")) { michael@0: // The XHR spec says only UTF-8 is supported for responseType == "json" michael@0: LogMessage("JSONCharsetWarning", GetOwner()); michael@0: mResponseCharset.AssignLiteral("UTF-8"); michael@0: } michael@0: michael@0: mDecoder = EncodingUtils::DecoderForEncoding(mResponseCharset); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::AppendToResponseText(const char * aSrcBuffer, michael@0: uint32_t aSrcBufferLen) michael@0: { michael@0: NS_ENSURE_STATE(mDecoder); michael@0: michael@0: int32_t destBufferLen; michael@0: nsresult rv = mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, michael@0: &destBufferLen); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mResponseText.SetCapacity(mResponseText.Length() + destBufferLen, fallible_t())) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: char16_t* destBuffer = mResponseText.BeginWriting() + mResponseText.Length(); michael@0: michael@0: int32_t totalChars = mResponseText.Length(); michael@0: michael@0: // This code here is basically a copy of a similar thing in michael@0: // nsScanner::Append(const char* aBuffer, uint32_t aLen). michael@0: int32_t srclen = (int32_t)aSrcBufferLen; michael@0: int32_t destlen = (int32_t)destBufferLen; michael@0: rv = mDecoder->Convert(aSrcBuffer, michael@0: &srclen, michael@0: destBuffer, michael@0: &destlen); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: totalChars += destlen; michael@0: michael@0: mResponseText.SetLength(totalChars); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute AString responseText; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetResponseText(nsAString& aResponseText) michael@0: { michael@0: ErrorResult rv; michael@0: nsString responseText; michael@0: GetResponseText(responseText, rv); michael@0: aResponseText = responseText; michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::GetResponseText(nsString& aResponseText, ErrorResult& aRv) michael@0: { michael@0: aResponseText.Truncate(); michael@0: michael@0: if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT && michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_TEXT && michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT && michael@0: !mInLoadProgressEvent) { michael@0: aResponseText.SetIsVoid(true); michael@0: return; michael@0: } michael@0: michael@0: if (!(mState & (XML_HTTP_REQUEST_DONE | XML_HTTP_REQUEST_LOADING))) { michael@0: return; michael@0: } michael@0: michael@0: // We only decode text lazily if we're also parsing to a doc. michael@0: // Also, if we've decoded all current data already, then no need to decode michael@0: // more. michael@0: if (!mResponseXML || michael@0: mResponseBodyDecodedPos == mResponseBody.Length()) { michael@0: aResponseText = mResponseText; michael@0: return; michael@0: } michael@0: michael@0: if (mResponseCharset != mResponseXML->GetDocumentCharacterSet()) { michael@0: mResponseCharset = mResponseXML->GetDocumentCharacterSet(); michael@0: mResponseText.Truncate(); michael@0: mResponseBodyDecodedPos = 0; michael@0: mDecoder = EncodingUtils::DecoderForEncoding(mResponseCharset); michael@0: } michael@0: michael@0: NS_ASSERTION(mResponseBodyDecodedPos < mResponseBody.Length(), michael@0: "Unexpected mResponseBodyDecodedPos"); michael@0: aRv = AppendToResponseText(mResponseBody.get() + mResponseBodyDecodedPos, michael@0: mResponseBody.Length() - mResponseBodyDecodedPos); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: mResponseBodyDecodedPos = mResponseBody.Length(); michael@0: michael@0: if (mState & XML_HTTP_REQUEST_DONE) { michael@0: // Free memory buffer which we no longer need michael@0: mResponseBody.Truncate(); michael@0: mResponseBodyDecodedPos = 0; michael@0: } michael@0: michael@0: aResponseText = mResponseText; michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::CreateResponseParsedJSON(JSContext* aCx) michael@0: { michael@0: if (!aCx) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: RootJSResultObjects(); michael@0: michael@0: // The Unicode converter has already zapped the BOM if there was one michael@0: JS::Rooted value(aCx); michael@0: if (!JS_ParseJSON(aCx, michael@0: static_cast(mResponseText.get()), mResponseText.Length(), michael@0: &value)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mResultJSON = value; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::CreatePartialBlob() michael@0: { michael@0: if (mDOMFile) { michael@0: if (mLoadTotal == mLoadTransferred) { michael@0: mResponseBlob = mDOMFile; michael@0: } else { michael@0: mResponseBlob = michael@0: mDOMFile->CreateSlice(0, mLoadTransferred, EmptyString()); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // mBlobSet can be null if the request has been canceled michael@0: if (!mBlobSet) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString contentType; michael@0: if (mLoadTotal == mLoadTransferred) { michael@0: mChannel->GetContentType(contentType); michael@0: } michael@0: michael@0: mResponseBlob = mBlobSet->GetBlobInternal(contentType); michael@0: } michael@0: michael@0: /* attribute AString responseType; */ michael@0: NS_IMETHODIMP nsXMLHttpRequest::GetResponseType(nsAString& aResponseType) michael@0: { michael@0: switch (mResponseType) { michael@0: case XML_HTTP_RESPONSE_TYPE_DEFAULT: michael@0: aResponseType.Truncate(); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER: michael@0: aResponseType.AssignLiteral("arraybuffer"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_BLOB: michael@0: aResponseType.AssignLiteral("blob"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_DOCUMENT: michael@0: aResponseType.AssignLiteral("document"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_TEXT: michael@0: aResponseType.AssignLiteral("text"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_JSON: michael@0: aResponseType.AssignLiteral("json"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT: michael@0: aResponseType.AssignLiteral("moz-chunked-text"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER: michael@0: aResponseType.AssignLiteral("moz-chunked-arraybuffer"); michael@0: break; michael@0: case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB: michael@0: aResponseType.AssignLiteral("moz-blob"); michael@0: break; michael@0: default: michael@0: NS_ERROR("Should not happen"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: nsXMLHttpRequest::StaticAssertions() michael@0: { michael@0: #define ASSERT_ENUM_EQUAL(_lc, _uc) \ michael@0: static_assert(\ michael@0: static_cast(XMLHttpRequestResponseType::_lc) \ michael@0: == XML_HTTP_RESPONSE_TYPE_ ## _uc, \ michael@0: #_uc " should match") michael@0: michael@0: ASSERT_ENUM_EQUAL(_empty, DEFAULT); michael@0: ASSERT_ENUM_EQUAL(Arraybuffer, ARRAYBUFFER); michael@0: ASSERT_ENUM_EQUAL(Blob, BLOB); michael@0: ASSERT_ENUM_EQUAL(Document, DOCUMENT); michael@0: ASSERT_ENUM_EQUAL(Json, JSON); michael@0: ASSERT_ENUM_EQUAL(Text, TEXT); michael@0: ASSERT_ENUM_EQUAL(Moz_chunked_text, CHUNKED_TEXT); michael@0: ASSERT_ENUM_EQUAL(Moz_chunked_arraybuffer, CHUNKED_ARRAYBUFFER); michael@0: ASSERT_ENUM_EQUAL(Moz_blob, MOZ_BLOB); michael@0: #undef ASSERT_ENUM_EQUAL michael@0: } michael@0: #endif michael@0: michael@0: /* attribute AString responseType; */ michael@0: NS_IMETHODIMP nsXMLHttpRequest::SetResponseType(const nsAString& aResponseType) michael@0: { michael@0: nsXMLHttpRequest::ResponseTypeEnum responseType; michael@0: if (aResponseType.IsEmpty()) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_DEFAULT; michael@0: } else if (aResponseType.EqualsLiteral("arraybuffer")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER; michael@0: } else if (aResponseType.EqualsLiteral("blob")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_BLOB; michael@0: } else if (aResponseType.EqualsLiteral("document")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_DOCUMENT; michael@0: } else if (aResponseType.EqualsLiteral("text")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_TEXT; michael@0: } else if (aResponseType.EqualsLiteral("json")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_JSON; michael@0: } else if (aResponseType.EqualsLiteral("moz-chunked-text")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT; michael@0: } else if (aResponseType.EqualsLiteral("moz-chunked-arraybuffer")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER; michael@0: } else if (aResponseType.EqualsLiteral("moz-blob")) { michael@0: responseType = XML_HTTP_RESPONSE_TYPE_MOZ_BLOB; michael@0: } else { michael@0: return NS_OK; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: SetResponseType(responseType, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SetResponseType(XMLHttpRequestResponseType aType, michael@0: ErrorResult& aRv) michael@0: { michael@0: SetResponseType(ResponseTypeEnum(static_cast(aType)), aRv); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SetResponseType(nsXMLHttpRequest::ResponseTypeEnum aResponseType, michael@0: ErrorResult& aRv) michael@0: { michael@0: // If the state is not OPENED or HEADERS_RECEIVED raise an michael@0: // INVALID_STATE_ERR exception and terminate these steps. michael@0: if (!(mState & (XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT | michael@0: XML_HTTP_REQUEST_HEADERS_RECEIVED))) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // sync request is not allowed setting responseType in window context michael@0: if (HasOrHasHadOwner() && michael@0: !(mState & (XML_HTTP_REQUEST_UNSENT | XML_HTTP_REQUEST_ASYNC))) { michael@0: LogMessage("ResponseTypeSyncXHRWarning", GetOwner()); michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!(mState & XML_HTTP_REQUEST_ASYNC) && michael@0: (aResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT || michael@0: aResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER)) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Set the responseType attribute's value to the given value. michael@0: mResponseType = aResponseType; michael@0: michael@0: } michael@0: michael@0: /* readonly attribute jsval response; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetResponse(JSContext *aCx, JS::MutableHandle aResult) michael@0: { michael@0: ErrorResult rv; michael@0: GetResponse(aCx, aResult, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::GetResponse(JSContext* aCx, michael@0: JS::MutableHandle aResponse, michael@0: ErrorResult& aRv) michael@0: { michael@0: switch (mResponseType) { michael@0: case XML_HTTP_RESPONSE_TYPE_DEFAULT: michael@0: case XML_HTTP_RESPONSE_TYPE_TEXT: michael@0: case XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT: michael@0: { michael@0: nsString str; michael@0: aRv = GetResponseText(str); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: if (!xpc::StringToJsval(aCx, str, aResponse)) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER: michael@0: case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER: michael@0: { michael@0: if (!(mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && michael@0: mState & XML_HTTP_REQUEST_DONE) && michael@0: !(mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER && michael@0: mInLoadProgressEvent)) { michael@0: aResponse.setNull(); michael@0: return; michael@0: } michael@0: michael@0: if (!mResultArrayBuffer) { michael@0: RootJSResultObjects(); michael@0: michael@0: mResultArrayBuffer = mArrayBufferBuilder.getArrayBuffer(aCx); michael@0: if (!mResultArrayBuffer) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: } michael@0: JS::ExposeObjectToActiveJS(mResultArrayBuffer); michael@0: aResponse.setObject(*mResultArrayBuffer); michael@0: return; michael@0: } michael@0: case XML_HTTP_RESPONSE_TYPE_BLOB: michael@0: case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB: michael@0: { michael@0: if (!(mState & XML_HTTP_REQUEST_DONE)) { michael@0: if (mResponseType != XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { michael@0: aResponse.setNull(); michael@0: return; michael@0: } michael@0: michael@0: if (!mResponseBlob) { michael@0: CreatePartialBlob(); michael@0: } michael@0: } michael@0: michael@0: if (!mResponseBlob) { michael@0: aResponse.setNull(); michael@0: return; michael@0: } michael@0: michael@0: aRv = nsContentUtils::WrapNative(aCx, mResponseBlob, aResponse); michael@0: return; michael@0: } michael@0: case XML_HTTP_RESPONSE_TYPE_DOCUMENT: michael@0: { michael@0: if (!(mState & XML_HTTP_REQUEST_DONE) || !mResponseXML) { michael@0: aResponse.setNull(); michael@0: return; michael@0: } michael@0: michael@0: aRv = nsContentUtils::WrapNative(aCx, mResponseXML, aResponse); michael@0: return; michael@0: } michael@0: case XML_HTTP_RESPONSE_TYPE_JSON: michael@0: { michael@0: if (!(mState & XML_HTTP_REQUEST_DONE)) { michael@0: aResponse.setNull(); michael@0: return; michael@0: } michael@0: michael@0: if (mResultJSON.isUndefined()) { michael@0: aRv = CreateResponseParsedJSON(aCx); michael@0: mResponseText.Truncate(); michael@0: if (aRv.Failed()) { michael@0: // Per spec, errors aren't propagated. null is returned instead. michael@0: aRv = NS_OK; michael@0: // It would be nice to log the error to the console. That's hard to michael@0: // do without calling window.onerror as a side effect, though. michael@0: JS_ClearPendingException(aCx); michael@0: mResultJSON.setNull(); michael@0: } michael@0: } michael@0: JS::ExposeValueToActiveJS(mResultJSON); michael@0: aResponse.set(mResultJSON); michael@0: return; michael@0: } michael@0: default: michael@0: NS_ERROR("Should not happen"); michael@0: } michael@0: michael@0: aResponse.setNull(); michael@0: } michael@0: michael@0: /* readonly attribute unsigned long status; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetStatus(uint32_t *aStatus) michael@0: { michael@0: *aStatus = Status(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsXMLHttpRequest::Status() michael@0: { michael@0: if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { michael@0: // Make sure we don't leak status information from denied cross-site michael@0: // requests. michael@0: if (mChannel) { michael@0: nsresult status; michael@0: mChannel->GetStatus(&status); michael@0: if (NS_FAILED(status)) { michael@0: return 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint16_t readyState; michael@0: GetReadyState(&readyState); michael@0: if (readyState == UNSENT || readyState == OPENED) { michael@0: return 0; michael@0: } michael@0: michael@0: if (mErrorLoad) { michael@0: // Let's simulate the http protocol for jar/app requests: michael@0: nsCOMPtr jarChannel = GetCurrentJARChannel(); michael@0: if (jarChannel) { michael@0: nsresult status; michael@0: mChannel->GetStatus(&status); michael@0: michael@0: if (status == NS_ERROR_FILE_NOT_FOUND) { michael@0: return 404; // Not Found michael@0: } else { michael@0: return 500; // Internal Error michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nsCOMPtr httpChannel = GetCurrentHttpChannel(); michael@0: if (!httpChannel) { michael@0: michael@0: // Let's simulate the http protocol for jar/app requests: michael@0: nsCOMPtr jarChannel = GetCurrentJARChannel(); michael@0: if (jarChannel) { michael@0: return 200; // Ok michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t status; michael@0: nsresult rv = httpChannel->GetResponseStatus(&status); michael@0: if (NS_FAILED(rv)) { michael@0: status = 0; michael@0: } michael@0: michael@0: return status; michael@0: } michael@0: michael@0: IMPL_CSTRING_GETTER(GetStatusText) michael@0: void michael@0: nsXMLHttpRequest::GetStatusText(nsCString& aStatusText) michael@0: { michael@0: nsCOMPtr httpChannel = GetCurrentHttpChannel(); michael@0: michael@0: aStatusText.Truncate(); michael@0: michael@0: if (!httpChannel) { michael@0: return; michael@0: } michael@0: michael@0: if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { michael@0: // Make sure we don't leak status information from denied cross-site michael@0: // requests. michael@0: if (mChannel) { michael@0: nsresult status; michael@0: mChannel->GetStatus(&status); michael@0: if (NS_FAILED(status)) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: httpChannel->GetResponseStatusText(aStatusText); michael@0: michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::CloseRequestWithError(const nsAString& aType, michael@0: const uint32_t aFlag) michael@0: { michael@0: if (mChannel) { michael@0: mChannel->Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: if (mCORSPreflightChannel) { michael@0: mCORSPreflightChannel->Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: if (mTimeoutTimer) { michael@0: mTimeoutTimer->Cancel(); michael@0: } michael@0: uint32_t responseLength = mResponseBody.Length(); michael@0: ResetResponse(); michael@0: mState |= aFlag; michael@0: michael@0: // If we're in the destructor, don't risk dispatching an event. michael@0: if (mState & XML_HTTP_REQUEST_DELETED) { michael@0: mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; michael@0: return; michael@0: } michael@0: michael@0: if (!(mState & (XML_HTTP_REQUEST_UNSENT | michael@0: XML_HTTP_REQUEST_OPENED | michael@0: XML_HTTP_REQUEST_DONE))) { michael@0: ChangeState(XML_HTTP_REQUEST_DONE, true); michael@0: michael@0: if (!(mState & XML_HTTP_REQUEST_SYNCLOOPING)) { michael@0: DispatchProgressEvent(this, aType, mLoadLengthComputable, responseLength, michael@0: mLoadTotal); michael@0: if (mUpload && !mUploadComplete) { michael@0: mUploadComplete = true; michael@0: DispatchProgressEvent(mUpload, aType, true, mUploadTransferred, michael@0: mUploadTotal); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The ChangeState call above calls onreadystatechange handlers which michael@0: // if they load a new url will cause nsXMLHttpRequest::Open to clear michael@0: // the abort state bit. If this occurs we're not uninitialized (bug 361773). michael@0: if (mState & XML_HTTP_REQUEST_ABORTED) { michael@0: ChangeState(XML_HTTP_REQUEST_UNSENT, false); // IE seems to do it michael@0: } michael@0: michael@0: mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; michael@0: } michael@0: michael@0: /* void abort (); */ michael@0: void michael@0: nsXMLHttpRequest::Abort() michael@0: { michael@0: CloseRequestWithError(NS_LITERAL_STRING(ABORT_STR), XML_HTTP_REQUEST_ABORTED); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SlowAbort() michael@0: { michael@0: Abort(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /*Method that checks if it is safe to expose a header value to the client. michael@0: It is used to check what headers are exposed for CORS requests.*/ michael@0: bool michael@0: nsXMLHttpRequest::IsSafeHeader(const nsACString& header, nsIHttpChannel* httpChannel) michael@0: { michael@0: // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts. michael@0: if (!IsSystemXHR() && michael@0: (header.LowerCaseEqualsASCII("set-cookie") || michael@0: header.LowerCaseEqualsASCII("set-cookie2"))) { michael@0: NS_WARNING("blocked access to response header"); michael@0: return false; michael@0: } michael@0: // if this is not a CORS call all headers are safe michael@0: if (!(mState & XML_HTTP_REQUEST_USE_XSITE_AC)){ michael@0: return true; michael@0: } michael@0: // Check for dangerous headers michael@0: // Make sure we don't leak header information from denied cross-site michael@0: // requests. michael@0: if (mChannel) { michael@0: nsresult status; michael@0: mChannel->GetStatus(&status); michael@0: if (NS_FAILED(status)) { michael@0: return false; michael@0: } michael@0: } michael@0: const char* kCrossOriginSafeHeaders[] = { michael@0: "cache-control", "content-language", "content-type", "expires", michael@0: "last-modified", "pragma" michael@0: }; michael@0: for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) { michael@0: if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) { michael@0: return true; michael@0: } michael@0: } michael@0: nsAutoCString headerVal; michael@0: // The "Access-Control-Expose-Headers" header contains a comma separated michael@0: // list of method names. michael@0: httpChannel-> michael@0: GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), michael@0: headerVal); michael@0: nsCCharSeparatedTokenizer exposeTokens(headerVal, ','); michael@0: bool isSafe = false; michael@0: while (exposeTokens.hasMoreTokens()) { michael@0: const nsDependentCSubstring& token = exposeTokens.nextToken(); michael@0: if (token.IsEmpty()) { michael@0: continue; michael@0: } michael@0: if (!IsValidHTTPToken(token)) { michael@0: return false; michael@0: } michael@0: if (header.Equals(token, nsCaseInsensitiveCStringComparator())) { michael@0: isSafe = true; michael@0: } michael@0: } michael@0: return isSafe; michael@0: } michael@0: michael@0: /* ByteString getAllResponseHeaders(); */ michael@0: IMPL_CSTRING_GETTER(GetAllResponseHeaders) michael@0: void michael@0: nsXMLHttpRequest::GetAllResponseHeaders(nsCString& aResponseHeaders) michael@0: { michael@0: aResponseHeaders.Truncate(); michael@0: michael@0: // If the state is UNSENT or OPENED, michael@0: // return the empty string and terminate these steps. michael@0: if (mState & (XML_HTTP_REQUEST_UNSENT | michael@0: XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) { michael@0: return; michael@0: } michael@0: michael@0: if (nsCOMPtr httpChannel = GetCurrentHttpChannel()) { michael@0: nsRefPtr visitor = new nsHeaderVisitor(this, httpChannel); michael@0: if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) { michael@0: aResponseHeaders = visitor->Headers(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (!mChannel) { michael@0: return; michael@0: } michael@0: michael@0: // Even non-http channels supply content type. michael@0: nsAutoCString value; michael@0: if (NS_SUCCEEDED(mChannel->GetContentType(value))) { michael@0: aResponseHeaders.AppendLiteral("Content-Type: "); michael@0: aResponseHeaders.Append(value); michael@0: if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) { michael@0: aResponseHeaders.AppendLiteral(";charset="); michael@0: aResponseHeaders.Append(value); michael@0: } michael@0: aResponseHeaders.AppendLiteral("\r\n"); michael@0: } michael@0: michael@0: int64_t length; michael@0: if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) { michael@0: aResponseHeaders.AppendLiteral("Content-Length: "); michael@0: aResponseHeaders.AppendInt(length); michael@0: aResponseHeaders.AppendLiteral("\r\n"); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetResponseHeader(const nsACString& aHeader, michael@0: nsACString& aResult) michael@0: { michael@0: ErrorResult rv; michael@0: GetResponseHeader(aHeader, aResult, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::GetResponseHeader(const nsACString& header, michael@0: nsACString& _retval, ErrorResult& aRv) michael@0: { michael@0: _retval.SetIsVoid(true); michael@0: michael@0: nsCOMPtr httpChannel = GetCurrentHttpChannel(); michael@0: michael@0: if (!httpChannel) { michael@0: // If the state is UNSENT or OPENED, michael@0: // return null and terminate these steps. michael@0: if (mState & (XML_HTTP_REQUEST_UNSENT | michael@0: XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) { michael@0: return; michael@0: } michael@0: michael@0: // Even non-http channels supply content type and content length. michael@0: // Remember we don't leak header information from denied cross-site michael@0: // requests. michael@0: nsresult status; michael@0: if (!mChannel || michael@0: NS_FAILED(mChannel->GetStatus(&status)) || michael@0: NS_FAILED(status)) { michael@0: return; michael@0: } michael@0: michael@0: // Content Type: michael@0: if (header.LowerCaseEqualsASCII("content-type")) { michael@0: if (NS_FAILED(mChannel->GetContentType(_retval))) { michael@0: // Means no content type michael@0: _retval.SetIsVoid(true); michael@0: return; michael@0: } michael@0: michael@0: nsCString value; michael@0: if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && michael@0: !value.IsEmpty()) { michael@0: _retval.Append(";charset="); michael@0: _retval.Append(value); michael@0: } michael@0: } michael@0: michael@0: // Content Length: michael@0: else if (header.LowerCaseEqualsASCII("content-length")) { michael@0: int64_t length; michael@0: if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) { michael@0: _retval.AppendInt(length); michael@0: } michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Check for dangerous headers michael@0: if (!IsSafeHeader(header, httpChannel)) { michael@0: return; michael@0: } michael@0: michael@0: aRv = httpChannel->GetResponseHeader(header, _retval); michael@0: if (aRv.ErrorCode() == NS_ERROR_NOT_AVAILABLE) { michael@0: // Means no header michael@0: _retval.SetIsVoid(true); michael@0: aRv = NS_OK; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXMLHttpRequest::GetLoadGroup() const michael@0: { michael@0: if (mState & XML_HTTP_REQUEST_BACKGROUND) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: nsIScriptContext* sc = michael@0: const_cast(this)->GetContextForEventHandlers(&rv); michael@0: nsCOMPtr doc = michael@0: nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: if (doc) { michael@0: return doc->GetDocumentLoadGroup(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::CreateReadystatechangeEvent(nsIDOMEvent** aDOMEvent) michael@0: { michael@0: nsresult rv = EventDispatcher::CreateEvent(this, nullptr, nullptr, michael@0: NS_LITERAL_STRING("Events"), michael@0: aDOMEvent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: (*aDOMEvent)->InitEvent(NS_LITERAL_STRING(READYSTATE_STR), michael@0: false, false); michael@0: michael@0: // We assume anyone who managed to call CreateReadystatechangeEvent is trusted michael@0: (*aDOMEvent)->SetTrusted(true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::DispatchProgressEvent(DOMEventTargetHelper* aTarget, michael@0: const nsAString& aType, michael@0: bool aLengthComputable, michael@0: uint64_t aLoaded, uint64_t aTotal) michael@0: { michael@0: NS_ASSERTION(aTarget, "null target"); michael@0: NS_ASSERTION(!aType.IsEmpty(), "missing event type"); michael@0: michael@0: if (NS_FAILED(CheckInnerWindowCorrectness()) || michael@0: (!AllowUploadProgress() && aTarget == mUpload)) { michael@0: return; michael@0: } michael@0: michael@0: bool dispatchLoadend = aType.EqualsLiteral(LOAD_STR) || michael@0: aType.EqualsLiteral(ERROR_STR) || michael@0: aType.EqualsLiteral(TIMEOUT_STR) || michael@0: aType.EqualsLiteral(ABORT_STR); michael@0: michael@0: nsCOMPtr event; michael@0: nsresult rv = NS_NewDOMProgressEvent(getter_AddRefs(event), this, michael@0: nullptr, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr progress = do_QueryInterface(event); michael@0: if (!progress) { michael@0: return; michael@0: } michael@0: michael@0: progress->InitProgressEvent(aType, false, false, aLengthComputable, michael@0: aLoaded, (aTotal == UINT64_MAX) ? 0 : aTotal); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: michael@0: if (dispatchLoadend) { michael@0: DispatchProgressEvent(aTarget, NS_LITERAL_STRING(LOADEND_STR), michael@0: aLengthComputable, aLoaded, aTotal); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXMLHttpRequest::GetCurrentHttpChannel() michael@0: { michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel); michael@0: return httpChannel.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXMLHttpRequest::GetCurrentJARChannel() michael@0: { michael@0: nsCOMPtr appChannel = do_QueryInterface(mChannel); michael@0: return appChannel.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsXMLHttpRequest::IsSystemXHR() michael@0: { michael@0: return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal); michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel) michael@0: { michael@0: // A system XHR (chrome code or a web app with the right permission) can michael@0: // always perform cross-site requests. In the web app case, however, we michael@0: // must still check for protected URIs like file:///. michael@0: if (IsSystemXHR()) { michael@0: if (!nsContentUtils::IsSystemPrincipal(mPrincipal)) { michael@0: nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); michael@0: nsCOMPtr uri; michael@0: aChannel->GetOriginalURI(getter_AddRefs(uri)); michael@0: return secMan->CheckLoadURIWithPrincipal( michael@0: mPrincipal, uri, nsIScriptSecurityManager::STANDARD); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If this is a same-origin request or the channel's URI inherits michael@0: // its principal, it's allowed. michael@0: if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This is a cross-site request michael@0: mState |= XML_HTTP_REQUEST_USE_XSITE_AC; michael@0: michael@0: // Check if we need to do a preflight request. michael@0: nsCOMPtr httpChannel = do_QueryInterface(aChannel); michael@0: NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI); michael@0: michael@0: nsAutoCString method; michael@0: httpChannel->GetRequestMethod(method); michael@0: if (!mCORSUnsafeHeaders.IsEmpty() || michael@0: (mUpload && mUpload->HasListeners()) || michael@0: (!method.LowerCaseEqualsLiteral("get") && michael@0: !method.LowerCaseEqualsLiteral("post") && michael@0: !method.LowerCaseEqualsLiteral("head"))) { michael@0: mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url, michael@0: bool async, const nsAString& user, michael@0: const nsAString& password, uint8_t optional_argc) michael@0: { michael@0: if (!optional_argc) { michael@0: // No optional arguments were passed in. Default async to true. michael@0: async = true; michael@0: } michael@0: Optional realUser; michael@0: if (optional_argc > 1) { michael@0: realUser = &user; michael@0: } michael@0: Optional realPassword; michael@0: if (optional_argc > 2) { michael@0: realPassword = &password; michael@0: } michael@0: return Open(method, url, async, realUser, realPassword); michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url, michael@0: bool async, const Optional& user, michael@0: const Optional& password) michael@0: { michael@0: NS_ENSURE_ARG(!inMethod.IsEmpty()); michael@0: michael@0: if (!async && !DontWarnAboutSyncXHR() && GetOwner() && michael@0: GetOwner()->GetExtantDoc()) { michael@0: GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest); michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC, michael@0: async ? 0 : 1); michael@0: michael@0: NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Disallow HTTP/1.1 TRACE method (see bug 302489) michael@0: // and MS IIS equivalent TRACK (see bug 381264) michael@0: // and CONNECT michael@0: if (inMethod.LowerCaseEqualsLiteral("trace") || michael@0: inMethod.LowerCaseEqualsLiteral("connect") || michael@0: inMethod.LowerCaseEqualsLiteral("track")) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsAutoCString method; michael@0: // GET, POST, DELETE, HEAD, OPTIONS, PUT methods normalized to upper case michael@0: if (inMethod.LowerCaseEqualsLiteral("get")) { michael@0: method.Assign(NS_LITERAL_CSTRING("GET")); michael@0: } else if (inMethod.LowerCaseEqualsLiteral("post")) { michael@0: method.Assign(NS_LITERAL_CSTRING("POST")); michael@0: } else if (inMethod.LowerCaseEqualsLiteral("delete")) { michael@0: method.Assign(NS_LITERAL_CSTRING("DELETE")); michael@0: } else if (inMethod.LowerCaseEqualsLiteral("head")) { michael@0: method.Assign(NS_LITERAL_CSTRING("HEAD")); michael@0: } else if (inMethod.LowerCaseEqualsLiteral("options")) { michael@0: method.Assign(NS_LITERAL_CSTRING("OPTIONS")); michael@0: } else if (inMethod.LowerCaseEqualsLiteral("put")) { michael@0: method.Assign(NS_LITERAL_CSTRING("PUT")); michael@0: } else { michael@0: method = inMethod; // other methods are not normalized michael@0: } michael@0: michael@0: // sync request is not allowed using withCredential or responseType michael@0: // in window context michael@0: if (!async && HasOrHasHadOwner() && michael@0: (mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS || michael@0: mTimeoutMilliseconds || michael@0: mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT)) { michael@0: if (mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS) { michael@0: LogMessage("WithCredentialsSyncXHRWarning", GetOwner()); michael@0: } michael@0: if (mTimeoutMilliseconds) { michael@0: LogMessage("TimeoutSyncXHRWarning", GetOwner()); michael@0: } michael@0: if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT) { michael@0: LogMessage("ResponseTypeSyncXHRWarning", GetOwner()); michael@0: } michael@0: return NS_ERROR_DOM_INVALID_ACCESS_ERR; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr uri; michael@0: michael@0: if (mState & (XML_HTTP_REQUEST_OPENED | michael@0: XML_HTTP_REQUEST_HEADERS_RECEIVED | michael@0: XML_HTTP_REQUEST_LOADING | michael@0: XML_HTTP_REQUEST_SENT)) { michael@0: // IE aborts as well michael@0: Abort(); michael@0: michael@0: // XXX We should probably send a warning to the JS console michael@0: // that load was aborted and event listeners were cleared michael@0: // since this looks like a situation that could happen michael@0: // by accident and you could spend a lot of time wondering michael@0: // why things didn't work. michael@0: } michael@0: michael@0: // Unset any pre-existing aborted and timed-out states. michael@0: mState &= ~XML_HTTP_REQUEST_ABORTED & ~XML_HTTP_REQUEST_TIMED_OUT; michael@0: michael@0: if (async) { michael@0: mState |= XML_HTTP_REQUEST_ASYNC; michael@0: } else { michael@0: mState &= ~XML_HTTP_REQUEST_ASYNC; michael@0: } michael@0: michael@0: nsIScriptContext* sc = GetContextForEventHandlers(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr doc = michael@0: nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: michael@0: nsCOMPtr baseURI; michael@0: if (mBaseURI) { michael@0: baseURI = mBaseURI; michael@0: } michael@0: else if (doc) { michael@0: baseURI = doc->GetBaseURI(); michael@0: } michael@0: michael@0: rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, baseURI); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = CheckInnerWindowCorrectness(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: int16_t shouldLoad = nsIContentPolicy::ACCEPT; michael@0: rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_XMLHTTPREQUEST, michael@0: uri, michael@0: mPrincipal, michael@0: doc, michael@0: EmptyCString(), //mime guess michael@0: nullptr, //extra michael@0: &shouldLoad, michael@0: nsContentUtils::GetContentPolicy(), michael@0: nsContentUtils::GetSecurityManager()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (NS_CP_REJECTED(shouldLoad)) { michael@0: // Disallowed by content policy michael@0: return NS_ERROR_CONTENT_BLOCKED; michael@0: } michael@0: michael@0: // XXXbz this is wrong: we should only be looking at whether michael@0: // user/password were passed, not at the values! See bug 759624. michael@0: if (user.WasPassed() && !user.Value().IsEmpty()) { michael@0: nsAutoCString userpass; michael@0: CopyUTF16toUTF8(user.Value(), userpass); michael@0: if (password.WasPassed() && !password.Value().IsEmpty()) { michael@0: userpass.Append(':'); michael@0: AppendUTF16toUTF8(password.Value(), userpass); michael@0: } michael@0: uri->SetUserPass(userpass); michael@0: } michael@0: michael@0: // Clear our record of previously set headers so future header set michael@0: // operations will merge/override correctly. michael@0: mAlreadySetHeaders.Clear(); michael@0: michael@0: // When we are called from JS we can find the load group for the page, michael@0: // and add ourselves to it. This way any pending requests michael@0: // will be automatically aborted if the user leaves the page. michael@0: nsCOMPtr loadGroup = GetLoadGroup(); michael@0: michael@0: // get Content Security Policy from principal to pass into channel michael@0: nsCOMPtr channelPolicy; michael@0: nsCOMPtr csp; michael@0: rv = mPrincipal->GetCsp(getter_AddRefs(csp)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (csp) { michael@0: channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); michael@0: channelPolicy->SetContentSecurityPolicy(csp); michael@0: channelPolicy->SetLoadType(nsIContentPolicy::TYPE_XMLHTTPREQUEST); michael@0: } michael@0: rv = NS_NewChannel(getter_AddRefs(mChannel), michael@0: uri, michael@0: nullptr, // ioService michael@0: loadGroup, michael@0: nullptr, // callbacks michael@0: nsIRequest::LOAD_BACKGROUND, michael@0: channelPolicy); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC | michael@0: XML_HTTP_REQUEST_NEED_AC_PREFLIGHT); michael@0: michael@0: nsCOMPtr httpChannel(do_QueryInterface(mChannel)); michael@0: if (httpChannel) { michael@0: rv = httpChannel->SetRequestMethod(method); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set the initiator type michael@0: nsCOMPtr timedChannel(do_QueryInterface(httpChannel)); michael@0: if (timedChannel) { michael@0: timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest")); michael@0: } michael@0: } michael@0: michael@0: ChangeState(XML_HTTP_REQUEST_OPENED); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* michael@0: * "Copy" from a stream. michael@0: */ michael@0: NS_METHOD michael@0: nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in, michael@0: void* closure, michael@0: const char* fromRawSegment, michael@0: uint32_t toOffset, michael@0: uint32_t count, michael@0: uint32_t *writeCount) michael@0: { michael@0: nsXMLHttpRequest* xmlHttpRequest = static_cast(closure); michael@0: if (!xmlHttpRequest || !writeCount) { michael@0: NS_WARNING("XMLHttpRequest cannot read from stream: no closure or writeCount"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || michael@0: xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { michael@0: if (!xmlHttpRequest->mDOMFile) { michael@0: if (!xmlHttpRequest->mBlobSet) { michael@0: xmlHttpRequest->mBlobSet = new BlobSet(); michael@0: } michael@0: rv = xmlHttpRequest->mBlobSet->AppendVoidPtr(fromRawSegment, count); michael@0: } michael@0: // Clear the cache so that the blob size is updated. michael@0: if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { michael@0: xmlHttpRequest->mResponseBlob = nullptr; michael@0: } michael@0: } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER || michael@0: xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) { michael@0: // get the initial capacity to something reasonable to avoid a bunch of reallocs right michael@0: // at the start michael@0: if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0) michael@0: xmlHttpRequest->mArrayBufferBuilder.setCapacity(PR_MAX(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE)); michael@0: michael@0: xmlHttpRequest->mArrayBufferBuilder.append(reinterpret_cast(fromRawSegment), count, michael@0: XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH); michael@0: } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT && michael@0: xmlHttpRequest->mResponseXML) { michael@0: // Copy for our own use michael@0: uint32_t previousLength = xmlHttpRequest->mResponseBody.Length(); michael@0: xmlHttpRequest->mResponseBody.Append(fromRawSegment,count); michael@0: if (count > 0 && xmlHttpRequest->mResponseBody.Length() == previousLength) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT || michael@0: xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_TEXT || michael@0: xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_JSON || michael@0: xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) { michael@0: NS_ASSERTION(!xmlHttpRequest->mResponseXML, michael@0: "We shouldn't be parsing a doc here"); michael@0: xmlHttpRequest->AppendToResponseText(fromRawSegment, count); michael@0: } michael@0: michael@0: if (xmlHttpRequest->mState & XML_HTTP_REQUEST_PARSEBODY) { michael@0: // Give the same data to the parser. michael@0: michael@0: // We need to wrap the data in a new lightweight stream and pass that michael@0: // to the parser, because calling ReadSegments() recursively on the same michael@0: // stream is not supported. michael@0: nsCOMPtr copyStream; michael@0: rv = NS_NewByteInputStream(getter_AddRefs(copyStream), fromRawSegment, count); michael@0: michael@0: if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) { michael@0: NS_ASSERTION(copyStream, "NS_NewByteInputStream lied"); michael@0: nsresult parsingResult = xmlHttpRequest->mXMLParserStreamListener michael@0: ->OnDataAvailable(xmlHttpRequest->mChannel, michael@0: xmlHttpRequest->mContext, michael@0: copyStream, toOffset, count); michael@0: michael@0: // No use to continue parsing if we failed here, but we michael@0: // should still finish reading the stream michael@0: if (NS_FAILED(parsingResult)) { michael@0: xmlHttpRequest->mState &= ~XML_HTTP_REQUEST_PARSEBODY; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *writeCount = count; michael@0: } else { michael@0: *writeCount = 0; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool nsXMLHttpRequest::CreateDOMFile(nsIRequest *request) michael@0: { michael@0: nsCOMPtr file; michael@0: nsCOMPtr fc = do_QueryInterface(request); michael@0: if (fc) { michael@0: fc->GetFile(getter_AddRefs(file)); michael@0: } michael@0: michael@0: if (!file) michael@0: return false; michael@0: michael@0: nsAutoCString contentType; michael@0: mChannel->GetContentType(contentType); michael@0: michael@0: mDOMFile = michael@0: new nsDOMFileFile(file, EmptyString(), NS_ConvertASCIItoUTF16(contentType)); michael@0: mBlobSet = nullptr; michael@0: NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::OnDataAvailable(nsIRequest *request, michael@0: nsISupports *ctxt, michael@0: nsIInputStream *inStr, michael@0: uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(inStr); michael@0: michael@0: NS_ABORT_IF_FALSE(mContext.get() == ctxt,"start context different from OnDataAvailable context"); michael@0: michael@0: mProgressSinceLastProgressEvent = true; michael@0: michael@0: bool cancelable = false; michael@0: if ((mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || michael@0: mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) && !mDOMFile) { michael@0: cancelable = CreateDOMFile(request); michael@0: // The nsIStreamListener contract mandates us michael@0: // to read from the stream before returning. michael@0: } michael@0: michael@0: uint32_t totalRead; michael@0: nsresult rv = inStr->ReadSegments(nsXMLHttpRequest::StreamReaderFunc, michael@0: (void*)this, count, &totalRead); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (cancelable) { michael@0: // We don't have to read from the local file for the blob response michael@0: mDOMFile->GetSize(&mLoadTransferred); michael@0: ChangeState(XML_HTTP_REQUEST_LOADING); michael@0: return request->Cancel(NS_OK); michael@0: } michael@0: michael@0: mLoadTransferred += totalRead; michael@0: michael@0: ChangeState(XML_HTTP_REQUEST_LOADING); michael@0: michael@0: MaybeDispatchProgressEvents(false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) michael@0: { michael@0: PROFILER_LABEL("nsXMLHttpRequest", "OnStartRequest"); michael@0: nsresult rv = NS_OK; michael@0: if (!mFirstStartRequestSeen && mRequestObserver) { michael@0: mFirstStartRequestSeen = true; michael@0: mRequestObserver->OnStartRequest(request, ctxt); michael@0: } michael@0: michael@0: if (request != mChannel) { michael@0: // Can this still happen? michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Don't do anything if we have been aborted michael@0: if (mState & XML_HTTP_REQUEST_UNSENT) michael@0: return NS_OK; michael@0: michael@0: /* Apparently, Abort() should set XML_HTTP_REQUEST_UNSENT. See bug 361773. michael@0: XHR2 spec says this is correct. */ michael@0: if (mState & XML_HTTP_REQUEST_ABORTED) { michael@0: NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!"); michael@0: michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Don't do anything if we have timed out. michael@0: if (mState & XML_HTTP_REQUEST_TIMED_OUT) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr channel(do_QueryInterface(request)); michael@0: NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr documentPrincipal; michael@0: if (IsSystemXHR()) { michael@0: // Don't give this document the system principal. We need to keep track of michael@0: // mPrincipal being system because we use it for various security checks michael@0: // that should be passing, but the document data shouldn't get a system michael@0: // principal. michael@0: nsresult rv; michael@0: documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: documentPrincipal = mPrincipal; michael@0: } michael@0: michael@0: channel->SetOwner(documentPrincipal); michael@0: michael@0: nsresult status; michael@0: request->GetStatus(&status); michael@0: mErrorLoad = mErrorLoad || NS_FAILED(status); michael@0: michael@0: if (mUpload && !mUploadComplete && !mErrorLoad && michael@0: (mState & XML_HTTP_REQUEST_ASYNC)) { michael@0: if (mProgressTimerIsActive) { michael@0: mProgressTimerIsActive = false; michael@0: mProgressNotifier->Cancel(); michael@0: } michael@0: if (mUploadTransferred < mUploadTotal) { michael@0: mUploadTransferred = mUploadTotal; michael@0: mProgressSinceLastProgressEvent = true; michael@0: mUploadLengthComputable = true; michael@0: MaybeDispatchProgressEvents(true); michael@0: } michael@0: mUploadComplete = true; michael@0: DispatchProgressEvent(mUpload, NS_LITERAL_STRING(LOAD_STR), michael@0: true, mUploadTotal, mUploadTotal); michael@0: } michael@0: michael@0: mContext = ctxt; michael@0: mState |= XML_HTTP_REQUEST_PARSEBODY; michael@0: ChangeState(XML_HTTP_REQUEST_HEADERS_RECEIVED); michael@0: michael@0: ResetResponse(); michael@0: michael@0: if (!mOverrideMimeType.IsEmpty()) { michael@0: channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType)); michael@0: } michael@0: michael@0: DetectCharset(); michael@0: michael@0: // Set up arraybuffer michael@0: if (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && NS_SUCCEEDED(status)) { michael@0: int64_t contentLength; michael@0: rv = channel->GetContentLength(&contentLength); michael@0: if (NS_SUCCEEDED(rv) && michael@0: contentLength > 0 && michael@0: contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) { michael@0: mArrayBufferBuilder.setCapacity(static_cast(contentLength)); michael@0: } michael@0: } michael@0: michael@0: // Set up responseXML michael@0: bool parseBody = mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT || michael@0: mResponseType == XML_HTTP_RESPONSE_TYPE_DOCUMENT; michael@0: nsCOMPtr httpChannel(do_QueryInterface(mChannel)); michael@0: if (parseBody && httpChannel) { michael@0: nsAutoCString method; michael@0: httpChannel->GetRequestMethod(method); michael@0: parseBody = !method.EqualsLiteral("HEAD"); michael@0: } michael@0: michael@0: mIsHtml = false; michael@0: mWarnAboutSyncHtml = false; michael@0: if (parseBody && NS_SUCCEEDED(status)) { michael@0: // We can gain a huge performance win by not even trying to michael@0: // parse non-XML data. This also protects us from the situation michael@0: // where we have an XML document and sink, but HTML (or other) michael@0: // parser, which can produce unreliable results. michael@0: nsAutoCString type; michael@0: channel->GetContentType(type); michael@0: michael@0: if ((mResponseType == XML_HTTP_RESPONSE_TYPE_DOCUMENT) && michael@0: type.EqualsLiteral("text/html")) { michael@0: // HTML parsing is only supported for responseType == "document" to michael@0: // avoid running the parser and, worse, populating responseXML for michael@0: // legacy users of XHR who use responseType == "" for retrieving the michael@0: // responseText of text/html resources. This legacy case is so common michael@0: // that it's not useful to emit a warning about it. michael@0: if (!(mState & XML_HTTP_REQUEST_ASYNC)) { michael@0: // We don't make cool new features available in the bad synchronous michael@0: // mode. The synchronous mode is for legacy only. michael@0: mWarnAboutSyncHtml = true; michael@0: mState &= ~XML_HTTP_REQUEST_PARSEBODY; michael@0: } else { michael@0: mIsHtml = true; michael@0: } michael@0: } else if (type.Find("xml") == kNotFound) { michael@0: mState &= ~XML_HTTP_REQUEST_PARSEBODY; michael@0: } michael@0: } else { michael@0: // The request failed, so we shouldn't be parsing anyway michael@0: mState &= ~XML_HTTP_REQUEST_PARSEBODY; michael@0: } michael@0: michael@0: if (mState & XML_HTTP_REQUEST_PARSEBODY) { michael@0: nsCOMPtr baseURI, docURI; michael@0: rv = mChannel->GetURI(getter_AddRefs(docURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: baseURI = docURI; michael@0: michael@0: nsIScriptContext* sc = GetContextForEventHandlers(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr doc = michael@0: nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: nsCOMPtr chromeXHRDocURI, chromeXHRDocBaseURI; michael@0: if (doc) { michael@0: chromeXHRDocURI = doc->GetDocumentURI(); michael@0: chromeXHRDocBaseURI = doc->GetBaseURI(); michael@0: } michael@0: michael@0: // Create an empty document from it. Here we have to cheat a little bit... michael@0: // Setting the base URI to |baseURI| won't work if the document has a null michael@0: // principal, so use mPrincipal when creating the document, then reset the michael@0: // principal. michael@0: const nsAString& emptyStr = EmptyString(); michael@0: nsCOMPtr responseDoc; michael@0: nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject(); michael@0: rv = NS_NewDOMDocument(getter_AddRefs(responseDoc), michael@0: emptyStr, emptyStr, nullptr, docURI, michael@0: baseURI, mPrincipal, true, global, michael@0: mIsHtml ? DocumentFlavorHTML : michael@0: DocumentFlavorLegacyGuess); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mResponseXML = do_QueryInterface(responseDoc); michael@0: mResponseXML->SetPrincipal(documentPrincipal); michael@0: mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI); michael@0: mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI); michael@0: michael@0: if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { michael@0: mResponseXML->ForceEnableXULXBL(); michael@0: } michael@0: michael@0: if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { michael@0: nsCOMPtr htmlDoc = do_QueryInterface(mResponseXML); michael@0: if (htmlDoc) { michael@0: htmlDoc->DisableCookieAccess(); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr listener; michael@0: nsCOMPtr loadGroup; michael@0: channel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: michael@0: rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup, michael@0: nullptr, getter_AddRefs(listener), michael@0: !(mState & XML_HTTP_REQUEST_USE_XSITE_AC)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mXMLParserStreamListener = listener; michael@0: rv = mXMLParserStreamListener->OnStartRequest(request, ctxt); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // We won't get any progress events anyway if we didn't have progress michael@0: // events when starting the request - so maybe no need to start timer here. michael@0: if (NS_SUCCEEDED(rv) && michael@0: (mState & XML_HTTP_REQUEST_ASYNC) && michael@0: HasListenersFor(nsGkAtoms::onprogress)) { michael@0: StartProgressEventTimer(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status, in wstring statusArg); */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) michael@0: { michael@0: PROFILER_LABEL("content", "nsXMLHttpRequest::OnStopRequest"); michael@0: if (request != mChannel) { michael@0: // Can this still happen? michael@0: return NS_OK; michael@0: } michael@0: michael@0: mWaitingForOnStopRequest = false; michael@0: michael@0: if (mRequestObserver) { michael@0: NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!"); michael@0: mFirstStartRequestSeen = false; michael@0: mRequestObserver->OnStopRequest(request, ctxt, status); michael@0: } michael@0: michael@0: // make sure to notify the listener if we were aborted michael@0: // XXX in fact, why don't we do the cleanup below in this case?? michael@0: // XML_HTTP_REQUEST_UNSENT is for abort calls. See OnStartRequest above. michael@0: if ((mState & XML_HTTP_REQUEST_UNSENT) || michael@0: (mState & XML_HTTP_REQUEST_TIMED_OUT)) { michael@0: if (mXMLParserStreamListener) michael@0: (void) mXMLParserStreamListener->OnStopRequest(request, ctxt, status); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Is this good enough here? michael@0: if (mState & XML_HTTP_REQUEST_PARSEBODY && mXMLParserStreamListener) { michael@0: mXMLParserStreamListener->OnStopRequest(request, ctxt, status); michael@0: } michael@0: michael@0: mXMLParserStreamListener = nullptr; michael@0: mContext = nullptr; michael@0: michael@0: // If we're received data since the last progress event, make sure to fire michael@0: // an event for it, except in the HTML case, defer the last progress event michael@0: // until the parser is done. michael@0: if (!mIsHtml) { michael@0: MaybeDispatchProgressEvents(true); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(status) && michael@0: (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || michael@0: mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB)) { michael@0: if (!mDOMFile) { michael@0: CreateDOMFile(request); michael@0: } michael@0: if (mDOMFile) { michael@0: mResponseBlob = mDOMFile; michael@0: mDOMFile = nullptr; michael@0: } else { michael@0: // mBlobSet can be null if the channel is non-file non-cacheable michael@0: // and if the response length is zero. michael@0: if (!mBlobSet) { michael@0: mBlobSet = new BlobSet(); michael@0: } michael@0: // Smaller files may be written in cache map instead of separate files. michael@0: // Also, no-store response cannot be written in persistent cache. michael@0: nsAutoCString contentType; michael@0: mChannel->GetContentType(contentType); michael@0: mResponseBlob = mBlobSet->GetBlobInternal(contentType); michael@0: mBlobSet = nullptr; michael@0: } michael@0: NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); michael@0: NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty"); michael@0: } else if (NS_SUCCEEDED(status) && michael@0: (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER || michael@0: mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER)) { michael@0: // set the capacity down to the actual length, to realloc back michael@0: // down to the actual size michael@0: if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) { michael@0: // this should never happen! michael@0: status = NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr channel(do_QueryInterface(request)); michael@0: NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); michael@0: michael@0: channel->SetNotificationCallbacks(nullptr); michael@0: mNotificationCallbacks = nullptr; michael@0: mChannelEventSink = nullptr; michael@0: mProgressEventSink = nullptr; michael@0: michael@0: mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; michael@0: michael@0: if (NS_FAILED(status)) { michael@0: // This can happen if the server is unreachable. Other possible michael@0: // reasons are that the user leaves the page or hits the ESC key. michael@0: michael@0: mErrorLoad = true; michael@0: mResponseXML = nullptr; michael@0: } michael@0: michael@0: // If we're uninitialized at this point, we encountered an error michael@0: // earlier and listeners have already been notified. Also we do michael@0: // not want to do this if we already completed. michael@0: if (mState & (XML_HTTP_REQUEST_UNSENT | michael@0: XML_HTTP_REQUEST_DONE)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mResponseXML) { michael@0: ChangeStateToDone(); michael@0: return NS_OK; michael@0: } michael@0: if (mIsHtml) { michael@0: NS_ASSERTION(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), michael@0: "We weren't supposed to support HTML parsing with XHR!"); michael@0: nsCOMPtr eventTarget = do_QueryInterface(mResponseXML); michael@0: EventListenerManager* manager = michael@0: eventTarget->GetOrCreateListenerManager(); michael@0: manager->AddEventListenerByType(new nsXHRParseEndListener(this), michael@0: NS_LITERAL_STRING("DOMContentLoaded"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: return NS_OK; michael@0: } michael@0: // We might have been sent non-XML data. If that was the case, michael@0: // we should null out the document member. The idea in this michael@0: // check here is that if there is no document element it is not michael@0: // an XML document. We might need a fancier check... michael@0: if (!mResponseXML->GetRootElement()) { michael@0: mResponseXML = nullptr; michael@0: } michael@0: ChangeStateToDone(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::ChangeStateToDone() michael@0: { michael@0: if (mIsHtml) { michael@0: // In the HTML case, this has to be deferred, because the parser doesn't michael@0: // do it's job synchronously. michael@0: MaybeDispatchProgressEvents(true); michael@0: } michael@0: michael@0: ChangeState(XML_HTTP_REQUEST_DONE, true); michael@0: if (mTimeoutTimer) { michael@0: mTimeoutTimer->Cancel(); michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(errorStr, ERROR_STR); michael@0: NS_NAMED_LITERAL_STRING(loadStr, LOAD_STR); michael@0: DispatchProgressEvent(this, michael@0: mErrorLoad ? errorStr : loadStr, michael@0: !mErrorLoad, michael@0: mLoadTransferred, michael@0: mErrorLoad ? 0 : mLoadTransferred); michael@0: if (mErrorLoad && mUpload && !mUploadComplete) { michael@0: DispatchProgressEvent(mUpload, errorStr, true, michael@0: mUploadTransferred, mUploadTotal); michael@0: } michael@0: michael@0: if (mErrorLoad) { michael@0: // By nulling out channel here we make it so that Send() can test michael@0: // for that and throw. Also calling the various status michael@0: // methods/members will not throw. michael@0: // This matches what IE does. michael@0: mChannel = nullptr; michael@0: mCORSPreflightChannel = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SendAsBinary(const nsAString &aBody) michael@0: { michael@0: ErrorResult rv; michael@0: SendAsBinary(aBody, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SendAsBinary(const nsAString &aBody, michael@0: ErrorResult& aRv) michael@0: { michael@0: char *data = static_cast(NS_Alloc(aBody.Length() + 1)); michael@0: if (!data) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: michael@0: if (GetOwner() && GetOwner()->GetExtantDoc()) { michael@0: GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSendAsBinary); michael@0: } michael@0: michael@0: nsAString::const_iterator iter, end; michael@0: aBody.BeginReading(iter); michael@0: aBody.EndReading(end); michael@0: char *p = data; michael@0: while (iter != end) { michael@0: if (*iter & 0xFF00) { michael@0: NS_Free(data); michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); michael@0: return; michael@0: } michael@0: *p++ = static_cast(*iter++); michael@0: } michael@0: *p = '\0'; michael@0: michael@0: nsCOMPtr stream; michael@0: aRv = NS_NewByteInputStream(getter_AddRefs(stream), data, aBody.Length(), michael@0: NS_ASSIGNMENT_ADOPT); michael@0: if (aRv.Failed()) { michael@0: NS_Free(data); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr variant = new nsVariant(); michael@0: michael@0: aRv = variant->SetAsISupports(stream); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: aRv = Send(variant); michael@0: } michael@0: michael@0: static nsresult michael@0: GetRequestBody(nsIDOMDocument* aDoc, nsIInputStream** aResult, michael@0: uint64_t* aContentLength, nsACString& aContentType, michael@0: nsACString& aCharset) michael@0: { michael@0: aContentType.AssignLiteral("application/xml"); michael@0: nsAutoString inputEncoding; michael@0: aDoc->GetInputEncoding(inputEncoding); michael@0: if (!DOMStringIsNull(inputEncoding)) { michael@0: CopyUTF16toUTF8(inputEncoding, aCharset); michael@0: } michael@0: else { michael@0: aCharset.AssignLiteral("UTF-8"); michael@0: } michael@0: michael@0: // Serialize to a stream so that the encoding used will michael@0: // match the document's. michael@0: nsresult rv; michael@0: nsCOMPtr serializer = michael@0: do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr storStream; michael@0: rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr output; michael@0: rv = storStream->GetOutputStream(0, getter_AddRefs(output)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make sure to use the encoding we'll send michael@0: rv = serializer->SerializeToStream(aDoc, output, aCharset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: output->Close(); michael@0: michael@0: uint32_t length; michael@0: rv = storStream->GetLength(&length); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *aContentLength = length; michael@0: michael@0: return storStream->NewInputStream(0, aResult); michael@0: } michael@0: michael@0: static nsresult michael@0: GetRequestBody(const nsAString& aString, nsIInputStream** aResult, michael@0: uint64_t* aContentLength, nsACString& aContentType, michael@0: nsACString& aCharset) michael@0: { michael@0: aContentType.AssignLiteral("text/plain"); michael@0: aCharset.AssignLiteral("UTF-8"); michael@0: michael@0: nsCString converted = NS_ConvertUTF16toUTF8(aString); michael@0: *aContentLength = converted.Length(); michael@0: return NS_NewCStringInputStream(aResult, converted); michael@0: } michael@0: michael@0: static nsresult michael@0: GetRequestBody(nsIInputStream* aStream, nsIInputStream** aResult, michael@0: uint64_t* aContentLength, nsACString& aContentType, michael@0: nsACString& aCharset) michael@0: { michael@0: aContentType.AssignLiteral("text/plain"); michael@0: aCharset.Truncate(); michael@0: michael@0: nsresult rv = aStream->Available(aContentLength); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ADDREF(*aResult = aStream); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: GetRequestBody(nsIXHRSendable* aSendable, nsIInputStream** aResult, uint64_t* aContentLength, michael@0: nsACString& aContentType, nsACString& aCharset) michael@0: { michael@0: return aSendable->GetSendInfo(aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: // Used for array buffers and array buffer views michael@0: static nsresult michael@0: GetRequestBody(const uint8_t* aData, uint32_t aDataLength, michael@0: nsIInputStream** aResult, uint64_t* aContentLength, michael@0: nsACString& aContentType, nsACString& aCharset) michael@0: { michael@0: aContentType.SetIsVoid(true); michael@0: aCharset.Truncate(); michael@0: michael@0: *aContentLength = aDataLength; michael@0: const char* data = reinterpret_cast(aData); michael@0: michael@0: nsCOMPtr stream; michael@0: nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength, michael@0: NS_ASSIGNMENT_COPY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stream.forget(aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: GetRequestBody(nsIVariant* aBody, nsIInputStream** aResult, uint64_t* aContentLength, michael@0: nsACString& aContentType, nsACString& aCharset) michael@0: { michael@0: *aResult = nullptr; michael@0: michael@0: uint16_t dataType; michael@0: nsresult rv = aBody->GetDataType(&dataType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (dataType == nsIDataType::VTYPE_INTERFACE || michael@0: dataType == nsIDataType::VTYPE_INTERFACE_IS) { michael@0: nsCOMPtr supports; michael@0: nsID *iid; michael@0: rv = aBody->GetAsInterface(&iid, getter_AddRefs(supports)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsMemory::Free(iid); michael@0: michael@0: // document? michael@0: nsCOMPtr doc = do_QueryInterface(supports); michael@0: if (doc) { michael@0: return GetRequestBody(doc, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: // nsISupportsString? michael@0: nsCOMPtr wstr = do_QueryInterface(supports); michael@0: if (wstr) { michael@0: nsAutoString string; michael@0: wstr->GetData(string); michael@0: michael@0: return GetRequestBody(string, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: // nsIInputStream? michael@0: nsCOMPtr stream = do_QueryInterface(supports); michael@0: if (stream) { michael@0: return GetRequestBody(stream, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: // nsIXHRSendable? michael@0: nsCOMPtr sendable = do_QueryInterface(supports); michael@0: if (sendable) { michael@0: return GetRequestBody(sendable, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: // ArrayBuffer? michael@0: AutoSafeJSContext cx; michael@0: JS::Rooted realVal(cx); michael@0: michael@0: nsresult rv = aBody->GetAsJSVal(&realVal); michael@0: if (NS_SUCCEEDED(rv) && !JSVAL_IS_PRIMITIVE(realVal)) { michael@0: JS::Rooted obj(cx, JSVAL_TO_OBJECT(realVal)); michael@0: if (JS_IsArrayBufferObject(obj)) { michael@0: ArrayBuffer buf(obj); michael@0: buf.ComputeLengthAndData(); michael@0: return GetRequestBody(buf.Data(), buf.Length(), aResult, michael@0: aContentLength, aContentType, aCharset); michael@0: } michael@0: } michael@0: } michael@0: else if (dataType == nsIDataType::VTYPE_VOID || michael@0: dataType == nsIDataType::VTYPE_EMPTY) { michael@0: // Makes us act as if !aBody, don't upload anything michael@0: aContentType.AssignLiteral("text/plain"); michael@0: aCharset.AssignLiteral("UTF-8"); michael@0: *aContentLength = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: char16_t* data = nullptr; michael@0: uint32_t len = 0; michael@0: rv = aBody->GetAsWStringWithSize(&len, &data); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString string; michael@0: string.Adopt(data, len); michael@0: michael@0: return GetRequestBody(string, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsXMLHttpRequest::GetRequestBody(nsIVariant* aVariant, michael@0: const Nullable& aBody, michael@0: nsIInputStream** aResult, michael@0: uint64_t* aContentLength, michael@0: nsACString& aContentType, nsACString& aCharset) michael@0: { michael@0: if (aVariant) { michael@0: return ::GetRequestBody(aVariant, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: michael@0: const RequestBody& body = aBody.Value(); michael@0: RequestBody::Value value = body.GetValue(); michael@0: switch (body.GetType()) { michael@0: case nsXMLHttpRequest::RequestBody::ArrayBuffer: michael@0: { michael@0: const ArrayBuffer* buffer = value.mArrayBuffer; michael@0: buffer->ComputeLengthAndData(); michael@0: return ::GetRequestBody(buffer->Data(), buffer->Length(), aResult, michael@0: aContentLength, aContentType, aCharset); michael@0: } michael@0: case nsXMLHttpRequest::RequestBody::ArrayBufferView: michael@0: { michael@0: const ArrayBufferView* view = value.mArrayBufferView; michael@0: view->ComputeLengthAndData(); michael@0: return ::GetRequestBody(view->Data(), view->Length(), aResult, michael@0: aContentLength, aContentType, aCharset); michael@0: } michael@0: case nsXMLHttpRequest::RequestBody::Blob: michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr sendable = do_QueryInterface(value.mBlob, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return ::GetRequestBody(sendable, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: case nsXMLHttpRequest::RequestBody::Document: michael@0: { michael@0: nsCOMPtr document = do_QueryInterface(value.mDocument); michael@0: return ::GetRequestBody(document, aResult, aContentLength, aContentType, aCharset); michael@0: } michael@0: case nsXMLHttpRequest::RequestBody::DOMString: michael@0: { michael@0: return ::GetRequestBody(*value.mString, aResult, aContentLength, michael@0: aContentType, aCharset); michael@0: } michael@0: case nsXMLHttpRequest::RequestBody::FormData: michael@0: { michael@0: MOZ_ASSERT(value.mFormData); michael@0: return ::GetRequestBody(value.mFormData, aResult, aContentLength, michael@0: aContentType, aCharset); michael@0: } michael@0: case nsXMLHttpRequest::RequestBody::InputStream: michael@0: { michael@0: return ::GetRequestBody(value.mStream, aResult, aContentLength, michael@0: aContentType, aCharset); michael@0: } michael@0: default: michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: NS_NOTREACHED("Default cases exist for a reason"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void send (in nsIVariant aBody); */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::Send(nsIVariant *aBody) michael@0: { michael@0: return Send(aBody, Nullable()); michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable& aBody) michael@0: { michael@0: NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Return error if we're already processing a request michael@0: if (XML_HTTP_REQUEST_SENT & mState) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Make sure we've been opened michael@0: if (!mChannel || !(XML_HTTP_REQUEST_OPENED & mState)) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: michael@0: // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which michael@0: // in turn keeps STOP button from becoming active. If the consumer passed in michael@0: // a progress event handler we must load with nsIRequest::LOAD_NORMAL or michael@0: // necko won't generate any progress notifications. michael@0: if (HasListenersFor(nsGkAtoms::onprogress) || michael@0: (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) { michael@0: nsLoadFlags loadFlags; michael@0: mChannel->GetLoadFlags(&loadFlags); michael@0: loadFlags &= ~nsIRequest::LOAD_BACKGROUND; michael@0: loadFlags |= nsIRequest::LOAD_NORMAL; michael@0: mChannel->SetLoadFlags(loadFlags); michael@0: } michael@0: michael@0: // XXX We should probably send a warning to the JS console michael@0: // if there are no event listeners set and we are doing michael@0: // an asynchronous call. michael@0: michael@0: // Ignore argument if method is GET, there is no point in trying to michael@0: // upload anything michael@0: nsAutoCString method; michael@0: nsCOMPtr httpChannel(do_QueryInterface(mChannel)); michael@0: michael@0: if (httpChannel) { michael@0: httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase michael@0: michael@0: if (!IsSystemXHR()) { michael@0: // Get the referrer for the request. michael@0: // michael@0: // If it weren't for history.push/replaceState, we could just use the michael@0: // principal's URI here. But since we want changes to the URI effected michael@0: // by push/replaceState to be reflected in the XHR referrer, we have to michael@0: // be more clever. michael@0: // michael@0: // If the document's original URI (before any push/replaceStates) matches michael@0: // our principal, then we use the document's current URI (after michael@0: // push/replaceStates). Otherwise (if the document is, say, a data: michael@0: // URI), we just use the principal's URI. michael@0: michael@0: nsCOMPtr principalURI; michael@0: mPrincipal->GetURI(getter_AddRefs(principalURI)); michael@0: michael@0: nsIScriptContext* sc = GetContextForEventHandlers(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr doc = michael@0: nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: michael@0: nsCOMPtr docCurURI; michael@0: nsCOMPtr docOrigURI; michael@0: if (doc) { michael@0: docCurURI = doc->GetDocumentURI(); michael@0: docOrigURI = doc->GetOriginalURI(); michael@0: } michael@0: michael@0: nsCOMPtr referrerURI; michael@0: michael@0: if (principalURI && docCurURI && docOrigURI) { michael@0: bool equal = false; michael@0: principalURI->Equals(docOrigURI, &equal); michael@0: if (equal) { michael@0: referrerURI = docCurURI; michael@0: } michael@0: } michael@0: michael@0: if (!referrerURI) michael@0: referrerURI = principalURI; michael@0: michael@0: httpChannel->SetReferrer(referrerURI); michael@0: } michael@0: michael@0: // Some extensions override the http protocol handler and provide their own michael@0: // implementation. The channels returned from that implementation doesn't michael@0: // seem to always implement the nsIUploadChannel2 interface, presumably michael@0: // because it's a new interface. michael@0: // Eventually we should remove this and simply require that http channels michael@0: // implement the new interface. michael@0: // See bug 529041 michael@0: nsCOMPtr uploadChannel2 = michael@0: do_QueryInterface(httpChannel); michael@0: if (!uploadChannel2) { michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (consoleService) { michael@0: consoleService->LogStringMessage(NS_LITERAL_STRING( michael@0: "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." michael@0: ).get()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mUploadTransferred = 0; michael@0: mUploadTotal = 0; michael@0: // By default we don't have any upload, so mark upload complete. michael@0: mUploadComplete = true; michael@0: mErrorLoad = false; michael@0: mLoadLengthComputable = false; michael@0: mLoadTotal = 0; michael@0: if ((aVariant || !aBody.IsNull()) && httpChannel && michael@0: !method.LowerCaseEqualsLiteral("get") && michael@0: !method.LowerCaseEqualsLiteral("head")) { michael@0: michael@0: nsAutoCString charset; michael@0: nsAutoCString defaultContentType; michael@0: nsCOMPtr postDataStream; michael@0: michael@0: rv = GetRequestBody(aVariant, aBody, getter_AddRefs(postDataStream), michael@0: &mUploadTotal, defaultContentType, charset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (postDataStream) { michael@0: // If no content type header was set by the client, we set it to michael@0: // application/xml. michael@0: nsAutoCString contentType; michael@0: if (NS_FAILED(httpChannel-> michael@0: GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), michael@0: contentType)) || michael@0: contentType.IsEmpty()) { michael@0: contentType = defaultContentType; michael@0: } michael@0: michael@0: // We don't want to set a charset for streams. michael@0: if (!charset.IsEmpty()) { michael@0: nsAutoCString specifiedCharset; michael@0: bool haveCharset; michael@0: int32_t charsetStart, charsetEnd; michael@0: rv = NS_ExtractCharsetFromContentType(contentType, specifiedCharset, michael@0: &haveCharset, &charsetStart, michael@0: &charsetEnd); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // special case: the extracted charset is quoted with single quotes michael@0: // -- for the purpose of preserving what was set we want to handle michael@0: // them as delimiters (although they aren't really) michael@0: if (specifiedCharset.Length() >= 2 && michael@0: specifiedCharset.First() == '\'' && michael@0: specifiedCharset.Last() == '\'') { michael@0: specifiedCharset = Substring(specifiedCharset, 1, michael@0: specifiedCharset.Length() - 2); michael@0: } michael@0: michael@0: // If the content-type the page set already has a charset parameter, michael@0: // and it's the same charset, up to case, as |charset|, just send the michael@0: // page-set content-type header. Apparently at least michael@0: // google-web-toolkit is broken and relies on the exact case of its michael@0: // charset parameter, which makes things break if we use |charset| michael@0: // (which is always a fully resolved charset per our charset alias michael@0: // table, hence might be differently cased). michael@0: if (!specifiedCharset.Equals(charset, michael@0: nsCaseInsensitiveCStringComparator())) { michael@0: nsAutoCString newCharset("; charset="); michael@0: newCharset.Append(charset); michael@0: contentType.Replace(charsetStart, charsetEnd - charsetStart, michael@0: newCharset); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If necessary, wrap the stream in a buffered stream so as to guarantee michael@0: // support for our upload when calling ExplicitSetUploadStream. michael@0: if (!NS_InputStreamIsBuffered(postDataStream)) { michael@0: nsCOMPtr bufferedStream; michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), michael@0: postDataStream, michael@0: 4096); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: postDataStream = bufferedStream; michael@0: } michael@0: michael@0: mUploadComplete = false; michael@0: michael@0: // We want to use a newer version of the upload channel that won't michael@0: // ignore the necessary headers for an empty Content-Type. michael@0: nsCOMPtr uploadChannel2(do_QueryInterface(httpChannel)); michael@0: // This assertion will fire if buggy extensions are installed michael@0: NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2"); michael@0: if (uploadChannel2) { michael@0: uploadChannel2->ExplicitSetUploadStream(postDataStream, contentType, michael@0: mUploadTotal, method, false); michael@0: } michael@0: else { michael@0: // http channel doesn't support the new nsIUploadChannel2. Emulate michael@0: // as best we can using nsIUploadChannel michael@0: if (contentType.IsEmpty()) { michael@0: contentType.AssignLiteral("application/octet-stream"); michael@0: } michael@0: nsCOMPtr uploadChannel = michael@0: do_QueryInterface(httpChannel); michael@0: uploadChannel->SetUploadStream(postDataStream, contentType, mUploadTotal); michael@0: // Reset the method to its original value michael@0: httpChannel->SetRequestMethod(method); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (httpChannel) { michael@0: nsAutoCString contentTypeHeader; michael@0: rv = httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), michael@0: contentTypeHeader); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsAutoCString contentType, charset; michael@0: rv = NS_ParseContentType(contentTypeHeader, contentType, charset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!contentType.LowerCaseEqualsLiteral("text/plain") && michael@0: !contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") && michael@0: !contentType.LowerCaseEqualsLiteral("multipart/form-data")) { michael@0: mCORSUnsafeHeaders.AppendElement(NS_LITERAL_CSTRING("Content-Type")); michael@0: } michael@0: } michael@0: } michael@0: michael@0: ResetResponse(); michael@0: michael@0: rv = CheckChannelForCrossSiteRequest(mChannel); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS); michael@0: michael@0: // Hook us up to listen to redirects and the like michael@0: mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); michael@0: mChannel->SetNotificationCallbacks(this); michael@0: michael@0: // Blocking gets are common enough out of XHR that we should mark michael@0: // the channel slow by default for pipeline purposes michael@0: AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE); michael@0: michael@0: nsCOMPtr michael@0: internalHttpChannel(do_QueryInterface(mChannel)); michael@0: if (internalHttpChannel) { michael@0: // we never let XHR be blocked by head CSS/JS loads to avoid michael@0: // potential deadlock where server generation of CSS/JS requires michael@0: // an XHR signal. michael@0: internalHttpChannel->SetLoadUnblocked(true); michael@0: michael@0: // Disable Necko-internal response timeouts. michael@0: internalHttpChannel->SetResponseTimeoutEnabled(false); michael@0: } michael@0: michael@0: nsCOMPtr listener = this; michael@0: if (!IsSystemXHR()) { michael@0: // Always create a nsCORSListenerProxy here even if it's michael@0: // a same-origin request right now, since it could be redirected. michael@0: nsRefPtr corsListener = michael@0: new nsCORSListenerProxy(listener, mPrincipal, withCredentials); michael@0: rv = corsListener->Init(mChannel, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: listener = corsListener; michael@0: } michael@0: else { michael@0: // Because of bug 682305, we can't let listener be the XHR object itself michael@0: // because JS wouldn't be able to use it. So if we haven't otherwise michael@0: // created a listener around 'this', do so now. michael@0: michael@0: listener = new nsStreamListenerWrapper(listener); michael@0: } michael@0: michael@0: if (mIsAnon) { michael@0: AddLoadFlags(mChannel, nsIRequest::LOAD_ANONYMOUS); michael@0: } michael@0: else { michael@0: AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS); michael@0: } michael@0: michael@0: NS_ASSERTION(listener != this, michael@0: "Using an object as a listener that can't be exposed to JS"); michael@0: michael@0: // Bypass the network cache in cases where it makes no sense: michael@0: // POST responses are always unique, and we provide no API that would michael@0: // allow our consumers to specify a "cache key" to access old POST michael@0: // responses, so they are not worth caching. michael@0: if (method.EqualsLiteral("POST")) { michael@0: AddLoadFlags(mChannel, michael@0: nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::INHIBIT_CACHING); michael@0: } michael@0: // When we are sync loading, we need to bypass the local cache when it would michael@0: // otherwise block us waiting for exclusive access to the cache. If we don't michael@0: // do this, then we could dead lock in some cases (see bug 309424). michael@0: else if (!(mState & XML_HTTP_REQUEST_ASYNC)) { michael@0: AddLoadFlags(mChannel, michael@0: nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY); michael@0: } michael@0: michael@0: // Since we expect XML data, set the type hint accordingly michael@0: // if the channel doesn't know any content type. michael@0: // This means that we always try to parse local files as XML michael@0: // ignoring return value, as this is not critical michael@0: nsAutoCString contentType; michael@0: if (NS_FAILED(mChannel->GetContentType(contentType)) || michael@0: contentType.IsEmpty() || michael@0: contentType.Equals(UNKNOWN_CONTENT_TYPE)) { michael@0: mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml")); michael@0: } michael@0: michael@0: // We're about to send the request. Start our timeout. michael@0: mRequestSentTime = PR_Now(); michael@0: StartTimeoutTimer(); michael@0: michael@0: // Set up the preflight if needed michael@0: if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) { michael@0: // Check to see if this initial OPTIONS request has already been cached michael@0: // in our special Access Control Cache. michael@0: michael@0: rv = NS_StartCORSPreflight(mChannel, listener, michael@0: mPrincipal, withCredentials, michael@0: mCORSUnsafeHeaders, michael@0: getter_AddRefs(mCORSPreflightChannel)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: // Start reading from the channel michael@0: rv = mChannel->AsyncOpen(listener, nullptr); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // Drop our ref to the channel to avoid cycles michael@0: mChannel = nullptr; michael@0: mCORSPreflightChannel = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: // Either AsyncOpen was called, or CORS will open the channel later. michael@0: mWaitingForOnStopRequest = true; michael@0: michael@0: // If we're synchronous, spin an event loop here and wait michael@0: if (!(mState & XML_HTTP_REQUEST_ASYNC)) { michael@0: mState |= XML_HTTP_REQUEST_SYNCLOOPING; michael@0: michael@0: nsCOMPtr suspendedDoc; michael@0: nsCOMPtr resumeTimeoutRunnable; michael@0: if (GetOwner()) { michael@0: nsCOMPtr topWindow; michael@0: if (NS_SUCCEEDED(GetOwner()->GetTop(getter_AddRefs(topWindow)))) { michael@0: nsCOMPtr suspendedWindow(do_QueryInterface(topWindow)); michael@0: if (suspendedWindow && michael@0: (suspendedWindow = suspendedWindow->GetCurrentInnerWindow())) { michael@0: suspendedDoc = suspendedWindow->GetExtantDoc(); michael@0: if (suspendedDoc) { michael@0: suspendedDoc->SuppressEventHandling(nsIDocument::eEvents); michael@0: } michael@0: suspendedWindow->SuspendTimeouts(1, false); michael@0: resumeTimeoutRunnable = new nsResumeTimeoutsEvent(suspendedWindow); michael@0: } michael@0: } michael@0: } michael@0: michael@0: ChangeState(XML_HTTP_REQUEST_SENT); michael@0: michael@0: { michael@0: nsAutoSyncOperation sync(suspendedDoc); michael@0: // Note, calling ChangeState may have cleared michael@0: // XML_HTTP_REQUEST_SYNCLOOPING flag. michael@0: nsIThread *thread = NS_GetCurrentThread(); michael@0: while (mState & XML_HTTP_REQUEST_SYNCLOOPING) { michael@0: if (!NS_ProcessNextEvent(thread)) { michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (suspendedDoc) { michael@0: suspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, michael@0: true); michael@0: } michael@0: michael@0: if (resumeTimeoutRunnable) { michael@0: NS_DispatchToCurrentThread(resumeTimeoutRunnable); michael@0: } michael@0: } else { michael@0: // Now that we've successfully opened the channel, we can change state. Note michael@0: // that this needs to come after the AsyncOpen() and rv check, because this michael@0: // can run script that would try to restart this request, and that could end michael@0: // up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails. michael@0: ChangeState(XML_HTTP_REQUEST_SENT); michael@0: if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) { michael@0: StartProgressEventTimer(); michael@0: } michael@0: DispatchProgressEvent(this, NS_LITERAL_STRING(LOADSTART_STR), false, michael@0: 0, 0); michael@0: if (mUpload && !mUploadComplete) { michael@0: DispatchProgressEvent(mUpload, NS_LITERAL_STRING(LOADSTART_STR), true, michael@0: 0, mUploadTotal); michael@0: } michael@0: } michael@0: michael@0: if (!mChannel) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* void setRequestHeader (in ByteString header, in ByteString value); */ michael@0: // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SetRequestHeader(const nsACString& header, michael@0: const nsACString& value) michael@0: { michael@0: // Step 1 and 2 michael@0: if (!(mState & XML_HTTP_REQUEST_OPENED)) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: NS_ASSERTION(mChannel, "mChannel must be valid if we're OPENED."); michael@0: michael@0: // Step 3 michael@0: // Make sure we don't store an invalid header name in mCORSUnsafeHeaders michael@0: if (!IsValidHTTPToken(header)) { // XXX nsHttp::IsValidToken? michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: // Check that we haven't already opened the channel. We can't rely on michael@0: // the channel throwing from mChannel->SetRequestHeader since we might michael@0: // still be waiting for mCORSPreflightChannel to actually open mChannel michael@0: if (mCORSPreflightChannel) { michael@0: bool pending; michael@0: nsresult rv = mCORSPreflightChannel->IsPending(&pending); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (pending) { michael@0: return NS_ERROR_IN_PROGRESS; michael@0: } michael@0: } michael@0: michael@0: if (!mChannel) // open() initializes mChannel, and open() michael@0: return NS_ERROR_FAILURE; // must be called before first setRequestHeader() michael@0: michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel); michael@0: if (!httpChannel) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We will merge XHR headers, per the spec (secion 4.6.2) unless: michael@0: // 1 - The caller is privileged and setting an invalid header, michael@0: // or michael@0: // 2 - we have not yet explicitly set that header; this allows web michael@0: // content to override default headers the first time they set them. michael@0: bool mergeHeaders = true; michael@0: michael@0: // Prevent modification to certain HTTP headers (see bug 302263), unless michael@0: // the executing script is privileged. michael@0: bool isInvalidHeader = false; michael@0: static const char *kInvalidHeaders[] = { michael@0: "accept-charset", "accept-encoding", "access-control-request-headers", michael@0: "access-control-request-method", "connection", "content-length", michael@0: "cookie", "cookie2", "content-transfer-encoding", "date", "dnt", michael@0: "expect", "host", "keep-alive", "origin", "referer", "te", "trailer", michael@0: "transfer-encoding", "upgrade", "user-agent", "via" michael@0: }; michael@0: uint32_t i; michael@0: for (i = 0; i < ArrayLength(kInvalidHeaders); ++i) { michael@0: if (header.LowerCaseEqualsASCII(kInvalidHeaders[i])) { michael@0: isInvalidHeader = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!IsSystemXHR()) { michael@0: // Step 5: Check for dangerous headers. michael@0: if (isInvalidHeader) { michael@0: NS_WARNING("refusing to set request header"); michael@0: return NS_OK; michael@0: } michael@0: if (StringBeginsWith(header, NS_LITERAL_CSTRING("proxy-"), michael@0: nsCaseInsensitiveCStringComparator()) || michael@0: StringBeginsWith(header, NS_LITERAL_CSTRING("sec-"), michael@0: nsCaseInsensitiveCStringComparator())) { michael@0: NS_WARNING("refusing to set request header"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Check for dangerous cross-site headers michael@0: bool safeHeader = IsSystemXHR(); michael@0: if (!safeHeader) { michael@0: // Content-Type isn't always safe, but we'll deal with it in Send() michael@0: const char *kCrossOriginSafeHeaders[] = { michael@0: "accept", "accept-language", "content-language", "content-type", michael@0: "last-event-id" michael@0: }; michael@0: for (i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) { michael@0: if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) { michael@0: safeHeader = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!safeHeader) { michael@0: if (!mCORSUnsafeHeaders.Contains(header)) { michael@0: mCORSUnsafeHeaders.AppendElement(header); michael@0: } michael@0: } michael@0: } else { michael@0: // Case 1 above michael@0: if (isInvalidHeader) { michael@0: mergeHeaders = false; michael@0: } michael@0: } michael@0: michael@0: if (!mAlreadySetHeaders.Contains(header)) { michael@0: // Case 2 above michael@0: mergeHeaders = false; michael@0: } michael@0: michael@0: // Merge headers depending on what we decided above. michael@0: nsresult rv = httpChannel->SetRequestHeader(header, value, mergeHeaders); michael@0: if (rv == NS_ERROR_INVALID_ARG) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Remember that we've set this header, so subsequent set operations will merge values. michael@0: mAlreadySetHeaders.PutEntry(nsCString(header)); michael@0: michael@0: // We'll want to duplicate this header for any replacement channels (eg. on redirect) michael@0: RequestHeader reqHeader = { michael@0: nsCString(header), nsCString(value) michael@0: }; michael@0: mModifiedRequestHeaders.AppendElement(reqHeader); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: /* attribute unsigned long timeout; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetTimeout(uint32_t *aTimeout) michael@0: { michael@0: *aTimeout = Timeout(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SetTimeout(uint32_t aTimeout) michael@0: { michael@0: ErrorResult rv; michael@0: SetTimeout(aTimeout, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) michael@0: { michael@0: if (!(mState & (XML_HTTP_REQUEST_ASYNC | XML_HTTP_REQUEST_UNSENT)) && michael@0: HasOrHasHadOwner()) { michael@0: /* Timeout is not supported for synchronous requests with an owning window, michael@0: per XHR2 spec. */ michael@0: LogMessage("TimeoutSyncXHRWarning", GetOwner()); michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: michael@0: mTimeoutMilliseconds = aTimeout; michael@0: if (mRequestSentTime) { michael@0: StartTimeoutTimer(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::StartTimeoutTimer() michael@0: { michael@0: NS_ABORT_IF_FALSE(mRequestSentTime, michael@0: "StartTimeoutTimer mustn't be called before the request was sent!"); michael@0: if (mState & XML_HTTP_REQUEST_DONE) { michael@0: // do nothing! michael@0: return; michael@0: } michael@0: michael@0: if (mTimeoutTimer) { michael@0: mTimeoutTimer->Cancel(); michael@0: } michael@0: michael@0: if (!mTimeoutMilliseconds) { michael@0: return; michael@0: } michael@0: michael@0: if (!mTimeoutTimer) { michael@0: mTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: } michael@0: uint32_t elapsed = michael@0: (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC); michael@0: mTimeoutTimer->InitWithCallback( michael@0: this, michael@0: mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0, michael@0: nsITimer::TYPE_ONE_SHOT michael@0: ); michael@0: } michael@0: michael@0: /* readonly attribute unsigned short readyState; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetReadyState(uint16_t *aState) michael@0: { michael@0: *aState = ReadyState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint16_t michael@0: nsXMLHttpRequest::ReadyState() michael@0: { michael@0: // Translate some of our internal states for external consumers michael@0: if (mState & XML_HTTP_REQUEST_UNSENT) { michael@0: return UNSENT; michael@0: } michael@0: if (mState & (XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) { michael@0: return OPENED; michael@0: } michael@0: if (mState & XML_HTTP_REQUEST_HEADERS_RECEIVED) { michael@0: return HEADERS_RECEIVED; michael@0: } michael@0: if (mState & XML_HTTP_REQUEST_LOADING) { michael@0: return LOADING; michael@0: } michael@0: MOZ_ASSERT(mState & XML_HTTP_REQUEST_DONE); michael@0: return DONE; michael@0: } michael@0: michael@0: /* void overrideMimeType(in DOMString mimetype); */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SlowOverrideMimeType(const nsAString& aMimeType) michael@0: { michael@0: OverrideMimeType(aMimeType); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* attribute boolean mozBackgroundRequest; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetMozBackgroundRequest(bool *_retval) michael@0: { michael@0: *_retval = MozBackgroundRequest(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXMLHttpRequest::MozBackgroundRequest() michael@0: { michael@0: return !!(mState & XML_HTTP_REQUEST_BACKGROUND); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SetMozBackgroundRequest(bool aMozBackgroundRequest) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: SetMozBackgroundRequest(aMozBackgroundRequest, rv); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SetMozBackgroundRequest(bool aMozBackgroundRequest, nsresult& aRv) michael@0: { michael@0: if (!IsSystemXHR()) { michael@0: aRv = NS_ERROR_DOM_SECURITY_ERR; michael@0: return; michael@0: } michael@0: michael@0: if (!(mState & XML_HTTP_REQUEST_UNSENT)) { michael@0: // Can't change this while we're in the middle of something. michael@0: aRv = NS_ERROR_IN_PROGRESS; michael@0: return; michael@0: } michael@0: michael@0: if (aMozBackgroundRequest) { michael@0: mState |= XML_HTTP_REQUEST_BACKGROUND; michael@0: } else { michael@0: mState &= ~XML_HTTP_REQUEST_BACKGROUND; michael@0: } michael@0: } michael@0: michael@0: /* attribute boolean withCredentials; */ michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetWithCredentials(bool *_retval) michael@0: { michael@0: *_retval = WithCredentials(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXMLHttpRequest::WithCredentials() michael@0: { michael@0: return !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::SetWithCredentials(bool aWithCredentials) michael@0: { michael@0: ErrorResult rv; michael@0: SetWithCredentials(aWithCredentials, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) michael@0: { michael@0: // Return error if we're already processing a request michael@0: if (XML_HTTP_REQUEST_SENT & mState) { michael@0: aRv = NS_ERROR_FAILURE; michael@0: return; michael@0: } michael@0: michael@0: // sync request is not allowed setting withCredentials in window context michael@0: if (HasOrHasHadOwner() && michael@0: !(mState & (XML_HTTP_REQUEST_UNSENT | XML_HTTP_REQUEST_ASYNC))) { michael@0: LogMessage("WithCredentialsSyncXHRWarning", GetOwner()); michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (aWithCredentials) { michael@0: mState |= XML_HTTP_REQUEST_AC_WITH_CREDENTIALS; michael@0: } else { michael@0: mState &= ~XML_HTTP_REQUEST_AC_WITH_CREDENTIALS; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLHttpRequest::ChangeState(uint32_t aState, bool aBroadcast) michael@0: { michael@0: // If we are setting one of the mutually exclusive states, michael@0: // unset those state bits first. michael@0: if (aState & XML_HTTP_REQUEST_LOADSTATES) { michael@0: mState &= ~XML_HTTP_REQUEST_LOADSTATES; michael@0: } michael@0: mState |= aState; michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (mProgressNotifier && michael@0: !(aState & (XML_HTTP_REQUEST_HEADERS_RECEIVED | XML_HTTP_REQUEST_LOADING))) { michael@0: mProgressTimerIsActive = false; michael@0: mProgressNotifier->Cancel(); michael@0: } michael@0: michael@0: if ((aState & XML_HTTP_REQUEST_LOADSTATES) && // Broadcast load states only michael@0: aState != XML_HTTP_REQUEST_SENT && // And not internal ones michael@0: aBroadcast && michael@0: (mState & XML_HTTP_REQUEST_ASYNC || michael@0: aState & XML_HTTP_REQUEST_OPENED || michael@0: aState & XML_HTTP_REQUEST_DONE)) { michael@0: nsCOMPtr event; michael@0: rv = CreateReadystatechangeEvent(getter_AddRefs(event)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* michael@0: * Simple helper class that just forwards the redirect callback back michael@0: * to the nsXMLHttpRequest. michael@0: */ michael@0: class AsyncVerifyRedirectCallbackForwarder MOZ_FINAL : public nsIAsyncVerifyRedirectCallback michael@0: { michael@0: public: michael@0: AsyncVerifyRedirectCallbackForwarder(nsXMLHttpRequest *xhr) michael@0: : mXHR(xhr) michael@0: { michael@0: } michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackForwarder) michael@0: michael@0: // nsIAsyncVerifyRedirectCallback implementation michael@0: NS_IMETHOD OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: mXHR->OnRedirectVerifyCallback(result); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mXHR; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder, mXHR) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackForwarder) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncVerifyRedirectCallbackForwarder) michael@0: michael@0: michael@0: ///////////////////////////////////////////////////// michael@0: // nsIChannelEventSink methods: michael@0: // michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: NS_PRECONDITION(aNewChannel, "Redirect without a channel?"); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) { michael@0: rv = CheckChannelForCrossSiteRequest(aNewChannel); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("nsXMLHttpRequest::OnChannelRedirect: " michael@0: "CheckChannelForCrossSiteRequest returned failure"); michael@0: return rv; michael@0: } michael@0: michael@0: // Disable redirects for preflighted cross-site requests entirely for now michael@0: // Note, do this after the call to CheckChannelForCrossSiteRequest michael@0: // to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date michael@0: if ((mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT)) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: } michael@0: michael@0: // Prepare to receive callback michael@0: mRedirectCallback = callback; michael@0: mNewRedirectChannel = aNewChannel; michael@0: michael@0: if (mChannelEventSink) { michael@0: nsRefPtr fwd = michael@0: new AsyncVerifyRedirectCallbackForwarder(this); michael@0: michael@0: rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel, michael@0: aNewChannel, michael@0: aFlags, fwd); michael@0: if (NS_FAILED(rv)) { michael@0: mRedirectCallback = nullptr; michael@0: mNewRedirectChannel = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); michael@0: NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: mChannel = mNewRedirectChannel; michael@0: michael@0: nsCOMPtr httpChannel(do_QueryInterface(mChannel)); michael@0: if (httpChannel) { michael@0: // Ensure all original headers are duplicated for the new channel (bug #553888) michael@0: for (uint32_t i = mModifiedRequestHeaders.Length(); i > 0; ) { michael@0: --i; michael@0: httpChannel->SetRequestHeader(mModifiedRequestHeaders[i].header, michael@0: mModifiedRequestHeaders[i].value, michael@0: false); michael@0: } michael@0: } michael@0: } else { michael@0: mErrorLoad = true; michael@0: } michael@0: michael@0: mNewRedirectChannel = nullptr; michael@0: michael@0: mRedirectCallback->OnRedirectVerifyCallback(result); michael@0: mRedirectCallback = nullptr; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////// michael@0: // nsIProgressEventSink methods: michael@0: // michael@0: michael@0: void michael@0: nsXMLHttpRequest::MaybeDispatchProgressEvents(bool aFinalProgress) michael@0: { michael@0: if (aFinalProgress && mProgressTimerIsActive) { michael@0: mProgressTimerIsActive = false; michael@0: mProgressNotifier->Cancel(); michael@0: } michael@0: michael@0: if (mProgressTimerIsActive || michael@0: !mProgressSinceLastProgressEvent || michael@0: mErrorLoad || michael@0: !(mState & XML_HTTP_REQUEST_ASYNC)) { michael@0: return; michael@0: } michael@0: michael@0: if (!aFinalProgress) { michael@0: StartProgressEventTimer(); michael@0: } michael@0: michael@0: // We're uploading if our state is XML_HTTP_REQUEST_OPENED or michael@0: // XML_HTTP_REQUEST_SENT michael@0: if ((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState) { michael@0: if (mUpload && !mUploadComplete) { michael@0: DispatchProgressEvent(mUpload, NS_LITERAL_STRING(PROGRESS_STR), michael@0: mUploadLengthComputable, mUploadTransferred, michael@0: mUploadTotal); michael@0: } michael@0: } else { michael@0: if (aFinalProgress) { michael@0: mLoadTotal = mLoadTransferred; michael@0: } michael@0: mInLoadProgressEvent = true; michael@0: DispatchProgressEvent(this, NS_LITERAL_STRING(PROGRESS_STR), michael@0: mLoadLengthComputable, mLoadTransferred, michael@0: mLoadTotal); michael@0: mInLoadProgressEvent = false; michael@0: if (mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT || michael@0: mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) { michael@0: mResponseBody.Truncate(); michael@0: mResponseText.Truncate(); michael@0: mResultArrayBuffer = nullptr; michael@0: mArrayBufferBuilder.reset(); michael@0: } michael@0: } michael@0: michael@0: mProgressSinceLastProgressEvent = false; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::OnProgress(nsIRequest *aRequest, nsISupports *aContext, uint64_t aProgress, uint64_t aProgressMax) michael@0: { michael@0: // We're uploading if our state is XML_HTTP_REQUEST_OPENED or michael@0: // XML_HTTP_REQUEST_SENT michael@0: bool upload = !!((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState); michael@0: // When uploading, OnProgress reports also headers in aProgress and aProgressMax. michael@0: // So, try to remove the headers, if possible. michael@0: bool lengthComputable = (aProgressMax != UINT64_MAX); michael@0: if (upload) { michael@0: uint64_t loaded = aProgress; michael@0: uint64_t total = aProgressMax; michael@0: if (lengthComputable) { michael@0: uint64_t headerSize = aProgressMax - mUploadTotal; michael@0: loaded -= headerSize; michael@0: total -= headerSize; michael@0: } michael@0: mUploadLengthComputable = lengthComputable; michael@0: mUploadTransferred = loaded; michael@0: mProgressSinceLastProgressEvent = true; michael@0: michael@0: MaybeDispatchProgressEvents(false); michael@0: } else { michael@0: mLoadLengthComputable = lengthComputable; michael@0: mLoadTotal = lengthComputable ? aProgressMax : 0; michael@0: michael@0: // Don't dispatch progress events here. OnDataAvailable will take care michael@0: // of that. michael@0: } michael@0: michael@0: if (mProgressEventSink) { michael@0: mProgressEventSink->OnProgress(aRequest, aContext, aProgress, michael@0: aProgressMax); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::OnStatus(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatus, const char16_t *aStatusArg) michael@0: { michael@0: if (mProgressEventSink) { michael@0: mProgressEventSink->OnStatus(aRequest, aContext, aStatus, aStatusArg); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXMLHttpRequest::AllowUploadProgress() michael@0: { michael@0: return !(mState & XML_HTTP_REQUEST_USE_XSITE_AC) || michael@0: (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT); michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////// michael@0: // nsIInterfaceRequestor methods: michael@0: // michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Make sure to return ourselves for the channel event sink interface and michael@0: // progress event sink interface, no matter what. We can forward these to michael@0: // mNotificationCallbacks if it wants to get notifications for them. But we michael@0: // need to see these notifications for proper functioning. michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: mChannelEventSink = do_GetInterface(mNotificationCallbacks); michael@0: *aResult = static_cast(EnsureXPCOMifier().take()); michael@0: return NS_OK; michael@0: } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) { michael@0: mProgressEventSink = do_GetInterface(mNotificationCallbacks); michael@0: *aResult = static_cast(EnsureXPCOMifier().take()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Now give mNotificationCallbacks (if non-null) a chance to return the michael@0: // desired interface. michael@0: if (mNotificationCallbacks) { michael@0: rv = mNotificationCallbacks->GetInterface(aIID, aResult); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!"); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (mState & XML_HTTP_REQUEST_BACKGROUND) { michael@0: nsCOMPtr badCertHandler(do_CreateInstance(NS_BADCERTHANDLER_CONTRACTID, &rv)); michael@0: michael@0: // Ignore failure to get component, we may not have all its dependencies michael@0: // available michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = badCertHandler->GetInterface(aIID, aResult); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || michael@0: aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { michael@0: michael@0: nsCOMPtr uri; michael@0: rv = mChannel->GetURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Verify that it's ok to prompt for credentials here, per spec michael@0: // http://xhr.spec.whatwg.org/#the-send%28%29-method michael@0: bool showPrompt = true; michael@0: michael@0: // If authentication fails, XMLHttpRequest origin and michael@0: // the request URL are same origin, ... michael@0: /* Disabled - bug: 799540 michael@0: if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { michael@0: showPrompt = false; michael@0: } michael@0: */ michael@0: michael@0: // ... Authorization is not in the list of author request headers, ... michael@0: if (showPrompt) { michael@0: for (uint32_t i = 0, len = mModifiedRequestHeaders.Length(); i < len; ++i) { michael@0: if (mModifiedRequestHeaders[i].header. michael@0: LowerCaseEqualsLiteral("authorization")) { michael@0: showPrompt = false; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // ... request username is null, and request password is null, michael@0: if (showPrompt) { michael@0: michael@0: nsCString username; michael@0: rv = uri->GetUsername(username); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCString password; michael@0: rv = uri->GetPassword(password); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!username.IsEmpty() || !password.IsEmpty()) { michael@0: showPrompt = false; michael@0: } michael@0: } michael@0: michael@0: // ... user agents should prompt the end user for their username and password. michael@0: if (!showPrompt) { michael@0: nsRefPtr prompt = new XMLHttpRequestAuthPrompt(); michael@0: if (!prompt) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return prompt->QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: nsCOMPtr wwatch = michael@0: do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the an auth prompter for our window so that the parenting michael@0: // of the dialogs works as it should when using tabs. michael@0: michael@0: nsCOMPtr window; michael@0: if (GetOwner()) { michael@0: window = GetOwner()->GetOuterWindow(); michael@0: } michael@0: michael@0: return wwatch->GetPrompt(window, aIID, michael@0: reinterpret_cast(aResult)); michael@0: } michael@0: // Now check for the various XHR non-DOM interfaces, except michael@0: // nsIProgressEventSink and nsIChannelEventSink which we already michael@0: // handled above. michael@0: else if (aIID.Equals(NS_GET_IID(nsIStreamListener))) { michael@0: *aResult = static_cast(EnsureXPCOMifier().take()); michael@0: return NS_OK; michael@0: } michael@0: else if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) { michael@0: *aResult = static_cast(EnsureXPCOMifier().take()); michael@0: return NS_OK; michael@0: } michael@0: else if (aIID.Equals(NS_GET_IID(nsITimerCallback))) { michael@0: *aResult = static_cast(EnsureXPCOMifier().take()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::GetInterface(JSContext* aCx, nsIJSID* aIID, michael@0: JS::MutableHandle aRetval, michael@0: ErrorResult& aRv) michael@0: { michael@0: dom::GetInterface(aCx, this, aIID, aRetval, aRv); michael@0: } michael@0: michael@0: nsXMLHttpRequestUpload* michael@0: nsXMLHttpRequest::Upload() michael@0: { michael@0: if (!mUpload) { michael@0: mUpload = new nsXMLHttpRequestUpload(this); michael@0: } michael@0: return mUpload; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetUpload(nsIXMLHttpRequestUpload** aUpload) michael@0: { michael@0: nsRefPtr upload = Upload(); michael@0: upload.forget(aUpload); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXMLHttpRequest::MozAnon() michael@0: { michael@0: return mIsAnon; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetMozAnon(bool* aAnon) michael@0: { michael@0: *aAnon = MozAnon(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXMLHttpRequest::MozSystem() michael@0: { michael@0: return IsSystemXHR(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::GetMozSystem(bool* aSystem) michael@0: { michael@0: *aSystem = MozSystem(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::HandleTimeoutCallback() michael@0: { michael@0: if (mState & XML_HTTP_REQUEST_DONE) { michael@0: NS_NOTREACHED("nsXMLHttpRequest::HandleTimeoutCallback with completed request"); michael@0: // do nothing! michael@0: return; michael@0: } michael@0: michael@0: CloseRequestWithError(NS_LITERAL_STRING(TIMEOUT_STR), michael@0: XML_HTTP_REQUEST_TIMED_OUT); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequest::Notify(nsITimer* aTimer) michael@0: { michael@0: if (mProgressNotifier == aTimer) { michael@0: HandleProgressTimerCallback(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mTimeoutTimer == aTimer) { michael@0: HandleTimeoutCallback(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Just in case some JS user wants to QI to nsITimerCallback and play with us... michael@0: NS_WARNING("Unexpected timer!"); michael@0: return NS_ERROR_INVALID_POINTER; michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::HandleProgressTimerCallback() michael@0: { michael@0: mProgressTimerIsActive = false; michael@0: MaybeDispatchProgressEvents(false); michael@0: } michael@0: michael@0: void michael@0: nsXMLHttpRequest::StartProgressEventTimer() michael@0: { michael@0: if (!mProgressNotifier) { michael@0: mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: } michael@0: if (mProgressNotifier) { michael@0: mProgressTimerIsActive = true; michael@0: mProgressNotifier->Cancel(); michael@0: mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXMLHttpRequest::EnsureXPCOMifier() michael@0: { michael@0: if (!mXPCOMifier) { michael@0: mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this); michael@0: } michael@0: nsRefPtr newRef(mXPCOMifier); michael@0: return newRef.forget(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXMLHttpRequest::nsHeaderVisitor, nsIHttpHeaderVisitor) michael@0: michael@0: NS_IMETHODIMP nsXMLHttpRequest:: michael@0: nsHeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value) michael@0: { michael@0: if (mXHR->IsSafeHeader(header, mHttpChannel)) { michael@0: mHeaders.Append(header); michael@0: mHeaders.Append(": "); michael@0: mHeaders.Append(value); michael@0: mHeaders.Append("\r\n"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsXMLHttpRequestXPCOMifier implementation michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsITimerCallback) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier) michael@0: michael@0: // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous michael@0: // inheritance from nsISupports. michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier) michael@0: if (tmp->mXHR) { michael@0: tmp->mXHR->mXPCOMifier = nullptr; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: // Return ourselves for the things we implement (except michael@0: // nsIInterfaceRequestor) and the XHR for the rest. michael@0: if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) { michael@0: nsresult rv = QueryInterface(aIID, aResult); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return mXHR->GetInterface(aIID, aResult); michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: ArrayBufferBuilder::ArrayBufferBuilder() michael@0: : mDataPtr(nullptr), michael@0: mCapacity(0), michael@0: mLength(0) michael@0: { michael@0: } michael@0: michael@0: ArrayBufferBuilder::~ArrayBufferBuilder() michael@0: { michael@0: reset(); michael@0: } michael@0: michael@0: void michael@0: ArrayBufferBuilder::reset() michael@0: { michael@0: if (mDataPtr) { michael@0: JS_free(nullptr, mDataPtr); michael@0: } michael@0: mDataPtr = nullptr; michael@0: mCapacity = mLength = 0; michael@0: } michael@0: michael@0: bool michael@0: ArrayBufferBuilder::setCapacity(uint32_t aNewCap) michael@0: { michael@0: uint8_t *newdata = (uint8_t *) JS_ReallocateArrayBufferContents(nullptr, aNewCap, mDataPtr, mCapacity); michael@0: if (!newdata) { michael@0: return false; michael@0: } michael@0: michael@0: mDataPtr = newdata; michael@0: mCapacity = aNewCap; michael@0: if (mLength > aNewCap) { michael@0: mLength = aNewCap; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ArrayBufferBuilder::append(const uint8_t *aNewData, uint32_t aDataLen, michael@0: uint32_t aMaxGrowth) michael@0: { michael@0: if (mLength + aDataLen > mCapacity) { michael@0: uint32_t newcap; michael@0: // Double while under aMaxGrowth or if not specified. michael@0: if (!aMaxGrowth || mCapacity < aMaxGrowth) { michael@0: newcap = mCapacity * 2; michael@0: } else { michael@0: newcap = mCapacity + aMaxGrowth; michael@0: } michael@0: michael@0: // But make sure there's always enough to satisfy our request. michael@0: if (newcap < mLength + aDataLen) { michael@0: newcap = mLength + aDataLen; michael@0: } michael@0: michael@0: // Did we overflow? michael@0: if (newcap < mCapacity) { michael@0: return false; michael@0: } michael@0: michael@0: if (!setCapacity(newcap)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Assert that the region isn't overlapping so we can memcpy. michael@0: MOZ_ASSERT(!areOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, michael@0: aDataLen)); michael@0: michael@0: memcpy(mDataPtr + mLength, aNewData, aDataLen); michael@0: mLength += aDataLen; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: JSObject* michael@0: ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) michael@0: { michael@0: // we need to check for mLength == 0, because nothing may have been michael@0: // added michael@0: if (mCapacity > mLength || mLength == 0) { michael@0: if (!setCapacity(mLength)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: JSObject* obj = JS_NewArrayBufferWithContents(aCx, mLength, mDataPtr); michael@0: mDataPtr = nullptr; michael@0: mLength = mCapacity = 0; michael@0: if (!obj) { michael@0: js_free(mDataPtr); michael@0: return nullptr; michael@0: } michael@0: return obj; michael@0: } michael@0: michael@0: /* static */ bool michael@0: ArrayBufferBuilder::areOverlappingRegions(const uint8_t* aStart1, michael@0: uint32_t aLength1, michael@0: const uint8_t* aStart2, michael@0: uint32_t aLength2) michael@0: { michael@0: const uint8_t* end1 = aStart1 + aLength1; michael@0: const uint8_t* end2 = aStart2 + aLength2; michael@0: michael@0: const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2; michael@0: const uint8_t* min_end = end1 < end2 ? end1 : end2; michael@0: michael@0: return max_start < min_end; michael@0: } michael@0: michael@0: } // namespace mozilla