Skip to content

Commit d3b32cc

Browse files
sharevbCorentinTh
andauthored
feat(new tool): Outlook Safelink Decoder (CorentinTh#911)
* feat(new tool): Outlook Safelink Decoder Fix CorentinTh#897 * Use native URL parsing Co-authored-by: Corentin THOMASSET <[email protected]> * Update src/tools/safelink-decoder/index.ts --------- Co-authored-by: Corentin THOMASSET <[email protected]>
1 parent fe349ad commit d3b32cc

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

src/tools/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { tool as basicAuthGenerator } from './basic-auth-generator';
55
import { tool as asciiTextDrawer } from './ascii-text-drawer';
66

77
import { tool as textToUnicode } from './text-to-unicode';
8-
8+
import { tool as safelinkDecoder } from './safelink-decoder';
99
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
1010
import { tool as numeronymGenerator } from './numeronym-generator';
1111
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -127,6 +127,7 @@ export const toolsByCategory: ToolCategory[] = [
127127
userAgentParser,
128128
httpStatusCodes,
129129
jsonDiff,
130+
safelinkDecoder,
130131
],
131132
},
132133
{

src/tools/safelink-decoder/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Mailbox } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Outlook Safelink decoder',
6+
path: '/safelink-decoder',
7+
description: 'Decode Outlook SafeLink links',
8+
keywords: ['outlook', 'safelink', 'decoder'],
9+
component: () => import('./safelink-decoder.vue'),
10+
icon: Mailbox,
11+
createdAt: new Date('2024-03-11'),
12+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { decodeSafeLinksURL } from './safelink-decoder.service';
3+
4+
describe('safelink-decoder', () => {
5+
describe('decodeSafeLinksURL', () => {
6+
describe('decode outlook safelink urls', () => {
7+
it('should decode basic safelink urls', () => {
8+
expect(decodeSafeLinksURL('https://aus01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3Dsafelink%26rlz%3D1&data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&reserved=0'))
9+
.toBe('https://www.google.com/search?q=safelink&rlz=1');
10+
});
11+
it('should decode encoded safelink urls', () => {
12+
expect(decodeSafeLinksURL('https://aus01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3Dsafelink%26rlz%3D1&amp;data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&amp;sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&amp;reserved=0'))
13+
.toBe('https://www.google.com/search?q=safelink&rlz=1');
14+
});
15+
it('throw on not outlook safelink urls', () => {
16+
expect(() => decodeSafeLinksURL('https://google.com'))
17+
.toThrow('Invalid SafeLinks URL provided');
18+
});
19+
});
20+
});
21+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function decodeSafeLinksURL(safeLinksUrl: string) {
2+
if (!safeLinksUrl.match(/\.safelinks\.protection\.outlook\.com/)) {
3+
throw new Error('Invalid SafeLinks URL provided');
4+
}
5+
6+
return new URL(safeLinksUrl).searchParams.get('url');
7+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
import { decodeSafeLinksURL } from './safelink-decoder.service';
3+
import TextareaCopyable from '@/components/TextareaCopyable.vue';
4+
5+
const inputSafeLinkUrl = ref('');
6+
const outputDecodedUrl = computed(() => {
7+
try {
8+
return decodeSafeLinksURL(inputSafeLinkUrl.value);
9+
}
10+
catch (e: any) {
11+
return e.toString();
12+
}
13+
});
14+
</script>
15+
16+
<template>
17+
<div>
18+
<c-input-text
19+
v-model:value="inputSafeLinkUrl"
20+
raw-text
21+
placeholder="Your input Outlook SafeLink Url..."
22+
autofocus
23+
label="Your input Outlook SafeLink Url:"
24+
/>
25+
26+
<n-divider />
27+
28+
<n-form-item label="Output decoded URL:">
29+
<TextareaCopyable :value="outputDecodedUrl" :word-wrap="true" />
30+
</n-form-item>
31+
</div>
32+
</template>

0 commit comments

Comments
 (0)