github.com/ooni/psiphon/tunnel-core@v0.0.0-20230105123940-fe12a24c96ee/oovendor/goptlib/args.go (about) 1 package pt 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 "strings" 8 ) 9 10 // Key–value mappings for the representation of client and server options. 11 12 // Args maps a string key to a list of values. It is similar to url.Values. 13 type Args map[string][]string 14 15 // Get the first value associated with the given key. If there are any values 16 // associated with the key, the value return has the value and ok is set to 17 // true. If there are no values for the given key, value is "" and ok is false. 18 // If you need access to multiple values, use the map directly. 19 func (args Args) Get(key string) (value string, ok bool) { 20 if args == nil { 21 return "", false 22 } 23 vals, ok := args[key] 24 if !ok || len(vals) == 0 { 25 return "", false 26 } 27 return vals[0], true 28 } 29 30 // Append value to the list of values for key. 31 func (args Args) Add(key, value string) { 32 args[key] = append(args[key], value) 33 } 34 35 // Return the index of the next unescaped byte in s that is in the term set, or 36 // else the length of the string if no terminators appear. Additionally return 37 // the unescaped string up to the returned index. 38 func indexUnescaped(s string, term []byte) (int, string, error) { 39 var i int 40 unesc := make([]byte, 0) 41 for i = 0; i < len(s); i++ { 42 b := s[i] 43 // A terminator byte? 44 if bytes.IndexByte(term, b) != -1 { 45 break 46 } 47 if b == '\\' { 48 i++ 49 if i >= len(s) { 50 return 0, "", fmt.Errorf("nothing following final escape in %q", s) 51 } 52 b = s[i] 53 } 54 unesc = append(unesc, b) 55 } 56 return i, string(unesc), nil 57 } 58 59 // Parse a name–value mapping as from an encoded SOCKS username/password. 60 // 61 // "If any [k=v] items are provided, they are configuration parameters for the 62 // proxy: Tor should separate them with semicolons ... If a key or value value 63 // must contain [an equals sign or] a semicolon or a backslash, it is escaped 64 // with a backslash." 65 func parseClientParameters(s string) (args Args, err error) { 66 args = make(Args) 67 if len(s) == 0 { 68 return 69 } 70 i := 0 71 for { 72 var key, value string 73 var offset, begin int 74 75 begin = i 76 // Read the key. 77 offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) 78 if err != nil { 79 return 80 } 81 i += offset 82 // End of string or no equals sign? 83 if i >= len(s) || s[i] != '=' { 84 err = fmt.Errorf("no equals sign in %q", s[begin:i]) 85 return 86 } 87 // Skip the equals sign. 88 i++ 89 // Read the value. 90 offset, value, err = indexUnescaped(s[i:], []byte{';'}) 91 if err != nil { 92 return 93 } 94 i += offset 95 if len(key) == 0 { 96 err = fmt.Errorf("empty key in %q", s[begin:i]) 97 return 98 } 99 args.Add(key, value) 100 if i >= len(s) { 101 break 102 } 103 // Skip the semicolon. 104 i++ 105 } 106 return args, nil 107 } 108 109 // Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. 110 // 111 // "<value> is a k=v string value with options that are to be passed to the 112 // transport. Colons, semicolons, equal signs and backslashes must be escaped 113 // with a backslash." 114 // Example: trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes 115 func parseServerTransportOptions(s string) (opts map[string]Args, err error) { 116 opts = make(map[string]Args) 117 if len(s) == 0 { 118 return 119 } 120 i := 0 121 for { 122 var methodName, key, value string 123 var offset, begin int 124 125 begin = i 126 // Read the method name. 127 offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) 128 if err != nil { 129 return 130 } 131 i += offset 132 // End of string or no colon? 133 if i >= len(s) || s[i] != ':' { 134 err = fmt.Errorf("no colon in %q", s[begin:i]) 135 return 136 } 137 // Skip the colon. 138 i++ 139 // Read the key. 140 offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) 141 if err != nil { 142 return 143 } 144 i += offset 145 // End of string or no equals sign? 146 if i >= len(s) || s[i] != '=' { 147 err = fmt.Errorf("no equals sign in %q", s[begin:i]) 148 return 149 } 150 // Skip the equals sign. 151 i++ 152 // Read the value. 153 offset, value, err = indexUnescaped(s[i:], []byte{';'}) 154 if err != nil { 155 return 156 } 157 i += offset 158 if len(methodName) == 0 { 159 err = fmt.Errorf("empty method name in %q", s[begin:i]) 160 return 161 } 162 if len(key) == 0 { 163 err = fmt.Errorf("empty key in %q", s[begin:i]) 164 return 165 } 166 if opts[methodName] == nil { 167 opts[methodName] = make(Args) 168 } 169 opts[methodName].Add(key, value) 170 if i >= len(s) { 171 break 172 } 173 // Skip the semicolon. 174 i++ 175 } 176 return opts, nil 177 } 178 179 // Escape backslashes and all the bytes that are in set. 180 func backslashEscape(s string, set []byte) string { 181 var buf bytes.Buffer 182 for _, b := range []byte(s) { 183 if b == '\\' || bytes.IndexByte(set, b) != -1 { 184 buf.WriteByte('\\') 185 } 186 buf.WriteByte(b) 187 } 188 return buf.String() 189 } 190 191 // Encode a name–value mapping so that it is suitable to go in the ARGS option 192 // of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not 193 // added. 194 // 195 // "Equal signs and commas [and backslashes] must be escaped with a backslash." 196 func encodeSmethodArgs(args Args) string { 197 if args == nil { 198 return "" 199 } 200 201 keys := make([]string, 0, len(args)) 202 for key := range args { 203 keys = append(keys, key) 204 } 205 sort.Strings(keys) 206 207 escape := func(s string) string { 208 return backslashEscape(s, []byte{'=', ','}) 209 } 210 211 var pairs []string 212 for _, key := range keys { 213 for _, value := range args[key] { 214 pairs = append(pairs, escape(key)+"="+escape(value)) 215 } 216 } 217 218 return strings.Join(pairs, ",") 219 }