Skip to content

Commit 0d80938

Browse files
committed
feat(new tool): HAR file sanitizer
Fix #811
1 parent 08d977b commit 0d80938

File tree

6 files changed

+461
-1
lines changed

6 files changed

+461
-1
lines changed
+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<script setup lang="ts">
2+
import { downloadFile } from './lib/downloadFile';
3+
import { defaultScrubItems, getHarInfo, sanitize } from './lib/har_sanitize';
4+
5+
// interface ScrubItems {
6+
// words: Set<string>
7+
// mimeTypes: Set<string>
8+
// }
9+
10+
type ScrubState = Record<ScrubType, Record<string, boolean>>;
11+
type ScrubType =
12+
| 'cookies'
13+
| 'headers'
14+
| 'queryArgs'
15+
| 'postParams'
16+
| 'mimeTypes';
17+
18+
const typeMap: Record<ScrubType, string> = {
19+
cookies: 'Cookies',
20+
mimeTypes: 'Mime Types',
21+
headers: 'Headers',
22+
postParams: 'Post Body Params',
23+
queryArgs: 'Query String Parameters',
24+
};
25+
26+
const defaulScrubState: ScrubState = {
27+
cookies: {},
28+
headers: {},
29+
queryArgs: {},
30+
postParams: {},
31+
mimeTypes: {},
32+
};
33+
const scrubItemsToClean = ref<ScrubState>(defaulScrubState);
34+
35+
function getScrubableItems(input: string): ScrubState {
36+
const rawItems = getHarInfo(input);
37+
const output = { ...defaulScrubState };
38+
Object.entries(rawItems).forEach(([key, items]: [string, string[]]) => {
39+
output[key as ScrubType] = items.reduce(
40+
(acc, curr) => {
41+
if (!curr) {
42+
return acc;
43+
}
44+
acc[curr] = defaultScrubItems.includes(curr);
45+
return acc;
46+
},
47+
{} as Record<string, boolean>,
48+
);
49+
return null;
50+
});
51+
return output;
52+
}
53+
54+
function sanitizeHar(input: string, scrubItems: ScrubState) {
55+
const words = new Set<string>();
56+
Object.entries(scrubItems.cookies).forEach(([key, val]) => {
57+
if (val) {
58+
words.add(key);
59+
}
60+
});
61+
Object.entries(scrubItems.headers).forEach(([key, val]) => {
62+
if (val) {
63+
words.add(key);
64+
}
65+
});
66+
Object.entries(scrubItems.queryArgs).forEach(([key, val]) => {
67+
if (val) {
68+
words.add(key);
69+
}
70+
});
71+
Object.entries(scrubItems.postParams).forEach(([key, val]) => {
72+
if (val) {
73+
words.add(key);
74+
}
75+
});
76+
77+
const mimeTypes = new Set<string>();
78+
Object.entries(scrubItems.mimeTypes).forEach(([key, val]) => {
79+
if (val) {
80+
mimeTypes.add(key);
81+
}
82+
});
83+
return sanitize(input, {
84+
scrubWords: [...words],
85+
scrubMimetypes: [...mimeTypes],
86+
});
87+
}
88+
89+
const file = ref<File | null>(null);
90+
91+
const error = ref('');
92+
93+
// const base64OutputImage = ref('');
94+
// const fileName = ref('');
95+
// const format = ref('jpg');
96+
// const formats = [
97+
// { value: 'jpg', label: 'JPEG' },
98+
// { value: 'png', label: 'PNG' },
99+
// ];
100+
// const { download } = useDownloadFileFromBase64(
101+
// {
102+
// source: base64OutputImage,
103+
// filename: fileName,
104+
// });
105+
106+
function readAsTextAsync(file: File) {
107+
return new Promise<string>((resolve, reject) => {
108+
const reader = new FileReader();
109+
reader.readAsText(file);
110+
reader.onload = () => resolve(reader.result?.toString() ?? '');
111+
reader.onerror = error => reject(error);
112+
});
113+
}
114+
115+
const harContent = ref('');
116+
async function onFileUploaded(uploadedFile: File) {
117+
file.value = uploadedFile;
118+
harContent.value = await readAsTextAsync(uploadedFile);
119+
120+
error.value = '';
121+
try {
122+
scrubItemsToClean.value = getScrubableItems(harContent.value);
123+
}
124+
catch (e: any) {
125+
error.value = e.toString();
126+
}
127+
}
128+
129+
function processHar() {
130+
downloadFile(sanitizeHar(harContent.value, scrubItemsToClean.value), `sanitized-${file.value?.name}`);
131+
}
132+
</script>
133+
134+
<template>
135+
<div>
136+
<div style="flex: 0 0 100%" mb-3>
137+
<div mx-auto max-w-600px>
138+
<c-file-upload
139+
title="Drag and drop a HAR file here, or click to select a file"
140+
accept=".har" @file-upload="onFileUploaded"
141+
/>
142+
</div>
143+
</div>
144+
145+
<c-alert v-if="error" title="Error">
146+
{{ error }}
147+
</c-alert>
148+
149+
<c-card v-for="(title, key) in typeMap" :key="key" :title="title">
150+
<n-checkbox @update:checked="(allChecked: boolean) => Object.keys(scrubItemsToClean[key]).forEach((name) => scrubItemsToClean[key][name] = allChecked)">
151+
All {{ title }}
152+
</n-checkbox>
153+
<n-checkbox v-for="(checked, name) in scrubItemsToClean[key]" :key="name" v-model:checked="scrubItemsToClean[key][name]">
154+
{{ name }}
155+
</n-checkbox>
156+
</c-card>
157+
158+
<div v-if="!error" mt-3 flex justify-center>
159+
<c-button @click="processHar()">
160+
Sanitize and download
161+
</c-button>
162+
</div>
163+
</div>
164+
</template>

src/tools/har-sanitizer/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ClearFormatting } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'HAR Sanitizer',
6+
path: '/har-sanitizer',
7+
description: 'HAR Files Sanitizer',
8+
keywords: ['har', 'sanitizer'],
9+
component: () => import('./har-sanitizer.vue'),
10+
icon: ClearFormatting,
11+
createdAt: new Date('2024-06-17'),
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export function downloadFile(harOutput: string, name: string) {
2+
const blob = new Blob([harOutput], { type: 'application/json' });
3+
4+
// Create a URL for the Blob
5+
const url = URL.createObjectURL(blob);
6+
7+
// Create an anchor element to trigger the download
8+
const a = document.createElement('a');
9+
a.href = url;
10+
// Set file name
11+
a.download = name;
12+
a.style.display = 'none';
13+
document.body.appendChild(a);
14+
a.click();
15+
16+
// Clean up by removing the anchor and revoking the URL
17+
document.body.removeChild(a);
18+
URL.revokeObjectURL(url);
19+
}

0 commit comments

Comments
 (0)