diff --git a/assets/icons/data.svg b/assets/icons/data.svg new file mode 100644 index 0000000..e78ad13 --- /dev/null +++ b/assets/icons/data.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/esbuild.config.js b/esbuild.config.js index 8f38539..d99ff61 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -25,5 +25,3 @@ esbuild }, }) .catch(() => process.exit(1)); - -// npx esbuild sidebar/sidebar.tsx test.ts --bundle report-window/report-window.tsx --bundle background.ts --bundle --outdir=./lib diff --git a/memory.ts b/memory.ts index 92c86ec..7b60311 100644 --- a/memory.ts +++ b/memory.ts @@ -1,101 +1,107 @@ -import ExtendedRequest from "./extended-request"; -import { getshorthost, makeThrottle } from "./util"; -import { EventEmitter } from "events"; -import { RequestCluster } from "./request-cluster"; +import ExtendedRequest from './extended-request'; +import { getshorthost, makeThrottle } from './util'; +import { EventEmitter } from 'events'; +import { RequestCluster } from './request-cluster'; export default class Memory extends EventEmitter { - origin_to_history = {} as Record>; - private throttle = makeThrottle(200); - async register(request: ExtendedRequest) { - await request.init(); - // console.log("registering request for", request.origin); - if (!request.isThirdParty()) { - return; + origin_to_history = {} as Record>; + private throttle = makeThrottle(200); + async register(request: ExtendedRequest) { + await request.init(); + if (!request.isThirdParty()) { + return; + } + if (!this.origin_to_history[request.origin]) { + this.origin_to_history[request.origin] = {}; + } + const shorthost = getshorthost(new URL(request.url).host); + if (!this.origin_to_history[request.origin][shorthost]) { + const cluster = new RequestCluster(shorthost); + this.origin_to_history[request.origin][shorthost] = cluster; + } + this.origin_to_history[request.origin][shorthost].add(request); + this.emit('change'); } - if (!this.origin_to_history[request.origin]) { - this.origin_to_history[request.origin] = {}; + + constructor() { + super(); + + browser.webRequest.onBeforeRequest.addListener( + async (request) => { + new ExtendedRequest(request); + }, + { urls: [''] }, + ['requestBody'] + ); + browser.webRequest.onBeforeSendHeaders.addListener( + async (request) => { + const extendedRequest = ExtendedRequest.by_id[ + request.requestId + ].addHeaders(request.requestHeaders || []); + this.register(extendedRequest); + }, + { urls: [''] }, + ['requestHeaders'] + ); } - const shorthost = getshorthost(new URL(request.url).host); - if (!this.origin_to_history[request.origin][shorthost]) { - const cluster = new RequestCluster(shorthost); - this.origin_to_history[request.origin][shorthost] = cluster; + + emit(eventName: string, immediate = false) { + try { + if (immediate) { + super.emit(eventName); + return; + } else { + this.throttle(() => super.emit(eventName)); + } + return true; + } catch (e) { + // debugger; + console.error(e); + } } - this.origin_to_history[request.origin][shorthost].add(request); - this.emit("change"); - } - constructor() { - super(); - - browser.webRequest.onBeforeRequest.addListener( - async (request) => { - new ExtendedRequest(request); - }, - { urls: [""] }, - ["requestBody"] - ); - browser.webRequest.onBeforeSendHeaders.addListener( - async (request) => { - const extendedRequest = ExtendedRequest.by_id[ - request.requestId - ].addHeaders(request.requestHeaders || []); - this.register(extendedRequest); - }, - { urls: [""] }, - ["requestHeaders"] - ); - } - - 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 { + return this.origin_to_history[origin] || {}; } - } - getClustersForOrigin(origin: string): Record { - return this.origin_to_history[origin] || {}; - } + async removeCookiesFor(origin: string, shorthost?: string): Promise { + if (shorthost) { + const cookies = await browser.cookies.getAll({ domain: shorthost }); + for (const cookie of cookies) { + console.log( + 'removing cookie', + cookie.name, + 'from', + cookie.domain + ); + await browser.cookies.remove({ + name: cookie.name, + url: `https://${cookie.domain}`, + }); + } + } else { + const clusters = this.getClustersForOrigin(origin); - async removeCookiesFor(origin: string, shorthost?: string): Promise { - if (shorthost) { - const cookies = await browser.cookies.getAll({ domain: shorthost }); - for (const cookie of cookies) { - console.log("removing cookie", cookie.name, "from", cookie.domain); - await browser.cookies.remove({ - name: cookie.name, - url: `https://${cookie.domain}`, - }); - } - } else { - const clusters = this.getClustersForOrigin(origin); - - await Promise.all( - Object.values(clusters) - .filter((cluster) => !shorthost || cluster.id === shorthost) - .map((cluster) => this.removeCookiesFor(origin, cluster.id)) - ); + await Promise.all( + Object.values(clusters) + .filter((cluster) => !shorthost || cluster.id === shorthost) + .map((cluster) => this.removeCookiesFor(origin, cluster.id)) + ); + } } - } - async removeRequestsFor(origin: string) { - this.origin_to_history[origin] = {}; - } + async removeRequestsFor(origin: string) { + this.origin_to_history[origin] = {}; + } } export function init() { - const memory = new Memory(); + const memory = new Memory(); - (window as any).memory = memory; + (window as any).memory = memory; } export function getMemory(): Memory { - return (browser.extension.getBackgroundPage().window as any).memory as Memory; + return (browser.extension.getBackgroundPage().window as any) + .memory as Memory; } diff --git a/report-window/report-window.tsx b/report-window/report-window.tsx index 0fe6cd7..174f409 100644 --- a/report-window/report-window.tsx +++ b/report-window/report-window.tsx @@ -114,7 +114,6 @@ function Report() { console.time('rendering template'); const result = (
- {/*Generuj treść maila dla {origin} diff --git a/sidebar/sidebar.tsx b/sidebar/sidebar.tsx index 8e75107..6a2fd89 100644 --- a/sidebar/sidebar.tsx +++ b/sidebar/sidebar.tsx @@ -10,6 +10,8 @@ import TrashIcon from '../assets/icons/trash_full.svg'; import MailIcon from '../assets/icons/mail.svg'; import ShortLeftIcon from '../assets/icons/short_left.svg'; import CloseBigIcon from '../assets/icons/close_big.svg'; +import CookiesIcon from '../assets/icons/cookie.svg'; +import DataIcon from '../assets/icons/data.svg'; async function getCurrentTab() { const [tab] = await browser.tabs.query({ @@ -75,15 +77,6 @@ const Sidebar = () => { return (
- {/*
- - -
*/}
{ {stolenDataView ? ( @@ -232,8 +223,6 @@ const Sidebar = () => { /> )} - - {/*
Footer marks → {JSON.stringify(marksOccurrence)}
*/}
); }; diff --git a/sidebar/stolen-data-cluster.tsx b/sidebar/stolen-data-cluster.tsx index 340002c..611c845 100644 --- a/sidebar/stolen-data-cluster.tsx +++ b/sidebar/stolen-data-cluster.tsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import { getMemory } from '../memory'; import { StolenDataEntry } from '../stolen-data-entry'; @@ -38,7 +38,6 @@ function StolenDataValue({ e.stopPropagation(); }} title={maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)} - // style={{ color: entry.isMarked ? 'black' : 'gray' }} > {body} @@ -120,8 +119,6 @@ function StolenDataRow({ ) : null} - {/* */} - ); @@ -194,56 +191,5 @@ export default function StolenDataCluster({
- - //
- //

