Skip to content

Commit 2691579

Browse files
committed
Merge branch 'feat/email-parser' into chore/all-my-stuffs
# Conflicts: # components.d.ts # package.json # pnpm-lock.yaml # src/components/TextareaCopyable.vue # src/tools/index.ts # vite.config.ts
2 parents 1b59a9a + 10a3bab commit 2691579

File tree

9 files changed

+422
-6
lines changed

9 files changed

+422
-6
lines changed

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
"@it-tools/bip39": "^0.0.4",
4343
"@it-tools/oggen": "^1.3.0",
4444
"@regexper/render": "^1.0.0",
45+
"@kenjiuno/decompressrtf": "^0.1.4",
46+
"@kenjiuno/msgreader": "^1.22.0",
4547
"@sindresorhus/slugify": "^2.2.1",
4648
"@tabler/icons-vue": "^3.20.0",
4749
"@tiptap/pm": "2.1.6",
@@ -92,6 +94,8 @@
9294
"iso-639-1": "^3.1.3",
9395
"js-base64": "^3.7.7",
9496
"jsbarcode": "^3.11.6",
97+
"iconv-lite": "^0.6.3",
98+
"js-base64": "^3.7.6",
9599
"json5": "^2.2.3",
96100
"jwt-decode": "^3.1.2",
97101
"libphonenumber-js": "^1.10.28",
@@ -116,6 +120,9 @@
116120
"randexp": "^0.5.3",
117121
"regex": "^4.3.3",
118122
"shiki": "^1.22.0",
123+
"postal-mime": "^2.2.7",
124+
"qrcode": "^1.5.1",
125+
"rtf-stream-parser": "^3.8.0",
119126
"sql-formatter": "^13.0.0",
120127
"sshpk": "^1.18.0",
121128
"ua-parser-js": "^1.0.35",
@@ -175,6 +182,7 @@
175182
"unplugin-vue-components": "^0.25.0",
176183
"vite": "^4.4.9",
177184
"vite-plugin-node-polyfills": "^0.21.0",
185+
"vite-plugin-node-polyfills": "^0.22.0",
178186
"vite-plugin-pwa": "^0.16.0",
179187
"vite-plugin-top-level-await": "^1.4.2",
180188
"vite-plugin-vue-markdown": "^0.23.5",

pnpm-lock.yaml

