package pt import ( "bytes" "encoding/json" "fmt" "sort" "strings" ) // Key–value mappings for the representation of client and server options. // Args maps a string key to a list of values. It is similar to url.Values. type Args map[string][]string // Get the first value associated with the given key. If there are any values // associated with the key, the value return has the value and ok is set to // true. If there are no values for the given key, value is "" and ok is false. // If you need access to multiple values, use the map directly. func (args Args) Get(key string) (value string, ok bool) { if args == nil { return "", false } vals, ok := args[key] if !ok || len(vals) == 0 { return "", false } return vals[0], true } // Append value to the list of values for key. func (args Args) Add(key, value string) { args[key] = append(args[key], value) } // Return the index of the next unescaped byte in s that is in the term set, or // else the length of the string if no terminators appear. Additionally return // the unescaped string up to the returned index. func indexUnescaped(s string, term []byte) (int, string, error) { var i int unesc := make([]byte, 0) for i = 0; i < len(s); i++ { b := s[i] // A terminator byte? if bytes.IndexByte(term, b) != -1 { break } if b == '\\' { i++ if i >= len(s) { return 0, "", fmt.Errorf("nothing following final escape in %q", s) } b = s[i] } unesc = append(unesc, b) } return i, string(unesc), nil } // Parse a name–value mapping as from an encoded SOCKS username/password. // // "If any [k=v] items are provided, they are configuration parameters for the // proxy: Tor should separate them with semicolons ... If a key or value value // must contain [an equals sign or] a semicolon or a backslash, it is escaped // with a backslash." func parseClientParameters(s string) (args Args, err error) { args = make(Args) if len(s) == 0 { return } i := 0 for { var key, value string var offset, begin int begin = i // Read the key. offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) if err != nil { return } i += offset // End of string or no equals sign? if i >= len(s) || s[i] != '=' { err = fmt.Errorf("no equals sign in %q", s[begin:i]) return } // Skip the equals sign. i++ // Read the value. offset, value, err = indexUnescaped(s[i:], []byte{';'}) if err != nil { return } i += offset if len(key) == 0 { err = fmt.Errorf("empty key in %q", s[begin:i]) return } args.Add(key, value) if i >= len(s) { break } // Skip the semicolon. i++ } return args, nil } func ParsePT2ClientParameters(s string) (args Args, err error) { args = make(Args) if len(s) == 0 { return } decoder := json.NewDecoder(strings.NewReader(s)) var result map[string]string if err := decoder.Decode(&result); err != nil { fmt.Errorf("Error decoding JSON %q", err) return nil, err } for key, value := range result { args.Add(key, value) } return args, nil } func ParsePT2ServerParameters(s string) (params map[string]Args, err error) { opts := make(map[string]Args) if len(s) == 0 { return opts, nil } decoder := json.NewDecoder(strings.NewReader(s)) var result map[string]map[string]string if err := decoder.Decode(&result); err != nil { fmt.Errorf("Error decoding JSON %q", err) return nil, err } for key, sub := range result { args := make(Args) for key2, value := range sub { args.Add(key2, value) } opts[key] = args } return opts, nil } // Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. // // " is a k=v string value with options that are to be passed to the // transport. Colons, semicolons, equal signs and backslashes must be escaped // with a backslash." // Example: trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes func ParseServerTransportOptions(s string) (opts map[string]Args, err error) { opts = make(map[string]Args) if len(s) == 0 { return } i := 0 for { var methodName, key, value string var offset, begin int begin = i // Read the method name. offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) if err != nil { return } i += offset // End of string or no colon? if i >= len(s) || s[i] != ':' { err = fmt.Errorf("no colon in %q", s[begin:i]) return } // Skip the colon. i++ // Read the key. offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) if err != nil { return } i += offset // End of string or no equals sign? if i >= len(s) || s[i] != '=' { err = fmt.Errorf("no equals sign in %q", s[begin:i]) return } // Skip the equals sign. i++ // Read the value. offset, value, err = indexUnescaped(s[i:], []byte{';'}) if err != nil { return } i += offset if len(methodName) == 0 { err = fmt.Errorf("empty method name in %q", s[begin:i]) return } if len(key) == 0 { err = fmt.Errorf("empty key in %q", s[begin:i]) return } if opts[methodName] == nil { opts[methodName] = make(Args) } opts[methodName].Add(key, value) if i >= len(s) { break } // Skip the semicolon. i++ } return opts, nil } // Escape backslashes and all the bytes that are in set. func backslashEscape(s string, set []byte) string { var buf bytes.Buffer for _, b := range []byte(s) { if b == '\\' || bytes.IndexByte(set, b) != -1 { buf.WriteByte('\\') } buf.WriteByte(b) } return buf.String() } // Encode a name–value mapping so that it is suitable to go in the ARGS option // of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not // added. // // "Equal signs and commas [and backslashes] must be escaped with a backslash." func encodeSmethodArgs(args Args) string { if args == nil { return "" } keys := make([]string, 0, len(args)) for key := range args { keys = append(keys, key) } sort.Strings(keys) escape := func(s string) string { return backslashEscape(s, []byte{'=', ','}) } var pairs []string for _, key := range keys { for _, value := range args[key] { pairs = append(pairs, escape(key)+"="+escape(value)) } } return strings.Join(pairs, ",") }