Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 307 additions & 0 deletions scripts/libs/yaml_dns.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
#!/bin/sh
# Copyright (C) Juewuy

# Extract the top-level dns section without requiring a YAML parser. The
# merger below normalizes block and one-line flow mappings before overlaying
# fields, so the final configuration contains only one dns key.

yaml_dns_extract() {
awk '
function top_level(line) {
return line !~ /^[[:space:]#]/ && line ~ /^[^:]+:/
}
function brace_delta(line, i, c, quote, escaped, delta) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "#" && (i == 1 || substr(line, i - 1, 1) ~ /[[:space:]]/)) break
else if (c == "{") delta++
else if (c == "}") delta--
}
return delta
}
{
if (!found) {
if ($0 ~ /^dns:[[:space:]]*\{/) {
print
flow = 1
balance = brace_delta($0)
if (balance <= 0) exit
found = 1
next
}
if ($0 ~ /^dns:[[:space:]]*($|#)/) {
print
found = 1
}
next
}
if (flow) {
print
balance += brace_delta($0)
if (balance <= 0) exit
next
}
if (top_level($0)) exit
print
}' "$1"
}

yaml_dns_without() {
awk '
function top_level(line) {
return line !~ /^[[:space:]#]/ && line ~ /^[^:]+:/
}
function brace_delta(line, i, c, quote, escaped, delta) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "#" && (i == 1 || substr(line, i - 1, 1) ~ /[[:space:]]/)) break
else if (c == "{") delta++
else if (c == "}") delta--
}
return delta
}
{
if (!in_dns) {
if ($0 ~ /^dns:[[:space:]]*\{/) {
in_dns = 1
flow = 1
balance = brace_delta($0)
if (balance <= 0) in_dns = 0
next
}
if ($0 ~ /^dns:[[:space:]]*($|#)/) {
in_dns = 1
next
}
print
next
}
if (flow) {
balance += brace_delta($0)
if (balance <= 0) in_dns = 0
next
}
if (top_level($0)) {
in_dns = 0
print
next
}
}' "$1"
}

yaml_top_level_section() {
awk -v section="$2" '
function top_level(line) {
return line !~ /^[[:space:]#]/ && line ~ /^[^:]+:/
}
{
if (!started && $0 ~ section) started = 1
if (started && top_level($0) && $0 !~ section) exit
if (started) print
}' "$1"
}

yaml_dns_merge() {
awk -v original="${1:-/dev/null}" \
-v managed="${2:-/dev/null}" \
-v user="${3:-/dev/null}" '
function trim(value) {
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function strip_comment(line, i, c, quote, escaped) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "#" && (i == 1 || substr(line, i - 1, 1) ~ /[[:space:]]/))
return substr(line, 1, i - 1)
}
return line
}
function brace_delta(line, i, c, quote, escaped, delta) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "#" && (i == 1 || substr(line, i - 1, 1) ~ /[[:space:]]/)) break
else if (c == "{") delta++
else if (c == "}") delta--
}
return delta
}
function add_entry(source, key, value) {
if (!(source SUBSEP key in entry)) {
order[source, ++count[source]] = key
}
entry[source, key] = value
}
function append_entry(source, key, line) {
if (entry[source, key] == "") entry[source, key] = line
else entry[source, key] = entry[source, key] "\n" line
}
function flow_pair(source, pair, i, c, depth_brace, depth_square, quote, escaped, colon, key, value) {
pair = trim(pair)
colon = 0
depth_brace = depth_square = 0
quote = escaped = 0
for (i = 1; i <= length(pair); i++) {
c = substr(pair, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
continue
}
if (c == "\047") quote = "\047"
else if (c == "\042") quote = "\042"
else if (c == "{") depth_brace++
else if (c == "}") depth_brace--
else if (c == "[") depth_square++
else if (c == "]") depth_square--
else if (c == ":" && depth_brace == 0 && depth_square == 0) {
colon = i
break
}
}
if (!colon) return
key = trim(substr(pair, 1, colon - 1))
value = trim(substr(pair, colon + 1))
if ((substr(key, 1, 1) == "\047" && substr(key, length(key), 1) == "\047") ||
(substr(key, 1, 1) == "\042" && substr(key, length(key), 1) == "\042"))
key = substr(key, 2, length(key) - 2)
add_entry(source, key, " " key ": " value)
}
function parse_flow(source, text, i, c, depth_brace, depth_square, quote, escaped, start) {
text = trim(strip_comment(text))
if (substr(text, 1, 1) == "{") text = substr(text, 2)
if (substr(text, length(text), 1) == "}") text = substr(text, 1, length(text) - 1)
Comment thread
yorkyang2333 marked this conversation as resolved.
start = 1
depth_brace = depth_square = 0
quote = escaped = 0
for (i = 1; i <= length(text); i++) {
c = substr(text, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
continue
}
if (c == "\047") quote = "\047"
else if (c == "\042") quote = "\042"
else if (c == "{") depth_brace++
else if (c == "}") depth_brace--
else if (c == "[") depth_square++
else if (c == "]") depth_square--
else if (c == "," && depth_brace == 0 && depth_square == 0) {
flow_pair(source, substr(text, start, i - start))
start = i + 1
}
}
flow_pair(source, substr(text, start))
}
function parse_file(file, source, line, rest, key, value) {
started = flow = balance = 0
current = ""
flow_text = ""
while ((getline line < file) > 0) {
if (!started) {
if (line ~ /^dns:[[:space:]]*\{/) {
rest = line
sub(/^dns:[[:space:]]*/, "", rest)
flow_text = strip_comment(rest)
flow = 1
balance = brace_delta(line)
started = 1
if (balance <= 0) {
parse_flow(source, flow_text)
started = flow = 0
}
} else if (line ~ /^dns:[[:space:]]*($|#)/) {
started = 1
}
continue
}
if (flow) {
flow_text = flow_text " " trim(strip_comment(line))
balance += brace_delta(line)
if (balance <= 0) {
parse_flow(source, flow_text)
started = flow = 0
}
continue
}
if (line ~ /^[[:space:]][[:space:]][A-Za-z0-9_-]+[[:space:]]*:/) {
if (current != "") add_entry(source, key, value)
key = line
sub(/^[[:space:]]+/, "", key)
sub(/[[:space:]]*:.*/, "", key)
value = line
current = key
} else if (current != "") {
value = value "\n" line
}
}
if (current != "") add_entry(source, key, value)
close(file)
}
function managed_key(key) {
return key == "enable" || key == "listen" || key == "use-hosts" ||
key == "ipv6" || key == "default-nameserver" || key == "direct-nameserver" ||
key == "enhanced-mode" || key == "fake-ip-range" || key == "fake-ip-range6" ||
key == "fake-ip-filter" || key == "respect-rules" ||
key == "proxy-server-nameserver" || key == "nameserver"
}
BEGIN {
parse_file(original, "original")
parse_file(managed, "managed")
parse_file(user, "user")
print "dns:"
for (i = 1; i <= count["original"]; i++) {
key = order["original", i]
# nameserver-policy is intentionally passthrough: ShellCrash
# generated policy must not replace subscription-specific routes.
if (!managed_key(key) && !("user" SUBSEP key in entry))
print entry["original", key]
}
for (i = 1; i <= count["managed"]; i++) {
key = order["managed", i]
if (!("user" SUBSEP key in entry) &&
!(key == "nameserver-policy" && ("original" SUBSEP key in entry)))
print entry["managed", key]
}
for (i = 1; i <= count["user"]; i++) {
key = order["user", i]
print entry["user", key]
}
}'
}

case "${1:-}" in
extract)
yaml_dns_extract "$2"
;;
without)
yaml_dns_without "$2"
;;
merge)
yaml_dns_merge "$2" "$3" "$4"
;;
esac
4 changes: 3 additions & 1 deletion scripts/menus/dns.sh
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,9 @@ set_dns_adv() {
if echo "$crashcore" | grep -qE 'meta|singbox'; then
dns_nameserver='https://dns.alidns.com/dns-query, https://doh.pub/dns-query'
dns_fallback='https://cloudflare-dns.com/dns-query, https://dns.google/dns-query, https://doh.opendns.com/dns-query'
dns_resolver='https://223.5.5.5/dns-query, 2400:3200::1'
# Mihomo's default-nameserver resolver must be plain IPs;
# encrypted resolvers belong in dns_nameserver/dns_fallback.
dns_resolver='223.5.5.5, 2400:3200::1'
setconfig dns_nameserver "'$dns_nameserver'"
setconfig dns_fallback "'$dns_fallback'"
setconfig dns_resolver "'$dns_resolver'"
Expand Down
8 changes: 6 additions & 2 deletions scripts/menus/providers_clash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ __IS_PROVIDERS_CLASH=1
load_lang providers

. "$CRASHDIR"/libs/web_get_bin.sh
. "$CRASHDIR"/libs/yaml_dns.sh

# 生成clash的providers配置文件
gen_providers() {
Expand All @@ -29,11 +30,14 @@ gen_providers() {
fi
# 生成proxy_providers模块
mkdir -p "$TMPDIR"/providers
# Keep an optional DNS section from the provider template. It is otherwise
# lost when the template is reduced to proxy providers, groups and rules.
yaml_dns_extract "$TMPDIR"/provider_temp_file >"$TMPDIR"/providers/dns.yaml
# 预创建文件并写入对应文件头
echo 'proxy-providers:' >"$TMPDIR"/providers/providers.yaml
# 切割模版文件
sed -n '/^proxy-groups:/,/^[a-z]/ { /^rule/d; p; }' "$TMPDIR"/provider_temp_file >"$TMPDIR"/providers/proxy-groups.yaml
sed -n '/^rule/,$p' "$TMPDIR"/provider_temp_file >"$TMPDIR"/providers/rules.yaml
yaml_top_level_section "$TMPDIR"/provider_temp_file '^rule' >"$TMPDIR"/providers/rules.yaml
rm -rf "$TMPDIR"/provider_temp_file
# 基于单订阅生成providers模块
if [ -n "$1" ]; then
Expand All @@ -58,7 +62,7 @@ gen_providers() {
fi
# 修饰模版文件并合并
sed -i "s/{providers_tags}/$providers_tags/g" "$TMPDIR"/providers/proxy-groups.yaml
cut -c 1- "$TMPDIR"/providers/providers.yaml "$TMPDIR"/providers/proxy-groups.yaml "$TMPDIR"/providers/rules.yaml >"$TMPDIR"/config.yaml
cut -c 1- "$TMPDIR"/providers/providers.yaml "$TMPDIR"/providers/proxy-groups.yaml "$TMPDIR"/providers/rules.yaml "$TMPDIR"/providers/dns.yaml >"$TMPDIR"/config.yaml
Comment thread
yorkyang2333 marked this conversation as resolved.
rm -rf "$TMPDIR"/providers
# 调用内核测试
. "$CRASHDIR"/starts/check_core.sh && check_core && "$TMPDIR"/CrashCore -t -d "$BINDIR" -f "$TMPDIR"/config.yaml
Expand Down
Loading