Lines changed: 40 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/TextareaCopyable.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ const props = withDefaults(
2525
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
2626
copyMessage?: string
2727
wordWrap?: boolean
28+
<<<<<<< HEAD
2829
<<<<<<< HEAD
2930
downloadFileName?: string
3031
downloadButtonText?: string
3132
=======
3233
>>>>>>> feat/cert-key-parser
34+
=======
35+
downloadFileName?: string
36+
downloadButtonText?: string
37+
>>>>>>> feat/email-parser
3338
}>(),
3439
{
3540
followHeightOf: null,
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<script setup lang="ts">
2+
import PostalMime from 'postal-mime';
3+
4+
const inputType = ref<'file' | 'content'>('file');
5+
const emailContent = ref('');
6+
const fileInput = ref() as Ref<File | null>;
7+
const error = ref('');
8+
9+
const parsedEmail = computedAsync(async () => {
10+
const emailContentValue = emailContent.value;
11+
const file = fileInput.value;
12+
error.value = '';
13+
try {
14+
if (inputType.value === 'file' && file) {
15+
return await PostalMime.parse(await file.arrayBuffer());
16+
}
17+
else if (inputType.value === 'content' && emailContentValue) {
18+
return await PostalMime.parse(emailContentValue);
19+
}
20+
else {
21+
return null;
22+
}
23+
}
24+
catch (e: any) {
25+
error.value = e.toString();
26+
return null;
27+
}
28+
});
29+
30+
function downloadFile(data: ArrayBuffer, fileName: string, fileType: string) {
31+
const blob = new Blob([data], { type: fileType || 'application/octet-stream' });
32+
const downloadUrl = URL.createObjectURL(blob);
33+
const a = document.createElement('a');
34+
a.href = downloadUrl;
35+
a.download = fileName;
36+
document.body.appendChild(a);
37+
a.click();
38+
URL.revokeObjectURL(downloadUrl);
39+
}
40+
41+
function onUpload(file: File) {
42+
if (file) {
43+
fileInput.value = file;
44+
}
45+
}
46+
</script>
47+
48+
<template>
49+
<div style="max-width: 600px;">
50+
<c-card title="Input" mb-2>
51+
<n-radio-group v-model:value="inputType" name="radiogroup" mb-2 flex justify-center>
52+
<n-space>
53+
<n-radio
54+
value="file"
55+
label="File"
56+
/>
57+
<n-radio
58+
value="content"
59+
label="Content"
60+
/>
61+
</n-space>
62+
</n-radio-group>
63+
64+
<c-file-upload
65+
v-if="inputType === 'file'"
66+
title="Drag and drop EML file here, or click to select a file"
67+
@file-upload="onUpload"
68+
/>
69+
70+
<c-input-text
71+
v-if="inputType === 'content'"
72+
v-model:value="emailContent"
73+
label="Email Content"
74+
multiline
75+
placeholder="Put your eml/email content here..."
76+
rows="15"
77+
mb-2
78+
/>
79+
</c-card>
80+
81+
<c-alert v-if="error">
82+
{{ error }}
83+
</c-alert>
84+
85+
<c-card v-if="!error && parsedEmail" title="Output">
86+
<input-copyable v-if="fileInput?.name" label="File Name" :value="fileInput?.name" />
87+
<input-copyable v-if="parsedEmail.date" label="Date" :value="parsedEmail.date" />
88+
<input-copyable v-if="parsedEmail.from?.name" label="From (name)" :value="parsedEmail.from?.name" />
89+
<input-copyable v-if="parsedEmail.from" label="From (address)" :value="parsedEmail.from?.address || parsedEmail.from" />
90+
<input-copyable v-if="parsedEmail.to" label="To" :value="JSON.stringify(parsedEmail.to)" />
91+
<input-copyable v-if="parsedEmail.cc" label="Cc" :value="JSON.stringify(parsedEmail.cc)" />
92+
<input-copyable v-if="parsedEmail.bcc" label="Bcc" :value="JSON.stringify(parsedEmail.bcc)" />
93+
<input-copyable v-if="parsedEmail.replyTo" label="Reply-To" :value="JSON.stringify(parsedEmail.replyTo)" />
94+
<input-copyable v-if="parsedEmail.subject" label="Subject" :value="parsedEmail.subject" />
95+
<c-card v-if="parsedEmail.text" title="Plain Content" mb-2>
96+
<details>
97+
<summary>See content</summary>
98+
<textarea-copyable :value="parsedEmail.text" word-wrap />
99+
</details>
100+
</c-card>
101+
<c-card v-if="parsedEmail.html" title="Html Content" mb-2>
102+
<details>
103+
<summary>See content</summary>
104+
<textarea-copyable :value="parsedEmail.html" word-wrap />
105+
</details>
106+
</c-card>
107+
<c-card v-if="parsedEmail?.attachments?.length" title="Attachments" mb-2>
108+
<n-table>
109+
<thead>
110+
<tr>
111+
<th scope="col">
112+
Attachment
113+
</th><th scope="col" />
114+
</tr>
115+
</thead>
116+
<tbody>
117+
<tr
118+
v-for="(h, index) in parsedEmail.attachments || []"
119+
:key="index"
120+
>
121+
<td>
122+
{{ `${h.filename || h.contentId || 'noname'} (${h.mimeType}) / ${h.disposition}` }}
123+
</td>
124+
<td>
125+
<c-button @click="downloadFile(h.content, h.filename || h.contentId || 'noname', h.mimeType)">
126+
Download
127+
</c-button>
128+
</td>
129+
</tr>
130+
</tbody>
131+
</n-table>
132+
</c-card>
133+
134+
<input-copyable v-if="parsedEmail.messageId" label="Message Id" :value="parsedEmail.messageId" />
135+
<input-copyable v-if="parsedEmail.inReplyTo" label="In Reply To" :value="parsedEmail.inReplyTo" />
136+
<input-copyable v-if="parsedEmail.references" label="References" :value="parsedEmail.references" />
137+
<input-copyable v-if="parsedEmail.deliveredTo" label="Delivered To" :value="parsedEmail.deliveredTo" />
138+
<input-copyable v-if="parsedEmail.returnPath" label="Return Path" :value="parsedEmail.returnPath" />
139+
<input-copyable v-if="parsedEmail.sender?.name" label="Sender (name)" :value="parsedEmail.sender?.name" />
140+
<input-copyable v-if="parsedEmail.sender" label="Sender (address)" :value="parsedEmail.sender?.address || parsedEmail.sender" />
141+
142+
<c-card title="All Headers" mt-2>
143+
<input-copyable
144+
v-for="(h, index) in parsedEmail.headers || []"
145+
:key="index"
146+
:label="h.key"
147+
:value="h.value"
148+
/>
149+
</c-card>
150+
</c-card>
151+
</div>
152+
</template>

src/tools/email-parser/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Mail } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Email Parser',
6+
path: '/email-parser',
7+
description: 'Parse and extract information from raw Email content',
8+
keywords: ['email', 'parser', 'header', 'rfc2822', 'rfc5322', 'rfc822'],
9+
component: () => import('./email-parser.vue'),
10+
icon: Mail,
11+
createdAt: new Date('2024-08-15'),
12+
});

src/tools/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { tool as bounceParser } from './bounce-parser';
77
import { tool as codeHighlighter } from './code-highlighter';
88
import { tool as colorWheel } from './color-wheel';
99
import { tool as currencyConverter } from './currency-converter';
10+
import { tool as emailParser } from './email-parser';
11+
import { tool as outlookParser } from './outlook-parser';
1012

1113
import { tool as cssXpathConverter } from './css-xpath-converter';
1214
import { tool as cssSelectorsMemo } from './css-selectors-memo';
@@ -226,6 +228,8 @@ export const toolsByCategory: ToolCategory[] = [
226228
macAddressGenerator,
227229
ipv6UlaGenerator,
228230
dnsQueries,
231+
emailParser,
232+
outlookParser,
229233
],
230234
},
231235
{

src/tools/outlook-parser/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Mail } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Outlook MSG Parser',
6+
path: '/outlook-parser',
7+
description: 'Parse Outlook MSG Files',
8+
keywords: ['outlook', 'email', 'msg', 'parser'],
9+
component: () => import('./outlook-parser.vue'),
10+
icon: Mail,
11+
createdAt: new Date('2024-08-15'),
12+
});

0 commit comments

Comments
 (0)