config.go 4.2 KB

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