Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set sw=2 ts=8 et ft=cpp : */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "MediaParent.h"
8 :
9 : #include "mozilla/Base64.h"
10 : #include <mozilla/StaticMutex.h>
11 :
12 : #include "MediaUtils.h"
13 : #include "MediaEngine.h"
14 : #include "VideoUtils.h"
15 : #include "nsAutoPtr.h"
16 : #include "nsThreadUtils.h"
17 : #include "nsNetCID.h"
18 : #include "nsNetUtil.h"
19 : #include "nsIInputStream.h"
20 : #include "nsILineInputStream.h"
21 : #include "nsIOutputStream.h"
22 : #include "nsISafeOutputStream.h"
23 : #include "nsAppDirectoryServiceDefs.h"
24 : #include "nsISupportsImpl.h"
25 : #include "mozilla/Logging.h"
26 :
27 : #undef LOG
28 : mozilla::LazyLogModule gMediaParentLog("MediaParent");
29 : #define LOG(args) MOZ_LOG(gMediaParentLog, mozilla::LogLevel::Debug, args)
30 :
31 : // A file in the profile dir is used to persist mOriginKeys used to anonymize
32 : // deviceIds to be unique per origin, to avoid them being supercookies.
33 :
34 : #define ORIGINKEYS_FILE "enumerate_devices.txt"
35 : #define ORIGINKEYS_VERSION "1"
36 :
37 : namespace mozilla {
38 : namespace media {
39 :
40 : static OriginKeyStore* sOriginKeyStore = nullptr;
41 :
42 0 : class OriginKeyStore : public nsISupports
43 : {
44 : NS_DECL_THREADSAFE_ISUPPORTS
45 0 : class OriginKey
46 : {
47 : public:
48 : static const size_t DecodedLength = 18;
49 : static const size_t EncodedLength = DecodedLength * 4 / 3;
50 :
51 0 : explicit OriginKey(const nsACString& aKey, int64_t aSecondsStamp = 0) // 0 = temporal
52 0 : : mKey(aKey)
53 0 : , mSecondsStamp(aSecondsStamp) {}
54 :
55 : nsCString mKey; // Base64 encoded.
56 : int64_t mSecondsStamp;
57 : };
58 :
59 0 : class OriginKeysTable
60 : {
61 : public:
62 0 : OriginKeysTable() : mPersistCount(0) {}
63 :
64 : nsresult
65 0 : GetPrincipalKey(const ipc::PrincipalInfo& aPrincipalInfo,
66 : nsCString& aResult, bool aPersist = false)
67 : {
68 0 : nsAutoCString principalString;
69 0 : PrincipalInfoToString(aPrincipalInfo, principalString);
70 :
71 : OriginKey* key;
72 0 : if (!mKeys.Get(principalString, &key)) {
73 0 : nsCString salt; // Make a new one
74 0 : nsresult rv = GenerateRandomName(salt, key->EncodedLength);
75 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
76 0 : return rv;
77 : }
78 0 : key = new OriginKey(salt);
79 0 : mKeys.Put(principalString, key);
80 : }
81 0 : if (aPersist && !key->mSecondsStamp) {
82 0 : key->mSecondsStamp = PR_Now() / PR_USEC_PER_SEC;
83 0 : mPersistCount++;
84 : }
85 0 : aResult = key->mKey;
86 0 : return NS_OK;
87 : }
88 :
89 0 : void Clear(int64_t aSinceWhen)
90 : {
91 : // Avoid int64_t* <-> void* casting offset
92 0 : OriginKey since(nsCString(), aSinceWhen / PR_USEC_PER_SEC);
93 0 : for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) {
94 0 : nsAutoPtr<OriginKey>& originKey = iter.Data();
95 0 : LOG((((originKey->mSecondsStamp >= since.mSecondsStamp)?
96 : "%s: REMOVE %" PRId64 " >= %" PRId64 :
97 : "%s: KEEP %" PRId64 " < %" PRId64),
98 : __FUNCTION__, originKey->mSecondsStamp, since.mSecondsStamp));
99 :
100 0 : if (originKey->mSecondsStamp >= since.mSecondsStamp) {
101 0 : iter.Remove();
102 : }
103 : }
104 0 : mPersistCount = 0;
105 0 : }
106 :
107 : private:
108 : void
109 0 : PrincipalInfoToString(const ipc::PrincipalInfo& aPrincipalInfo,
110 : nsACString& aString)
111 : {
112 0 : switch (aPrincipalInfo.type()) {
113 : case ipc::PrincipalInfo::TSystemPrincipalInfo:
114 0 : aString.Assign("[System Principal]");
115 0 : return;
116 :
117 : case ipc::PrincipalInfo::TNullPrincipalInfo: {
118 : const ipc::NullPrincipalInfo& info =
119 0 : aPrincipalInfo.get_NullPrincipalInfo();
120 0 : aString.Assign(info.spec());
121 0 : return;
122 : }
123 :
124 : case ipc::PrincipalInfo::TContentPrincipalInfo: {
125 : const ipc::ContentPrincipalInfo& info =
126 0 : aPrincipalInfo.get_ContentPrincipalInfo();
127 0 : aString.Assign(info.originNoSuffix());
128 :
129 0 : nsAutoCString suffix;
130 0 : info.attrs().CreateSuffix(suffix);
131 0 : aString.Append(suffix);
132 0 : return;
133 : }
134 :
135 : case ipc::PrincipalInfo::TExpandedPrincipalInfo: {
136 : const ipc::ExpandedPrincipalInfo& info =
137 0 : aPrincipalInfo.get_ExpandedPrincipalInfo();
138 :
139 0 : aString.Assign("[Expanded Principal [");
140 :
141 0 : for (uint32_t i = 0; i < info.whitelist().Length(); i++) {
142 0 : nsAutoCString str;
143 0 : PrincipalInfoToString(info.whitelist()[i], str);
144 :
145 0 : if (i != 0) {
146 0 : aString.Append(", ");
147 : }
148 :
149 0 : aString.Append(str);
150 : }
151 :
152 0 : aString.Append("]]");
153 0 : return;
154 : }
155 :
156 : default:
157 0 : MOZ_CRASH("Unknown PrincipalInfo type!");
158 : }
159 : }
160 :
161 : protected:
162 : nsClassHashtable<nsCStringHashKey, OriginKey> mKeys;
163 : size_t mPersistCount;
164 : };
165 :
166 0 : class OriginKeysLoader : public OriginKeysTable
167 : {
168 : public:
169 0 : OriginKeysLoader() {}
170 :
171 : nsresult
172 0 : GetPrincipalKey(const ipc::PrincipalInfo& aPrincipalInfo,
173 : nsCString& aResult, bool aPersist = false)
174 : {
175 0 : auto before = mPersistCount;
176 0 : nsresult rv = OriginKeysTable::GetPrincipalKey(aPrincipalInfo, aResult,
177 0 : aPersist);
178 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
179 0 : return rv;
180 : }
181 :
182 0 : if (mPersistCount != before) {
183 0 : Save();
184 : }
185 0 : return NS_OK;
186 : }
187 :
188 : already_AddRefed<nsIFile>
189 0 : GetFile()
190 : {
191 0 : MOZ_ASSERT(mProfileDir);
192 0 : nsCOMPtr<nsIFile> file;
193 0 : nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
194 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
195 0 : return nullptr;
196 : }
197 0 : file->Append(NS_LITERAL_STRING(ORIGINKEYS_FILE));
198 0 : return file.forget();
199 : }
200 :
201 : // Format of file is key secondsstamp origin (first line is version #):
202 : //
203 : // 1
204 : // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424733961 http://fiddle.jshell.net
205 : // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424734841 http://mozilla.github.io
206 : // etc.
207 :
208 0 : nsresult Read()
209 : {
210 0 : nsCOMPtr<nsIFile> file = GetFile();
211 0 : if (NS_WARN_IF(!file)) {
212 0 : return NS_ERROR_UNEXPECTED;
213 : }
214 : bool exists;
215 0 : nsresult rv = file->Exists(&exists);
216 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
217 0 : return rv;
218 : }
219 0 : if (!exists) {
220 0 : return NS_OK;
221 : }
222 :
223 0 : nsCOMPtr<nsIInputStream> stream;
224 0 : rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
225 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
226 0 : return rv;
227 : }
228 0 : nsCOMPtr<nsILineInputStream> i = do_QueryInterface(stream);
229 0 : MOZ_ASSERT(i);
230 0 : MOZ_ASSERT(!mPersistCount);
231 :
232 0 : nsCString line;
233 : bool hasMoreLines;
234 0 : rv = i->ReadLine(line, &hasMoreLines);
235 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
236 0 : return rv;
237 : }
238 0 : if (!line.EqualsLiteral(ORIGINKEYS_VERSION)) {
239 : // If version on disk is newer than we can understand then ignore it.
240 0 : return NS_OK;
241 : }
242 :
243 0 : while (hasMoreLines) {
244 0 : rv = i->ReadLine(line, &hasMoreLines);
245 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
246 0 : return rv;
247 : }
248 : // Read key secondsstamp origin.
249 : // Ignore any lines that don't fit format in the comment above exactly.
250 0 : int32_t f = line.FindChar(' ');
251 0 : if (f < 0) {
252 0 : continue;
253 : }
254 0 : const nsACString& key = Substring(line, 0, f);
255 0 : const nsACString& s = Substring(line, f+1);
256 0 : f = s.FindChar(' ');
257 0 : if (f < 0) {
258 0 : continue;
259 : }
260 0 : int64_t secondsstamp = nsCString(Substring(s, 0, f)).ToInteger64(&rv);
261 0 : if (NS_FAILED(rv)) {
262 0 : continue;
263 : }
264 0 : const nsACString& origin = Substring(s, f+1);
265 :
266 : // Validate key
267 0 : if (key.Length() != OriginKey::EncodedLength) {
268 0 : continue;
269 : }
270 0 : nsCString dummy;
271 0 : rv = Base64Decode(key, dummy);
272 0 : if (NS_FAILED(rv)) {
273 0 : continue;
274 : }
275 0 : mKeys.Put(origin, new OriginKey(key, secondsstamp));
276 : }
277 0 : mPersistCount = mKeys.Count();
278 0 : return NS_OK;
279 : }
280 :
281 : nsresult
282 0 : Write()
283 : {
284 0 : nsCOMPtr<nsIFile> file = GetFile();
285 0 : if (NS_WARN_IF(!file)) {
286 0 : return NS_ERROR_UNEXPECTED;
287 : }
288 :
289 0 : nsCOMPtr<nsIOutputStream> stream;
290 0 : nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
291 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
292 0 : return rv;
293 : }
294 :
295 0 : nsAutoCString versionBuffer;
296 0 : versionBuffer.AppendLiteral(ORIGINKEYS_VERSION);
297 0 : versionBuffer.Append('\n');
298 :
299 : uint32_t count;
300 0 : rv = stream->Write(versionBuffer.Data(), versionBuffer.Length(), &count);
301 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
302 0 : return rv;
303 : }
304 0 : if (count != versionBuffer.Length()) {
305 0 : return NS_ERROR_UNEXPECTED;
306 : }
307 0 : for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) {
308 0 : const nsACString& origin = iter.Key();
309 0 : OriginKey* originKey = iter.UserData();
310 :
311 0 : if (!originKey->mSecondsStamp) {
312 0 : continue; // don't write temporal ones
313 : }
314 :
315 0 : nsCString originBuffer;
316 0 : originBuffer.Append(originKey->mKey);
317 0 : originBuffer.Append(' ');
318 0 : originBuffer.AppendInt(originKey->mSecondsStamp);
319 0 : originBuffer.Append(' ');
320 0 : originBuffer.Append(origin);
321 0 : originBuffer.Append('\n');
322 :
323 0 : rv = stream->Write(originBuffer.Data(), originBuffer.Length(), &count);
324 0 : if (NS_WARN_IF(NS_FAILED(rv)) || count != originBuffer.Length()) {
325 0 : break;
326 : }
327 : }
328 :
329 0 : nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
330 0 : MOZ_ASSERT(safeStream);
331 :
332 0 : rv = safeStream->Finish();
333 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
334 0 : return rv;
335 : }
336 0 : return NS_OK;
337 : }
338 :
339 0 : nsresult Load()
340 : {
341 0 : nsresult rv = Read();
342 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
343 0 : Delete();
344 : }
345 0 : return rv;
346 : }
347 :
348 0 : nsresult Save()
349 : {
350 0 : nsresult rv = Write();
351 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
352 0 : NS_WARNING("Failed to write data for EnumerateDevices id-persistence.");
353 0 : Delete();
354 : }
355 0 : return rv;
356 : }
357 :
358 0 : void Clear(int64_t aSinceWhen)
359 : {
360 0 : OriginKeysTable::Clear(aSinceWhen);
361 0 : Delete();
362 0 : Save();
363 0 : }
364 :
365 0 : nsresult Delete()
366 : {
367 0 : nsCOMPtr<nsIFile> file = GetFile();
368 0 : if (NS_WARN_IF(!file)) {
369 0 : return NS_ERROR_UNEXPECTED;
370 : }
371 0 : nsresult rv = file->Remove(false);
372 0 : if (rv == NS_ERROR_FILE_NOT_FOUND) {
373 0 : return NS_OK;
374 : }
375 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
376 0 : return rv;
377 : }
378 0 : return NS_OK;
379 : }
380 :
381 : void
382 0 : SetProfileDir(nsIFile* aProfileDir)
383 : {
384 0 : MOZ_ASSERT(!NS_IsMainThread());
385 0 : bool first = !mProfileDir;
386 0 : mProfileDir = aProfileDir;
387 : // Load from disk when we first get a profileDir, but not subsequently.
388 0 : if (first) {
389 0 : Load();
390 : }
391 0 : }
392 : private:
393 : nsCOMPtr<nsIFile> mProfileDir;
394 : };
395 :
396 : private:
397 0 : virtual ~OriginKeyStore()
398 0 : {
399 0 : sOriginKeyStore = nullptr;
400 0 : LOG((__FUNCTION__));
401 0 : }
402 :
403 : public:
404 0 : static OriginKeyStore* Get()
405 : {
406 0 : MOZ_ASSERT(NS_IsMainThread());
407 0 : if (!sOriginKeyStore) {
408 0 : sOriginKeyStore = new OriginKeyStore();
409 : }
410 0 : return sOriginKeyStore;
411 : }
412 :
413 : // Only accessed on StreamTS thread
414 : OriginKeysLoader mOriginKeys;
415 : OriginKeysTable mPrivateBrowsingOriginKeys;
416 : };
417 :
418 0 : NS_IMPL_ISUPPORTS0(OriginKeyStore)
419 :
420 0 : bool NonE10s::SendGetPrincipalKeyResponse(const uint32_t& aRequestId,
421 : nsCString aKey) {
422 0 : MediaManager* mgr = MediaManager::GetIfExists();
423 0 : if (!mgr) {
424 0 : return false;
425 : }
426 0 : RefPtr<Pledge<nsCString>> pledge = mgr->mGetPrincipalKeyPledges.Remove(aRequestId);
427 0 : if (pledge) {
428 0 : pledge->Resolve(aKey);
429 : }
430 0 : return true;
431 : }
432 :
433 : template<class Super> mozilla::ipc::IPCResult
434 0 : Parent<Super>::RecvGetPrincipalKey(const uint32_t& aRequestId,
435 : const ipc::PrincipalInfo& aPrincipalInfo,
436 : const bool& aPersist)
437 : {
438 0 : MOZ_ASSERT(NS_IsMainThread());
439 :
440 : // First, get profile dir.
441 :
442 0 : MOZ_ASSERT(NS_IsMainThread());
443 0 : nsCOMPtr<nsIFile> profileDir;
444 0 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
445 0 : getter_AddRefs(profileDir));
446 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
447 0 : return IPCResult(this, false);
448 : }
449 :
450 : // Then over to stream-transport thread to do the actual file io.
451 : // Stash a pledge to hold the answer and get an id for this request.
452 :
453 0 : RefPtr<Pledge<nsCString>> p = new Pledge<nsCString>();
454 0 : uint32_t id = mOutstandingPledges.Append(*p);
455 :
456 0 : nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
457 0 : MOZ_ASSERT(sts);
458 0 : RefPtr<Parent<Super>> that(this);
459 :
460 0 : rv = sts->Dispatch(NewRunnableFrom([this, that, id, profileDir,
461 0 : aPrincipalInfo, aPersist]() -> nsresult {
462 0 : MOZ_ASSERT(!NS_IsMainThread());
463 0 : mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir);
464 :
465 : nsresult rv;
466 0 : nsAutoCString result;
467 0 : if (IsPincipalInfoPrivate(aPrincipalInfo)) {
468 0 : rv = mOriginKeyStore->mPrivateBrowsingOriginKeys.GetPrincipalKey(aPrincipalInfo, result);
469 : } else {
470 0 : rv = mOriginKeyStore->mOriginKeys.GetPrincipalKey(aPrincipalInfo, result, aPersist);
471 : }
472 :
473 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
474 0 : return rv;
475 : }
476 :
477 : // Pass result back to main thread.
478 0 : rv = NS_DispatchToMainThread(NewRunnableFrom([this, that, id,
479 0 : result]() -> nsresult {
480 0 : if (mDestroyed) {
481 0 : return NS_OK;
482 : }
483 0 : RefPtr<Pledge<nsCString>> p = mOutstandingPledges.Remove(id);
484 0 : if (!p) {
485 0 : return NS_ERROR_UNEXPECTED;
486 : }
487 0 : p->Resolve(result);
488 0 : return NS_OK;
489 : }), NS_DISPATCH_NORMAL);
490 :
491 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
492 0 : return rv;
493 : }
494 0 : return NS_OK;
495 : }), NS_DISPATCH_NORMAL);
496 :
497 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
498 0 : return IPCResult(this, false);
499 : }
500 0 : p->Then([this, that, aRequestId](const nsCString& aKey) mutable {
501 0 : if (mDestroyed) {
502 0 : return NS_OK;
503 : }
504 0 : Unused << this->SendGetPrincipalKeyResponse(aRequestId, aKey);
505 0 : return NS_OK;
506 : });
507 0 : return IPC_OK();
508 : }
509 :
510 : template<class Super> mozilla::ipc::IPCResult
511 0 : Parent<Super>::RecvSanitizeOriginKeys(const uint64_t& aSinceWhen,
512 : const bool& aOnlyPrivateBrowsing)
513 : {
514 0 : MOZ_ASSERT(NS_IsMainThread());
515 0 : nsCOMPtr<nsIFile> profileDir;
516 0 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
517 0 : getter_AddRefs(profileDir));
518 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
519 0 : return IPCResult(this, false);
520 : }
521 : // Over to stream-transport thread to do the file io.
522 :
523 0 : nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
524 0 : MOZ_ASSERT(sts);
525 0 : RefPtr<OriginKeyStore> store(mOriginKeyStore);
526 :
527 0 : rv = sts->Dispatch(NewRunnableFrom([profileDir, store, aSinceWhen,
528 0 : aOnlyPrivateBrowsing]() -> nsresult {
529 0 : MOZ_ASSERT(!NS_IsMainThread());
530 0 : store->mPrivateBrowsingOriginKeys.Clear(aSinceWhen);
531 0 : if (!aOnlyPrivateBrowsing) {
532 0 : store->mOriginKeys.SetProfileDir(profileDir);
533 0 : store->mOriginKeys.Clear(aSinceWhen);
534 : }
535 0 : return NS_OK;
536 : }), NS_DISPATCH_NORMAL);
537 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
538 0 : return IPCResult(this, false);
539 : }
540 0 : return IPC_OK();
541 : }
542 :
543 : template<class Super> void
544 0 : Parent<Super>::ActorDestroy(ActorDestroyReason aWhy)
545 : {
546 : // No more IPC from here
547 0 : mDestroyed = true;
548 0 : LOG((__FUNCTION__));
549 0 : }
550 :
551 : template<class Super>
552 0 : Parent<Super>::Parent()
553 : : mOriginKeyStore(OriginKeyStore::Get())
554 0 : , mDestroyed(false)
555 : {
556 0 : LOG(("media::Parent: %p", this));
557 0 : }
558 :
559 : template<class Super>
560 0 : Parent<Super>::~Parent()
561 : {
562 0 : LOG(("~media::Parent: %p", this));
563 0 : }
564 :
565 : PMediaParent*
566 0 : AllocPMediaParent()
567 : {
568 0 : Parent<PMediaParent>* obj = new Parent<PMediaParent>();
569 0 : obj->AddRef();
570 0 : return obj;
571 : }
572 :
573 : bool
574 0 : DeallocPMediaParent(media::PMediaParent *aActor)
575 : {
576 0 : static_cast<Parent<PMediaParent>*>(aActor)->Release();
577 0 : return true;
578 : }
579 :
580 : } // namespace media
581 : } // namespace mozilla
582 :
583 : // Instantiate templates to satisfy linker
584 : template class mozilla::media::Parent<mozilla::media::NonE10s>;
|