From 35de242f0a379ce32f9a9f266f258337bea4d9d2 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:37:16 +0800 Subject: [PATCH] cmd: replace lua with base64 --- common/base64/base64.go | 6 ++ common/base64/decode.go | 38 +++++++++++++ common/base64/decode_test.go | 28 ++++++++++ common/base64/encode.go | 29 ++++++++++ common/base64/encode_test.go | 62 +++++++++++++++++++++ common/lua/encode.go | 29 ---------- common/lua/encode_test.go | 88 ------------------------------ common/lua/program.go | 32 +---------- common/lua/program_test.go | 17 +++--- infra/conf/dns_test.go | 20 +++---- main/commands/all/base64/base64.go | 26 +++++++++ main/commands/all/base64/decode.go | 52 ++++++++++++++++++ main/commands/all/base64/encode.go | 47 ++++++++++++++++ main/commands/all/commands.go | 4 +- main/commands/all/lua/encode.go | 62 --------------------- main/commands/all/lua/lua.go | 13 ----- 16 files changed, 312 insertions(+), 241 deletions(-) create mode 100644 common/base64/base64.go create mode 100644 common/base64/decode.go create mode 100644 common/base64/decode_test.go create mode 100644 common/base64/encode.go create mode 100644 common/base64/encode_test.go delete mode 100644 common/lua/encode.go delete mode 100644 common/lua/encode_test.go create mode 100644 main/commands/all/base64/base64.go create mode 100644 main/commands/all/base64/decode.go create mode 100644 main/commands/all/base64/encode.go delete mode 100644 main/commands/all/lua/encode.go delete mode 100644 main/commands/all/lua/lua.go diff --git a/common/base64/base64.go b/common/base64/base64.go new file mode 100644 index 000000000..71b16a887 --- /dev/null +++ b/common/base64/base64.go @@ -0,0 +1,6 @@ +package base64 + +const ( + Prefix = "base64:" + GzipPrefix = "base64+gzip:" +) diff --git a/common/base64/decode.go b/common/base64/decode.go new file mode 100644 index 000000000..052b0037e --- /dev/null +++ b/common/base64/decode.go @@ -0,0 +1,38 @@ +package base64 + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io" + "strings" + + "github.com/xtls/xray-core/common/errors" +) + +// Decode decodes Base64 text with an optional [Prefix] or [GzipPrefix]. +func Decode(value string) ([]byte, error) { + value, zip := strings.CutPrefix(value, GzipPrefix) + if !zip { + value = strings.TrimPrefix(value, Prefix) + } + + data, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return nil, errors.New("failed to decode base64").Base(err) + } + if !zip { + return data, nil + } + + reader, err := gzip.NewReader(bytes.NewReader(data)) + if err != nil { + return nil, errors.New("failed to decompress gzip").Base(err) + } + defer reader.Close() + data, err = io.ReadAll(reader) + if err != nil { + return nil, errors.New("failed to decompress gzip").Base(err) + } + return data, nil +} diff --git a/common/base64/decode_test.go b/common/base64/decode_test.go new file mode 100644 index 000000000..e5209f023 --- /dev/null +++ b/common/base64/decode_test.go @@ -0,0 +1,28 @@ +package base64 + +import ( + "bytes" + "testing" +) + +func TestDecodeFormats(t *testing.T) { + compressed, err := Encode([]byte("x"), true) + if err != nil { + t.Fatal(err) + } + for _, tc := range []struct { + name, value, want string + }{ + {"unprefixed", "eA==", "x"}, + {"base64", Prefix + "eA==", "x"}, + {"gzip", compressed, "x"}, + {"empty", Prefix, ""}, + } { + t.Run(tc.name, func(t *testing.T) { + data, err := Decode(tc.value) + if err != nil || !bytes.Equal(data, []byte(tc.want)) { + t.Fatalf("Decode = %q, %v; want %q", data, err, tc.want) + } + }) + } +} diff --git a/common/base64/encode.go b/common/base64/encode.go new file mode 100644 index 000000000..20ca72d7c --- /dev/null +++ b/common/base64/encode.go @@ -0,0 +1,29 @@ +package base64 + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + + "github.com/xtls/xray-core/common/errors" +) + +// Encode returns prefixed Base64 text, optionally compressed with gzip at the highest level. +func Encode(data []byte, zip bool) (string, error) { + if !zip { + return Prefix + base64.StdEncoding.EncodeToString(data), nil + } + + var buf bytes.Buffer + writer, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) + if err != nil { + return "", errors.New("failed to compress gzip").Base(err) + } + if _, err := writer.Write(data); err != nil { + return "", errors.New("failed to compress gzip").Base(err) + } + if err := writer.Close(); err != nil { + return "", errors.New("failed to compress gzip").Base(err) + } + return GzipPrefix + base64.StdEncoding.EncodeToString(buf.Bytes()), nil +} diff --git a/common/base64/encode_test.go b/common/base64/encode_test.go new file mode 100644 index 000000000..0616d345b --- /dev/null +++ b/common/base64/encode_test.go @@ -0,0 +1,62 @@ +package base64 + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io" + "strings" + "testing" +) + +func TestEncodeFormats(t *testing.T) { + for _, tc := range []struct { + name string + data []byte + }{ + {"empty", nil}, + {"bytes", []byte("你好\r\n\x00\xff")}, + } { + t.Run(tc.name, func(t *testing.T) { + for _, zip := range []bool{false, true} { + prefix := Prefix + if zip { + prefix = GzipPrefix + } + t.Run(prefix, func(t *testing.T) { + value, err := Encode(tc.data, zip) + if err != nil { + t.Fatal(err) + } + payload, ok := strings.CutPrefix(value, prefix) + if !ok { + t.Fatalf("encoded value does not start with %q", prefix) + } + // Read the output independently of Decode so matching bugs cannot hide. + data, err := base64.StdEncoding.DecodeString(payload) + if err != nil { + t.Fatal(err) + } + if zip { + // gzip's XFL byte identifies the compression level we selected. + if len(data) < 10 || data[8] != 2 { + t.Fatal("gzip output does not use the highest compression level") + } + reader, err := gzip.NewReader(bytes.NewReader(data)) + if err != nil { + t.Fatal(err) + } + defer reader.Close() + data, err = io.ReadAll(reader) + if err != nil { + t.Fatal(err) + } + } + if !bytes.Equal(data, tc.data) { + t.Fatal("encoded output does not preserve the input bytes") + } + }) + } + }) + } +} diff --git a/common/lua/encode.go b/common/lua/encode.go deleted file mode 100644 index a45282274..000000000 --- a/common/lua/encode.go +++ /dev/null @@ -1,29 +0,0 @@ -package lua - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - - "github.com/xtls/xray-core/common/errors" -) - -// Encode returns an inline script, optionally compressed with gzip at the highest level. -func Encode(code []byte, zip bool) (string, error) { - if !zip { - return "base64:" + base64.StdEncoding.EncodeToString(code), nil - } - - var data bytes.Buffer - writer, err := gzip.NewWriterLevel(&data, gzip.BestCompression) - if err != nil { - return "", errors.New("failed to compress gzip").Base(err) - } - if _, err := writer.Write(code); err != nil { - return "", errors.New("failed to compress gzip").Base(err) - } - if err := writer.Close(); err != nil { - return "", errors.New("failed to compress gzip").Base(err) - } - return "base64+gzip:" + base64.StdEncoding.EncodeToString(data.Bytes()), nil -} diff --git a/common/lua/encode_test.go b/common/lua/encode_test.go deleted file mode 100644 index 52256ee01..000000000 --- a/common/lua/encode_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package lua - -import ( - "bytes" - "context" - "encoding/base64" - "strings" - "testing" - - glua "github.com/yuin/gopher-lua" -) - -func TestEncodeRoundTrip(t *testing.T) { - for _, tc := range []struct { - name string - code []byte - }{ - {"empty", nil}, - {"padding", []byte("x")}, - {"unicode", []byte(`value = "你好"`)}, - {"multiline", []byte("-- comment\r\nvalue = 42\n")}, - {"binary", []byte{0, 0xff, 0xfe, 1, 2}}, - {"large", bytes.Repeat([]byte("-- repeated code\n"), 32768)}, - } { - t.Run(tc.name, func(t *testing.T) { - for _, zip := range []bool{false, true} { - prefix := "base64:" - if zip { - prefix = "base64+gzip:" - } - t.Run(prefix, func(t *testing.T) { - script, err := Encode(tc.code, zip) - if err != nil { - t.Fatal(err) - } - encoded, ok := strings.CutPrefix(script, prefix) - if !ok { - t.Fatalf("encoded script does not start with %q", prefix) - } - code, err := decode(encoded, zip) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(code, tc.code) { - t.Fatal("decoded bytes differ from input") - } - }) - } - }) - } -} - -func TestEncodeBestCompression(t *testing.T) { - script, err := Encode([]byte("value = 42"), true) - if err != nil { - t.Fatal(err) - } - data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(script, "base64+gzip:")) - if err != nil { - t.Fatal(err) - } - // The gzip XFL header byte is 2 for the highest compression level. - if len(data) < 10 || data[8] != 2 { - t.Fatal("gzip header does not indicate highest compression") - } -} - -func TestEncodeCompile(t *testing.T) { - for _, zip := range []bool{false, true} { - script, err := Encode([]byte(`value = "你好"`), zip) - if err != nil { - t.Fatal(err) - } - program, err := Compile(script) - if err != nil { - t.Fatal(err) - } - state, err := program.NewState(context.Background(), nil, nil) - if err != nil { - t.Fatal(err) - } - if got := state.GetGlobal("value"); got != glua.LString("你好") { - state.Close() - t.Fatalf("value = %v, want 你好", got) - } - state.Close() - } -} diff --git a/common/lua/program.go b/common/lua/program.go index 24a745f91..f37e91d50 100644 --- a/common/lua/program.go +++ b/common/lua/program.go @@ -2,15 +2,12 @@ package lua import ( "bytes" - "compress/gzip" "context" - "encoding/base64" - "io" "os" "strings" "time" - "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/base64" "github.com/xtls/xray-core/common/platform" glua "github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua/parse" @@ -30,10 +27,8 @@ func Compile(script string) (*Program, error) { var code []byte var err error name := "[inline]" - if s, ok := strings.CutPrefix(script, "base64+gzip:"); ok { - code, err = decode(s, true) - } else if s, ok := strings.CutPrefix(script, "base64:"); ok { - code, err = decode(s, false) + if strings.HasPrefix(script, base64.GzipPrefix) || strings.HasPrefix(script, base64.Prefix) { + code, err = base64.Decode(script) } else if name, err = platform.ResolveLuaFile(script); err == nil { code, err = os.ReadFile(name) } @@ -52,27 +47,6 @@ func Compile(script string) (*Program, error) { return &Program{proto: proto}, nil } -func decode(s string, zip bool) ([]byte, error) { - code, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return nil, errors.New("failed to decode base64").Base(err) - } - if !zip { - return code, nil - } - - reader, err := gzip.NewReader(bytes.NewReader(code)) - if err != nil { - return nil, errors.New("failed to decompress gzip").Base(err) - } - defer reader.Close() - code, err = io.ReadAll(reader) - if err != nil { - return nil, errors.New("failed to decompress gzip").Base(err) - } - return code, nil -} - // NewState creates a state, runs register, executes the program under ctx, and // runs validate. It removes the initialization context before returning a state // owned by the caller. diff --git a/common/lua/program_test.go b/common/lua/program_test.go index 51675599b..0465f7a15 100644 --- a/common/lua/program_test.go +++ b/common/lua/program_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + xbase64 "github.com/xtls/xray-core/common/base64" glua "github.com/yuin/gopher-lua" ) @@ -101,9 +102,9 @@ func TestCompileInline(t *testing.T) { name, script string value glua.LValue }{ - {"base64", "base64:" + base64.StdEncoding.EncodeToString([]byte(code)), glua.LString("你好")}, - {"base64+gzip", "base64+gzip:" + base64.StdEncoding.EncodeToString(compressSource(t, code)), glua.LString("你好")}, - {"empty base64", "base64:", glua.LNil}, + {"base64", xbase64.Prefix + base64.StdEncoding.EncodeToString([]byte(code)), glua.LString("你好")}, + {"base64+gzip", xbase64.GzipPrefix + base64.StdEncoding.EncodeToString(compressSource(t, code)), glua.LString("你好")}, + {"empty base64", xbase64.Prefix, glua.LNil}, } { t.Run(tc.name, func(t *testing.T) { program, err := Compile(tc.script) @@ -133,10 +134,10 @@ func TestCompileRejectsInvalidInline(t *testing.T) { wantErr error wantMessage string }{ - {"base64", "base64:%", base64.CorruptInputError(0), "failed to decode base64"}, - {"gzip base64", "base64+gzip:%", base64.CorruptInputError(0), "failed to decode base64"}, - {"gzip header", "base64+gzip:" + base64.StdEncoding.EncodeToString([]byte("value = 'uncompressed'")), gzip.ErrHeader, "failed to decompress gzip"}, - {"gzip checksum", "base64+gzip:" + base64.StdEncoding.EncodeToString(corrupt), gzip.ErrChecksum, "failed to decompress gzip"}, + {"base64", xbase64.Prefix + "%", base64.CorruptInputError(0), "failed to decode base64"}, + {"gzip base64", xbase64.GzipPrefix + "%", base64.CorruptInputError(0), "failed to decode base64"}, + {"gzip header", xbase64.GzipPrefix + base64.StdEncoding.EncodeToString([]byte("value = 'uncompressed'")), gzip.ErrHeader, "failed to decompress gzip"}, + {"gzip checksum", xbase64.GzipPrefix + base64.StdEncoding.EncodeToString(corrupt), gzip.ErrChecksum, "failed to decompress gzip"}, } { t.Run(tc.name, func(t *testing.T) { program, err := Compile(tc.script) @@ -155,7 +156,7 @@ func TestCompileInlineDiagnostics(t *testing.T) { {"function broken(", true}, {`error("initialization failed")`, false}, } { - program, err := Compile("base64:" + base64.StdEncoding.EncodeToString([]byte(tc.code))) + program, err := Compile(xbase64.Prefix + base64.StdEncoding.EncodeToString([]byte(tc.code))) if tc.compileError { if program != nil || err == nil { t.Fatalf("Compile = %v, %v; want nil program and syntax error", program, err) diff --git a/infra/conf/dns_test.go b/infra/conf/dns_test.go index 62d303e98..0cca52522 100644 --- a/infra/conf/dns_test.go +++ b/infra/conf/dns_test.go @@ -144,16 +144,16 @@ func TestDNSScriptConfig(t *testing.T) { input string wantError string }{ - {name: "script only", input: `{"script":"base64:%"}`}, - {name: "compatible fields", input: `{"script":"base64:%","servers":["1.1.1.1",{"id":"primary","address":"8.8.8.8","expectedIPs":["8.8.8.8"],"queryStrategy":"UseIPv4","disableCache":true}],"hosts":{"example.com":"127.0.0.1"},"disableCache":true}`}, - {name: "disabled options", input: `{"script":"base64:%","disableFallback":false,"disableFallbackIfMatch":false,"enableParallelQuery":false,"servers":[{"address":"1.1.1.1","domains":[],"skipFallback":false,"finalQuery":false}]}`}, - {name: "domains", input: `{"script":"base64:%","servers":[{"address":"1.1.1.1","domains":["domain:example.com"]}]}`, wantError: "domains"}, - {name: "skip fallback", input: `{"script":"base64:%","servers":[{"address":"1.1.1.1","skipFallback":true}]}`, wantError: "skipFallback"}, - {name: "final query", input: `{"script":"base64:%","servers":[{"address":"1.1.1.1","finalQuery":true}]}`, wantError: "finalQuery"}, - {name: "later server", input: `{"script":"base64:%","servers":["1.1.1.1",{"address":"8.8.8.8","finalQuery":true}]}`, wantError: "server 1"}, - {name: "disable fallback", input: `{"script":"base64:%","disableFallback":true}`, wantError: "disableFallback"}, - {name: "disable fallback if match", input: `{"script":"base64:%","disableFallbackIfMatch":true}`, wantError: "disableFallbackIfMatch"}, - {name: "parallel query", input: `{"script":"base64:%","enableParallelQuery":true}`, wantError: "enableParallelQuery"}, + {name: "script only", input: `{"script":"script.lua"}`}, + {name: "compatible fields", input: `{"script":"script.lua","servers":["1.1.1.1",{"id":"primary","address":"8.8.8.8","expectedIPs":["8.8.8.8"],"queryStrategy":"UseIPv4","disableCache":true}],"hosts":{"example.com":"127.0.0.1"},"disableCache":true}`}, + {name: "disabled options", input: `{"script":"script.lua","disableFallback":false,"disableFallbackIfMatch":false,"enableParallelQuery":false,"servers":[{"address":"1.1.1.1","domains":[],"skipFallback":false,"finalQuery":false}]}`}, + {name: "domains", input: `{"script":"script.lua","servers":[{"address":"1.1.1.1","domains":["domain:example.com"]}]}`, wantError: "domains"}, + {name: "skip fallback", input: `{"script":"script.lua","servers":[{"address":"1.1.1.1","skipFallback":true}]}`, wantError: "skipFallback"}, + {name: "final query", input: `{"script":"script.lua","servers":[{"address":"1.1.1.1","finalQuery":true}]}`, wantError: "finalQuery"}, + {name: "later server", input: `{"script":"script.lua","servers":["1.1.1.1",{"address":"8.8.8.8","finalQuery":true}]}`, wantError: "server 1"}, + {name: "disable fallback", input: `{"script":"script.lua","disableFallback":true}`, wantError: "disableFallback"}, + {name: "disable fallback if match", input: `{"script":"script.lua","disableFallbackIfMatch":true}`, wantError: "disableFallbackIfMatch"}, + {name: "parallel query", input: `{"script":"script.lua","enableParallelQuery":true}`, wantError: "enableParallelQuery"}, {name: "empty script", input: `{"script":"","disableFallback":true,"disableFallbackIfMatch":true,"enableParallelQuery":true,"servers":[{"address":"1.1.1.1","domains":["example.com"],"skipFallback":true,"finalQuery":true}]}`}, } { t.Run(test.name, func(t *testing.T) { diff --git a/main/commands/all/base64/base64.go b/main/commands/all/base64/base64.go new file mode 100644 index 000000000..7bdc41977 --- /dev/null +++ b/main/commands/all/base64/base64.go @@ -0,0 +1,26 @@ +package base64 + +import ( + "io" + "os" + + "github.com/xtls/xray-core/main/commands/base" +) + +// CmdBase64 holds all Base64 subcommands. +var CmdBase64 = &base.Command{ + UsageLine: "{{.Exec}} base64", + Short: "Encode and decode Base64", + Long: "{{.Exec}} {{.LongName}} provides tools to encode and decode arbitrary bytes.", + Commands: []*base.Command{ + cmdEncode, + cmdDecode, + }, +} + +func readInput(name string) ([]byte, error) { + if name == "stdin:" { + return io.ReadAll(os.Stdin) + } + return os.ReadFile(name) +} diff --git a/main/commands/all/base64/decode.go b/main/commands/all/base64/decode.go new file mode 100644 index 000000000..7d3298818 --- /dev/null +++ b/main/commands/all/base64/decode.go @@ -0,0 +1,52 @@ +package base64 + +import ( + "fmt" + "os" + "strings" + + xbase64 "github.com/xtls/xray-core/common/base64" + "github.com/xtls/xray-core/main/commands/base" +) + +var cmdDecode = &base.Command{ + UsageLine: "{{.Exec}} base64 decode [-gzip] [file | stdin:]", + Short: "Decode Base64", + Long: fmt.Sprintf(` +Decode Base64 from a file or stdin:. +Accepts %s, %s, or no prefix. +Use -gzip to decompress gzip; %s is decompressed automatically. + +Examples: + + {{.Exec}} base64 decode encoded.txt + {{.Exec}} base64 decode -gzip stdin: +`, xbase64.Prefix, xbase64.GzipPrefix, xbase64.GzipPrefix), +} + +var decodeGzipEnabled = cmdDecode.Flag.Bool("gzip", false, "Decompress the decoded bytes with gzip.") + +func init() { + cmdDecode.Run = executeDecode +} + +func executeDecode(cmd *base.Command, args []string) { + if len(args) != 1 { + cmd.Usage() + } + input, err := readInput(args[0]) + if err != nil { + base.Fatalf("failed to read input: %s", err) + } + value := strings.TrimSpace(string(input)) + if *decodeGzipEnabled && !strings.HasPrefix(value, xbase64.GzipPrefix) { + value = xbase64.GzipPrefix + strings.TrimPrefix(value, xbase64.Prefix) + } + data, err := xbase64.Decode(value) + if err != nil { + base.Fatalf("failed to decode input: %s", err) + } + if _, err := os.Stdout.Write(data); err != nil { + base.Fatalf("failed to write decoded output: %s", err) + } +} diff --git a/main/commands/all/base64/encode.go b/main/commands/all/base64/encode.go new file mode 100644 index 000000000..7e3c16dde --- /dev/null +++ b/main/commands/all/base64/encode.go @@ -0,0 +1,47 @@ +package base64 + +import ( + "fmt" + "os" + + xbase64 "github.com/xtls/xray-core/common/base64" + "github.com/xtls/xray-core/main/commands/base" +) + +var cmdEncode = &base.Command{ + UsageLine: "{{.Exec}} base64 encode [-gzip] [file | stdin:]", + Short: "Encode as Base64", + Long: fmt.Sprintf(` +Encode a file or stdin: as Base64 with the %s prefix. +Use -gzip to compress with gzip and output %s. + +Examples: + + {{.Exec}} base64 encode data.bin + {{.Exec}} base64 encode -gzip data.bin + {{.Exec}} base64 encode stdin: +`, xbase64.Prefix, xbase64.GzipPrefix), +} + +var gzipEnabled = cmdEncode.Flag.Bool("gzip", false, "Compress the input with gzip.") + +func init() { + cmdEncode.Run = executeEncode +} + +func executeEncode(cmd *base.Command, args []string) { + if len(args) != 1 { + cmd.Usage() + } + data, err := readInput(args[0]) + if err != nil { + base.Fatalf("failed to read input: %s", err) + } + value, err := xbase64.Encode(data, *gzipEnabled) + if err != nil { + base.Fatalf("failed to encode input: %s", err) + } + if _, err := fmt.Fprintln(os.Stdout, value); err != nil { + base.Fatalf("failed to write encoded output: %s", err) + } +} diff --git a/main/commands/all/commands.go b/main/commands/all/commands.go index 3ed3830ee..be2771da9 100644 --- a/main/commands/all/commands.go +++ b/main/commands/all/commands.go @@ -2,8 +2,8 @@ package all import ( "github.com/xtls/xray-core/main/commands/all/api" + "github.com/xtls/xray-core/main/commands/all/base64" "github.com/xtls/xray-core/main/commands/all/convert" - "github.com/xtls/xray-core/main/commands/all/lua" "github.com/xtls/xray-core/main/commands/all/tls" "github.com/xtls/xray-core/main/commands/base" ) @@ -12,8 +12,8 @@ func init() { base.RootCommand.Commands = append( base.RootCommand.Commands, api.CmdAPI, + base64.CmdBase64, convert.CmdConvert, - lua.CmdLua, tls.CmdTLS, cmdUUID, cmdX25519, diff --git a/main/commands/all/lua/encode.go b/main/commands/all/lua/encode.go deleted file mode 100644 index 7ecd8ece2..000000000 --- a/main/commands/all/lua/encode.go +++ /dev/null @@ -1,62 +0,0 @@ -package lua - -import ( - "fmt" - "io" - "os" - - xlua "github.com/xtls/xray-core/common/lua" - "github.com/xtls/xray-core/main/commands/base" -) - -var cmdEncode = &base.Command{ - UsageLine: "{{.Exec}} lua encode [-gzip=false] [file | stdin:]", - Short: "Encode a Lua script for inline configuration", - Long: ` -Encode one file or stdin: as an inline Lua script for the script configuration field. -Files are read from the given path without searching configuration directories. -The original bytes are preserved; the script is not executed or validated. -The encoded string is written to stdout on one line, without quotes. - -Arguments: - - -gzip=false - Disable compression and output base64: instead of base64+gzip:. - By default, gzip uses the highest compression level. - -Examples: - - {{.Exec}} lua encode script.lua - {{.Exec}} lua encode -gzip=false script.lua - {{.Exec}} lua encode stdin: -`, -} - -var gzipEnabled = cmdEncode.Flag.Bool("gzip", true, "Compress the script with gzip.") - -func init() { - cmdEncode.Run = executeEncode -} - -func executeEncode(cmd *base.Command, args []string) { - if len(args) != 1 { - cmd.Usage() - } - var code []byte - var err error - if args[0] == "stdin:" { - code, err = io.ReadAll(os.Stdin) - } else { - code, err = os.ReadFile(args[0]) - } - if err != nil { - base.Fatalf("failed to read script: %s", err) - } - script, err := xlua.Encode(code, *gzipEnabled) - if err != nil { - base.Fatalf("failed to encode script: %s", err) - } - if _, err := fmt.Fprintln(os.Stdout, script); err != nil { - base.Fatalf("failed to write encoded script: %s", err) - } -} diff --git a/main/commands/all/lua/lua.go b/main/commands/all/lua/lua.go deleted file mode 100644 index a97d28420..000000000 --- a/main/commands/all/lua/lua.go +++ /dev/null @@ -1,13 +0,0 @@ -package lua - -import "github.com/xtls/xray-core/main/commands/base" - -// CmdLua holds all Lua subcommands. -var CmdLua = &base.Command{ - UsageLine: "{{.Exec}} lua", - Short: "Lua tools", - Long: "{{.Exec}} {{.LongName}} provides tools for Lua scripts.", - Commands: []*base.Command{ - cmdEncode, - }, -}