From a84a8f8c1031e4e7070d3fae5b615f5af1adae2a Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Sun, 21 Nov 2021 18:21:31 +0100 Subject: [PATCH] Include data value previews in the email. --- mark.ts | 32 +++++++++++++++++++++++++ report-window/domain-summary.tsx | 30 ++++++++++++------------ report-window/email-template.tsx | 17 ++++++++------ report-window/har-converter.tsx | 30 +++++++++++------------- report-window/report-window.tsx | 40 ++++++++++++++++++-------------- request-cluster.ts | 10 +++++++- sidebar/stolen-data-cluster.tsx | 9 ++++++- stolen-data-entry.ts | 38 ++++++++++++++++++++++-------- tsconfig.json | 1 + 9 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 mark.ts diff --git a/mark.ts b/mark.ts new file mode 100644 index 0000000..6fa4f13 --- /dev/null +++ b/mark.ts @@ -0,0 +1,32 @@ +import { Classifications, StolenDataEntry } from "./stolen-data-entry"; + +export default class Mark { + classification: keyof typeof Classifications; + constructor(public entry: StolenDataEntry, public key: string) { + this.classification = entry.classification; + } + + getParsedValue() { + return this.entry.getParsedValue(this.key); + } + + get shorthost() { + return this.entry.request.shorthost; + } + + get source() { + return this.entry.source; + } + + get name() { + return this.entry.name; + } + + get originalURL() { + return this.entry.request.originalURL; + } + + get valuePreview(): string { + return this.entry.getValuePreview(this.key); + } +} diff --git a/report-window/domain-summary.tsx b/report-window/domain-summary.tsx index a96cb13..274519a 100644 --- a/report-window/domain-summary.tsx +++ b/report-window/domain-summary.tsx @@ -26,22 +26,22 @@ export default function DomainSummary({ diff --git a/report-window/email-template.tsx b/report-window/email-template.tsx index dfd3c87..0d18588 100644 --- a/report-window/email-template.tsx +++ b/report-window/email-template.tsx @@ -1,24 +1,27 @@ import React, { useState } from "react"; +import Mark from "../mark"; import { RequestCluster } from "../request-cluster"; -import { StolenDataEntry } from "../stolen-data-entry"; import { getDate, toBase64 } from "../util"; import DomainSummary from "./domain-summary"; type PopupState = "not_clicked" | "clicked_but_invalid"; export default function EmailTemplate({ - marked_entries, + marks, clusters, + version, }: { - marked_entries: StolenDataEntry[]; + marks: Mark[]; clusters: Record; + version: number; }): JSX.Element { const [popupState, setPopupState] = useState("not_clicked"); const [acceptAllName, setAcceptAllName] = useState( "Zaakceptuj wszystkie" ); - const [popupScreenshotBase64, setPopupScreenshotBase64] = - useState(null); + const [popupScreenshotBase64, setPopupScreenshotBase64] = useState( + null + ); return (
@@ -64,8 +67,8 @@ export default function EmailTemplate({ ) : null}

Dzień dobry, w dniu {getDate()} odwiedziłem stronę{" "} - {marked_entries[0].request.originalURL}. Strona ta wysłała moje dane - osobowe do podmiotów trzecich - bez mojej zgody.{" "} + {marks[0].originalURL}. Strona ta wysłała moje dane osobowe do podmiotów + trzecich - bez mojej zgody.{" "}

    {Object.values(clusters) diff --git a/report-window/har-converter.tsx b/report-window/har-converter.tsx index 7a11e11..7103546 100644 --- a/report-window/har-converter.tsx +++ b/report-window/har-converter.tsx @@ -1,20 +1,18 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { HAREntry } from "../extended-request"; -import { StolenDataEntry } from "../stolen-data-entry"; +import Mark from "../mark"; import { getshorthost, unique } from "../util"; function handleNewFile( element: HTMLInputElement, - marked_entries: StolenDataEntry[], + marks: Mark[], setFiltered: (Blob) => void ): void { const reader = new FileReader(); reader.addEventListener("load", () => { const content = JSON.parse(reader.result as string); content.log.entries = content.log.entries.filter((har_entry: HAREntry) => - marked_entries.some((stolen_entry) => - stolen_entry.matchesHAREntry(har_entry) - ) + marks.some((mark) => mark.entry.matchesHAREntry(har_entry)) ); setFiltered( new Blob([JSON.stringify(content)], { type: "application/json" }) @@ -23,8 +21,8 @@ function handleNewFile( reader.readAsText(element.files[0]); } -function generateFakeHAR(marked_entries: StolenDataEntry[]) { - const requests = marked_entries.map((entry) => entry.request); +function generateFakeHAR(marks: Mark[]) { + const requests = marks.map((mark) => mark.entry.request); return { log: { version: "1.2", @@ -52,14 +50,14 @@ function generateFakeHAR(marked_entries: StolenDataEntry[]) { }; } -export default function HARConverter({ - marked_entries, -}: { - marked_entries: StolenDataEntry[]; -}) { +export default function HARConverter({ marks }: { marks: Mark[] }) { const [filtered, setFiltered] = useState(null); const [filename, setFilename] = useState(""); - const fakeHAR = generateFakeHAR(marked_entries); + const [fakeHAR, setFakeHAR] = useState>(); + useEffect(() => { + setFakeHAR(generateFakeHAR(marks)); + }, []); + return (
    { setFilename(e.target.files[0].name); - handleNewFile(e.target, marked_entries, setFiltered); + handleNewFile(e.target, marks, setFiltered); }} /> {(filtered && ( @@ -84,7 +82,7 @@ export default function HARConverter({ new Blob([JSON.stringify(fakeHAR)], { type: "application/json" }) )} download={`${getshorthost( - marked_entries[0].request.originalURL + marks[0].originalURL )}-${new Date().toJSON()}-trimmed.har`} > Pobierz "zfałszowany" HAR diff --git a/report-window/report-window.tsx b/report-window/report-window.tsx index 3e55fe8..3d76534 100644 --- a/report-window/report-window.tsx +++ b/report-window/report-window.tsx @@ -2,7 +2,7 @@ import React from "react"; import ReactDOM from "react-dom"; import { getMemory } from "../memory"; import { Classifications } from "../stolen-data-entry"; -import { useEmitter } from "../util"; +import { reduceConcat, useEmitter } from "../util"; import EmailTemplate from "./email-template"; import HARConverter from "./har-converter"; @@ -15,13 +15,15 @@ function Report() { setCounter((c) => c + 1); } const clusters = getMemory().getClustersForOrigin(origin); - const marked_entries = Object.values(clusters) + const marks = Object.values(clusters) .map((cluster) => cluster.getMarkedRequests()) - .reduce((a, b) => a.concat(b), []) + .reduce(reduceConcat, []) .map((request) => request.getMarkedEntries()) - .reduce((a, b) => a.concat(b), []); + .reduce(reduceConcat, []) + .map((entry) => entry.marks) + .reduce(reduceConcat, []); return ( -
    +

    Generuj treść maila dla {origin}

    @@ -33,42 +35,46 @@ function Report() { - {marked_entries.map((entry) => ( + {marks.map((mark) => ( - +
    {entry.request.shorthost}{mark.shorthost} - {entry.source}:{entry.name} - {entry.markedKeys.join(",")} + {mark.source}:{mark.name} + {mark.key} - {entry.value} + {mark.valuePreview} + {/* always gonna have + one key, because unwrapEntry is calle above */}
    - - + +
    ); } diff --git a/request-cluster.ts b/request-cluster.ts index bf4ea05..0e6c1e0 100644 --- a/request-cluster.ts +++ b/request-cluster.ts @@ -105,7 +105,7 @@ export class RequestCluster extends EventEmitter { return this.requests.some((request) => request.hasMark()); } - getMarkedEntries() { + getMarkedEntries(): StolenDataEntry[] { return this.requests .map((request) => request.getMarkedEntries()) .reduce(reduceConcat, []); @@ -114,4 +114,12 @@ export class RequestCluster extends EventEmitter { exposesOrigin() { return this.requests.some((request) => request.exposesOrigin()); } + + getMarks() { + return this.requests + .map((request) => request.getMarkedEntries()) + .reduce(reduceConcat, []) + .map((entry) => entry.marks) + .reduce(reduceConcat, []); + } } diff --git a/sidebar/stolen-data-cluster.tsx b/sidebar/stolen-data-cluster.tsx index 26f29b7..3a866bc 100644 --- a/sidebar/stolen-data-cluster.tsx +++ b/sidebar/stolen-data-cluster.tsx @@ -118,7 +118,14 @@ export default function StolenDataCluster({ key={origin + cluster.id + entry.getUniqueKey()} data-key={origin + cluster.id + entry.getUniqueKey()} > - + entry.addMark("")} + > {entry.getNames().map(hyphenate).join(", ")} {entry.getSources().map((source) => icons[source])} diff --git a/stolen-data-entry.ts b/stolen-data-entry.ts index 01c0629..942030a 100644 --- a/stolen-data-entry.ts +++ b/stolen-data-entry.ts @@ -1,5 +1,6 @@ import { TCModel } from "@iabtcf/core"; import ExtendedRequest, { HAREntry } from "./extended-request"; +import Mark from "./mark"; import { getMemory } from "./memory"; import { getshorthost, @@ -18,6 +19,8 @@ export const Classifications = { location: "Informacje na temat mojego położenia", }; +const ID_PREVIEW_MAX_LENGTH = 20; + const id = (function* id() { let i = 0; while (true) { @@ -30,7 +33,7 @@ export class StolenDataEntry { public isIAB = false; public iab: TCModel | null = null; public id: number; - public markedKeys: string[] = []; + public marks: Mark[] = []; public classification: keyof typeof Classifications; constructor( @@ -79,6 +82,7 @@ export class StolenDataEntry { } else if (isURL(value)) { const url = new URL(value); const object = { + [Symbol.for("originalURL")]: value, host: url.host, path: url.pathname, ...Object.fromEntries( @@ -93,7 +97,7 @@ export class StolenDataEntry { } } - getParsedValue(key_path: string): string | Record { + getParsedValue(key_path: string): string | Record { let object = StolenDataEntry.parseValue(this.value); for (const key of key_path.split(".")) { if (key === "") continue; @@ -103,20 +107,20 @@ export class StolenDataEntry { } addMark(key: string) { - this.markedKeys.push(key); - getMemory().emit("change"); // to trigger rerender + this.marks.push(new Mark(this, key)); + getMemory().emit("change", true); // to trigger rerender } hasMark(key?: string) { if (key) { - return this.markedKeys.some((k) => k == key); + return this.marks.some((k) => k.key == key); } else { - return this.markedKeys.length > 0; + return this.marks.length > 0; } } removeMark(key: string) { - this.markedKeys = this.markedKeys.filter((e) => e != key); + this.marks = this.marks.filter((mark) => mark.key != key); getMemory().emit("change"); // to trigger rerender } @@ -155,15 +159,29 @@ export class StolenDataEntry { matchesHAREntry(har: HAREntry): boolean { return this.request.matchesHAREntry(har); } + + getValuePreview(key = ""): string { + const value = this.getParsedValue(key); + const str = value.toString(); + if (this.classification == "id") { + return ( + str.slice(0, Math.min(str.length / 3, ID_PREVIEW_MAX_LENGTH)) + "(...)" + ); + } else if (typeof value === "object" && value[Symbol.for("originalURL")]) { + return value[Symbol.for("originalURL")] as string; + } else { + return str; + } + } } export class MergedStolenDataEntry { constructor(public entries: StolenDataEntry[]) { const all_marks = unique( - entries.map((entry) => entry.markedKeys).reduce(reduceConcat, []) + entries.map((entry) => entry.marks).reduce(reduceConcat, []) ); for (const entry of entries) { - entry.markedKeys = all_marks; + entry.marks = all_marks; } // getMemory().emit("change"); // to trigger render } @@ -211,7 +229,7 @@ export class MergedStolenDataEntry { getMarkedValues() { return this.entries - .map((entry) => entry.markedKeys) + .map((entry) => entry.marks) .reduce((a, b) => a.concat(b), []); } diff --git a/tsconfig.json b/tsconfig.json index de745d1..23259eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "esModuleInterop": true, "lib": ["es2017", "dom", "es2019"], "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"], + "target": "es2019", "outDir": "lib" } }