/* -*- 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/CachePushStreamChild.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/ReadStream.h" #include "mozilla/ipc/BackgroundChild.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::BackgroundChild; using mozilla::ipc::FileDescriptor; using mozilla::ipc::PBackgroundChild; using mozilla::ipc::PFileDescriptorSetChild; namespace { static bool HasVaryStar(mozilla::dom::InternalHeaders* aHeaders) { nsAutoTArray varyHeaders; ErrorResult rv; aHeaders->GetAll(NS_LITERAL_CSTRING("vary"), varyHeaders, rv); MOZ_ALWAYS_TRUE(!rv.Failed()); for (uint32_t i = 0; i < varyHeaders.Length(); ++i) { nsAutoCString varyValue(varyHeaders[i]); char* rawBuffer = varyValue.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 SerializeNormalStream(nsIInputStream* aStream, CacheReadStream& aReadStreamOut) { nsAutoTArray fds; SerializeInputStream(aStream, aReadStreamOut.params(), fds); PFileDescriptorSetChild* fdSet = nullptr; if (!fds.IsEmpty()) { // We should not be serializing until we have an actor ready PBackgroundChild* manager = BackgroundChild::GetForCurrentThread(); MOZ_ASSERT(manager); fdSet = manager->SendPFileDescriptorSetConstructor(fds[0]); for (uint32_t i = 1; i < fds.Length(); ++i) { Unused << fdSet->SendAddFileDescriptor(fds[i]); } } if (fdSet) { aReadStreamOut.fds() = fdSet; } else { aReadStreamOut.fds() = void_t(); } } void ToHeadersEntryList(nsTArray& aOut, InternalHeaders* aHeaders) { MOZ_ASSERT(aHeaders); nsAutoTArray 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(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(&request, aBodyAction, aRv); if (aRv.Failed()) { return nullptr; } return request.GetInternalRequest(); } return ToInternalRequest(aIn.GetAsUSVString(), aRv); } already_AddRefed TypeUtils::ToInternalRequest(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(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, ErrorResult& aRv) { MOZ_ASSERT(aIn); aIn->GetMethod(aOut.method()); nsAutoCString url; aIn->GetURL(url); 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; } } aIn->GetReferrer(aOut.referrer()); RefPtr headers = aIn->Headers(); MOZ_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(); 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(), aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } void TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut, InternalResponse& aIn, ErrorResult& aRv) { aOut.type() = aIn.Type(); aIn.GetUnfilteredUrl(aOut.url()); if (aOut.url() != EmptyCString()) { // Pass all Response URL schemes through... The spec only requires we take // action on invalid schemes for Request objects. ProcessURL(aOut.url(), nullptr, nullptr, nullptr, aRv); if (aRv.Failed()) { return; } } aOut.status() = aIn.GetUnfilteredStatus(); aOut.statusText() = aIn.GetUnfilteredStatusText(); RefPtr headers = aIn.UnfilteredHeaders(); MOZ_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(); } } void TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn, 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(); } 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; 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) { RefPtr error = InternalResponse::NetworkError(); RefPtr r = new Response(GetGlobalObject(), error); return r.forget(); } RefPtr ir = new InternalResponse(aIn.status(), aIn.statusText()); ir->SetUrl(aIn.url()); RefPtr internalHeaders = ToInternalHeaders(aIn.headers(), aIn.headersGuard()); ErrorResult result; ir->Headers()->SetGuard(aIn.headersGuard(), result); MOZ_ASSERT(!result.Failed()); ir->Headers()->Fill(*internalHeaders, result); MOZ_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); 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_ASSERT(ir); RefPtr ref = new Response(GetGlobalObject(), ir); return ref.forget(); } already_AddRefed TypeUtils::ToInternalRequest(const CacheRequest& aIn) { RefPtr internalRequest = new InternalRequest(); internalRequest->SetMethod(aIn.method()); nsAutoCString url(aIn.urlWithoutQuery()); url.Append(aIn.urlQuery()); internalRequest->SetURL(url); internalRequest->SetReferrer(aIn.referrer()); internalRequest->SetMode(aIn.mode()); internalRequest->SetCredentialsMode(aIn.credentials()); internalRequest->SetContentPolicyType(aIn.contentPolicyType()); internalRequest->SetCacheMode(aIn.requestCache()); internalRequest->SetRedirectMode(aIn.requestRedirect()); 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_ASSERT(!result.Failed()); internalRequest->Headers()->SetGuard(aIn.headersGuard(), result); MOZ_ASSERT(!result.Failed()); nsCOMPtr stream = ReadStream::Create(aIn.body()); internalRequest->SetBody(stream); return internalRequest.forget(); } already_AddRefed TypeUtils::ToRequest(const CacheRequest& aIn) { RefPtr internalRequest = ToInternalRequest(aIn); RefPtr request = new Request(GetGlobalObject(), internalRequest); 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 nsAFlatCString& 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") || scheme.LowerCaseEqualsLiteral("app"); } 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_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(Request* aRequest, BodyAction aBodyAction, ErrorResult& aRv) { MOZ_ASSERT(aRequest); if (aBodyAction == IgnoreBody) { return; } if (aRequest->BodyUsed()) { aRv.ThrowTypeError(); return; } nsCOMPtr stream; aRequest->GetBody(getter_AddRefs(stream)); if (stream) { aRequest->SetBodyUsed(); } } 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_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, ErrorResult& aRv) { *aStreamOut = void_t(); if (!aStream) { return; } // Option 1: Send a cache-specific ReadStream if we can. RefPtr controlled = do_QueryObject(aStream); if (controlled) { controlled->Serialize(aStreamOut); return; } CacheReadStream readStream; readStream.controlChild() = nullptr; readStream.controlParent() = nullptr; readStream.pushStreamChild() = nullptr; readStream.pushStreamParent() = nullptr; // Option 2: Do normal stream serialization if its supported. nsCOMPtr serial = do_QueryInterface(aStream); if (serial) { SerializeNormalStream(aStream, readStream); // Option 3: As a last resort push data across manually. Should only be // needed for nsPipe input stream. Only works for async, // non-blocking streams. } else { SerializePushStream(aStream, readStream, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } *aStreamOut = readStream; } void TypeUtils::SerializePushStream(nsIInputStream* aStream, CacheReadStream& aReadStreamOut, ErrorResult& aRv) { nsCOMPtr asyncStream = do_QueryInterface(aStream); if (NS_WARN_IF(!asyncStream)) { aRv = NS_ERROR_FAILURE; return; } bool nonBlocking = false; aRv = asyncStream->IsNonBlocking(&nonBlocking); if (NS_WARN_IF(aRv.Failed())) { return; } if (NS_WARN_IF(!nonBlocking)) { aRv = NS_ERROR_FAILURE; return; } aReadStreamOut.pushStreamChild() = CreatePushStream(asyncStream); MOZ_ASSERT(aReadStreamOut.pushStreamChild()); aReadStreamOut.params() = void_t(); aReadStreamOut.fds() = void_t(); // CachePushStreamChild::Start() must be called after sending the stream // across to the parent side. } } // namespace cache } // namespace dom } // namespace mozilla