[tor-commits] [goptlib/master] parseServerTransportOptions.

dcf at torproject.org dcf at torproject.org
Mon Dec 9 02:49:51 UTC 2013


commit 11d2149f1de4c0975ce70f8eaceb863b9058675c
Author: David Fifield <david at 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)
+		}
+	}
+}





More information about the tor-commits mailing list