Skip to content

Commit 73deeb3

Browse files
committed
feat(new tool): Outlook Safelink Decoder
Fix #897
1 parent a07806c commit 73deeb3

File tree

5 files changed

+91
-0
lines changed

5 files changed

+91
-0
lines changed

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
44
import { tool as textToUnicode } from './text-to-unicode';
5+
import { tool as safelinkDecoder } from './safelink-decoder';
56
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
67
import { tool as numeronymGenerator } from './numeronym-generator';
78
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -123,6 +124,7 @@ export const toolsByCategory: ToolCategory[] = [
123124
userAgentParser,
124125
httpStatusCodes,
125126
jsonDiff,
127+
safelinkDecoder,
126128
],
127129
},
128130
{

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-02-25'),
12+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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&data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&reserved=0'))
13+
.toBe('https://www.google.com/search?q=safelink&rlz=1');
14+
});
15+
});
16+
});
17+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export function decodeSafeLinksURL(safeLinksUrl: string) {
2+
// Decode the ATP SafeLinks URL
3+
let originalURL;
4+
5+
try {
6+
// Decode the URL
7+
originalURL = decodeURIComponent(safeLinksUrl);
8+
9+
// Check if it matches the SafeLinks pattern
10+
if (originalURL.match(/\.safelinks\.protection\.outlook\.com\/\?url=.+&data=/)) {
11+
// Extract the original URL
12+
originalURL = originalURL.split('?url=')[1].split('&data=')[0];
13+
}
14+
else if (originalURL.match(/\.safelinks\.protection\.outlook\.com\/\?url=.+&data=/)) {
15+
// Handle the case where "&" is encoded as "&"
16+
originalURL = originalURL.split('?url=')[1].split('&data=')[0];
17+
}
18+
else {
19+
throw new Error('Invalid SafeLinks URL provided');
20+
}
21+
}
22+
catch (error: any) {
23+
// Handle errors
24+
throw new Error(`Failed to decode SafeLinks URL: ${error}`);
25+
}
26+
27+
return originalURL;
28+
}
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)