From 41034ceb46c8417a06e17817740594d2bc0ea40a Mon Sep 17 00:00:00 2001 From: xream Date: Thu, 27 Feb 2025 20:54:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=84=E8=8C=83=E5=8C=96=20subscript?= =?UTF-8?q?ion-userinfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- .../src/core/proxy-utils/processors/index.js | 2 + backend/src/restful/download.js | 16 ++++---- backend/src/restful/file.js | 4 +- backend/src/utils/flow.js | 38 +++++++++++++++++++ 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/backend/package.json b/backend/package.json index 8947125..25a7988 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.16.57", + "version": "2.16.58", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/processors/index.js b/backend/src/core/proxy-utils/processors/index.js index 5cc4a40..2929ea7 100644 --- a/backend/src/core/proxy-utils/processors/index.js +++ b/backend/src/core/proxy-utils/processors/index.js @@ -20,6 +20,7 @@ import { validCheck, flowTransfer, getRmainingDays, + normalizeFlowHeader, } from '@/utils/flow'; function isObject(item) { @@ -1083,6 +1084,7 @@ function createDynamicFunction(name, script, $arguments, $options) { flowTransfer, validCheck, getRmainingDays, + normalizeFlowHeader, }; if ($.env.isLoon) { return new Function( diff --git a/backend/src/restful/download.js b/backend/src/restful/download.js index 6f1cfa3..9028b6e 100644 --- a/backend/src/restful/download.js +++ b/backend/src/restful/download.js @@ -5,7 +5,7 @@ import { import { ProxyUtils } from '@/core/proxy-utils'; import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants'; import { findByName } from '@/utils/database'; -import { getFlowHeaders } from '@/utils/flow'; +import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow'; import $ from '@/core/app'; import { failed } from '@/restful/response'; import { InternalServerError, ResourceNotFoundError } from '@/restful/errors'; @@ -259,7 +259,10 @@ async function downloadSubscription(req, res) { $arguments.flowUrl, ); if (flowInfo) { - res.set('subscription-userinfo', flowInfo); + res.set( + 'subscription-userinfo', + normalizeFlowHeader(flowInfo), + ); } } } catch (err) { @@ -293,10 +296,9 @@ async function downloadSubscription(req, res) { } res.set( 'subscription-userinfo', - [subUserInfo, flowInfo] - .filter((i) => i) - .join('; ') - .replace(/\s*;\s*;\s*/g, ';'), + normalizeFlowHeader( + [subUserInfo, flowInfo].filter((i) => i).join(';'), + ), ); } @@ -556,7 +558,7 @@ async function downloadCollection(req, res) { if (subUserInfo) { res.set( 'subscription-userinfo', - subUserInfo.replace(/\s*;\s*;\s*/g, ';'), + normalizeFlowHeader(subUserInfo), ); } if (platform === 'JSON') { diff --git a/backend/src/restful/file.js b/backend/src/restful/file.js index 51ee72e..dbbf067 100644 --- a/backend/src/restful/file.js +++ b/backend/src/restful/file.js @@ -1,5 +1,5 @@ import { deleteByName, findByName, updateByName } from '@/utils/database'; -import { getFlowHeaders } from '@/utils/flow'; +import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow'; import { FILES_KEY } from '@/constants'; import { failed, success } from '@/restful/response'; import $ from '@/core/app'; @@ -148,7 +148,7 @@ async function getFile(req, res) { if (flowInfo) { res.set( 'subscription-userinfo', - flowInfo.replace(/\s*;\s*;\s*/g, ';'), + normalizeFlowHeader(flowInfo), ); } } diff --git a/backend/src/utils/flow.js b/backend/src/utils/flow.js index 1111185..b45e48e 100644 --- a/backend/src/utils/flow.js +++ b/backend/src/utils/flow.js @@ -313,3 +313,41 @@ export function getRmainingDays(opt = {}) { $.error(`getRmainingDays failed: ${e.message ?? e}`); } } + +export function normalizeFlowHeader(flowHeaders) { + try { + // 使用 Map 保持顺序并处理重复键 + const kvMap = new Map(); + + flowHeaders + .split(';') + .map((p) => p.trim()) + .filter(Boolean) + .forEach((pair) => { + const eqIndex = pair.indexOf('='); + if (eqIndex === -1) return; + + const key = pair.slice(0, eqIndex).trim(); + const encodedValue = pair.slice(eqIndex + 1).trim(); + + // 只保留第一个出现的 key + if (!kvMap.has(key)) { + try { + // 解码 URI 组件并保留原始值作为 fallback + const decodedValue = decodeURIComponent(encodedValue); + kvMap.set(key, decodedValue); + } catch (e) { + kvMap.set(key, encodedValue); + } + } + }); + + // 拼接标准化字符串 + return Array.from(kvMap.entries()) + .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) // 重新编码保持兼容性 + .join('; '); + } catch (e) { + $.error(`normalizeFlowHeader failed: ${e.message ?? e}`); + return flowHeaders; + } +}