diff --git a/src/tools/index.ts b/src/tools/index.ts
index 388cfaf49..886db95d9 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as emailNormalizer } from './email-normalizer';
+import { tool as xVrSpamcauseDecoder } from './x-vr-spamcause-decoder';
import { tool as asciiTextDrawer } from './ascii-text-drawer';
@@ -164,7 +165,15 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Network',
- components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator],
+ components: [
+ ipv4SubnetCalculator,
+ ipv4AddressConverter,
+ ipv4RangeExpander,
+ macAddressLookup,
+ macAddressGenerator,
+ ipv6UlaGenerator,
+ xVrSpamcauseDecoder,
+ ],
},
{
name: 'Math',
diff --git a/src/tools/x-vr-spamcause-decoder/index.ts b/src/tools/x-vr-spamcause-decoder/index.ts
new file mode 100644
index 000000000..e544e9821
--- /dev/null
+++ b/src/tools/x-vr-spamcause-decoder/index.ts
@@ -0,0 +1,12 @@
+import { RecordMail } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'X vr spamcause decoder',
+ path: '/x-vr-spamcause-decoder',
+ description: 'Decode X-VR-SPAMCAUSE header in OVH mails',
+ keywords: ['ovh', 'vade', 'retro', 'vr', 'spam', 'spamcause', 'decoder'],
+ component: () => import('./x-vr-spamcause-decoder.vue'),
+ icon: RecordMail,
+ createdAt: new Date('2024-08-15'),
+});
diff --git a/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.service.test.ts b/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.service.test.ts
new file mode 100644
index 000000000..22f4bda6f
--- /dev/null
+++ b/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.service.test.ts
@@ -0,0 +1,10 @@
+import { describe, expect, it } from 'vitest';
+import { spamCauseDecode } from './x-vr-spamcause-decoder.service';
+
+describe('x-vr-spamcause-decoder', () => {
+ it('should decode X-VR-SPAMCAUSE', () => {
+ expect(spamCauseDecode('gggruggvucftvghtrhhoucdtuddrfeelgedrvdduucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuqfggjfenuceurghilhhouhhtmecufedttdenucgohfhorhgsihguuggvnhfjughrucdlhedttddm')).toBe(
+ 'Vade Retro 01.394.21 AS+AV+AP+RT Profile: OVH; Bailout: 300; ^ForbiddenHdr (500)',
+ );
+ });
+});
diff --git a/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.service.ts b/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.service.ts
new file mode 100644
index 000000000..ffb0135d5
--- /dev/null
+++ b/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.service.ts
@@ -0,0 +1,24 @@
+function unrot(a: string, b: string, pos: number) {
+ if (b === undefined) {
+ return '';
+ }
+
+ if (pos % 2 === 0) {
+ const c = a;
+ a = b;
+ b = c;
+ }
+
+ const offset = ('g'.charCodeAt(0) - a.charCodeAt(0)) * 16;
+ return String.fromCharCode((a.charCodeAt(0) + b.charCodeAt(0)) - 'x'.charCodeAt(0) - offset);
+}
+
+export function spamCauseDecode(text: string) {
+ let result = '';
+
+ for (let n = 0; n < text.length; n += 2) {
+ result += unrot(text[n], text[n + 1], Math.floor(n / 2));
+ }
+
+ return result;
+}
diff --git a/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.vue b/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.vue
new file mode 100644
index 000000000..317892871
--- /dev/null
+++ b/src/tools/x-vr-spamcause-decoder/x-vr-spamcause-decoder.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+