commit fb691a255911631b41b8eba33a034180e21a39a4 Author: David Fifield david@bamsoftware.com Date: Sat Dec 7 02:32:33 2013 -0800
parseClientParameters. --- args.go | 80 +++++++++++++++++++++++++++++++++++++ args_test.go | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+)
diff --git a/args.go b/args.go index 6c8fb86..df3aced 100644 --- a/args.go +++ b/args.go @@ -1,5 +1,11 @@ package pt
+import ( + "bytes" + "errors" + "fmt" +) + // 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. @@ -23,3 +29,77 @@ func (args Args) Get(key string) (value string, ok bool) { 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 not 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, "", errors.New(fmt.Sprintf("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 = 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(key) == 0 { + err = errors.New(fmt.Sprintf("empty key in %q", s[begin:i])) + return + } + args.Add(key, value) + if i >= len(s) { + break + } + // Skip the semicolon. + i++ + } + return args, nil +} diff --git a/args_test.go b/args_test.go index c89cef3..69194fd 100644 --- a/args_test.go +++ b/args_test.go @@ -4,6 +4,34 @@ import ( "testing" )
+func stringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func argsEqual(a, b Args) bool { + for k, av := range a { + bv := b[k] + if !stringSlicesEqual(av, bv) { + return false + } + } + for k, bv := range b { + av := a[k] + if !stringSlicesEqual(av, bv) { + return false + } + } + return true +} + func TestArgsGet(t *testing.T) { args := Args{ "a": []string{}, @@ -55,3 +83,101 @@ func TestArgsAdd(t *testing.T) { t.Error() } } + +func TestParseClientParameters(t *testing.T) { + badTests := [...]string{ + "key", + "=value", + "==value", + "==key=value", + "key=value\", + "a=b;key=value\", + "a;b=c", + ";", + "key=value;", + ";key=value", + "key\=value", + } + goodTests := [...]struct { + input string + expected Args + }{ + { + "", + Args{}, + }, + { + "key=", + Args{"key": []string{""}}, + }, + { + "key==", + Args{"key": []string{"="}}, + }, + { + "key=value", + Args{"key": []string{"value"}}, + }, + { + "a=b=c", + Args{"a": []string{"b=c"}}, + }, + { + "key=a\nb", + Args{"key": []string{"a\nb"}}, + }, + { + "key=value\;", + Args{"key": []string{"value;"}}, + }, + { + "key="value"", + Args{"key": []string{""value""}}, + }, + { + "key=""value""", + Args{"key": []string{"""value"""}}, + }, + { + ""key=value"", + Args{""key": []string{"value""}}, + }, + { + "key=value;key=value", + Args{"key": []string{"value", "value"}}, + }, + { + "key=value1;key=value2", + Args{"key": []string{"value1", "value2"}}, + }, + { + "key1=value1;key2=value2;key1=value3", + Args{"key1": []string{"value1", "value3"}, "key2": []string{"value2"}}, + }, + { + "\;=\;;\\=\;", + Args{";": []string{";"}, "\": []string{";"}}, + }, + { + "a\=b=c", + Args{"a=b": []string{"c"}}, + }, + } + + for _, input := range badTests { + _, err := parseClientParameters(input) + if err == nil { + t.Errorf("%q unexpectedly succeeded", input) + } + } + + for _, test := range goodTests { + args, err := parseClientParameters(test.input) + if err != nil { + t.Errorf("%q unexpectedly returned an error: %s", test.input, err) + } + if !argsEqual(args, test.expected) { + t.Errorf("%q → %q (expected %q)", test.input, args, test.expected) + } + } +}
tor-commits@lists.torproject.org