Compare commits

..

No commits in common. "a84a8f8c1031e4e7070d3fae5b615f5af1adae2a" and "2bcf72f65234fb388148c3cb50d4d3cd4b43b592" have entirely different histories.

11 changed files with 73 additions and 198 deletions

32
mark.ts
View File

@ -1,32 +0,0 @@
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);
}
}

View File

@ -1,11 +1,10 @@
import ExtendedRequest from "./extended-request"; import ExtendedRequest from "./extended-request";
import { getshorthost, makeThrottle } from "./util"; import { getshorthost } from "./util";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { RequestCluster } from "./request-cluster"; import { RequestCluster } from "./request-cluster";
export default class Memory extends EventEmitter { export default class Memory extends EventEmitter {
origin_to_history = {} as Record<string, Record<string, RequestCluster>>; origin_to_history = {} as Record<string, Record<string, RequestCluster>>;
private throttle = makeThrottle(200);
async register(request: ExtendedRequest) { async register(request: ExtendedRequest) {
await request.init(); await request.init();
console.log("registering request for", request.origin); console.log("registering request for", request.origin);
@ -35,20 +34,6 @@ export default class Memory extends EventEmitter {
); );
} }
emit(eventName: string, immediate = false) {
try {
if (immediate) {
super.emit(eventName);
return;
} else {
this.throttle(() => super.emit(eventName));
}
return true;
} catch (e) {
debugger;
}
}
getClustersForOrigin(origin: string): Record<string, RequestCluster> { getClustersForOrigin(origin: string): Record<string, RequestCluster> {
return this.origin_to_history[origin] || {}; return this.origin_to_history[origin] || {};
} }

View File

@ -5,7 +5,6 @@ import { Classifications, Sources } from "../stolen-data-entry";
const emailClassifications: Record<keyof typeof Classifications, string> = { const emailClassifications: Record<keyof typeof Classifications, string> = {
id: "sztucznie nadane mi ID", id: "sztucznie nadane mi ID",
history: "część mojej historii przeglądania", history: "część mojej historii przeglądania",
location: "informacje na temat mojej lokalizacji geograficznej",
}; };
const emailSources: Record<Sources, string> = { const emailSources: Record<Sources, string> = {
@ -26,22 +25,22 @@ export default function DomainSummary({
<ul> <ul>
<li>Mój adres IP</li> <li>Mój adres IP</li>
{cluster {cluster
.getMarks() .getMarkedEntries()
.sort((markA, markB) => .sort((entryA, entryB) => (entryA.value > entryB.value ? -1 : 1))
markA.entry.value > markB.entry.value ? -1 : 1 .reduce((acc, entry, index, arr) => {
) if (index === 0) {
.map((mark) => ( return [entry];
}
if (entry.value != arr[index - 1].value) {
acc.push(entry);
}
return acc;
}, [])
.map((entry) => (
<li> <li>
{emailClassifications[mark.classification]}{" "} {emailClassifications[entry.classification]}{" "}
{emailSources[mark.source]} (nazwa: {mark.name},{" "} {emailSources[entry.source]}
{mark.key ? ( &nbsp;(<code>{entry.name.trim()}</code>)
<>
pozycja <code>{mark.key}</code>,
</>
) : (
""
)}
wartość: <code>{mark.valuePreview}</code>)
</li> </li>
))} ))}
</ul> </ul>

View File

@ -1,27 +1,24 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Mark from "../mark";
import { RequestCluster } from "../request-cluster"; import { RequestCluster } from "../request-cluster";
import { StolenDataEntry } from "../stolen-data-entry";
import { getDate, toBase64 } from "../util"; import { getDate, toBase64 } from "../util";
import DomainSummary from "./domain-summary"; import DomainSummary from "./domain-summary";
type PopupState = "not_clicked" | "clicked_but_invalid"; type PopupState = "not_clicked" | "clicked_but_invalid";
export default function EmailTemplate({ export default function EmailTemplate({
marks, marked_entries,
clusters, clusters,
version,
}: { }: {
marks: Mark[]; marked_entries: StolenDataEntry[];
clusters: Record<string, RequestCluster>; clusters: Record<string, RequestCluster>;
version: number;
}): JSX.Element { }): JSX.Element {
const [popupState, setPopupState] = useState<PopupState>("not_clicked"); const [popupState, setPopupState] = useState<PopupState>("not_clicked");
const [acceptAllName, setAcceptAllName] = useState<string>( const [acceptAllName, setAcceptAllName] = useState<string>(
"Zaakceptuj wszystkie" "Zaakceptuj wszystkie"
); );
const [popupScreenshotBase64, setPopupScreenshotBase64] = useState<string>( const [popupScreenshotBase64, setPopupScreenshotBase64] =
null useState<string>(null);
);
return ( return (
<div> <div>
@ -67,8 +64,8 @@ export default function EmailTemplate({
) : null} ) : null}
<p> <p>
Dzień dobry, w dniu {getDate()} odwiedziłem stronę{" "} Dzień dobry, w dniu {getDate()} odwiedziłem stronę{" "}
{marks[0].originalURL}. Strona ta wysłała moje dane osobowe do podmiotów {marked_entries[0].request.originalURL}. Strona ta wysłała moje dane
trzecich - bez mojej zgody.{" "} osobowe do podmiotów trzecich - bez mojej zgody.{" "}
</p> </p>
<ul> <ul>
{Object.values(clusters) {Object.values(clusters)
@ -77,15 +74,10 @@ export default function EmailTemplate({
<DomainSummary cluster={cluster} /> <DomainSummary cluster={cluster} />
))} ))}
</ul> </ul>
<p>
{" "}
Dane te zostały wysłane przez Państwa stronę - a mówiąc dokładniej,
przez zamieszczone przez Państwa na tej stronie skrypty.
</p>
{popupState === "not_clicked" ? ( {popupState === "not_clicked" ? (
<p> <p>
Nastąpiło to, zanim zdążyłem w ogóle przeczytać treść wyskakującego Dane te zostały wysłane przez Państwa stronę, zanim zdążyłem w ogóle
okienka ze zgodami. przeczytać treść wyskakującego okienka ze zgodami.
</p> </p>
) : null} ) : null}
{popupState === "clicked_but_invalid" ? ( {popupState === "clicked_but_invalid" ? (
@ -153,10 +145,12 @@ export default function EmailTemplate({
procesów przetwarzania danych. procesów przetwarzania danych.
</p> </p>
<p> <p>
Niniejszym zwracam się także z żądaniem ujawnienia tożsamości podmiotów, Niniejszym zwracam się także z żądaniem wycofania przesłanych przez
które właścicielami wyżej wymienionych domen, abym mógł zapoznać się Państwa stronę moich danych osobowych z baz wyżej wymienionych podmiotów
z ich politykami prywatności i zwrócić się do tych podmiotów o usunięcie oraz przesłania potwierdzenia uwiarygadniającego pomyślne wycofanie tych
z ich baz wysłanych przez Państwa stronę moich danych. danych. Proszę też o przesłanie tożsamości podmiotów, które
właścicielami wyżej wymienionych domen, abym mógł zapoznać się z ich
politykami prywatności.
</p> </p>
<p> <p>
Proszę też o wysłanie kopii danych zebranych na mój temat i wysłanych do Proszę też o wysłanie kopii danych zebranych na mój temat i wysłanych do

View File

@ -1,18 +1,20 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { HAREntry } from "../extended-request"; import { HAREntry } from "../extended-request";
import Mark from "../mark"; import { StolenDataEntry } from "../stolen-data-entry";
import { getshorthost, unique } from "../util"; import { getshorthost, unique } from "../util";
function handleNewFile( function handleNewFile(
element: HTMLInputElement, element: HTMLInputElement,
marks: Mark[], marked_entries: StolenDataEntry[],
setFiltered: (Blob) => void setFiltered: (Blob) => void
): void { ): void {
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener("load", () => { reader.addEventListener("load", () => {
const content = JSON.parse(reader.result as string); const content = JSON.parse(reader.result as string);
content.log.entries = content.log.entries.filter((har_entry: HAREntry) => content.log.entries = content.log.entries.filter((har_entry: HAREntry) =>
marks.some((mark) => mark.entry.matchesHAREntry(har_entry)) marked_entries.some((stolen_entry) =>
stolen_entry.matchesHAREntry(har_entry)
)
); );
setFiltered( setFiltered(
new Blob([JSON.stringify(content)], { type: "application/json" }) new Blob([JSON.stringify(content)], { type: "application/json" })
@ -21,8 +23,8 @@ function handleNewFile(
reader.readAsText(element.files[0]); reader.readAsText(element.files[0]);
} }
function generateFakeHAR(marks: Mark[]) { function generateFakeHAR(marked_entries: StolenDataEntry[]) {
const requests = marks.map((mark) => mark.entry.request); const requests = marked_entries.map((entry) => entry.request);
return { return {
log: { log: {
version: "1.2", version: "1.2",
@ -50,14 +52,14 @@ function generateFakeHAR(marks: Mark[]) {
}; };
} }
export default function HARConverter({ marks }: { marks: Mark[] }) { export default function HARConverter({
marked_entries,
}: {
marked_entries: StolenDataEntry[];
}) {
const [filtered, setFiltered] = useState<Blob | null>(null); const [filtered, setFiltered] = useState<Blob | null>(null);
const [filename, setFilename] = useState(""); const [filename, setFilename] = useState("");
const [fakeHAR, setFakeHAR] = useState<ReturnType<typeof generateFakeHAR>>(); const fakeHAR = generateFakeHAR(marked_entries);
useEffect(() => {
setFakeHAR(generateFakeHAR(marks));
}, []);
return ( return (
<div> <div>
<input <input
@ -65,7 +67,7 @@ export default function HARConverter({ marks }: { marks: Mark[] }) {
accept=".har" accept=".har"
onChange={(e) => { onChange={(e) => {
setFilename(e.target.files[0].name); setFilename(e.target.files[0].name);
handleNewFile(e.target, marks, setFiltered); handleNewFile(e.target, marked_entries, setFiltered);
}} }}
/> />
{(filtered && ( {(filtered && (
@ -82,7 +84,7 @@ export default function HARConverter({ marks }: { marks: Mark[] }) {
new Blob([JSON.stringify(fakeHAR)], { type: "application/json" }) new Blob([JSON.stringify(fakeHAR)], { type: "application/json" })
)} )}
download={`${getshorthost( download={`${getshorthost(
marks[0].originalURL marked_entries[0].request.originalURL
)}-${new Date().toJSON()}-trimmed.har`} )}-${new Date().toJSON()}-trimmed.har`}
> >
Pobierz "zfałszowany" HAR Pobierz "zfałszowany" HAR

View File

@ -1,8 +1,6 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { getMemory } from "../memory"; import { getMemory } from "../memory";
import { Classifications } from "../stolen-data-entry";
import { reduceConcat, useEmitter } from "../util";
import EmailTemplate from "./email-template"; import EmailTemplate from "./email-template";
import HARConverter from "./har-converter"; import HARConverter from "./har-converter";
@ -10,20 +8,14 @@ function Report() {
const origin = new URL(document.location.toString()).searchParams.get( const origin = new URL(document.location.toString()).searchParams.get(
"origin" "origin"
); );
const [counter, setCounter] = useEmitter(getMemory());
function refresh() {
setCounter((c) => c + 1);
}
const clusters = getMemory().getClustersForOrigin(origin); const clusters = getMemory().getClustersForOrigin(origin);
const marks = Object.values(clusters) const marked_entries = Object.values(clusters)
.map((cluster) => cluster.getMarkedRequests()) .map((cluster) => cluster.getMarkedRequests())
.reduce(reduceConcat, []) .reduce((a, b) => a.concat(b), [])
.map((request) => request.getMarkedEntries()) .map((request) => request.getMarkedEntries())
.reduce(reduceConcat, []) .reduce((a, b) => a.concat(b), []);
.map((entry) => entry.marks)
.reduce(reduceConcat, []);
return ( return (
<div {...{ "data-version": counter }}> <div>
<h1>Generuj treść maila dla {origin}</h1> <h1>Generuj treść maila dla {origin}</h1>
<table> <table>
<thead> <thead>
@ -35,46 +27,34 @@ function Report() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{marks.map((mark) => ( {marked_entries.map((entry) => (
<tr <tr
key={mark.entry.request.originalURL + ";" + mark.key}
style={{ style={{
backgroundColor: backgroundColor:
mark.classification == "id" ? "yellow" : "white", entry.classification == "id" ? "yellow" : "white",
}} }}
> >
<td>{mark.shorthost}</td> <td>{entry.request.shorthost}</td>
<td style={{ overflowWrap: "anywhere" }}> <td style={{ overflowWrap: "anywhere" }}>
{mark.source}:{mark.name} {entry.source}:{entry.name}
{mark.key} {entry.markedKeys.join(",")}
</td> </td>
<td <td
style={{ style={{
width: "400px", width: "400px",
overflowWrap: "anywhere", overflowWrap: "anywhere",
backgroundColor: mark.entry.isRelatedToID() backgroundColor: entry.isRelatedToID()
? "#ffff0054" ? "#ffff0054"
: "white", : "white",
}} }}
> >
{mark.valuePreview} {entry.value}
{/* always gonna have
one key, because unwrapEntry is calle above */}
</td> </td>
<td> <td>
<select <select value={entry.classification}>
value={mark.classification}
onChange={(e) => {
mark.classification = e.target
.value as keyof typeof Classifications;
console.log("changed classification!");
refresh();
}}
>
{[ {[
["history", "Historia przeglądania"], ["history", "Historia przeglądania"],
["id", "Sztucznie nadane id"], ["id", "Sztucznie nadane id"],
["location", "Lokalizacja"],
].map(([key, name]) => ( ].map(([key, name]) => (
<option key={key} value={key}> <option key={key} value={key}>
{name} {name}
@ -86,8 +66,8 @@ function Report() {
))} ))}
</tbody> </tbody>
</table> </table>
<EmailTemplate {...{ marks, clusters, version: counter }} /> <EmailTemplate {...{ marked_entries, clusters }} />
<HARConverter {...{ marks }} /> <HARConverter {...{ marked_entries }} />
</div> </div>
); );
} }

View File

@ -105,7 +105,7 @@ export class RequestCluster extends EventEmitter {
return this.requests.some((request) => request.hasMark()); return this.requests.some((request) => request.hasMark());
} }
getMarkedEntries(): StolenDataEntry[] { getMarkedEntries() {
return this.requests return this.requests
.map((request) => request.getMarkedEntries()) .map((request) => request.getMarkedEntries())
.reduce(reduceConcat, []); .reduce(reduceConcat, []);
@ -114,12 +114,4 @@ export class RequestCluster extends EventEmitter {
exposesOrigin() { exposesOrigin() {
return this.requests.some((request) => request.exposesOrigin()); return this.requests.some((request) => request.exposesOrigin());
} }
getMarks() {
return this.requests
.map((request) => request.getMarkedEntries())
.reduce(reduceConcat, [])
.map((entry) => entry.marks)
.reduce(reduceConcat, []);
}
} }

View File

@ -118,14 +118,7 @@ export default function StolenDataCluster({
key={origin + cluster.id + entry.getUniqueKey()} key={origin + cluster.id + entry.getUniqueKey()}
data-key={origin + cluster.id + entry.getUniqueKey()} data-key={origin + cluster.id + entry.getUniqueKey()}
> >
<th <th style={{ width: "100px", overflowWrap: "anywhere" }}>
style={{
width: "100px",
overflowWrap: "anywhere",
border: entry.hasMark("") ? "2px solid red" : "",
}}
onClick={() => entry.addMark("")}
>
{entry.getNames().map(hyphenate).join(", ")} {entry.getNames().map(hyphenate).join(", ")}
</th> </th>
<td>{entry.getSources().map((source) => icons[source])}</td> <td>{entry.getSources().map((source) => icons[source])}</td>

View File

@ -1,6 +1,5 @@
import { TCModel } from "@iabtcf/core"; import { TCModel } from "@iabtcf/core";
import ExtendedRequest, { HAREntry } from "./extended-request"; import ExtendedRequest, { HAREntry } from "./extended-request";
import Mark from "./mark";
import { getMemory } from "./memory"; import { getMemory } from "./memory";
import { import {
getshorthost, getshorthost,
@ -16,11 +15,8 @@ export type Sources = "cookie" | "pathname" | "queryparams" | "header";
export const Classifications = <const>{ export const Classifications = <const>{
id: "Sztucznie nadane ID", id: "Sztucznie nadane ID",
history: "Część historii przeglądania", history: "Część historii przeglądania",
location: "Informacje na temat mojego położenia",
}; };
const ID_PREVIEW_MAX_LENGTH = 20;
const id = (function* id() { const id = (function* id() {
let i = 0; let i = 0;
while (true) { while (true) {
@ -33,7 +29,7 @@ export class StolenDataEntry {
public isIAB = false; public isIAB = false;
public iab: TCModel | null = null; public iab: TCModel | null = null;
public id: number; public id: number;
public marks: Mark[] = []; public markedKeys: string[] = [];
public classification: keyof typeof Classifications; public classification: keyof typeof Classifications;
constructor( constructor(
@ -82,7 +78,6 @@ export class StolenDataEntry {
} else if (isURL(value)) { } else if (isURL(value)) {
const url = new URL(value); const url = new URL(value);
const object = { const object = {
[Symbol.for("originalURL")]: value,
host: url.host, host: url.host,
path: url.pathname, path: url.pathname,
...Object.fromEntries( ...Object.fromEntries(
@ -97,7 +92,7 @@ export class StolenDataEntry {
} }
} }
getParsedValue(key_path: string): string | Record<string | symbol, unknown> { getParsedValue(key_path: string): string | Record<string, unknown> {
let object = StolenDataEntry.parseValue(this.value); let object = StolenDataEntry.parseValue(this.value);
for (const key of key_path.split(".")) { for (const key of key_path.split(".")) {
if (key === "") continue; if (key === "") continue;
@ -107,20 +102,20 @@ export class StolenDataEntry {
} }
addMark(key: string) { addMark(key: string) {
this.marks.push(new Mark(this, key)); this.markedKeys.push(key);
getMemory().emit("change", true); // to trigger rerender getMemory().emit("change"); // to trigger rerender
} }
hasMark(key?: string) { hasMark(key?: string) {
if (key) { if (key) {
return this.marks.some((k) => k.key == key); return this.markedKeys.some((k) => k == key);
} else { } else {
return this.marks.length > 0; return this.markedKeys.length > 0;
} }
} }
removeMark(key: string) { removeMark(key: string) {
this.marks = this.marks.filter((mark) => mark.key != key); this.markedKeys = this.markedKeys.filter((e) => e != key);
getMemory().emit("change"); // to trigger rerender getMemory().emit("change"); // to trigger rerender
} }
@ -159,29 +154,15 @@ export class StolenDataEntry {
matchesHAREntry(har: HAREntry): boolean { matchesHAREntry(har: HAREntry): boolean {
return this.request.matchesHAREntry(har); 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 { export class MergedStolenDataEntry {
constructor(public entries: StolenDataEntry[]) { constructor(public entries: StolenDataEntry[]) {
const all_marks = unique( const all_marks = unique(
entries.map((entry) => entry.marks).reduce(reduceConcat, []) entries.map((entry) => entry.markedKeys).reduce(reduceConcat, [])
); );
for (const entry of entries) { for (const entry of entries) {
entry.marks = all_marks; entry.markedKeys = all_marks;
} }
// getMemory().emit("change"); // to trigger render // getMemory().emit("change"); // to trigger render
} }
@ -229,7 +210,7 @@ export class MergedStolenDataEntry {
getMarkedValues() { getMarkedValues() {
return this.entries return this.entries
.map((entry) => entry.marks) .map((entry) => entry.markedKeys)
.reduce((a, b) => a.concat(b), []); .reduce((a, b) => a.concat(b), []);
} }

View File

@ -4,7 +4,6 @@
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["es2017", "dom", "es2019"], "lib": ["es2017", "dom", "es2019"],
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"], "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"],
"target": "es2019",
"outDir": "lib" "outDir": "lib"
} }
} }

18
util.ts
View File

@ -138,21 +138,3 @@ export function toBase64(file: File): Promise<string> {
FR.readAsDataURL(file); FR.readAsDataURL(file);
}); });
} }
export function makeThrottle(interval: number) {
let last_emit = 0;
function emit(callback: () => void) {
if (Date.now() - last_emit > interval) {
callback();
last_emit = Date.now();
return true;
} else {
return false;
}
}
return function (callback: () => void) {
if (!emit(callback)) {
setTimeout(() => emit(callback), interval);
}
};
}