package filesystem import ( "errors" "fmt" "io" "os" "path/filepath" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/platform" ) type FileReaderFunc func(path string) (io.ReadCloser, error) var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) { return os.Open(path) } func ReadFile(path string) ([]byte, error) { reader, err := NewFileReader(path) if err != nil { return nil, err } defer reader.Close() return buf.ReadAllToBytes(reader) } func OpenAsset(file string) (io.ReadCloser, int64, error) { path, info, err := getAssetFileLocation(file) if err != nil { return nil, 0, err } r, err := NewFileReader(path) return r, info.Size(), err } func ResolveAsset(file string) (string, error) { path, _, err := getAssetFileLocation(file) return path, err } func getAssetFileLocation(file string) (string, os.FileInfo, error) { if !filepath.IsLocal(file) || file == "." { return "", nil, errors.New("asset path must stay in asset directory") } local, err := filepath.Localize(file) if err != nil { return "", nil, err } return ResolveRegularFile(platform.GetAssetLocation(local)) } func ReadCert(file string) ([]byte, error) { if filepath.IsAbs(file) { return ReadFile(file) } return ReadFile(platform.GetCertLocation(file)) } // 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. // 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) } // Best effort: EvalSymlinks may fail when Windows 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 }