mirror of https://github.com/XTLS/Xray-core
				
				
				
			
		
			
				
	
	
		
			188 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			188 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
package core
 | 
						|
 | 
						|
import (
 | 
						|
	"io"
 | 
						|
	"slices"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/xtls/xray-core/common"
 | 
						|
	"github.com/xtls/xray-core/common/buf"
 | 
						|
	"github.com/xtls/xray-core/common/cmdarg"
 | 
						|
	"github.com/xtls/xray-core/common/errors"
 | 
						|
	"github.com/xtls/xray-core/main/confloader"
 | 
						|
	"google.golang.org/protobuf/proto"
 | 
						|
)
 | 
						|
 | 
						|
// ConfigFormat is a configurable format of Xray config file.
 | 
						|
type ConfigFormat struct {
 | 
						|
	Name      string
 | 
						|
	Extension []string
 | 
						|
	Loader    ConfigLoader
 | 
						|
}
 | 
						|
 | 
						|
type ConfigSource struct {
 | 
						|
	Name   string
 | 
						|
	Format string
 | 
						|
}
 | 
						|
 | 
						|
// ConfigLoader is a utility to load Xray config from external source.
 | 
						|
type ConfigLoader func(input interface{}) (*Config, error)
 | 
						|
 | 
						|
// ConfigBuilder is a builder to build core.Config from filenames and formats
 | 
						|
type ConfigBuilder func(files []*ConfigSource) (*Config, error)
 | 
						|
 | 
						|
// ConfigsMerger merges multiple json configs into a single one
 | 
						|
type ConfigsMerger func(files []*ConfigSource) (string, error)
 | 
						|
 | 
						|
var (
 | 
						|
	configLoaderByName    = make(map[string]*ConfigFormat)
 | 
						|
	configLoaderByExt     = make(map[string]*ConfigFormat)
 | 
						|
	ConfigBuilderForFiles ConfigBuilder
 | 
						|
	ConfigMergedFormFiles ConfigsMerger
 | 
						|
)
 | 
						|
 | 
						|
// RegisterConfigLoader add a new ConfigLoader.
 | 
						|
func RegisterConfigLoader(format *ConfigFormat) error {
 | 
						|
	name := strings.ToLower(format.Name)
 | 
						|
	if _, found := configLoaderByName[name]; found {
 | 
						|
		return errors.New(format.Name, " already registered.")
 | 
						|
	}
 | 
						|
	configLoaderByName[name] = format
 | 
						|
 | 
						|
	for _, ext := range format.Extension {
 | 
						|
		lext := strings.ToLower(ext)
 | 
						|
		if f, found := configLoaderByExt[lext]; found {
 | 
						|
			return errors.New(ext, " already registered to ", f.Name)
 | 
						|
		}
 | 
						|
		configLoaderByExt[lext] = format
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func GetMergedConfig(args cmdarg.Arg) (string, error) {
 | 
						|
	var files []*ConfigSource
 | 
						|
	supported := []string{"json", "yaml", "toml"}
 | 
						|
	for _, file := range args {
 | 
						|
		format := getFormat(file)
 | 
						|
		if slices.Contains(supported, format) {
 | 
						|
			files = append(files, &ConfigSource{
 | 
						|
				Name:   file,
 | 
						|
				Format: format,
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ConfigMergedFormFiles(files)
 | 
						|
}
 | 
						|
 | 
						|
func GetFormatByExtension(ext string) string {
 | 
						|
	switch strings.ToLower(ext) {
 | 
						|
	case "pb", "protobuf":
 | 
						|
		return "protobuf"
 | 
						|
	case "yaml", "yml":
 | 
						|
		return "yaml"
 | 
						|
	case "toml":
 | 
						|
		return "toml"
 | 
						|
	case "json", "jsonc":
 | 
						|
		return "json"
 | 
						|
	default:
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getExtension(filename string) string {
 | 
						|
	idx := strings.LastIndexByte(filename, '.')
 | 
						|
	if idx == -1 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return filename[idx+1:]
 | 
						|
}
 | 
						|
 | 
						|
func getFormat(filename string) string {
 | 
						|
	return GetFormatByExtension(getExtension(filename))
 | 
						|
}
 | 
						|
 | 
						|
func LoadConfig(formatName string, input interface{}) (*Config, error) {
 | 
						|
	switch v := input.(type) {
 | 
						|
	case cmdarg.Arg:
 | 
						|
		files := make([]*ConfigSource, len(v))
 | 
						|
		hasProtobuf := false
 | 
						|
		for i, file := range v {
 | 
						|
			var f string
 | 
						|
 | 
						|
			if formatName == "auto" {
 | 
						|
				if file != "stdin:" {
 | 
						|
					f = getFormat(file)
 | 
						|
				} else {
 | 
						|
					f = "json"
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				f = formatName
 | 
						|
			}
 | 
						|
 | 
						|
			if f == "" {
 | 
						|
				return nil, errors.New("Failed to get format of ", file).AtWarning()
 | 
						|
			}
 | 
						|
 | 
						|
			if f == "protobuf" {
 | 
						|
				hasProtobuf = true
 | 
						|
			}
 | 
						|
			files[i] = &ConfigSource{
 | 
						|
				Name:   file,
 | 
						|
				Format: f,
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// only one protobuf config file is allowed
 | 
						|
		if hasProtobuf {
 | 
						|
			if len(v) == 1 {
 | 
						|
				return configLoaderByName["protobuf"].Loader(v)
 | 
						|
			} else {
 | 
						|
				return nil, errors.New("Only one protobuf config file is allowed").AtWarning()
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// to avoid import cycle
 | 
						|
		return ConfigBuilderForFiles(files)
 | 
						|
	case io.Reader:
 | 
						|
		if f, found := configLoaderByName[formatName]; found {
 | 
						|
			return f.Loader(v)
 | 
						|
		} else {
 | 
						|
			return nil, errors.New("Unable to load config in", formatName).AtWarning()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, errors.New("Unable to load config").AtWarning()
 | 
						|
}
 | 
						|
 | 
						|
func loadProtobufConfig(data []byte) (*Config, error) {
 | 
						|
	config := new(Config)
 | 
						|
	if err := proto.Unmarshal(data, config); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return config, nil
 | 
						|
}
 | 
						|
 | 
						|
func init() {
 | 
						|
	common.Must(RegisterConfigLoader(&ConfigFormat{
 | 
						|
		Name:      "Protobuf",
 | 
						|
		Extension: []string{"pb"},
 | 
						|
		Loader: func(input interface{}) (*Config, error) {
 | 
						|
			switch v := input.(type) {
 | 
						|
			case cmdarg.Arg:
 | 
						|
				r, err := confloader.LoadConfig(v[0])
 | 
						|
				common.Must(err)
 | 
						|
				data, err := buf.ReadAllToBytes(r)
 | 
						|
				common.Must(err)
 | 
						|
				return loadProtobufConfig(data)
 | 
						|
			case io.Reader:
 | 
						|
				data, err := buf.ReadAllToBytes(v)
 | 
						|
				common.Must(err)
 | 
						|
				return loadProtobufConfig(data)
 | 
						|
			default:
 | 
						|
				return nil, errors.New("unknown type")
 | 
						|
			}
 | 
						|
		},
 | 
						|
	}))
 | 
						|
}
 |