Merge pull request 'fix: normalize literal \n separators in ParseProxyLines' (#4) into main
Build and Publish Docker Image / build-and-push (push) Successful in 16m8s

This commit is contained in:
杨豪
2026-08-28 21:40:46 +08:00
2 changed files with 99 additions and 0 deletions
+8
View File
@@ -127,7 +127,15 @@ func ParseYamlProxies(raw string) []model.ProxyNode {
// ParseProxyLines splits raw text into lines, skips comments/blanks/section headers,
// and parses each line as a URI or client config line.
//
// Literal "\n" / "\r\n" sequences (backslash + n, as produced by some frontends
// stuffing multi-line content into a single string) are normalized to real
// newlines before splitting so each link is parsed as its own line.
func ParseProxyLines(raw string) []model.ProxyNode {
if strings.Contains(raw, `\n`) {
raw = strings.ReplaceAll(raw, `\r\n`, "\n")
raw = strings.ReplaceAll(raw, `\n`, "\n")
}
lines := strings.Split(raw, "\n")
var result []model.ProxyNode
index := 0
+91
View File
@@ -290,3 +290,94 @@ func TestNodeJSONSerialization(t *testing.T) {
t.Errorf("expected type ss after round-trip, got %v", back["type"])
}
}
func vlessURI(name string) string {
return "vless://uuid-1234@1.2.3.4:443?encryption=none&security=tls&type=ws&host=example.com&path=%2Fpath&sni=example.com#" + name
}
// TestParseProxyLinesLiteralNewline reproduces the production case: content
// stored with literal "\n" (backslash + n) between links instead of real
// newlines. It must normalize to real newlines and parse every link.
func TestParseProxyLinesLiteralNewline(t *testing.T) {
raw := strings.Join([]string{
vlessURI("ali-seoul"),
vlessURI("Ali-Tokyo"),
vlessURI("racknerd-la"),
}, `\n`)
nodes := ParseProxies(raw)
if len(nodes) != 3 {
t.Fatalf("expected 3 nodes from literal-\\n separated links, got %d", len(nodes))
}
wantNames := []string{"ali-seoul", "Ali-Tokyo", "racknerd-la"}
for i, want := range wantNames {
if got := nodes[i]["name"]; got != want {
t.Errorf("node %d: expected name %q, got %q", i, want, got)
}
}
}
// TestParseProxyLinesLiteralCRLF normalizes literal "\\r\\n" sequences too.
func TestParseProxyLinesLiteralCRLF(t *testing.T) {
raw := strings.Join([]string{
vlessURI("Node1"),
vlessURI("Node2"),
}, `\r\n`)
nodes := ParseProxies(raw)
if len(nodes) != 2 {
t.Fatalf("expected 2 nodes from literal-\\r\\n separated links, got %d", len(nodes))
}
if nodes[0]["name"] != "Node1" || nodes[1]["name"] != "Node2" {
t.Errorf("unexpected names: %v / %v", nodes[0]["name"], nodes[1]["name"])
}
}
// TestParseProxyLinesRealNewlineUnaffected: real newline input still parses
// the same after the normalization change.
func TestParseProxyLinesRealNewlineUnaffected(t *testing.T) {
raw := strings.Join([]string{
vlessURI("Node1"),
"",
"# comment",
vlessURI("Node2"),
}, "\n")
nodes := ParseProxies(raw)
if len(nodes) != 2 {
t.Fatalf("expected 2 nodes, got %d", len(nodes))
}
if nodes[0]["name"] != "Node1" || nodes[1]["name"] != "Node2" {
t.Errorf("unexpected names: %v / %v", nodes[0]["name"], nodes[1]["name"])
}
}
// TestParseProxyLinesSingleLinkUnchanged: a single link with no separators
// keeps its exact behavior (name untouched).
func TestParseProxyLinesSingleLinkUnchanged(t *testing.T) {
raw := vlessURI("solo")
nodes := ParseProxies(raw)
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
if nodes[0]["name"] != "solo" {
t.Errorf("expected name solo, got %v", nodes[0]["name"])
}
}
// TestDecodeMaybeBase64LiteralNewlinePassthrough: structured literal-\n content
// must pass through DecodeMaybeBase64 unchanged — normalization happens inside
// ParseProxyLines (after format/base64 detection, before splitting) and must
// not interfere with base64 detection.
func TestDecodeMaybeBase64LiteralNewlinePassthrough(t *testing.T) {
raw := strings.Join([]string{
vlessURI("Node1"),
vlessURI("Node2"),
}, `\n`)
if got := DecodeMaybeBase64(raw); got != raw {
t.Error("structured literal-\\n content must pass through unchanged")
}
if nodes := ParseProxies(DecodeMaybeBase64(raw)); len(nodes) != 2 {
t.Fatalf("expected 2 nodes through full pipeline, got %d", len(nodes))
}
}