refactor: centralize safe decode helpers

This commit is contained in:
thePR0M3TH3AN committed 2025-10-02 12:48:24 -04:00
1 parent fb0c2a31ea
commit c2c7f6445d
5 files changed
+115 -48

No files matched your search

+9 -24
View File
@@ -1,25 +1,14 @@
import { WSS_TRACKERS } from "./constants.js"; import { WSS_TRACKERS } from "./constants.js";
import {
safeDecodeURIComponent,
safeDecodeURIComponentLoose,
} from "./utils/safeDecode.js";
const HEX_INFO_HASH = /^[0-9a-f]{40}$/i; const HEX_INFO_HASH = /^[0-9a-f]{40}$/i;
const BTIH_PREFIX = "urn:btih:"; const BTIH_PREFIX = "urn:btih:";
const MAGNET_SCHEME = "magnet:"; const MAGNET_SCHEME = "magnet:";
const ENCODED_BTih_PATTERN = /xt=urn%3Abtih%3A([0-9a-z]+)/gi; const ENCODED_BTih_PATTERN = /xt=urn%3Abtih%3A([0-9a-z]+)/gi;
function decodeLoose(value) {
if (typeof value !== "string") {
return "";
}
const trimmed = value.trim();
if (!trimmed) {
return "";
}
try {
return decodeURIComponent(trimmed);
} catch (err) {
return trimmed;
}
}
export function safeDecodeMagnet(value) { export function safeDecodeMagnet(value) {
if (typeof value !== "string") { if (typeof value !== "string") {
return ""; return "";
@@ -35,15 +24,11 @@ export function safeDecodeMagnet(value) {
break; break;
} }
try { const candidate = safeDecodeURIComponent(decoded);
const candidate = decodeURIComponent(decoded); if (!candidate || candidate === decoded) {
if (!candidate || candidate === decoded) {
break;
}
decoded = candidate.trim();
} catch (err) {
break; break;
} }
decoded = candidate.trim();
} }
return decoded; return decoded;
@@ -73,7 +58,7 @@ export function normalizeForComparison(value) {
function createParam(key, value) { function createParam(key, value) {
const trimmedValue = typeof value === "string" ? value.trim() : ""; const trimmedValue = typeof value === "string" ? value.trim() : "";
const decoded = decodeLoose(trimmedValue); const decoded = safeDecodeURIComponentLoose(trimmedValue);
const comparisonBasis = decoded || trimmedValue; const comparisonBasis = decoded || trimmedValue;
return { return {
key, key,
@@ -154,7 +139,7 @@ export function normalizeMagnetInput(rawValue) {
const lowerKey = key.toLowerCase(); const lowerKey = key.toLowerCase();
let value = rawVal.trim(); let value = rawVal.trim();
if (lowerKey === "xt" && value) { if (lowerKey === "xt" && value) {
const decoded = decodeLoose(value); const decoded = safeDecodeURIComponentLoose(value);
if (decoded && decoded !== value) { if (decoded && decoded !== value) {
value = decoded; value = decoded;
didMutate = true; didMutate = true;
+2 -12
View File
@@ -1,19 +1,9 @@
import { createCardObserver } from "./dom/cardObserver.js"; import { createCardObserver } from "./dom/cardObserver.js";
import { safeDecodeURIComponent } from "./utils/safeDecode.js";
const ROOT_MARGIN = "0px"; const ROOT_MARGIN = "0px";
const THRESHOLD = 0.25; const THRESHOLD = 0.25;
function decodeUrl(value) {
if (typeof value !== "string" || !value) {
return "";
}
try {
return decodeURIComponent(value);
} catch (err) {
return value;
}
}
const urlCardObserver = createCardObserver({ const urlCardObserver = createCardObserver({
rootMargin: ROOT_MARGIN, rootMargin: ROOT_MARGIN,
threshold: THRESHOLD, threshold: THRESHOLD,
@@ -51,7 +41,7 @@ const urlCardObserver = createCardObserver({
return; return;
} }
const url = decodeUrl(encodedUrl); const url = safeDecodeURIComponent(encodedUrl);
if (!url) { if (!url) {
return; return;
} }
+32
View File
@@ -0,0 +1,32 @@
export function safeDecodeURIComponent(value) {
if (typeof value !== "string") {
return "";
}
if (value === "") {
return "";
}
try {
return decodeURIComponent(value);
} catch (err) {
return value;
}
}
export function safeDecodeURIComponentLoose(value, { trim = true } = {}) {
if (typeof value !== "string") {
return "";
}
const working = trim ? value.trim() : value;
if (trim && !working) {
return "";
}
try {
return decodeURIComponent(working);
} catch (err) {
return working;
}
}
+2 -12
View File
@@ -2,20 +2,10 @@
import WebTorrent from "./webtorrent.min.js"; import WebTorrent from "./webtorrent.min.js";
import { WSS_TRACKERS } from "./constants.js"; import { WSS_TRACKERS } from "./constants.js";
import { safeDecodeURIComponent } from "./utils/safeDecode.js";
const DEFAULT_PROBE_TRACKERS = Object.freeze([...WSS_TRACKERS]); const DEFAULT_PROBE_TRACKERS = Object.freeze([...WSS_TRACKERS]);
function decodeComponentSafe(value) {
if (typeof value !== "string") {
return "";
}
try {
return decodeURIComponent(value);
} catch (err) {
return value;
}
}
function normalizeTrackerList(trackers) { function normalizeTrackerList(trackers) {
const normalized = []; const normalized = [];
const seen = new Set(); const seen = new Set();
@@ -73,7 +63,7 @@ function appendProbeTrackers(magnetURI, trackers) {
if (!rawKey || rawKey.trim().toLowerCase() !== "tr") { if (!rawKey || rawKey.trim().toLowerCase() !== "tr") {
return; return;
} }
const decoded = decodeComponentSafe(rawValue).trim().toLowerCase(); const decoded = safeDecodeURIComponent(rawValue).trim().toLowerCase();
if (decoded) { if (decoded) {
trackerSet.add(decoded); trackerSet.add(decoded);
} }
+70
View File
@@ -0,0 +1,70 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
safeDecodeURIComponent,
safeDecodeURIComponentLoose,
} from "../js/utils/safeDecode.js";
import { safeDecodeMagnet } from "../js/magnetUtils.js";
test("safeDecodeURIComponent returns original value on malformed sequences", () => {
const malformed = "%E0%A4%A";
assert.equal(
safeDecodeURIComponent(malformed),
malformed,
"Malformed sequences should return the original input"
);
});
test("safeDecode helpers handle double-encoded values when applied repeatedly", () => {
const raw = "magnet:?xt=urn:btih:abcdef";
const doubleEncoded = encodeURIComponent(encodeURIComponent(raw));
const firstPass = safeDecodeURIComponent(doubleEncoded);
assert.equal(
firstPass,
encodeURIComponent(raw),
"First pass should peel one encoding layer"
);
const secondPass = safeDecodeURIComponent(firstPass);
assert.equal(
secondPass,
raw,
"Second pass should reveal the original string"
);
assert.equal(
safeDecodeMagnet(doubleEncoded),
raw,
"safeDecodeMagnet should continue to handle double-encoded magnets"
);
});
test("safeDecodeURIComponentLoose trims inputs by default", () => {
const padded = " magnet:?dn=Example";
assert.equal(
safeDecodeURIComponentLoose(padded),
"magnet:?dn=Example",
"Loose decoder should trim whitespace when decoding"
);
});
test("safeDecodeURIComponentLoose preserves whitespace when trim is false", () => {
const padded = " magnet:?dn=Example";
assert.equal(
safeDecodeURIComponentLoose(padded, { trim: false }),
padded,
"Loose decoder should return the original string when trim is disabled and decoding is unnecessary"
);
});
test("safeDecodeURIComponent handles empty strings consistently", () => {
assert.equal(safeDecodeURIComponent(""), "", "Empty input should stay empty");
assert.equal(
safeDecodeURIComponentLoose(""),
"",
"Loose decoder should return empty for empty input"
);
});