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