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