diff --git a/src/tools/index.ts b/src/tools/index.ts
index 492ec6f5e..aa861c935 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -5,7 +5,7 @@ import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as asciiTextDrawer } from './ascii-text-drawer';
import { tool as textToUnicode } from './text-to-unicode';
-
+import { tool as safelinkDecoder } from './safelink-decoder';
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
import { tool as numeronymGenerator } from './numeronym-generator';
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -127,6 +127,7 @@ export const toolsByCategory: ToolCategory[] = [
userAgentParser,
httpStatusCodes,
jsonDiff,
+ safelinkDecoder,
],
},
{
diff --git a/src/tools/safelink-decoder/index.ts b/src/tools/safelink-decoder/index.ts
new file mode 100644
index 000000000..ef865108f
--- /dev/null
+++ b/src/tools/safelink-decoder/index.ts
@@ -0,0 +1,12 @@
+import { Mailbox } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Outlook Safelink decoder',
+ path: '/safelink-decoder',
+ description: 'Decode Outlook SafeLink links',
+ keywords: ['outlook', 'safelink', 'decoder'],
+ component: () => import('./safelink-decoder.vue'),
+ icon: Mailbox,
+ createdAt: new Date('2024-03-11'),
+});
diff --git a/src/tools/safelink-decoder/safelink-decoder.service.test.ts b/src/tools/safelink-decoder/safelink-decoder.service.test.ts
new file mode 100644
index 000000000..b601f01e8
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.service.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from 'vitest';
+import { decodeSafeLinksURL } from './safelink-decoder.service';
+
+describe('safelink-decoder', () => {
+ describe('decodeSafeLinksURL', () => {
+ describe('decode outlook safelink urls', () => {
+ it('should decode basic safelink urls', () => {
+ 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'))
+ .toBe('https://www.google.com/search?q=safelink&rlz=1');
+ });
+ it('should decode encoded safelink urls', () => {
+ 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'))
+ .toBe('https://www.google.com/search?q=safelink&rlz=1');
+ });
+ it('throw on not outlook safelink urls', () => {
+ expect(() => decodeSafeLinksURL('https://google.com'))
+ .toThrow('Invalid SafeLinks URL provided');
+ });
+ });
+ });
+});
diff --git a/src/tools/safelink-decoder/safelink-decoder.service.ts b/src/tools/safelink-decoder/safelink-decoder.service.ts
new file mode 100644
index 000000000..96be00ab7
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.service.ts
@@ -0,0 +1,7 @@
+export function decodeSafeLinksURL(safeLinksUrl: string) {
+ if (!safeLinksUrl.match(/\.safelinks\.protection\.outlook\.com/)) {
+ throw new Error('Invalid SafeLinks URL provided');
+ }
+
+ return new URL(safeLinksUrl).searchParams.get('url');
+}
diff --git a/src/tools/safelink-decoder/safelink-decoder.vue b/src/tools/safelink-decoder/safelink-decoder.vue
new file mode 100644
index 000000000..01337eb23
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+