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