config.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package core
  2. import (
  3. "io"
  4. "slices"
  5. "strings"
  6. "github.com/xtls/xray-core/common"
  7. "github.com/xtls/xray-core/common/buf"
  8. "github.com/xtls/xray-core/common/cmdarg"
  9. "github.com/xtls/xray-core/common/errors"
  10. "github.com/xtls/xray-core/main/confloader"
  11. "google.golang.org/protobuf/proto"
  12. )
  13. // ConfigFormat is a configurable format of Xray config file.
  14. type ConfigFormat struct {
  15. Name string
  16. Extension []string
  17. Loader ConfigLoader
  18. }
  19. type ConfigSource struct {
  20. Name string
  21. Format string
  22. }
  23. // ConfigLoader is a utility to load Xray config from external source.
  24. type ConfigLoader func(input interface{}) (*Config, error)
  25. // ConfigBuilder is a builder to build core.Config from filenames and formats
  26. type ConfigBuilder func(files []*ConfigSource) (*Config, error)
  27. // ConfigsMerger merges multiple json configs into a single one
  28. type ConfigsMerger func(files []*ConfigSource) (string, error)
  29. var (
  30. configLoaderByName = make(map[string]*ConfigFormat)
  31. configLoaderByExt = make(map[string]*ConfigFormat)
  32. ConfigBuilderForFiles ConfigBuilder
  33. ConfigMergedFormFiles ConfigsMerger
  34. )
  35. // RegisterConfigLoader add a new ConfigLoader.
  36. func RegisterConfigLoader(format *ConfigFormat) error {
  37. name := strings.ToLower(format.Name)
  38. if _, found := configLoaderByName[name]; found {
  39. return errors.New(format.Name, " already registered.")
  40. }
  41. configLoaderByName[name] = format
  42. for _, ext := range format.Extension {
  43. lext := strings.ToLower(ext)
  44. if f, found := configLoaderByExt[lext]; found {
  45. return errors.New(ext, " already registered to ", f.Name)
  46. }
  47. configLoaderByExt[lext] = format
  48. }
  49. return nil
  50. }
  51. func GetMergedConfig(args cmdarg.Arg) (string, error) {
  52. var files []*ConfigSource
  53. supported := []string{"json", "yaml", "toml"}
  54. for _, file := range args {
  55. format := "json"
  56. if file != "stdin:" {
  57. format = GetFormat(file)
  58. }
  59. if slices.Contains(supported, format) {
  60. files = append(files, &ConfigSource{
  61. Name: file,
  62. Format: format,
  63. })
  64. }
  65. }
  66. return ConfigMergedFormFiles(files)
  67. }
  68. func GetFormatByExtension(ext string) string {
  69. switch strings.ToLower(ext) {
  70. case "pb", "protobuf":
  71. return "protobuf"
  72. case "yaml", "yml":
  73. return "yaml"
  74. case "toml":
  75. return "toml"
  76. case "json", "jsonc":
  77. return "json"
  78. default:
  79. return ""
  80. }
  81. }
  82. func getExtension(filename string) string {
  83. idx := strings.LastIndexByte(filename, '.')
  84. if idx == -1 {
  85. return ""
  86. }
  87. return filename[idx+1:]
  88. }
  89. func GetFormat(filename string) string {
  90. return GetFormatByExtension(getExtension(filename))
  91. }
  92. func LoadConfig(formatName string, input interface{}) (*Config, error) {
  93. switch v := input.(type) {
  94. case cmdarg.Arg:
  95. files := make([]*ConfigSource, len(v))
  96. hasProtobuf := false
  97. for i, file := range v {
  98. var f string
  99. if formatName == "auto" {
  100. if file != "stdin:" {
  101. f = GetFormat(file)
  102. } else {
  103. f = "json"
  104. }
  105. } else {
  106. f = formatName
  107. }
  108. if f == "" {
  109. return nil, errors.New("Failed to get format of ", file).AtWarning()
  110. }
  111. if f == "protobuf" {
  112. hasProtobuf = true
  113. }
  114. files[i] = &ConfigSource{
  115. Name: file,
  116. Format: f,
  117. }
  118. }
  119. // only one protobuf config file is allowed
  120. if hasProtobuf {
  121. if len(v) == 1 {
  122. return configLoaderByName["protobuf"].Loader(v)
  123. } else {
  124. return nil, errors.New("Only one protobuf config file is allowed").AtWarning()
  125. }
  126. }
  127. // to avoid import cycle
  128. return ConfigBuilderForFiles(files)
  129. case io.Reader:
  130. if f, found := configLoaderByName[formatName]; found {
  131. return f.Loader(v)
  132. } else {
  133. return nil, errors.New("Unable to load config in", formatName).AtWarning()
  134. }
  135. }
  136. return nil, errors.New("Unable to load config").AtWarning()
  137. }
  138. func loadProtobufConfig(data []byte) (*Config, error) {
  139. config := new(Config)
  140. if err := proto.Unmarshal(data, config); err != nil {
  141. return nil, err
  142. }
  143. return config, nil
  144. }
  145. func init() {
  146. common.Must(RegisterConfigLoader(&ConfigFormat{
  147. Name: "Protobuf",
  148. Extension: []string{"pb"},
  149. Loader: func(input interface{}) (*Config, error) {
  150. switch v := input.(type) {
  151. case cmdarg.Arg:
  152. r, err := confloader.LoadConfig(v[0])
  153. common.Must(err)
  154. data, err := buf.ReadAllToBytes(r)
  155. common.Must(err)
  156. return loadProtobufConfig(data)
  157. case io.Reader:
  158. data, err := buf.ReadAllToBytes(v)
  159. common.Must(err)
  160. return loadProtobufConfig(data)
  161. default:
  162. return nil, errors.New("unknown type")
  163. }
  164. },
  165. }))
  166. }