- // {cluster.id}{' '} - // {cluster.hasCookies() ? '🍪' : ''} x{cluster.requests.length}{' '} - // {/* getMemory().removeCookiesFor(origin, shorthost)} - // * > - // * Wyczyść cookiesy - // * */} - // { - // cluster.autoMark(); - // refresh(); - // e.preventDefault(); - // }} - // > - // Zaznacz auto - // - //

- //
- // {cluster.getFullHosts().map((host) => ( - // - // {host},{' '} - // - // ))} - //
- // - // - // {cluster - // .calculateRepresentativeStolenData({ - // minValueLength, - // cookiesOnly, - // cookiesOrOriginOnly, - // }) - // .map((entry) => ( - // - // ))} - // - //
- //
); } diff --git a/sidebar/stolen-data.tsx b/sidebar/stolen-data.tsx index c39a4f1..6f40441 100644 --- a/sidebar/stolen-data.tsx +++ b/sidebar/stolen-data.tsx @@ -40,49 +40,6 @@ export function StolenData({ ); return (
- {/* - */} - - {/* */} - - {/* */} - Domeny oraz przesłane informacje {clusters.map((cluster) => { diff --git a/stolen-data-entry.ts b/stolen-data-entry.ts index 6183ffd..f14ed1e 100644 --- a/stolen-data-entry.ts +++ b/stolen-data-entry.ts @@ -1,265 +1,269 @@ -// import { TCModel } from "@iabtcf/core"; -import { EventEmitter } from "events"; -import ExtendedRequest, { HAREntry } from "./extended-request"; +import { EventEmitter } from 'events'; +import ExtendedRequest, { HAREntry } from './extended-request'; import { - getshorthost, - isBase64, - isBase64JSON, - isJSONObject, - isURL, - maskString, - parseToObject, - safeDecodeURIComponent, -} from "./util"; + getshorthost, + isBase64, + isBase64JSON, + isJSONObject, + isURL, + maskString, + parseToObject, + safeDecodeURIComponent, +} from './util'; export type Sources = - | "cookie" - | "pathname" - | "queryparams" - | "header" - | "request_body"; + | 'cookie' + | 'pathname' + | 'queryparams' + | 'header' + | 'request_body'; export const Classifications = { - id: "Identyfikator internetowy", - history: "Część historii przeglądania", - location: "Informacje na temat mojego położenia", + id: 'Identyfikator internetowy', + history: 'Część historii przeglądania', + location: 'Informacje na temat mojego położenia', }; const ID_PREVIEW_MAX_LENGTH = 20; const MIN_COOKIE_LENGTH_FOR_AUTO_MARK = 15; const id = (function* id() { - let i = 0; - while (true) { - i++; - yield i; - } + let i = 0; + while (true) { + i++; + yield i; + } })(); -export type DecodingSchema = "base64" | "raw"; +export type DecodingSchema = 'base64' | 'raw'; export class StolenDataEntry extends EventEmitter { - public isIAB = false; - // public iab: TCModel | null = null; - public id: number; - private marked = false; - public classification: keyof typeof Classifications; - public decoding_applied: DecodingSchema = "raw"; - public decodings_available: DecodingSchema[] = ["raw"]; + public isIAB = false; + public id: number; + private marked = false; + public classification: keyof typeof Classifications; + public decoding_applied: DecodingSchema = 'raw'; + public decodings_available: DecodingSchema[] = ['raw']; - constructor( - public request: ExtendedRequest, - public source: Sources, - public name: string, - public value: string - ) { - // try { - // this.iab = TCString.decode(value); - // // console.log(this.iab); - // this.isIAB = true; - // } catch (e) {} - super(); - this.id = id.next().value as number; - this.classification = this.classify(); - if (isBase64(value)) { - this.decodings_available.push("base64"); - } - } - - getPriority() { - let priority = 0; - priority += Math.min(this.value.length, 50); - const url = new URL(this.request.originalURL); - if (this.value.includes(url.host)) { - priority += 100; - } - if (this.value.includes(url.pathname)) { - priority += 100; - } - if (this.source === "cookie") { - priority += 200; - } - return priority; - } - - get isMarked() { - return this.marked; - } - - hasValue(value: string) { - return this.value === value; - } - - static parseValue(value: unknown): string | Record { - if (isBase64JSON(value)) { - return StolenDataEntry.parseValue({ base64: JSON.parse(atob(value)) }); - } - if (value === undefined) { - return ""; - } - if (isJSONObject(value)) { - const object = parseToObject(value); - return object; - } else if (isURL(value)) { - const url = new URL(value); - let hash = url.hash; - if (hash.includes("=")) { - //facebook sometimes includes querystring-encoded data into the hash... attempt to parse it - try { - hash = Object.fromEntries( - hash - .slice(1) - .split("&") - .map((kv) => kv.split("=")) - ); - } catch (e) { - // failed to parse as query string - console.log( - "Failed attempt to parse hash location as query string, probably safe to ignore:", - e - ); + constructor( + public request: ExtendedRequest, + public source: Sources, + public name: string, + public value: string + ) { + super(); + this.id = id.next().value as number; + this.classification = this.classify(); + if (isBase64(value)) { + this.decodings_available.push('base64'); } - } - const searchParams = Object.fromEntries( - ( - url.searchParams as unknown as { - entries: () => Iterable<[string, string]>; - } - ).entries() - ); - if (typeof hash !== "object" && Object.keys(searchParams).length === 0) { - return value; // just a string; - } - const object = { - [Symbol.for("originalString")]: value, // so it doesn't appear raw in the table but can be easily retrieved later - host: url.host, - path: url.pathname, - searchParams, - ...(hash === "" ? {} : typeof hash === "string" ? { hash } : hash), - }; - return object; - } else if (value === null) { - return "null"; - } else { - return value.toString(); } - } - getParsedValue(key_path: string): string | Record { - let object = StolenDataEntry.parseValue(this.value); - for (const key of key_path.split(".")) { - if (key === "") continue; - object = StolenDataEntry.parseValue(object[key]); + getPriority() { + let priority = 0; + priority += Math.min(this.value.length, 50); + const url = new URL(this.request.originalURL); + if (this.value.includes(url.host)) { + priority += 100; + } + if (this.value.includes(url.pathname)) { + priority += 100; + } + if (this.source === 'cookie') { + priority += 200; + } + return priority; } - return object; - } - mark() { - const had_been_marked_before = this.marked; - this.marked = true; - if (!had_been_marked_before) { - this.emit("change"); + get isMarked() { + return this.marked; } - } - unmark() { - const had_been_marked_before = this.marked; - this.marked = false; - if (had_been_marked_before) { - this.emit("change"); + hasValue(value: string) { + return this.value === value; } - } - toggleMark() { - if (this.marked) { - this.unmark(); - } else { - this.mark(); + static parseValue(value: unknown): string | Record { + if (isBase64JSON(value)) { + return StolenDataEntry.parseValue({ + base64: JSON.parse(atob(value)), + }); + } + if (value === undefined) { + return ''; + } + if (isJSONObject(value)) { + const object = parseToObject(value); + return object; + } else if (isURL(value)) { + const url = new URL(value); + let hash = url.hash; + if (hash.includes('=')) { + //facebook sometimes includes querystring-encoded data into the hash... attempt to parse it + try { + hash = Object.fromEntries( + hash + .slice(1) + .split('&') + .map((kv) => kv.split('=')) + ); + } catch (e) { + // failed to parse as query string + console.log( + 'Failed attempt to parse hash location as query string, probably safe to ignore:', + e + ); + } + } + const searchParams = Object.fromEntries( + ( + url.searchParams as unknown as { + entries: () => Iterable<[string, string]>; + } + ).entries() + ); + if ( + typeof hash !== 'object' && + Object.keys(searchParams).length === 0 + ) { + return value; // just a string; + } + const object = { + [Symbol.for('originalString')]: value, // so it doesn't appear raw in the table but can be easily retrieved later + host: url.host, + path: url.pathname, + searchParams, + ...(hash === '' + ? {} + : typeof hash === 'string' + ? { hash } + : hash), + }; + return object; + } else if (value === null) { + return 'null'; + } else { + return value.toString(); + } } - } - private classify(): keyof typeof Classifications { - let result: keyof typeof Classifications; - if (this.exposesOrigin()) { - result = "history"; - } else { - result = "id"; + getParsedValue( + key_path: string + ): string | Record { + let object = StolenDataEntry.parseValue(this.value); + for (const key of key_path.split('.')) { + if (key === '') continue; + object = StolenDataEntry.parseValue(object[key]); + } + return object; } - return result; - } - isRelatedToID() { - return this.request.stolenData.some( - (entry) => entry.classification == "id" - ); - } - - matchesHAREntry(har: HAREntry): boolean { - return this.request.matchesHAREntry(har); - } - - getValuePreview(key = ""): string { - const value = this.getParsedValue(key); - const str = - typeof value === "object" && value[Symbol.for("originalString")] - ? (value[Symbol.for("originalString")] as string) - : value.toString(); - if (typeof value !== "object" && this.classification == "id") { - return maskString(value, 1 / 3, ID_PREVIEW_MAX_LENGTH); - } else if ( - typeof value === "object" && - value[Symbol.for("originalString")] - ) { - return value[Symbol.for("originalString")] as string; - } else { - return str; + mark() { + const had_been_marked_before = this.marked; + this.marked = true; + if (!had_been_marked_before) { + this.emit('change'); + } } - } - getUniqueKey() { - return this.request.shorthost + ";" + this.name + ";" + this.value; - } - - exposesOrigin(): boolean { - return this.exposesHost() || this.exposesPath(); - } - - autoMark() { - if ( - this.classification == "history" || - ((this.source === "cookie" || - this.name.toLowerCase().includes("id") || - this.name.toLowerCase().includes("cookie") || - this.name.toLowerCase().includes("ga") || - this.name.toLowerCase().includes("ses") || - this.name.toLowerCase().includes("fb")) && - this.value.length > MIN_COOKIE_LENGTH_FOR_AUTO_MARK) - ) { - if ( - (this.request.shorthost.includes("google") || - this.request.shorthost.includes("youtube")) && - this.name == "CONSENT" - ) { - // this cookie contains "YES" and might distract the person looking at it into thinking i gave consent on the reported site - return; - } - this.mark(); + unmark() { + const had_been_marked_before = this.marked; + this.marked = false; + if (had_been_marked_before) { + this.emit('change'); + } } - } - exposesPath() { - return ( - this.request.originalPathname !== "/" && - [this.value, safeDecodeURIComponent(this.value)].some((haystack) => - haystack.includes(this.request.originalPathname) - ) - ); - } + toggleMark() { + if (this.marked) { + this.unmark(); + } else { + this.mark(); + } + } - exposesHost() { - return [this.value, safeDecodeURIComponent(this.value)].some((haystack) => - haystack.includes(getshorthost(this.request.origin)) - ); - } + private classify(): keyof typeof Classifications { + let result: keyof typeof Classifications; + if (this.exposesOrigin()) { + result = 'history'; + } else { + result = 'id'; + } + return result; + } + + isRelatedToID() { + return this.request.stolenData.some( + (entry) => entry.classification == 'id' + ); + } + + matchesHAREntry(har: HAREntry): boolean { + return this.request.matchesHAREntry(har); + } + + getValuePreview(key = ''): string { + const value = this.getParsedValue(key); + const str = + typeof value === 'object' && value[Symbol.for('originalString')] + ? (value[Symbol.for('originalString')] as string) + : value.toString(); + if (typeof value !== 'object' && this.classification == 'id') { + return maskString(value, 1 / 3, ID_PREVIEW_MAX_LENGTH); + } else if ( + typeof value === 'object' && + value[Symbol.for('originalString')] + ) { + return value[Symbol.for('originalString')] as string; + } else { + return str; + } + } + + getUniqueKey() { + return this.request.shorthost + ';' + this.name + ';' + this.value; + } + + exposesOrigin(): boolean { + return this.exposesHost() || this.exposesPath(); + } + + autoMark() { + if ( + this.classification == 'history' || + ((this.source === 'cookie' || + this.name.toLowerCase().includes('id') || + this.name.toLowerCase().includes('cookie') || + this.name.toLowerCase().includes('ga') || + this.name.toLowerCase().includes('ses') || + this.name.toLowerCase().includes('fb')) && + this.value.length > MIN_COOKIE_LENGTH_FOR_AUTO_MARK) + ) { + if ( + (this.request.shorthost.includes('google') || + this.request.shorthost.includes('youtube')) && + this.name == 'CONSENT' + ) { + // this cookie contains "YES" and might distract the person looking at it into thinking i gave consent on the reported site + return; + } + this.mark(); + } + } + + exposesPath() { + return ( + this.request.originalPathname !== '/' && + [this.value, safeDecodeURIComponent(this.value)].some((haystack) => + haystack.includes(this.request.originalPathname) + ) + ); + } + + exposesHost() { + return [this.value, safeDecodeURIComponent(this.value)].some( + (haystack) => haystack.includes(getshorthost(this.request.origin)) + ); + } }