move to common/platform/filesystem

This commit is contained in:
Meo597
2026-10-26 00:59:46 +08:00
parent 36bff1bfe7
commit 5b21e5dcbd
9 changed files with 163 additions and 160 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"github.com/xtls/xray-core/common/base64"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/platform/filesystem"
glua "github.com/yuin/gopher-lua"
"github.com/yuin/gopher-lua/parse"
)
@@ -36,7 +36,7 @@ func Compile(script string) (*Program, error) {
// TODO: Restore inline script support and re-enable the related tests.
return nil, errors.New("currently unavailable")
code, err = base64.Decode(script)
} else if name, err = platform.ResolveLuaFile(script); err == nil {
} else if name, err = filesystem.ResolveLuaFile(script); err == nil {
code, err = os.ReadFile(name)
// TODO: Restore privileged signature authorization and re-enable the related tests and CLI entries.
// if err == nil {
+58 -1
View File
@@ -2,6 +2,7 @@ package filesystem
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
@@ -61,7 +62,7 @@ func getAssetFileLocation(file string) (string, os.FileInfo, error) {
if err != nil {
return "", nil, err
}
return platform.ResolveRegularFile(platform.GetAssetLocation(local))
return ResolveRegularFile(platform.GetAssetLocation(local))
}
func ReadCert(file string) ([]byte, error) {
@@ -85,3 +86,59 @@ func CopyFile(dst string, src string) error {
_, err = f.Write(bytes)
return err
}
// 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) (resolved string, err error) {
if filepath.IsAbs(path) {
resolved, _, err = ResolveRegularFile(path)
} else {
for _, dir := range []string{
platform.GetConfDirPath(),
platform.NewEnvFlag(platform.ConfigLocation).GetValue(func() string { return "" }),
".",
platform.GetExecutableDir(),
} {
if dir != "" {
if resolved, _, err = ResolveRegularFile(filepath.Join(dir, path)); err == nil || !errors.Is(err, os.ErrNotExist) {
break
}
}
}
}
if err != nil {
return "", err
}
if filepath.Ext(resolved) != ".lua" {
return "", errors.New("Lua file extension must be .lua")
}
return resolved, nil
}
// ResolveRegularFile returns an absolute path and metadata of a regular file.
// File symlinks are resolved when possible; otherwise the original path is kept.
func ResolveRegularFile(path string) (string, os.FileInfo, error) {
path, err := filepath.Abs(path)
if err != nil {
return "", nil, fmt.Errorf("failed to resolve file path: %w", err)
}
info, err := os.Lstat(path)
if err != nil {
return "", nil, fmt.Errorf("failed to inspect file %q: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 {
// Best effort: EvalSymlinks may fail when junctions are involved.
if resolved, err := filepath.EvalSymlinks(path); err == nil {
path = resolved
}
info, err = os.Stat(path)
if err != nil {
return "", nil, fmt.Errorf("failed to inspect file %q: %w", path, err)
}
}
if !info.Mode().IsRegular() {
return "", nil, fmt.Errorf("file is not a regular file: %s", path)
}
// Hard links are not handled.
return path, info, nil
}
+92
View File
@@ -1,9 +1,15 @@
package filesystem_test
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/platform"
. "github.com/xtls/xray-core/common/platform/filesystem"
)
@@ -30,3 +36,89 @@ func TestStatAssetRejectsInvalidPath(t *testing.T) {
}
}
}
func TestResolveLuaFile(t *testing.T) {
workingDir := t.TempDir()
t.Chdir(workingDir)
executable, err := os.Executable()
common.Must(err)
file, err := os.CreateTemp(filepath.Dir(executable), "lua-*.lua")
common.Must(err)
common.Must(file.Close())
defer os.Remove(file.Name())
name := filepath.Base(file.Name())
paths := []string{
filepath.Join(t.TempDir(), name),
filepath.Join(t.TempDir(), name),
filepath.Join(workingDir, name),
file.Name(),
}
t.Setenv(platform.ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(platform.ConfigLocation, filepath.Dir(paths[1]))
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])
}
for i, want := range paths {
if i == 2 {
t.Setenv(platform.ConfdirLocation, "")
t.Setenv(platform.ConfigLocation, "")
}
if got, err := ResolveLuaFile(name); err != nil || got != want {
t.Fatalf("resolved path = %q, %v; want %q", got, err, want)
}
common.Must(os.Remove(want))
}
if _, err := ResolveLuaFile(name); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("missing file error = %v", err)
}
t.Setenv(platform.ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(platform.ConfigLocation, filepath.Dir(paths[1]))
common.Must(os.Mkdir(paths[0], 0o700))
common.Must(os.WriteFile(paths[1], nil, 0o600))
for _, path := range []string{"", name, filepath.Join(t.TempDir(), name)} {
if _, err := ResolveLuaFile(path); err == nil {
t.Fatalf("accepted invalid path %q", path)
}
}
}
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)
}
})
}
}
+2 -2
View File
@@ -14,7 +14,7 @@ func LineSeparator() string {
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
defPath := filepath.Join(assetPath, file)
for _, p := range []string{
defPath,
@@ -36,6 +36,6 @@ func GetAssetLocation(file string) string {
// GetCertLocation searches for `file` in the env dir and the executable dir
func GetCertLocation(file string) string {
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
return filepath.Join(certPath, file)
}
+2 -60
View File
@@ -1,8 +1,6 @@
package platform // import "github.com/xtls/xray-core/common/platform"
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
@@ -74,7 +72,7 @@ func NormalizeEnvName(name string) string {
return strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), ".", "_")
}
func getExecutableDir() string {
func GetExecutableDir() string {
exec, err := os.Executable()
if err != nil {
return ""
@@ -83,7 +81,7 @@ func getExecutableDir() string {
}
func GetConfigurationPath() string {
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)
configPath := NewEnvFlag(ConfigLocation).GetValue(GetExecutableDir)
return filepath.Join(configPath, "config.json")
}
@@ -92,59 +90,3 @@ func GetConfDirPath() string {
configPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return "" })
return configPath
}
// 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) (resolved string, err error) {
if filepath.IsAbs(path) {
resolved, _, err = ResolveRegularFile(path)
} else {
for _, dir := range []string{
GetConfDirPath(),
NewEnvFlag(ConfigLocation).GetValue(func() string { return "" }),
".",
getExecutableDir(),
} {
if dir != "" {
if resolved, _, err = ResolveRegularFile(filepath.Join(dir, path)); err == nil || !errors.Is(err, os.ErrNotExist) {
break
}
}
}
}
if err != nil {
return "", err
}
if filepath.Ext(resolved) != ".lua" {
return "", errors.New("Lua file extension must be .lua")
}
return resolved, nil
}
// ResolveRegularFile returns an absolute path and metadata of a regular file.
// File symlinks are resolved when possible; otherwise the original path is kept.
func ResolveRegularFile(path string) (string, os.FileInfo, error) {
path, err := filepath.Abs(path)
if err != nil {
return "", nil, fmt.Errorf("failed to resolve file path: %w", err)
}
info, err := os.Lstat(path)
if err != nil {
return "", nil, fmt.Errorf("failed to inspect file %q: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 {
// Best effort: EvalSymlinks may fail when junctions are involved.
if resolved, err := filepath.EvalSymlinks(path); err == nil {
path = resolved
}
info, err = os.Stat(path)
if err != nil {
return "", nil, fmt.Errorf("failed to inspect file %q: %w", path, err)
}
}
if !info.Mode().IsRegular() {
return "", nil, fmt.Errorf("file is not a regular file: %s", path)
}
// Hard links are not handled.
return path, info, nil
}
-88
View File
@@ -1,11 +1,9 @@
package platform_test
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/xtls/xray-core/common"
@@ -66,89 +64,3 @@ func TestGetAssetLocation(t *testing.T) {
}
}
}
func TestResolveLuaFile(t *testing.T) {
workingDir := t.TempDir()
t.Chdir(workingDir)
executable, err := os.Executable()
common.Must(err)
file, err := os.CreateTemp(filepath.Dir(executable), "lua-*.lua")
common.Must(err)
common.Must(file.Close())
defer os.Remove(file.Name())
name := filepath.Base(file.Name())
paths := []string{
filepath.Join(t.TempDir(), name),
filepath.Join(t.TempDir(), name),
filepath.Join(workingDir, name),
file.Name(),
}
t.Setenv(ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(ConfigLocation, filepath.Dir(paths[1]))
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])
}
for i, want := range paths {
if i == 2 {
t.Setenv(ConfdirLocation, "")
t.Setenv(ConfigLocation, "")
}
if got, err := ResolveLuaFile(name); err != nil || got != want {
t.Fatalf("resolved path = %q, %v; want %q", got, err, want)
}
common.Must(os.Remove(want))
}
if _, err := ResolveLuaFile(name); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("missing file error = %v", err)
}
t.Setenv(ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(ConfigLocation, filepath.Dir(paths[1]))
common.Must(os.Mkdir(paths[0], 0o700))
common.Must(os.WriteFile(paths[1], nil, 0o600))
for _, path := range []string{"", name, filepath.Join(t.TempDir(), name)} {
if _, err := ResolveLuaFile(path); err == nil {
t.Fatalf("accepted invalid path %q", path)
}
}
}
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)
}
})
}
}
+2 -2
View File
@@ -11,12 +11,12 @@ func LineSeparator() string {
// GetAssetLocation searches for `file` in the env dir and the executable dir
func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
return filepath.Join(assetPath, file)
}
// GetCertLocation searches for `file` in the env dir and the executable dir
func GetCertLocation(file string) string {
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
return filepath.Join(certPath, file)
}
+3 -3
View File
@@ -5,7 +5,7 @@ import (
"path/filepath"
xlua "github.com/xtls/xray-core/common/lua"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/main/commands/base"
)
@@ -33,7 +33,7 @@ func executeSign(cmd *base.Command, args []string) {
cmd.Usage()
}
input, info, err := platform.ResolveRegularFile(args[0])
input, info, err := filesystem.ResolveRegularFile(args[0])
if err != nil {
base.Fatalf("failed to inspect Lua file: %s", err)
}
@@ -67,7 +67,7 @@ func executeSign(cmd *base.Command, args []string) {
}
func writeSignedFile(path string, code []byte, mode os.FileMode) error {
if resolved, info, err := platform.ResolveRegularFile(path); err == nil {
if resolved, info, err := filesystem.ResolveRegularFile(path); err == nil {
path = resolved
mode = info.Mode().Perm()
} else if !os.IsNotExist(err) {
+2 -2
View File
@@ -14,7 +14,7 @@ import (
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/main/confloader"
@@ -59,7 +59,7 @@ func readConfigFile(file *os.File) ([]byte, error) {
return nil, err
}
if info.Mode().IsRegular() {
path, _, err := platform.ResolveRegularFile(file.Name())
path, _, err := filesystem.ResolveRegularFile(file.Name())
if err != nil {
return nil, err
}