Add possibility to generate a trimmed HAR file

This commit is contained in:
Kuba Orlik 2021-11-09 17:47:42 +01:00
parent 399b5eca9d
commit a859d0239f
4 changed files with 159 additions and 21 deletions

View File

@ -1,25 +1,42 @@
import { StolenDataEntry } from "./stolen-data-entry"; import { StolenDataEntry } from "./stolen-data-entry";
import { getshorthost, parseCookie, Request } from "./util"; import { getshorthost, parseCookie, Request } from "./util";
type NameValue = { name: string; value: string };
export type HAREntry = { export type HAREntry = {
pageref: string; pageref: string;
startedDateTime: string; startedDateTime: string;
request: { request: {
bodySize: number; bodySize: number;
cookies: {}[]; cookies: NameValue[];
headers: {}[]; headers: NameValue[];
headersSize: number; headersSize: number;
httpVersion: string; httpVersion: string;
method: string; method: string;
postData: { postData?: {
mimeType: string; mimeType: string;
params: { name: string; value: string }[]; params: NameValue[];
text: string; text: string;
}; };
queryString: { name: string; value: string }[]; queryString: NameValue[];
url: string; url: string;
}; };
response: {}; // not relevant response: {
status: number;
statusText: string;
httpVersion: string;
headers: NameValue[];
cookies: NameValue[];
content: {
mimeType: string;
size: number;
encoding: "base64";
text: string;
};
redirectURL: "";
headersSize: number;
bodySize: number;
}; // not relevant
cache: {}; cache: {};
timings: {}; timings: {};
time: number; time: number;
@ -48,6 +65,13 @@ export default class ExtendedRequest {
public initialized = false; public initialized = false;
public stolenData: StolenDataEntry[]; public stolenData: StolenDataEntry[];
constructor(public data: Request) {
this.tabId = data.tabId;
this.url = data.url;
this.requestHeaders = data.requestHeaders;
this.shorthost = getshorthost(data.url);
}
async init() { async init() {
await this.cacheOrigin(); await this.cacheOrigin();
this.initialized = true; this.initialized = true;
@ -67,6 +91,8 @@ export default class ExtendedRequest {
); );
if (headers.Referer) { if (headers.Referer) {
url = headers.Referer; url = headers.Referer;
} else {
url = this.data.url;
} }
} }
@ -90,8 +116,10 @@ export default class ExtendedRequest {
} }
getReferer() { getReferer() {
return this.data.requestHeaders.filter((h) => h.name === "Referer")[0] return (
.value; this.data.requestHeaders.filter((h) => h.name === "Referer")?.[0].value ||
"missing-referrer"
);
} }
exposesOrigin() { exposesOrigin() {
@ -180,13 +208,6 @@ export default class ExtendedRequest {
); );
} }
constructor(public data: Request) {
this.tabId = data.tabId;
this.url = data.url;
this.requestHeaders = data.requestHeaders;
this.shorthost = getshorthost(data.url);
}
hasMark() { hasMark() {
return this.stolenData.some((data) => data.hasMark()); return this.stolenData.some((data) => data.hasMark());
} }
@ -204,4 +225,57 @@ export default class ExtendedRequest {
const hrq = har.request; const hrq = har.request;
return rq.url == hrq.url; return rq.url == hrq.url;
} }
toHAR(): HAREntry {
return {
pageref: "page_1",
startedDateTime: `${new Date().toJSON().replace("Z", "+01:00")}`,
request: {
bodySize: 0,
method: this.data.method,
url: this.data.url,
headersSize: 100,
httpVersion: "HTTP/2",
headers: this.data.requestHeaders as NameValue[],
cookies: this.getCookieData().map((cookie) => ({
name: cookie.name,
value: cookie.value,
})),
queryString: this.getQueryParams().map((param) => ({
name: param.name,
value: param.value,
})),
},
response: {
status: 200,
statusText: "OK",
httpVersion: "HTTP/2",
headers: [],
cookies: [],
content: {
mimeType: "text/plain",
size: 15,
encoding: "base64",
text: "ZG9lc24ndCBtYXR0ZXIK",
},
redirectURL: "",
headersSize: 15,
bodySize: 15,
},
cache: {},
timings: {
blocked: -1,
dns: 0,
connect: 0,
ssl: 0,
send: 0,
wait: 79,
receive: 0,
},
time: 79,
_securityState: "secure",
serverIPAddress: "31.13.92.36",
connection: "443",
};
}
} }

View File

