mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
refactor: centralize safe decode helpers
This commit is contained in:
1 parent
fb0c2a31ea
commit
c2c7f6445d
5 files changed
+115
-48
No files matched your search
+9
-24
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user