github.com/imannamdari/v2ray-core/v5@v5.0.5/config.go (about) 1 package core 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "os" 8 "path/filepath" 9 "reflect" 10 "strings" 11 12 "google.golang.org/protobuf/proto" 13 14 "github.com/imannamdari/v2ray-core/v5/common" 15 "github.com/imannamdari/v2ray-core/v5/common/buf" 16 "github.com/imannamdari/v2ray-core/v5/common/cmdarg" 17 ) 18 19 const ( 20 // FormatAuto represents all available formats by auto selecting 21 FormatAuto = "auto" 22 // FormatJSON represents json format 23 FormatJSON = "json" 24 // FormatTOML represents toml format 25 FormatTOML = "toml" 26 // FormatYAML represents yaml format 27 FormatYAML = "yaml" 28 // FormatProtobuf represents protobuf format 29 FormatProtobuf = "protobuf" 30 // FormatProtobufShort is the short of FormatProtobuf 31 FormatProtobufShort = "pb" 32 ) 33 34 // ConfigFormat is a configurable format of V2Ray config file. 35 type ConfigFormat struct { 36 Name []string 37 Extension []string 38 Loader ConfigLoader 39 } 40 41 // ConfigLoader is a utility to load V2Ray config from external source. 42 type ConfigLoader func(input interface{}) (*Config, error) 43 44 var ( 45 configLoaders = make([]*ConfigFormat, 0) 46 configLoaderByName = make(map[string]*ConfigFormat) 47 configLoaderByExt = make(map[string]*ConfigFormat) 48 ) 49 50 // RegisterConfigLoader add a new ConfigLoader. 51 func RegisterConfigLoader(format *ConfigFormat) error { 52 for _, name := range format.Name { 53 if _, found := configLoaderByName[name]; found { 54 return newError(name, " already registered.") 55 } 56 configLoaderByName[name] = format 57 } 58 59 for _, ext := range format.Extension { 60 lext := strings.ToLower(ext) 61 if f, found := configLoaderByExt[lext]; found { 62 return newError(ext, " already registered to ", f.Name) 63 } 64 configLoaderByExt[lext] = format 65 } 66 configLoaders = append(configLoaders, format) 67 return nil 68 } 69 70 func getExtension(filename string) string { 71 ext := filepath.Ext(filename) 72 return strings.ToLower(ext) 73 } 74 75 // GetLoaderExtensions get config loader extensions. 76 func GetLoaderExtensions(formatName string) ([]string, error) { 77 if formatName == FormatAuto { 78 return GetAllExtensions(), nil 79 } 80 if f, found := configLoaderByName[formatName]; found { 81 return f.Extension, nil 82 } 83 return nil, newError("config loader not found: ", formatName).AtWarning() 84 } 85 86 // GetAllExtensions get all extensions supported 87 func GetAllExtensions() []string { 88 extensions := make([]string, 0) 89 for _, f := range configLoaderByName { 90 extensions = append(extensions, f.Extension...) 91 } 92 return extensions 93 } 94 95 // LoadConfig loads multiple config with given format from given source. 96 // input accepts: 97 // * string of a single filename/url(s) to open to read 98 // * []string slice of multiple filename/url(s) to open to read 99 // * io.Reader that reads a config content (the original way) 100 func LoadConfig(formatName string, input interface{}) (*Config, error) { 101 cnt := getInputCount(input) 102 if cnt == 0 { 103 log.Println("Using config from STDIN") 104 input = os.Stdin 105 cnt = 1 106 } 107 if formatName == FormatAuto && cnt == 1 { 108 // This ensures only to call auto loader for multiple files, 109 // so that it can only care about merging scenarios 110 return loadSingleConfigAutoFormat(input) 111 } 112 // if input is a slice with single element, extract it 113 // so that unmergeable loaders don't need to deal with 114 // slices 115 s := reflect.Indirect(reflect.ValueOf(input)) 116 k := s.Kind() 117 if (k == reflect.Slice || k == reflect.Array) && s.Len() == 1 { 118 value := reflect.Indirect(s.Index(0)) 119 if value.Kind() == reflect.String { 120 // string type alias 121 input = fmt.Sprint(value.Interface()) 122 } else { 123 input = value.Interface() 124 } 125 } 126 f, found := configLoaderByName[formatName] 127 if !found { 128 return nil, newError("config loader not found: ", formatName).AtWarning() 129 } 130 return f.Loader(input) 131 } 132 133 // loadSingleConfigAutoFormat loads a single config with from given source. 134 // input accepts: 135 // * string of a single filename/url(s) to open to read 136 // * io.Reader that reads a config content (the original way) 137 func loadSingleConfigAutoFormat(input interface{}) (*Config, error) { 138 switch v := input.(type) { 139 case cmdarg.Arg: 140 return loadSingleConfigAutoFormatFromFile(v.String()) 141 case string: 142 return loadSingleConfigByTryingAllLoaders(v) 143 case io.Reader: 144 data, err := buf.ReadAllToBytes(v) 145 if err != nil { 146 return nil, err 147 } 148 return loadSingleConfigByTryingAllLoaders(data) 149 default: 150 return loadSingleConfigByTryingAllLoaders(v) 151 } 152 } 153 154 func loadSingleConfigAutoFormatFromFile(file string) (*Config, error) { 155 extension := getExtension(file) 156 if extension != "" { 157 lowerName := strings.ToLower(extension) 158 if f, found := configLoaderByExt[lowerName]; found { 159 return f.Loader(file) 160 } 161 return nil, newError("config loader not found for: ", extension).AtWarning() 162 } 163 164 return loadSingleConfigByTryingAllLoaders(file) 165 } 166 167 func loadSingleConfigByTryingAllLoaders(input interface{}) (*Config, error) { 168 var errorReasons strings.Builder 169 170 for _, f := range configLoaders { 171 if f.Name[0] == FormatAuto { 172 continue 173 } 174 c, err := f.Loader(input) 175 if err == nil { 176 return c, nil 177 } 178 errorReasons.WriteString(fmt.Sprintf("unable to parse as %v:%v;", f.Name[0], err.Error())) 179 } 180 181 return nil, newError("tried all loaders but failed when attempting to parse: ", input, ";", errorReasons.String()).AtWarning() 182 } 183 184 func getInputCount(input interface{}) int { 185 s := reflect.Indirect(reflect.ValueOf(input)) 186 k := s.Kind() 187 if k == reflect.Slice || k == reflect.Array { 188 return s.Len() 189 } 190 return 1 191 } 192 193 func loadProtobufConfig(data []byte) (*Config, error) { 194 config := new(Config) 195 if err := proto.Unmarshal(data, config); err != nil { 196 return nil, err 197 } 198 return config, nil 199 } 200 201 func init() { 202 common.Must(RegisterConfigLoader(&ConfigFormat{ 203 Name: []string{FormatProtobuf, FormatProtobufShort}, 204 Extension: []string{".pb"}, 205 Loader: func(input interface{}) (*Config, error) { 206 switch v := input.(type) { 207 case string: 208 r, err := cmdarg.LoadArg(v) 209 if err != nil { 210 return nil, err 211 } 212 data, err := buf.ReadAllToBytes(r) 213 if err != nil { 214 return nil, err 215 } 216 return loadProtobufConfig(data) 217 case io.Reader: 218 data, err := buf.ReadAllToBytes(v) 219 if err != nil { 220 return nil, err 221 } 222 return loadProtobufConfig(data) 223 default: 224 return nil, newError("unknown type") 225 } 226 }, 227 })) 228 }