[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