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  }