@ -23,6 +23,7 @@ export default function DomainSummary({
<li> <li>
Właściciel domeny <strong>{cluster.id}</strong> otrzymał:{" "} Właściciel domeny <strong>{cluster.id}</strong> otrzymał:{" "}
<ul> <ul>
<li>Mój adres IP</li>
{cluster {cluster
.getMarkedEntries() .getMarkedEntries()
.sort((entryA, entryB) => (entryA.value > entryB.value ? -1 : 1)) .sort((entryA, entryB) => (entryA.value > entryB.value ? -1 : 1))

View File

@ -1,12 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { HAREntry } from "../extended-request"; import { HAREntry } from "../extended-request";
import { StolenDataEntry } from "../stolen-data-entry"; import { StolenDataEntry } from "../stolen-data-entry";
import { getshorthost, unique } from "../util";
function handleNewFile( function handleNewFile(
element: HTMLInputElement, element: HTMLInputElement,
marked_entries: StolenDataEntry[], marked_entries: StolenDataEntry[],
setFiltered: (Blob) => void setFiltered: (Blob) => 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);
@ -22,6 +23,35 @@ function handleNewFile(
reader.readAsText(element.files[0]); reader.readAsText(element.files[0]);
} }
function generateFakeHAR(marked_entries: StolenDataEntry[]) {
const requests = marked_entries.map((entry) => entry.request);
return {
log: {
version: "1.2",
creator: {
name: "Firefox",
version: "94.0",
},
browser: {
name: "Firefox",
version: "94.0",
},
pages: [
{
startedDateTime: "2021-11-08T20:27:23.195+01:00",
id: "page_1",
title: "HAR DUmp",
pageTimings: {
onContentLoad: 467,
onLoad: 4226,
},
},
],
entries: unique(requests).map((r) => r.toHAR()),
},
};
}
export default function HARConverter({ export default function HARConverter({
marked_entries, marked_entries,
}: { }: {
@ -29,6 +59,7 @@ export default function HARConverter({
}) { }) {
const [filtered, setFiltered] = useState<Blob | null>(null); const [filtered, setFiltered] = useState<Blob | null>(null);
const [filename, setFilename] = useState(""); const [filename, setFilename] = useState("");
const fakeHAR = generateFakeHAR(marked_entries);
return ( return (
<div> <div>
<input <input
@ -48,6 +79,16 @@ export default function HARConverter({
</a> </a>
)) || )) ||
null} null}
<a
href={URL.createObjectURL(
new Blob([JSON.stringify(fakeHAR)], { type: "application/json" })
)}
download={`${getshorthost(
marked_entries[0].request.originalURL
)}-${new Date().toJSON()}-faked.har`}
>
Pobierz "zfałszowany" HAR
</a>
</div> </div>
); );
} }

32
util.ts
View File

@ -6,9 +6,31 @@ export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
export type Unarray<T> = T extends Array<infer R> ? R : T; export type Unarray<T> = T extends Array<infer R> ? R : T;
export type Tab = Unarray<Unpromisify<ReturnType<typeof browser.tabs.query>>>; export type Tab = Unarray<Unpromisify<ReturnType<typeof browser.tabs.query>>>;
export type Request = Parameters< export type Request = {
Parameters<typeof browser.webRequest.onBeforeSendHeaders.addListener>[0] cookieStoreId?: string;
>[0]; documentUrl?: string; // RL of the document in which the resource will be loaded. For example, if the web page at "https://example.com" contains an image or an iframe, then the documentUrl for the image or iframe will be "https://example.com". For a top-level document, documentUrl is undefined.
frameId: number;
incognito?: boolean;
method: string;
originUrl: string;
parentFrameId: number;
proxyInfo?: {
host: string;
port: number;
type: string;
username: string;
proxyDNS: boolean;
failoverTimeout: number;
};
requestHeaders?: { name: string; value?: string; binaryValue?: number[] }[];
requestId: string;
tabId: number;
thirdParty?: boolean;
timeStamp: number;
type: string;
url: string; // the target of the request;
urlClassification?: { firstParty: string[]; thirdParty: string[] };
};
export function getshorthost(host: string) { export function getshorthost(host: string) {
return host return host
@ -83,8 +105,8 @@ export function hyphenate(str: string): string {
return str.replace(/[_\[A-Z]/g, `${String.fromCharCode(173)}$&`); return str.replace(/[_\[A-Z]/g, `${String.fromCharCode(173)}$&`);
} }
export function unique(array: string[]) { export function unique<T>(array: T[]): Array<T> {
return Array.from(new Set(array)); return Array.from(new Set<T>(array));
} }
export function allSubhosts(host: string) { export function allSubhosts(host: string) {