mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-07 14:57:12 -04:00
Merge c5c4e5d026
into 07eea0f484
This commit is contained in:
commit
309e2c1388
14 changed files with 7042 additions and 8486 deletions
|
@ -286,6 +286,9 @@
|
||||||
"watchTriggerable": true,
|
"watchTriggerable": true,
|
||||||
"watchWithFilter": true,
|
"watchWithFilter": true,
|
||||||
"whenever": true,
|
"whenever": true,
|
||||||
"toValue": true
|
"toValue": true,
|
||||||
|
"injectLocal": true,
|
||||||
|
"provideLocal": true,
|
||||||
|
"useClipboardItems": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
auto-imports.d.ts
vendored
9
auto-imports.d.ts
vendored
|
@ -36,6 +36,7 @@ declare global {
|
||||||
const h: typeof import('vue')['h']
|
const h: typeof import('vue')['h']
|
||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
const inject: typeof import('vue')['inject']
|
const inject: typeof import('vue')['inject']
|
||||||
|
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
@ -65,6 +66,7 @@ declare global {
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: typeof import('vue')['provide']
|
||||||
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
const reactify: typeof import('@vueuse/core')['reactify']
|
const reactify: typeof import('@vueuse/core')['reactify']
|
||||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||||
const reactive: typeof import('vue')['reactive']
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
@ -128,6 +130,7 @@ declare global {
|
||||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||||
const useCached: typeof import('@vueuse/core')['useCached']
|
const useCached: typeof import('@vueuse/core')['useCached']
|
||||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||||
|
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||||
|
@ -326,6 +329,7 @@ declare module 'vue' {
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
@ -355,6 +359,7 @@ declare module 'vue' {
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
@ -418,6 +423,7 @@ declare module 'vue' {
|
||||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||||
|
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||||
|
@ -610,6 +616,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
@ -639,6 +646,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
@ -702,6 +710,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||||
|
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||||
|
|
4
components.d.ts
vendored
4
components.d.ts
vendored
|
@ -82,6 +82,7 @@ declare module '@vue/runtime-core' {
|
||||||
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
|
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
|
||||||
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
|
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
|
||||||
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
|
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
|
||||||
|
HarSanitizer: typeof import('./src/tools/har-sanitizer/har-sanitizer.vue')['default']
|
||||||
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
|
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
|
||||||
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
|
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
|
||||||
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
|
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
|
||||||
|
@ -133,8 +134,8 @@ declare module '@vue/runtime-core' {
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
@ -142,7 +143,6 @@ declare module '@vue/runtime-core' {
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NTable: typeof import('naive-ui')['NTable']
|
|
||||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||||
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||||
|
|
|
@ -115,6 +115,7 @@
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/har-format": "^1.2.16",
|
||||||
"@types/jsdom": "^21.0.0",
|
"@types/jsdom": "^21.0.0",
|
||||||
"@types/lodash": "^4.14.192",
|
"@types/lodash": "^4.14.192",
|
||||||
"@types/mime-types": "^2.1.1",
|
"@types/mime-types": "^2.1.1",
|
||||||
|
|
15056
pnpm-lock.yaml
generated
15056
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -128,7 +128,7 @@ function activateOption(option: PaletteOption) {
|
||||||
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
||||||
|
|
||||||
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
||||||
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
<div ml-3 mt-3 text-sm text-primary font-bold op-60>
|
||||||
{{ category }}
|
{{ category }}
|
||||||
</div>
|
</div>
|
||||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
||||||
|
|
150
src/tools/har-sanitizer/har-sanitizer.vue
Normal file
150
src/tools/har-sanitizer/har-sanitizer.vue
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { downloadFile } from './lib/downloadFile';
|
||||||
|
import { defaultScrubItems, getHarInfo, sanitize } from './lib/har_sanitize';
|
||||||
|
|
||||||
|
type ScrubState = Record<ScrubType, Record<string, boolean>>;
|
||||||
|
type ScrubType =
|
||||||
|
| 'cookies'
|
||||||
|
| 'headers'
|
||||||
|
| 'queryArgs'
|
||||||
|
| 'postParams'
|
||||||
|
| 'mimeTypes';
|
||||||
|
|
||||||
|
const typeMap: Record<ScrubType, string> = {
|
||||||
|
cookies: 'Cookies',
|
||||||
|
mimeTypes: 'Mime Types',
|
||||||
|
headers: 'Headers',
|
||||||
|
postParams: 'Post Body Params',
|
||||||
|
queryArgs: 'Query String Parameters',
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaulScrubState: ScrubState = {
|
||||||
|
cookies: {},
|
||||||
|
headers: {},
|
||||||
|
queryArgs: {},
|
||||||
|
postParams: {},
|
||||||
|
mimeTypes: {},
|
||||||
|
};
|
||||||
|
const scrubItemsToClean = ref<ScrubState>(defaulScrubState);
|
||||||
|
|
||||||
|
function getScrubableItems(input: string): ScrubState {
|
||||||
|
const rawItems = getHarInfo(input);
|
||||||
|
const output = { ...defaulScrubState };
|
||||||
|
Object.entries(rawItems).forEach(([key, items]: [string, string[]]) => {
|
||||||
|
output[key as ScrubType] = items.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
if (!curr) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
acc[curr] = defaultScrubItems.includes(curr);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, boolean>,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeHar(input: string, scrubItems: ScrubState) {
|
||||||
|
const words = new Set<string>();
|
||||||
|
Object.entries(scrubItems.cookies).forEach(([key, val]) => {
|
||||||
|
if (val) {
|
||||||
|
words.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.entries(scrubItems.headers).forEach(([key, val]) => {
|
||||||
|
if (val) {
|
||||||
|
words.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.entries(scrubItems.queryArgs).forEach(([key, val]) => {
|
||||||
|
if (val) {
|
||||||
|
words.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.entries(scrubItems.postParams).forEach(([key, val]) => {
|
||||||
|
if (val) {
|
||||||
|
words.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mimeTypes = new Set<string>();
|
||||||
|
Object.entries(scrubItems.mimeTypes).forEach(([key, val]) => {
|
||||||
|
if (val) {
|
||||||
|
mimeTypes.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sanitize(input, {
|
||||||
|
scrubWords: [...words],
|
||||||
|
scrubMimetypes: [...mimeTypes],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = ref<File | null>(null);
|
||||||
|
|
||||||
|
const error = ref('');
|
||||||
|
|
||||||
|
function readAsTextAsync(file: File) {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(file);
|
||||||
|
reader.onload = () => resolve(reader.result?.toString() ?? '');
|
||||||
|
reader.onerror = error => reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const harContent = ref('');
|
||||||
|
async function onFileUploaded(uploadedFile: File) {
|
||||||
|
file.value = uploadedFile;
|
||||||
|
harContent.value = await readAsTextAsync(uploadedFile);
|
||||||
|
|
||||||
|
error.value = '';
|
||||||
|
try {
|
||||||
|
scrubItemsToClean.value = getScrubableItems(harContent.value);
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
error.value = e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processHar() {
|
||||||
|
downloadFile(sanitizeHar(harContent.value, scrubItemsToClean.value), `sanitized-${file.value?.name}`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="flex: 0 0 100%" mb-3>
|
||||||
|
<div mx-auto max-w-600px>
|
||||||
|
<c-file-upload
|
||||||
|
title="Drag and drop a HAR file here, or click to select a file"
|
||||||
|
accept=".har" @file-upload="onFileUploaded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<c-alert v-if="error" title="Error">
|
||||||
|
{{ error }}
|
||||||
|
</c-alert>
|
||||||
|
|
||||||
|
<div v-for="(title, key) in typeMap" :key="key" mb-1>
|
||||||
|
<c-card v-if="Object.keys(scrubItemsToClean[key]).length" :title="title">
|
||||||
|
<n-checkbox font-size-5 @update:checked="(allChecked: boolean) => Object.keys(scrubItemsToClean[key]).forEach((name) => scrubItemsToClean[key][name] = allChecked)">
|
||||||
|
All {{ title }}
|
||||||
|
</n-checkbox>
|
||||||
|
<n-space size="large">
|
||||||
|
<n-checkbox v-for="(checked, name) in scrubItemsToClean[key]" :key="name" v-model:checked="scrubItemsToClean[key][name]" style="width: 150px">
|
||||||
|
{{ name }}
|
||||||
|
</n-checkbox>
|
||||||
|
</n-space>
|
||||||
|
</c-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!error" mt-3 flex justify-center>
|
||||||
|
<c-button @click="processHar()">
|
||||||
|
Sanitize and download
|
||||||
|
</c-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
12
src/tools/har-sanitizer/index.ts
Normal file
12
src/tools/har-sanitizer/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { ClearFormatting } from '@vicons/tabler';
|
||||||
|
import { defineTool } from '../tool';
|
||||||
|
|
||||||
|
export const tool = defineTool({
|
||||||
|
name: 'HAR Sanitizer',
|
||||||
|
path: '/har-sanitizer',
|
||||||
|
description: 'HAR Files Sanitizer',
|
||||||
|
keywords: ['har', 'sanitizer'],
|
||||||
|
component: () => import('./har-sanitizer.vue'),
|
||||||
|
icon: ClearFormatting,
|
||||||
|
createdAt: new Date('2024-06-17'),
|
||||||
|
});
|
19
src/tools/har-sanitizer/lib/downloadFile.ts
Normal file
19
src/tools/har-sanitizer/lib/downloadFile.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export function downloadFile(harOutput: string, name: string) {
|
||||||
|
const blob = new Blob([harOutput], { type: 'application/json' });
|
||||||
|
|
||||||
|
// Create a URL for the Blob
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// Create an anchor element to trigger the download
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
// Set file name
|
||||||
|
a.download = name;
|
||||||
|
a.style.display = 'none';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
// Clean up by removing the anchor and revoking the URL
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
237
src/tools/har-sanitizer/lib/har_sanitize.tsx
Normal file
237
src/tools/har-sanitizer/lib/har_sanitize.tsx
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
/* eslint-disable prefer-regex-literals */
|
||||||
|
import type { Cookie, Har, Header, Param, QueryString } from 'har-format';
|
||||||
|
|
||||||
|
export interface PossibleScrubItems {
|
||||||
|
headers: string[]
|
||||||
|
cookies: string[]
|
||||||
|
queryArgs: string[]
|
||||||
|
postParams: string[]
|
||||||
|
mimeTypes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMimeTypesList = ['application/javascript', 'text/javascript'];
|
||||||
|
|
||||||
|
const defaultWordList = [
|
||||||
|
'Authorization',
|
||||||
|
'SAMLRequest',
|
||||||
|
'SAMLResponse',
|
||||||
|
'access_token',
|
||||||
|
'appID',
|
||||||
|
'assertion',
|
||||||
|
'auth',
|
||||||
|
'authenticity_token',
|
||||||
|
'challenge',
|
||||||
|
'client_id',
|
||||||
|
'client_secret',
|
||||||
|
'code',
|
||||||
|
'code_challenge',
|
||||||
|
'code_verifier',
|
||||||
|
'email',
|
||||||
|
'facetID',
|
||||||
|
'fcParams',
|
||||||
|
'id_token',
|
||||||
|
'password',
|
||||||
|
'refresh_token',
|
||||||
|
'serverData',
|
||||||
|
'shdf',
|
||||||
|
'state',
|
||||||
|
'token',
|
||||||
|
'usg',
|
||||||
|
'vses2',
|
||||||
|
'x-client-data',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const defaultScrubItems = [...defaultMimeTypesList, ...defaultWordList];
|
||||||
|
|
||||||
|
// The default list of regexes that aren't word dependent
|
||||||
|
// Uses double list so it matches format of word regex
|
||||||
|
const defaultRegex = [
|
||||||
|
[
|
||||||
|
// Redact signature on JWTs
|
||||||
|
{
|
||||||
|
regex: new RegExp(
|
||||||
|
'\\b(ey[A-Za-z0-9-_=]+)\\.(ey[A-Za-z0-9-_=]+)\\.[A-Za-z0-9-_.+/=]+\\b',
|
||||||
|
'g',
|
||||||
|
),
|
||||||
|
replacement: '$1.$2.redacted',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
function buildRegex(word: string) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
// [full word]=[capture]
|
||||||
|
regex: new RegExp(
|
||||||
|
`([\\s";,&?]+${word}=)([\\w+-_/=#|.%&:!*()\`~'"]+?)(&|\\\\",|",|"\\s|"}}|;){1}`,
|
||||||
|
'g',
|
||||||
|
),
|
||||||
|
replacement: `$1[${word} redacted]$3`,
|
||||||
|
},
|
||||||
|
// Set up this way in case "value" isn't directly after "name"
|
||||||
|
// {
|
||||||
|
// "name": "[word]",
|
||||||
|
// "something": "not wanted",
|
||||||
|
// "value": "[capture]"
|
||||||
|
// }
|
||||||
|
{
|
||||||
|
regex: new RegExp(
|
||||||
|
`("name": "${word}",[\\s\\w+:"-\\%!*()\`~'.,#]*?"value": ")((?:\\\\"|[^"])*?)(")`,
|
||||||
|
'g',
|
||||||
|
),
|
||||||
|
replacement: `$1[${word} redacted]$3`,
|
||||||
|
},
|
||||||
|
// "name" comes after "value"
|
||||||
|
// {
|
||||||
|
// "value": "[capture]",
|
||||||
|
// "something": "not wanted",
|
||||||
|
// "name": "[word]"
|
||||||
|
// }
|
||||||
|
{
|
||||||
|
regex: new RegExp(
|
||||||
|
`("value": ")([\\w+-_:&+=#$~/()\\\\.\\,*!|%"\\s;]+)("[,\\s}}]+)([\\s\\w+:"-\\\\%!*\`()~'#.]*"name": "${word}")`,
|
||||||
|
'g',
|
||||||
|
),
|
||||||
|
replacement: `$1[${word} redacted]$3$4`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeContentForMimeTypes(input: string, scrubList: string[]) {
|
||||||
|
const harJSON = JSON.parse(input);
|
||||||
|
|
||||||
|
const entries = harJSON.log.entries;
|
||||||
|
if (!entries) {
|
||||||
|
throw new Error('failed to find entries in HAR file');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const response = entry.response;
|
||||||
|
if (response && scrubList.includes(response.content.mimeType)) {
|
||||||
|
response.content.text = `[${response.content.mimeType} redacted]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(harJSON, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHarInfo(input: string): PossibleScrubItems {
|
||||||
|
const output = {
|
||||||
|
headers: new Set<string>(),
|
||||||
|
queryArgs: new Set<string>(),
|
||||||
|
cookies: new Set<string>(),
|
||||||
|
postParams: new Set<string>(),
|
||||||
|
mimeTypes: new Set<string>(),
|
||||||
|
};
|
||||||
|
const harJSON: Har = JSON.parse(input);
|
||||||
|
|
||||||
|
const entries = harJSON.log.entries;
|
||||||
|
if (!entries) {
|
||||||
|
throw new Error('failed to find entries in HAR file');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const response = entry.response;
|
||||||
|
response.headers.map((header: Header) => output.headers.add(header.name));
|
||||||
|
response.cookies.map((cookie: Cookie) => output.cookies.add(cookie.name));
|
||||||
|
output.mimeTypes.add(response.content.mimeType);
|
||||||
|
|
||||||
|
const request = entry.request;
|
||||||
|
request.headers.map((header: Header) => output.headers.add(header.name));
|
||||||
|
request.queryString.map((arg: QueryString) =>
|
||||||
|
output.queryArgs.add(arg.name),
|
||||||
|
);
|
||||||
|
request.cookies.map((cookie: Cookie) => output.cookies.add(cookie.name));
|
||||||
|
if (request.postData) {
|
||||||
|
request.postData.params?.map((param: Param) =>
|
||||||
|
output.postParams.add(param.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
headers: [...output.headers].sort((a, b) => a.localeCompare(b)),
|
||||||
|
queryArgs: [...output.queryArgs].sort((a, b) => a.localeCompare(b)),
|
||||||
|
cookies: [...output.cookies].sort((a, b) => a.localeCompare(b)),
|
||||||
|
postParams: [...output.postParams].sort((a, b) => a.localeCompare(b)),
|
||||||
|
mimeTypes: [...output.mimeTypes].sort((a, b) => a.localeCompare(b)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScrubMimeTypes(
|
||||||
|
options?: SanitizeOptions,
|
||||||
|
possibleScrubItems?: PossibleScrubItems,
|
||||||
|
) {
|
||||||
|
if (options?.allMimeTypes && !!possibleScrubItems) {
|
||||||
|
return possibleScrubItems.mimeTypes;
|
||||||
|
}
|
||||||
|
return options?.scrubMimetypes || defaultMimeTypesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScrubWords(
|
||||||
|
options?: SanitizeOptions,
|
||||||
|
possibleScrubItems?: PossibleScrubItems,
|
||||||
|
) {
|
||||||
|
let scrubWords = options?.scrubWords || [];
|
||||||
|
if (options?.allCookies && !!possibleScrubItems) {
|
||||||
|
scrubWords = scrubWords.concat(possibleScrubItems.cookies);
|
||||||
|
}
|
||||||
|
if (options?.allHeaders && !!possibleScrubItems) {
|
||||||
|
scrubWords = scrubWords.concat(possibleScrubItems.headers);
|
||||||
|
}
|
||||||
|
if (options?.allQueryArgs && !!possibleScrubItems) {
|
||||||
|
scrubWords = scrubWords.concat(possibleScrubItems.queryArgs);
|
||||||
|
}
|
||||||
|
if (options?.allPostParams && !!possibleScrubItems) {
|
||||||
|
scrubWords = scrubWords.concat(possibleScrubItems.postParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrubWords || defaultScrubItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SanitizeOptions {
|
||||||
|
scrubWords?: string[]
|
||||||
|
scrubMimetypes?: string[]
|
||||||
|
allCookies?: boolean
|
||||||
|
allHeaders?: boolean
|
||||||
|
allQueryArgs?: boolean
|
||||||
|
allMimeTypes?: boolean
|
||||||
|
allPostParams?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitize(input: string, options?: SanitizeOptions) {
|
||||||
|
let possibleScrubItems: PossibleScrubItems | undefined;
|
||||||
|
if (
|
||||||
|
options?.allCookies
|
||||||
|
|| options?.allHeaders
|
||||||
|
|| options?.allMimeTypes
|
||||||
|
|| options?.allQueryArgs
|
||||||
|
|| options?.allPostParams
|
||||||
|
) {
|
||||||
|
// we have to parse the HAR to get the full list of things we could scrub
|
||||||
|
possibleScrubItems = getHarInfo(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove specific mime responses first
|
||||||
|
input = removeContentForMimeTypes(
|
||||||
|
input,
|
||||||
|
getScrubMimeTypes(options, possibleScrubItems),
|
||||||
|
);
|
||||||
|
|
||||||
|
// trim the list of words we are looking for down to the ones actually in the HAR file
|
||||||
|
const wordList = getScrubWords(options, possibleScrubItems).filter(val =>
|
||||||
|
input.includes(val),
|
||||||
|
);
|
||||||
|
|
||||||
|
// build list of regexes needed to actually scrub the file
|
||||||
|
const wordSpecificScrubList = wordList.map(word => buildRegex(word));
|
||||||
|
const allScrubList = defaultRegex.concat(wordSpecificScrubList);
|
||||||
|
|
||||||
|
for (const scrubList of allScrubList) {
|
||||||
|
for (const scrub of scrubList) {
|
||||||
|
input = input.replace(scrub.regex, scrub.replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
18
src/tools/har-sanitizer/lib/hash.ts
Normal file
18
src/tools/har-sanitizer/lib/hash.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export type HashMap = Record<string, string[]>;
|
||||||
|
|
||||||
|
export function toHashString(obj: HashMap): string {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
Object.entries(obj).forEach(([key, stringArr]) => {
|
||||||
|
stringArr.forEach((value) => {
|
||||||
|
params.append(key, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return `#${params.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHashMap(hash: string): HashMap {
|
||||||
|
const existingParams = new URLSearchParams(hash.replace('#', ''));
|
||||||
|
return Object.fromEntries(
|
||||||
|
[...existingParams.keys()].map(key => [key, existingParams.getAll(key)]),
|
||||||
|
);
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import { tool as jsonToXml } from './json-to-xml';
|
||||||
import { tool as regexTester } from './regex-tester';
|
import { tool as regexTester } from './regex-tester';
|
||||||
import { tool as regexMemo } from './regex-memo';
|
import { tool as regexMemo } from './regex-memo';
|
||||||
import { tool as markdownToHtml } from './markdown-to-html';
|
import { tool as markdownToHtml } from './markdown-to-html';
|
||||||
|
import { tool as harSanitizer } from './har-sanitizer';
|
||||||
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
|
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
|
||||||
import { tool as numeronymGenerator } from './numeronym-generator';
|
import { tool as numeronymGenerator } from './numeronym-generator';
|
||||||
import { tool as macAddressGenerator } from './mac-address-generator';
|
import { tool as macAddressGenerator } from './mac-address-generator';
|
||||||
|
@ -164,7 +165,15 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Network',
|
name: 'Network',
|
||||||
components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator],
|
components: [
|
||||||
|
ipv4SubnetCalculator,
|
||||||
|
ipv4AddressConverter,
|
||||||
|
ipv4RangeExpander,
|
||||||
|
macAddressLookup,
|
||||||
|
macAddressGenerator,
|
||||||
|
ipv6UlaGenerator,
|
||||||
|
harSanitizer,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Math',
|
name: 'Math',
|
||||||
|
|
|
@ -151,7 +151,7 @@ function onSearchInput() {
|
||||||
>
|
>
|
||||||
<div flex-1 truncate>
|
<div flex-1 truncate>
|
||||||
<slot name="displayed-value">
|
<slot name="displayed-value">
|
||||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
|
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full color-current lh-normal @input="onSearchInput">
|
||||||
<span v-else-if="selectedOption" lh-normal>
|
<span v-else-if="selectedOption" lh-normal>
|
||||||
{{ selectedOption.label }}
|
{{ selectedOption.label }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -39,7 +39,7 @@ const headers = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<div class="relative overflow-x-auto rounded">
|
<div class="relative overflow-x-auto rounded">
|
||||||
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
||||||
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
<thead v-if="!hideHeaders" class="bg-#ffffff text-gray-700 uppercase dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
||||||
{{ header.label }}
|
{{ header.label }}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue