diff options
Diffstat (limited to 'dom/media/systemservices/MediaParent.cpp')
-rw-r--r-- | dom/media/systemservices/MediaParent.cpp | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/dom/media/systemservices/MediaParent.cpp b/dom/media/systemservices/MediaParent.cpp new file mode 100644 index 000000000..109a44a28 --- /dev/null +++ b/dom/media/systemservices/MediaParent.cpp @@ -0,0 +1,516 @@ +/* -*- 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 "nsAutoPtr.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 "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 "enumerate_devices.txt" +#define ORIGINKEYS_VERSION "1" + +namespace mozilla { +namespace media { + +static OriginKeyStore* sOriginKeyStore = nullptr; + +class OriginKeyStore : public nsISupports +{ + NS_DECL_THREADSAFE_ISUPPORTS + 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 + GetOriginKey(const nsACString& aOrigin, nsCString& aResult, bool aPersist = false) + { + OriginKey* key; + if (!mKeys.Get(aOrigin, &key)) { + nsCString salt; // Make a new one + nsresult rv = GenerateRandomName(salt, key->EncodedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + key = new OriginKey(salt); + mKeys.Put(aOrigin, key); + } + 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()) { + nsAutoPtr<OriginKey>& originKey = iter.Data(); + LOG((((originKey->mSecondsStamp >= since.mSecondsStamp)? + "%s: REMOVE %lld >= %lld" : + "%s: KEEP %lld < %lld"), + __FUNCTION__, originKey->mSecondsStamp, since.mSecondsStamp)); + + if (originKey->mSecondsStamp >= since.mSecondsStamp) { + iter.Remove(); + } + } + mPersistCount = 0; + } + + protected: + nsClassHashtable<nsCStringHashKey, OriginKey> mKeys; + size_t mPersistCount; + }; + + class OriginKeysLoader : public OriginKeysTable + { + public: + OriginKeysLoader() {} + + nsresult + GetOriginKey(const nsACString& aOrigin, nsCString& aResult, bool aPersist) + { + auto before = mPersistCount; + OriginKeysTable::GetOriginKey(aOrigin, aResult, aPersist); + 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(NS_LITERAL_STRING(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 = nsCString(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.Put(origin, new 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 (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) { + const nsACString& origin = iter.Key(); + OriginKey* originKey = iter.UserData(); + + 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: + virtual ~OriginKeyStore() + { + sOriginKeyStore = nullptr; + LOG((__FUNCTION__)); + } + +public: + static OriginKeyStore* Get() + { + MOZ_ASSERT(NS_IsMainThread()); + if (!sOriginKeyStore) { + sOriginKeyStore = new OriginKeyStore(); + } + return sOriginKeyStore; + } + + // Only accessed on StreamTS thread + OriginKeysLoader mOriginKeys; + OriginKeysTable mPrivateBrowsingOriginKeys; +}; + +NS_IMPL_ISUPPORTS0(OriginKeyStore) + +bool NonE10s::SendGetOriginKeyResponse(const uint32_t& aRequestId, + nsCString aKey) { + MediaManager* mgr = MediaManager::GetIfExists(); + if (!mgr) { + return false; + } + RefPtr<Pledge<nsCString>> pledge = mgr->mGetOriginKeyPledges.Remove(aRequestId); + if (pledge) { + pledge->Resolve(aKey); + } + return true; +} + +template<class Super> bool +Parent<Super>::RecvGetOriginKey(const uint32_t& aRequestId, + const nsCString& aOrigin, + const bool& aPrivateBrowsing, + const bool& aPersist) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // First, get profile dir. + + 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 false; + } + + // Then over to stream-transport thread to do the actual file io. + // Stash a pledge to hold the answer and get an id for this request. + + RefPtr<Pledge<nsCString>> p = new Pledge<nsCString>(); + uint32_t id = mOutstandingPledges.Append(*p); + + nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(sts); + RefPtr<Parent<Super>> that(this); + + rv = sts->Dispatch(NewRunnableFrom([this, that, id, profileDir, aOrigin, + aPrivateBrowsing, aPersist]() -> nsresult { + MOZ_ASSERT(!NS_IsMainThread()); + mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir); + nsCString result; + if (aPrivateBrowsing) { + mOriginKeyStore->mPrivateBrowsingOriginKeys.GetOriginKey(aOrigin, result); + } else { + mOriginKeyStore->mOriginKeys.GetOriginKey(aOrigin, result, aPersist); + } + + // Pass result back to main thread. + nsresult rv; + rv = NS_DispatchToMainThread(NewRunnableFrom([this, that, id, + result]() -> nsresult { + if (mDestroyed) { + return NS_OK; + } + RefPtr<Pledge<nsCString>> p = mOutstandingPledges.Remove(id); + if (!p) { + return NS_ERROR_UNEXPECTED; + } + p->Resolve(result); + return NS_OK; + }), NS_DISPATCH_NORMAL); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + }), NS_DISPATCH_NORMAL); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + p->Then([this, that, aRequestId](const nsCString& aKey) mutable { + if (mDestroyed) { + return NS_OK; + } + Unused << this->SendGetOriginKeyResponse(aRequestId, aKey); + return NS_OK; + }); + return true; +} + +template<class Super> bool +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 false; + } + // Over to stream-transport thread to do the file io. + + nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(sts); + RefPtr<OriginKeyStore> store(mOriginKeyStore); + + rv = sts->Dispatch(NewRunnableFrom([profileDir, store, aSinceWhen, + aOnlyPrivateBrowsing]() -> nsresult { + MOZ_ASSERT(!NS_IsMainThread()); + store->mPrivateBrowsingOriginKeys.Clear(aSinceWhen); + if (!aOnlyPrivateBrowsing) { + store->mOriginKeys.SetProfileDir(profileDir); + store->mOriginKeys.Clear(aSinceWhen); + } + return NS_OK; + }), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + return true; +} + +template<class Super> void +Parent<Super>::ActorDestroy(ActorDestroyReason aWhy) +{ + // No more IPC from here + mDestroyed = true; + LOG((__FUNCTION__)); +} + +template<class Super> +Parent<Super>::Parent() + : mOriginKeyStore(OriginKeyStore::Get()) + , mDestroyed(false) +{ + LOG(("media::Parent: %p", this)); +} + +template<class Super> +Parent<Super>::~Parent() +{ + 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 media +} // namespace mozilla + +// Instantiate templates to satisfy linker +template class mozilla::media::Parent<mozilla::media::NonE10s>; |