summaryrefslogtreecommitdiffstats
path: root/dom/cache/TypeUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/cache/TypeUtils.cpp')
-rw-r--r--dom/cache/TypeUtils.cpp499
1 files changed, 499 insertions, 0 deletions
diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp
new file mode 100644
index 0000000000..5e42f0a079
--- /dev/null
+++ b/dom/cache/TypeUtils.cpp
@@ -0,0 +1,499 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/cache/TypeUtils.h"
+
+#include <algorithm>
+#include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/CacheStorageBinding.h"
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/dom/InternalRequest.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/cache/CacheTypes.h"
+#include "mozilla/dom/cache/ReadStream.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsCOMPtr.h"
+#include "nsHttp.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsPromiseFlatString.h"
+#include "nsQueryObject.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsURLParsers.h"
+
+namespace mozilla::dom::cache {
+
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::FileDescriptor;
+using mozilla::ipc::PBackgroundChild;
+
+namespace {
+
+static bool HasVaryStar(mozilla::dom::InternalHeaders* aHeaders) {
+ nsCString varyHeaders;
+ ErrorResult rv;
+ aHeaders->Get("vary"_ns, varyHeaders, rv);
+ MOZ_ALWAYS_TRUE(!rv.Failed());
+
+ for (const nsACString& header :
+ nsCCharSeparatedTokenizer(varyHeaders, NS_HTTP_HEADER_SEP).ToRange()) {
+ if (header.EqualsLiteral("*")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsTArray<HeadersEntry> ToHeadersEntryList(InternalHeaders* aHeaders) {
+ MOZ_DIAGNOSTIC_ASSERT(aHeaders);
+
+ AutoTArray<InternalHeaders::Entry, 16> entryList;
+ aHeaders->GetEntries(entryList);
+
+ nsTArray<HeadersEntry> result;
+ result.SetCapacity(entryList.Length());
+ std::transform(entryList.cbegin(), entryList.cend(), MakeBackInserter(result),
+ [](const auto& entry) {
+ return HeadersEntry(entry.mName, entry.mValue);
+ });
+
+ return result;
+}
+
+} // namespace
+
+SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest(
+ JSContext* aCx, const RequestOrUSVString& aIn, BodyAction aBodyAction,
+ ErrorResult& aRv) {
+ if (aIn.IsRequest()) {
+ Request& request = aIn.GetAsRequest();
+
+ // Check and set bodyUsed flag immediately because its on Request
+ // instead of InternalRequest.
+ CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return request.GetInternalRequest();
+ }
+
+ return ToInternalRequest(aIn.GetAsUSVString(), aRv);
+}
+
+SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest(
+ JSContext* aCx, const OwningRequestOrUSVString& aIn, BodyAction aBodyAction,
+ ErrorResult& aRv) {
+ if (aIn.IsRequest()) {
+ Request& request = aIn.GetAsRequest();
+
+ // Check and set bodyUsed flag immediately because its on Request
+ // instead of InternalRequest.
+ CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return request.GetInternalRequest();
+ }
+
+ return ToInternalRequest(aIn.GetAsUSVString(), aRv);
+}
+
+void TypeUtils::ToCacheRequest(CacheRequest& aOut, const InternalRequest& aIn,
+ BodyAction aBodyAction,
+ SchemeAction aSchemeAction, ErrorResult& aRv) {
+ aIn.GetMethod(aOut.method());
+ nsCString url(aIn.GetURLWithoutFragment());
+ bool schemeValid;
+ ProcessURL(url, &schemeValid, &aOut.urlWithoutQuery(), &aOut.urlQuery(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (!schemeValid) {
+ if (aSchemeAction == TypeErrorOnInvalidScheme) {
+ aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>("Request", url);
+ return;
+ }
+ }
+ aOut.urlFragment() = aIn.GetFragment();
+
+ aIn.GetReferrer(aOut.referrer());
+ aOut.referrerPolicy() = aIn.ReferrerPolicy_();
+ RefPtr<InternalHeaders> headers = aIn.Headers();
+ MOZ_DIAGNOSTIC_ASSERT(headers);
+ aOut.headers() = ToHeadersEntryList(headers);
+ aOut.headersGuard() = headers->Guard();
+ aOut.mode() = aIn.Mode();
+ aOut.credentials() = aIn.GetCredentialsMode();
+ aOut.contentPolicyType() = aIn.ContentPolicyType();
+ aOut.requestCache() = aIn.GetCacheMode();
+ aOut.requestRedirect() = aIn.GetRedirectMode();
+
+ aOut.integrity() = aIn.GetIntegrity();
+ aOut.loadingEmbedderPolicy() = aIn.GetEmbedderPolicy();
+ const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo =
+ aIn.GetPrincipalInfo();
+ if (principalInfo) {
+ aOut.principalInfo() = Some(*(principalInfo.get()));
+ }
+
+ if (aBodyAction == IgnoreBody) {
+ aOut.body() = Nothing();
+ return;
+ }
+
+ // BodyUsed flag is checked and set previously in ToInternalRequest()
+
+ nsCOMPtr<nsIInputStream> stream;
+ aIn.GetBody(getter_AddRefs(stream));
+ SerializeCacheStream(stream, &aOut.body(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+void TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
+ InternalResponse& aIn,
+ ErrorResult& aRv) {
+ aOut.type() = aIn.Type();
+
+ aIn.GetUnfilteredURLList(aOut.urlList());
+ AutoTArray<nsCString, 4> urlList;
+ aIn.GetURLList(urlList);
+
+ for (uint32_t i = 0; i < aOut.urlList().Length(); i++) {
+ MOZ_DIAGNOSTIC_ASSERT(!aOut.urlList()[i].IsEmpty());
+ // Pass all Response URL schemes through... The spec only requires we take
+ // action on invalid schemes for Request objects.
+ ProcessURL(aOut.urlList()[i], nullptr, nullptr, nullptr, aRv);
+ }
+
+ aOut.status() = aIn.GetUnfilteredStatus();
+ aOut.statusText() = aIn.GetUnfilteredStatusText();
+ RefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders();
+ MOZ_DIAGNOSTIC_ASSERT(headers);
+ if (aIn.Type() != ResponseType::Opaque && HasVaryStar(headers)) {
+ aRv.ThrowTypeError("Invalid Response object with a 'Vary: *' header.");
+ return;
+ }
+ aOut.headers() = ToHeadersEntryList(headers);
+ aOut.headersGuard() = headers->Guard();
+ aOut.securityInfo() = aIn.GetChannelInfo().SecurityInfo();
+ if (aIn.GetPrincipalInfo()) {
+ aOut.principalInfo() = Some(*aIn.GetPrincipalInfo());
+ } else {
+ aOut.principalInfo() = Nothing();
+ }
+
+ aOut.paddingInfo() = aIn.GetPaddingInfo();
+ aOut.paddingSize() = aIn.GetPaddingSize();
+}
+
+void TypeUtils::ToCacheResponse(JSContext* aCx, CacheResponse& aOut,
+ Response& aIn, ErrorResult& aRv) {
+ if (aIn.BodyUsed()) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return;
+ }
+
+ SafeRefPtr<InternalResponse> ir = aIn.GetInternalResponse();
+ ToCacheResponseWithoutBody(aOut, *ir, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ ir->GetUnfilteredBody(getter_AddRefs(stream));
+ if (stream) {
+ aIn.SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ SerializeCacheStream(stream, &aOut.body(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+// static
+void TypeUtils::ToCacheQueryParams(CacheQueryParams& aOut,
+ const CacheQueryOptions& aIn) {
+ aOut.ignoreSearch() = aIn.mIgnoreSearch;
+ aOut.ignoreMethod() = aIn.mIgnoreMethod;
+ aOut.ignoreVary() = aIn.mIgnoreVary;
+}
+
+// static
+void TypeUtils::ToCacheQueryParams(CacheQueryParams& aOut,
+ const MultiCacheQueryOptions& aIn) {
+ ToCacheQueryParams(aOut, static_cast<const CacheQueryOptions&>(aIn));
+ aOut.cacheNameSet() = aIn.mCacheName.WasPassed();
+ if (aOut.cacheNameSet()) {
+ aOut.cacheName() = aIn.mCacheName.Value();
+ } else {
+ aOut.cacheName() = u""_ns;
+ }
+}
+
+already_AddRefed<Response> TypeUtils::ToResponse(const CacheResponse& aIn) {
+ if (aIn.type() == ResponseType::Error) {
+ // We don't bother tracking the internal error code for cached responses...
+ RefPtr<Response> r =
+ new Response(GetGlobalObject(),
+ InternalResponse::NetworkError(NS_ERROR_FAILURE), nullptr);
+ return r.forget();
+ }
+
+ SafeRefPtr<InternalResponse> ir =
+ MakeSafeRefPtr<InternalResponse>(aIn.status(), aIn.statusText());
+ ir->SetURLList(aIn.urlList());
+
+ RefPtr<InternalHeaders> internalHeaders =
+ ToInternalHeaders(aIn.headers(), aIn.headersGuard());
+ ErrorResult result;
+
+ // Be careful to fill the headers before setting the guard in order to
+ // correctly re-create the original headers.
+ ir->Headers()->Fill(*internalHeaders, result);
+ MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
+ ir->Headers()->SetGuard(aIn.headersGuard(), result);
+ MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
+
+ ir->InitChannelInfo(aIn.securityInfo());
+ if (aIn.principalInfo().isSome()) {
+ UniquePtr<mozilla::ipc::PrincipalInfo> info(
+ new mozilla::ipc::PrincipalInfo(aIn.principalInfo().ref()));
+ ir->SetPrincipalInfo(std::move(info));
+ }
+
+ nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
+ ir->SetBody(stream, InternalResponse::UNKNOWN_BODY_SIZE);
+
+ switch (aIn.type()) {
+ case ResponseType::Basic:
+ ir = ir->BasicResponse();
+ break;
+ case ResponseType::Cors:
+ ir = ir->CORSResponse();
+ break;
+ case ResponseType::Default:
+ break;
+ case ResponseType::Opaque:
+ ir = ir->OpaqueResponse();
+ break;
+ case ResponseType::Opaqueredirect:
+ ir = ir->OpaqueRedirectResponse();
+ break;
+ default:
+ MOZ_CRASH("Unexpected ResponseType!");
+ }
+ MOZ_DIAGNOSTIC_ASSERT(ir);
+
+ ir->SetPaddingSize(aIn.paddingSize());
+
+ RefPtr<Response> ref =
+ new Response(GetGlobalObject(), std::move(ir), nullptr);
+ return ref.forget();
+}
+SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest(
+ const CacheRequest& aIn) {
+ nsAutoCString url(aIn.urlWithoutQuery());
+ url.Append(aIn.urlQuery());
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(url, aIn.urlFragment());
+ internalRequest->SetMethod(aIn.method());
+ internalRequest->SetReferrer(aIn.referrer());
+ internalRequest->SetReferrerPolicy(aIn.referrerPolicy());
+ internalRequest->SetMode(aIn.mode());
+ internalRequest->SetCredentialsMode(aIn.credentials());
+ internalRequest->SetContentPolicyType(aIn.contentPolicyType());
+ internalRequest->SetCacheMode(aIn.requestCache());
+ internalRequest->SetRedirectMode(aIn.requestRedirect());
+ internalRequest->SetIntegrity(aIn.integrity());
+
+ RefPtr<InternalHeaders> internalHeaders =
+ ToInternalHeaders(aIn.headers(), aIn.headersGuard());
+ ErrorResult result;
+
+ // Be careful to fill the headers before setting the guard in order to
+ // correctly re-create the original headers.
+ internalRequest->Headers()->Fill(*internalHeaders, result);
+ MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
+
+ internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
+ MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
+
+ nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
+
+ internalRequest->SetBody(stream, -1);
+
+ return internalRequest;
+}
+
+SafeRefPtr<Request> TypeUtils::ToRequest(const CacheRequest& aIn) {
+ return MakeSafeRefPtr<Request>(GetGlobalObject(), ToInternalRequest(aIn),
+ nullptr);
+}
+
+// static
+already_AddRefed<InternalHeaders> TypeUtils::ToInternalHeaders(
+ const nsTArray<HeadersEntry>& aHeadersEntryList, HeadersGuardEnum aGuard) {
+ nsTArray<InternalHeaders::Entry> entryList;
+ entryList.SetCapacity(aHeadersEntryList.Length());
+ std::transform(aHeadersEntryList.cbegin(), aHeadersEntryList.cend(),
+ MakeBackInserter(entryList), [](const auto& headersEntry) {
+ return InternalHeaders::Entry(headersEntry.name(),
+ headersEntry.value());
+ });
+
+ RefPtr<InternalHeaders> ref =
+ new InternalHeaders(std::move(entryList), aGuard);
+ return ref.forget();
+}
+
+// Utility function to remove the fragment from a URL, check its scheme, and
+// optionally provide a URL without the query. We're not using nsIURL or URL to
+// do this because they require going to the main thread. static
+void TypeUtils::ProcessURL(nsACString& aUrl, bool* aSchemeValidOut,
+ nsACString* aUrlWithoutQueryOut,
+ nsACString* aUrlQueryOut, ErrorResult& aRv) {
+ const nsCString& flatURL = PromiseFlatCString(aUrl);
+ const char* url = flatURL.get();
+
+ // off the main thread URL parsing using nsStdURLParser.
+ nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser();
+
+ uint32_t pathPos;
+ int32_t pathLen;
+ uint32_t schemePos;
+ int32_t schemeLen;
+ aRv = urlParser->ParseURL(url, flatURL.Length(), &schemePos, &schemeLen,
+ nullptr, nullptr, // ignore authority
+ &pathPos, &pathLen);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (aSchemeValidOut) {
+ nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
+ *aSchemeValidOut =
+ scheme.LowerCaseEqualsLiteral("http") ||
+ scheme.LowerCaseEqualsLiteral("https") ||
+ (StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup() &&
+ scheme.LowerCaseEqualsLiteral("moz-extension"));
+ }
+
+ uint32_t queryPos;
+ int32_t queryLen;
+
+ aRv = urlParser->ParsePath(url + pathPos, flatURL.Length() - pathPos, nullptr,
+ nullptr, // ignore filepath
+ &queryPos, &queryLen, nullptr, nullptr);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!aUrlWithoutQueryOut) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aUrlQueryOut);
+
+ if (queryLen < 0) {
+ *aUrlWithoutQueryOut = aUrl;
+ aUrlQueryOut->Truncate();
+ return;
+ }
+
+ // ParsePath gives us query position relative to the start of the path
+ queryPos += pathPos;
+
+ *aUrlWithoutQueryOut = Substring(aUrl, 0, queryPos - 1);
+ *aUrlQueryOut = Substring(aUrl, queryPos - 1, queryLen + 1);
+}
+
+void TypeUtils::CheckAndSetBodyUsed(JSContext* aCx, Request& aRequest,
+ BodyAction aBodyAction, ErrorResult& aRv) {
+ if (aBodyAction == IgnoreBody) {
+ return;
+ }
+
+ if (aRequest.BodyUsed()) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ aRequest.GetBody(getter_AddRefs(stream));
+ if (stream) {
+ aRequest.SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+}
+
+SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest(const nsAString& aIn,
+ ErrorResult& aRv) {
+ RequestOrUSVString requestOrString;
+ requestOrString.SetAsUSVString().ShareOrDependUpon(aIn);
+
+ // Re-create a GlobalObject stack object so we can use webidl Constructors.
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+ GlobalObject global(cx, GetGlobalObject()->GetGlobalJSObject());
+ MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
+
+ RootedDictionary<RequestInit> requestInit(cx);
+ SafeRefPtr<Request> request =
+ Request::Constructor(global, requestOrString, requestInit, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return request->GetInternalRequest();
+}
+
+void TypeUtils::SerializeCacheStream(nsIInputStream* aStream,
+ Maybe<CacheReadStream>* aStreamOut,
+ ErrorResult& aRv) {
+ *aStreamOut = Nothing();
+ if (!aStream) {
+ return;
+ }
+
+ RefPtr<ReadStream> controlled = do_QueryObject(aStream);
+ if (controlled) {
+ controlled->Serialize(aStreamOut, aRv);
+ return;
+ }
+
+ aStreamOut->emplace(CacheReadStream());
+ CacheReadStream& cacheStream = aStreamOut->ref();
+
+ cacheStream.control() = nullptr;
+
+ MOZ_ALWAYS_TRUE(mozilla::ipc::SerializeIPCStream(do_AddRef(aStream),
+ cacheStream.stream(),
+ /* aAllowLazy */ false));
+}
+
+} // namespace mozilla::dom::cache