mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 20:33:56 +08:00
72 lines
2.8 KiB
Ucode
72 lines
2.8 KiB
Ucode
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Pure subscription decoder. The fetcher returns raw bytes
|
|
* and the orchestrator used to inline the JSON / base64 / SIP008
|
|
* handling. Pulling it out gives the orchestrator a single
|
|
* `decode(content, log, url)` call and lets us unit-test each input
|
|
* shape without involving the network or UCI.
|
|
*
|
|
* Three input shapes are recognised, in order:
|
|
* 1. JSON with a top-level 'servers' array (clash proxy-provider)
|
|
* 2. JSON array directly (the same data, just unwrapped)
|
|
* 3. Shadowsocks SIP008 JSON: top-level array of objects with
|
|
* server+method. Each entry is tagged nodetype='sip008' so the
|
|
* orchestrator can dispatch them differently downstream.
|
|
*
|
|
* On JSON failure, decode the content as base64 and split on '\n'
|
|
* to get one share-link URI per line (sing-box subscription format).
|
|
*
|
|
* On total failure (no JSON, no base64), return an empty list and
|
|
* let the caller log the empty result.
|
|
*
|
|
* `url` is optional and only used to enrich the parse-failed log
|
|
* line; the decoder itself never makes a network call.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import { isEmpty, decodeBase64Str, redactUrl } from '../homeproxy-pro.uc';
|
|
|
|
export function decode(content, log, url) {
|
|
if (isEmpty(content))
|
|
return [];
|
|
|
|
let nodes;
|
|
try {
|
|
const parsed = json(content);
|
|
nodes = parsed.servers || parsed;
|
|
|
|
/* Shadowsocks SIP008: each entry is a JSON object with
|
|
* server + method, not a URI string.
|
|
*
|
|
* The probe has to check the type first. It used to be a bare
|
|
* `nodes[0].server`, which THROWS in ucode when nodes[0] is a
|
|
* string ("left-hand side expression is not an array or
|
|
* object") - and the throw was swallowed by the JSON
|
|
* try/catch below, so a perfectly valid `{"servers":["vless://…"]}`
|
|
* or `["vless://…"]` subscription fell through to the base64
|
|
* fallback, failed there too and decoded to an empty list. Two
|
|
* real subscription shapes were silently discarded, and the
|
|
* decoder test had recorded that as the contract. The same
|
|
* guard applies inside the loop so a mixed list (SIP008 objects
|
|
* plus share links) keeps both halves instead of losing
|
|
* everything on the first string. */
|
|
if (type(nodes[0]) === 'object' && nodes[0].server && nodes[0].method) {
|
|
for (let i = 0; i < length(nodes); i++)
|
|
if (type(nodes[i]) === 'object')
|
|
nodes[i].nodetype = 'sip008';
|
|
}
|
|
} catch (e) {
|
|
/* The URL carries the subscription token and this line lands in
|
|
* homeproxy-pro.log, which is world-readable. fetcher.uc and the
|
|
* orchestrator both redact; this path was missed. */
|
|
const tag = url ? sprintf('for %s, ', redactUrl(url)) : '';
|
|
log(sprintf('JSON parse failed %strying base64: %s', tag, e.message));
|
|
const decoded = decodeBase64Str(content);
|
|
nodes = decoded ? split(trim(decoded), '\n') : [];
|
|
}
|
|
|
|
return nodes;
|
|
};
|