github.com/sagernet/sing-box@v1.9.0-rc.20/transport/sip003/args.go (about)

     1  package sip003
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  )
     7  
     8  // mod from https://github.com/shadowsocks/v2ray-plugin/blob/master/args.go
     9  
    10  // Args maps a string key to a list of values. It is similar to url.Values.
    11  type Args map[string][]string
    12  
    13  // Get the first value associated with the given key. If there are any values
    14  // associated with the key, the value return has the value and ok is set to
    15  // true. If there are no values for the given key, value is "" and ok is false.
    16  // If you need access to multiple values, use the map directly.
    17  func (args Args) Get(key string) (value string, ok bool) {
    18  	if args == nil {
    19  		return "", false
    20  	}
    21  	vals, ok := args[key]
    22  	if !ok || len(vals) == 0 {
    23  		return "", false
    24  	}
    25  	return vals[0], true
    26  }
    27  
    28  // Add Append value to the list of values for key.
    29  func (args Args) Add(key, value string) {
    30  	args[key] = append(args[key], value)
    31  }
    32  
    33  // Return the index of the next unescaped byte in s that is in the term set, or
    34  // else the length of the string if no terminators appear. Additionally return
    35  // the unescaped string up to the returned index.
    36  func indexUnescaped(s string, term []byte) (int, string, error) {
    37  	var i int
    38  	unesc := make([]byte, 0)
    39  	for i = 0; i < len(s); i++ {
    40  		b := s[i]
    41  		// A terminator byte?
    42  		if bytes.IndexByte(term, b) != -1 {
    43  			break
    44  		}
    45  		if b == '\\' {
    46  			i++
    47  			if i >= len(s) {
    48  				return 0, "", fmt.Errorf("nothing following final escape in %q", s)
    49  			}
    50  			b = s[i]
    51  		}
    52  		unesc = append(unesc, b)
    53  	}
    54  	return i, string(unesc), nil
    55  }
    56  
    57  // ParsePluginOptions Parse a name–value mapping as from SS_PLUGIN_OPTIONS.
    58  //
    59  // "<value> is a k=v string value with options that are to be passed to the
    60  // transport. semicolons, equal signs and backslashes must be escaped
    61  // with a backslash."
    62  // Example: secret=nou;cache=/tmp/cache;secret=yes
    63  func ParsePluginOptions(s string) (opts Args, err error) {
    64  	opts = make(Args)
    65  	if len(s) == 0 {
    66  		return
    67  	}
    68  	i := 0
    69  	for {
    70  		var key, value string
    71  		var offset, begin int
    72  
    73  		if i >= len(s) {
    74  			break
    75  		}
    76  		begin = i
    77  		// Read the key.
    78  		offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
    79  		if err != nil {
    80  			return
    81  		}
    82  		if len(key) == 0 {
    83  			err = fmt.Errorf("empty key in %q", s[begin:i])
    84  			return
    85  		}
    86  		i += offset
    87  		// End of string or no equals sign?
    88  		if i >= len(s) || s[i] != '=' {
    89  			opts.Add(key, "1")
    90  			// Skip the semicolon.
    91  			i++
    92  			continue
    93  		}
    94  		// Skip the equals sign.
    95  		i++
    96  		// Read the value.
    97  		offset, value, err = indexUnescaped(s[i:], []byte{';'})
    98  		if err != nil {
    99  			return
   100  		}
   101  		i += offset
   102  		opts.Add(key, value)
   103  		// Skip the semicolon.
   104  		i++
   105  	}
   106  	return opts, nil
   107  }
   108  
   109  // Escape backslashes and all the bytes that are in set.
   110  func backslashEscape(s string, set []byte) string {
   111  	var buf bytes.Buffer
   112  	for _, b := range []byte(s) {
   113  		if b == '\\' || bytes.IndexByte(set, b) != -1 {
   114  			buf.WriteByte('\\')
   115  		}
   116  		buf.WriteByte(b)
   117  	}
   118  	return buf.String()
   119  }