Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / fetch / FetchUtil.cpp
blob330ef6dda6a9ab9aef689f6a68af0b2371b41da9
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/. */
7 #include "FetchUtil.h"
9 #include "js/friend/ErrorMessages.h" // JSMSG_*
10 #include "nsCRT.h"
11 #include "nsError.h"
12 #include "nsIAsyncInputStream.h"
13 #include "nsIHttpChannel.h"
14 #include "nsNetUtil.h"
15 #include "nsStreamUtils.h"
16 #include "nsString.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 {
26 // static
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;
50 } else {
51 outMethod = aMethod; // Case unchanged for non-standard methods
53 return NS_OK;
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
67 return true;
70 return false;
73 // static
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)) {
85 return false;
88 if (aStart.get() == beginning) {
89 *aWasEmptyHeader = true;
90 return true;
93 nsAutoCString header(beginning, aStart.get() - beginning);
95 nsACString::const_iterator headerStart, iter, headerEnd;
96 header.BeginReading(headerStart);
97 header.EndReading(headerEnd);
98 iter = headerStart;
99 if (!FindCharInReadable(':', iter, headerEnd)) {
100 return false;
103 aHeaderName.Assign(StringHead(header, iter - headerStart));
104 aHeaderName.CompressWhitespace();
105 if (!NS_IsValidHTTPToken(aHeaderName)) {
106 return false;
109 aHeaderValue.Assign(Substring(++iter, headerEnd));
110 if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
111 return false;
113 aHeaderValue.CompressWhitespace();
115 return PushOverLine(aStart, aEnd);
118 // static
119 nsresult FetchUtil::SetRequestReferrer(nsIPrincipal* aPrincipal, Document* aDoc,
120 nsIHttpChannel* aChannel,
121 InternalRequest& aRequest) {
122 MOZ_ASSERT(NS_IsMainThread());
124 nsresult rv = NS_OK;
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);
138 } else {
139 // From "Determine request's Referrer" step 3
140 // "If request's referrer is a URL, let referrerSource be request's
141 // referrer."
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();
153 if (referrerInfo) {
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);
162 return NS_OK;
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();
177 if (obs) {
178 obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
182 public:
183 NS_DECL_ISUPPORTS
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)) {
195 return nullptr;
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))) {
203 return nullptr;
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) {}
215 NS_IMETHOD
216 Run() override {
217 mDoomed = nullptr;
218 return NS_OK;
222 // nsIObserver:
224 NS_IMETHOD
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);
230 if (!mStream) {
231 return NS_OK;
234 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
235 if (!SameCOMIdentity(aSubject, window)) {
236 return NS_OK;
239 // mStream->Close() will call JSStreamConsumer::OnInputStreamReady which may
240 // then destory itself, dropping the last reference to 'this'.
241 RefPtr<WindowStreamOwner> keepAlive(this);
243 mStream->Close();
244 mStream = nullptr;
245 mGlobal = nullptr;
246 return NS_OK;
250 NS_IMPL_ISUPPORTS(WindowStreamOwner, nsIObserver, nsISupportsWeakReference)
252 class WorkerStreamOwner final {
253 public:
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]() {
263 if (self->mStream) {
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) {
274 return nullptr;
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)) {}
287 NS_IMETHOD
288 Run() override {
289 mDoomed = nullptr;
290 return NS_OK;
293 nsresult Cancel() override { return Run(); }
296 private:
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());
340 } else {
341 MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
342 destroyer =
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;
363 return NS_OK;
366 public:
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))) {
376 return false;
379 RefPtr<JSStreamConsumer> consumer;
380 if (aMaybeWorker) {
381 RefPtr<WorkerStreamOwner> owner =
382 WorkerStreamOwner::Create(asyncStream, aMaybeWorker);
383 if (!owner) {
384 return false;
387 consumer = new JSStreamConsumer(std::move(owner), aGlobal, aConsumer);
388 } else {
389 RefPtr<WindowStreamOwner> owner =
390 WindowStreamOwner::Create(asyncStream, aGlobal);
391 if (!owner) {
392 return false;
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:
409 NS_IMETHOD
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);
415 nsresult rv;
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();
425 return NS_OK;
428 if (NS_FAILED(rv)) {
429 mConsumer->streamError(size_t(rv));
430 return NS_OK;
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) {
438 return NS_OK;
440 if (NS_WARN_IF(NS_FAILED(rv))) {
441 mConsumer->streamError(size_t(rv));
442 return NS_OK;
445 rv = aStream->AsyncWait(this, 0, 0, nullptr);
446 if (NS_WARN_IF(NS_FAILED(rv))) {
447 mConsumer->streamError(size_t(rv));
448 return NS_OK;
451 return NS_OK;
455 NS_IMPL_ISUPPORTS(JSStreamConsumer, nsIInputStreamCallback)
457 static bool ThrowException(JSContext* aCx, unsigned errorNumber) {
458 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, errorNumber);
459 return false;
462 // static
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);
471 if (NS_FAILED(rv)) {
472 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_VALUE);
475 const char* requiredMimeType = nullptr;
476 switch (aMimeType) {
477 case JS::MimeType::Wasm:
478 requiredMimeType = WASM_CONTENT_TYPE;
479 break;
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(),
488 requiredMimeType);
489 return false;
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);
507 if (used) {
508 return ThrowException(aCx, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED);
511 switch (aMimeType) {
512 case JS::MimeType::Wasm:
513 nsAutoString url;
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());
524 break;
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));
534 if (!body) {
535 aConsumer->streamEnd();
536 return true;
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,
548 aMaybeWorker)) {
549 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
552 return true;
555 // static
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)) {
563 return;
566 JS_SetPendingException(aCx, value);
569 } // namespace mozilla::dom