commit 11d2149f1de4c0975ce70f8eaceb863b9058675c Author: David Fifield david@bamsoftware.com Date: Sun Dec 8 01:59:57 2013 -0800
parseServerTransportOptions. --- args.go | 70 +++++++++++++++++++++++++++++++++++++ args_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+)
diff --git a/args.go b/args.go index df3aced..a0a3bd5 100644 --- a/args.go +++ b/args.go @@ -103,3 +103,73 @@ func parseClientParameters(s string) (args Args, err error) { } return args, nil } + +// Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. +// +// "<value> 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 = errors.New(fmt.Sprintf("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 = errors.New(fmt.Sprintf("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 = errors.New(fmt.Sprintf("empty method name in %q", s[begin:i])) + return + } + if len(key) == 0 { + err = errors.New(fmt.Sprintf("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 +} diff --git a/args_test.go b/args_test.go index 69194fd..078c79a 100644 --- a/args_test.go +++ b/args_test.go @@ -181,3 +181,113 @@ func TestParseClientParameters(t *testing.T) { } } } + +func optsEqual(a, b map[string]Args) bool { + for k, av := range a { + bv, ok := b[k] + if !ok || !argsEqual(av, bv) { + return false + } + } + for k, bv := range b { + av, ok := a[k] + if !ok || !argsEqual(av, bv) { + return false + } + } + return true +} + +func TestParseServerTransportOptions(t *testing.T) { + badTests := [...]string{ + ":=", + "t:=", + ":k=", + ":=v", + "t:=v", + "t:=v", + "t:k=v;", + "abc", + "t:", + "key=value", + "=value", + "t:k=v\", + "t1:k=v;t2:k=v\", + "t:=key=value", + "t:==key=value", + "t:;key=value", + "t:key\=value", + } + goodTests := [...]struct { + input string + expected map[string]Args + }{ + { + "", + map[string]Args{}, + }, + { + "t:k=v", + map[string]Args{ + "t": Args{"k": []string{"v"}}, + }, + }, + { + "t1:k=v1;t2:k=v2;t1:k=v3", + map[string]Args{ + "t1": Args{"k": []string{"v1", "v3"}}, + "t2": Args{"k": []string{"v2"}}, + }, + }, + { + "t\:1:k=v;t\=2:k=v;t\;3:k=v;t\\4:k=v", + map[string]Args{ + "t:1": Args{"k": []string{"v"}}, + "t=2": Args{"k": []string{"v"}}, + "t;3": Args{"k": []string{"v"}}, + "t\4": Args{"k": []string{"v"}}, + }, + }, + { + "t:k\:1=v;t:k\=2=v;t:k\;3=v;t:k\\4=v", + map[string]Args{ + "t": Args{ + "k:1": []string{"v"}, + "k=2": []string{"v"}, + "k;3": []string{"v"}, + "k\4": []string{"v"}, + }, + }, + }, + { + "t:k=v\:1;t:k=v\=2;t:k=v\;3;t:k=v\\4", + map[string]Args{ + "t": Args{"k": []string{"v:1", "v=2", "v;3", "v\4"}}, + }, + }, + { + "trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes", + map[string]Args{ + "trebuchet": Args{"secret": []string{"nou"}, "cache": []string{"/tmp/cache"}}, + "ballista": Args{"secret": []string{"yes"}}, + }, + }, + } + + for _, input := range badTests { + _, err := parseServerTransportOptions(input) + if err == nil { + t.Errorf("%q unexpectedly succeeded", input) + } + } + + for _, test := range goodTests { + opts, err := parseServerTransportOptions(test.input) + if err != nil { + t.Errorf("%q unexpectedly returned an error: %s", test.input, err) + } + if !optsEqual(opts, test.expected) { + t.Errorf("%q → %q (expected %q)", test.input, opts, test.expected) + } + } +}
tor-commits@lists.torproject.org