diff --git a/app/geodata/download_test.go b/app/geodata/download_test.go index 32b9fa61c..c7e225aa7 100644 --- a/app/geodata/download_test.go +++ b/app/geodata/download_test.go @@ -40,11 +40,18 @@ func TestDownloadOneLuaGuard(t *testing.T) { if err != nil { t.Fatal(err) } - data, err := os.ReadFile(staged.temp) + tx, err := swapAll([]stage{staged}) + if err != nil { + t.Fatal(err) + } + if err := tx.commit(); err != nil { + t.Fatal(err) + } + data, err := os.ReadFile(staged.target) if err != nil || string(data) != luaGuardHeader+tc.body { t.Fatalf("downloaded data = % x, %v", data, err) } - program, err := xlua.Compile(staged.temp) + program, err := xlua.Compile(staged.target) var syntaxErr *parse.Error if program != nil || !errors.As(err, &syntaxErr) || syntaxErr.Pos.Line != 1 || syntaxErr.Pos.Column != 1 { t.Fatalf("want Lua rejection at the first byte, got %v, %v", program, err) @@ -57,7 +64,7 @@ func TestDownloadOneLuaGuard(t *testing.T) { if len(list.Entry) != 1 || list.Entry[0].Code != tc.code { t.Fatalf("header changed geodata: %v", &list) } - if _, err := commongeodata.ParseDomainRules([]string{"ext:" + filepath.Base(staged.temp) + ":" + tc.code}, commongeodata.Domain_Full); err != nil { + if _, err := commongeodata.ParseDomainRules([]string{"ext:" + filepath.Base(staged.target) + ":" + tc.code}, commongeodata.Domain_Full); err != nil { t.Fatalf("geodata loader rejected the header: %v", err) } } diff --git a/common/lua/program_test.go b/common/lua/program_test.go index 28e752d7a..1c0664976 100644 --- a/common/lua/program_test.go +++ b/common/lua/program_test.go @@ -21,10 +21,7 @@ import ( ) func TestProgramStatesAreIndependent(t *testing.T) { - path := filepath.Join(t.TempDir(), "state.lua") - if err := os.WriteFile(path, []byte("value = (value or 0) + 1"), 0o600); err != nil { - t.Fatal(err) - } + path := writeTestScript(t, "value = (value or 0) + 1") program, err := Compile(path) if err != nil { t.Fatal(err) @@ -191,6 +188,22 @@ func TestCompileInlineDiagnostics(t *testing.T) { } */ +func TestCompileFileExtension(t *testing.T) { + for _, name := range []string{"script", "script.dat", "script.lua.txt", "script.LUA"} { + path := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil { + t.Fatal(err) + } + if program, err := Compile(path); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") { + t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", path, program, err) + } + } + path := writeTestScript(t, "value = 42") + string(filepath.Separator) + "." + if program, err := Compile(path); program == nil || err != nil { + t.Fatalf("Compile(%q) = %v, %v; want acceptance of the resolved .lua path", path, program, err) + } +} + func TestCompileRejectsReadFailure(t *testing.T) { path := filepath.Join(t.TempDir(), "unreadable.lua") if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil { @@ -255,6 +268,10 @@ func writeTestScript(t *testing.T, code string) string { if err := os.WriteFile(path, []byte(code), 0o600); err != nil { t.Fatal(err) } + path, err := filepath.EvalSymlinks(path) + if err != nil { + t.Fatal(err) + } return path } diff --git a/common/platform/platform.go b/common/platform/platform.go index 4c5cf2a0f..9a650876a 100644 --- a/common/platform/platform.go +++ b/common/platform/platform.go @@ -93,7 +93,7 @@ func GetConfDirPath() string { return configPath } -// ResolveLuaFile finds a local Lua script and returns its absolute path. +// ResolveLuaFile finds a local .lua script and returns its resolved absolute path. // Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir. func ResolveLuaFile(path string) (string, error) { if path == "" { @@ -113,7 +113,22 @@ func ResolveLuaFile(path string) (string, error) { } } } - return resolveFile(paths) + path, err := resolveFile(paths) + if err != nil { + return "", err + } + return resolveLuaTarget(path) +} + +func resolveLuaTarget(path string) (string, error) { + path, err := filepath.EvalSymlinks(path) + if err != nil { + return "", fmt.Errorf("failed to resolve Lua file symlinks: %w", err) + } + if filepath.Ext(path) != ".lua" { + return "", errors.New("Lua file extension must be .lua") + } + return path, nil } func resolveFile(paths []string) (string, error) { diff --git a/common/platform/platform_test.go b/common/platform/platform_test.go index 95d5b6545..87df1a195 100644 --- a/common/platform/platform_test.go +++ b/common/platform/platform_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" "github.com/xtls/xray-core/common" @@ -88,6 +89,10 @@ func TestResolveLuaFile(t *testing.T) { for _, path := range paths[:3] { common.Must(os.WriteFile(path, nil, 0o600)) } + for i, path := range paths { + paths[i], err = filepath.EvalSymlinks(path) + common.Must(err) + } if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] { t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2]) } @@ -115,3 +120,35 @@ func TestResolveLuaFile(t *testing.T) { } } } + +func TestResolveLuaFileSymlink(t *testing.T) { + for _, tc := range []struct { + link, target string + valid bool + }{ + {"alias.lua", "data.dat", false}, + {"alias.dat", "script.lua", true}, + } { + t.Run(tc.link, func(t *testing.T) { + dir, err := filepath.EvalSymlinks(t.TempDir()) + common.Must(err) + target := filepath.Join(dir, tc.target) + common.Must(os.WriteFile(target, nil, 0o600)) + link := filepath.Join(dir, tc.link) + if err := os.Symlink(tc.target, link); err != nil { + if runtime.GOOS == "windows" { + t.Skipf("cannot create symlinks: %v", err) + } + t.Fatal(err) + } + got, err := ResolveLuaFile(link) + if tc.valid { + if err != nil || got != target { + t.Fatalf("ResolveLuaFile(%q) = %q, %v; want %q", link, got, err, target) + } + } else if err == nil || got != "" || !strings.Contains(err.Error(), "Lua file extension must be .lua") { + t.Fatalf("ResolveLuaFile(%q) = %q, %v; want .lua extension error", link, got, err) + } + }) + } +}