From 5fe4effdb8580d295351167aebd1cc226d11efb1 Mon Sep 17 00:00:00 2001 From: Justine Alexandra Roberts Tunney Date: Sat, 11 Apr 2015 01:33:03 -0400 Subject: [PATCH] Simplify escaping stuff. --- sip/addr.go | 12 ++-- sip/addr_test.go | 4 +- sip/escape.go | 50 ++++---------- sip/escape_test.go | 21 +++--- sip/params.go | 8 +-- sip/quote.go | 29 +++----- sip/quote_test.go | 5 +- sip/sip.rl | 2 +- sip/uri.go | 10 +-- util/escape.go | 166 --------------------------------------------- 10 files changed, 52 insertions(+), 255 deletions(-) delete mode 100755 util/escape.go diff --git a/sip/addr.go b/sip/addr.go index 7735932..c1e2565 100755 --- a/sip/addr.go +++ b/sip/addr.go @@ -59,16 +59,16 @@ func (addr *Addr) Tag() *Addr { // Reassembles a SIP address into a buffer. func (addr *Addr) Append(b *bytes.Buffer) error { if addr.Display != "" { - b.WriteString("\"") - b.WriteString(util.EscapeDisplay(addr.Display)) - b.WriteString("\" ") + appendQuoted(b, []byte(addr.Display)) + b.WriteByte(' ') } - b.WriteString("<") + b.WriteByte('<') addr.Uri.Append(b) - b.WriteString(">") + b.WriteByte('>') addr.Params.AppendQuoted(b) if addr.Next != nil { - b.WriteString(",") + b.WriteByte(',') + b.WriteByte(' ') addr.Next.Append(b) } return nil diff --git a/sip/addr_test.go b/sip/addr_test.go index 3dda157..5b60f73 100755 --- a/sip/addr_test.go +++ b/sip/addr_test.go @@ -134,7 +134,7 @@ var addrTests = []addrTest{ }, addrTest{ - s: `,"Ditto" `, + s: `, Ditto `, addr: sip.Addr{ Uri: &sip.URI{ Scheme: "sip", @@ -152,7 +152,7 @@ var addrTests = []addrTest{ }, addrTest{ - s: `,,`, + s: `, , `, addr: sip.Addr{ Uri: &sip.URI{ Scheme: "sip", diff --git a/sip/escape.go b/sip/escape.go index 1e44471..1405613 100644 --- a/sip/escape.go +++ b/sip/escape.go @@ -1,49 +1,23 @@ package sip -// escapeUser escapes a URI user, which can't use quoting. -func escapeUser(s []byte) []byte { - return escape(s, userc) -} - -// escapePass escapes a URI password, which can't use quoting. -func escapePass(s []byte) []byte { - return escape(s, passc) -} - -// escapeParam escapes a URI parameter, which can't use quoting. -func escapeParam(s []byte) []byte { - return escape(s, paramc) -} +import ( + "bytes" +) -// escapeHeader escapes a URI header, which can't use quoting. -func escapeHeader(s []byte) []byte { - return escape(s, headerc) -} +const ( + hexChars = "0123456789abcdef" +) -// escape provides arbitrary URI escaping. -func escape(s []byte, p func(byte) bool) []byte { - hc := 0 - for i := 0; i < len(s); i++ { - if !p(s[i]) { - hc++ - } - } - if hc == 0 { - return s - } - res := make([]byte, len(s)+2*hc) - j := 0 +// appendEscaped appends while URL encoding bytes that don't match the predicate.. +func appendEscaped(b *bytes.Buffer, s []byte, p func(byte) bool) { for i := 0; i < len(s); i++ { c := s[i] if p(c) { - res[j] = c - j++ + b.WriteByte(c) } else { - res[j] = '%' - res[j+1] = hexChars[c>>4] - res[j+2] = hexChars[c%16] - j += 3 + b.WriteByte('%') + b.WriteByte(hexChars[c>>4]) + b.WriteByte(hexChars[c%16]) } } - return res } diff --git a/sip/escape_test.go b/sip/escape_test.go index 1d0c933..771457c 100644 --- a/sip/escape_test.go +++ b/sip/escape_test.go @@ -1,6 +1,7 @@ package sip import ( + "bytes" "testing" ) @@ -8,7 +9,7 @@ type escapeTest struct { name string in string out string - p func([]byte) []byte + p func(byte) bool } var escapeTests = []escapeTest{ @@ -17,42 +18,38 @@ var escapeTests = []escapeTest{ name: "Param Normal", in: "hello", out: "hello", - p: escapeParam, + p: paramc, }, escapeTest{ name: "User Normal", in: "hello", out: "hello", - p: escapeUser, + p: userc, }, escapeTest{ name: "Param Spacing", in: "hello there", out: "hello%20there", - p: escapeParam, + p: paramc, }, escapeTest{ name: "User Spacing", in: "hello there", out: "hello%20there", - p: escapeUser, + p: userc, }, } func TestEscape(t *testing.T) { for _, test := range escapeTests { - out := string(test.p([]byte(test.in))) + var b bytes.Buffer + appendEscaped(&b, []byte(test.in), test.p) + out := b.String() if test.out != out { t.Errorf("%s: %s != %s", test.name, test.out, out) } } } - -func BenchmarkEscapeParam(b *testing.B) { - for i := 0; i < b.N; i++ { - escapeParam([]byte("hello there")) - } -} diff --git a/sip/params.go b/sip/params.go index 83c1c60..55722b6 100644 --- a/sip/params.go +++ b/sip/params.go @@ -26,11 +26,11 @@ func (params Params) Append(b *bytes.Buffer) { sort.Strings(keys) for _, k := range keys { b.WriteByte(';') - b.Write(escapeParam([]byte(k))) + appendEscaped(b, []byte(k), paramc) v := params[k] if v != "" { b.WriteByte('=') - b.Write(escapeParam([]byte(v))) + appendEscaped(b, []byte(v), paramc) } } } @@ -47,11 +47,11 @@ func (params Params) AppendQuoted(b *bytes.Buffer) { sort.Strings(keys) for _, k := range keys { b.WriteByte(';') - b.Write(tokencify([]byte(k))) + appendSanitized(b, []byte(k), tokenc) v := params[k] if v != "" { b.WriteByte('=') - b.Write(quote([]byte(v))) + appendQuoted(b, []byte(v)) } } } diff --git a/sip/quote.go b/sip/quote.go index 3ca318b..a718958 100644 --- a/sip/quote.go +++ b/sip/quote.go @@ -4,43 +4,33 @@ import ( "bytes" ) -const ( - hexChars = "0123456789abcdef" -) - -// tokencify removes all characters that aren't tokenc. -func tokencify(s []byte) []byte { - t := make([]byte, len(s)) - j := 0 - l := 0 +// appendSanitized appends stripping all characters that don't match the predicate. +func appendSanitized(b *bytes.Buffer, s []byte, p func(byte) bool) { for i := 0; i < len(s); i++ { - if tokenc(s[i]) { - t[j] = s[i] - j++ - l++ + if p(s[i]) { + b.WriteByte(s[i]) } } - return t[:l] } // quote formats an address parameter value or display name. // // Quotation marks are only added if necessary. This implementation will // truncate on input error. -func quote(s []byte) []byte { +func appendQuoted(b *bytes.Buffer, s []byte) { for i := 0; i < len(s); i++ { if !tokenc(s[i]) && s[i] != ' ' { - return quoteQuoted(s) + appendQuoteQuoted(b, s) + return } } - return s + b.Write(s) } // quoteQuoted formats an address parameter value or display name with quotes. // // This implementation will truncate on input error. -func quoteQuoted(s []byte) []byte { - var b bytes.Buffer +func appendQuoteQuoted(b *bytes.Buffer, s []byte) { b.WriteByte('"') for i := 0; i < len(s); i++ { if qdtextc(s[i]) { @@ -59,5 +49,4 @@ func quoteQuoted(s []byte) []byte { b.WriteByte(s[i]) } b.WriteByte('"') - return b.Bytes() } diff --git a/sip/quote_test.go b/sip/quote_test.go index 85f3981..802eb89 100644 --- a/sip/quote_test.go +++ b/sip/quote_test.go @@ -1,6 +1,7 @@ package sip import ( + "bytes" "testing" ) @@ -57,7 +58,9 @@ var quoteTests = []quoteTest{ func TestQuote(t *testing.T) { for _, test := range quoteTests { - out := string(quote([]byte(test.in))) + var b bytes.Buffer + appendQuoted(&b, []byte(test.in)) + out := b.String() if test.out != out { t.Errorf("%s: %s != %s", test.name, test.out, out) } diff --git a/sip/sip.rl b/sip/sip.rl index 325aafb..00431d1 100644 --- a/sip/sip.rl +++ b/sip/sip.rl @@ -47,6 +47,7 @@ # header, using commas within a single header, or both. # # See: http://www.colm.net/files/ragel/ragel-guide-6.9.pdf +# See: https://tools.ietf.org/html/rfc2234 machine sip; @@ -271,7 +272,6 @@ action MinExpires { action lookAheadWSP { lookAheadWSP(data, p, pe) } -# https://tools.ietf.org/html/rfc2234 SP = " "; HTAB = "\t"; CR = "\r"; diff --git a/sip/uri.go b/sip/uri.go index f0b58bc..af8aa2f 100755 --- a/sip/uri.go +++ b/sip/uri.go @@ -83,11 +83,11 @@ func (uri *URI) Append(b *bytes.Buffer) { } if uri.User != "" { if uri.Pass != "" { - b.Write(escapeUser([]byte(uri.User))) + appendEscaped(b, []byte(uri.User), userc) b.WriteByte(':') - b.Write(escapePass([]byte(uri.Pass))) + appendEscaped(b, []byte(uri.Pass), passc) } else { - b.Write(escapeUser([]byte(uri.User))) + appendEscaped(b, []byte(uri.User), userc) } b.WriteByte('@') } @@ -152,11 +152,11 @@ func (headers URIHeaders) Append(b *bytes.Buffer) { } else { b.WriteByte('&') } - b.Write(escapeHeader([]byte(k))) + appendEscaped(b, []byte(k), headerc) v := headers[k] if v != "" { b.WriteByte('=') - b.Write(escapeHeader([]byte(v))) + appendEscaped(b, []byte(v), headerc) } } } diff --git a/util/escape.go b/util/escape.go deleted file mode 100755 index fb7d09c..0000000 --- a/util/escape.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// this code adapted from go/src/pkg/http/url.go for gosip because the -// darn public interface won't let me turn off doPlus :'( - -package util - -import ( - "strconv" -) - -type URLEscapeError string - -func (e URLEscapeError) Error() string { - return "invalid URL escape " + strconv.Quote(string(e)) -} - -// Escape quoted stuff in SIP addresses. -func EscapeDisplay(s string) (res string) { - for _, c := range s { - switch c { - case '"': - res += "\\\"" - case '\\': - res += "\\\\" - default: - res += string(c) - } - } - return -} - -// Return true if the specified character should be escaped when -// appearing in a URL string, according to RFC 2396. -func shouldEscape(c byte) bool { - if c <= ' ' || c >= 0x7F { - return true - } - switch c { - case '<', '>', '#', '%', '"', // RFC 2396 delims - '{', '}', '|', '\\', '^', '[', ']', '`', // RFC2396 unwise - '?', '&', '=', '@': // RFC 2396 reserved in path - return true - } - return false -} - -// urlUnescape is like URLUnescape but can be told not to -// convert + into space. URLUnescape implements what is -// called "URL encoding" but that only applies to query strings. -// Elsewhere in the URL, + does not mean space. -func URLUnescape(s string, doPlus bool) (string, error) { - // Count %, check that they're well-formed. - n := 0 - hasPlus := false - for i := 0; i < len(s); { - switch s[i] { - case '%': - n++ - if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { - s = s[i:] - if len(s) > 3 { - s = s[0:3] - } - return "", URLEscapeError(s) - } - i += 3 - case '+': - hasPlus = doPlus - i++ - default: - i++ - } - } - - if n == 0 && !hasPlus { - return s, nil - } - - t := make([]byte, len(s)-2*n) - j := 0 - for i := 0; i < len(s); { - switch s[i] { - case '%': - t[j] = unhex(s[i+1])<<4 | unhex(s[i+2]) - j++ - i += 3 - case '+': - if doPlus { - t[j] = ' ' - } else { - t[j] = '+' - } - j++ - i++ - default: - t[j] = s[i] - j++ - i++ - } - } - return string(t), nil -} - -func URLEscape(s string, doPlus bool) string { - spaceCount, hexCount := 0, 0 - for i := 0; i < len(s); i++ { - c := s[i] - if shouldEscape(c) { - if c == ' ' && doPlus { - spaceCount++ - } else { - hexCount++ - } - } - } - - if spaceCount == 0 && hexCount == 0 { - return s - } - - t := make([]byte, len(s)+2*hexCount) - j := 0 - for i := 0; i < len(s); i++ { - switch c := s[i]; { - case c == ' ' && doPlus: - t[j] = '+' - j++ - case shouldEscape(c): - t[j] = '%' - t[j+1] = "0123456789abcdef"[c>>4] - t[j+2] = "0123456789abcdef"[c&15] - j += 3 - default: - t[j] = s[i] - j++ - } - } - return string(t) -} - -func ishex(c byte) bool { - switch { - case '0' <= c && c <= '9': - return true - case 'a' <= c && c <= 'f': - return true - case 'A' <= c && c <= 'F': - return true - } - return false -} - -func unhex(c byte) byte { - switch { - case '0' <= c && c <= '9': - return c - '0' - case 'a' <= c && c <= 'f': - return c - 'a' + 10 - case 'A' <= c && c <= 'F': - return c - 'A' + 10 - } - return 0 -}