github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/core/config.go (about)

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