[tor-commits] [goptlib/master] parseClientParameters.
dcf at torproject.org
dcf at torproject.org
Mon Dec 9 02:49:51 UTC 2013
commit fb691a255911631b41b8eba33a034180e21a39a4
Author: David Fifield <david at 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)
+ }
+ }
+}
More information about the tor-commits
mailing list