commit fb691a255911631b41b8eba33a034180e21a39a4
Author: David Fifield <[email protected]>
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 mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to