Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "XMLHttpRequestMainThread.h"
8
9
#include <algorithm>
10
#ifndef XP_WIN
11
# include <unistd.h>
12
#endif
13
#include "mozilla/ArrayUtils.h"
14
#include "mozilla/CheckedInt.h"
15
#include "mozilla/dom/BlobBinding.h"
16
#include "mozilla/dom/BlobURLProtocolHandler.h"
17
#include "mozilla/dom/DocGroup.h"
18
#include "mozilla/dom/DOMString.h"
19
#include "mozilla/dom/File.h"
20
#include "mozilla/dom/FileBinding.h"
21
#include "mozilla/dom/FileCreatorHelper.h"
22
#include "mozilla/dom/FetchUtil.h"
23
#include "mozilla/dom/FormData.h"
24
#include "mozilla/dom/MutableBlobStorage.h"
25
#include "mozilla/dom/XMLDocument.h"
26
#include "mozilla/dom/URLSearchParams.h"
27
#include "mozilla/dom/Promise.h"
28
#include "mozilla/dom/PromiseNativeHandler.h"
29
#include "mozilla/dom/WorkerError.h"
30
#include "mozilla/Encoding.h"
31
#include "mozilla/EventDispatcher.h"
32
#include "mozilla/EventListenerManager.h"
33
#include "mozilla/EventStateManager.h"
34
#include "mozilla/LoadInfo.h"
35
#include "mozilla/LoadContext.h"
36
#include "mozilla/MemoryReporting.h"
37
#include "mozilla/StaticPrefs_dom.h"
38
#include "mozilla/StaticPrefs_network.h"
39
#include "mozilla/StaticPrefs_privacy.h"
40
#include "mozilla/dom/ProgressEvent.h"
41
#include "nsIJARChannel.h"
42
#include "nsIJARURI.h"
43
#include "nsLayoutCID.h"
44
#include "nsReadableUtils.h"
45
46
#include "nsIURI.h"
47
#include "nsIURIMutator.h"
48
#include "nsILoadGroup.h"
49
#include "nsNetUtil.h"
50
#include "nsStringStream.h"
51
#include "nsIAuthPrompt.h"
52
#include "nsIAuthPrompt2.h"
53
#include "nsIClassOfService.h"
54
#include "nsIOutputStream.h"
55
#include "nsISupportsPrimitives.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 "nsIScriptSecurityManager.h"
65
#include "nsIVariant.h"
66
#include "nsVariant.h"
67
#include "nsIScriptError.h"
68
#include "nsIStreamConverterService.h"
69
#include "nsICachingChannel.h"
70
#include "nsContentUtils.h"
71
#include "nsCycleCollectionParticipant.h"
72
#include "nsError.h"
73
#include "nsIStorageStream.h"
74
#include "nsIPromptFactory.h"
75
#include "nsIWindowWatcher.h"
76
#include "nsIConsoleService.h"
77
#include "nsIContentSecurityPolicy.h"
78
#include "nsAsyncRedirectVerifyHelper.h"
79
#include "nsStringBuffer.h"
80
#include "nsIFileChannel.h"
81
#include "mozilla/Telemetry.h"
82
#include "js/ArrayBuffer.h" // JS::{Create,Release}MappedArrayBufferContents,New{,Mapped}ArrayBufferWithContents
83
#include "js/JSON.h" // JS_ParseJSON
84
#include "js/MemoryFunctions.h"
85
#include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
86
#include "js/Value.h" // JS::{,Undefined}Value
87
#include "jsapi.h" // JS_ClearPendingException
88
#include "GeckoProfiler.h"
89
#include "mozilla/dom/XMLHttpRequestBinding.h"
90
#include "mozilla/Attributes.h"
91
#include "MultipartBlobImpl.h"
92
#include "nsIPermissionManager.h"
93
#include "nsMimeTypes.h"
94
#include "nsIHttpChannelInternal.h"
95
#include "nsIClassOfService.h"
96
#include "nsCharSeparatedTokenizer.h"
97
#include "nsStreamListenerWrapper.h"
98
#include "nsITimedChannel.h"
99
#include "nsWrapperCacheInlines.h"
100
#include "nsZipArchive.h"
101
#include "mozilla/Preferences.h"
102
#include "private/pprio.h"
103
#include "XMLHttpRequestUpload.h"
104
105
// Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
106
// replaced by FileCreatorHelper#CreateFileW.
107
#ifdef CreateFile
108
# undef CreateFile
109
#endif
110
111
using namespace mozilla::net;
112
113
namespace mozilla {
114
namespace dom {
115
116
// Maximum size that we'll grow an ArrayBuffer instead of doubling,
117
// once doubling reaches this threshold
118
const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
119
// start at 32k to avoid lots of doubling right at the start
120
const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
121
// the maximum Content-Length that we'll preallocate. 1GB. Must fit
122
// in an int32_t!
123
const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
124
1 * 1024 * 1024 * 1024LL;
125
126
namespace {
127
const nsLiteralString ProgressEventTypeStrings[] = {
128
NS_LITERAL_STRING("loadstart"), NS_LITERAL_STRING("progress"),
129
NS_LITERAL_STRING("error"), NS_LITERAL_STRING("abort"),
130
NS_LITERAL_STRING("timeout"), NS_LITERAL_STRING("load"),
131
NS_LITERAL_STRING("loadend")};
132
static_assert(MOZ_ARRAY_LENGTH(ProgressEventTypeStrings) ==
133
size_t(XMLHttpRequestMainThread::ProgressEventType::ENUM_MAX),
134
"Mismatched lengths for ProgressEventTypeStrings and "
135
"ProgressEventType enums");
136
137
const nsString kLiteralString_readystatechange =
138
NS_LITERAL_STRING("readystatechange");
139
const nsString kLiteralString_xmlhttprequest =
140
NS_LITERAL_STRING("xmlhttprequest");
141
const nsString kLiteralString_DOMContentLoaded =
142
NS_LITERAL_STRING("DOMContentLoaded");
143
const nsCString kLiteralString_charset = NS_LITERAL_CSTRING("charset");
144
const nsCString kLiteralString_UTF_8 = NS_LITERAL_CSTRING("UTF-8");
145
} // namespace
146
147
// CIDs
148
#define NS_BADCERTHANDLER_CONTRACTID \
149
"@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
150
151
#define NS_PROGRESS_EVENT_INTERVAL 50
152
#define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
153
154
NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
155
156
class nsResumeTimeoutsEvent : public Runnable {
157
public:
158
explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
159
: Runnable("dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
160
161
NS_IMETHOD Run() override {
162
mWindow->Resume();
163
return NS_OK;
164
}
165
166
private:
167
nsCOMPtr<nsPIDOMWindowInner> mWindow;
168
};
169
170
// This helper function adds the given load flags to the request's existing
171
// load flags.
172
static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
173
nsLoadFlags flags;
174
request->GetLoadFlags(&flags);
175
flags |= newFlags;
176
request->SetLoadFlags(flags);
177
}
178
179
// We are in a sync event loop.
180
#define NOT_CALLABLE_IN_SYNC_SEND \
181
if (mFlagSyncLooping) { \
182
return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT; \
183
}
184
185
#define NOT_CALLABLE_IN_SYNC_SEND_RV \
186
if (mFlagSyncLooping) { \
187
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
188
return; \
189
}
190
191
/////////////////////////////////////////////
192
//
193
//
194
/////////////////////////////////////////////
195
196
bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
197
198
XMLHttpRequestMainThread::XMLHttpRequestMainThread()
199
: mResponseBodyDecodedPos(0),
200
mResponseType(XMLHttpRequestResponseType::_empty),
201
mRequestObserver(nullptr),
202
mState(XMLHttpRequest_Binding::UNSENT),
203
mFlagSynchronous(false),
204
mFlagAborted(false),
205
mFlagParseBody(false),
206
mFlagSyncLooping(false),
207
mFlagBackgroundRequest(false),
208
mFlagHadUploadListenersOnSend(false),
209
mFlagACwithCredentials(false),
210
mFlagTimedOut(false),
211
mFlagDeleted(false),
212
mFlagSend(false),
213
mUploadTransferred(0),
214
mUploadTotal(0),
215
mUploadComplete(true),
216
mProgressSinceLastProgressEvent(false),
217
mRequestSentTime(0),
218
mTimeoutMilliseconds(0),
219
mErrorLoad(ErrorType::eOK),
220
mErrorParsingXML(false),
221
mWaitingForOnStopRequest(false),
222
mProgressTimerIsActive(false),
223
mIsHtml(false),
224
mWarnAboutSyncHtml(false),
225
mLoadTotal(-1),
226
mLoadTransferred(0),
227
mIsSystem(false),
228
mIsAnon(false),
229
mFirstStartRequestSeen(false),
230
mInLoadProgressEvent(false),
231
mResultJSON(JS::UndefinedValue()),
232
mResultArrayBuffer(nullptr),
233
mIsMappedArrayBuffer(false),
234
mXPCOMifier(nullptr),
235
mEventDispatchingSuspended(false),
236
mEofDecoded(false),
237
mDelayedDoneNotifier(nullptr) {
238
mozilla::HoldJSObjects(this);
239
}
240
241
XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
242
MOZ_ASSERT(
243
!mDelayedDoneNotifier,
244
"How can we have mDelayedDoneNotifier, which owns us, in destructor?");
245
246
mFlagDeleted = true;
247
248
if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
249
mState == XMLHttpRequest_Binding::LOADING) {
250
Abort();
251
}
252
253
if (mParseEndListener) {
254
mParseEndListener->SetIsStale();
255
mParseEndListener = nullptr;
256
}
257
258
MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
259
mFlagSyncLooping = false;
260
261
mResultJSON.setUndefined();
262
mResultArrayBuffer = nullptr;
263
mozilla::DropJSObjects(this);
264
}
265
266
void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
267
if (!aAnon && !aSystem) {
268
return;
269
}
270
271
// Check for permissions.
272
// Chrome is always allowed access, so do the permission check only
273
// for non-chrome pages.
274
if (!IsSystemXHR() && aSystem) {
275
nsIGlobalObject* global = GetOwnerGlobal();
276
if (NS_WARN_IF(!global)) {
277
SetParameters(aAnon, false);
278
return;
279
}
280
281
nsIPrincipal* principal = global->PrincipalOrNull();
282
if (NS_WARN_IF(!principal)) {
283
SetParameters(aAnon, false);
284
return;
285
}
286
287
nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
288
if (NS_WARN_IF(!permMgr)) {
289
SetParameters(aAnon, false);
290
return;
291
}
292
293
uint32_t permission;
294
nsresult rv = permMgr->TestPermissionFromPrincipal(
295
principal, NS_LITERAL_CSTRING("systemXHR"), &permission);
296
if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
297
SetParameters(aAnon, false);
298
return;
299
}
300
}
301
302
SetParameters(aAnon, aSystem);
303
}
304
305
void XMLHttpRequestMainThread::SetClientInfoAndController(
306
const ClientInfo& aClientInfo,
307
const Maybe<ServiceWorkerDescriptor>& aController) {
308
mClientInfo.emplace(aClientInfo);
309
mController = aController;
310
}
311
312
void XMLHttpRequestMainThread::ResetResponse() {
313
mResponseXML = nullptr;
314
mResponseBody.Truncate();
315
TruncateResponseText();
316
mResponseBlob = nullptr;
317
mBlobStorage = nullptr;
318
mResultArrayBuffer = nullptr;
319
mArrayBufferBuilder.reset();
320
mResultJSON.setUndefined();
321
mLoadTransferred = 0;
322
mResponseBodyDecodedPos = 0;
323
mEofDecoded = false;
324
}
325
326
void XMLHttpRequestMainThread::SetRequestObserver(
327
nsIRequestObserver* aObserver) {
328
mRequestObserver = aObserver;
329
}
330
331
NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
332
333
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
334
XMLHttpRequestEventTarget)
335
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
336
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
337
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
338
339
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
340
341
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
342
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
343
344
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
345
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
346
347
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
348
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
349
350
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
351
XMLHttpRequestEventTarget)
352
tmp->mResultArrayBuffer = nullptr;
353
tmp->mArrayBufferBuilder.reset();
354
tmp->mResultJSON.setUndefined();
355
NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
356
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
357
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
358
359
NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
360
361
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
362
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
363
364
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
365
NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
366
367
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
368
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
369
370
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
371
XMLHttpRequestEventTarget)
372
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
373
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
374
NS_IMPL_CYCLE_COLLECTION_TRACE_END
375
376
bool XMLHttpRequestMainThread::IsCertainlyAliveForCC() const {
377
return mWaitingForOnStopRequest;
378
}
379
380
// QueryInterface implementation for XMLHttpRequestMainThread
381
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
382
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
383
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
384
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
385
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
386
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
387
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
388
NS_INTERFACE_MAP_ENTRY(nsINamed)
389
NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
390
NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
391
392
NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
393
NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
394
395
void XMLHttpRequestMainThread::DisconnectFromOwner() {
396
XMLHttpRequestEventTarget::DisconnectFromOwner();
397
Abort();
398
}
399
400
size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
401
MallocSizeOf aMallocSizeOf) const {
402
size_t n = aMallocSizeOf(this);
403
n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
404
405
// Why is this safe? Because no-one else will report this string. The
406
// other possible sharers of this string are as follows.
407
//
408
// - The JS engine could hold copies if the JS code holds references, e.g.
409
// |var text = XHR.responseText|. However, those references will be via JS
410
// external strings, for which the JS memory reporter does *not* report the
411
// chars.
412
//
413
// - Binary extensions, but they're *extremely* unlikely to do any memory
414
// reporting.
415
//
416
n += mResponseText.SizeOfThis(aMallocSizeOf);
417
418
return n;
419
420
// Measurement of the following members may be added later if DMD finds it is
421
// worthwhile:
422
// - lots
423
}
424
425
static void LogMessage(
426
const char* aWarning, nsPIDOMWindowInner* aWindow,
427
const nsTArray<nsString>& aParams = nsTArray<nsString>()) {
428
nsCOMPtr<Document> doc;
429
if (aWindow) {
430
doc = aWindow->GetExtantDoc();
431
}
432
nsContentUtils::ReportToConsole(
433
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), doc,
434
nsContentUtils::eDOM_PROPERTIES, aWarning, aParams);
435
}
436
437
Document* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
438
if (mResponseType != XMLHttpRequestResponseType::_empty &&
439
mResponseType != XMLHttpRequestResponseType::Document) {
440
aRv.Throw(
441
NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSEXML);
442
return nullptr;
443
}
444
if (mWarnAboutSyncHtml) {
445
mWarnAboutSyncHtml = false;
446
LogMessage("HTMLSyncXHRWarning", GetOwner());
447
}
448
if (mState != XMLHttpRequest_Binding::DONE) {
449
return nullptr;
450
}
451
return mResponseXML;
452
}
453
454
/*
455
* This piece copied from XMLDocument, we try to get the charset
456
* from HTTP headers.
457
*/
458
nsresult XMLHttpRequestMainThread::DetectCharset() {
459
mDecoder = nullptr;
460
461
if (mResponseType != XMLHttpRequestResponseType::_empty &&
462
mResponseType != XMLHttpRequestResponseType::Text &&
463
mResponseType != XMLHttpRequestResponseType::Json) {
464
return NS_OK;
465
}
466
467
nsAutoCString charsetVal;
468
const Encoding* encoding;
469
bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
470
(encoding = Encoding::ForLabel(charsetVal));
471
if (!ok) {
472
// MS documentation states UTF-8 is default for responseText
473
encoding = UTF_8_ENCODING;
474
}
475
476
if (mResponseType == XMLHttpRequestResponseType::Json &&
477
encoding != UTF_8_ENCODING) {
478
// The XHR spec says only UTF-8 is supported for responseType == "json"
479
LogMessage("JSONCharsetWarning", GetOwner());
480
encoding = UTF_8_ENCODING;
481
}
482
483
// Only sniff the BOM for non-JSON responseTypes
484
if (mResponseType == XMLHttpRequestResponseType::Json) {
485
mDecoder = encoding->NewDecoderWithBOMRemoval();
486
} else {
487
mDecoder = encoding->NewDecoder();
488
}
489
490
return NS_OK;
491
}
492
493
nsresult XMLHttpRequestMainThread::AppendToResponseText(
494
Span<const uint8_t> aBuffer, bool aLast) {
495
// Call this with an empty buffer to send the decoder the signal
496
// that we have hit the end of the stream.
497
498
NS_ENSURE_STATE(mDecoder);
499
500
CheckedInt<size_t> destBufferLen =
501
mDecoder->MaxUTF16BufferLength(aBuffer.Length());
502
503
{ // scope for holding the mutex that protects mResponseText
504
XMLHttpRequestStringWriterHelper helper(mResponseText);
505
506
uint32_t len = helper.Length();
507
508
destBufferLen += len;
509
if (!destBufferLen.isValid() || destBufferLen.value() > UINT32_MAX) {
510
return NS_ERROR_OUT_OF_MEMORY;
511
}
512
513
nsresult rv;
514
BulkWriteHandle<char16_t> handle =
515
helper.BulkWrite(destBufferLen.value(), rv);
516
if (NS_FAILED(rv)) {
517
return rv;
518
}
519
520
uint32_t result;
521
size_t read;
522
size_t written;
523
bool hadErrors;
524
Tie(result, read, written, hadErrors) =
525
mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
526
MOZ_ASSERT(result == kInputEmpty);
527
MOZ_ASSERT(read == aBuffer.Length());
528
len += written;
529
MOZ_ASSERT(len <= destBufferLen.value());
530
Unused << hadErrors;
531
handle.Finish(len, false);
532
} // release mutex
533
534
if (aLast) {
535
// Drop the finished decoder to avoid calling into a decoder
536
// that has finished.
537
mDecoder = nullptr;
538
mEofDecoded = true;
539
}
540
return NS_OK;
541
}
542
543
void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
544
ErrorResult& aRv) {
545
XMLHttpRequestStringSnapshot snapshot;
546
GetResponseText(snapshot, aRv);
547
if (aRv.Failed()) {
548
return;
549
}
550
551
if (!snapshot.GetAsString(aResponseText)) {
552
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
553
return;
554
}
555
}
556
557
void XMLHttpRequestMainThread::GetResponseText(
558
XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
559
aSnapshot.Reset();
560
561
if (mResponseType != XMLHttpRequestResponseType::_empty &&
562
mResponseType != XMLHttpRequestResponseType::Text) {
563
aRv.Throw(
564
NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT);
565
return;
566
}
567
568
if (mState != XMLHttpRequest_Binding::LOADING &&
569
mState != XMLHttpRequest_Binding::DONE) {
570
return;
571
}
572
573
// Main Fetch step 18 requires to ignore body for head/connect methods.
574
if (mRequestMethod.EqualsLiteral("HEAD") ||
575
mRequestMethod.EqualsLiteral("CONNECT")) {
576
return;
577
}
578
579
// We only decode text lazily if we're also parsing to a doc.
580
// Also, if we've decoded all current data already, then no need to decode
581
// more.
582
if ((!mResponseXML && !mErrorParsingXML) ||
583
(mResponseBodyDecodedPos == mResponseBody.Length() &&
584
(mState != XMLHttpRequest_Binding::DONE || mEofDecoded))) {
585
mResponseText.CreateSnapshot(aSnapshot);
586
return;
587
}
588
589
MatchCharsetAndDecoderToResponseDocument();
590
591
MOZ_ASSERT(mResponseBodyDecodedPos < mResponseBody.Length() ||
592
mState == XMLHttpRequest_Binding::DONE,
593
"Unexpected mResponseBodyDecodedPos");
594
Span<const uint8_t> span = mResponseBody;
595
aRv = AppendToResponseText(span.From(mResponseBodyDecodedPos),
596
mState == XMLHttpRequest_Binding::DONE);
597
if (aRv.Failed()) {
598
return;
599
}
600
601
mResponseBodyDecodedPos = mResponseBody.Length();
602
603
if (mEofDecoded) {
604
// Free memory buffer which we no longer need
605
mResponseBody.Truncate();
606
mResponseBodyDecodedPos = 0;
607
}
608
609
mResponseText.CreateSnapshot(aSnapshot);
610
}
611
612
nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
613
if (!aCx) {
614
return NS_ERROR_FAILURE;
615
}
616
617
nsAutoString string;
618
if (!mResponseText.GetAsString(string)) {
619
return NS_ERROR_OUT_OF_MEMORY;
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
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
656
void XMLHttpRequestMainThread::GetResponse(
657
JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
658
switch (mResponseType) {
659
case XMLHttpRequestResponseType::_empty:
660
case XMLHttpRequestResponseType::Text: {
661
DOMString str;
662
GetResponseText(str, aRv);
663
if (aRv.Failed()) {
664
return;
665
}
666
if (!xpc::StringToJsval(aCx, str, aResponse)) {
667
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
668
}
669
return;
670
}
671
672
case XMLHttpRequestResponseType::Arraybuffer: {
673
if (mState != XMLHttpRequest_Binding::DONE) {
674
aResponse.setNull();
675
return;
676
}
677
678
if (!mResultArrayBuffer) {
679
mResultArrayBuffer = mArrayBufferBuilder.getArrayBuffer(aCx);
680
if (!mResultArrayBuffer) {
681
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
682
return;
683
}
684
}
685
aResponse.setObject(*mResultArrayBuffer);
686
return;
687
}
688
case XMLHttpRequestResponseType::Blob: {
689
if (mState != XMLHttpRequest_Binding::DONE) {
690
aResponse.setNull();
691
return;
692
}
693
694
if (!mResponseBlob) {
695
aResponse.setNull();
696
return;
697
}
698
699
GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse);
700
return;
701
}
702
case XMLHttpRequestResponseType::Document: {
703
if (!mResponseXML || mState != XMLHttpRequest_Binding::DONE) {
704
aResponse.setNull();
705
return;
706
}
707
708
aRv =
709
nsContentUtils::WrapNative(aCx, ToSupports(mResponseXML), aResponse);
710
return;
711
}
712
case XMLHttpRequestResponseType::Json: {
713
if (mState != XMLHttpRequest_Binding::DONE) {
714
aResponse.setNull();
715
return;
716
}
717
718
if (mResultJSON.isUndefined()) {
719
aRv = CreateResponseParsedJSON(aCx);
720
TruncateResponseText();
721
if (aRv.Failed()) {
722
// Per spec, errors aren't propagated. null is returned instead.
723
aRv = NS_OK;
724
// It would be nice to log the error to the console. That's hard to
725
// do without calling window.onerror as a side effect, though.
726
JS_ClearPendingException(aCx);
727
mResultJSON.setNull();
728
}
729
}
730
aResponse.set(mResultJSON);
731
return;
732
}
733
default:
734
NS_ERROR("Should not happen");
735
}
736
737
aResponse.setNull();
738
}
739
740
bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const {
741
if (!mChannel) {
742
return false;
743
}
744
745
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
746
return loadInfo->GetTainting() == LoadTainting::CORS;
747
}
748
749
bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
750
if (IsCrossSiteCORSRequest()) {
751
nsresult rv;
752
mChannel->GetStatus(&rv);
753
if (NS_FAILED(rv)) {
754
return true;
755
}
756
}
757
return false;
758
}
759
760
void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
761
aUrl.Truncate();
762
763
if ((mState == XMLHttpRequest_Binding::UNSENT ||
764
mState == XMLHttpRequest_Binding::OPENED) ||
765
!mChannel) {
766
return;
767
}
768
769
// Make sure we don't leak responseURL information from denied cross-site
770
// requests.
771
if (IsDeniedCrossSiteCORSRequest()) {
772
return;
773
}
774
775
nsCOMPtr<nsIURI> responseUrl;
776
if (NS_FAILED(NS_GetFinalChannelURI(mChannel, getter_AddRefs(responseUrl)))) {
777
return;
778
}
779
780
nsAutoCString temp;
781
responseUrl->GetSpecIgnoringRef(temp);
782
CopyUTF8toUTF16(temp, aUrl);
783
}
784
785
uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
786
// Make sure we don't leak status information from denied cross-site
787
// requests.
788
if (IsDeniedCrossSiteCORSRequest()) {
789
return 0;
790
}
791
792
if (mState == XMLHttpRequest_Binding::UNSENT ||
793
mState == XMLHttpRequest_Binding::OPENED) {
794
return 0;
795
}
796
797
if (mErrorLoad != ErrorType::eOK) {
798
// Let's simulate the http protocol for jar/app requests:
799
nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
800
if (jarChannel) {
801
nsresult status;
802
mChannel->GetStatus(&status);
803
804
if (status == NS_ERROR_FILE_NOT_FOUND) {
805
return 404; // Not Found
806
} else {
807
return 500; // Internal Error
808
}
809
}
810
811
return 0;
812
}
813
814
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
815
if (!httpChannel) {
816
// Pretend like we got a 200 response, since our load was successful
817
return 200;
818
}
819
820
uint32_t status;
821
nsresult rv = httpChannel->GetResponseStatus(&status);
822
if (NS_FAILED(rv)) {
823
status = 0;
824
}
825
826
return status;
827
}
828
829
void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
830
ErrorResult& aRv) {
831
// Return an empty status text on all error loads.
832
aStatusText.Truncate();
833
834
// Make sure we don't leak status information from denied cross-site
835
// requests.
836
if (IsDeniedCrossSiteCORSRequest()) {
837
return;
838
}
839
840
// Check the current XHR state to see if it is valid to obtain the statusText
841
// value. This check is to prevent the status text for redirects from being
842
// available before all the redirects have been followed and HTTP headers have
843
// been received.
844
if (mState == XMLHttpRequest_Binding::UNSENT ||
845
mState == XMLHttpRequest_Binding::OPENED) {
846
return;
847
}
848
849
if (mErrorLoad != ErrorType::eOK) {
850
return;
851
}
852
853
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
854
if (httpChannel) {
855
Unused << httpChannel->GetResponseStatusText(aStatusText);
856
} else {
857
aStatusText.AssignLiteral("OK");
858
}
859
}
860
861
void XMLHttpRequestMainThread::TerminateOngoingFetch() {
862
if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
863
mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
864
mState == XMLHttpRequest_Binding::LOADING) {
865
CloseRequest();
866
}
867
}
868
869
void XMLHttpRequestMainThread::CloseRequest() {
870
mWaitingForOnStopRequest = false;
871
mErrorLoad = ErrorType::eTerminated;
872
if (mChannel) {
873
mChannel->Cancel(NS_BINDING_ABORTED);
874
}
875
if (mTimeoutTimer) {
876
mTimeoutTimer->Cancel();
877
}
878
}
879
880
void XMLHttpRequestMainThread::CloseRequestWithError(
881
const ProgressEventType aType) {
882
CloseRequest();
883
884
ResetResponse();
885
886
// If we're in the destructor, don't risk dispatching an event.
887
if (mFlagDeleted) {
888
mFlagSyncLooping = false;
889
return;
890
}
891
892
if (mState != XMLHttpRequest_Binding::UNSENT &&
893
!(mState == XMLHttpRequest_Binding::OPENED && !mFlagSend) &&
894
mState != XMLHttpRequest_Binding::DONE) {
895
ChangeState(XMLHttpRequest_Binding::DONE, true);
896
897
if (!mFlagSyncLooping) {
898
if (mUpload && !mUploadComplete) {
899
mUploadComplete = true;
900
DispatchProgressEvent(mUpload, aType, 0, -1);
901
}
902
DispatchProgressEvent(this, aType, 0, -1);
903
}
904
}
905
906
// The ChangeState call above calls onreadystatechange handlers which
907
// if they load a new url will cause XMLHttpRequestMainThread::Open to clear
908
// the abort state bit. If this occurs we're not uninitialized (bug 361773).
909
if (mFlagAborted) {
910
ChangeState(XMLHttpRequest_Binding::UNSENT, false); // IE seems to do it
911
}
912
913
mFlagSyncLooping = false;
914
}
915
916
void XMLHttpRequestMainThread::RequestErrorSteps(
917
const ProgressEventType aEventType, const nsresult aOptionalException,
918
ErrorResult& aRv) {
919
// Step 1
920
mState = XMLHttpRequest_Binding::DONE;
921
922
StopProgressEventTimer();
923
924
// Step 2
925
mFlagSend = false;
926
927
// Step 3
928
ResetResponse();
929
930
// If we're in the destructor, don't risk dispatching an event.
931
if (mFlagDeleted) {
932
mFlagSyncLooping = false;
933
return;
934
}
935
936
// Step 4
937
if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
938
aRv.Throw(aOptionalException);
939
return;
940
}
941
942
// Step 5
943
FireReadystatechangeEvent();
944
945
// Step 6
946
if (mUpload && !mUploadComplete) {
947
// Step 6-1
948
mUploadComplete = true;
949
950
// Step 6-2
951
if (mFlagHadUploadListenersOnSend) {
952
// Steps 6-3, 6-4 (loadend is fired for us)
953
DispatchProgressEvent(mUpload, aEventType, 0, -1);
954
}
955
}
956
957
// Steps 7 and 8 (loadend is fired for us)
958
DispatchProgressEvent(this, aEventType, 0, -1);
959
}
960
961
void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
962
NOT_CALLABLE_IN_SYNC_SEND_RV
963
AbortInternal(aRv);
964
}
965
966
void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
967
mFlagAborted = true;
968
DisconnectDoneNotifier();
969
970
// Step 1
971
TerminateOngoingFetch();
972
973
// Step 2
974
if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
975
mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
976
mState == XMLHttpRequest_Binding::LOADING) {
977
RequestErrorSteps(ProgressEventType::abort, NS_OK, aRv);
978
}
979
980
// Step 3
981
if (mState == XMLHttpRequest_Binding::DONE) {
982
ChangeState(XMLHttpRequest_Binding::UNSENT,
983
false); // no ReadystateChange event
984
}
985
986
mFlagSyncLooping = false;
987
}
988
989
/*Method that checks if it is safe to expose a header value to the client.
990
It is used to check what headers are exposed for CORS requests.*/
991
bool XMLHttpRequestMainThread::IsSafeHeader(
992
const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel) const {
993
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
994
if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
995
NS_WARNING("blocked access to response header");
996
return false;
997
}
998
// if this is not a CORS call all headers are safe
999
if (!IsCrossSiteCORSRequest()) {
1000
return true;
1001
}
1002
// Check for dangerous headers
1003
// Make sure we don't leak header information from denied cross-site
1004
// requests.
1005
if (mChannel) {
1006
nsresult status;
1007
mChannel->GetStatus(&status);
1008
if (NS_FAILED(status)) {
1009
return false;
1010
}
1011
}
1012
const char* kCrossOriginSafeHeaders[] = {"cache-control", "content-language",
1013
"content-type", "expires",
1014
"last-modified", "pragma"};
1015
for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
1016
if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
1017
return true;
1018
}
1019
}
1020
nsAutoCString headerVal;
1021
// The "Access-Control-Expose-Headers" header contains a comma separated
1022
// list of method names.
1023
Unused << aHttpChannel->GetResponseHeader(
1024
NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), headerVal);
1025
nsCCharSeparatedTokenizer exposeTokens(headerVal, ',');
1026
bool isSafe = false;
1027
while (exposeTokens.hasMoreTokens()) {
1028
const nsDependentCSubstring& token = exposeTokens.nextToken();
1029
if (token.IsEmpty()) {
1030
continue;
1031
}
1032
if (!NS_IsValidHTTPToken(token)) {
1033
return false;
1034
}
1035
1036
if (token.EqualsLiteral("*") && !mFlagACwithCredentials) {
1037
isSafe = true;
1038
} else if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator())) {
1039
isSafe = true;
1040
}
1041
}
1042
1043
return isSafe;
1044
}
1045
1046
void XMLHttpRequestMainThread::GetAllResponseHeaders(
1047
nsACString& aResponseHeaders, ErrorResult& aRv) {
1048
NOT_CALLABLE_IN_SYNC_SEND_RV
1049
1050
aResponseHeaders.Truncate();
1051
1052
// If the state is UNSENT or OPENED,
1053
// return the empty string and terminate these steps.
1054
if (mState == XMLHttpRequest_Binding::UNSENT ||
1055
mState == XMLHttpRequest_Binding::OPENED) {
1056
return;
1057
}
1058
1059
if (mErrorLoad != ErrorType::eOK) {
1060
return;
1061
}
1062
1063
if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
1064
RefPtr<nsHeaderVisitor> visitor =
1065
new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
1066
if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
1067
aResponseHeaders = visitor->Headers();
1068
}
1069
return;
1070
}
1071
1072
if (!mChannel) {
1073
return;
1074
}
1075
1076
// Even non-http channels supply content type.
1077
nsAutoCString value;
1078
if (NS_SUCCEEDED(mChannel->GetContentType(value))) {
1079
aResponseHeaders.AppendLiteral("Content-Type: ");
1080
aResponseHeaders.Append(value);
1081
if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
1082
aResponseHeaders.AppendLiteral(";charset=");
1083
aResponseHeaders.Append(value);
1084
}
1085
aResponseHeaders.AppendLiteral("\r\n");
1086
}
1087
1088
// Don't provide Content-Length for data URIs
1089
nsCOMPtr<nsIURI> uri;
1090
if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
1091
!uri->SchemeIs("data")) {
1092
int64_t length;
1093
if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1094
aResponseHeaders.AppendLiteral("Content-Length: ");
1095
aResponseHeaders.AppendInt(length);
1096
aResponseHeaders.AppendLiteral("\r\n");
1097
}
1098
}
1099
}
1100
1101
void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
1102
nsACString& _retval,
1103
ErrorResult& aRv) {
1104
NOT_CALLABLE_IN_SYNC_SEND_RV
1105
1106
_retval.SetIsVoid(true);
1107
1108
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1109
1110
if (!httpChannel) {
1111
// If the state is UNSENT or OPENED,
1112
// return null and terminate these steps.
1113
if (mState == XMLHttpRequest_Binding::UNSENT ||
1114
mState == XMLHttpRequest_Binding::OPENED) {
1115
return;
1116
}
1117
1118
// Even non-http channels supply content type and content length.
1119
// Remember we don't leak header information from denied cross-site
1120
// requests. However, we handle file: and blob: URLs for blob response
1121
// types by canceling them with a specific error, so we have to allow
1122
// them to pass through this check.
1123
nsresult status;
1124
if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
1125
(NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
1126
return;
1127
}
1128
1129
// Content Type:
1130
if (header.LowerCaseEqualsASCII("content-type")) {
1131
if (NS_FAILED(mChannel->GetContentType(_retval))) {
1132
// Means no content type
1133
_retval.SetIsVoid(true);
1134
return;
1135
}
1136
1137
nsCString value;
1138
if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) &&
1139
!value.IsEmpty()) {
1140
_retval.AppendLiteral(";charset=");
1141
_retval.Append(value);
1142
}
1143
}
1144
1145
// Content Length:
1146
else if (header.LowerCaseEqualsASCII("content-length")) {
1147
int64_t length;
1148
if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1149
_retval.AppendInt(length);
1150
}
1151
}
1152
1153
return;
1154
}
1155
1156
// Check for dangerous headers
1157
if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
1158
return;
1159
}
1160
1161
aRv = httpChannel->GetResponseHeader(header, _retval);
1162
if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
1163
// Means no header
1164
_retval.SetIsVoid(true);
1165
aRv.SuppressException();
1166
}
1167
}
1168
1169
already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup() const {
1170
if (mFlagBackgroundRequest) {
1171
return nullptr;
1172
}
1173
1174
if (mLoadGroup) {
1175
nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
1176
return ref.forget();
1177
}
1178
1179
Document* doc = GetDocumentIfCurrent();
1180
if (doc) {
1181
return doc->GetDocumentLoadGroup();
1182
}
1183
1184
return nullptr;
1185
}
1186
1187
nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
1188
MOZ_ASSERT(mState != XMLHttpRequest_Binding::UNSENT);
1189
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1190
event->InitEvent(kLiteralString_readystatechange, false, false);
1191
// We assume anyone who managed to call CreateReadystatechangeEvent is trusted
1192
event->SetTrusted(true);
1193
DispatchOrStoreEvent(this, event);
1194
return NS_OK;
1195
}
1196
1197
void XMLHttpRequestMainThread::DispatchProgressEvent(
1198
DOMEventTargetHelper* aTarget, const ProgressEventType aType,
1199
int64_t aLoaded, int64_t aTotal) {
1200
NS_ASSERTION(aTarget, "null target");
1201
1202
if (NS_FAILED(CheckCurrentGlobalCorrectness()) ||
1203
(!AllowUploadProgress() && aTarget == mUpload)) {
1204
return;
1205
}
1206
1207
// If blocked by CORS, zero-out the stats on progress events
1208
// and never fire "progress" or "load" events at all.
1209
if (IsDeniedCrossSiteCORSRequest()) {
1210
if (aType == ProgressEventType::progress ||
1211
aType == ProgressEventType::load) {
1212
return;
1213
}
1214
aLoaded = 0;
1215
aTotal = -1;
1216
}
1217
1218
if (aType == ProgressEventType::progress) {
1219
mInLoadProgressEvent = true;
1220
}
1221
1222
ProgressEventInit init;
1223
init.mBubbles = false;
1224
init.mCancelable = false;
1225
init.mLengthComputable = aTotal != -1; // XHR spec step 6.1
1226
init.mLoaded = aLoaded;
1227
init.mTotal = (aTotal == -1) ? 0 : aTotal;
1228
1229
const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
1230
RefPtr<ProgressEvent> event =
1231
ProgressEvent::Constructor(aTarget, typeString, init);
1232
event->SetTrusted(true);
1233
1234
DispatchOrStoreEvent(aTarget, event);
1235
1236
if (aType == ProgressEventType::progress) {
1237
mInLoadProgressEvent = false;
1238
}
1239
1240
// If we're sending a load, error, timeout or abort event, then
1241
// also dispatch the subsequent loadend event.
1242
if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
1243
aType == ProgressEventType::timeout ||
1244
aType == ProgressEventType::abort) {
1245
DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
1246
}
1247
}
1248
1249
void XMLHttpRequestMainThread::DispatchOrStoreEvent(
1250
DOMEventTargetHelper* aTarget, Event* aEvent) {
1251
MOZ_ASSERT(aTarget);
1252
MOZ_ASSERT(aEvent);
1253
1254
if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1255
return;
1256
}
1257
1258
if (mEventDispatchingSuspended) {
1259
PendingEvent* event = mPendingEvents.AppendElement();
1260
event->mTarget = aTarget;
1261
event->mEvent = aEvent;
1262
return;
1263
}
1264
1265
aTarget->DispatchEvent(*aEvent);
1266
}
1267
1268
void XMLHttpRequestMainThread::SuspendEventDispatching() {
1269
MOZ_ASSERT(!mEventDispatchingSuspended);
1270
mEventDispatchingSuspended = true;
1271
}
1272
1273
void XMLHttpRequestMainThread::ResumeEventDispatching() {
1274
MOZ_ASSERT(mEventDispatchingSuspended);
1275
mEventDispatchingSuspended = false;
1276
1277
nsTArray<PendingEvent> pendingEvents;
1278
pendingEvents.SwapElements(mPendingEvents);
1279
1280
if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1281
return;
1282
}
1283
1284
for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1285
pendingEvents[i].mTarget->DispatchEvent(*pendingEvents[i].mEvent);
1286
}
1287
}
1288
1289
already_AddRefed<nsIHttpChannel>
1290
XMLHttpRequestMainThread::GetCurrentHttpChannel() {
1291
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1292
return httpChannel.forget();
1293
}
1294
1295
already_AddRefed<nsIJARChannel>
1296
XMLHttpRequestMainThread::GetCurrentJARChannel() {
1297
nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
1298
return appChannel.forget();
1299
}
1300
1301
bool XMLHttpRequestMainThread::IsSystemXHR() const {
1302
return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
1303
}
1304
1305
bool XMLHttpRequestMainThread::InUploadPhase() const {
1306
// We're in the upload phase while our state is OPENED.
1307
return mState == XMLHttpRequest_Binding::OPENED;
1308
}
1309
1310
// This case is hit when the async parameter is outright omitted, which
1311
// should set it to true (and the username and password to null).
1312
void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1313
const nsAString& aUrl, ErrorResult& aRv) {
1314
Open(aMethod, aUrl, true, VoidString(), VoidString(), aRv);
1315
}
1316
1317
// This case is hit when the async parameter is specified, even if the
1318
// JS value was "undefined" (which due to legacy reasons should be
1319
// treated as true, which is how it will already be passed in here).
1320
void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1321
const nsAString& aUrl, bool aAsync,
1322
const nsAString& aUsername,
1323
const nsAString& aPassword,
1324
ErrorResult& aRv) {
1325
nsresult rv =
1326
Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword);
1327
if (NS_FAILED(rv)) {
1328
aRv.Throw(rv);
1329
}
1330
}
1331
1332
nsresult XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1333
const nsACString& aUrl, bool aAsync,
1334
const nsAString& aUsername,
1335
const nsAString& aPassword) {
1336
NOT_CALLABLE_IN_SYNC_SEND
1337
1338
// Gecko-specific
1339
if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
1340
GetOwner()->GetExtantDoc()) {
1341
GetOwner()->GetExtantDoc()->WarnOnceAbout(Document::eSyncXMLHttpRequest);
1342
}
1343
1344
Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
1345
aAsync ? 0 : 1);
1346
1347
// Step 1
1348
nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
1349
if (!responsibleDocument) {
1350
// This could be because we're no longer current or because we're in some
1351
// non-window context...
1352
nsresult rv = CheckCurrentGlobalCorrectness();
1353
if (NS_WARN_IF(NS_FAILED(rv))) {
1354
return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
1355
}
1356
}
1357
NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
1358
1359
// Gecko-specific
1360
if (!aAsync && responsibleDocument && GetOwner()) {
1361
// We have no extant document during unload, so the above general
1362
// syncXHR warning will not display. But we do want to display a
1363
// recommendation to use sendBeacon instead of syncXHR during unload.
1364
nsCOMPtr<nsIDocShell> shell = responsibleDocument->GetDocShell();
1365
if (shell) {
1366
bool inUnload = false;
1367
shell->GetIsInUnload(&inUnload);
1368
if (inUnload) {
1369
LogMessage("UseSendBeaconDuringUnloadAndPagehideWarning", GetOwner());
1370
}
1371
}
1372
}
1373
1374
// Steps 2-4
1375
nsAutoCString method;
1376
nsresult rv = FetchUtil::GetValidRequestMethod(aMethod, method);
1377
if (NS_WARN_IF(NS_FAILED(rv))) {
1378
return rv;
1379
}
1380
1381
// Steps 5-6
1382
nsIURI* baseURI = nullptr;
1383
if (mBaseURI) {
1384
baseURI = mBaseURI;
1385
} else if (responsibleDocument) {
1386
baseURI = responsibleDocument->GetBaseURI();
1387
}
1388
1389
// Use the responsible document's encoding for the URL if we have one,
1390
// except for dedicated workers. Use UTF-8 otherwise.
1391
NotNull<const Encoding*> originCharset = UTF_8_ENCODING;
1392
if (responsibleDocument &&
1393
responsibleDocument->NodePrincipal() == mPrincipal) {
1394
originCharset = responsibleDocument->GetDocumentCharacterSet();
1395
}
1396
1397
nsCOMPtr<nsIURI> parsedURL;
1398
rv = NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
1399
if (NS_FAILED(rv)) {
1400
if (rv == NS_ERROR_MALFORMED_URI) {
1401
return NS_ERROR_DOM_MALFORMED_URI;
1402
}
1403
return rv;
1404
}
1405
if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1406
return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
1407
}
1408
1409
// Step 7
1410
// This is already handled by the other Open() method, which passes
1411
// username and password in as NullStrings.
1412
1413
// Step 8
1414
nsAutoCString host;
1415
parsedURL->GetHost(host);
1416
if (!host.IsEmpty() && (!aUsername.IsVoid() || !aPassword.IsVoid())) {
1417
auto mutator = NS_MutateURI(parsedURL);
1418
if (!aUsername.IsVoid()) {
1419
mutator.SetUsername(NS_ConvertUTF16toUTF8(aUsername));
1420
}
1421
if (!aPassword.IsVoid()) {
1422
mutator.SetPassword(NS_ConvertUTF16toUTF8(aPassword));
1423
}
1424
Unused << mutator.Finalize(parsedURL);
1425
}
1426
1427
// Step 9
1428
if (!aAsync && HasOrHasHadOwner() &&
1429
(mTimeoutMilliseconds ||
1430
mResponseType != XMLHttpRequestResponseType::_empty)) {
1431
if (mTimeoutMilliseconds) {
1432
LogMessage("TimeoutSyncXHRWarning", GetOwner());
1433
}
1434
if (mResponseType != XMLHttpRequestResponseType::_empty) {
1435
LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
1436
}
1437
return NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC;
1438
}
1439
1440
// Step 10
1441
TerminateOngoingFetch();
1442
1443
// Step 11
1444
// timeouts are handled without a flag
1445
DisconnectDoneNotifier();
1446
mFlagSend = false;
1447
mRequestMethod.Assign(method);
1448
mRequestURL = parsedURL;
1449
mFlagSynchronous = !aAsync;
1450
mAuthorRequestHeaders.Clear();
1451
ResetResponse();
1452
1453
// Gecko-specific
1454
mFlagHadUploadListenersOnSend = false;
1455
mFlagAborted = false;
1456
mFlagTimedOut = false;
1457
mDecoder = nullptr;
1458
1459
// Per spec we should only create the channel on send(), but we have internal
1460
// code that relies on the channel being created now, and that code is not
1461
// always IsSystemXHR(). However, we're not supposed to throw channel-creation
1462
// errors during open(), so we silently ignore those here.
1463
CreateChannel();
1464
1465
// Step 12
1466
if (mState != XMLHttpRequest_Binding::OPENED) {
1467
mState = XMLHttpRequest_Binding::OPENED;
1468
FireReadystatechangeEvent();
1469
}
1470
1471
return NS_OK;
1472
}
1473
1474
void XMLHttpRequestMainThread::SetOriginAttributes(
1475
const OriginAttributesDictionary& aAttrs) {
1476
MOZ_ASSERT((mState == XMLHttpRequest_Binding::OPENED) && !mFlagSend);
1477
1478
OriginAttributes attrs(aAttrs);
1479
1480
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
1481
loadInfo->SetOriginAttributes(attrs);
1482
}
1483
1484
/*
1485
* "Copy" from a stream.
1486
*/
1487
nsresult XMLHttpRequestMainThread::StreamReaderFunc(
1488
nsIInputStream* in, void* closure, const char* fromRawSegment,
1489
uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
1490
XMLHttpRequestMainThread* xmlHttpRequest =
1491
static_cast<XMLHttpRequestMainThread*>(closure);
1492
if (!xmlHttpRequest || !writeCount) {
1493
NS_WARNING(
1494
"XMLHttpRequest cannot read from stream: no closure or writeCount");
1495
return NS_ERROR_FAILURE;
1496
}
1497
1498
nsresult rv = NS_OK;
1499
1500
if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
1501
xmlHttpRequest->MaybeCreateBlobStorage();
1502
rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
1503
} else if (xmlHttpRequest->mResponseType ==
1504
XMLHttpRequestResponseType::Arraybuffer &&
1505
!xmlHttpRequest->mIsMappedArrayBuffer) {
1506
// get the initial capacity to something reasonable to avoid a bunch of
1507
// reallocs right at the start
1508
if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0)
1509
xmlHttpRequest->mArrayBufferBuilder.setCapacity(
1510
std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
1511
1512
if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder.append(
1513
reinterpret_cast<const uint8_t*>(fromRawSegment), count,
1514
XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
1515
return NS_ERROR_OUT_OF_MEMORY;
1516
}
1517
1518
} else if (xmlHttpRequest->mResponseType ==
1519
XMLHttpRequestResponseType::_empty &&
1520
xmlHttpRequest->mResponseXML) {
1521
// Copy for our own use
1522
if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
1523
fallible)) {
1524
return NS_ERROR_OUT_OF_MEMORY;
1525
}
1526
} else if (xmlHttpRequest->mResponseType ==
1527
XMLHttpRequestResponseType::_empty ||
1528
xmlHttpRequest->mResponseType ==
1529
XMLHttpRequestResponseType::Text ||
1530
xmlHttpRequest->mResponseType ==
1531
XMLHttpRequestResponseType::Json) {
1532
MOZ_ASSERT(!xmlHttpRequest->mResponseXML,
1533
"We shouldn't be parsing a doc here");
1534
rv = xmlHttpRequest->AppendToResponseText(
1535
AsBytes(MakeSpan(fromRawSegment, count)));
1536
if (NS_WARN_IF(NS_FAILED(rv))) {
1537
return rv;
1538
}
1539
}
1540
1541
if (xmlHttpRequest->mFlagParseBody) {
1542
// Give the same data to the parser.
1543
1544
// We need to wrap the data in a new lightweight stream and pass that
1545
// to the parser, because calling ReadSegments() recursively on the same
1546
// stream is not supported.
1547
nsCOMPtr<nsIInputStream> copyStream;
1548
rv = NS_NewByteInputStream(getter_AddRefs(copyStream),
1549
MakeSpan(fromRawSegment, count),
1550
NS_ASSIGNMENT_DEPEND);
1551
1552
if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
1553
NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
1554
nsresult parsingResult =
1555
xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
1556
xmlHttpRequest->mChannel, copyStream, toOffset, count);
1557
1558
// No use to continue parsing if we failed here, but we
1559
// should still finish reading the stream
1560
if (NS_FAILED(parsingResult)) {
1561
xmlHttpRequest->mFlagParseBody = false;
1562
}
1563
}
1564
}
1565
1566
if (NS_SUCCEEDED(rv)) {
1567
*writeCount = count;
1568
} else {
1569
*writeCount = 0;
1570
}
1571
1572
return rv;
1573
}
1574
1575
namespace {
1576
1577
void GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI) {
1578
MOZ_ASSERT(aRequest);
1579
MOZ_ASSERT(aURI);
1580
1581
*aURI = nullptr;
1582
1583
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1584
if (!channel) {
1585
return;
1586
}
1587
1588
nsCOMPtr<nsIURI> uri;
1589
nsresult rv = channel->GetURI(getter_AddRefs(uri));
1590
if (NS_FAILED(rv)) {
1591
return;
1592
}
1593
1594
if (!dom::IsBlobURI(uri)) {
1595
return;
1596
}
1597
1598
uri.forget(aURI);
1599
}
1600
1601
nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
1602
MOZ_ASSERT(aRequest);
1603
MOZ_ASSERT(aFile);
1604
1605
*aFile = nullptr;
1606
1607
nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
1608
if (!fc) {
1609
return NS_OK;
1610
}
1611
1612
nsCOMPtr<nsIFile> file;
1613
nsresult rv = fc->GetFile(getter_AddRefs(file));
1614
if (NS_WARN_IF(NS_FAILED(rv))) {
1615
return rv;
1616
}
1617
1618
file.forget(aFile);
1619
return NS_OK;
1620
}
1621
1622
nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
1623
const char* aFromRawSegment, uint32_t aToOffset,
1624
uint32_t aCount, uint32_t* aWriteCount) {
1625
*aWriteCount = aCount;
1626
return NS_OK;
1627
}
1628
1629
class FileCreationHandler final : public PromiseNativeHandler {
1630
public:
1631
NS_DECL_ISUPPORTS
1632
1633
static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
1634
MOZ_ASSERT(aPromise);
1635
1636
RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
1637
aPromise->AppendNativeHandler(handler);
1638
}
1639
1640
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1641
if (NS_WARN_IF(!aValue.isObject())) {
1642
mXHR->LocalFileToBlobCompleted(nullptr);
1643
return;
1644
}
1645
1646
RefPtr<Blob> blob;
1647
if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
1648
mXHR->LocalFileToBlobCompleted(nullptr);
1649
return;
1650
}
1651
1652
mXHR->LocalFileToBlobCompleted(blob);
1653
}
1654
1655
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1656
mXHR->LocalFileToBlobCompleted(nullptr);
1657
}
1658
1659
private:
1660
explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
1661
MOZ_ASSERT(aXHR);
1662
}
1663
1664
~FileCreationHandler() = default;
1665
1666
RefPtr<XMLHttpRequestMainThread> mXHR;
1667
};
1668
1669
NS_IMPL_ISUPPORTS0(FileCreationHandler)
1670
1671
} // namespace
1672
1673
void XMLHttpRequestMainThread::LocalFileToBlobCompleted(Blob* aBlob) {
1674
MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
1675
1676
mResponseBlob = aBlob;
1677
mBlobStorage = nullptr;
1678
NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1679
1680
ChangeStateToDone(mFlagSyncLooping);
1681
}
1682
1683
NS_IMETHODIMP
1684
XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
1685
nsIInputStream* inStr,
1686
uint64_t sourceOffset,
1687
uint32_t count) {
1688
NS_ENSURE_ARG_POINTER(inStr);
1689
1690
mProgressSinceLastProgressEvent = true;
1691
XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
1692
1693
nsresult rv;
1694
1695
if (mResponseType == XMLHttpRequestResponseType::Blob) {
1696
nsCOMPtr<nsIFile> localFile;
1697
nsCOMPtr<nsIURI> blobURI;
1698
GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
1699
if (blobURI) {
1700
RefPtr<BlobImpl> blobImpl;
1701
rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
1702
if (NS_SUCCEEDED(rv)) {
1703
if (blobImpl) {
1704
mResponseBlob = Blob::Create(GetOwner(), blobImpl);
1705
}
1706
if (!mResponseBlob) {
1707
rv = NS_ERROR_FILE_NOT_FOUND;
1708
}
1709
}
1710
} else {
1711
rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
1712
}
1713
if (NS_WARN_IF(NS_FAILED(rv))) {
1714
return rv;
1715
}
1716
1717
if (mResponseBlob || localFile) {
1718
mBlobStorage = nullptr;
1719
NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1720
1721
// The nsIStreamListener contract mandates us to read from the stream
1722
// before returning.
1723
uint32_t totalRead;
1724
rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
1725
&totalRead);
1726
NS_ENSURE_SUCCESS(rv, rv);
1727
1728
ChangeState(XMLHttpRequest_Binding::LOADING);
1729
1730
// Cancel() must be called with an error. We use
1731
// NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
1732
// just because we can retrieve the File from the channel directly.
1733
return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
1734
}
1735
}
1736
1737
uint32_t totalRead;
1738
rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
1739
(void*)this, count, &totalRead);
1740
NS_ENSURE_SUCCESS(rv, rv);
1741
1742
// Fire the first progress event/loading state change
1743
if (mState == XMLHttpRequest_Binding::HEADERS_RECEIVED) {
1744
ChangeState(XMLHttpRequest_Binding::LOADING);
1745
if (!mFlagSynchronous) {
1746
DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
1747
mLoadTotal);
1748
}
1749
mProgressSinceLastProgressEvent = false;
1750
}
1751
1752
if (!mFlagSynchronous && !mProgressTimerIsActive) {
1753
StartProgressEventTimer();
1754
}
1755
1756
return NS_OK;
1757
}
1758
1759
NS_IMETHODIMP
1760
XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
1761
AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
1762
1763
nsresult rv = NS_OK;
1764
if (!mFirstStartRequestSeen && mRequestObserver) {
1765
mFirstStartRequestSeen = true;
1766
mRequestObserver->OnStartRequest(request);
1767
}
1768
1769
if (request != mChannel) {
1770
// Can this still happen?
1771
return NS_OK;
1772
}
1773
1774
// Don't do anything if we have been aborted
1775
if (mState == XMLHttpRequest_Binding::UNSENT) {
1776
return NS_OK;
1777
}
1778
1779
// Don't do anything if we're in mid-abort, but let the request
1780
// know (this can happen due to race conditions in valid XHRs,
1781
// see bz1070763 for info).
1782
if (mFlagAborted) {
1783
return NS_BINDING_ABORTED;
1784
}
1785
1786
// Don't do anything if we have timed out.
1787
if (mFlagTimedOut) {
1788
return NS_OK;
1789
}
1790
1791
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
1792
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
1793
1794
nsresult status;
1795
request->GetStatus(&status);
1796
if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
1797
mErrorLoad = ErrorType::eRequest;
1798
}
1799
1800
// Upload phase is now over. If we were uploading anything,
1801
// stop the timer and fire any final progress events.
1802
if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
1803
!mFlagSynchronous) {
1804
StopProgressEventTimer();
1805
1806
mUploadTransferred = mUploadTotal;
1807
1808
if (mProgressSinceLastProgressEvent) {
1809
DispatchProgressEvent(mUpload, ProgressEventType::progress,
1810
mUploadTransferred, mUploadTotal);
1811
mProgressSinceLastProgressEvent = false;
1812
}
1813
1814
mUploadComplete = true;
1815
DispatchProgressEvent(mUpload, ProgressEventType::load, mUploadTotal,
1816
mUploadTotal);
1817
}
1818
1819
mFlagParseBody = true;
1820
if (mErrorLoad == ErrorType::eOK) {
1821
ChangeState(XMLHttpRequest_Binding::HEADERS_RECEIVED);
1822
}
1823
1824
ResetResponse();
1825
1826
if (!mOverrideMimeType.IsEmpty()) {
1827
channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
1828
}
1829
1830
// Fallback to 'application/octet-stream'
1831
nsAutoCString type;