/* -*- 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 "mozilla/Unused.h" #include "mozilla/dom/CacheBinding.h" #include "mozilla/dom/InternalRequest.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/Response.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/PFileDescriptorSetChild.h" #include "mozilla/ipc/InputStreamUtils.h" #include "nsCOMPtr.h" #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "nsIIPCSerializableInputStream.h" #include "nsQueryObject.h" #include "nsPromiseFlatString.h" #include "nsStreamUtils.h" #include "nsString.h" #include "nsURLParsers.h" #include "nsCRT.h" #include "nsHttp.h" namespace mozilla { namespace dom { namespace cache { using mozilla::ipc::AutoIPCStream; using mozilla::ipc::BackgroundChild; using mozilla::ipc::FileDescriptor; using mozilla::ipc::PBackgroundChild; using mozilla::ipc::PFileDescriptorSetChild; namespace { static bool HasVaryStar(mozilla::dom::InternalHeaders* aHeaders) { nsCString varyHeaders; ErrorResult rv; aHeaders->Get(NS_LITERAL_CSTRING("vary"), varyHeaders, rv); MOZ_ALWAYS_TRUE(!rv.Failed()); char* rawBuffer = varyHeaders.BeginWriting(); char* token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer); for (; token; token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) { nsDependentCString header(token); if (header.EqualsLiteral("*")) { return true; } } return false; } void ToHeadersEntryList(nsTArray& aOut, InternalHeaders* aHeaders) { MOZ_DIAGNOSTIC_ASSERT(aHeaders); AutoTArray entryList; aHeaders->GetEntries(entryList); for (uint32_t i = 0; i < entryList.Length(); ++i) { InternalHeaders::Entry& entry = entryList[i]; aOut.AppendElement(HeadersEntry(entry.mName, entry.mValue)); } } } // namespace already_AddRefed 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); } already_AddRefed TypeUtils::ToInternalRequest(JSContext* aCx, const OwningRequestOrUSVString& aIn, BodyAction aBodyAction, ErrorResult& aRv) { if (aIn.IsRequest()) { RefPtr request = aIn.GetAsRequest().get(); // 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, InternalRequest* aIn, BodyAction aBodyAction, SchemeAction aSchemeAction, nsTArray>& aStreamCleanupList, ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(aIn); 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) { NS_ConvertUTF8toUTF16 urlUTF16(url); aRv.ThrowTypeError(NS_LITERAL_STRING("Request"), urlUTF16); return; } } aOut.urlFragment() = aIn->GetFragment(); aIn->GetReferrer(aOut.referrer()); aOut.referrerPolicy() = aIn->ReferrerPolicy_(); RefPtr headers = aIn->Headers(); MOZ_DIAGNOSTIC_ASSERT(headers); ToHeadersEntryList(aOut.headers(), 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(); if (aBodyAction == IgnoreBody) { aOut.body() = void_t(); return; } // BodyUsed flag is checked and set previously in ToInternalRequest() nsCOMPtr stream; aIn->GetBody(getter_AddRefs(stream)); SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, 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 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 headers = aIn.UnfilteredHeaders(); MOZ_DIAGNOSTIC_ASSERT(headers); if (HasVaryStar(headers)) { aRv.ThrowTypeError(); return; } ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); aOut.channelInfo() = aIn.GetChannelInfo().AsIPCChannelInfo(); if (aIn.GetPrincipalInfo()) { aOut.principalInfo() = *aIn.GetPrincipalInfo(); } else { aOut.principalInfo() = void_t(); } aOut.paddingInfo() = aIn.GetPaddingInfo(); aOut.paddingSize() = aIn.GetPaddingSize(); } void TypeUtils::ToCacheResponse(JSContext* aCx, CacheResponse& aOut, Response& aIn, nsTArray>& aStreamCleanupList, ErrorResult& aRv) { if (aIn.BodyUsed()) { aRv.ThrowTypeError(); return; } RefPtr ir = aIn.GetInternalResponse(); ToCacheResponseWithoutBody(aOut, *ir, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } nsCOMPtr stream; ir->GetUnfilteredBody(getter_AddRefs(stream)); if (stream) { aIn.SetBodyUsed(aCx, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, 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; aOut.cacheNameSet() = aIn.mCacheName.WasPassed(); if (aOut.cacheNameSet()) { aOut.cacheName() = aIn.mCacheName.Value(); } else { aOut.cacheName() = NS_LITERAL_STRING(""); } } already_AddRefed TypeUtils::ToResponse(const CacheResponse& aIn) { if (aIn.type() == ResponseType::Error) { // We don't bother tracking the internal error code for cached responses... RefPtr error = InternalResponse::NetworkError(NS_ERROR_FAILURE); RefPtr r = new Response(GetGlobalObject(), error, nullptr); return r.forget(); } RefPtr ir = new InternalResponse(aIn.status(), aIn.statusText()); ir->SetURLList(aIn.urlList()); RefPtr 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.channelInfo()); if (aIn.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) { UniquePtr info(new mozilla::ipc::PrincipalInfo(aIn.principalInfo().get_PrincipalInfo())); ir->SetPrincipalInfo(Move(info)); } nsCOMPtr 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 ref = new Response(GetGlobalObject(), ir, nullptr); return ref.forget(); } already_AddRefed TypeUtils::ToInternalRequest(const CacheRequest& aIn) { nsAutoCString url(aIn.urlWithoutQuery()); url.Append(aIn.urlQuery()); RefPtr internalRequest = new 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 = 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 stream = ReadStream::Create(aIn.body()); internalRequest->SetBody(stream, -1); return internalRequest.forget(); } already_AddRefed TypeUtils::ToRequest(const CacheRequest& aIn) { RefPtr internalRequest = ToInternalRequest(aIn); RefPtr request = new Request(GetGlobalObject(), internalRequest, nullptr); return request.forget(); } // static already_AddRefed TypeUtils::ToInternalHeaders(const nsTArray& aHeadersEntryList, HeadersGuardEnum aGuard) { nsTArray entryList(aHeadersEntryList.Length()); for (uint32_t i = 0; i < aHeadersEntryList.Length(); ++i) { const HeadersEntry& headersEntry = aHeadersEntryList[i]; entryList.AppendElement(InternalHeaders::Entry(headersEntry.name(), headersEntry.value())); } RefPtr ref = new InternalHeaders(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 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"); } 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 = EmptyCString(); 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) { MOZ_DIAGNOSTIC_ASSERT(aRequest); if (aBodyAction == IgnoreBody) { return; } if (aRequest->BodyUsed()) { aRv.ThrowTypeError(); return; } nsCOMPtr stream; aRequest->GetBody(getter_AddRefs(stream)); if (stream) { aRequest->SetBodyUsed(aCx, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } } already_AddRefed TypeUtils::ToInternalRequest(const nsAString& aIn, ErrorResult& aRv) { RequestOrUSVString requestOrString; requestOrString.SetAsUSVString().Rebind(aIn.Data(), aIn.Length()); // 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()); RefPtr request = Request::Constructor(global, requestOrString, RequestInit(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return request->GetInternalRequest(); } void TypeUtils::SerializeCacheStream(nsIInputStream* aStream, CacheReadStreamOrVoid* aStreamOut, nsTArray>& aStreamCleanupList, ErrorResult& aRv) { *aStreamOut = void_t(); if (!aStream) { return; } RefPtr controlled = do_QueryObject(aStream); if (controlled) { controlled->Serialize(aStreamOut, aStreamCleanupList, aRv); return; } *aStreamOut = CacheReadStream(); CacheReadStream& cacheStream = aStreamOut->get_CacheReadStream(); cacheStream.controlChild() = nullptr; cacheStream.controlParent() = nullptr; UniquePtr autoStream(new AutoIPCStream(cacheStream.stream())); autoStream->Serialize(aStream, GetIPCManager()); aStreamCleanupList.AppendElement(Move(autoStream)); } } // namespace cache } // namespace dom } // namespace mozilla