github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/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/v2fly/v2ray-core/v5/common"
    15  	"github.com/v2fly/v2ray-core/v5/common/buf"
    16  	"github.com/v2fly/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  }