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/. */
9 #include "js/friend/ErrorMessages.h" // JSMSG_*
12 #include "nsIAsyncInputStream.h"
13 #include "nsIHttpChannel.h"
14 #include "nsNetUtil.h"
15 #include "nsStreamUtils.h"
17 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/DOMException.h"
20 #include "mozilla/dom/InternalRequest.h"
21 #include "mozilla/dom/Response.h"
22 #include "mozilla/dom/WorkerRef.h"
24 namespace mozilla::dom
{
27 nsresult
FetchUtil::GetValidRequestMethod(const nsACString
& aMethod
,
28 nsCString
& outMethod
) {
29 nsAutoCString
upperCaseMethod(aMethod
);
30 ToUpperCase(upperCaseMethod
);
31 if (!NS_IsValidHTTPToken(aMethod
)) {
32 outMethod
.SetIsVoid(true);
33 return NS_ERROR_DOM_SYNTAX_ERR
;
36 if (upperCaseMethod
.EqualsLiteral("CONNECT") ||
37 upperCaseMethod
.EqualsLiteral("TRACE") ||
38 upperCaseMethod
.EqualsLiteral("TRACK")) {
39 outMethod
.SetIsVoid(true);
40 return NS_ERROR_DOM_SECURITY_ERR
;
43 if (upperCaseMethod
.EqualsLiteral("DELETE") ||
44 upperCaseMethod
.EqualsLiteral("GET") ||
45 upperCaseMethod
.EqualsLiteral("HEAD") ||
46 upperCaseMethod
.EqualsLiteral("OPTIONS") ||
47 upperCaseMethod
.EqualsLiteral("POST") ||
48 upperCaseMethod
.EqualsLiteral("PUT")) {
49 outMethod
= upperCaseMethod
;
51 outMethod
= aMethod
; // Case unchanged for non-standard methods
56 static bool FindCRLF(nsACString::const_iterator
& aStart
,
57 nsACString::const_iterator
& aEnd
) {
58 nsACString::const_iterator
end(aEnd
);
59 return FindInReadable("\r\n"_ns
, aStart
, end
);
62 // Reads over a CRLF and positions start after it.
63 static bool PushOverLine(nsACString::const_iterator
& aStart
,
64 const nsACString::const_iterator
& aEnd
) {
65 if (*aStart
== nsCRT::CR
&& (aEnd
- aStart
> 1) && *(++aStart
) == nsCRT::LF
) {
66 ++aStart
; // advance to after CRLF
74 bool FetchUtil::ExtractHeader(nsACString::const_iterator
& aStart
,
75 nsACString::const_iterator
& aEnd
,
76 nsCString
& aHeaderName
, nsCString
& aHeaderValue
,
77 bool* aWasEmptyHeader
) {
78 MOZ_ASSERT(aWasEmptyHeader
);
79 // Set it to a valid value here so we don't forget later.
80 *aWasEmptyHeader
= false;
82 const char* beginning
= aStart
.get();
83 nsACString::const_iterator
end(aEnd
);
84 if (!FindCRLF(aStart
, end
)) {
88 if (aStart
.get() == beginning
) {
89 *aWasEmptyHeader
= true;
93 nsAutoCString
header(beginning
, aStart
.get() - beginning
);
95 nsACString::const_iterator headerStart
, iter
, headerEnd
;
96 header
.BeginReading(headerStart
);
97 header
.EndReading(headerEnd
);
99 if (!FindCharInReadable(':', iter
, headerEnd
)) {
103 aHeaderName
.Assign(StringHead(header
, iter
- headerStart
));
104 aHeaderName
.CompressWhitespace();
105 if (!NS_IsValidHTTPToken(aHeaderName
)) {
109 aHeaderValue
.Assign(Substring(++iter
, headerEnd
));
110 if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue
)) {
113 aHeaderValue
.CompressWhitespace();
115 return PushOverLine(aStart
, aEnd
);
119 nsresult
FetchUtil::SetRequestReferrer(nsIPrincipal
* aPrincipal
, Document
* aDoc
,
120 nsIHttpChannel
* aChannel
,
121 InternalRequest
& aRequest
) {
122 MOZ_ASSERT(NS_IsMainThread());
125 nsAutoString referrer
;
126 aRequest
.GetReferrer(referrer
);
128 ReferrerPolicy policy
= aRequest
.ReferrerPolicy_();
129 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
130 if (referrer
.IsEmpty()) {
131 // This is the case request’s referrer is "no-referrer"
132 referrerInfo
= new ReferrerInfo(nullptr, ReferrerPolicy::No_referrer
);
133 } else if (referrer
.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR
)) {
134 referrerInfo
= ReferrerInfo::CreateForFetch(aPrincipal
, aDoc
);
135 // In the first step, we should use referrer info from requetInit
136 referrerInfo
= static_cast<ReferrerInfo
*>(referrerInfo
.get())
137 ->CloneWithNewPolicy(policy
);
139 // From "Determine request's Referrer" step 3
140 // "If request's referrer is a URL, let referrerSource be request's
142 nsCOMPtr
<nsIURI
> referrerURI
;
143 rv
= NS_NewURI(getter_AddRefs(referrerURI
), referrer
);
144 NS_ENSURE_SUCCESS(rv
, rv
);
145 referrerInfo
= new ReferrerInfo(referrerURI
, policy
);
148 rv
= aChannel
->SetReferrerInfoWithoutClone(referrerInfo
);
149 NS_ENSURE_SUCCESS(rv
, rv
);
151 nsAutoString computedReferrerSpec
;
152 referrerInfo
= aChannel
->GetReferrerInfo();
154 Unused
<< referrerInfo
->GetComputedReferrerSpec(computedReferrerSpec
);
157 // Step 8 https://fetch.spec.whatwg.org/#main-fetch
158 // If request’s referrer is not "no-referrer", set request’s referrer to
159 // the result of invoking determine request’s referrer.
160 aRequest
.SetReferrer(computedReferrerSpec
);
165 class WindowStreamOwner final
: public nsIObserver
,
166 public nsSupportsWeakReference
{
167 // Read from any thread but only set/cleared on the main thread. The lifecycle
168 // of WindowStreamOwner prevents concurrent read/clear.
169 nsCOMPtr
<nsIAsyncInputStream
> mStream
;
171 nsCOMPtr
<nsIGlobalObject
> mGlobal
;
173 ~WindowStreamOwner() {
174 MOZ_ASSERT(NS_IsMainThread());
176 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
178 obs
->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC
);
185 WindowStreamOwner(nsIAsyncInputStream
* aStream
, nsIGlobalObject
* aGlobal
)
186 : mStream(aStream
), mGlobal(aGlobal
) {
187 MOZ_DIAGNOSTIC_ASSERT(mGlobal
);
188 MOZ_ASSERT(NS_IsMainThread());
191 static already_AddRefed
<WindowStreamOwner
> Create(
192 nsIAsyncInputStream
* aStream
, nsIGlobalObject
* aGlobal
) {
193 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
194 if (NS_WARN_IF(!os
)) {
198 RefPtr
<WindowStreamOwner
> self
= new WindowStreamOwner(aStream
, aGlobal
);
200 // Holds nsIWeakReference to self.
201 nsresult rv
= os
->AddObserver(self
, DOM_WINDOW_DESTROYED_TOPIC
, true);
202 if (NS_WARN_IF(NS_FAILED(rv
))) {
206 return self
.forget();
209 struct Destroyer final
: Runnable
{
210 RefPtr
<WindowStreamOwner
> mDoomed
;
212 explicit Destroyer(already_AddRefed
<WindowStreamOwner
> aDoomed
)
213 : Runnable("WindowStreamOwner::Destroyer"), mDoomed(aDoomed
) {}
225 Observe(nsISupports
* aSubject
, const char* aTopic
,
226 const char16_t
* aData
) override
{
227 MOZ_ASSERT(NS_IsMainThread());
228 MOZ_DIAGNOSTIC_ASSERT(strcmp(aTopic
, DOM_WINDOW_DESTROYED_TOPIC
) == 0);
234 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(mGlobal
);
235 if (!SameCOMIdentity(aSubject
, window
)) {
239 // mStream->Close() will call JSStreamConsumer::OnInputStreamReady which may
240 // then destory itself, dropping the last reference to 'this'.
241 RefPtr
<WindowStreamOwner
> keepAlive(this);
250 NS_IMPL_ISUPPORTS(WindowStreamOwner
, nsIObserver
, nsISupportsWeakReference
)
252 class WorkerStreamOwner final
{
254 NS_INLINE_DECL_REFCOUNTING(WorkerStreamOwner
)
256 explicit WorkerStreamOwner(nsIAsyncInputStream
* aStream
) : mStream(aStream
) {}
258 static already_AddRefed
<WorkerStreamOwner
> Create(
259 nsIAsyncInputStream
* aStream
, WorkerPrivate
* aWorker
) {
260 RefPtr
<WorkerStreamOwner
> self
= new WorkerStreamOwner(aStream
);
262 self
->mWorkerRef
= WeakWorkerRef::Create(aWorker
, [self
]() {
264 // If this Close() calls JSStreamConsumer::OnInputStreamReady and drops
265 // the last reference to the JSStreamConsumer, 'this' will not be
266 // destroyed since ~JSStreamConsumer() only enqueues a Destroyer.
267 self
->mStream
->Close();
268 self
->mStream
= nullptr;
269 self
->mWorkerRef
= nullptr;
273 if (!self
->mWorkerRef
) {
277 return self
.forget();
280 struct Destroyer final
: CancelableRunnable
{
281 RefPtr
<WorkerStreamOwner
> mDoomed
;
283 explicit Destroyer(RefPtr
<WorkerStreamOwner
>&& aDoomed
)
284 : CancelableRunnable("WorkerStreamOwner::Destroyer"),
285 mDoomed(std::move(aDoomed
)) {}
293 nsresult
Cancel() override
{ return Run(); }
297 ~WorkerStreamOwner() = default;
299 // Read from any thread but only set/cleared on the worker thread. The
300 // lifecycle of WorkerStreamOwner prevents concurrent read/clear.
301 nsCOMPtr
<nsIAsyncInputStream
> mStream
;
302 RefPtr
<WeakWorkerRef
> mWorkerRef
;
305 class JSStreamConsumer final
: public nsIInputStreamCallback
{
306 nsCOMPtr
<nsIEventTarget
> mOwningEventTarget
;
307 RefPtr
<WindowStreamOwner
> mWindowStreamOwner
;
308 RefPtr
<WorkerStreamOwner
> mWorkerStreamOwner
;
309 JS::StreamConsumer
* mConsumer
;
310 bool mConsumerAborted
;
312 JSStreamConsumer(already_AddRefed
<WindowStreamOwner
> aWindowStreamOwner
,
313 nsIGlobalObject
* aGlobal
, JS::StreamConsumer
* aConsumer
)
314 : mOwningEventTarget(aGlobal
->EventTargetFor(TaskCategory::Other
)),
315 mWindowStreamOwner(aWindowStreamOwner
),
316 mConsumer(aConsumer
),
317 mConsumerAborted(false) {
318 MOZ_DIAGNOSTIC_ASSERT(mWindowStreamOwner
);
319 MOZ_DIAGNOSTIC_ASSERT(mConsumer
);
322 JSStreamConsumer(RefPtr
<WorkerStreamOwner
> aWorkerStreamOwner
,
323 nsIGlobalObject
* aGlobal
, JS::StreamConsumer
* aConsumer
)
324 : mOwningEventTarget(aGlobal
->EventTargetFor(TaskCategory::Other
)),
325 mWorkerStreamOwner(std::move(aWorkerStreamOwner
)),
326 mConsumer(aConsumer
),
327 mConsumerAborted(false) {
328 MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner
);
329 MOZ_DIAGNOSTIC_ASSERT(mConsumer
);
332 ~JSStreamConsumer() {
333 // Both WindowStreamOwner and WorkerStreamOwner need to be destroyed on
334 // their global's event target thread.
336 RefPtr
<Runnable
> destroyer
;
337 if (mWindowStreamOwner
) {
338 MOZ_DIAGNOSTIC_ASSERT(!mWorkerStreamOwner
);
339 destroyer
= new WindowStreamOwner::Destroyer(mWindowStreamOwner
.forget());
341 MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner
);
343 new WorkerStreamOwner::Destroyer(std::move(mWorkerStreamOwner
));
346 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget
->Dispatch(destroyer
.forget()));
349 static nsresult
WriteSegment(nsIInputStream
* aStream
, void* aClosure
,
350 const char* aFromSegment
, uint32_t aToOffset
,
351 uint32_t aCount
, uint32_t* aWriteCount
) {
352 JSStreamConsumer
* self
= reinterpret_cast<JSStreamConsumer
*>(aClosure
);
353 MOZ_DIAGNOSTIC_ASSERT(!self
->mConsumerAborted
);
355 // This callback can be called on any thread which is explicitly allowed by
356 // this particular JS API call.
357 if (!self
->mConsumer
->consumeChunk((const uint8_t*)aFromSegment
, aCount
)) {
358 self
->mConsumerAborted
= true;
359 return NS_ERROR_UNEXPECTED
;
362 *aWriteCount
= aCount
;
367 NS_DECL_THREADSAFE_ISUPPORTS
369 static bool Start(nsCOMPtr
<nsIInputStream
>&& aStream
,
370 JS::StreamConsumer
* aConsumer
, nsIGlobalObject
* aGlobal
,
371 WorkerPrivate
* aMaybeWorker
) {
372 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
;
373 nsresult rv
= NS_MakeAsyncNonBlockingInputStream(
374 aStream
.forget(), getter_AddRefs(asyncStream
));
375 if (NS_WARN_IF(NS_FAILED(rv
))) {
379 RefPtr
<JSStreamConsumer
> consumer
;
381 RefPtr
<WorkerStreamOwner
> owner
=
382 WorkerStreamOwner::Create(asyncStream
, aMaybeWorker
);
387 consumer
= new JSStreamConsumer(std::move(owner
), aGlobal
, aConsumer
);
389 RefPtr
<WindowStreamOwner
> owner
=
390 WindowStreamOwner::Create(asyncStream
, aGlobal
);
395 consumer
= new JSStreamConsumer(owner
.forget(), aGlobal
, aConsumer
);
398 // This AsyncWait() creates a ref-cycle between asyncStream and consumer:
400 // asyncStream -> consumer -> (Window|Worker)StreamOwner -> asyncStream
402 // The cycle is broken when the stream completes or errors out and
403 // asyncStream drops its reference to consumer.
404 return NS_SUCCEEDED(asyncStream
->AsyncWait(consumer
, 0, 0, nullptr));
407 // nsIInputStreamCallback:
410 OnInputStreamReady(nsIAsyncInputStream
* aStream
) override
{
411 // Can be called on any stream. The JS API calls made below explicitly
412 // support being called from any thread.
413 MOZ_DIAGNOSTIC_ASSERT(!mConsumerAborted
);
417 uint64_t available
= 0;
418 rv
= aStream
->Available(&available
);
419 if (NS_SUCCEEDED(rv
) && available
== 0) {
420 rv
= NS_BASE_STREAM_CLOSED
;
423 if (rv
== NS_BASE_STREAM_CLOSED
) {
424 mConsumer
->streamEnd();
429 mConsumer
->streamError(size_t(rv
));
433 // Check mConsumerAborted before NS_FAILED to avoid calling streamError()
434 // if consumeChunk() returned false per JS API contract.
435 uint32_t written
= 0;
436 rv
= aStream
->ReadSegments(WriteSegment
, this, available
, &written
);
437 if (mConsumerAborted
) {
440 if (NS_WARN_IF(NS_FAILED(rv
))) {
441 mConsumer
->streamError(size_t(rv
));
445 rv
= aStream
->AsyncWait(this, 0, 0, nullptr);
446 if (NS_WARN_IF(NS_FAILED(rv
))) {
447 mConsumer
->streamError(size_t(rv
));
455 NS_IMPL_ISUPPORTS(JSStreamConsumer
, nsIInputStreamCallback
)
457 static bool ThrowException(JSContext
* aCx
, unsigned errorNumber
) {
458 JS_ReportErrorNumberASCII(aCx
, js::GetErrorMessage
, nullptr, errorNumber
);
463 bool FetchUtil::StreamResponseToJS(JSContext
* aCx
, JS::HandleObject aObj
,
464 JS::MimeType aMimeType
,
465 JS::StreamConsumer
* aConsumer
,
466 WorkerPrivate
* aMaybeWorker
) {
467 MOZ_ASSERT(!aMaybeWorker
== NS_IsMainThread());
469 RefPtr
<Response
> response
;
470 nsresult rv
= UNWRAP_OBJECT(Response
, aObj
, response
);
472 return ThrowException(aCx
, JSMSG_WASM_BAD_RESPONSE_VALUE
);
475 const char* requiredMimeType
= nullptr;
477 case JS::MimeType::Wasm
:
478 requiredMimeType
= WASM_CONTENT_TYPE
;
482 nsAutoCString mimeType
;
483 response
->GetMimeType(mimeType
);
485 if (!mimeType
.EqualsASCII(requiredMimeType
)) {
486 JS_ReportErrorNumberASCII(aCx
, js::GetErrorMessage
, nullptr,
487 JSMSG_WASM_BAD_RESPONSE_MIME_TYPE
, mimeType
.get(),
492 if (response
->Type() != ResponseType::Basic
&&
493 response
->Type() != ResponseType::Cors
&&
494 response
->Type() != ResponseType::Default
) {
495 return ThrowException(aCx
, JSMSG_WASM_BAD_RESPONSE_CORS_SAME_ORIGIN
);
498 if (!response
->Ok()) {
499 return ThrowException(aCx
, JSMSG_WASM_BAD_RESPONSE_STATUS
);
502 IgnoredErrorResult result
;
503 bool used
= response
->GetBodyUsed(result
);
504 if (NS_WARN_IF(result
.Failed())) {
505 return ThrowException(aCx
, JSMSG_WASM_ERROR_CONSUMING_RESPONSE
);
508 return ThrowException(aCx
, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED
);
512 case JS::MimeType::Wasm
:
514 response
->GetUrl(url
);
516 nsCString sourceMapUrl
;
517 response
->GetInternalHeaders()->Get("SourceMap"_ns
, sourceMapUrl
, result
);
518 if (NS_WARN_IF(result
.Failed())) {
519 return ThrowException(aCx
, JSMSG_WASM_ERROR_CONSUMING_RESPONSE
);
521 NS_ConvertUTF16toUTF8
urlUTF8(url
);
522 aConsumer
->noteResponseURLs(
523 urlUTF8
.get(), sourceMapUrl
.IsVoid() ? nullptr : sourceMapUrl
.get());
527 RefPtr
<InternalResponse
> ir
= response
->GetInternalResponse();
528 if (NS_WARN_IF(!ir
)) {
529 return ThrowException(aCx
, JSMSG_OUT_OF_MEMORY
);
532 nsCOMPtr
<nsIInputStream
> body
;
533 ir
->GetUnfilteredBody(getter_AddRefs(body
));
535 aConsumer
->streamEnd();
539 IgnoredErrorResult error
;
540 response
->SetBodyUsed(aCx
, error
);
541 if (NS_WARN_IF(error
.Failed())) {
542 return ThrowException(aCx
, JSMSG_WASM_ERROR_CONSUMING_RESPONSE
);
545 nsIGlobalObject
* global
= xpc::NativeGlobal(js::UncheckedUnwrap(aObj
));
547 if (!JSStreamConsumer::Start(std::move(body
), aConsumer
, global
,
549 return ThrowException(aCx
, JSMSG_OUT_OF_MEMORY
);
556 void FetchUtil::ReportJSStreamError(JSContext
* aCx
, size_t aErrorCode
) {
557 // For now, convert *all* errors into AbortError.
559 RefPtr
<DOMException
> e
= DOMException::Create(NS_ERROR_DOM_ABORT_ERR
);
561 JS::Rooted
<JS::Value
> value(aCx
);
562 if (!GetOrCreateDOMReflector(aCx
, e
, &value
)) {
566 JS_SetPendingException(aCx
, value
);
569 } // namespace mozilla::dom