Compare commits

..
Author SHA1 Message Date
Meo597 92a7a528d1 move more to pb 2026-10-27 01:31:55 +08:00
Meo597 092159313a limit 100 2026-10-27 01:22:26 +08:00
Meo597 72b4d0ca45 pb bypass 2026-10-27 01:18:27 +08:00
Meo597 e7038e99e6 vformat 2026-10-27 00:51:40 +08:00
Meo597 60580634a4 limit cron 1min 2026-10-27 00:39:11 +08:00
Meo597 8351e49ed6 limit the file ext for downloads 2026-10-26 09:05:07 +08:00
Meo597 f6855314ea limit the file size for downloads 2026-10-26 08:56:18 +08:00
15 changed files with 304 additions and 126 deletions
+11 -3
View File
@@ -21,7 +21,10 @@ import (
"golang.org/x/net/http2"
)
const idleTimeout = 30 * time.Second
const (
idleTimeout = 30 * time.Second
maxDownloadSize int64 = 256 * 1024 * 1024
)
// DO NOT EDIT. Ignored by geodata, rejected by Lua.
const luaGuardHeader = "\x12\x01\x00"
@@ -216,14 +219,19 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
io.Copy(io.Discard, resp.Body)
return errors.New("unexpected status code: ", resp.StatusCode)
}
if resp.ContentLength > maxDownloadSize {
return errors.New("geodata download exceeds 256 MiB")
}
n, err := io.Copy(writer, resp.Body)
n, err := io.Copy(writer, io.LimitReader(resp.Body, maxDownloadSize+1))
if err != nil {
return err
}
if n > maxDownloadSize {
return errors.New("geodata download exceeds 256 MiB")
}
if n == 0 {
return errors.New("empty response body")
}
+53
View File
@@ -7,10 +7,13 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
commongeodata "github.com/xtls/xray-core/common/geodata"
xlua "github.com/xtls/xray-core/common/lua"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
"github.com/yuin/gopher-lua/parse"
"google.golang.org/protobuf/proto"
)
@@ -71,3 +74,53 @@ func TestDownloadOneLuaGuard(t *testing.T) {
})
}
}
func TestProtobufConfigValidation(t *testing.T) {
dir := t.TempDir()
t.Setenv("xray.location.asset", dir)
for _, name := range []string{"asset.dat", "asset.DAT"} {
if err := os.WriteFile(filepath.Join(dir, name), nil, 0o600); err != nil {
t.Fatal(err)
}
}
if err := os.Mkdir(filepath.Join(dir, "directory.dat"), 0o700); err != nil {
t.Fatal(err)
}
server, err := core.New(&core.Config{})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = server.Close() })
for _, tc := range []struct {
name string
config *Config
wantError string
}{
{"minutely cron", &Config{Cron: "* * * * *"}, ""},
{"minimum interval", &Config{Cron: "@every 1m"}, ""},
{"short interval", &Config{Cron: "@every 59s"}, "interval must be at least 1 minute"},
{"fractional interval", &Config{Cron: "@every 59.999s"}, "interval must be at least 1 minute"},
{"invalid cron", &Config{Cron: "invalid"}, "invalid geodata cron"},
{"dat extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.dat"}}}, ""},
{"uppercase extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.DAT"}}}, ""},
{"invalid extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.lua"}}}, "extension must be .dat"},
{"missing extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset"}}}, "extension must be .dat"},
{"missing file", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "missing.dat"}}}, "invalid geodata asset file"},
{"directory", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "directory.dat"}}}, "asset is not a regular file"},
} {
t.Run(tc.name, func(t *testing.T) {
config, err := serial.ToTypedMessage(tc.config).GetInstance()
if err != nil {
t.Fatal(err)
}
_, err = core.CreateObject(server, config)
if tc.wantError == "" {
if err != nil {
t.Fatal(err)
}
} else if err == nil || !strings.Contains(err.Error(), tc.wantError) {
t.Fatalf("CreateObject() error = %v; want %q", err, tc.wantError)
}
})
}
}
+23 -3
View File
@@ -2,12 +2,16 @@ package geodata
import (
"context"
"path/filepath"
"strings"
"sync"
"time"
"github.com/robfig/cron/v3"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
commongeodata "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/routing"
)
@@ -25,6 +29,24 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
if config.Cron == "" {
return &Instance{}, nil
}
if len(config.Assets) > 100 {
return nil, errors.New("geodata supports at most 100 assets")
}
for _, asset := range config.Assets {
if !strings.EqualFold(filepath.Ext(asset.GetFile()), ".dat") {
return nil, errors.New("geodata asset file extension must be .dat")
}
if _, err := filesystem.StatAsset(asset.GetFile()); err != nil {
return nil, errors.New("invalid geodata asset file: ", asset.GetFile()).Base(err)
}
}
schedule, err := cron.ParseStandard(config.Cron)
if err != nil {
return nil, errors.New("invalid geodata cron").Base(err)
}
if interval, ok := schedule.(cron.ConstantDelaySchedule); ok && interval.Delay < time.Minute {
return nil, errors.New("geodata cron interval must be at least 1 minute")
}
g := &Instance{
assets: config.Assets,
@@ -44,9 +66,7 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
cron.WithLogger(cron.DiscardLogger),
)
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil {
return nil, errors.New("invalid geodata cron").Base(err)
}
g.tasker.Schedule(schedule, cron.FuncJob(g.execute))
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
return g, nil
+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/filesystem"
"github.com/xtls/xray-core/common/platform"
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 = filesystem.ResolveLuaFile(script); err == nil {
} else if name, err = platform.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 {
+11 -8
View File
@@ -187,20 +187,19 @@ func TestCompileInlineDiagnostics(t *testing.T) {
*/
func TestCompileFileExtension(t *testing.T) {
for _, name := range []string{"script", "script.dat", "script.lua.txt"} {
if program, err := Compile(name); 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", name, program, err)
}
}
for _, name := range []string{"script.lua", "script.LUA", "script.Lua", "script.lUa"} {
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 {
t.Fatalf("Compile(%q) = %v, %v; want successful compilation", path, program, 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) {
@@ -235,6 +234,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
}
+24 -24
View File
@@ -5,7 +5,6 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/platform"
@@ -27,6 +26,14 @@ func ReadFile(path string) ([]byte, error) {
return buf.ReadAllToBytes(reader)
}
func ReadAsset(file string) ([]byte, error) {
path, _, err := getAssetFileLocation(file)
if err != nil {
return nil, err
}
return ReadFile(path)
}
func OpenAsset(file string) (io.ReadCloser, int64, error) {
path, info, err := getAssetFileLocation(file)
if err != nil {
@@ -36,6 +43,11 @@ func OpenAsset(file string) (io.ReadCloser, int64, error) {
return r, info.Size(), err
}
func StatAsset(file string) (os.FileInfo, error) {
_, info, err := getAssetFileLocation(file)
return info, err
}
func ResolveAsset(file string) (string, error) {
path, _, err := getAssetFileLocation(file)
return path, err
@@ -67,29 +79,17 @@ func ReadCert(file string) ([]byte, error) {
return ReadFile(platform.GetCertLocation(file))
}
// ResolveLuaFile finds a local .lua script and returns its absolute path.
// Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
func ResolveLuaFile(path string) (string, error) {
if !strings.EqualFold(filepath.Ext(path), ".lua") {
return "", errors.New("Lua file extension must be .lua")
func CopyFile(dst string, src string) error {
bytes, err := ReadFile(src)
if err != nil {
return err
}
if filepath.IsAbs(path) {
return path, nil
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
for _, dir := range []string{
platform.GetConfDirPath(),
platform.NewEnvFlag(platform.ConfigLocation).GetValue(func() string { return "" }),
".",
platform.GetExecutableDir(),
} {
if dir != "" {
resolved := filepath.Join(dir, path)
if _, err := os.Stat(resolved); err == nil {
return filepath.Abs(resolved)
} else if !os.IsNotExist(err) {
return "", err
}
}
}
return "", os.ErrNotExist
defer f.Close()
_, err = f.Write(bytes)
return err
}
+2 -47
View File
@@ -1,17 +1,13 @@
package filesystem_test
import (
"errors"
"os"
"path/filepath"
"testing"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/platform"
. "github.com/xtls/xray-core/common/platform/filesystem"
)
func TestResolveAssetRejectsInvalidPath(t *testing.T) {
func TestStatAssetRejectsInvalidPath(t *testing.T) {
for _, file := range []string{
"",
".",
@@ -29,49 +25,8 @@ func TestResolveAssetRejectsInvalidPath(t *testing.T) {
`nested\..\geoip.dat`,
filepath.Join(t.TempDir(), "geoip.dat"),
} {
if _, err := ResolveAsset(file); err == nil {
if _, err := StatAsset(file); err == nil {
t.Fatalf("expected error for %q", file)
}
}
}
func TestResolveLuaFile(t *testing.T) {
t.Chdir(t.TempDir())
workingDir, err := os.Getwd()
common.Must(err)
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))
}
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)
}
}
+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)
}
+65 -2
View File
@@ -1,6 +1,8 @@
package platform // import "github.com/xtls/xray-core/common/platform"
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
@@ -72,7 +74,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 ""
@@ -81,7 +83,7 @@ func GetExecutableDir() string {
}
func GetConfigurationPath() string {
configPath := NewEnvFlag(ConfigLocation).GetValue(GetExecutableDir)
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)
return filepath.Join(configPath, "config.json")
}
@@ -90,3 +92,64 @@ 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) (string, error) {
if path == "" {
return "", errors.New("Lua file path is empty")
}
paths := []string{path}
if !filepath.IsAbs(path) {
paths = nil
for _, dir := range []string{
GetConfDirPath(),
NewEnvFlag(ConfigLocation).GetValue(func() string { return "" }),
".",
getExecutableDir(),
} {
if dir != "" {
paths = append(paths, filepath.Join(dir, path))
}
}
}
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) {
var tried []string
for _, path := range paths {
path, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("failed to resolve file path: %w", err)
}
tried = append(tried, path)
info, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
return "", fmt.Errorf("failed to inspect file %q: %w", path, err)
}
if !info.Mode().IsRegular() {
return "", fmt.Errorf("file is not a regular file: %s", path)
}
return path, nil
}
return "", fmt.Errorf("file not found; tried %q: %w", tried, os.ErrNotExist)
}
+88
View File
@@ -1,9 +1,11 @@
package platform_test
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/xtls/xray-core/common"
@@ -64,3 +66,89 @@ 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)
}
-4
View File
@@ -6,7 +6,6 @@ import (
"github.com/robfig/cron/v3"
"github.com/xtls/xray-core/app/geodata"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform/filesystem"
"google.golang.org/protobuf/proto"
)
@@ -19,9 +18,6 @@ func (c *GeodataAssetConfig) Build() (*geodata.Asset, error) {
if err := validateHTTPS(c.URL); err != nil {
return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err)
}
if _, err := filesystem.ResolveAsset(c.File); err != nil {
return nil, errors.New("invalid geodata asset file: ", c.File).Base(err)
}
return &geodata.Asset{
Url: c.URL,
File: c.File,
-23
View File
@@ -1,7 +1,6 @@
package conf_test
import (
"path/filepath"
"testing"
"github.com/xtls/xray-core/app/geodata"
@@ -9,8 +8,6 @@ import (
)
func TestGeodataConfig(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
creator := func() Buildable {
return new(GeodataConfig)
}
@@ -38,27 +35,7 @@ func TestGeodataConfig(t *testing.T) {
})
}
func TestGeodataAssetConfig(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
if _, err := (&GeodataAssetConfig{
URL: "https://example.com/geoip.dat",
File: "geoip.dat",
}).Build(); err != nil {
t.Fatal(err)
}
if _, err := (&GeodataAssetConfig{
URL: "https://example.com/geoip.dat",
File: "missing.dat",
}).Build(); err == nil {
t.Fatal("expected error")
}
}
func TestGeodataAssetConfigInvalidURL(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
for _, rawURL := range []string{
"",
"http://example.com/geoip.dat",
+11 -4
View File
@@ -3,7 +3,6 @@ package lua
import (
"os"
"path/filepath"
"strings"
xlua "github.com/xtls/xray-core/common/lua"
"github.com/xtls/xray-core/main/commands/base"
@@ -33,13 +32,14 @@ func executeSign(cmd *base.Command, args []string) {
cmd.Usage()
}
if !strings.EqualFold(filepath.Ext(args[0]), ".lua") {
base.Fatalf("Lua file extension must be .lua")
}
info, err := os.Stat(args[0])
if err != nil {
base.Fatalf("failed to inspect Lua file: %s", err)
}
if !info.Mode().IsRegular() {
base.Fatalf("Lua input is not a regular file: %s", args[0])
}
code, err := os.ReadFile(args[0])
if err != nil {
base.Fatalf("failed to read Lua file: %s", err)
@@ -67,7 +67,14 @@ func executeSign(cmd *base.Command, args []string) {
func writeSignedFile(path string, code []byte, mode os.FileMode) error {
if info, err := os.Stat(path); err == nil {
if !info.Mode().IsRegular() {
return &os.PathError{Op: "write", Path: path, Err: os.ErrInvalid}
}
mode = info.Mode().Perm()
path, err = filepath.EvalSymlinks(path)
if err != nil {
return err
}
} else if !os.IsNotExist(err) {
return err
}
+10 -2
View File
@@ -58,10 +58,18 @@ func readConfigFile(file *os.File) ([]byte, error) {
return nil, err
}
if info.Mode().IsRegular() {
switch strings.ToLower(filepath.Ext(file.Name())) {
path, err := filepath.Abs(file.Name())
if err != nil {
return nil, err
}
path, err = filepath.EvalSymlinks(path)
if err != nil {
return nil, err
}
switch strings.ToLower(filepath.Ext(path)) {
case ".jsonc", ".json", ".yml", ".yaml", ".toml", ".pb", ".protobuf":
default:
return nil, errors.New("unsupported config file extension: ", file.Name())
return nil, errors.New("unsupported config file extension: ", path)
}
}
return io.ReadAll(file)