Skip to content

Commit 6de54f9

Browse files
Update main.go
1 parent 9f7bdfe commit 6de54f9

1 file changed

Lines changed: 52 additions & 34 deletions

File tree

main.go

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,7 +1426,7 @@ func fetchRaw(rawURL string, timeout time.Duration) fetchResult {
14261426
type failDetail struct {
14271427
mu sync.Mutex
14281428
reasons map[string]int
1429-
samples map[string][]string // up to 100 sample config URIs per reason
1429+
samples map[string][]string
14301430
}
14311431

14321432
func validateAll(lines []string) []configResult {
@@ -1665,14 +1665,11 @@ func classifyFailReason(reason string) string {
16651665
return "PARSE › url parse error"
16661666
case strings.HasPrefix(r, "PARSE: unsupported cipher:"):
16671667
return "PARSE › unsupported SS cipher"
1668+
case strings.HasPrefix(r, "PARSE: unsupported transport: xhttp"),
1669+
strings.HasPrefix(r, "PARSE: unsupported transport: splithttp"):
1670+
return "PARSE › unsupported transport (xhttp/splithttp)"
16681671
case strings.HasPrefix(r, "PARSE: unsupported transport:"):
1669-
msg := strings.TrimPrefix(r, "PARSE: unsupported transport: ")
1670-
switch msg {
1671-
case "xhttp", "splithttp":
1672-
return "PARSE › unsupported transport (xhttp/splithttp)"
1673-
default:
1674-
return "PARSE › unsupported transport (kcp/quic/mkcp)"
1675-
}
1672+
return "PARSE › unsupported transport (kcp/quic/mkcp)"
16761673
case r == "PARSE: missing @" || r == "PARSE: missing server" ||
16771674
r == "PARSE: missing uuid" || r == "PARSE: missing password" ||
16781675
r == "PARSE: missing port" || r == "PARSE: missing auth":
@@ -2146,10 +2143,10 @@ func toSingBoxOutbound(configURL, protocol string) (string, string) {
21462143
}
21472144

21482145
func sanitizeProxyURL(raw string) string {
2149-
// ── HTML entity decode ──────────────────────────────────────────────
2150-
// Config sources (Telegram web, HTML pages) often HTML-encode ampersands:
2151-
// ?security=reality&pbk=KEY → url.Parse reads "amp;pbk" not "pbk"
2152-
// so q.Get("pbk") returns "" → "reality missing public key" error on ~420 configs.
2146+
// ── HTML entity decode ────────────────────────────────────────────────────
2147+
// Sources like Telegram web / HTML pages HTML-encode ampersands:
2148+
// ?security=reality&pbk=KEY → url.Parse reads key as "amp;pbk" not "pbk"
2149+
// so q.Get("pbk") returns "" → "reality missing public key" for ~420 configs.
21532150
raw = strings.ReplaceAll(raw, "&", "&")
21542151
raw = strings.ReplaceAll(raw, "&lt;", "<")
21552152
raw = strings.ReplaceAll(raw, "&gt;", ">")
@@ -2187,15 +2184,13 @@ func sanitizeProxyURL(raw string) string {
21872184
}
21882185

21892186
func normalizeUUID(u string) string {
2190-
// Standard UUID: 8-4-4-4-12 hex chars with dashes
2191-
// Some configs provide a UUID as 32 hex chars without dashes.
2192-
// sing-box requires the dashed format.
2187+
// Some configs provide UUID as 32 hex chars without dashes.
2188+
// sing-box requires the standard 8-4-4-4-12 dashed format.
21932189
if len(u) == 32 {
21942190
allHex := true
21952191
for _, c := range u {
21962192
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
2197-
allHex = false
2198-
break
2193+
allHex = false; break
21992194
}
22002195
}
22012196
if allHex {
@@ -2205,6 +2200,28 @@ func normalizeUUID(u string) string {
22052200
return u
22062201
}
22072202

2203+
func sanitizeSNI(sni, fallback string) string {
2204+
// SNI must be a valid ASCII hostname — no emoji, spaces, wildcards in SNI extension.
2205+
// Wildcard like *.domain.com: strip the "*." prefix → domain.com
2206+
// (TLS RFC 6066 does not permit wildcards in SNI)
2207+
if strings.HasPrefix(sni, "*.") {
2208+
sni = sni[2:]
2209+
}
2210+
if sni == "" {
2211+
return fallback
2212+
}
2213+
for _, c := range sni {
2214+
if c > 127 || (c != '.' && c != '-' && !(c >= 'a' && c <= 'z') &&
2215+
!(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9')) {
2216+
return fallback
2217+
}
2218+
}
2219+
if len(sni) > 0 && (sni[0] == '.' || sni[0] == '-' || sni[len(sni)-1] == '.' || sni[len(sni)-1] == '-') {
2220+
return fallback
2221+
}
2222+
return sni
2223+
}
2224+
22082225
func encodeUserInfo(s string) string {
22092226
var buf strings.Builder
22102227
for i := 0; i < len(s); i++ {
@@ -2252,7 +2269,7 @@ func parseVMessURItoD(data string) (map[string]interface{}, string) {
22522269
"scy": first(q.Get("encryption"), q.Get("scy"), "auto"),
22532270
"net": first(q.Get("type"), q.Get("net"), "tcp"),
22542271
"tls": tlsVal,
2255-
"sni": first(q.Get("sni"), q.Get("peer"), host),
2272+
"sni": sanitizeSNI(first(q.Get("sni"), q.Get("peer"), ""), host),
22562273
"path": q.Get("path"),
22572274
"host": q.Get("host"),
22582275
"serviceName": q.Get("serviceName"),
@@ -2424,20 +2441,21 @@ func parseVLess(raw string) (string, string) {
24242441
return "", "port: " + err.Error()
24252442
}
24262443
q := u.Query()
2427-
// TrimSpace: handles configs where the security value has trailing whitespace
2444+
// TrimSpace: handles configs with trailing whitespace in security value
24282445
security := strings.TrimSpace(strings.ToLower(q.Get("security")))
24292446
network := strings.ToLower(q.Get("type"))
24302447
if network == "" {
24312448
network = "tcp"
24322449
}
24332450
// Reject transports not supported by installed sing-box:
2434-
// xhttp/splithttp causes ~5000 START failures → filter at PARSE
2435-
// kcp/mkcp/quic are Xray-only never supported by sing-box
2451+
// xhttp/splithttp ~5000 START failures (rejected by sing-box config parser)
2452+
// kcp/mkcp/quic Xray-only, never supported by sing-box
24362453
switch network {
24372454
case "xhttp", "splithttp", "kcp", "mkcp", "quic":
24382455
return "", "unsupported transport: " + network
24392456
}
2440-
sni := first(q.Get("sni"), q.Get("peer"), server)
2457+
// sanitizeSNI: fixes wildcard SNI (*.domain.com→domain.com) and strips emoji/invalid chars
2458+
sni := sanitizeSNI(first(q.Get("sni"), q.Get("peer"), ""), server)
24412459
// Filter flow: sing-box only supports xtls-rprx-vision; others cause FATAL config errors
24422460
flow := q.Get("flow")
24432461
if !singboxSupportedFlows[flow] {
@@ -2449,8 +2467,14 @@ func parseVLess(raw string) (string, string) {
24492467
}
24502468
transport := buildTransportJSON(network, first(q.Get("path"), "/"), q.Get("host"),
24512469
first(q.Get("serviceName"), q.Get("path")))
2452-
return fmt.Sprintf(`{"type":"vless","tag":"proxy","server":%q,"server_port":%d,"uuid":%q%s%s}`,
2453-
server, port, uuid, tlsJSON, transport), ""
2470+
// packetEncoding: pass through to sing-box (supports xudp/packetaddr for VLESS)
2471+
packetEnc := q.Get("packetEncoding")
2472+
packetJSON := ""
2473+
if packetEnc == "xudp" || packetEnc == "packetaddr" {
2474+
packetJSON = fmt.Sprintf(`,"packet_encoding":%q`, packetEnc)
2475+
}
2476+
return fmt.Sprintf(`{"type":"vless","tag":"proxy","server":%q,"server_port":%d,"uuid":%q%s%s%s}`,
2477+
server, port, uuid, tlsJSON, transport, packetJSON), ""
24542478
}
24552479

24562480
func vlessTLS(security, sni, flow string, q url.Values) (string, string) {
@@ -2461,7 +2485,7 @@ func vlessTLS(security, sni, flow string, q url.Values) (string, string) {
24612485
switch security {
24622486
case "tls", "xtls":
24632487
// Do NOT include flowJSON: sing-box only accepts xtls-rprx-vision flow
2464-
// with reality TLS. Adding it to plain TLS causes START failures.
2488+
// with reality. Including it with plain TLS causes ~5000 START failures.
24652489
s := fmt.Sprintf(`,"tls":{"enabled":true,"insecure":true,"server_name":%q`, sni)
24662490
if fp := q.Get("fp"); fp != "" {
24672491
s += fmt.Sprintf(`,"utls":{"enabled":true,"fingerprint":%q}`, fp)
@@ -2536,7 +2560,6 @@ func parseTrojan(raw string) (string, string) {
25362560
return "", "port: " + err.Error()
25372561
}
25382562
q := u.Query()
2539-
sni := first(q.Get("sni"), q.Get("peer"), server)
25402563
tls := fmt.Sprintf(`,"tls":{"enabled":true,"insecure":true,"server_name":%q`, sni)
25412564
if fp := q.Get("fp"); fp != "" {
25422565
tls += fmt.Sprintf(`,"utls":{"enabled":true,"fingerprint":%q}`, fp)
@@ -2547,6 +2570,7 @@ func parseTrojan(raw string) (string, string) {
25472570
case "xhttp", "splithttp", "kcp", "mkcp", "quic":
25482571
return "", "unsupported transport: " + network
25492572
}
2573+
sni := sanitizeSNI(first(q.Get("sni"), q.Get("peer"), ""), server)
25502574
transport := buildTransportJSON(network, first(q.Get("path"), "/"), q.Get("host"),
25512575
first(q.Get("serviceName"), q.Get("path")))
25522576
return fmt.Sprintf(`{"type":"trojan","tag":"proxy","server":%q,"server_port":%d,"password":%q%s%s}`,
@@ -2611,7 +2635,6 @@ func parseShadowsocks(raw string) (string, string) {
26112635
if !fastPathOK {
26122636
atIdx := strings.LastIndex(trimmed, "@")
26132637
if atIdx == -1 {
2614-
// Strip query string before b64 decode: ss://BASE64?plugin=obfs
26152638
b64Src := trimmed
26162639
if qi := strings.Index(b64Src, "?"); qi != -1 { b64Src = b64Src[:qi] }
26172640
decoded, err := decodeBase64([]byte(b64Src))
@@ -3976,18 +3999,14 @@ func extractErr(stderr string) string {
39763999
}
39774000

39784001
func extractErrVerbose(stderr string) string {
3979-
// Picks the most informative sing-box error line.
3980-
// Extracts the "msg" field from JSON-format sing-box log lines.
39814002
var first, best string
39824003
priority := []string{"invalid", "failed", "decode", "unsupported", "error"}
39834004
for _, line := range strings.Split(stderr, "\n") {
39844005
line = strings.TrimSpace(line)
39854006
if line == "" { continue }
39864007
lower := strings.ToLower(line)
39874008
if strings.Contains(lower, "warn") || strings.Contains(lower, "deprecated") { continue }
3988-
if strings.Contains(lower, `"level":"info"`) || strings.Contains(lower, `"level":"debug"`) ||
3989-
strings.Contains(lower, "level=info") || strings.Contains(lower, "level=debug") { continue }
3990-
// Extract msg field from JSON log: {"level":"error","msg":"decode failed: ..."}
4009+
if strings.Contains(lower, `"level":"info"`) || strings.Contains(lower, "level=info") { continue }
39914010
if idx := strings.Index(line, `"msg":"`); idx != -1 {
39924011
end := strings.Index(line[idx+7:], `"`)
39934012
if end != -1 { line = line[idx+7 : idx+7+end]; lower = strings.ToLower(line) }
@@ -3999,8 +4018,7 @@ func extractErrVerbose(stderr string) string {
39994018
}
40004019
}
40014020
}
4002-
r := best
4003-
if r == "" { r = first }
4021+
r := best; if r == "" { r = first }
40044022
if len(r) > 180 { r = r[:180] + "..." }
40054023
return r
40064024
}

0 commit comments

Comments
 (0)