config.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package core
  2. import (
  3. "io"
  4. "strings"
  5. "github.com/golang/protobuf/proto"
  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/main/confloader"
  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. var (
  22. configLoaderByName = make(map[string]*ConfigFormat)
  23. configLoaderByExt = make(map[string]*ConfigFormat)
  24. ConfigBuilderForFiles ConfigBuilder
  25. )
  26. // RegisterConfigLoader add a new ConfigLoader.
  27. func RegisterConfigLoader(format *ConfigFormat) error {
  28. name := strings.ToLower(format.Name)
  29. if _, found := configLoaderByName[name]; found {
  30. return newError(format.Name, " already registered.")
  31. }
  32. configLoaderByName[name] = format
  33. for _, ext := range format.Extension {
  34. lext := strings.ToLower(ext)
  35. if f, found := configLoaderByExt[lext]; found {
  36. return newError(ext, " already registered to ", f.Name)
  37. }
  38. configLoaderByExt[lext] = format
  39. }
  40. return nil
  41. }
  42. func GetFormatByExtension(ext string) string {
  43. switch strings.ToLower(ext) {
  44. case "pb", "protobuf":
  45. return "protobuf"
  46. case "yaml", "yml":
  47. return "yaml"
  48. case "toml":
  49. return "toml"
  50. case "json":
  51. return "json"
  52. default:
  53. return ""
  54. }
  55. }
  56. func getExtension(filename string) string {
  57. idx := strings.LastIndexByte(filename, '.')
  58. if idx == -1 {
  59. return ""
  60. }
  61. return filename[idx+1:]
  62. }
  63. func getFormat(filename string) string {
  64. return GetFormatByExtension(getExtension(filename))
  65. }
  66. func LoadConfig(formatName string, input interface{}) (*Config, error) {
  67. switch v := input.(type) {
  68. case cmdarg.Arg:
  69. formats := make([]string, len(v))
  70. hasProtobuf := false
  71. for i, file := range v {
  72. var f string
  73. if formatName == "auto" {
  74. if file != "stdin:" {
  75. f = getFormat(file)
  76. } else {
  77. f = "json"
  78. }
  79. } else {
  80. f = formatName
  81. }
  82. if f == "" {
  83. return nil, newError("Failed to get format of ", file).AtWarning()
  84. }
  85. if f == "protobuf" {
  86. hasProtobuf = true
  87. }
  88. formats[i] = f
  89. }
  90. // only one protobuf config file is allowed
  91. if hasProtobuf {
  92. if len(v) == 1 {
  93. return configLoaderByName["protobuf"].Loader(v)
  94. } else {
  95. return nil, newError("Only one protobuf config file is allowed").AtWarning()
  96. }
  97. }
  98. // to avoid import cycle
  99. return ConfigBuilderForFiles(v, formats)
  100. case io.Reader:
  101. if f, found := configLoaderByName[formatName]; found {
  102. return f.Loader(v)
  103. } else {
  104. return nil, newError("Unable to load config in", formatName).AtWarning()
  105. }
  106. }
  107. return nil, newError("Unable to load config").AtWarning()
  108. }
  109. func loadProtobufConfig(data []byte) (*Config, error) {
  110. config := new(Config)
  111. if err := proto.Unmarshal(data, config); err != nil {
  112. return nil, err
  113. }
  114. return config, nil
  115. }
  116. func init() {
  117. common.Must(RegisterConfigLoader(&ConfigFormat{
  118. Name: "Protobuf",
  119. Extension: []string{"pb"},
  120. Loader: func(input interface{}) (*Config, error) {
  121. switch v := input.(type) {
  122. case cmdarg.Arg:
  123. r, err := confloader.LoadConfig(v[0])
  124. common.Must(err)
  125. data, err := buf.ReadAllToBytes(r)
  126. common.Must(err)
  127. return loadProtobufConfig(data)
  128. case io.Reader:
  129. data, err := buf.ReadAllToBytes(v)
  130. common.Must(err)
  131. return loadProtobufConfig(data)
  132. default:
  133. return nil, newError("unknow type")
  134. }
  135. },
  136. }))
  137. }