config.go 4.3 KB

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