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