mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 04:03:04 +08:00
Fix direct share-link classification and conversion (#315)
This commit is contained in:
@@ -206,6 +206,16 @@ clash sub remove <名称>
|
|||||||
|
|
||||||
WSL / 普通用户如果无权写入 `/etc/environment`,`clashon` 会自动降级:运行时照常启动,当前 Shell 代理变量生效;系统代理持久接管和开机代理保持不可用。
|
WSL / 普通用户如果无权写入 `/etc/environment`,`clashon` 会自动降级:运行时照常启动,当前 Shell 代理变量生效;系统代理持久接管和开机代理保持不可用。
|
||||||
|
|
||||||
|
### 直接分享链接
|
||||||
|
|
||||||
|
`clash add` 可以直接导入 `vmess://`、`vless://`、`trojan://`、`tuic://`、`hysteria2://`、`hy2://` 和 `anytls://` 分享链接:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clash add "vless://..." jp
|
||||||
|
```
|
||||||
|
|
||||||
|
分享链接通常包含 `&` 等 Shell 特殊字符,请始终使用引号包住完整链接。链接中的凭据会保存在本机订阅配置中,但命令反馈只显示脱敏后的协议类型。
|
||||||
|
|
||||||
### 本地配置导入
|
### 本地配置导入
|
||||||
|
|
||||||
推荐使用交互导入,放置目录为:`$PROJECT_DIR/runtime/subscriptions/`
|
推荐使用交互导入,放置目录为:`$PROJECT_DIR/runtime/subscriptions/`
|
||||||
|
|||||||
@@ -1423,7 +1423,7 @@ print_add_feedback() {
|
|||||||
local url="${2:-}"
|
local url="${2:-}"
|
||||||
|
|
||||||
echo "✔ 已添加订阅并设为当前主订阅:$name"
|
echo "✔ 已添加订阅并设为当前主订阅:$name"
|
||||||
[ -n "${url:-}" ] && echo "📡 URL:$url"
|
[ -n "${url:-}" ] && echo "📡 URL:$(subscription_url_for_display "$url")"
|
||||||
echo "📋 最新订阅列表(clash ls):"
|
echo "📋 最新订阅列表(clash ls):"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+95
-34
@@ -458,18 +458,23 @@ subscription_url_scheme() {
|
|||||||
local url="$1"
|
local url="$1"
|
||||||
|
|
||||||
case "$url" in
|
case "$url" in
|
||||||
http://*) echo "http" ;;
|
http://*) echo "http" ;;
|
||||||
https://*) echo "https" ;;
|
https://*) echo "https" ;;
|
||||||
file://*) echo "file" ;;
|
file://*) echo "file" ;;
|
||||||
*) echo "unknown" ;;
|
vmess://*) echo "vmess" ;;
|
||||||
|
vless://*) echo "vless" ;;
|
||||||
|
trojan://*) echo "trojan" ;;
|
||||||
|
tuic://*) echo "tuic" ;;
|
||||||
|
hysteria2://*) echo "hysteria2" ;;
|
||||||
|
hy2://*) echo "hy2" ;;
|
||||||
|
anytls://*) echo "anytls" ;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription_url_is_supported() {
|
subscription_scheme_is_share_link() {
|
||||||
local url="$1"
|
case "$1" in
|
||||||
|
vmess|vless|trojan|tuic|hysteria2|hy2|anytls)
|
||||||
case "$(subscription_url_scheme "$url")" in
|
|
||||||
http|https|file)
|
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -478,6 +483,37 @@ subscription_url_is_supported() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscription_url_is_share_link() {
|
||||||
|
subscription_scheme_is_share_link "$(subscription_url_scheme "$1")"
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription_url_is_supported() {
|
||||||
|
local url="$1" scheme
|
||||||
|
|
||||||
|
scheme="$(subscription_url_scheme "$url")"
|
||||||
|
|
||||||
|
case "$scheme" in
|
||||||
|
http|https|file)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
subscription_scheme_is_share_link "$scheme"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription_url_for_display() {
|
||||||
|
local url="$1" scheme
|
||||||
|
|
||||||
|
scheme="$(subscription_url_scheme "$url")"
|
||||||
|
if subscription_scheme_is_share_link "$scheme"; then
|
||||||
|
printf '%s://<redacted>\n' "$scheme"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$url"
|
||||||
|
}
|
||||||
|
|
||||||
subscription_file_path_from_url() {
|
subscription_file_path_from_url() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local path
|
local path
|
||||||
@@ -801,20 +837,22 @@ subscription_cache_store() {
|
|||||||
local fmt="${2:-clash}"
|
local fmt="${2:-clash}"
|
||||||
local src="$3"
|
local src="$3"
|
||||||
local source_url="${4:-}"
|
local source_url="${4:-}"
|
||||||
local cache_file meta_file
|
local cache_file meta_file display_url display_source_url
|
||||||
|
|
||||||
[ -s "$src" ] || return 0
|
[ -s "$src" ] || return 0
|
||||||
|
|
||||||
cache_file="$(subscription_cache_file "$url" "$fmt")"
|
cache_file="$(subscription_cache_file "$url" "$fmt")"
|
||||||
meta_file="$(subscription_cache_meta_file "$url" "$fmt")"
|
meta_file="$(subscription_cache_meta_file "$url" "$fmt")"
|
||||||
|
display_url="$(subscription_url_for_display "$url")"
|
||||||
|
display_source_url="$(subscription_url_for_display "$source_url")"
|
||||||
|
|
||||||
mkdir -p "$(download_cache_dir)"
|
mkdir -p "$(download_cache_dir)"
|
||||||
cp -f "$src" "$cache_file"
|
cp -f "$src" "$cache_file"
|
||||||
|
|
||||||
cat > "$meta_file" <<EOF
|
cat > "$meta_file" <<EOF
|
||||||
CACHE_URL="$url"
|
CACHE_URL="$display_url"
|
||||||
CACHE_FORMAT="$fmt"
|
CACHE_FORMAT="$fmt"
|
||||||
CACHE_SOURCE_URL="$source_url"
|
CACHE_SOURCE_URL="$display_source_url"
|
||||||
CACHE_TIME="$(now_datetime)"
|
CACHE_TIME="$(now_datetime)"
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -1242,6 +1280,7 @@ print_subscription_health_one() {
|
|||||||
|
|
||||||
type_text="$(subscription_format_by_name "$name" 2>/dev/null || echo "clash")"
|
type_text="$(subscription_format_by_name "$name" 2>/dev/null || echo "clash")"
|
||||||
url_text="$(subscription_url_by_name "$name" 2>/dev/null || true)"
|
url_text="$(subscription_url_by_name "$name" 2>/dev/null || true)"
|
||||||
|
url_text="$(subscription_url_for_display "$url_text")"
|
||||||
|
|
||||||
echo "📡 订阅名称:$name"
|
echo "📡 订阅名称:$name"
|
||||||
echo "🔧 订阅类型:$type_text"
|
echo "🔧 订阅类型:$type_text"
|
||||||
@@ -1269,6 +1308,7 @@ print_subscription_health_verbose() {
|
|||||||
enabled="$("$(yq_bin)" eval ".sources.${name}.enabled // false" "$file" 2>/dev/null)"
|
enabled="$("$(yq_bin)" eval ".sources.${name}.enabled // false" "$file" 2>/dev/null)"
|
||||||
fmt="$("$(yq_bin)" eval ".sources.${name}.type // \"clash\"" "$file" 2>/dev/null)"
|
fmt="$("$(yq_bin)" eval ".sources.${name}.type // \"clash\"" "$file" 2>/dev/null)"
|
||||||
url="$("$(yq_bin)" eval ".sources.${name}.url // \"\"" "$file" 2>/dev/null)"
|
url="$("$(yq_bin)" eval ".sources.${name}.url // \"\"" "$file" 2>/dev/null)"
|
||||||
|
url="$(subscription_url_for_display "$url")"
|
||||||
|
|
||||||
if [ "$name" = "$active" ]; then
|
if [ "$name" = "$active" ]; then
|
||||||
echo "* $name"
|
echo "* $name"
|
||||||
@@ -2372,6 +2412,7 @@ show_subscription() {
|
|||||||
|
|
||||||
url="$(subscription_url 2>/dev/null || true)"
|
url="$(subscription_url 2>/dev/null || true)"
|
||||||
fmt="$(subscription_format)"
|
fmt="$(subscription_format)"
|
||||||
|
url="$(subscription_url_for_display "$url")"
|
||||||
|
|
||||||
if [ -n "${url:-}" ]; then
|
if [ -n "${url:-}" ]; then
|
||||||
echo "订阅地址:$url"
|
echo "订阅地址:$url"
|
||||||
@@ -2997,6 +3038,7 @@ print_subscription_pick_line() {
|
|||||||
|
|
||||||
type_text="$(subscription_format_by_name "$name" 2>/dev/null || echo "clash")"
|
type_text="$(subscription_format_by_name "$name" 2>/dev/null || echo "clash")"
|
||||||
url_text="$(subscription_url_by_name "$name" 2>/dev/null || true)"
|
url_text="$(subscription_url_by_name "$name" 2>/dev/null || true)"
|
||||||
|
url_text="$(subscription_url_for_display "$url_text")"
|
||||||
[ -n "${url_text:-}" ] || url_text="-"
|
[ -n "${url_text:-}" ] || url_text="-"
|
||||||
|
|
||||||
if [ "$show_index" = "true" ]; then
|
if [ "$show_index" = "true" ]; then
|
||||||
@@ -3103,6 +3145,11 @@ subscription_list_recommendation_lines() {
|
|||||||
detect_subscription_format() {
|
detect_subscription_format() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
|
|
||||||
|
if subscription_url_is_share_link "$url"; then
|
||||||
|
echo "convert"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
case "$url" in
|
case "$url" in
|
||||||
*.yaml|*.yml|*".yaml?"*|*".yml?"*)
|
*.yaml|*.yml|*".yaml?"*|*".yml?"*)
|
||||||
echo "clash"
|
echo "clash"
|
||||||
@@ -3542,7 +3589,7 @@ convert_subscription_via_subconverter() {
|
|||||||
local api tmp_file curl_error_file
|
local api tmp_file curl_error_file
|
||||||
local curl_meta curl_rc http_code effective_url errexit_was_set
|
local curl_meta curl_rc http_code effective_url errexit_was_set
|
||||||
local log_file
|
local log_file
|
||||||
local scheme convert_url subscription_ua
|
local scheme convert_url display_convert_url subscription_ua
|
||||||
local validate_ok="false"
|
local validate_ok="false"
|
||||||
local failure_type process_status preview_file
|
local failure_type process_status preview_file
|
||||||
|
|
||||||
@@ -3561,10 +3608,13 @@ convert_subscription_via_subconverter() {
|
|||||||
convert_url="$LOCAL_SUBSCRIPTION_CONVERT_URL"
|
convert_url="$LOCAL_SUBSCRIPTION_CONVERT_URL"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
die "不支持的订阅协议:$url"
|
subscription_scheme_is_share_link "$scheme" \
|
||||||
|
|| die "不支持的订阅协议:$url"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
display_convert_url="$(subscription_url_for_display "$convert_url")"
|
||||||
|
|
||||||
case "$fetch_reason" in
|
case "$fetch_reason" in
|
||||||
auto|install|bootstrap|"")
|
auto|install|bootstrap|"")
|
||||||
if [ "$scheme" != "file" ] && subscription_cache_restore "$url" "convert" "$out_file"; then
|
if [ "$scheme" != "file" ] && subscription_cache_restore "$url" "convert" "$out_file"; then
|
||||||
@@ -3627,10 +3677,16 @@ convert_subscription_via_subconverter() {
|
|||||||
if [ "$scheme" = "file" ]; then
|
if [ "$scheme" = "file" ]; then
|
||||||
echo "param: url=<local subscription share links>"
|
echo "param: url=<local subscription share links>"
|
||||||
else
|
else
|
||||||
echo "param: url=$convert_url"
|
echo "param: url=$display_convert_url"
|
||||||
fi
|
fi
|
||||||
echo "not_sent_params: insert/config/emoji/list (use subconverter defaults)"
|
echo "not_sent_params: insert/config/emoji/list (use subconverter defaults)"
|
||||||
[ -n "${effective_url:-}" ] && echo "effective_url: $effective_url"
|
if [ -n "${effective_url:-}" ]; then
|
||||||
|
if subscription_scheme_is_share_link "$scheme"; then
|
||||||
|
echo "effective_url: <redacted local subconverter request>"
|
||||||
|
else
|
||||||
|
echo "effective_url: $effective_url"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
echo "curl_rc: $curl_rc"
|
echo "curl_rc: $curl_rc"
|
||||||
echo "http_code: ${http_code:-unknown}"
|
echo "http_code: ${http_code:-unknown}"
|
||||||
echo "process_status: $process_status"
|
echo "process_status: $process_status"
|
||||||
@@ -4067,25 +4123,30 @@ fetch_subscription_source() {
|
|||||||
else
|
else
|
||||||
reason="订阅转换失败"
|
reason="订阅转换失败"
|
||||||
if [ "${SUBCONVERTER_LAST_ZERO_NODES:-false}" = "true" ]; then
|
if [ "${SUBCONVERTER_LAST_ZERO_NODES:-false}" = "true" ]; then
|
||||||
warn "subconverter 未解析到节点,尝试直接使用原始订阅(Clash YAML fallback)"
|
if subscription_url_is_share_link "$url"; then
|
||||||
|
warn "subconverter 未从分享链接解析到节点"
|
||||||
rm -f "$raw_file" "$candidate_file" 2>/dev/null || true
|
reason="订阅转换失败:No nodes were found;分享链接无法作为 Clash YAML fallback"
|
||||||
raw_file="$(mktemp)"
|
|
||||||
candidate_file="$(mktemp)"
|
|
||||||
rm -f "$raw_file" "$candidate_file" 2>/dev/null || true
|
|
||||||
|
|
||||||
if download_subscription_yaml "$url" "$raw_file" "$fetch_reason"; then
|
|
||||||
if build_runtime_candidate_from_payload "$raw_file" "$candidate_file" "$name"; then
|
|
||||||
mv -f "$candidate_file" "$out_file"
|
|
||||||
rm -f "$raw_file" 2>/dev/null || true
|
|
||||||
mark_subscription_health_success "$name"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
write_subscription_invalid_debug_snapshot "$raw_file"
|
|
||||||
reason="订阅转换失败:No nodes were found;已尝试原始订阅 fallback,但原始订阅不是可直接运行的 Clash YAML"
|
|
||||||
else
|
else
|
||||||
reason="订阅转换失败:No nodes were found;原始订阅 fallback 下载失败"
|
warn "subconverter 未解析到节点,尝试直接使用原始订阅(Clash YAML fallback)"
|
||||||
|
|
||||||
|
rm -f "$raw_file" "$candidate_file" 2>/dev/null || true
|
||||||
|
raw_file="$(mktemp)"
|
||||||
|
candidate_file="$(mktemp)"
|
||||||
|
rm -f "$raw_file" "$candidate_file" 2>/dev/null || true
|
||||||
|
|
||||||
|
if download_subscription_yaml "$url" "$raw_file" "$fetch_reason"; then
|
||||||
|
if build_runtime_candidate_from_payload "$raw_file" "$candidate_file" "$name"; then
|
||||||
|
mv -f "$candidate_file" "$out_file"
|
||||||
|
rm -f "$raw_file" 2>/dev/null || true
|
||||||
|
mark_subscription_health_success "$name"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_subscription_invalid_debug_snapshot "$raw_file"
|
||||||
|
reason="订阅转换失败:No nodes were found;已尝试原始订阅 fallback,但原始订阅不是可直接运行的 Clash YAML"
|
||||||
|
else
|
||||||
|
reason="订阅转换失败:No nodes were found;原始订阅 fallback 下载失败"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
source_clashctl_for_tests() {
|
||||||
|
set -- ""
|
||||||
|
# Source the real command functions; suppress the no-arg usage printed by the dispatcher.
|
||||||
|
source "$PROJECT_DIR/scripts/core/clashctl.sh" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
local name="$1"
|
||||||
|
local expected="$2"
|
||||||
|
local actual="$3"
|
||||||
|
|
||||||
|
if [ "$actual" != "$expected" ]; then
|
||||||
|
echo "not ok - $name: got '$actual', expected '$expected'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok - $name"
|
||||||
|
}
|
||||||
|
|
||||||
|
source_clashctl_for_tests
|
||||||
|
|
||||||
|
schemes=(vmess vless trojan tuic hysteria2 hy2 anytls)
|
||||||
|
urls=(
|
||||||
|
'vmess://test-payload'
|
||||||
|
'vless://00000000-0000-0000-0000-000000000000@example.com:443?security=tls&alpn=h2&flow=xtls-rprx-vision#jp'
|
||||||
|
'trojan://test-password@example.com:443#trojan'
|
||||||
|
'tuic://00000000-0000-0000-0000-000000000000:test-password@example.com:443#tuic'
|
||||||
|
'hysteria2://test-password@example.com:443#hy2'
|
||||||
|
'hy2://test-password@example.com:443#hy2-short'
|
||||||
|
'anytls://test-password@example.com:443#anytls'
|
||||||
|
)
|
||||||
|
|
||||||
|
for index in "${!urls[@]}"; do
|
||||||
|
scheme="${schemes[$index]}"
|
||||||
|
url="${urls[$index]}"
|
||||||
|
|
||||||
|
assert_equal "$scheme scheme detection" "$scheme" "$(subscription_url_scheme "$url")"
|
||||||
|
|
||||||
|
if ! subscription_url_is_supported "$url"; then
|
||||||
|
echo "not ok - $scheme direct share link should be accepted" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - $scheme direct share link is accepted"
|
||||||
|
|
||||||
|
assert_equal "$scheme format detection" "convert" "$(detect_subscription_format "$url")"
|
||||||
|
assert_equal "$scheme display redaction" "$scheme://<redacted>" "$(subscription_url_for_display "$url")"
|
||||||
|
done
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"https subscriptions remain direct Clash inputs" \
|
||||||
|
"clash" \
|
||||||
|
"$(detect_subscription_format 'https://example.com/subscription')"
|
||||||
|
assert_equal \
|
||||||
|
"file subscriptions remain direct Clash inputs" \
|
||||||
|
"clash" \
|
||||||
|
"$(detect_subscription_format 'file:///tmp/subscription.yaml')"
|
||||||
|
|
||||||
|
if subscription_url_is_supported 'vlesss://invalid-scheme'; then
|
||||||
|
echo "not ok - unknown share-link-like schemes should remain rejected" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - unknown share-link-like schemes remain rejected"
|
||||||
|
|
||||||
|
feedback="$(print_add_feedback jp "${urls[1]}")"
|
||||||
|
if printf '%s\n' "$feedback" | grep -Fq '00000000-0000-0000-0000-000000000000'; then
|
||||||
|
echo "not ok - add feedback should not expose share-link credentials" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! printf '%s\n' "$feedback" | grep -Fq 'vless://<redacted>'; then
|
||||||
|
echo "not ok - add feedback should identify a redacted VLESS link" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - add feedback redacts share-link credentials"
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
calls_file="$tmp_dir/calls"
|
||||||
|
sample_url="${urls[1]}"
|
||||||
|
|
||||||
|
prepare() { :; }
|
||||||
|
ensure_add_use_prerequisites() { :; }
|
||||||
|
ui_progress_line() { :; }
|
||||||
|
ui_progress_done() { :; }
|
||||||
|
set_subscription() {
|
||||||
|
printf 'set|%s|%s|%s|%s\n' "$1" "$2" "$3" "$4" >> "$calls_file"
|
||||||
|
}
|
||||||
|
set_active_subscription() { printf 'active|%s\n' "$1" >> "$calls_file"; }
|
||||||
|
apply_runtime_change_after_config_mutation() { printf 'apply\n' >> "$calls_file"; }
|
||||||
|
print_add_feedback() { printf 'feedback|%s|%s\n' "$1" "$2" >> "$calls_file"; }
|
||||||
|
cmd_ls() { printf 'list\n' >> "$calls_file"; }
|
||||||
|
|
||||||
|
cmd_add "$sample_url" jp
|
||||||
|
|
||||||
|
expected_calls="$(printf '%s\n' \
|
||||||
|
"set|$sample_url|convert|jp|false" \
|
||||||
|
"active|jp" \
|
||||||
|
"apply" \
|
||||||
|
"feedback|jp|$sample_url" \
|
||||||
|
"list")"
|
||||||
|
assert_equal "clash add preserves and classifies a direct share link" "$expected_calls" "$(cat "$calls_file")"
|
||||||
|
|
||||||
|
(
|
||||||
|
# Exercise the production converter with only the local subprocess boundary stubbed.
|
||||||
|
source "$PROJECT_DIR/scripts/core/config.sh"
|
||||||
|
|
||||||
|
captured_url_file="$tmp_dir/captured-converter-url"
|
||||||
|
converter_log_file="$tmp_dir/subconverter.log"
|
||||||
|
converter_output_file="$tmp_dir/converted.yaml"
|
||||||
|
|
||||||
|
start_subconverter() { return 0; }
|
||||||
|
subconverter_url() { echo "http://127.0.0.1:25500"; }
|
||||||
|
subconverter_log_file() { echo "$converter_log_file"; }
|
||||||
|
subconverter_running() { return 0; }
|
||||||
|
subscription_yaml_validate() { return 0; }
|
||||||
|
subscription_yaml_has_no_nodes() { return 1; }
|
||||||
|
subscription_cache_store() { :; }
|
||||||
|
curl() {
|
||||||
|
local output_file="" argument
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
argument="$1"
|
||||||
|
case "$argument" in
|
||||||
|
--data-urlencode)
|
||||||
|
shift
|
||||||
|
case "${1:-}" in
|
||||||
|
url=*) printf '%s\n' "${1#url=}" > "$captured_url_file" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
-o)
|
||||||
|
shift
|
||||||
|
output_file="${1:-}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift || true
|
||||||
|
done
|
||||||
|
|
||||||
|
printf 'proxies: []\nproxy-groups: []\nrules: []\n' > "$output_file"
|
||||||
|
printf '200\nhttp://127.0.0.1:25500/sub?url=credential-bearing-link'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! convert_subscription_via_subconverter \
|
||||||
|
"$sample_url" \
|
||||||
|
"$converter_output_file" \
|
||||||
|
"manual-add" \
|
||||||
|
"direct-share-link"; then
|
||||||
|
echo "not ok - converter should accept a direct share link" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"converter receives the complete direct share link" \
|
||||||
|
"$sample_url" \
|
||||||
|
"$(cat "$captured_url_file")"
|
||||||
|
|
||||||
|
if grep -Fq '00000000-0000-0000-0000-000000000000' "$converter_log_file"; then
|
||||||
|
echo "not ok - converter diagnostics should not expose share-link credentials" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -Fq 'param: url=vless://<redacted>' "$converter_log_file"; then
|
||||||
|
echo "not ok - converter diagnostics should identify a redacted VLESS link" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - converter diagnostics redact share-link credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
# Re-source the production functions for the failure-path check.
|
||||||
|
source "$PROJECT_DIR/scripts/core/config.sh"
|
||||||
|
|
||||||
|
fallback_calls="$tmp_dir/fallback-calls"
|
||||||
|
subscription_url_by_name() { printf '%s\n' "$sample_url"; }
|
||||||
|
subscription_format_by_name() { echo "convert"; }
|
||||||
|
clear_subscription_cache() { :; }
|
||||||
|
convert_subscription_via_subconverter() {
|
||||||
|
SUBCONVERTER_LAST_ZERO_NODES="true"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
download_subscription_yaml() {
|
||||||
|
echo "unexpected-download" >> "$fallback_calls"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
mark_subscription_health_failure() { printf '%s\n' "$2" > "$tmp_dir/failure-reason"; }
|
||||||
|
warn() { :; }
|
||||||
|
|
||||||
|
if fetch_subscription_source jp "$tmp_dir/unused-runtime.yaml" "manual-add"; then
|
||||||
|
echo "not ok - a zero-node direct share link should report conversion failure" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$fallback_calls" ]; then
|
||||||
|
echo "not ok - a direct share link should not enter the Clash YAML download fallback" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected_reason="订阅转换失败:No nodes were found;分享链接无法作为 Clash YAML fallback"
|
||||||
|
assert_equal \
|
||||||
|
"zero-node share links fail without an invalid download fallback" \
|
||||||
|
"$expected_reason" \
|
||||||
|
"$(cat "$tmp_dir/failure-reason")"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user