1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "XMLHttpRequestMainThread.h"
8 
9 #include <algorithm>
10 #ifndef XP_WIN
11 #include <unistd.h>
12 #endif
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/CheckedInt.h"
15 #include "mozilla/dom/BlobBinding.h"
16 #include "mozilla/dom/BlobSet.h"
17 #include "mozilla/dom/DocGroup.h"
18 #include "mozilla/dom/DOMString.h"
19 #include "mozilla/dom/File.h"
20 #include "mozilla/dom/FileBinding.h"
21 #include "mozilla/dom/FileCreatorHelper.h"
22 #include "mozilla/dom/FetchUtil.h"
23 #include "mozilla/dom/FormData.h"
24 #include "mozilla/dom/MutableBlobStorage.h"
25 #include "mozilla/dom/XMLDocument.h"
26 #include "mozilla/dom/URLSearchParams.h"
27 #include "mozilla/dom/PromiseNativeHandler.h"
28 #include "mozilla/Encoding.h"
29 #include "mozilla/EventDispatcher.h"
30 #include "mozilla/EventListenerManager.h"
31 #include "mozilla/EventStateManager.h"
32 #include "mozilla/LoadInfo.h"
33 #include "mozilla/LoadContext.h"
34 #include "mozilla/MemoryReporting.h"
35 #include "nsIDOMDocument.h"
36 #include "mozilla/dom/ProgressEvent.h"
37 #include "nsIJARChannel.h"
38 #include "nsIJARURI.h"
39 #include "nsLayoutCID.h"
40 #include "nsReadableUtils.h"
41 
42 #include "nsIURI.h"
43 #include "nsIURIMutator.h"
44 #include "nsILoadGroup.h"
45 #include "nsNetUtil.h"
46 #include "nsStringStream.h"
47 #include "nsIAuthPrompt.h"
48 #include "nsIAuthPrompt2.h"
49 #include "nsIClassOfService.h"
50 #include "nsIOutputStream.h"
51 #include "nsISupportsPrimitives.h"
52 #include "nsISupportsPriority.h"
53 #include "nsIInterfaceRequestorUtils.h"
54 #include "nsStreamUtils.h"
55 #include "nsThreadUtils.h"
56 #include "nsIUploadChannel.h"
57 #include "nsIUploadChannel2.h"
58 #include "nsIDOMSerializer.h"
59 #include "nsXPCOM.h"
60 #include "nsIDOMEventListener.h"
61 #include "nsIScriptSecurityManager.h"
62 #include "nsIVariant.h"
63 #include "nsVariant.h"
64 #include "nsIScriptError.h"
65 #include "nsIStreamConverterService.h"
66 #include "nsICachingChannel.h"
67 #include "nsContentUtils.h"
68 #include "nsCycleCollectionParticipant.h"
69 #include "nsError.h"
70 #include "nsIHTMLDocument.h"
71 #include "nsIStorageStream.h"
72 #include "nsIPromptFactory.h"
73 #include "nsIWindowWatcher.h"
74 #include "nsIConsoleService.h"
75 #include "nsIContentSecurityPolicy.h"
76 #include "nsAsyncRedirectVerifyHelper.h"
77 #include "nsStringBuffer.h"
78 #include "nsIFileChannel.h"
79 #include "mozilla/Telemetry.h"
80 #include "jsfriendapi.h"
81 #include "GeckoProfiler.h"
82 #include "mozilla/dom/XMLHttpRequestBinding.h"
83 #include "mozilla/Attributes.h"
84 #include "MultipartBlobImpl.h"
85 #include "nsIPermissionManager.h"
86 #include "nsMimeTypes.h"
87 #include "nsIHttpChannelInternal.h"
88 #include "nsIClassOfService.h"
89 #include "nsCharSeparatedTokenizer.h"
90 #include "nsStreamListenerWrapper.h"
91 #include "xpcjsid.h"
92 #include "nsITimedChannel.h"
93 #include "nsWrapperCacheInlines.h"
94 #include "nsZipArchive.h"
95 #include "mozilla/Preferences.h"
96 #include "private/pprio.h"
97 #include "XMLHttpRequestUpload.h"
98 
99 // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
100 // replaced by FileCreatorHelper#CreateFileW.
101 #ifdef CreateFile
102 #undef CreateFile
103 #endif
104 
105 using namespace mozilla::net;
106 
107 namespace mozilla {
108 namespace dom {
109 
110 // Maximum size that we'll grow an ArrayBuffer instead of doubling,
111 // once doubling reaches this threshold
112 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
113 // start at 32k to avoid lots of doubling right at the start
114 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
115 // the maximum Content-Length that we'll preallocate.  1GB.  Must fit
116 // in an int32_t!
117 const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
118     1 * 1024 * 1024 * 1024LL;
119 
120 namespace {
121 const nsLiteralString ProgressEventTypeStrings[] = {
122     NS_LITERAL_STRING("loadstart"), NS_LITERAL_STRING("progress"),
123     NS_LITERAL_STRING("error"),     NS_LITERAL_STRING("abort"),
124     NS_LITERAL_STRING("timeout"),   NS_LITERAL_STRING("load"),
125     NS_LITERAL_STRING("loadend")};
126 static_assert(MOZ_ARRAY_LENGTH(ProgressEventTypeStrings) ==
127                   size_t(XMLHttpRequestMainThread::ProgressEventType::ENUM_MAX),
128               "Mismatched lengths for ProgressEventTypeStrings and "
129               "ProgressEventType enums");
130 
131 const nsString kLiteralString_readystatechange =
132     NS_LITERAL_STRING("readystatechange");
133 const nsString kLiteralString_xmlhttprequest =
134     NS_LITERAL_STRING("xmlhttprequest");
135 const nsString kLiteralString_DOMContentLoaded =
136     NS_LITERAL_STRING("DOMContentLoaded");
137 }  // namespace
138 
139 // CIDs
140 #define NS_BADCERTHANDLER_CONTRACTID \
141   "@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
142 
143 #define NS_PROGRESS_EVENT_INTERVAL 50
144 #define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
145 
146 NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
147 
148 class nsResumeTimeoutsEvent : public Runnable {
149  public:
nsResumeTimeoutsEvent(nsPIDOMWindowInner * aWindow)150   explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
151       : Runnable("dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
152 
Run()153   NS_IMETHOD Run() override {
154     mWindow->Resume();
155     return NS_OK;
156   }
157 
158  private:
159   nsCOMPtr<nsPIDOMWindowInner> mWindow;
160 };
161 
162 // This helper function adds the given load flags to the request's existing
163 // load flags.
AddLoadFlags(nsIRequest * request,nsLoadFlags newFlags)164 static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
165   nsLoadFlags flags;
166   request->GetLoadFlags(&flags);
167   flags |= newFlags;
168   request->SetLoadFlags(flags);
169 }
170 
171 // We are in a sync event loop.
172 #define NOT_CALLABLE_IN_SYNC_SEND                              \
173   if (mFlagSyncLooping) {                                      \
174     return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT; \
175   }
176 
177 #define NOT_CALLABLE_IN_SYNC_SEND_RV                               \
178   if (mFlagSyncLooping) {                                          \
179     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
180     return;                                                        \
181   }
182 
183 /////////////////////////////////////////////
184 //
185 //
186 /////////////////////////////////////////////
187 
188 bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
189 
XMLHttpRequestMainThread()190 XMLHttpRequestMainThread::XMLHttpRequestMainThread()
191     : mResponseBodyDecodedPos(0),
192       mResponseCharset(nullptr),
193       mResponseType(XMLHttpRequestResponseType::_empty),
194       mRequestObserver(nullptr),
195       mState(XMLHttpRequestBinding::UNSENT),
196       mFlagSynchronous(false),
197       mFlagAborted(false),
198       mFlagParseBody(false),
199       mFlagSyncLooping(false),
200       mFlagBackgroundRequest(false),
201       mFlagHadUploadListenersOnSend(false),
202       mFlagACwithCredentials(false),
203       mFlagTimedOut(false),
204       mFlagDeleted(false),
205       mFlagSend(false),
206       mUploadTransferred(0),
207       mUploadTotal(0),
208       mUploadComplete(true),
209       mProgressSinceLastProgressEvent(false),
210       mRequestSentTime(0),
211       mTimeoutMilliseconds(0),
212       mErrorLoad(ErrorType::eOK),
213       mErrorParsingXML(false),
214       mWaitingForOnStopRequest(false),
215       mProgressTimerIsActive(false),
216       mIsHtml(false),
217       mWarnAboutSyncHtml(false),
218       mLoadTotal(-1),
219       mIsSystem(false),
220       mIsAnon(false),
221       mFirstStartRequestSeen(false),
222       mInLoadProgressEvent(false),
223       mResultJSON(JS::UndefinedValue()),
224       mResultArrayBuffer(nullptr),
225       mIsMappedArrayBuffer(false),
226       mXPCOMifier(nullptr),
227       mEventDispatchingSuspended(false) {
228   mozilla::HoldJSObjects(this);
229 }
230 
~XMLHttpRequestMainThread()231 XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
232   mFlagDeleted = true;
233 
234   if ((mState == XMLHttpRequestBinding::OPENED && mFlagSend) ||
235       mState == XMLHttpRequestBinding::LOADING) {
236     Abort();
237   }
238 
239   if (mParseEndListener) {
240     mParseEndListener->SetIsStale();
241     mParseEndListener = nullptr;
242   }
243 
244   MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
245   mFlagSyncLooping = false;
246 
247   mResultJSON.setUndefined();
248   mResultArrayBuffer = nullptr;
249   mozilla::DropJSObjects(this);
250 }
251 
252 /**
253  * This Init method should only be called by C++ consumers.
254  */
Init(nsIPrincipal * aPrincipal,nsIGlobalObject * aGlobalObject,nsIURI * aBaseURI,nsILoadGroup * aLoadGroup)255 nsresult XMLHttpRequestMainThread::Init(nsIPrincipal* aPrincipal,
256                                         nsIGlobalObject* aGlobalObject,
257                                         nsIURI* aBaseURI,
258                                         nsILoadGroup* aLoadGroup) {
259   NS_ENSURE_ARG_POINTER(aPrincipal);
260   Construct(aPrincipal, aGlobalObject, aBaseURI, aLoadGroup);
261   return NS_OK;
262 }
263 
InitParameters(bool aAnon,bool aSystem)264 void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
265   if (!aAnon && !aSystem) {
266     return;
267   }
268 
269   // Check for permissions.
270   // Chrome is always allowed access, so do the permission check only
271   // for non-chrome pages.
272   if (!IsSystemXHR() && aSystem) {
273     nsIGlobalObject* global = GetOwnerGlobal();
274     if (NS_WARN_IF(!global)) {
275       SetParameters(aAnon, false);
276       return;
277     }
278 
279     nsIPrincipal* principal = global->PrincipalOrNull();
280     if (NS_WARN_IF(!principal)) {
281       SetParameters(aAnon, false);
282       return;
283     }
284 
285     nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
286     if (NS_WARN_IF(!permMgr)) {
287       SetParameters(aAnon, false);
288       return;
289     }
290 
291     uint32_t permission;
292     nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "systemXHR",
293                                                        &permission);
294     if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
295       SetParameters(aAnon, false);
296       return;
297     }
298   }
299 
300   SetParameters(aAnon, aSystem);
301 }
302 
SetClientInfoAndController(const ClientInfo & aClientInfo,const Maybe<ServiceWorkerDescriptor> & aController)303 void XMLHttpRequestMainThread::SetClientInfoAndController(
304     const ClientInfo& aClientInfo,
305     const Maybe<ServiceWorkerDescriptor>& aController) {
306   mClientInfo.emplace(aClientInfo);
307   mController = aController;
308 }
309 
ResetResponse()310 void XMLHttpRequestMainThread::ResetResponse() {
311   mResponseXML = nullptr;
312   mResponseBody.Truncate();
313   TruncateResponseText();
314   mResponseBlob = nullptr;
315   mBlobStorage = nullptr;
316   mResultArrayBuffer = nullptr;
317   mArrayBufferBuilder.reset();
318   mResultJSON.setUndefined();
319   mLoadTransferred = 0;
320   mResponseBodyDecodedPos = 0;
321 }
322 
SetRequestObserver(nsIRequestObserver * aObserver)323 void XMLHttpRequestMainThread::SetRequestObserver(
324     nsIRequestObserver* aObserver) {
325   mRequestObserver = aObserver;
326 }
327 
328 NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
329 
330 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
331                                                   XMLHttpRequestEventTarget)
332   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
333   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
334   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
335 
336   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
337 
338   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
339   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
340 
341   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
342   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
343 
344   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
345 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
346 
347 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
348                                                 XMLHttpRequestEventTarget)
349   tmp->mResultArrayBuffer = nullptr;
350   tmp->mArrayBufferBuilder.reset();
351   tmp->mResultJSON.setUndefined();
352   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)353   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
354   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
355 
356   NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
357 
358   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
359   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
360 
361   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
362   NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
363 
364   NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
365 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
366 
367 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
368                                                XMLHttpRequestEventTarget)
369   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
370   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
371 NS_IMPL_CYCLE_COLLECTION_TRACE_END
372 
373 bool XMLHttpRequestMainThread::IsCertainlyAliveForCC() const {
374   return mWaitingForOnStopRequest;
375 }
376 
377 // QueryInterface implementation for XMLHttpRequestMainThread
378 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)379   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
380   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
381   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
382   NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
383   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
384   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
385   NS_INTERFACE_MAP_ENTRY(nsINamed)
386   NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
387 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
388 
389 NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
390 NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
391 
392 void XMLHttpRequestMainThread::DisconnectFromOwner() {
393   XMLHttpRequestEventTarget::DisconnectFromOwner();
394   Abort();
395 }
396 
SizeOfEventTargetIncludingThis(MallocSizeOf aMallocSizeOf) const397 size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
398     MallocSizeOf aMallocSizeOf) const {
399   size_t n = aMallocSizeOf(this);
400   n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
401 
402   // Why is this safe?  Because no-one else will report this string.  The
403   // other possible sharers of this string are as follows.
404   //
405   // - The JS engine could hold copies if the JS code holds references, e.g.
406   //   |var text = XHR.responseText|.  However, those references will be via JS
407   //   external strings, for which the JS memory reporter does *not* report the
408   //   chars.
409   //
410   // - Binary extensions, but they're *extremely* unlikely to do any memory
411   //   reporting.
412   //
413   n += mResponseText.SizeOfThis(aMallocSizeOf);
414 
415   return n;
416 
417   // Measurement of the following members may be added later if DMD finds it is
418   // worthwhile:
419   // - lots
420 }
421 
LogMessage(const char * aWarning,nsPIDOMWindowInner * aWindow,const char16_t ** aParams=nullptr,uint32_t aParamCount=0)422 static void LogMessage(const char* aWarning, nsPIDOMWindowInner* aWindow,
423                        const char16_t** aParams = nullptr,
424                        uint32_t aParamCount = 0) {
425   nsCOMPtr<nsIDocument> doc;
426   if (aWindow) {
427     doc = aWindow->GetExtantDoc();
428   }
429   nsContentUtils::ReportToConsole(
430       nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), doc,
431       nsContentUtils::eDOM_PROPERTIES, aWarning, aParams, aParamCount);
432 }
433 
GetResponseXML(ErrorResult & aRv)434 nsIDocument* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
435   if (mResponseType != XMLHttpRequestResponseType::_empty &&
436       mResponseType != XMLHttpRequestResponseType::Document) {
437     aRv.Throw(
438         NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSEXML);
439     return nullptr;
440   }
441   if (mWarnAboutSyncHtml) {
442     mWarnAboutSyncHtml = false;
443     LogMessage("HTMLSyncXHRWarning", GetOwner());
444   }
445   if (mState != XMLHttpRequestBinding::DONE) {
446     return nullptr;
447   }
448   return mResponseXML;
449 }
450 
451 /*
452  * This piece copied from XMLDocument, we try to get the charset
453  * from HTTP headers.
454  */
DetectCharset()455 nsresult XMLHttpRequestMainThread::DetectCharset() {
456   mResponseCharset = nullptr;
457   mDecoder = nullptr;
458 
459   if (mResponseType != XMLHttpRequestResponseType::_empty &&
460       mResponseType != XMLHttpRequestResponseType::Text &&
461       mResponseType != XMLHttpRequestResponseType::Json) {
462     return NS_OK;
463   }
464 
465   nsAutoCString charsetVal;
466   const Encoding* encoding;
467   bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
468             (encoding = Encoding::ForLabel(charsetVal));
469   if (!ok) {
470     // MS documentation states UTF-8 is default for responseText
471     encoding = UTF_8_ENCODING;
472   }
473 
474   if (mResponseType == XMLHttpRequestResponseType::Json &&
475       encoding != UTF_8_ENCODING) {
476     // The XHR spec says only UTF-8 is supported for responseType == "json"
477     LogMessage("JSONCharsetWarning", GetOwner());
478     encoding = UTF_8_ENCODING;
479   }
480 
481   mResponseCharset = encoding;
482   mDecoder = encoding->NewDecoderWithBOMRemoval();
483 
484   return NS_OK;
485 }
486 
AppendToResponseText(const char * aSrcBuffer,uint32_t aSrcBufferLen)487 nsresult XMLHttpRequestMainThread::AppendToResponseText(
488     const char* aSrcBuffer, uint32_t aSrcBufferLen) {
489   NS_ENSURE_STATE(mDecoder);
490 
491   CheckedInt<size_t> destBufferLen =
492       mDecoder->MaxUTF16BufferLength(aSrcBufferLen);
493   if (!destBufferLen.isValid()) {
494     return NS_ERROR_OUT_OF_MEMORY;
495   }
496 
497   CheckedInt32 size = mResponseText.Length();
498   size += destBufferLen.value();
499   if (!size.isValid()) {
500     return NS_ERROR_OUT_OF_MEMORY;
501   }
502 
503   XMLHttpRequestStringWriterHelper helper(mResponseText);
504 
505   if (!helper.AddCapacity(destBufferLen.value())) {
506     return NS_ERROR_OUT_OF_MEMORY;
507   }
508 
509   // XXX there's no handling for incomplete byte sequences on EOF!
510   uint32_t result;
511   size_t read;
512   size_t written;
513   bool hadErrors;
514   Tie(result, read, written, hadErrors) = mDecoder->DecodeToUTF16(
515       AsBytes(MakeSpan(aSrcBuffer, aSrcBufferLen)),
516       MakeSpan(helper.EndOfExistingData(), destBufferLen.value()), false);
517   MOZ_ASSERT(result == kInputEmpty);
518   MOZ_ASSERT(read == aSrcBufferLen);
519   MOZ_ASSERT(written <= destBufferLen.value());
520   Unused << hadErrors;
521   helper.AddLength(written);
522   return NS_OK;
523 }
524 
GetResponseText(DOMString & aResponseText,ErrorResult & aRv)525 void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
526                                                ErrorResult& aRv) {
527   XMLHttpRequestStringSnapshot snapshot;
528   GetResponseText(snapshot, aRv);
529   if (aRv.Failed()) {
530     return;
531   }
532 
533   if (!snapshot.GetAsString(aResponseText)) {
534     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
535     return;
536   }
537 }
538 
GetResponseText(XMLHttpRequestStringSnapshot & aSnapshot,ErrorResult & aRv)539 void XMLHttpRequestMainThread::GetResponseText(
540     XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
541   aSnapshot.Reset();
542 
543   if (mResponseType != XMLHttpRequestResponseType::_empty &&
544       mResponseType != XMLHttpRequestResponseType::Text) {
545     aRv.Throw(
546         NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT);
547     return;
548   }
549 
550   if (mState != XMLHttpRequestBinding::LOADING &&
551       mState != XMLHttpRequestBinding::DONE) {
552     return;
553   }
554 
555   // Main Fetch step 18 requires to ignore body for head/connect methods.
556   if (mRequestMethod.EqualsLiteral("HEAD") ||
557       mRequestMethod.EqualsLiteral("CONNECT")) {
558     return;
559   }
560 
561   // We only decode text lazily if we're also parsing to a doc.
562   // Also, if we've decoded all current data already, then no need to decode
563   // more.
564   if ((!mResponseXML && !mErrorParsingXML) ||
565       mResponseBodyDecodedPos == mResponseBody.Length()) {
566     mResponseText.CreateSnapshot(aSnapshot);
567     return;
568   }
569 
570   MatchCharsetAndDecoderToResponseDocument();
571 
572   NS_ASSERTION(mResponseBodyDecodedPos < mResponseBody.Length(),
573                "Unexpected mResponseBodyDecodedPos");
574   aRv = AppendToResponseText(mResponseBody.get() + mResponseBodyDecodedPos,
575                              mResponseBody.Length() - mResponseBodyDecodedPos);
576   if (aRv.Failed()) {
577     return;
578   }
579 
580   mResponseBodyDecodedPos = mResponseBody.Length();
581 
582   if (mState == XMLHttpRequestBinding::DONE) {
583     // Free memory buffer which we no longer need
584     mResponseBody.Truncate();
585     mResponseBodyDecodedPos = 0;
586   }
587 
588   mResponseText.CreateSnapshot(aSnapshot);
589 }
590 
CreateResponseParsedJSON(JSContext * aCx)591 nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
592   if (!aCx) {
593     return NS_ERROR_FAILURE;
594   }
595 
596   nsAutoString string;
597   if (!mResponseText.GetAsString(string)) {
598     return NS_ERROR_OUT_OF_MEMORY;
599   }
600 
601   // The Unicode converter has already zapped the BOM if there was one
602   JS::Rooted<JS::Value> value(aCx);
603   if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
604     return NS_ERROR_FAILURE;
605   }
606 
607   mResultJSON = value;
608   return NS_OK;
609 }
610 
SetResponseType(XMLHttpRequestResponseType aResponseType,ErrorResult & aRv)611 void XMLHttpRequestMainThread::SetResponseType(
612     XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
613   NOT_CALLABLE_IN_SYNC_SEND_RV
614 
615   if (mState == XMLHttpRequestBinding::LOADING ||
616       mState == XMLHttpRequestBinding::DONE) {
617     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE);
618     return;
619   }
620 
621   // sync request is not allowed setting responseType in window context
622   if (HasOrHasHadOwner() && mState != XMLHttpRequestBinding::UNSENT &&
623       mFlagSynchronous) {
624     LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
625     aRv.Throw(
626         NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC);
627     return;
628   }
629 
630   if (mFlagSynchronous &&
631       aResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
632     aRv.Throw(
633         NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC);
634     return;
635   }
636 
637   // We want to get rid of this moz-only types. Bug 1335365.
638   if (aResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
639     Telemetry::Accumulate(Telemetry::MOZ_CHUNKED_ARRAYBUFFER_IN_XHR, 1);
640   }
641 
642   // Set the responseType attribute's value to the given value.
643   mResponseType = aResponseType;
644 }
645 
GetResponse(JSContext * aCx,JS::MutableHandle<JS::Value> aResponse,ErrorResult & aRv)646 void XMLHttpRequestMainThread::GetResponse(
647     JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
648   switch (mResponseType) {
649     case XMLHttpRequestResponseType::_empty:
650     case XMLHttpRequestResponseType::Text: {
651       DOMString str;
652       GetResponseText(str, aRv);
653       if (aRv.Failed()) {
654         return;
655       }
656       if (!xpc::StringToJsval(aCx, str, aResponse)) {
657         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
658       }
659       return;
660     }
661 
662     case XMLHttpRequestResponseType::Arraybuffer:
663     case XMLHttpRequestResponseType::Moz_chunked_arraybuffer: {
664       if (!(mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
665             mState == XMLHttpRequestBinding::DONE) &&
666           !(mResponseType ==
667                 XMLHttpRequestResponseType::Moz_chunked_arraybuffer &&
668             mInLoadProgressEvent)) {
669         aResponse.setNull();
670         return;
671       }
672 
673       if (!mResultArrayBuffer) {
674         mResultArrayBuffer = mArrayBufferBuilder.getArrayBuffer(aCx);
675         if (!mResultArrayBuffer) {
676           aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
677           return;
678         }
679       }
680       aResponse.setObject(*mResultArrayBuffer);
681       return;
682     }
683     case XMLHttpRequestResponseType::Blob: {
684       if (mState != XMLHttpRequestBinding::DONE) {
685         aResponse.setNull();
686         return;
687       }
688 
689       if (!mResponseBlob) {
690         aResponse.setNull();
691         return;
692       }
693 
694       GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse);
695       return;
696     }
697     case XMLHttpRequestResponseType::Document: {
698       if (!mResponseXML || mState != XMLHttpRequestBinding::DONE) {
699         aResponse.setNull();
700         return;
701       }
702 
703       aRv = nsContentUtils::WrapNative(aCx, mResponseXML, aResponse);
704       return;
705     }
706     case XMLHttpRequestResponseType::Json: {
707       if (mState != XMLHttpRequestBinding::DONE) {
708         aResponse.setNull();
709         return;
710       }
711 
712       if (mResultJSON.isUndefined()) {
713         aRv = CreateResponseParsedJSON(aCx);
714         TruncateResponseText();
715         if (aRv.Failed()) {
716           // Per spec, errors aren't propagated. null is returned instead.
717           aRv = NS_OK;
718           // It would be nice to log the error to the console. That's hard to
719           // do without calling window.onerror as a side effect, though.
720           JS_ClearPendingException(aCx);
721           mResultJSON.setNull();
722         }
723       }
724       aResponse.set(mResultJSON);
725       return;
726     }
727     default:
728       NS_ERROR("Should not happen");
729   }
730 
731   aResponse.setNull();
732 }
733 
IsCrossSiteCORSRequest() const734 bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const {
735   if (!mChannel) {
736     return false;
737   }
738 
739   nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
740   MOZ_ASSERT(loadInfo);
741 
742   if (!loadInfo) {
743     return false;
744   }
745 
746   return loadInfo->GetTainting() == LoadTainting::CORS;
747 }
748 
IsDeniedCrossSiteCORSRequest()749 bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
750   if (IsCrossSiteCORSRequest()) {
751     nsresult rv;
752     mChannel->GetStatus(&rv);
753     if (NS_FAILED(rv)) {
754       return true;
755     }
756   }
757   return false;
758 }
759 
GetResponseURL(nsAString & aUrl)760 void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
761   aUrl.Truncate();
762 
763   if ((mState == XMLHttpRequestBinding::UNSENT ||
764        mState == XMLHttpRequestBinding::OPENED) ||
765       !mChannel) {
766     return;
767   }
768 
769   // Make sure we don't leak responseURL information from denied cross-site
770   // requests.
771   if (IsDeniedCrossSiteCORSRequest()) {
772     return;
773   }
774 
775   nsCOMPtr<nsIURI> responseUrl;
776   mChannel->GetURI(getter_AddRefs(responseUrl));
777 
778   if (!responseUrl) {
779     return;
780   }
781 
782   nsAutoCString temp;
783   responseUrl->GetSpecIgnoringRef(temp);
784   CopyUTF8toUTF16(temp, aUrl);
785 }
786 
GetStatus(ErrorResult & aRv)787 uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
788   // Make sure we don't leak status information from denied cross-site
789   // requests.
790   if (IsDeniedCrossSiteCORSRequest()) {
791     return 0;
792   }
793 
794   if (mState == XMLHttpRequestBinding::UNSENT ||
795       mState == XMLHttpRequestBinding::OPENED) {
796     return 0;
797   }
798 
799   if (mErrorLoad != ErrorType::eOK) {
800     // Let's simulate the http protocol for jar/app requests:
801     nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
802     if (jarChannel) {
803       nsresult status;
804       mChannel->GetStatus(&status);
805 
806       if (status == NS_ERROR_FILE_NOT_FOUND) {
807         return 404;  // Not Found
808       } else {
809         return 500;  // Internal Error
810       }
811     }
812 
813     return 0;
814   }
815 
816   nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
817   if (!httpChannel) {
818     // Pretend like we got a 200 response, since our load was successful
819     return 200;
820   }
821 
822   uint32_t status;
823   nsresult rv = httpChannel->GetResponseStatus(&status);
824   if (NS_FAILED(rv)) {
825     status = 0;
826   }
827 
828   return status;
829 }
830 
GetStatusText(nsACString & aStatusText,ErrorResult & aRv)831 void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
832                                              ErrorResult& aRv) {
833   // Return an empty status text on all error loads.
834   aStatusText.Truncate();
835 
836   // Make sure we don't leak status information from denied cross-site
837   // requests.
838   if (IsDeniedCrossSiteCORSRequest()) {
839     return;
840   }
841 
842   // Check the current XHR state to see if it is valid to obtain the statusText
843   // value.  This check is to prevent the status text for redirects from being
844   // available before all the redirects have been followed and HTTP headers have
845   // been received.
846   if (mState == XMLHttpRequestBinding::UNSENT ||
847       mState == XMLHttpRequestBinding::OPENED) {
848     return;
849   }
850 
851   if (mErrorLoad != ErrorType::eOK) {
852     return;
853   }
854 
855   nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
856   if (httpChannel) {
857     Unused << httpChannel->GetResponseStatusText(aStatusText);
858   } else {
859     aStatusText.AssignLiteral("OK");
860   }
861 }
862 
TerminateOngoingFetch()863 void XMLHttpRequestMainThread::TerminateOngoingFetch() {
864   if ((mState == XMLHttpRequestBinding::OPENED && mFlagSend) ||
865       mState == XMLHttpRequestBinding::HEADERS_RECEIVED ||
866       mState == XMLHttpRequestBinding::LOADING) {
867     CloseRequest();
868   }
869 }
870 
CloseRequest()871 void XMLHttpRequestMainThread::CloseRequest() {
872   mWaitingForOnStopRequest = false;
873   mErrorLoad = ErrorType::eTerminated;
874   if (mChannel) {
875     mChannel->Cancel(NS_BINDING_ABORTED);
876   }
877   if (mTimeoutTimer) {
878     mTimeoutTimer->Cancel();
879   }
880 }
881 
CloseRequestWithError(const ProgressEventType aType)882 void XMLHttpRequestMainThread::CloseRequestWithError(
883     const ProgressEventType aType) {
884   CloseRequest();
885 
886   ResetResponse();
887 
888   // If we're in the destructor, don't risk dispatching an event.
889   if (mFlagDeleted) {
890     mFlagSyncLooping = false;
891     return;
892   }
893 
894   if (mState != XMLHttpRequestBinding::UNSENT &&
895       !(mState == XMLHttpRequestBinding::OPENED && !mFlagSend) &&
896       mState != XMLHttpRequestBinding::DONE) {
897     ChangeState(XMLHttpRequestBinding::DONE, true);
898 
899     if (!mFlagSyncLooping) {
900       if (mUpload && !mUploadComplete) {
901         mUploadComplete = true;
902         DispatchProgressEvent(mUpload, aType, 0, -1);
903       }
904       DispatchProgressEvent(this, aType, 0, -1);
905     }
906   }
907 
908   // The ChangeState call above calls onreadystatechange handlers which
909   // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
910   // the abort state bit. If this occurs we're not uninitialized (bug 361773).
911   if (mFlagAborted) {
912     ChangeState(XMLHttpRequestBinding::UNSENT, false);  // IE seems to do it
913   }
914 
915   mFlagSyncLooping = false;
916 }
917 
RequestErrorSteps(const ProgressEventType aEventType,const nsresult aOptionalException,ErrorResult & aRv)918 void XMLHttpRequestMainThread::RequestErrorSteps(
919     const ProgressEventType aEventType, const nsresult aOptionalException,
920     ErrorResult& aRv) {
921   // Step 1
922   mState = XMLHttpRequestBinding::DONE;
923 
924   StopProgressEventTimer();
925 
926   // Step 2
927   mFlagSend = false;
928 
929   // Step 3
930   ResetResponse();
931 
932   // If we're in the destructor, don't risk dispatching an event.
933   if (mFlagDeleted) {
934     mFlagSyncLooping = false;
935     return;
936   }
937 
938   // Step 4
939   if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
940     aRv.Throw(aOptionalException);
941     return;
942   }
943 
944   // Step 5
945   FireReadystatechangeEvent();
946 
947   // Step 6
948   if (mUpload && !mUploadComplete) {
949     // Step 6-1
950     mUploadComplete = true;
951 
952     // Step 6-2
953     if (mFlagHadUploadListenersOnSend) {
954       // Steps 6-3, 6-4 (loadend is fired for us)
955       DispatchProgressEvent(mUpload, aEventType, 0, -1);
956     }
957   }
958 
959   // Steps 7 and 8 (loadend is fired for us)
960   DispatchProgressEvent(this, aEventType, 0, -1);
961 }
962 
Abort(ErrorResult & aRv)963 void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
964   NOT_CALLABLE_IN_SYNC_SEND_RV
965   AbortInternal(aRv);
966 }
967 
AbortInternal(ErrorResult & aRv)968 void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
969   mFlagAborted = true;
970 
971   // Step 1
972   TerminateOngoingFetch();
973 
974   // Step 2
975   if ((mState == XMLHttpRequestBinding::OPENED && mFlagSend) ||
976       mState == XMLHttpRequestBinding::HEADERS_RECEIVED ||
977       mState == XMLHttpRequestBinding::LOADING) {
978     RequestErrorSteps(ProgressEventType::abort, NS_OK, aRv);
979   }
980 
981   // Step 3
982   if (mState == XMLHttpRequestBinding::DONE) {
983     ChangeState(XMLHttpRequestBinding::UNSENT,
984                 false);  // no ReadystateChange event
985   }
986 
987   mFlagSyncLooping = false;
988 }
989 
990 /*Method that checks if it is safe to expose a header value to the client.
991 It is used to check what headers are exposed for CORS requests.*/
IsSafeHeader(const nsACString & aHeader,NotNull<nsIHttpChannel * > aHttpChannel) const992 bool XMLHttpRequestMainThread::IsSafeHeader(
993     const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel) const {
994   // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
995   if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
996     NS_WARNING("blocked access to response header");
997     return false;
998   }
999   // if this is not a CORS call all headers are safe
1000   if (!IsCrossSiteCORSRequest()) {
1001     return true;
1002   }
1003   // Check for dangerous headers
1004   // Make sure we don't leak header information from denied cross-site
1005   // requests.
1006   if (mChannel) {
1007     nsresult status;
1008     mChannel->GetStatus(&status);
1009     if (NS_FAILED(status)) {
1010       return false;
1011     }
1012   }
1013   const char* kCrossOriginSafeHeaders[] = {"cache-control", "content-language",
1014                                            "content-type",  "expires",
1015                                            "last-modified", "pragma"};
1016   for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
1017     if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
1018       return true;
1019     }
1020   }
1021   nsAutoCString headerVal;
1022   // The "Access-Control-Expose-Headers" header contains a comma separated
1023   // list of method names.
1024   Unused << aHttpChannel->GetResponseHeader(
1025       NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), headerVal);
1026   nsCCharSeparatedTokenizer exposeTokens(headerVal, ',');
1027   bool isSafe = false;
1028   while (exposeTokens.hasMoreTokens()) {
1029     const nsDependentCSubstring& token = exposeTokens.nextToken();
1030     if (token.IsEmpty()) {
1031       continue;
1032     }
1033     if (!NS_IsValidHTTPToken(token)) {
1034       return false;
1035     }
1036     if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator())) {
1037       isSafe = true;
1038     }
1039   }
1040   return isSafe;
1041 }
1042 
GetAllResponseHeaders(nsACString & aResponseHeaders,ErrorResult & aRv)1043 void XMLHttpRequestMainThread::GetAllResponseHeaders(
1044     nsACString& aResponseHeaders, ErrorResult& aRv) {
1045   NOT_CALLABLE_IN_SYNC_SEND_RV
1046 
1047   aResponseHeaders.Truncate();
1048 
1049   // If the state is UNSENT or OPENED,
1050   // return the empty string and terminate these steps.
1051   if (mState == XMLHttpRequestBinding::UNSENT ||
1052       mState == XMLHttpRequestBinding::OPENED) {
1053     return;
1054   }
1055 
1056   if (mErrorLoad != ErrorType::eOK) {
1057     return;
1058   }
1059 
1060   if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
1061     RefPtr<nsHeaderVisitor> visitor =
1062         new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
1063     if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
1064       aResponseHeaders = visitor->Headers();
1065     }
1066     return;
1067   }
1068 
1069   if (!mChannel) {
1070     return;
1071   }
1072 
1073   // Even non-http channels supply content type.
1074   nsAutoCString value;
1075   if (NS_SUCCEEDED(mChannel->GetContentType(value))) {
1076     aResponseHeaders.AppendLiteral("Content-Type: ");
1077     aResponseHeaders.Append(value);
1078     if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
1079       aResponseHeaders.AppendLiteral(";charset=");
1080       aResponseHeaders.Append(value);
1081     }
1082     aResponseHeaders.AppendLiteral("\r\n");
1083   }
1084 
1085   // Don't provide Content-Length for data URIs
1086   nsCOMPtr<nsIURI> uri;
1087   bool isDataURI;
1088   if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
1089       NS_FAILED(uri->SchemeIs("data", &isDataURI)) || !isDataURI) {
1090     int64_t length;
1091     if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1092       aResponseHeaders.AppendLiteral("Content-Length: ");
1093       aResponseHeaders.AppendInt(length);
1094       aResponseHeaders.AppendLiteral("\r\n");
1095     }
1096   }
1097 }
1098 
GetResponseHeader(const nsACString & header,nsACString & _retval,ErrorResult & aRv)1099 void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
1100                                                  nsACString& _retval,
1101                                                  ErrorResult& aRv) {
1102   NOT_CALLABLE_IN_SYNC_SEND_RV
1103 
1104   _retval.SetIsVoid(true);
1105 
1106   nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1107 
1108   if (!httpChannel) {
1109     // If the state is UNSENT or OPENED,
1110     // return null and terminate these steps.
1111     if (mState == XMLHttpRequestBinding::UNSENT ||
1112         mState == XMLHttpRequestBinding::OPENED) {
1113       return;
1114     }
1115 
1116     // Even non-http channels supply content type and content length.
1117     // Remember we don't leak header information from denied cross-site
1118     // requests.
1119     nsresult status;
1120     if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
1121         NS_FAILED(status)) {
1122       return;
1123     }
1124 
1125     // Content Type:
1126     if (header.LowerCaseEqualsASCII("content-type")) {
1127       if (NS_FAILED(mChannel->GetContentType(_retval))) {
1128         // Means no content type
1129         _retval.SetIsVoid(true);
1130         return;
1131       }
1132 
1133       nsCString value;
1134       if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) &&
1135           !value.IsEmpty()) {
1136         _retval.AppendLiteral(";charset=");
1137         _retval.Append(value);
1138       }
1139     }
1140 
1141     // Content Length:
1142     else if (header.LowerCaseEqualsASCII("content-length")) {
1143       int64_t length;
1144       if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1145         _retval.AppendInt(length);
1146       }
1147     }
1148 
1149     return;
1150   }
1151 
1152   // Check for dangerous headers
1153   if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
1154     return;
1155   }
1156 
1157   aRv = httpChannel->GetResponseHeader(header, _retval);
1158   if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
1159     // Means no header
1160     _retval.SetIsVoid(true);
1161     aRv.SuppressException();
1162   }
1163 }
1164 
GetLoadGroup() const1165 already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup() const {
1166   if (mFlagBackgroundRequest) {
1167     return nullptr;
1168   }
1169 
1170   if (mLoadGroup) {
1171     nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
1172     return ref.forget();
1173   }
1174 
1175   nsIDocument* doc = GetDocumentIfCurrent();
1176   if (doc) {
1177     return doc->GetDocumentLoadGroup();
1178   }
1179 
1180   return nullptr;
1181 }
1182 
FireReadystatechangeEvent()1183 nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
1184   MOZ_ASSERT(mState != XMLHttpRequestBinding::UNSENT);
1185   RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1186   event->InitEvent(kLiteralString_readystatechange, false, false);
1187   // We assume anyone who managed to call CreateReadystatechangeEvent is trusted
1188   event->SetTrusted(true);
1189   DispatchOrStoreEvent(this, event);
1190   return NS_OK;
1191 }
1192 
DispatchProgressEvent(DOMEventTargetHelper * aTarget,const ProgressEventType aType,int64_t aLoaded,int64_t aTotal)1193 void XMLHttpRequestMainThread::DispatchProgressEvent(
1194     DOMEventTargetHelper* aTarget, const ProgressEventType aType,
1195     int64_t aLoaded, int64_t aTotal) {
1196   NS_ASSERTION(aTarget, "null target");
1197 
1198   if (NS_FAILED(CheckInnerWindowCorrectness()) ||
1199       (!AllowUploadProgress() && aTarget == mUpload)) {
1200     return;
1201   }
1202 
1203   // If blocked by CORS, zero-out the stats on progress events
1204   // and never fire "progress" or "load" events at all.
1205   if (IsDeniedCrossSiteCORSRequest()) {
1206     if (aType == ProgressEventType::progress ||
1207         aType == ProgressEventType::load) {
1208       return;
1209     }
1210     aLoaded = 0;
1211     aTotal = -1;
1212   }
1213 
1214   if (aType == ProgressEventType::progress) {
1215     mInLoadProgressEvent = true;
1216   }
1217 
1218   ProgressEventInit init;
1219   init.mBubbles = false;
1220   init.mCancelable = false;
1221   init.mLengthComputable = aTotal != -1;  // XHR spec step 6.1
1222   init.mLoaded = aLoaded;
1223   init.mTotal = (aTotal == -1) ? 0 : aTotal;
1224 
1225   const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
1226   RefPtr<ProgressEvent> event =
1227       ProgressEvent::Constructor(aTarget, typeString, init);
1228   event->SetTrusted(true);
1229 
1230   DispatchOrStoreEvent(aTarget, event);
1231 
1232   if (aType == ProgressEventType::progress) {
1233     mInLoadProgressEvent = false;
1234 
1235     // clear chunked responses after every progress event
1236     if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
1237       mResponseBody.Truncate();
1238       TruncateResponseText();
1239       mResultArrayBuffer = nullptr;
1240       mArrayBufferBuilder.reset();
1241     }
1242   }
1243 
1244   // If we're sending a load, error, timeout or abort event, then
1245   // also dispatch the subsequent loadend event.
1246   if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
1247       aType == ProgressEventType::timeout ||
1248       aType == ProgressEventType::abort) {
1249     DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
1250   }
1251 }
1252 
DispatchOrStoreEvent(DOMEventTargetHelper * aTarget,Event * aEvent)1253 void XMLHttpRequestMainThread::DispatchOrStoreEvent(
1254     DOMEventTargetHelper* aTarget, Event* aEvent) {
1255   MOZ_ASSERT(aTarget);
1256   MOZ_ASSERT(aEvent);
1257 
1258   if (mEventDispatchingSuspended) {
1259     PendingEvent* event = mPendingEvents.AppendElement();
1260     event->mTarget = aTarget;
1261     event->mEvent = aEvent;
1262     return;
1263   }
1264 
1265   bool dummy;
1266   aTarget->DispatchEvent(aEvent, &dummy);
1267 }
1268 
SuspendEventDispatching()1269 void XMLHttpRequestMainThread::SuspendEventDispatching() {
1270   MOZ_ASSERT(!mEventDispatchingSuspended);
1271   mEventDispatchingSuspended = true;
1272 }
1273 
ResumeEventDispatching()1274 void XMLHttpRequestMainThread::ResumeEventDispatching() {
1275   MOZ_ASSERT(mEventDispatchingSuspended);
1276   mEventDispatchingSuspended = false;
1277 
1278   nsTArray<PendingEvent> pendingEvents;
1279   pendingEvents.SwapElements(mPendingEvents);
1280 
1281   for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1282     bool dummy;
1283     pendingEvents[i].mTarget->DispatchEvent(pendingEvents[i].mEvent, &dummy);
1284   }
1285 }
1286 
1287 already_AddRefed<nsIHttpChannel>
GetCurrentHttpChannel()1288 XMLHttpRequestMainThread::GetCurrentHttpChannel() {
1289   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1290   return httpChannel.forget();
1291 }
1292 
1293 already_AddRefed<nsIJARChannel>
GetCurrentJARChannel()1294 XMLHttpRequestMainThread::GetCurrentJARChannel() {
1295   nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
1296   return appChannel.forget();
1297 }
1298 
IsSystemXHR() const1299 bool XMLHttpRequestMainThread::IsSystemXHR() const {
1300   return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
1301 }
1302 
InUploadPhase() const1303 bool XMLHttpRequestMainThread::InUploadPhase() const {
1304   // We're in the upload phase while our state is OPENED.
1305   return mState == XMLHttpRequestBinding::OPENED;
1306 }
1307 
1308 // This case is hit when the async parameter is outright omitted, which
1309 // should set it to true (and the username and password to null).
Open(const nsACString & aMethod,const nsAString & aUrl,ErrorResult & aRv)1310 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1311                                     const nsAString& aUrl, ErrorResult& aRv) {
1312   Open(aMethod, aUrl, true, VoidString(), VoidString(), aRv);
1313 }
1314 
1315 // This case is hit when the async parameter is specified, even if the
1316 // JS value was "undefined" (which due to legacy reasons should be
1317 // treated as true, which is how it will already be passed in here).
Open(const nsACString & aMethod,const nsAString & aUrl,bool aAsync,const nsAString & aUsername,const nsAString & aPassword,ErrorResult & aRv)1318 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1319                                     const nsAString& aUrl, bool aAsync,
1320                                     const nsAString& aUsername,
1321                                     const nsAString& aPassword,
1322                                     ErrorResult& aRv) {
1323   nsresult rv =
1324       Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword);
1325   if (NS_FAILED(rv)) {
1326     aRv.Throw(rv);
1327   }
1328 }
1329 
Open(const nsACString & aMethod,const nsACString & aUrl,bool aAsync,const nsAString & aUsername,const nsAString & aPassword)1330 nsresult XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1331                                         const nsACString& aUrl, bool aAsync,
1332                                         const nsAString& aUsername,
1333                                         const nsAString& aPassword) {
1334   NOT_CALLABLE_IN_SYNC_SEND
1335 
1336   // Gecko-specific
1337   if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
1338       GetOwner()->GetExtantDoc()) {
1339     GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest);
1340   }
1341 
1342   Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
1343                         aAsync ? 0 : 1);
1344 
1345   // Step 1
1346   nsCOMPtr<nsIDocument> responsibleDocument = GetDocumentIfCurrent();
1347   if (!responsibleDocument) {
1348     // This could be because we're no longer current or because we're in some
1349     // non-window context...
1350     nsresult rv = CheckInnerWindowCorrectness();
1351     if (NS_WARN_IF(NS_FAILED(rv))) {
1352       return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
1353     }
1354   }
1355   NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
1356 
1357   // Steps 2-4
1358   nsAutoCString method;
1359   nsresult rv = FetchUtil::GetValidRequestMethod(aMethod, method);
1360   if (NS_WARN_IF(NS_FAILED(rv))) {
1361     return rv;
1362   }
1363 
1364   // Steps 5-6
1365   nsCOMPtr<nsIURI> baseURI;
1366   if (mBaseURI) {
1367     baseURI = mBaseURI;
1368   } else if (responsibleDocument) {
1369     baseURI = responsibleDocument->GetBaseURI();
1370   }
1371 
1372   // Use the responsible document's encoding for the URL if we have one,
1373   // except for dedicated workers. Use UTF-8 otherwise.
1374   NotNull<const Encoding*> originCharset = UTF_8_ENCODING;
1375   if (responsibleDocument &&
1376       responsibleDocument->NodePrincipal() == mPrincipal) {
1377     originCharset = responsibleDocument->GetDocumentCharacterSet();
1378   }
1379 
1380   nsCOMPtr<nsIURI> parsedURL;
1381   rv = NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
1382   if (NS_FAILED(rv)) {
1383     if (rv == NS_ERROR_MALFORMED_URI) {
1384       return NS_ERROR_DOM_MALFORMED_URI;
1385     }
1386     return rv;
1387   }
1388   if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
1389     return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
1390   }
1391 
1392   // Step 7
1393   // This is already handled by the other Open() method, which passes
1394   // username and password in as NullStrings.
1395 
1396   // Step 8
1397   nsAutoCString host;
1398   parsedURL->GetHost(host);
1399   if (!host.IsEmpty()) {
1400     if (!aUsername.IsVoid() || !aPassword.IsVoid()) {
1401       Unused << NS_MutateURI(parsedURL)
1402                     .SetUsername(NS_ConvertUTF16toUTF8(aUsername))
1403                     .SetPassword(NS_ConvertUTF16toUTF8(aPassword))
1404                     .Finalize(parsedURL);
1405     }
1406   }
1407 
1408   // Step 9
1409   if (!aAsync && HasOrHasHadOwner() &&
1410       (mTimeoutMilliseconds ||
1411        mResponseType != XMLHttpRequestResponseType::_empty)) {
1412     if (mTimeoutMilliseconds) {
1413       LogMessage("TimeoutSyncXHRWarning", GetOwner());
1414     }
1415     if (mResponseType != XMLHttpRequestResponseType::_empty) {
1416       LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
1417     }
1418     return NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC;
1419   }
1420 
1421   // Step 10
1422   TerminateOngoingFetch();
1423 
1424   // Step 11
1425   // timeouts are handled without a flag
1426   mFlagSend = false;
1427   mRequestMethod.Assign(method);
1428   mRequestURL = parsedURL;
1429   mFlagSynchronous = !aAsync;
1430   mAuthorRequestHeaders.Clear();
1431   ResetResponse();
1432 
1433   // Gecko-specific
1434   mFlagHadUploadListenersOnSend = false;
1435   mFlagAborted = false;
1436   mFlagTimedOut = false;
1437 
1438   // Per spec we should only create the channel on send(), but we have internal
1439   // code that relies on the channel being created now, and that code is not
1440   // always IsSystemXHR(). However, we're not supposed to throw channel-creation
1441   // errors during open(), so we silently ignore those here.
1442   CreateChannel();
1443 
1444   // Step 12
1445   if (mState != XMLHttpRequestBinding::OPENED) {
1446     mState = XMLHttpRequestBinding::OPENED;
1447     FireReadystatechangeEvent();
1448   }
1449 
1450   return NS_OK;
1451 }
1452 
SetOriginAttributes(const OriginAttributesDictionary & aAttrs)1453 void XMLHttpRequestMainThread::SetOriginAttributes(
1454     const OriginAttributesDictionary& aAttrs) {
1455   MOZ_ASSERT((mState == XMLHttpRequestBinding::OPENED) && !mFlagSend);
1456 
1457   OriginAttributes attrs(aAttrs);
1458 
1459   nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
1460   MOZ_ASSERT(loadInfo);
1461   if (loadInfo) {
1462     loadInfo->SetOriginAttributes(attrs);
1463   }
1464 }
1465 
PopulateNetworkInterfaceId()1466 void XMLHttpRequestMainThread::PopulateNetworkInterfaceId() {
1467   if (mNetworkInterfaceId.IsEmpty()) {
1468     return;
1469   }
1470   nsCOMPtr<nsIHttpChannelInternal> channel(do_QueryInterface(mChannel));
1471   if (!channel) {
1472     return;
1473   }
1474   DebugOnly<nsresult> rv = channel->SetNetworkInterfaceId(mNetworkInterfaceId);
1475   MOZ_ASSERT(NS_SUCCEEDED(rv));
1476 }
1477 
1478 /*
1479  * "Copy" from a stream.
1480  */
StreamReaderFunc(nsIInputStream * in,void * closure,const char * fromRawSegment,uint32_t toOffset,uint32_t count,uint32_t * writeCount)1481 nsresult XMLHttpRequestMainThread::StreamReaderFunc(
1482     nsIInputStream* in, void* closure, const char* fromRawSegment,
1483     uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
1484   XMLHttpRequestMainThread* xmlHttpRequest =
1485       static_cast<XMLHttpRequestMainThread*>(closure);
1486   if (!xmlHttpRequest || !writeCount) {
1487     NS_WARNING(
1488         "XMLHttpRequest cannot read from stream: no closure or writeCount");
1489     return NS_ERROR_FAILURE;
1490   }
1491 
1492   nsresult rv = NS_OK;
1493 
1494   if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
1495     xmlHttpRequest->MaybeCreateBlobStorage();
1496     rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
1497   } else if ((xmlHttpRequest->mResponseType ==
1498                   XMLHttpRequestResponseType::Arraybuffer &&
1499               !xmlHttpRequest->mIsMappedArrayBuffer) ||
1500              xmlHttpRequest->mResponseType ==
1501                  XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
1502     // get the initial capacity to something reasonable to avoid a bunch of
1503     // reallocs right at the start
1504     if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0)
1505       xmlHttpRequest->mArrayBufferBuilder.setCapacity(
1506           std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
1507 
1508     if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder.append(
1509             reinterpret_cast<const uint8_t*>(fromRawSegment), count,
1510             XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
1511       return NS_ERROR_OUT_OF_MEMORY;
1512     }
1513 
1514   } else if (xmlHttpRequest->mResponseType ==
1515                  XMLHttpRequestResponseType::_empty &&
1516              xmlHttpRequest->mResponseXML) {
1517     // Copy for our own use
1518     if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
1519                                               fallible)) {
1520       return NS_ERROR_OUT_OF_MEMORY;
1521     }
1522   } else if (xmlHttpRequest->mResponseType ==
1523                  XMLHttpRequestResponseType::_empty ||
1524              xmlHttpRequest->mResponseType ==
1525                  XMLHttpRequestResponseType::Text ||
1526              xmlHttpRequest->mResponseType ==
1527                  XMLHttpRequestResponseType::Json) {
1528     NS_ASSERTION(!xmlHttpRequest->mResponseXML,
1529                  "We shouldn't be parsing a doc here");
1530     rv = xmlHttpRequest->AppendToResponseText(fromRawSegment, count);
1531     if (NS_WARN_IF(NS_FAILED(rv))) {
1532       return rv;
1533     }
1534   }
1535 
1536   if (xmlHttpRequest->mFlagParseBody) {
1537     // Give the same data to the parser.
1538 
1539     // We need to wrap the data in a new lightweight stream and pass that
1540     // to the parser, because calling ReadSegments() recursively on the same
1541     // stream is not supported.
1542     nsCOMPtr<nsIInputStream> copyStream;
1543     rv = NS_NewByteInputStream(getter_AddRefs(copyStream), fromRawSegment,
1544                                count);
1545 
1546     if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
1547       NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
1548       nsresult parsingResult =
1549           xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
1550               xmlHttpRequest->mChannel, xmlHttpRequest->mContext, copyStream,
1551               toOffset, count);
1552 
1553       // No use to continue parsing if we failed here, but we
1554       // should still finish reading the stream
1555       if (NS_FAILED(parsingResult)) {
1556         xmlHttpRequest->mFlagParseBody = false;
1557       }
1558     }
1559   }
1560 
1561   if (NS_SUCCEEDED(rv)) {
1562     *writeCount = count;
1563   } else {
1564     *writeCount = 0;
1565   }
1566 
1567   return rv;
1568 }
1569 
1570 namespace {
1571 
GetLocalFileFromChannel(nsIRequest * aRequest,nsIFile ** aFile)1572 nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
1573   MOZ_ASSERT(aRequest);
1574   MOZ_ASSERT(aFile);
1575 
1576   *aFile = nullptr;
1577 
1578   nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
1579   if (!fc) {
1580     return NS_OK;
1581   }
1582 
1583   nsCOMPtr<nsIFile> file;
1584   nsresult rv = fc->GetFile(getter_AddRefs(file));
1585   if (NS_WARN_IF(NS_FAILED(rv))) {
1586     return rv;
1587   }
1588 
1589   file.forget(aFile);
1590   return NS_OK;
1591 }
1592 
DummyStreamReaderFunc(nsIInputStream * aInputStream,void * aClosure,const char * aFromRawSegment,uint32_t aToOffset,uint32_t aCount,uint32_t * aWriteCount)1593 nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
1594                                const char* aFromRawSegment, uint32_t aToOffset,
1595                                uint32_t aCount, uint32_t* aWriteCount) {
1596   *aWriteCount = aCount;
1597   return NS_OK;
1598 }
1599 
1600 class FileCreationHandler final : public PromiseNativeHandler {
1601  public:
1602   NS_DECL_ISUPPORTS
1603 
Create(Promise * aPromise,XMLHttpRequestMainThread * aXHR)1604   static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
1605     MOZ_ASSERT(aPromise);
1606 
1607     RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
1608     aPromise->AppendNativeHandler(handler);
1609   }
1610 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1611   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1612     if (NS_WARN_IF(!aValue.isObject())) {
1613       mXHR->LocalFileToBlobCompleted(nullptr);
1614       return;
1615     }
1616 
1617     RefPtr<Blob> blob;
1618     if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
1619       mXHR->LocalFileToBlobCompleted(nullptr);
1620       return;
1621     }
1622 
1623     mXHR->LocalFileToBlobCompleted(blob);
1624   }
1625 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1626   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1627     mXHR->LocalFileToBlobCompleted(nullptr);
1628   }
1629 
1630  private:
FileCreationHandler(XMLHttpRequestMainThread * aXHR)1631   explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
1632     MOZ_ASSERT(aXHR);
1633   }
1634 
1635   ~FileCreationHandler() = default;
1636 
1637   RefPtr<XMLHttpRequestMainThread> mXHR;
1638 };
1639 
1640 NS_IMPL_ISUPPORTS0(FileCreationHandler)
1641 
1642 }  // namespace
1643 
LocalFileToBlobCompleted(Blob * aBlob)1644 void XMLHttpRequestMainThread::LocalFileToBlobCompleted(Blob* aBlob) {
1645   MOZ_ASSERT(mState != XMLHttpRequestBinding::DONE);
1646 
1647   mResponseBlob = aBlob;
1648   mBlobStorage = nullptr;
1649   NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1650 
1651   ChangeStateToDone();
1652 }
1653 
1654 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsISupports * ctxt,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)1655 XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
1656                                           nsISupports* ctxt,
1657                                           nsIInputStream* inStr,
1658                                           uint64_t sourceOffset,
1659                                           uint32_t count) {
1660   NS_ENSURE_ARG_POINTER(inStr);
1661 
1662   MOZ_ASSERT(mContext.get() == ctxt,
1663              "start context different from OnDataAvailable context");
1664 
1665   mProgressSinceLastProgressEvent = true;
1666   XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
1667 
1668   nsresult rv;
1669 
1670   nsCOMPtr<nsIFile> localFile;
1671   if (mResponseType == XMLHttpRequestResponseType::Blob) {
1672     rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
1673     if (NS_WARN_IF(NS_FAILED(rv))) {
1674       return rv;
1675     }
1676 
1677     if (localFile) {
1678       mBlobStorage = nullptr;
1679       NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1680 
1681       // The nsIStreamListener contract mandates us to read from the stream
1682       // before returning.
1683       uint32_t totalRead;
1684       rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
1685                                &totalRead);
1686       NS_ENSURE_SUCCESS(rv, rv);
1687 
1688       ChangeState(XMLHttpRequestBinding::LOADING);
1689 
1690       // Cancel() must be called with an error. We use
1691       // NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
1692       // just because we can retrieve the File from the channel directly.
1693       return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
1694     }
1695   }
1696 
1697   uint32_t totalRead;
1698   rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
1699                            (void*)this, count, &totalRead);
1700   NS_ENSURE_SUCCESS(rv, rv);
1701 
1702   // Fire the first progress event/loading state change
1703   if (mState == XMLHttpRequestBinding::HEADERS_RECEIVED) {
1704     ChangeState(XMLHttpRequestBinding::LOADING);
1705     if (!mFlagSynchronous) {
1706       DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
1707                             mLoadTotal);
1708     }
1709     mProgressSinceLastProgressEvent = false;
1710   }
1711 
1712   if (!mFlagSynchronous && !mProgressTimerIsActive) {
1713     StartProgressEventTimer();
1714   }
1715 
1716   return NS_OK;
1717 }
1718 
1719 NS_IMETHODIMP
OnStartRequest(nsIRequest * request,nsISupports * ctxt)1720 XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request,
1721                                          nsISupports* ctxt) {
1722   AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
1723 
1724   nsresult rv = NS_OK;
1725   if (!mFirstStartRequestSeen && mRequestObserver) {
1726     mFirstStartRequestSeen = true;
1727     mRequestObserver->OnStartRequest(request, ctxt);
1728   }
1729 
1730   if (request != mChannel) {
1731     // Can this still happen?
1732     return NS_OK;
1733   }
1734 
1735   // Don't do anything if we have been aborted
1736   if (mState == XMLHttpRequestBinding::UNSENT) {
1737     return NS_OK;
1738   }
1739 
1740   /* Apparently, Abort() should set UNSENT.  See bug 361773.
1741      XHR2 spec says this is correct. */
1742   if (mFlagAborted) {
1743     NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!");
1744 
1745     return NS_ERROR_UNEXPECTED;
1746   }
1747 
1748   // Don't do anything if we have timed out.
1749   if (mFlagTimedOut) {
1750     return NS_OK;
1751   }
1752 
1753   nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
1754   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
1755 
1756   nsresult status;
1757   request->GetStatus(&status);
1758   if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
1759     mErrorLoad = ErrorType::eRequest;
1760   }
1761 
1762   // Upload phase is now over. If we were uploading anything,
1763   // stop the timer and fire any final progress events.
1764   if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
1765       !mFlagSynchronous) {
1766     StopProgressEventTimer();
1767 
1768     mUploadTransferred = mUploadTotal;
1769 
1770     if (mProgressSinceLastProgressEvent) {
1771       DispatchProgressEvent(mUpload, ProgressEventType::progress,
1772                             mUploadTransferred, mUploadTotal);
1773       mProgressSinceLastProgressEvent = false;
1774     }
1775 
1776     mUploadComplete = true;
1777     DispatchProgressEvent(mUpload, ProgressEventType::load, mUploadTotal,
1778                           mUploadTotal);
1779   }
1780 
1781   mContext = ctxt;
1782   mFlagParseBody = true;
1783   ChangeState(XMLHttpRequestBinding::HEADERS_RECEIVED);
1784 
1785   ResetResponse();
1786 
1787   if (!mOverrideMimeType.IsEmpty()) {
1788     channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
1789   }
1790 
1791   // Fallback to 'application/octet-stream'
1792   nsAutoCString type;
1793   channel->GetContentType(type);
1794   if (type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
1795     channel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
1796   }
1797 
1798   DetectCharset();
1799 
1800   // Set up arraybuffer
1801   if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
1802       NS_SUCCEEDED(status)) {
1803     if (mIsMappedArrayBuffer) {
1804       nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
1805       if (jarChannel) {
1806         nsCOMPtr<nsIURI> uri;
1807         rv = channel->GetURI(getter_AddRefs(uri));
1808         if (NS_SUCCEEDED(rv)) {
1809           nsAutoCString file;
1810           nsAutoCString scheme;
1811           uri->GetScheme(scheme);
1812           if (scheme.LowerCaseEqualsLiteral("jar")) {
1813             nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
1814             if (jarURI) {
1815               jarURI->GetJAREntry(file);
1816             }
1817           }
1818           nsCOMPtr<nsIFile> jarFile;
1819           jarChannel->GetJarFile(getter_AddRefs(jarFile));
1820           if (!jarFile) {
1821             mIsMappedArrayBuffer = false;
1822           } else {
1823             rv = mArrayBufferBuilder.mapToFileInPackage(file, jarFile);
1824             // This can happen legitimately if there are compressed files
1825             // in the jarFile. See bug #1357219. No need to warn on the error.
1826             if (NS_FAILED(rv)) {
1827               mIsMappedArrayBuffer = false;
1828             } else {
1829               channel->SetContentType(
1830                   NS_LITERAL_CSTRING("application/mem-mapped"));
1831             }
1832           }
1833         }
1834       }
1835     }
1836     // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
1837     // and we want it fallback to the malloc way.
1838     if (!mIsMappedArrayBuffer) {
1839       int64_t contentLength;
1840       rv = channel->GetContentLength(&contentLength);
1841       if (NS_SUCCEEDED(rv) && contentLength > 0 &&
1842           contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
1843         mArrayBufferBuilder.setCapacity(static_cast<int32_t>(contentLength));
1844       }
1845     }
1846   }
1847 
1848   // Set up responseXML
1849   // Note: Main Fetch step 18 requires to ignore body for head/connect methods.
1850   bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
1851                     mResponseType == XMLHttpRequestResponseType::Document) &&
1852                    !(mRequestMethod.EqualsLiteral("HEAD") ||
1853                      mRequestMethod.EqualsLiteral("CONNECT"));
1854 
1855   mIsHtml = false;
1856   mWarnAboutSyncHtml = false;
1857   if (parseBody && NS_SUCCEEDED(status)) {
1858     // We can gain a huge performance win by not even trying to
1859     // parse non-XML data. This also protects us from the situation
1860     // where we have an XML document and sink, but HTML (or other)
1861     // parser, which can produce unreliable results.
1862     nsAutoCString type;
1863     channel->GetContentType(type);
1864 
1865     if ((mResponseType == XMLHttpRequestResponseType::Document) &&
1866         type.EqualsLiteral("text/html")) {
1867       // HTML parsing is only supported for responseType == "document" to
1868       // avoid running the parser and, worse, populating responseXML for
1869       // legacy users of XHR who use responseType == "" for retrieving the
1870       // responseText of text/html resources. This legacy case is so common
1871       // that it's not useful to emit a warning about it.
1872       if (mFlagSynchronous) {
1873         // We don't make cool new features available in the bad synchronous
1874         // mode. The synchronous mode is for legacy only.
1875         mWarnAboutSyncHtml = true;
1876         mFlagParseBody = false;
1877       } else {
1878         mIsHtml = true;
1879       }
1880     } else if (!(type.EqualsLiteral("text/xml") ||
1881                  type.EqualsLiteral("application/xml") ||
1882                  type.RFind("+xml", true, -1, 4) != kNotFound)) {
1883       // Follow https://xhr.spec.whatwg.org/
1884       // If final MIME type is not null, text/html, text/xml, application/xml,
1885       // or does not end in +xml, return null.
1886       mFlagParseBody = false;
1887     }
1888   } else {
1889     // The request failed, so we shouldn't be parsing anyway
1890     mFlagParseBody = false;
1891   }
1892 
1893   if (mFlagParseBody) {
1894     nsCOMPtr<nsIURI> baseURI, docURI;
1895     rv = mChannel->GetURI(getter_AddRefs(docURI));
1896     NS_ENSURE_SUCCESS(rv, rv);
1897     baseURI = docURI;
1898 
1899     nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
1900     nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
1901     if (doc) {
1902       chromeXHRDocURI = doc->GetDocumentURI();
1903       chromeXHRDocBaseURI = doc->GetBaseURI();
1904     } else {
1905       // If we're no longer current, just kill the load, though it really should
1906       // have been killed already.
1907       if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
1908         return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
1909       }
1910     }
1911 
1912     // Create an empty document from it.
1913     const nsAString& emptyStr = EmptyString();
1914     nsCOMPtr<nsIDOMDocument> responseDoc;
1915     nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
1916 
1917     nsCOMPtr<nsIPrincipal> requestingPrincipal;
1918     rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
1919         channel, getter_AddRefs(requestingPrincipal));
1920     NS_ENSURE_SUCCESS(rv, rv);
1921 
1922     rv = NS_NewDOMDocument(
1923         getter_AddRefs(responseDoc), emptyStr, emptyStr, nullptr, docURI,
1924         baseURI, requestingPrincipal, true, global,
1925         mIsHtml ? DocumentFlavorHTML : DocumentFlavorLegacyGuess);
1926     NS_ENSURE_SUCCESS(rv, rv);
1927     mResponseXML = do_QueryInterface(responseDoc);
1928     mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
1929     mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
1930 
1931     // suppress parsing failure messages to console for statuses which
1932     // can have empty bodies (see bug 884693).
1933     IgnoredErrorResult rv2;
1934     uint32_t responseStatus = GetStatus(rv2);
1935     if (!rv2.Failed() && (responseStatus == 201 || responseStatus == 202 ||
1936                           responseStatus == 204 || responseStatus == 205 ||
1937                           responseStatus == 304)) {
1938       mResponseXML->SetSuppressParserErrorConsoleMessages(true);
1939     }
1940 
1941     if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
1942       mResponseXML->ForceEnableXULXBL();
1943     }
1944 
1945     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
1946     MOZ_ASSERT(loadInfo);
1947     bool isCrossSite = false;
1948     if (loadInfo) {
1949       isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
1950     }
1951 
1952     if (isCrossSite) {
1953       nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mResponseXML);
1954       if (htmlDoc) {
1955         htmlDoc->DisableCookieAccess();
1956       }
1957     }
1958 
1959     nsCOMPtr<nsIStreamListener> listener;
1960     nsCOMPtr<nsILoadGroup> loadGroup;
1961     channel->GetLoadGroup(getter_AddRefs(loadGroup));
1962 
1963     // suppress <parsererror> nodes on XML document parse failure, but only
1964     // for non-privileged code (including Web Extensions). See bug 289714.
1965     if (!IsSystemXHR()) {
1966       mResponseXML->SetSuppressParserErrorElement(true);
1967     }
1968 
1969     rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
1970                                          nullptr, getter_AddRefs(listener),
1971                                          !isCrossSite);
1972     NS_ENSURE_SUCCESS(rv, rv);
1973 
1974     // the spec requires the response document.referrer to be the empty string
1975     mResponseXML->SetReferrer(NS_LITERAL_CSTRING(""));
1976 
1977     mXMLParserStreamListener = listener;
1978     rv = mXMLParserStreamListener->OnStartRequest(request, ctxt);
1979     NS_ENSURE_SUCCESS(rv, rv);
1980   }
1981 
1982   // Download phase beginning; start the progress event timer if necessary.
1983   if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
1984     StartProgressEventTimer();
1985   }
1986 
1987   return NS_OK;
1988 }
1989 
1990 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsISupports * ctxt,nsresult status)1991 XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsISupports* ctxt,
1992                                         nsresult status) {
1993   AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK);
1994 
1995   if (request != mChannel) {
1996     // Can this still happen?
1997     return NS_OK;
1998   }
1999 
2000   mWaitingForOnStopRequest = false;
2001 
2002   if (mRequestObserver) {
2003     NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!");
2004     mFirstStartRequestSeen = false;
2005     mRequestObserver->OnStopRequest(request, ctxt, status);
2006   }
2007 
2008   // make sure to notify the listener if we were aborted
2009   // XXX in fact, why don't we do the cleanup below in this case??
2010   // UNSENT is for abort calls.  See OnStartRequest above.
2011   if (mState == XMLHttpRequestBinding::UNSENT || mFlagTimedOut) {
2012     if (mXMLParserStreamListener)
2013       (void)mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
2014     return NS_OK;
2015   }
2016 
2017   // Is this good enough here?
2018   if (mXMLParserStreamListener && mFlagParseBody) {
2019     mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
2020   }
2021 
2022   mXMLParserStreamListener = nullptr;
2023   mContext = nullptr;
2024 
2025   bool waitingForBlobCreation = false;
2026 
2027   // If we have this error, we have to deal with a file: URL + responseType =
2028   // blob. We have this error because we canceled the channel. The status will
2029   // be set to NS_OK.
2030   if (status == NS_ERROR_FILE_ALREADY_EXISTS &&
2031       mResponseType == XMLHttpRequestResponseType::Blob) {
2032     nsCOMPtr<nsIFile> file;
2033     nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
2034     if (NS_WARN_IF(NS_FAILED(rv))) {
2035       return rv;
2036     }
2037 
2038     if (file) {
2039       nsAutoCString contentType;
2040       rv = mChannel->GetContentType(contentType);
2041       if (NS_WARN_IF(NS_FAILED(rv))) {
2042         return rv;
2043       }
2044 
2045       ChromeFilePropertyBag bag;
2046       bag.mType = NS_ConvertUTF8toUTF16(contentType);
2047 
2048       nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
2049 
2050       ErrorResult error;
2051       RefPtr<Promise> promise =
2052           FileCreatorHelper::CreateFile(global, file, bag, true, error);
2053       if (NS_WARN_IF(error.Failed())) {
2054         return error.StealNSResult();
2055       }
2056 
2057       FileCreationHandler::Create(promise, this);
2058       waitingForBlobCreation = true;
2059       status = NS_OK;
2060 
2061       NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2062       NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2063     }
2064   }
2065 
2066   if (NS_SUCCEEDED(status) &&
2067       mResponseType == XMLHttpRequestResponseType::Blob &&
2068       !waitingForBlobCreation) {
2069     // Smaller files may be written in cache map instead of separate files.
2070     // Also, no-store response cannot be written in persistent cache.
2071     nsAutoCString contentType;
2072     mChannel->GetContentType(contentType);
2073 
2074     // mBlobStorage can be null if the channel is non-file non-cacheable
2075     // and if the response length is zero.
2076     MaybeCreateBlobStorage();
2077     mBlobStorage->GetBlobWhenReady(GetOwner(), contentType, this);
2078     waitingForBlobCreation = true;
2079 
2080     NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2081     NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2082   } else if (NS_SUCCEEDED(status) &&
2083              ((mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
2084                !mIsMappedArrayBuffer) ||
2085               mResponseType ==
2086                   XMLHttpRequestResponseType::Moz_chunked_arraybuffer)) {
2087     // set the capacity down to the actual length, to realloc back
2088     // down to the actual size
2089     if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) {
2090       // this should never happen!
2091       status = NS_ERROR_UNEXPECTED;
2092     }
2093   }
2094 
2095   nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
2096   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
2097 
2098   channel->SetNotificationCallbacks(nullptr);
2099   mNotificationCallbacks = nullptr;
2100   mChannelEventSink = nullptr;
2101   mProgressEventSink = nullptr;
2102 
2103   mFlagSyncLooping = false;
2104   mRequestSentTime = 0;
2105 
2106   // update our charset and decoder to match mResponseXML,
2107   // before it is possibly nulled out
2108   MatchCharsetAndDecoderToResponseDocument();
2109 
2110   if (NS_FAILED(status)) {
2111     // This can happen if the server is unreachable. Other possible
2112     // reasons are that the user leaves the page or hits the ESC key.
2113 
2114     mErrorLoad = ErrorType::eUnreachable;
2115     mResponseXML = nullptr;
2116   }
2117 
2118   // If we're uninitialized at this point, we encountered an error
2119   // earlier and listeners have already been notified. Also we do
2120   // not want to do this if we already completed.
2121   if (mState == XMLHttpRequestBinding::UNSENT ||
2122       mState == XMLHttpRequestBinding::DONE) {
2123     return NS_OK;
2124   }
2125 
2126   if (!mResponseXML) {
2127     mFlagParseBody = false;
2128 
2129     // We postpone the 'done' until the creation of the Blob is completed.
2130     if (!waitingForBlobCreation) {
2131       ChangeStateToDone();
2132     }
2133 
2134     return NS_OK;
2135   }
2136 
2137   if (mIsHtml) {
2138     NS_ASSERTION(!mFlagSyncLooping,
2139                  "We weren't supposed to support HTML parsing with XHR!");
2140     mParseEndListener = new nsXHRParseEndListener(this);
2141     nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mResponseXML);
2142     EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
2143     manager->AddEventListenerByType(mParseEndListener,
2144                                     kLiteralString_DOMContentLoaded,
2145                                     TrustedEventsAtSystemGroupBubble());
2146     return NS_OK;
2147   } else {
2148     mFlagParseBody = false;
2149   }
2150 
2151   // We might have been sent non-XML data. If that was the case,
2152   // we should null out the document member. The idea in this
2153   // check here is that if there is no document element it is not
2154   // an XML document. We might need a fancier check...
2155   if (!mResponseXML->GetRootElement()) {
2156     mErrorParsingXML = true;
2157     mResponseXML = nullptr;
2158   }
2159   ChangeStateToDone();
2160   return NS_OK;
2161 }
2162 
OnBodyParseEnd()2163 void XMLHttpRequestMainThread::OnBodyParseEnd() {
2164   mFlagParseBody = false;
2165   mParseEndListener = nullptr;
2166   ChangeStateToDone();
2167 }
2168 
MatchCharsetAndDecoderToResponseDocument()2169 void XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument() {
2170   if (mResponseXML &&
2171       mResponseCharset != mResponseXML->GetDocumentCharacterSet()) {
2172     mResponseCharset = mResponseXML->GetDocumentCharacterSet();
2173     TruncateResponseText();
2174     mResponseBodyDecodedPos = 0;
2175     mDecoder = mResponseCharset->NewDecoderWithBOMRemoval();
2176   }
2177 }
2178 
ChangeStateToDone()2179 void XMLHttpRequestMainThread::ChangeStateToDone() {
2180   StopProgressEventTimer();
2181 
2182   MOZ_ASSERT(!mFlagParseBody,
2183              "ChangeStateToDone() called before async HTML parsing is done.");
2184 
2185   mFlagSend = false;
2186 
2187   if (mTimeoutTimer) {
2188     mTimeoutTimer->Cancel();
2189   }
2190 
2191   // Per spec, fire the last download progress event, if any,
2192   // before readystatechange=4/done. (Note that 0-sized responses
2193   // will have not sent a progress event yet, so one must be sent here).
2194   if (!mFlagSynchronous &&
2195       (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
2196     DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
2197                           mLoadTotal);
2198     mProgressSinceLastProgressEvent = false;
2199   }
2200 
2201   // Per spec, fire readystatechange=4/done before final error events.
2202   ChangeState(XMLHttpRequestBinding::DONE, true);
2203 
2204   // Per spec, if we failed in the upload phase, fire a final error
2205   // and loadend events for the upload after readystatechange=4/done.
2206   if (!mFlagSynchronous && mUpload && !mUploadComplete) {
2207     DispatchProgressEvent(mUpload, ProgressEventType::error, 0, -1);
2208   }
2209 
2210   // Per spec, fire download's load/error and loadend events after
2211   // readystatechange=4/done (and of course all upload events).
2212   if (mErrorLoad != ErrorType::eOK) {
2213     DispatchProgressEvent(this, ProgressEventType::error, 0, -1);
2214   } else {
2215     DispatchProgressEvent(this, ProgressEventType::load, mLoadTransferred,
2216                           mLoadTotal);
2217   }
2218 
2219   if (mErrorLoad != ErrorType::eOK) {
2220     // By nulling out channel here we make it so that Send() can test
2221     // for that and throw. Also calling the various status
2222     // methods/members will not throw.
2223     // This matches what IE does.
2224     mChannel = nullptr;
2225   }
2226 }
2227 
CreateChannel()2228 nsresult XMLHttpRequestMainThread::CreateChannel() {
2229   // When we are called from JS we can find the load group for the page,
2230   // and add ourselves to it. This way any pending requests
2231   // will be automatically aborted if the user leaves the page.
2232   nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
2233 
2234   nsSecurityFlags secFlags;
2235   nsLoadFlags loadFlags =
2236       nsIRequest::LOAD_BACKGROUND | nsIChannel::LOAD_CLASSIFY_URI;
2237   if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
2238     // When chrome is loading we want to make sure to sandbox any potential
2239     // result document. We also want to allow cross-origin loads.
2240     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
2241                nsILoadInfo::SEC_SANDBOXED;
2242   } else if (IsSystemXHR()) {
2243     // For pages that have appropriate permissions, we want to still allow
2244     // cross-origin loads, but make sure that the any potential result
2245     // documents get the same principal as the loader.
2246     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
2247                nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2248     loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
2249   } else {
2250     // Otherwise use CORS. Again, make sure that potential result documents
2251     // use the same principal as the loader.
2252     secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
2253                nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2254   }
2255 
2256   if (mIsAnon) {
2257     secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
2258   }
2259 
2260   // Use the responsibleDocument if we have it, except for dedicated workers
2261   // where it will be the parent document, which is not the one we want to use.
2262   nsresult rv;
2263   nsCOMPtr<nsIDocument> responsibleDocument = GetDocumentIfCurrent();
2264   if (responsibleDocument &&
2265       responsibleDocument->NodePrincipal() == mPrincipal) {
2266     rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL,
2267                        responsibleDocument, secFlags,
2268                        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2269                        nullptr,  // aPerformanceStorage
2270                        loadGroup,
2271                        nullptr,  // aCallbacks
2272                        loadFlags);
2273   } else if (mClientInfo.isSome()) {
2274     rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2275                        mClientInfo.ref(), mController, secFlags,
2276                        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2277                        mPerformanceStorage,  // aPerformanceStorage
2278                        loadGroup,
2279                        nullptr,  // aCallbacks
2280                        loadFlags);
2281   } else {
2282     // Otherwise use the principal.
2283     rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2284                        secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2285                        mPerformanceStorage,  // aPerformanceStorage
2286                        loadGroup,
2287                        nullptr,  // aCallbacks
2288                        loadFlags);
2289   }
2290   NS_ENSURE_SUCCESS(rv, rv);
2291 
2292   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2293   if (httpChannel) {
2294     rv = httpChannel->SetRequestMethod(mRequestMethod);
2295     NS_ENSURE_SUCCESS(rv, rv);
2296 
2297     // Set the initiator type
2298     nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
2299     if (timedChannel) {
2300       timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest"));
2301     }
2302   }
2303 
2304   return NS_OK;
2305 }
2306 
MaybeLowerChannelPriority()2307 void XMLHttpRequestMainThread::MaybeLowerChannelPriority() {
2308   nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
2309   if (!doc) {
2310     return;
2311   }
2312 
2313   AutoJSAPI jsapi;
2314   if (!jsapi.Init(GetOwnerGlobal())) {
2315     return;
2316   }
2317 
2318   JSContext* cx = jsapi.cx();
2319   nsAutoCString fileNameString;
2320   if (!nsJSUtils::GetCallingLocation(cx, fileNameString)) {
2321     return;
2322   }
2323 
2324   if (!doc->IsScriptTracking(fileNameString)) {
2325     return;
2326   }
2327 
2328   if (nsContentUtils::IsTailingEnabled()) {
2329     nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
2330     if (cos) {
2331       // Adding TailAllowed to overrule the Unblocked flag, but to preserve
2332       // the effect of Unblocked when tailing is off.
2333       cos->AddClassFlags(nsIClassOfService::Throttleable |
2334                          nsIClassOfService::Tail |
2335                          nsIClassOfService::TailAllowed);
2336     }
2337   }
2338 
2339   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
2340   if (p) {
2341     p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
2342   }
2343 }
2344 
InitiateFetch(already_AddRefed<nsIInputStream> aUploadStream,int64_t aUploadLength,nsACString & aUploadContentType)2345 nsresult XMLHttpRequestMainThread::InitiateFetch(
2346     already_AddRefed<nsIInputStream> aUploadStream, int64_t aUploadLength,
2347     nsACString& aUploadContentType) {
2348   nsresult rv;
2349   nsCOMPtr<nsIInputStream> uploadStream = Move(aUploadStream);
2350 
2351   // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
2352   // in turn keeps STOP button from becoming active.  If the consumer passed in
2353   // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
2354   // necko won't generate any progress notifications.
2355   if (HasListenersFor(nsGkAtoms::onprogress) ||
2356       (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
2357     nsLoadFlags loadFlags;
2358     mChannel->GetLoadFlags(&loadFlags);
2359     loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
2360     loadFlags |= nsIRequest::LOAD_NORMAL;
2361     mChannel->SetLoadFlags(loadFlags);
2362   }
2363 
2364   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2365   if (httpChannel) {
2366     // If the user hasn't overridden the Accept header, set it to */* per spec.
2367     if (!mAuthorRequestHeaders.Has("accept")) {
2368       mAuthorRequestHeaders.Set("accept", NS_LITERAL_CSTRING("*/*"));
2369     }
2370 
2371     mAuthorRequestHeaders.ApplyToChannel(httpChannel);
2372 
2373     if (!IsSystemXHR()) {
2374       nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
2375       nsCOMPtr<nsIDocument> doc = owner ? owner->GetExtantDoc() : nullptr;
2376       mozilla::net::ReferrerPolicy referrerPolicy =
2377           doc ? doc->GetReferrerPolicy() : mozilla::net::RP_Unset;
2378       nsContentUtils::SetFetchReferrerURIWithPolicy(
2379           mPrincipal, doc, httpChannel, referrerPolicy);
2380     }
2381 
2382     // Some extensions override the http protocol handler and provide their own
2383     // implementation. The channels returned from that implementation don't
2384     // always seem to implement the nsIUploadChannel2 interface, presumably
2385     // because it's a new interface. Eventually we should remove this and simply
2386     // require that http channels implement the new interface (see bug 529041).
2387     nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
2388     if (!uploadChannel2) {
2389       nsCOMPtr<nsIConsoleService> consoleService =
2390           do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2391       if (consoleService) {
2392         consoleService->LogStringMessage(
2393             u"Http channel implementation doesn't support nsIUploadChannel2. "
2394             "An extension has supplied a non-functional http protocol handler. "
2395             "This will break behavior and in future releases not work at all.");
2396       }
2397     }
2398 
2399     if (uploadStream) {
2400       // If necessary, wrap the stream in a buffered stream so as to guarantee
2401       // support for our upload when calling ExplicitSetUploadStream.
2402       if (!NS_InputStreamIsBuffered(uploadStream)) {
2403         nsCOMPtr<nsIInputStream> bufferedStream;
2404         rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2405                                        uploadStream.forget(), 4096);
2406         NS_ENSURE_SUCCESS(rv, rv);
2407 
2408         uploadStream = bufferedStream;
2409       }
2410 
2411       // We want to use a newer version of the upload channel that won't
2412       // ignore the necessary headers for an empty Content-Type.
2413       nsCOMPtr<nsIUploadChannel2> uploadChannel2(
2414           do_QueryInterface(httpChannel));
2415       // This assertion will fire if buggy extensions are installed
2416       NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
2417       if (uploadChannel2) {
2418         uploadChannel2->ExplicitSetUploadStream(
2419             uploadStream, aUploadContentType, mUploadTotal, mRequestMethod,
2420             false);
2421       } else {
2422         // The http channel doesn't support the new nsIUploadChannel2.
2423         // Emulate it as best we can using nsIUploadChannel.
2424         if (aUploadContentType.IsEmpty()) {
2425           aUploadContentType.AssignLiteral("application/octet-stream");
2426         }
2427         nsCOMPtr<nsIUploadChannel> uploadChannel =
2428             do_QueryInterface(httpChannel);
2429         uploadChannel->SetUploadStream(uploadStream, aUploadContentType,
2430                                        mUploadTotal);
2431         // Reset the method to its original value
2432         rv = httpChannel->SetRequestMethod(mRequestMethod);
2433         MOZ_ASSERT(NS_SUCCEEDED(rv));
2434       }
2435     }
2436   }
2437 
2438   // Due to the chrome-only XHR.channel API, we need a hacky way to set the
2439   // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
2440   // .withCredentials can be called after open() is called.
2441   // Not doing this for privileged system XHRs since those don't use CORS.
2442   if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
2443     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
2444     if (loadInfo) {
2445       static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
2446     }
2447   }
2448 
2449   // We never let XHR be blocked by head CSS/JS loads to avoid potential
2450   // deadlock where server generation of CSS/JS requires an XHR signal.
2451   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
2452   if (cos) {
2453     cos->AddClassFlags(nsIClassOfService::Unblocked);
2454 
2455     // Mark channel as urgent-start if the XHR is triggered by user input
2456     // events.
2457     if (EventStateManager::IsHandlingUserInput()) {
2458       cos->AddClassFlags(nsIClassOfService::UrgentStart);
2459     }
2460   }
2461 
2462   // Disable Necko-internal response timeouts.
2463   nsCOMPtr<nsIHttpChannelInternal> internalHttpChannel(
2464       do_QueryInterface(mChannel));
2465   if (internalHttpChannel) {
2466     rv = internalHttpChannel->SetResponseTimeoutEnabled(false);
2467     MOZ_ASSERT(NS_SUCCEEDED(rv));
2468   }
2469 
2470   if (!mIsAnon) {
2471     AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
2472   }
2473 
2474   // Bypass the network cache in cases where it makes no sense:
2475   // POST responses are always unique, and we provide no API that would
2476   // allow our consumers to specify a "cache key" to access old POST
2477   // responses, so they are not worth caching.
2478   if (mRequestMethod.EqualsLiteral("POST")) {
2479     AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
2480                                nsIRequest::INHIBIT_CACHING);
2481   } else {
2482     // When we are sync loading, we need to bypass the local cache when it would
2483     // otherwise block us waiting for exclusive access to the cache.  If we
2484     // don't do this, then we could dead lock in some cases (see bug 309424).
2485     //
2486     // Also don't block on the cache entry on async if it is busy - favoring
2487     // parallelism over cache hit rate for xhr. This does not disable the cache
2488     // everywhere - only in cases where more than one channel for the same URI
2489     // is accessed simultanously.
2490     AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
2491   }
2492 
2493   // Since we expect XML data, set the type hint accordingly
2494   // if the channel doesn't know any content type.
2495   // This means that we always try to parse local files as XML
2496   // ignoring return value, as this is not critical. Use text/xml as fallback
2497   // MIME type.
2498   nsAutoCString contentType;
2499   if (NS_FAILED(mChannel->GetContentType(contentType)) ||
2500       contentType.IsEmpty() ||
2501       contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2502     mChannel->SetContentType(NS_LITERAL_CSTRING("text/xml"));
2503   }
2504 
2505   // Set up the preflight if needed
2506   if (!IsSystemXHR()) {
2507     nsTArray<nsCString> CORSUnsafeHeaders;
2508     mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
2509     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
2510     if (loadInfo) {
2511       loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
2512                                      mFlagHadUploadListenersOnSend);
2513     }
2514   }
2515 
2516   // Hook us up to listen to redirects and the like. Only do this very late
2517   // since this creates a cycle between the channel and us. This cycle has
2518   // to be manually broken if anything below fails.
2519   mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
2520   mChannel->SetNotificationCallbacks(this);
2521 
2522   if (internalHttpChannel) {
2523     internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
2524   }
2525 
2526   // Because of bug 682305, we can't let listener be the XHR object itself
2527   // because JS wouldn't be able to use it. So create a listener around 'this'.
2528   // Make sure to hold a strong reference so that we don't leak the wrapper.
2529   nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
2530 
2531   // Check if this XHR is created from a tracking script.
2532   // If yes, lower the channel's priority.
2533   if (nsContentUtils::IsLowerNetworkPriority()) {
2534     MaybeLowerChannelPriority();
2535   }
2536 
2537   // Start reading from the channel
2538   rv = mChannel->AsyncOpen2(listener);
2539   listener = nullptr;
2540   if (NS_WARN_IF(NS_FAILED(rv))) {
2541     // Drop our ref to the channel to avoid cycles. Also drop channel's
2542     // ref to us to be extra safe.
2543     mChannel->SetNotificationCallbacks(mNotificationCallbacks);
2544     mChannel = nullptr;
2545 
2546     mErrorLoad = ErrorType::eChannelOpen;
2547 
2548     // Per spec, we throw on sync errors, but not async.
2549     if (mFlagSynchronous) {
2550       mState = XMLHttpRequestBinding::DONE;
2551       return NS_ERROR_DOM_NETWORK_ERR;
2552     }
2553   }
2554 
2555   return NS_OK;
2556 }
2557 
UnsuppressEventHandlingAndResume()2558 void XMLHttpRequestMainThread::UnsuppressEventHandlingAndResume() {
2559   MOZ_ASSERT(NS_IsMainThread());
2560   MOZ_ASSERT(mFlagSynchronous);
2561 
2562   if (mSuspendedDoc) {
2563     mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(true);
2564     mSuspendedDoc = nullptr;
2565   }
2566 
2567   if (mResumeTimeoutRunnable) {
2568     DispatchToMainThread(mResumeTimeoutRunnable.forget());
2569     mResumeTimeoutRunnable = nullptr;
2570   }
2571 }
2572 
Send(JSContext * aCx,const Nullable<DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString> & aData,ErrorResult & aRv)2573 void XMLHttpRequestMainThread::Send(
2574     JSContext* aCx,
2575     const Nullable<
2576         DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
2577         aData,
2578     ErrorResult& aRv) {
2579   NOT_CALLABLE_IN_SYNC_SEND_RV
2580 
2581   if (aData.IsNull()) {
2582     aRv = SendInternal(nullptr);
2583     return;
2584   }
2585 
2586   if (aData.Value().IsDocument()) {
2587     BodyExtractor<nsIDocument> body(&aData.Value().GetAsDocument());
2588     aRv = SendInternal(&body);
2589     return;
2590   }
2591 
2592   if (aData.Value().IsBlob()) {
2593     BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
2594     aRv = SendInternal(&body);
2595     return;
2596   }
2597 
2598   if (aData.Value().IsArrayBuffer()) {
2599     BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
2600     aRv = SendInternal(&body);
2601     return;
2602   }
2603 
2604   if (aData.Value().IsArrayBufferView()) {
2605     BodyExtractor<const ArrayBufferView> body(
2606         &aData.Value().GetAsArrayBufferView());
2607     aRv = SendInternal(&body);
2608     return;
2609   }
2610 
2611   if (aData.Value().IsFormData()) {
2612     BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
2613     aRv = SendInternal(&body);
2614     return;
2615   }
2616 
2617   if (aData.Value().IsURLSearchParams()) {
2618     BodyExtractor<const URLSearchParams> body(
2619         &aData.Value().GetAsURLSearchParams());
2620     aRv = SendInternal(&body);
2621     return;
2622   }
2623 
2624   if (aData.Value().IsUSVString()) {
2625     BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
2626     aRv = SendInternal(&body);
2627     return;
2628   }
2629 }
2630 
MaybeSilentSendFailure(nsresult aRv)2631 nsresult XMLHttpRequestMainThread::MaybeSilentSendFailure(nsresult aRv) {
2632   // Per spec, silently fail on async request failures; throw for sync.
2633   if (mFlagSynchronous) {
2634     mState = XMLHttpRequestBinding::DONE;
2635     return NS_ERROR_DOM_NETWORK_ERR;
2636   }
2637 
2638   // Defer the actual sending of async events just in case listeners
2639   // are attached after the send() method is called.
2640   Unused << NS_WARN_IF(
2641       NS_FAILED(DispatchToMainThread(NewRunnableMethod<ProgressEventType>(
2642           "dom::XMLHttpRequestMainThread::CloseRequestWithError", this,
2643           &XMLHttpRequestMainThread::CloseRequestWithError,
2644           ProgressEventType::error))));
2645   return NS_OK;
2646 }
2647 
SendInternal(const BodyExtractorBase * aBody)2648 nsresult XMLHttpRequestMainThread::SendInternal(
2649     const BodyExtractorBase* aBody) {
2650   MOZ_ASSERT(NS_IsMainThread());
2651 
2652   NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
2653 
2654   // Step 1
2655   if (mState != XMLHttpRequestBinding::OPENED) {
2656     return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED;
2657   }
2658 
2659   // Step 2
2660   if (mFlagSend) {
2661     return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING;
2662   }
2663 
2664   nsresult rv = CheckInnerWindowCorrectness();
2665   if (NS_FAILED(rv)) {
2666     return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
2667   }
2668 
2669   // If open() failed to create the channel, then throw a network error
2670   // as per spec. We really should create the channel here in send(), but
2671   // we have internal code relying on the channel being created in open().
2672   if (!mChannel) {
2673     mFlagSend = true;  // so CloseRequestWithError sets us to DONE.
2674     return MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
2675   }
2676 
2677   PopulateNetworkInterfaceId();
2678 
2679   // XXX We should probably send a warning to the JS console
2680   //     if there are no event listeners set and we are doing
2681   //     an asynchronous call.
2682 
2683   mUploadTransferred = 0;
2684   mUploadTotal = 0;
2685   // By default we don't have any upload, so mark upload complete.
2686   mUploadComplete = true;
2687   mErrorLoad = ErrorType::eOK;
2688   mLoadTotal = -1;
2689   nsCOMPtr<nsIInputStream> uploadStream;
2690   nsAutoCString uploadContentType;
2691   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2692   if (aBody && httpChannel && !mRequestMethod.EqualsLiteral("GET") &&
2693       !mRequestMethod.EqualsLiteral("HEAD")) {
2694     nsAutoCString charset;
2695     nsAutoCString defaultContentType;
2696     uint64_t size_u64;
2697     rv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64,
2698                             defaultContentType, charset);
2699     NS_ENSURE_SUCCESS(rv, rv);
2700 
2701     // make sure it fits within js MAX_SAFE_INTEGER
2702     mUploadTotal =
2703         net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
2704 
2705     if (uploadStream) {
2706       // If author set no Content-Type, use the default from GetAsStream().
2707       mAuthorRequestHeaders.Get("content-type", uploadContentType);
2708       if (uploadContentType.IsVoid()) {
2709         uploadContentType = defaultContentType;
2710       }
2711 
2712       // We don't want to set a charset for streams.
2713       if (!charset.IsEmpty()) {
2714         // Replace all case-insensitive matches of the charset in the
2715         // content-type with the correct case.
2716         RequestHeaders::CharsetIterator iter(uploadContentType);
2717         const nsCaseInsensitiveCStringComparator cmp;
2718         while (iter.Next()) {
2719           if (!iter.Equals(charset, cmp)) {
2720             iter.Replace(charset);
2721           }
2722         }
2723       }
2724 
2725       mUploadComplete = false;
2726     }
2727   }
2728 
2729   ResetResponse();
2730 
2731   // Check if we should enable cross-origin upload listeners.
2732   if (mUpload && mUpload->HasListeners()) {
2733     mFlagHadUploadListenersOnSend = true;
2734   }
2735 
2736   mIsMappedArrayBuffer = false;
2737   if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
2738       IsMappedArrayBufferEnabled()) {
2739     nsCOMPtr<nsIURI> uri;
2740     nsAutoCString scheme;
2741 
2742     rv = mChannel->GetURI(getter_AddRefs(uri));
2743     if (NS_SUCCEEDED(rv)) {
2744       uri->GetScheme(scheme);
2745       if (scheme.LowerCaseEqualsLiteral("jar")) {
2746         mIsMappedArrayBuffer = true;
2747       }
2748     }
2749   }
2750 
2751   rv = InitiateFetch(uploadStream.forget(), mUploadTotal, uploadContentType);
2752   NS_ENSURE_SUCCESS(rv, rv);
2753 
2754   // Start our timeout
2755   mRequestSentTime = PR_Now();
2756   StartTimeoutTimer();
2757 
2758   mWaitingForOnStopRequest = true;
2759 
2760   // Step 8
2761   mFlagSend = true;
2762 
2763   // If we're synchronous, spin an event loop here and wait
2764   if (mFlagSynchronous) {
2765     mFlagSyncLooping = true;
2766 
2767     if (GetOwner()) {
2768       if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
2769               GetOwner()->GetOuterWindow()->GetTop()) {
2770         if (nsCOMPtr<nsPIDOMWindowInner> topInner =
2771                 topWindow->GetCurrentInnerWindow()) {
2772           mSuspendedDoc = topWindow->GetExtantDoc();
2773           if (mSuspendedDoc) {
2774             mSuspendedDoc->SuppressEventHandling();
2775           }
2776           topInner->Suspend();
2777           mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
2778         }
2779       }
2780     }
2781 
2782     SuspendEventDispatching();
2783     StopProgressEventTimer();
2784 
2785     SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
2786     if (syncTimeoutType == eErrorOrExpired) {
2787       Abort();
2788       rv = NS_ERROR_DOM_NETWORK_ERR;
2789     }
2790 
2791     if (NS_SUCCEEDED(rv)) {
2792       nsAutoSyncOperation sync(mSuspendedDoc);
2793       if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
2794         rv = NS_ERROR_UNEXPECTED;
2795       }
2796 
2797       // Time expired... We should throw.
2798       if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
2799         rv = NS_ERROR_DOM_NETWORK_ERR;
2800       }
2801 
2802       CancelSyncTimeoutTimer();
2803     }
2804 
2805     UnsuppressEventHandlingAndResume();
2806     ResumeEventDispatching();
2807   } else {
2808     // Now that we've successfully opened the channel, we can change state. Note
2809     // that this needs to come after the AsyncOpen() and rv check, because this
2810     // can run script that would try to restart this request, and that could end
2811     // up doing our AsyncOpen on a null channel if the reentered AsyncOpen
2812     // fails.
2813     StopProgressEventTimer();
2814 
2815     // Upload phase beginning; start the progress event timer if necessary.
2816     if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
2817       StartProgressEventTimer();
2818     }
2819     // Dispatch loadstart events
2820     DispatchProgressEvent(this, ProgressEventType::loadstart, 0, -1);
2821     if (mUpload && !mUploadComplete) {
2822       DispatchProgressEvent(mUpload, ProgressEventType::loadstart, 0,
2823                             mUploadTotal);
2824     }
2825   }
2826 
2827   if (!mChannel) {
2828     return MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
2829   }
2830 
2831   return rv;
2832 }
2833 
2834 /* static */
IsMappedArrayBufferEnabled()2835 bool XMLHttpRequestMainThread::IsMappedArrayBufferEnabled() {
2836   static bool sMappedArrayBufferAdded = false;
2837   static bool sIsMappedArrayBufferEnabled;
2838 
2839   if (!sMappedArrayBufferAdded) {
2840     Preferences::AddBoolVarCache(&sIsMappedArrayBufferEnabled,
2841                                  "dom.mapped_arraybuffer.enabled", true);
2842     sMappedArrayBufferAdded = true;
2843   }
2844 
2845   return sIsMappedArrayBufferEnabled;
2846 }
2847 
2848 /* static */
IsLowercaseResponseHeader()2849 bool XMLHttpRequestMainThread::IsLowercaseResponseHeader() {
2850   static bool sLowercaseResponseHeaderAdded = false;
2851   static bool sIsLowercaseResponseHeaderEnabled;
2852 
2853   if (!sLowercaseResponseHeaderAdded) {
2854     Preferences::AddBoolVarCache(&sIsLowercaseResponseHeaderEnabled,
2855                                  "dom.xhr.lowercase_header.enabled", false);
2856     sLowercaseResponseHeaderAdded = true;
2857   }
2858 
2859   return sIsLowercaseResponseHeaderEnabled;
2860 }
2861 
2862 // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
SetRequestHeader(const nsACString & aName,const nsACString & aValue,ErrorResult & aRv)2863 void XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
2864                                                 const nsACString& aValue,
2865                                                 ErrorResult& aRv) {
2866   NOT_CALLABLE_IN_SYNC_SEND_RV
2867 
2868   // Step 1
2869   if (mState != XMLHttpRequestBinding::OPENED) {
2870     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED);
2871     return;
2872   }
2873 
2874   // Step 2
2875   if (mFlagSend) {
2876     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING);
2877     return;
2878   }
2879 
2880   // Step 3
2881   nsAutoCString value;
2882   NS_TrimHTTPWhitespace(aValue, value);
2883 
2884   // Step 4
2885   if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
2886     aRv.Throw(NS_ERROR_DOM_INVALID_HEADER_NAME);
2887     return;
2888   }
2889 
2890   // Step 5
2891   bool isPrivilegedCaller = IsSystemXHR();
2892   bool isForbiddenHeader = nsContentUtils::IsForbiddenRequestHeader(aName);
2893   if (!isPrivilegedCaller && isForbiddenHeader) {
2894     NS_ConvertUTF8toUTF16 name(aName);
2895     const char16_t* params[] = {name.get()};
2896     LogMessage("ForbiddenHeaderWarning", GetOwner(), params,
2897                ArrayLength(params));
2898     return;
2899   }
2900 
2901   // Step 6.1
2902   // Skipping for now, as normalizing the case of header names may not be
2903   // web-compatible. See bug 1285036.
2904 
2905   // Step 6.2-6.3
2906   // Gecko-specific: invalid headers can be set by privileged
2907   //                 callers, but will not merge.
2908   if (isPrivilegedCaller && isForbiddenHeader) {
2909     mAuthorRequestHeaders.Set(aName, value);
2910   } else {
2911     mAuthorRequestHeaders.MergeOrSet(aName, value);
2912   }
2913 }
2914 
SetTimeout(uint32_t aTimeout,ErrorResult & aRv)2915 void XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) {
2916   NOT_CALLABLE_IN_SYNC_SEND_RV
2917 
2918   if (mFlagSynchronous && mState != XMLHttpRequestBinding::UNSENT &&
2919       HasOrHasHadOwner()) {
2920     /* Timeout is not supported for synchronous requests with an owning window,
2921        per XHR2 spec. */
2922     LogMessage("TimeoutSyncXHRWarning", GetOwner());
2923     aRv.Throw(
2924         NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC);
2925     return;
2926   }
2927 
2928   mTimeoutMilliseconds = aTimeout;
2929   if (mRequestSentTime) {
2930     StartTimeoutTimer();
2931   }
2932 }
2933 
SetTimerEventTarget(nsITimer * aTimer)2934 void XMLHttpRequestMainThread::SetTimerEventTarget(nsITimer* aTimer) {
2935   if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
2936     nsCOMPtr<nsIEventTarget> target =
2937         global->EventTargetFor(TaskCategory::Other);
2938     aTimer->SetTarget(target);
2939   }
2940 }
2941 
DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable)2942 nsresult XMLHttpRequestMainThread::DispatchToMainThread(
2943     already_AddRefed<nsIRunnable> aRunnable) {
2944   if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
2945     nsCOMPtr<nsIEventTarget> target =
2946         global->EventTargetFor(TaskCategory::Other);
2947     MOZ_ASSERT(target);
2948 
2949     return target->Dispatch(Move(aRunnable), NS_DISPATCH_NORMAL);
2950   }
2951 
2952   return NS_DispatchToMainThread(Move(aRunnable));
2953 }
2954 
StartTimeoutTimer()2955 void XMLHttpRequestMainThread::StartTimeoutTimer() {
2956   MOZ_ASSERT(
2957       mRequestSentTime,
2958       "StartTimeoutTimer mustn't be called before the request was sent!");
2959   if (mState == XMLHttpRequestBinding::DONE) {
2960     // do nothing!
2961     return;
2962   }
2963 
2964   if (mTimeoutTimer) {
2965     mTimeoutTimer->Cancel();
2966   }
2967 
2968   if (!mTimeoutMilliseconds) {
2969     return;
2970   }
2971 
2972   if (!mTimeoutTimer) {
2973     mTimeoutTimer = NS_NewTimer();
2974     SetTimerEventTarget(mTimeoutTimer);
2975   }
2976   uint32_t elapsed =
2977       (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
2978   mTimeoutTimer->InitWithCallback(
2979       this, mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
2980       nsITimer::TYPE_ONE_SHOT);
2981 }
2982 
ReadyState() const2983 uint16_t XMLHttpRequestMainThread::ReadyState() const { return mState; }
2984 
OverrideMimeType(const nsAString & aMimeType,ErrorResult & aRv)2985 void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType,
2986                                                 ErrorResult& aRv) {
2987   NOT_CALLABLE_IN_SYNC_SEND_RV
2988 
2989   if (mState == XMLHttpRequestBinding::LOADING ||
2990       mState == XMLHttpRequestBinding::DONE) {
2991     ResetResponse();
2992     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE);
2993     return;
2994   }
2995 
2996   mOverrideMimeType = aMimeType;
2997 }
2998 
MozBackgroundRequest() const2999 bool XMLHttpRequestMainThread::MozBackgroundRequest() const {
3000   return mFlagBackgroundRequest;
3001 }
3002 
SetMozBackgroundRequest(bool aMozBackgroundRequest)3003 nsresult XMLHttpRequestMainThread::SetMozBackgroundRequest(
3004     bool aMozBackgroundRequest) {
3005   if (!IsSystemXHR()) {
3006     return NS_ERROR_DOM_SECURITY_ERR;
3007   }
3008 
3009   if (mState != XMLHttpRequestBinding::UNSENT) {
3010     // Can't change this while we're in the middle of something.
3011     return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING;
3012   }
3013 
3014   mFlagBackgroundRequest = aMozBackgroundRequest;
3015 
3016   return NS_OK;
3017 }
3018 
SetMozBackgroundRequest(bool aMozBackgroundRequest,ErrorResult & aRv)3019 void XMLHttpRequestMainThread::SetMozBackgroundRequest(
3020     bool aMozBackgroundRequest, ErrorResult& aRv) {
3021   // No errors for this webIDL method on main-thread.
3022   SetMozBackgroundRequest(aMozBackgroundRequest);
3023 }
3024 
WithCredentials() const3025 bool XMLHttpRequestMainThread::WithCredentials() const {
3026   return mFlagACwithCredentials;
3027 }
3028 
SetWithCredentials(bool aWithCredentials,ErrorResult & aRv)3029 void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
3030                                                   ErrorResult& aRv) {
3031   NOT_CALLABLE_IN_SYNC_SEND_RV
3032 
3033   // Return error if we're already processing a request.  Note that we can't use
3034   // ReadyState() here, because it can't differentiate between "opened" and
3035   // "sent", so we use mState directly.
3036 
3037   if ((mState != XMLHttpRequestBinding::UNSENT &&
3038        mState != XMLHttpRequestBinding::OPENED) ||
3039       mFlagSend || mIsAnon) {
3040     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING);
3041     return;
3042   }
3043 
3044   mFlagACwithCredentials = aWithCredentials;
3045 }
3046 
ChangeState(uint16_t aState,bool aBroadcast)3047 nsresult XMLHttpRequestMainThread::ChangeState(uint16_t aState,
3048                                                bool aBroadcast) {
3049   mState = aState;
3050   nsresult rv = NS_OK;
3051 
3052   if (aState != XMLHttpRequestBinding::HEADERS_RECEIVED &&
3053       aState != XMLHttpRequestBinding::LOADING) {
3054     StopProgressEventTimer();
3055   }
3056 
3057   if (aBroadcast &&
3058       (!mFlagSynchronous || aState == XMLHttpRequestBinding::OPENED ||
3059        aState == XMLHttpRequestBinding::DONE)) {
3060     rv = FireReadystatechangeEvent();
3061   }
3062 
3063   return rv;
3064 }
3065 
3066 /////////////////////////////////////////////////////
3067 // nsIChannelEventSink methods:
3068 //
3069 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * callback)3070 XMLHttpRequestMainThread::AsyncOnChannelRedirect(
3071     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
3072     nsIAsyncVerifyRedirectCallback* callback) {
3073   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
3074 
3075   // Prepare to receive callback
3076   mRedirectCallback = callback;
3077   mNewRedirectChannel = aNewChannel;
3078 
3079   if (mChannelEventSink) {
3080     nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd = EnsureXPCOMifier();
3081 
3082     nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(
3083         aOldChannel, aNewChannel, aFlags, fwd);
3084     if (NS_FAILED(rv)) {
3085       mRedirectCallback = nullptr;
3086       mNewRedirectChannel = nullptr;
3087     }
3088     return rv;
3089   }
3090   OnRedirectVerifyCallback(NS_OK);
3091   return NS_OK;
3092 }
3093 
OnRedirectVerifyCallback(nsresult result)3094 nsresult XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result) {
3095   NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
3096   NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
3097 
3098   if (NS_SUCCEEDED(result)) {
3099     mChannel = mNewRedirectChannel;
3100 
3101     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
3102     if (httpChannel) {
3103       // Ensure all original headers are duplicated for the new channel (bug
3104       // #553888)
3105       mAuthorRequestHeaders.ApplyToChannel(httpChannel);
3106     }
3107   } else {
3108     mErrorLoad = ErrorType::eRedirect;
3109   }
3110 
3111   mNewRedirectChannel = nullptr;
3112 
3113   mRedirectCallback->OnRedirectVerifyCallback(result);
3114   mRedirectCallback = nullptr;
3115 
3116   // It's important that we return success here. If we return the result code
3117   // that we were passed, JavaScript callers who cancel the redirect will wind
3118   // up throwing an exception in the process.
3119   return NS_OK;
3120 }
3121 
3122 /////////////////////////////////////////////////////
3123 // nsIProgressEventSink methods:
3124 //
3125 
3126 NS_IMETHODIMP
OnProgress(nsIRequest * aRequest,nsISupports * aContext,int64_t aProgress,int64_t aProgressMax)3127 XMLHttpRequestMainThread::OnProgress(nsIRequest* aRequest,
3128                                      nsISupports* aContext, int64_t aProgress,
3129                                      int64_t aProgressMax) {
3130   // When uploading, OnProgress reports also headers in aProgress and
3131   // aProgressMax. So, try to remove the headers, if possible.
3132   bool lengthComputable = (aProgressMax != -1);
3133   if (InUploadPhase()) {
3134     int64_t loaded = aProgress;
3135     if (lengthComputable) {
3136       int64_t headerSize = aProgressMax - mUploadTotal;
3137       loaded -= headerSize;
3138     }
3139     mUploadTransferred = loaded;
3140     mProgressSinceLastProgressEvent = true;
3141 
3142     if (!mFlagSynchronous && !mProgressTimerIsActive) {
3143       StartProgressEventTimer();
3144     }
3145   } else {
3146     mLoadTotal = aProgressMax;
3147     mLoadTransferred = aProgress;
3148     // OnDataAvailable() handles mProgressSinceLastProgressEvent
3149     // for the download phase.
3150   }
3151 
3152   if (mProgressEventSink) {
3153     mProgressEventSink->OnProgress(aRequest, aContext, aProgress, aProgressMax);
3154   }
3155 
3156   return NS_OK;
3157 }
3158 
3159 NS_IMETHODIMP
OnStatus(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus,const char16_t * aStatusArg)3160 XMLHttpRequestMainThread::OnStatus(nsIRequest* aRequest, nsISupports* aContext,
3161                                    nsresult aStatus,
3162                                    const char16_t* aStatusArg) {
3163   if (mProgressEventSink) {
3164     mProgressEventSink->OnStatus(aRequest, aContext, aStatus, aStatusArg);
3165   }
3166 
3167   return NS_OK;
3168 }
3169 
AllowUploadProgress()3170 bool XMLHttpRequestMainThread::AllowUploadProgress() {
3171   return !IsCrossSiteCORSRequest() || mFlagHadUploadListenersOnSend;
3172 }
3173 
3174 /////////////////////////////////////////////////////
3175 // nsIInterfaceRequestor methods:
3176 //
3177 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)3178 XMLHttpRequestMainThread::GetInterface(const nsIID& aIID, void** aResult) {
3179   nsresult rv;
3180 
3181   // Make sure to return ourselves for the channel event sink interface and
3182   // progress event sink interface, no matter what.  We can forward these to
3183   // mNotificationCallbacks if it wants to get notifications for them.  But we
3184   // need to see these notifications for proper functioning.
3185   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3186     mChannelEventSink = do_GetInterface(mNotificationCallbacks);
3187     *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
3188     return NS_OK;
3189   } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
3190     mProgressEventSink = do_GetInterface(mNotificationCallbacks);
3191     *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
3192     return NS_OK;
3193   }
3194 
3195   // Now give mNotificationCallbacks (if non-null) a chance to return the
3196   // desired interface.
3197   if (mNotificationCallbacks) {
3198     rv = mNotificationCallbacks->GetInterface(aIID, aResult);
3199     if (NS_SUCCEEDED(rv)) {
3200       NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
3201       return rv;
3202     }
3203   }
3204 
3205   if (mFlagBackgroundRequest) {
3206     nsCOMPtr<nsIInterfaceRequestor> badCertHandler(
3207         do_CreateInstance(NS_BADCERTHANDLER_CONTRACTID, &rv));
3208 
3209     // Ignore failure to get component, we may not have all its dependencies
3210     // available
3211     if (NS_SUCCEEDED(rv)) {
3212       rv = badCertHandler->GetInterface(aIID, aResult);
3213       if (NS_SUCCEEDED(rv)) return rv;
3214     }
3215   } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
3216              aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
3217     nsCOMPtr<nsIPromptFactory> wwatch =
3218         do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
3219     NS_ENSURE_SUCCESS(rv, rv);
3220 
3221     // Get the an auth prompter for our window so that the parenting
3222     // of the dialogs works as it should when using tabs.
3223 
3224     nsCOMPtr<nsPIDOMWindowOuter> window;
3225     if (GetOwner()) {
3226       window = GetOwner()->GetOuterWindow();
3227     }
3228 
3229     return wwatch->GetPrompt(window, aIID, reinterpret_cast<void**>(aResult));
3230   }
3231   // Now check for the various XHR non-DOM interfaces, except
3232   // nsIProgressEventSink and nsIChannelEventSink which we already
3233   // handled above.
3234   else if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
3235     *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
3236     return NS_OK;
3237   } else if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
3238     *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
3239     return NS_OK;
3240   } else if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
3241     *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
3242     return NS_OK;
3243   }
3244 
3245   return QueryInterface(aIID, aResult);
3246 }
3247 
GetInterface(JSContext * aCx,nsIJSID * aIID,JS::MutableHandle<JS::Value> aRetval,ErrorResult & aRv)3248 void XMLHttpRequestMainThread::GetInterface(
3249     JSContext* aCx, nsIJSID* aIID, JS::MutableHandle<JS::Value> aRetval,
3250     ErrorResult& aRv) {
3251   dom::GetInterface(aCx, this, aIID, aRetval, aRv);
3252 }
3253 
GetUpload(ErrorResult & aRv)3254 XMLHttpRequestUpload* XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv) {
3255   if (!mUpload) {
3256     mUpload = new XMLHttpRequestUpload(this);
3257   }
3258   return mUpload;
3259 }
3260 
MozAnon() const3261 bool XMLHttpRequestMainThread::MozAnon() const { return mIsAnon; }
3262 
MozSystem() const3263 bool XMLHttpRequestMainThread::MozSystem() const { return IsSystemXHR(); }
3264 
HandleTimeoutCallback()3265 void XMLHttpRequestMainThread::HandleTimeoutCallback() {
3266   if (mState == XMLHttpRequestBinding::DONE) {
3267     NS_NOTREACHED(
3268         "XMLHttpRequestMainThread::HandleTimeoutCallback with completed "
3269         "request");
3270     // do nothing!
3271     return;
3272   }
3273 
3274   mFlagTimedOut = true;
3275   CloseRequestWithError(ProgressEventType::timeout);
3276 }
3277 
3278 NS_IMETHODIMP
Notify(nsITimer * aTimer)3279 XMLHttpRequestMainThread::Notify(nsITimer* aTimer) {
3280   if (mProgressNotifier == aTimer) {
3281     HandleProgressTimerCallback();
3282     return NS_OK;
3283   }
3284 
3285   if (mTimeoutTimer == aTimer) {
3286     HandleTimeoutCallback();
3287     return NS_OK;
3288   }
3289 
3290   if (mSyncTimeoutTimer == aTimer) {
3291     HandleSyncTimeoutTimer();
3292     return NS_OK;
3293   }
3294 
3295   // Just in case some JS user wants to QI to nsITimerCallback and play with
3296   // us...
3297   NS_WARNING("Unexpected timer!");
3298   return NS_ERROR_INVALID_POINTER;
3299 }
3300 
HandleProgressTimerCallback()3301 void XMLHttpRequestMainThread::HandleProgressTimerCallback() {
3302   // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
3303   if (!mLoadTotal && mLoadTransferred) {
3304     return;
3305   }
3306 
3307   mProgressTimerIsActive = false;
3308 
3309   if (!mProgressSinceLastProgressEvent || mErrorLoad != ErrorType::eOK) {
3310     return;
3311   }
3312 
3313   if (InUploadPhase()) {
3314     if (mUpload && !mUploadComplete && mFlagHadUploadListenersOnSend) {
3315       DispatchProgressEvent(mUpload, ProgressEventType::progress,
3316                             mUploadTransferred, mUploadTotal);
3317     }
3318   } else {
3319     FireReadystatechangeEvent();
3320     DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
3321                           mLoadTotal);
3322   }
3323 
3324   mProgressSinceLastProgressEvent = false;
3325 
3326   StartProgressEventTimer();
3327 }
3328 
StopProgressEventTimer()3329 void XMLHttpRequestMainThread::StopProgressEventTimer() {
3330   if (mProgressNotifier) {
3331     mProgressTimerIsActive = false;
3332     mProgressNotifier->Cancel();
3333   }
3334 }
3335 
StartProgressEventTimer()3336 void XMLHttpRequestMainThread::StartProgressEventTimer() {
3337   if (!mProgressNotifier) {
3338     mProgressNotifier = NS_NewTimer();
3339     SetTimerEventTarget(mProgressNotifier);
3340   }
3341   if (mProgressNotifier) {
3342     mProgressTimerIsActive = true;
3343     mProgressNotifier->Cancel();
3344     mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
3345                                         nsITimer::TYPE_ONE_SHOT);
3346   }
3347 }
3348 
3349 XMLHttpRequestMainThread::SyncTimeoutType
MaybeStartSyncTimeoutTimer()3350 XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer() {
3351   MOZ_ASSERT(mFlagSynchronous);
3352 
3353   nsIDocument* doc = GetDocumentIfCurrent();
3354   if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
3355     return eNoTimerNeeded;
3356   }
3357 
3358   // If we are in a beforeunload or a unload event, we must force a timeout.
3359   TimeDuration diff =
3360       (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
3361   if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
3362     return eErrorOrExpired;
3363   }
3364 
3365   mSyncTimeoutTimer = NS_NewTimer();
3366   SetTimerEventTarget(mSyncTimeoutTimer);
3367   if (!mSyncTimeoutTimer) {
3368     return eErrorOrExpired;
3369   }
3370 
3371   uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
3372   nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
3373                                                     nsITimer::TYPE_ONE_SHOT);
3374   return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
3375 }
3376 
HandleSyncTimeoutTimer()3377 void XMLHttpRequestMainThread::HandleSyncTimeoutTimer() {
3378   MOZ_ASSERT(mSyncTimeoutTimer);
3379   MOZ_ASSERT(mFlagSyncLooping);
3380 
3381   CancelSyncTimeoutTimer();
3382   Abort();
3383 }
3384 
CancelSyncTimeoutTimer()3385 void XMLHttpRequestMainThread::CancelSyncTimeoutTimer() {
3386   if (mSyncTimeoutTimer) {
3387     mSyncTimeoutTimer->Cancel();
3388     mSyncTimeoutTimer = nullptr;
3389   }
3390 }
3391 
3392 already_AddRefed<nsXMLHttpRequestXPCOMifier>
EnsureXPCOMifier()3393 XMLHttpRequestMainThread::EnsureXPCOMifier() {
3394   if (!mXPCOMifier) {
3395     mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
3396   }
3397   RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
3398   return newRef.forget();
3399 }
3400 
ShouldBlockAuthPrompt()3401 bool XMLHttpRequestMainThread::ShouldBlockAuthPrompt() {
3402   // Verify that it's ok to prompt for credentials here, per spec
3403   // http://xhr.spec.whatwg.org/#the-send%28%29-method
3404 
3405   if (mAuthorRequestHeaders.Has("authorization")) {
3406     return true;
3407   }
3408 
3409   nsCOMPtr<nsIURI> uri;
3410   nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
3411   if (NS_WARN_IF(NS_FAILED(rv))) {
3412     return false;
3413   }
3414 
3415   // Also skip if a username and/or password is provided in the URI.
3416   nsCString username;
3417   rv = uri->GetUsername(username);
3418   if (NS_WARN_IF(NS_FAILED(rv))) {
3419     return false;
3420   }
3421 
3422   nsCString password;
3423   rv = uri->GetPassword(password);
3424   if (NS_WARN_IF(NS_FAILED(rv))) {
3425     return false;
3426   }
3427 
3428   if (!username.IsEmpty() || !password.IsEmpty()) {
3429     return true;
3430   }
3431 
3432   return false;
3433 }
3434 
TruncateResponseText()3435 void XMLHttpRequestMainThread::TruncateResponseText() {
3436   mResponseText.Truncate();
3437   XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
3438 }
3439 
NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,nsIHttpHeaderVisitor)3440 NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,
3441                   nsIHttpHeaderVisitor)
3442 
3443 NS_IMETHODIMP XMLHttpRequestMainThread::nsHeaderVisitor::VisitHeader(
3444     const nsACString& header, const nsACString& value) {
3445   if (mXHR.IsSafeHeader(header, mHttpChannel)) {
3446     if (!IsLowercaseResponseHeader()) {
3447       if (!mHeaderList.InsertElementSorted(HeaderEntry(header, value),
3448                                            fallible)) {
3449         return NS_ERROR_OUT_OF_MEMORY;
3450       }
3451       return NS_OK;
3452     }
3453 
3454     nsAutoCString lowerHeader(header);
3455     ToLowerCase(lowerHeader);
3456     if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
3457                                          fallible)) {
3458       return NS_ERROR_OUT_OF_MEMORY;
3459     }
3460   }
3461   return NS_OK;
3462 }
3463 
MaybeCreateBlobStorage()3464 void XMLHttpRequestMainThread::MaybeCreateBlobStorage() {
3465   MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
3466 
3467   if (mBlobStorage) {
3468     return;
3469   }
3470 
3471   MutableBlobStorage::MutableBlobStorageType storageType =
3472       BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
3473           ? MutableBlobStorage::eCouldBeInTemporaryFile
3474           : MutableBlobStorage::eOnlyInMemory;
3475 
3476   nsCOMPtr<nsIEventTarget> eventTarget;
3477   if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
3478     eventTarget = global->EventTargetFor(TaskCategory::Other);
3479   }
3480 
3481   mBlobStorage = new MutableBlobStorage(storageType, eventTarget);
3482 }
3483 
BlobStoreCompleted(MutableBlobStorage * aBlobStorage,Blob * aBlob,nsresult aRv)3484 void XMLHttpRequestMainThread::BlobStoreCompleted(
3485     MutableBlobStorage* aBlobStorage, Blob* aBlob, nsresult aRv) {
3486   // Ok, the state is changed...
3487   if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
3488     return;
3489   }
3490 
3491   MOZ_ASSERT(mState != XMLHttpRequestBinding::DONE);
3492 
3493   mResponseBlob = aBlob;
3494   mBlobStorage = nullptr;
3495 
3496   ChangeStateToDone();
3497 }
3498 
3499 NS_IMETHODIMP
GetName(nsACString & aName)3500 XMLHttpRequestMainThread::GetName(nsACString& aName) {
3501   aName.AssignLiteral("XMLHttpRequest");
3502   return NS_OK;
3503 }
3504 
3505 // nsXMLHttpRequestXPCOMifier implementation
3506 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
3507   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3508   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3509   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
3510   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
3511   NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
3512   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
3513   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
3514   NS_INTERFACE_MAP_ENTRY(nsINamed)
3515   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
3516 NS_INTERFACE_MAP_END
3517 
3518 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
3519 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
3520 
3521 // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
3522 // inheritance from nsISupports.
3523 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
3524 
3525 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
3526   if (tmp->mXHR) {
3527     tmp->mXHR->mXPCOMifier = nullptr;
3528   }
NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)3529   NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
3530 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3531 
3532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
3533   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
3534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3535 
3536 NS_IMETHODIMP
3537 nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
3538   // Return ourselves for the things we implement (except
3539   // nsIInterfaceRequestor) and the XHR for the rest.
3540   if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
3541     nsresult rv = QueryInterface(aIID, aResult);
3542     if (NS_SUCCEEDED(rv)) {
3543       return rv;
3544     }
3545   }
3546 
3547   return mXHR->GetInterface(aIID, aResult);
3548 }
3549 
ArrayBufferBuilder()3550 ArrayBufferBuilder::ArrayBufferBuilder()
3551     : mDataPtr(nullptr), mCapacity(0), mLength(0), mMapPtr(nullptr) {}
3552 
~ArrayBufferBuilder()3553 ArrayBufferBuilder::~ArrayBufferBuilder() { reset(); }
3554 
reset()3555 void ArrayBufferBuilder::reset() {
3556   if (mDataPtr) {
3557     JS_free(nullptr, mDataPtr);
3558   }
3559 
3560   if (mMapPtr) {
3561     JS_ReleaseMappedArrayBufferContents(mMapPtr, mLength);
3562     mMapPtr = nullptr;
3563   }
3564 
3565   mDataPtr = nullptr;
3566   mCapacity = mLength = 0;
3567 }
3568 
setCapacity(uint32_t aNewCap)3569 bool ArrayBufferBuilder::setCapacity(uint32_t aNewCap) {
3570   MOZ_ASSERT(!mMapPtr);
3571 
3572   // To ensure that realloc won't free mDataPtr, use a size of 1
3573   // instead of 0.
3574   uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
3575 
3576   if (!newdata) {
3577     return false;
3578   }
3579 
3580   if (aNewCap > mCapacity) {
3581     memset(newdata + mCapacity, 0, aNewCap - mCapacity);
3582   }
3583 
3584   mDataPtr = newdata;
3585   mCapacity = aNewCap;
3586   if (mLength > aNewCap) {
3587     mLength = aNewCap;
3588   }
3589 
3590   return true;
3591 }
3592 
append(const uint8_t * aNewData,uint32_t aDataLen,uint32_t aMaxGrowth)3593 bool ArrayBufferBuilder::append(const uint8_t* aNewData, uint32_t aDataLen,
3594                                 uint32_t aMaxGrowth) {
3595   MOZ_ASSERT(!mMapPtr);
3596 
3597   CheckedUint32 neededCapacity = mLength;
3598   neededCapacity += aDataLen;
3599   if (!neededCapacity.isValid()) {
3600     return false;
3601   }
3602   if (mLength + aDataLen > mCapacity) {
3603     CheckedUint32 newcap = mCapacity;
3604     // Double while under aMaxGrowth or if not specified.
3605     if (!aMaxGrowth || mCapacity < aMaxGrowth) {
3606       newcap *= 2;
3607     } else {
3608       newcap += aMaxGrowth;
3609     }
3610 
3611     if (!newcap.isValid()) {
3612       return false;
3613     }
3614 
3615     // But make sure there's always enough to satisfy our request.
3616     if (newcap.value() < neededCapacity.value()) {
3617       newcap = neededCapacity;
3618     }
3619 
3620     if (!setCapacity(newcap.value())) {
3621       return false;
3622     }
3623   }
3624 
3625   // Assert that the region isn't overlapping so we can memcpy.
3626   MOZ_ASSERT(
3627       !areOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
3628 
3629   memcpy(mDataPtr + mLength, aNewData, aDataLen);
3630   mLength += aDataLen;
3631 
3632   return true;
3633 }
3634 
getArrayBuffer(JSContext * aCx)3635 JSObject* ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) {
3636   if (mMapPtr) {
3637     JSObject* obj = JS_NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
3638     if (!obj) {
3639       JS_ReleaseMappedArrayBufferContents(mMapPtr, mLength);
3640     }
3641     mMapPtr = nullptr;
3642 
3643     // The memory-mapped contents will be released when the ArrayBuffer becomes
3644     // detached or is GC'd.
3645     return obj;
3646   }
3647 
3648   // we need to check for mLength == 0, because nothing may have been
3649   // added
3650   if (mCapacity > mLength || mLength == 0) {
3651     if (!setCapacity(mLength)) {
3652       return nullptr;
3653     }
3654   }
3655 
3656   JSObject* obj = JS_NewArrayBufferWithContents(aCx, mLength, mDataPtr);
3657   mLength = mCapacity = 0;
3658   if (!obj) {
3659     js_free(mDataPtr);
3660   }
3661   mDataPtr = nullptr;
3662   return obj;
3663 }
3664 
mapToFileInPackage(const nsCString & aFile,nsIFile * aJarFile)3665 nsresult ArrayBufferBuilder::mapToFileInPackage(const nsCString& aFile,
3666                                                 nsIFile* aJarFile) {
3667   nsresult rv;
3668 
3669   // Open Jar file to get related attributes of target file.
3670   RefPtr<nsZipArchive> zip = new nsZipArchive();
3671   rv = zip->OpenArchive(aJarFile);
3672   if (NS_FAILED(rv)) {
3673     return rv;
3674   }
3675   nsZipItem* zipItem = zip->GetItem(aFile.get());
3676   if (!zipItem) {
3677     return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
3678   }
3679 
3680   // If file was added to the package as stored(uncompressed), map to the
3681   // offset of file in zip package.
3682   if (!zipItem->Compression()) {
3683     uint32_t offset = zip->GetDataOffset(zipItem);
3684     uint32_t size = zipItem->RealSize();
3685     mozilla::AutoFDClose pr_fd;
3686     rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, &pr_fd.rwget());
3687     if (NS_FAILED(rv)) {
3688       return rv;
3689     }
3690     mMapPtr = JS_CreateMappedArrayBufferContents(
3691         PR_FileDesc2NativeHandle(pr_fd), offset, size);
3692     if (mMapPtr) {
3693       mLength = size;
3694       return NS_OK;
3695     }
3696   }
3697   return NS_ERROR_FAILURE;
3698 }
3699 
areOverlappingRegions(const uint8_t * aStart1,uint32_t aLength1,const uint8_t * aStart2,uint32_t aLength2)3700 /* static */ bool ArrayBufferBuilder::areOverlappingRegions(
3701     const uint8_t* aStart1, uint32_t aLength1, const uint8_t* aStart2,
3702     uint32_t aLength2) {
3703   const uint8_t* end1 = aStart1 + aLength1;
3704   const uint8_t* end2 = aStart2 + aLength2;
3705 
3706   const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
3707   const uint8_t* min_end = end1 < end2 ? end1 : end2;
3708 
3709   return max_start < min_end;
3710 }
3711 
Find(const nsACString & aName)3712 RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
3713   const nsCaseInsensitiveCStringComparator ignoreCase;
3714   for (RequestHeaders::RequestHeader& header : mHeaders) {
3715     if (header.mName.Equals(aName, ignoreCase)) {
3716       return &header;
3717     }
3718   }
3719   return nullptr;
3720 }
3721 
Has(const char * aName)3722 bool RequestHeaders::Has(const char* aName) {
3723   return Has(nsDependentCString(aName));
3724 }
3725 
Has(const nsACString & aName)3726 bool RequestHeaders::Has(const nsACString& aName) { return !!Find(aName); }
3727 
Get(const char * aName,nsACString & aValue)3728 void RequestHeaders::Get(const char* aName, nsACString& aValue) {
3729   Get(nsDependentCString(aName), aValue);
3730 }
3731 
Get(const nsACString & aName,nsACString & aValue)3732 void RequestHeaders::Get(const nsACString& aName, nsACString& aValue) {
3733   RequestHeader* header = Find(aName);
3734   if (header) {
3735     aValue = header->mValue;
3736   } else {
3737     aValue.SetIsVoid(true);
3738   }
3739 }
3740 
Set(const char * aName,const nsACString & aValue)3741 void RequestHeaders::Set(const char* aName, const nsACString& aValue) {
3742   Set(nsDependentCString(aName), aValue);
3743 }
3744 
Set(const nsACString & aName,const nsACString & aValue)3745 void RequestHeaders::Set(const nsACString& aName, const nsACString& aValue) {
3746   RequestHeader* header = Find(aName);
3747   if (header) {
3748     header->mValue.Assign(aValue);
3749   } else {
3750     RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
3751     mHeaders.AppendElement(newHeader);
3752   }
3753 }
3754 
MergeOrSet(const char * aName,const nsACString & aValue)3755 void RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue) {
3756   MergeOrSet(nsDependentCString(aName), aValue);
3757 }
3758 
MergeOrSet(const nsACString & aName,const nsACString & aValue)3759 void RequestHeaders::MergeOrSet(const nsACString& aName,
3760                                 const nsACString& aValue) {
3761   RequestHeader* header = Find(aName);
3762   if (header) {
3763     header->mValue.AppendLiteral(", ");
3764     header->mValue.Append(aValue);
3765   } else {
3766     RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
3767     mHeaders.AppendElement(newHeader);
3768   }
3769 }
3770 
Clear()3771 void RequestHeaders::Clear() { mHeaders.Clear(); }
3772 
ApplyToChannel(nsIHttpChannel * aHttpChannel) const3773 void RequestHeaders::ApplyToChannel(nsIHttpChannel* aHttpChannel) const {
3774   for (const RequestHeader& header : mHeaders) {
3775     if (header.mValue.IsEmpty()) {
3776       DebugOnly<nsresult> rv =
3777           aHttpChannel->SetEmptyRequestHeader(header.mName);
3778       MOZ_ASSERT(NS_SUCCEEDED(rv));
3779     } else {
3780       DebugOnly<nsresult> rv =
3781           aHttpChannel->SetRequestHeader(header.mName, header.mValue, false);
3782       MOZ_ASSERT(NS_SUCCEEDED(rv));
3783     }
3784   }
3785 }
3786 
GetCORSUnsafeHeaders(nsTArray<nsCString> & aArray) const3787 void RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const {
3788   static const char* kCrossOriginSafeHeaders[] = {
3789       "accept", "accept-language", "content-language", "content-type",
3790       "last-event-id"};
3791   const uint32_t kCrossOriginSafeHeadersLength =
3792       ArrayLength(kCrossOriginSafeHeaders);
3793   for (const RequestHeader& header : mHeaders) {
3794     bool safe = false;
3795     for (uint32_t i = 0; i < kCrossOriginSafeHeadersLength; ++i) {
3796       if (header.mName.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
3797         safe = true;
3798         break;
3799       }
3800     }
3801     if (!safe) {
3802       aArray.AppendElement(header.mName);
3803     }
3804   }
3805 }
3806 
CharsetIterator(nsACString & aSource)3807 RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource)
3808     : mValid(false),
3809       mCurPos(-1),
3810       mCurLen(-1),
3811       mCutoff(aSource.Length()),
3812       mSource(aSource) {}
3813 
Equals(const nsACString & aOther,const nsCStringComparator & aCmp) const3814 bool RequestHeaders::CharsetIterator::Equals(
3815     const nsACString& aOther, const nsCStringComparator& aCmp) const {
3816   if (mValid) {
3817     return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
3818   } else {
3819     return false;
3820   }
3821 }
3822 
Replace(const nsACString & aReplacement)3823 void RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement) {
3824   if (mValid) {
3825     mSource.Replace(mCurPos, mCurLen, aReplacement);
3826     mCurLen = aReplacement.Length();
3827   }
3828 }
3829 
Next()3830 bool RequestHeaders::CharsetIterator::Next() {
3831   int32_t start, end;
3832   nsAutoCString charset;
3833 
3834   // Look for another charset declaration in the string, limiting the
3835   // search to only the characters before the parts we've already searched
3836   // (before mCutoff), so that we don't find the same charset twice.
3837   NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff), charset,
3838                                    &mValid, &start, &end);
3839 
3840   if (!mValid) {
3841     return false;
3842   }
3843 
3844   // Everything after the = sign is the part of the charset we want.
3845   mCurPos = mSource.FindChar('=', start) + 1;
3846   mCurLen = end - mCurPos;
3847 
3848   // Special case: the extracted charset is quoted with single quotes.
3849   // For the purpose of preserving what was set we want to handle them
3850   // as delimiters (although they aren't really).
3851   if (charset.Length() >= 2 && charset.First() == '\'' &&
3852       charset.Last() == '\'') {
3853     ++mCurPos;
3854     mCurLen -= 2;
3855   }
3856 
3857   mCutoff = start;
3858 
3859   return true;
3860 }
3861 
3862 }  // namespace dom
3863 }  // namespace mozilla
3864