384 lines
13 KiB
C++
384 lines
13 KiB
C++
/* -*- 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/. */
|
|
|
|
#ifndef mozilla_ContentBlockingLog_h
|
|
#define mozilla_ContentBlockingLog_h
|
|
|
|
#include "mozilla/ContentBlockingNotifier.h"
|
|
#include "mozilla/JSONStringWriteFuncs.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/StaticPrefs_browser.h"
|
|
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsTArray.h"
|
|
#include "nsWindowSizes.h"
|
|
|
|
class nsIPrincipal;
|
|
|
|
namespace mozilla {
|
|
|
|
class nsRFPService;
|
|
|
|
class ContentBlockingLog final {
|
|
typedef ContentBlockingNotifier::StorageAccessPermissionGrantedReason
|
|
StorageAccessPermissionGrantedReason;
|
|
|
|
protected:
|
|
struct LogEntry {
|
|
uint32_t mType;
|
|
uint32_t mRepeatCount;
|
|
bool mBlocked;
|
|
Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>
|
|
mReason;
|
|
nsTArray<nsCString> mTrackingFullHashes;
|
|
Maybe<ContentBlockingNotifier::CanvasFingerprinter> mCanvasFingerprinter;
|
|
Maybe<bool> mCanvasFingerprinterKnownText;
|
|
};
|
|
|
|
struct OriginDataEntry {
|
|
OriginDataEntry()
|
|
: mHasLevel1TrackingContentLoaded(false),
|
|
mHasLevel2TrackingContentLoaded(false),
|
|
mHasSuspiciousFingerprintingActivity(false) {}
|
|
|
|
bool mHasLevel1TrackingContentLoaded;
|
|
bool mHasLevel2TrackingContentLoaded;
|
|
bool mHasSuspiciousFingerprintingActivity;
|
|
Maybe<bool> mHasCookiesLoaded;
|
|
Maybe<bool> mHasTrackerCookiesLoaded;
|
|
Maybe<bool> mHasSocialTrackerCookiesLoaded;
|
|
nsTArray<LogEntry> mLogs;
|
|
};
|
|
|
|
struct OriginEntry {
|
|
OriginEntry() { mData = MakeUnique<OriginDataEntry>(); }
|
|
|
|
nsCString mOrigin;
|
|
UniquePtr<OriginDataEntry> mData;
|
|
};
|
|
|
|
friend class nsRFPService;
|
|
|
|
typedef nsTArray<OriginEntry> OriginDataTable;
|
|
|
|
struct Comparator {
|
|
public:
|
|
bool Equals(const OriginDataTable::value_type& aLeft,
|
|
const OriginDataTable::value_type& aRight) const {
|
|
return aLeft.mOrigin.Equals(aRight.mOrigin);
|
|
}
|
|
|
|
bool Equals(const OriginDataTable::value_type& aLeft,
|
|
const nsACString& aRight) const {
|
|
return aLeft.mOrigin.Equals(aRight);
|
|
}
|
|
};
|
|
|
|
public:
|
|
static const nsLiteralCString kDummyOriginHash;
|
|
|
|
ContentBlockingLog() = default;
|
|
~ContentBlockingLog() = default;
|
|
|
|
// Record the log in the parent process. This should be called only in the
|
|
// parent process and will replace the RecordLog below after we remove the
|
|
// ContentBlockingLog from content processes.
|
|
Maybe<uint32_t> RecordLogParent(
|
|
const nsACString& aOrigin, uint32_t aType, bool aBlocked,
|
|
const Maybe<
|
|
ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
|
|
aReason = Nothing(),
|
|
const nsTArray<nsCString>& aTrackingFullHashes = nsTArray<nsCString>(),
|
|
const Maybe<ContentBlockingNotifier::CanvasFingerprinter>&
|
|
aCanvasFingerprinter = Nothing(),
|
|
const Maybe<bool> aCanvasFingerprinterKnownText = Nothing());
|
|
|
|
void RecordLog(
|
|
const nsACString& aOrigin, uint32_t aType, bool aBlocked,
|
|
const Maybe<
|
|
ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
|
|
aReason,
|
|
const nsTArray<nsCString>& aTrackingFullHashes) {
|
|
RecordLogInternal(aOrigin, aType, aBlocked, aReason, aTrackingFullHashes);
|
|
}
|
|
|
|
void ReportLog();
|
|
void ReportCanvasFingerprintingLog(nsIPrincipal* aFirstPartyPrincipal,
|
|
bool aShouldReport);
|
|
void ReportFontFingerprintingLog(nsIPrincipal* aFirstPartyPrincipal);
|
|
void ReportEmailTrackingLog(nsIPrincipal* aFirstPartyPrincipal);
|
|
|
|
nsAutoCString Stringify() {
|
|
nsAutoCString buffer;
|
|
|
|
JSONStringRefWriteFunc js(buffer);
|
|
JSONWriter w(js);
|
|
w.Start();
|
|
|
|
for (const OriginEntry& entry : mLog) {
|
|
if (!entry.mData) {
|
|
continue;
|
|
}
|
|
|
|
w.StartArrayProperty(entry.mOrigin, w.SingleLineStyle);
|
|
|
|
StringifyCustomFields(entry, w);
|
|
for (const LogEntry& item : entry.mData->mLogs) {
|
|
w.StartArrayElement(w.SingleLineStyle);
|
|
{
|
|
w.IntElement(item.mType);
|
|
w.BoolElement(item.mBlocked);
|
|
w.IntElement(item.mRepeatCount);
|
|
if (item.mReason.isSome()) {
|
|
w.IntElement(item.mReason.value());
|
|
}
|
|
}
|
|
w.EndArray();
|
|
}
|
|
w.EndArray();
|
|
}
|
|
|
|
w.End();
|
|
|
|
return buffer;
|
|
}
|
|
|
|
bool HasBlockedAnyOfType(uint32_t aType) const {
|
|
// Note: nothing inside this loop should return false, the goal for the
|
|
// loop is to scan the log to see if we find a matching entry, and if so
|
|
// we would return true, otherwise in the end of the function outside of
|
|
// the loop we take the common `return false;` statement.
|
|
for (const OriginEntry& entry : mLog) {
|
|
if (!entry.mData) {
|
|
continue;
|
|
}
|
|
|
|
if (aType ==
|
|
nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
|
|
if (entry.mData->mHasLevel1TrackingContentLoaded) {
|
|
return true;
|
|
}
|
|
} else if (aType == nsIWebProgressListener::
|
|
STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
|
|
if (entry.mData->mHasLevel2TrackingContentLoaded) {
|
|
return true;
|
|
}
|
|
} else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
|
|
if (entry.mData->mHasCookiesLoaded.isSome() &&
|
|
entry.mData->mHasCookiesLoaded.value()) {
|
|
return true;
|
|
}
|
|
} else if (aType ==
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER) {
|
|
if (entry.mData->mHasTrackerCookiesLoaded.isSome() &&
|
|
entry.mData->mHasTrackerCookiesLoaded.value()) {
|
|
return true;
|
|
}
|
|
} else if (aType ==
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER) {
|
|
if (entry.mData->mHasSocialTrackerCookiesLoaded.isSome() &&
|
|
entry.mData->mHasSocialTrackerCookiesLoaded.value()) {
|
|
return true;
|
|
}
|
|
} else {
|
|
for (const auto& item : entry.mData->mLogs) {
|
|
if (((item.mType & aType) != 0) && item.mBlocked) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
|
|
aSizes.mDOMSizes.mDOMOtherSize +=
|
|
mLog.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
|
|
|
|
// Now add the sizes of each origin log queue.
|
|
for (const OriginEntry& entry : mLog) {
|
|
if (entry.mData) {
|
|
aSizes.mDOMSizes.mDOMOtherSize +=
|
|
aSizes.mState.mMallocSizeOf(entry.mData.get()) +
|
|
entry.mData->mLogs.ShallowSizeOfExcludingThis(
|
|
aSizes.mState.mMallocSizeOf);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t GetContentBlockingEventsInLog() {
|
|
uint32_t events = 0;
|
|
|
|
// We iterate the whole log to produce the overview of blocked events.
|
|
for (const OriginEntry& entry : mLog) {
|
|
if (!entry.mData) {
|
|
continue;
|
|
}
|
|
|
|
if (entry.mData->mHasLevel1TrackingContentLoaded) {
|
|
events |= nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT;
|
|
}
|
|
|
|
if (entry.mData->mHasLevel2TrackingContentLoaded) {
|
|
events |= nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT;
|
|
}
|
|
|
|
if (entry.mData->mHasSuspiciousFingerprintingActivity) {
|
|
events |=
|
|
nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING;
|
|
}
|
|
|
|
if (entry.mData->mHasCookiesLoaded.isSome() &&
|
|
entry.mData->mHasCookiesLoaded.value()) {
|
|
events |= nsIWebProgressListener::STATE_COOKIES_LOADED;
|
|
}
|
|
|
|
if (entry.mData->mHasTrackerCookiesLoaded.isSome() &&
|
|
entry.mData->mHasTrackerCookiesLoaded.value()) {
|
|
events |= nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER;
|
|
}
|
|
|
|
if (entry.mData->mHasSocialTrackerCookiesLoaded.isSome() &&
|
|
entry.mData->mHasSocialTrackerCookiesLoaded.value()) {
|
|
events |= nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER;
|
|
}
|
|
|
|
for (const auto& item : entry.mData->mLogs) {
|
|
if (item.mBlocked ||
|
|
item.mType &
|
|
nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT) {
|
|
events |= item.mType;
|
|
}
|
|
}
|
|
}
|
|
|
|
return events;
|
|
}
|
|
|
|
private:
|
|
OriginEntry* RecordLogInternal(
|
|
const nsACString& aOrigin, uint32_t aType, bool aBlocked,
|
|
const Maybe<
|
|
ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
|
|
aReason = Nothing(),
|
|
const nsTArray<nsCString>& aTrackingFullHashes = nsTArray<nsCString>(),
|
|
const Maybe<ContentBlockingNotifier::CanvasFingerprinter>&
|
|
aCanvasFingerprinter = Nothing(),
|
|
const Maybe<bool> aCanvasFingerprinterKnownText = Nothing());
|
|
|
|
bool RecordLogEntryInCustomField(uint32_t aType, OriginEntry& aEntry,
|
|
bool aBlocked) {
|
|
if (aType ==
|
|
nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
|
|
aEntry.mData->mHasLevel1TrackingContentLoaded = aBlocked;
|
|
return true;
|
|
}
|
|
if (aType ==
|
|
nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
|
|
aEntry.mData->mHasLevel2TrackingContentLoaded = aBlocked;
|
|
return true;
|
|
}
|
|
if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
|
|
if (aEntry.mData->mHasCookiesLoaded.isSome()) {
|
|
aEntry.mData->mHasCookiesLoaded.ref() = aBlocked;
|
|
} else {
|
|
aEntry.mData->mHasCookiesLoaded.emplace(aBlocked);
|
|
}
|
|
return true;
|
|
}
|
|
if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER) {
|
|
if (aEntry.mData->mHasTrackerCookiesLoaded.isSome()) {
|
|
aEntry.mData->mHasTrackerCookiesLoaded.ref() = aBlocked;
|
|
} else {
|
|
aEntry.mData->mHasTrackerCookiesLoaded.emplace(aBlocked);
|
|
}
|
|
return true;
|
|
}
|
|
if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER) {
|
|
if (aEntry.mData->mHasSocialTrackerCookiesLoaded.isSome()) {
|
|
aEntry.mData->mHasSocialTrackerCookiesLoaded.ref() = aBlocked;
|
|
} else {
|
|
aEntry.mData->mHasSocialTrackerCookiesLoaded.emplace(aBlocked);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void StringifyCustomFields(const OriginEntry& aEntry, JSONWriter& aWriter) {
|
|
if (aEntry.mData->mHasLevel1TrackingContentLoaded) {
|
|
aWriter.StartArrayElement(aWriter.SingleLineStyle);
|
|
{
|
|
aWriter.IntElement(
|
|
nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT);
|
|
aWriter.BoolElement(true); // blocked
|
|
aWriter.IntElement(1); // repeat count
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
if (aEntry.mData->mHasLevel2TrackingContentLoaded) {
|
|
aWriter.StartArrayElement(aWriter.SingleLineStyle);
|
|
{
|
|
aWriter.IntElement(
|
|
nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT);
|
|
aWriter.BoolElement(true); // blocked
|
|
aWriter.IntElement(1); // repeat count
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
if (aEntry.mData->mHasCookiesLoaded.isSome()) {
|
|
aWriter.StartArrayElement(aWriter.SingleLineStyle);
|
|
{
|
|
aWriter.IntElement(nsIWebProgressListener::STATE_COOKIES_LOADED);
|
|
aWriter.BoolElement(
|
|
aEntry.mData->mHasCookiesLoaded.value()); // blocked
|
|
aWriter.IntElement(1); // repeat count
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
if (aEntry.mData->mHasTrackerCookiesLoaded.isSome()) {
|
|
aWriter.StartArrayElement(aWriter.SingleLineStyle);
|
|
{
|
|
aWriter.IntElement(
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER);
|
|
aWriter.BoolElement(
|
|
aEntry.mData->mHasTrackerCookiesLoaded.value()); // blocked
|
|
aWriter.IntElement(1); // repeat count
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
if (aEntry.mData->mHasSocialTrackerCookiesLoaded.isSome()) {
|
|
aWriter.StartArrayElement(aWriter.SingleLineStyle);
|
|
{
|
|
aWriter.IntElement(
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER);
|
|
aWriter.BoolElement(
|
|
aEntry.mData->mHasSocialTrackerCookiesLoaded.value()); // blocked
|
|
aWriter.IntElement(1); // repeat count
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
if (aEntry.mData->mHasSuspiciousFingerprintingActivity) {
|
|
aWriter.StartArrayElement(aWriter.SingleLineStyle);
|
|
{
|
|
aWriter.IntElement(
|
|
nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING);
|
|
aWriter.BoolElement(true); // blocked
|
|
aWriter.IntElement(1); // repeat count
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
}
|
|
|
|
private:
|
|
OriginDataTable mLog;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif
|