diff options
Diffstat (limited to 'dom/media/systemservices/MediaParent.cpp')
-rw-r--r-- | dom/media/systemservices/MediaParent.cpp | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/dom/media/systemservices/MediaParent.cpp b/dom/media/systemservices/MediaParent.cpp new file mode 100644 index 0000000000..d2fb06b8ae --- /dev/null +++ b/dom/media/systemservices/MediaParent.cpp @@ -0,0 +1,536 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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 "MediaParent.h" + +#include "mozilla/Base64.h" +#include <mozilla/StaticMutex.h> + +#include "MediaUtils.h" +#include "MediaEngine.h" +#include "VideoUtils.h" +#include "nsClassHashtable.h" +#include "nsThreadUtils.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsILineInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsISupportsImpl.h" +#include "mozilla/Logging.h" + +#undef LOG +mozilla::LazyLogModule gMediaParentLog("MediaParent"); +#define LOG(args) MOZ_LOG(gMediaParentLog, mozilla::LogLevel::Debug, args) + +// A file in the profile dir is used to persist mOriginKeys used to anonymize +// deviceIds to be unique per origin, to avoid them being supercookies. + +#define ORIGINKEYS_FILE u"enumerate_devices.txt" +#define ORIGINKEYS_VERSION "1" + +namespace mozilla::media { + +StaticMutex sOriginKeyStoreStsMutex; + +class OriginKeyStore { + NS_INLINE_DECL_REFCOUNTING(OriginKeyStore); + class OriginKey { + public: + static const size_t DecodedLength = 18; + static const size_t EncodedLength = DecodedLength * 4 / 3; + + explicit OriginKey(const nsACString& aKey, + int64_t aSecondsStamp = 0) // 0 = temporal + : mKey(aKey), mSecondsStamp(aSecondsStamp) {} + + nsCString mKey; // Base64 encoded. + int64_t mSecondsStamp; + }; + + class OriginKeysTable { + public: + OriginKeysTable() : mPersistCount(0) {} + + nsresult GetPrincipalKey(const ipc::PrincipalInfo& aPrincipalInfo, + nsCString& aResult, bool aPersist = false) { + nsAutoCString principalString; + PrincipalInfoToString(aPrincipalInfo, principalString); + + OriginKey* key; + if (!mKeys.Get(principalString, &key)) { + nsCString salt; // Make a new one + nsresult rv = GenerateRandomName(salt, OriginKey::EncodedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + key = mKeys.InsertOrUpdate(principalString, MakeUnique<OriginKey>(salt)) + .get(); + } + if (aPersist && !key->mSecondsStamp) { + key->mSecondsStamp = PR_Now() / PR_USEC_PER_SEC; + mPersistCount++; + } + aResult = key->mKey; + return NS_OK; + } + + void Clear(int64_t aSinceWhen) { + // Avoid int64_t* <-> void* casting offset + OriginKey since(nsCString(), aSinceWhen / PR_USEC_PER_SEC); + for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) { + auto originKey = iter.UserData(); + LOG((((originKey->mSecondsStamp >= since.mSecondsStamp) + ? "%s: REMOVE %" PRId64 " >= %" PRId64 + : "%s: KEEP %" PRId64 " < %" PRId64), + __FUNCTION__, originKey->mSecondsStamp, since.mSecondsStamp)); + + if (originKey->mSecondsStamp >= since.mSecondsStamp) { + iter.Remove(); + } + } + mPersistCount = 0; + } + + private: + void PrincipalInfoToString(const ipc::PrincipalInfo& aPrincipalInfo, + nsACString& aString) { + switch (aPrincipalInfo.type()) { + case ipc::PrincipalInfo::TSystemPrincipalInfo: + aString.AssignLiteral("[System Principal]"); + return; + + case ipc::PrincipalInfo::TNullPrincipalInfo: { + const ipc::NullPrincipalInfo& info = + aPrincipalInfo.get_NullPrincipalInfo(); + aString.Assign(info.spec()); + return; + } + + case ipc::PrincipalInfo::TContentPrincipalInfo: { + const ipc::ContentPrincipalInfo& info = + aPrincipalInfo.get_ContentPrincipalInfo(); + aString.Assign(info.originNoSuffix()); + + nsAutoCString suffix; + info.attrs().CreateSuffix(suffix); + aString.Append(suffix); + return; + } + + case ipc::PrincipalInfo::TExpandedPrincipalInfo: { + const ipc::ExpandedPrincipalInfo& info = + aPrincipalInfo.get_ExpandedPrincipalInfo(); + + aString.AssignLiteral("[Expanded Principal ["); + + for (uint32_t i = 0; i < info.allowlist().Length(); i++) { + nsAutoCString str; + PrincipalInfoToString(info.allowlist()[i], str); + + if (i != 0) { + aString.AppendLiteral(", "); + } + + aString.Append(str); + } + + aString.AppendLiteral("]]"); + return; + } + + default: + MOZ_CRASH("Unknown PrincipalInfo type!"); + } + } + + protected: + nsClassHashtable<nsCStringHashKey, OriginKey> mKeys; + size_t mPersistCount; + }; + + class OriginKeysLoader : public OriginKeysTable { + public: + OriginKeysLoader() = default; + + nsresult GetPrincipalKey(const ipc::PrincipalInfo& aPrincipalInfo, + nsCString& aResult, bool aPersist = false) { + auto before = mPersistCount; + nsresult rv = + OriginKeysTable::GetPrincipalKey(aPrincipalInfo, aResult, aPersist); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mPersistCount != before) { + Save(); + } + return NS_OK; + } + + already_AddRefed<nsIFile> GetFile() { + MOZ_ASSERT(mProfileDir); + nsCOMPtr<nsIFile> file; + nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + file->Append(nsLiteralString(ORIGINKEYS_FILE)); + return file.forget(); + } + + // Format of file is key secondsstamp origin (first line is version #): + // + // 1 + // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424733961 http://fiddle.jshell.net + // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424734841 http://mozilla.github.io + // etc. + + nsresult Read() { + nsCOMPtr<nsIFile> file = GetFile(); + if (NS_WARN_IF(!file)) { + return NS_ERROR_UNEXPECTED; + } + bool exists; + nsresult rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!exists) { + return NS_OK; + } + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsCOMPtr<nsILineInputStream> i = do_QueryInterface(stream); + MOZ_ASSERT(i); + MOZ_ASSERT(!mPersistCount); + + nsCString line; + bool hasMoreLines; + rv = i->ReadLine(line, &hasMoreLines); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!line.EqualsLiteral(ORIGINKEYS_VERSION)) { + // If version on disk is newer than we can understand then ignore it. + return NS_OK; + } + + while (hasMoreLines) { + rv = i->ReadLine(line, &hasMoreLines); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // Read key secondsstamp origin. + // Ignore any lines that don't fit format in the comment above exactly. + int32_t f = line.FindChar(' '); + if (f < 0) { + continue; + } + const nsACString& key = Substring(line, 0, f); + const nsACString& s = Substring(line, f + 1); + f = s.FindChar(' '); + if (f < 0) { + continue; + } + int64_t secondsstamp = Substring(s, 0, f).ToInteger64(&rv); + if (NS_FAILED(rv)) { + continue; + } + const nsACString& origin = Substring(s, f + 1); + + // Validate key + if (key.Length() != OriginKey::EncodedLength) { + continue; + } + nsCString dummy; + rv = Base64Decode(key, dummy); + if (NS_FAILED(rv)) { + continue; + } + mKeys.InsertOrUpdate(origin, MakeUnique<OriginKey>(key, secondsstamp)); + } + mPersistCount = mKeys.Count(); + return NS_OK; + } + + nsresult Write() { + nsCOMPtr<nsIFile> file = GetFile(); + if (NS_WARN_IF(!file)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = + NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString versionBuffer; + versionBuffer.AppendLiteral(ORIGINKEYS_VERSION); + versionBuffer.Append('\n'); + + uint32_t count; + rv = stream->Write(versionBuffer.Data(), versionBuffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (count != versionBuffer.Length()) { + return NS_ERROR_UNEXPECTED; + } + for (const auto& entry : mKeys) { + const nsACString& origin = entry.GetKey(); + OriginKey* originKey = entry.GetWeak(); + + if (!originKey->mSecondsStamp) { + continue; // don't write temporal ones + } + + nsCString originBuffer; + originBuffer.Append(originKey->mKey); + originBuffer.Append(' '); + originBuffer.AppendInt(originKey->mSecondsStamp); + originBuffer.Append(' '); + originBuffer.Append(origin); + originBuffer.Append('\n'); + + rv = stream->Write(originBuffer.Data(), originBuffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv)) || count != originBuffer.Length()) { + break; + } + } + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream); + MOZ_ASSERT(safeStream); + + rv = safeStream->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + nsresult Load() { + nsresult rv = Read(); + if (NS_WARN_IF(NS_FAILED(rv))) { + Delete(); + } + return rv; + } + + nsresult Save() { + nsresult rv = Write(); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_WARNING("Failed to write data for EnumerateDevices id-persistence."); + Delete(); + } + return rv; + } + + void Clear(int64_t aSinceWhen) { + OriginKeysTable::Clear(aSinceWhen); + Delete(); + Save(); + } + + nsresult Delete() { + nsCOMPtr<nsIFile> file = GetFile(); + if (NS_WARN_IF(!file)) { + return NS_ERROR_UNEXPECTED; + } + nsresult rv = file->Remove(false); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + return NS_OK; + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + void SetProfileDir(nsIFile* aProfileDir) { + MOZ_ASSERT(!NS_IsMainThread()); + bool first = !mProfileDir; + mProfileDir = aProfileDir; + // Load from disk when we first get a profileDir, but not subsequently. + if (first) { + Load(); + } + } + + private: + nsCOMPtr<nsIFile> mProfileDir; + }; + + private: + static OriginKeyStore* sOriginKeyStore; + + virtual ~OriginKeyStore() { + MOZ_ASSERT(NS_IsMainThread()); + sOriginKeyStore = nullptr; + LOG(("%s", __FUNCTION__)); + } + + public: + static RefPtr<OriginKeyStore> Get() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sOriginKeyStore) { + sOriginKeyStore = new OriginKeyStore(); + } + return RefPtr(sOriginKeyStore); + } + + // Only accessed on StreamTS threads + OriginKeysLoader mOriginKeys MOZ_GUARDED_BY(sOriginKeyStoreStsMutex); + OriginKeysTable mPrivateBrowsingOriginKeys + MOZ_GUARDED_BY(sOriginKeyStoreStsMutex); +}; +OriginKeyStore* OriginKeyStore::sOriginKeyStore = nullptr; + +template <class Super> +mozilla::ipc::IPCResult Parent<Super>::RecvGetPrincipalKey( + const ipc::PrincipalInfo& aPrincipalInfo, const bool& aPersist, + PMediaParent::GetPrincipalKeyResolver&& aResolve) { + MOZ_ASSERT(NS_IsMainThread()); + + // First, get profile dir. + + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPCResult(this, false); + } + + // Resolver has to be called in MainThread but the key is discovered + // in a different thread. We wrap the resolver around a MozPromise to make + // it more flexible and pass it to the new task. When this is done the + // resolver is resolved in MainThread. + + // Then over to stream-transport thread (a thread pool) to do the actual + // file io. Stash a promise to hold the answer and get an id for this request. + + nsCOMPtr<nsIEventTarget> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(sts); + auto taskQueue = TaskQueue::Create(sts.forget(), "RecvGetPrincipalKey"); + RefPtr<Parent<Super>> that(this); + + InvokeAsync( + taskQueue, __func__, + [this, that, profileDir, aPrincipalInfo, aPersist]() { + MOZ_ASSERT(!NS_IsMainThread()); + + StaticMutexAutoLock lock(sOriginKeyStoreStsMutex); + mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir); + + nsresult rv; + nsAutoCString result; + if (IsPrincipalInfoPrivate(aPrincipalInfo)) { + rv = mOriginKeyStore->mPrivateBrowsingOriginKeys.GetPrincipalKey( + aPrincipalInfo, result); + } else { + rv = mOriginKeyStore->mOriginKeys.GetPrincipalKey(aPrincipalInfo, + result, aPersist); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return PrincipalKeyPromise::CreateAndReject(rv, __func__); + } + return PrincipalKeyPromise::CreateAndResolve(result, __func__); + }) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolve](const PrincipalKeyPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsReject()) { + aResolve(""_ns); + } else { + aResolve(aValue.ResolveValue()); + } + }); + + return IPC_OK(); +} + +template <class Super> +mozilla::ipc::IPCResult Parent<Super>::RecvSanitizeOriginKeys( + const uint64_t& aSinceWhen, const bool& aOnlyPrivateBrowsing) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPCResult(this, false); + } + // Over to stream-transport thread (a thread pool) to do the file io. + + nsCOMPtr<nsIEventTarget> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(sts); + RefPtr<Parent<Super>> that(this); + + rv = sts->Dispatch( + NewRunnableFrom( + [this, that, profileDir, aSinceWhen, aOnlyPrivateBrowsing]() { + MOZ_ASSERT(!NS_IsMainThread()); + StaticMutexAutoLock lock(sOriginKeyStoreStsMutex); + mOriginKeyStore->mPrivateBrowsingOriginKeys.Clear(aSinceWhen); + if (!aOnlyPrivateBrowsing) { + mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir); + mOriginKeyStore->mOriginKeys.Clear(aSinceWhen); + } + return NS_OK; + }), + NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPCResult(this, false); + } + return IPC_OK(); +} + +template <class Super> +void Parent<Super>::ActorDestroy(ActorDestroyReason aWhy) { + // No more IPC from here + mDestroyed = true; + LOG(("%s", __FUNCTION__)); +} + +template <class Super> +Parent<Super>::Parent() + : mOriginKeyStore(OriginKeyStore::Get()), mDestroyed(false) { + LOG(("media::Parent: %p", this)); +} + +template <class Super> +Parent<Super>::~Parent() { + NS_ReleaseOnMainThread("Parent<Super>::mOriginKeyStore", + mOriginKeyStore.forget()); + LOG(("~media::Parent: %p", this)); +} + +PMediaParent* AllocPMediaParent() { + Parent<PMediaParent>* obj = new Parent<PMediaParent>(); + obj->AddRef(); + return obj; +} + +bool DeallocPMediaParent(media::PMediaParent* aActor) { + static_cast<Parent<PMediaParent>*>(aActor)->Release(); + return true; +} + +} // namespace mozilla::media + +// Instantiate templates to satisfy linker +template class mozilla::media::Parent<mozilla::media::NonE10s>; |