Browse Source

Simplify escaping stuff.

pull/2/head
Justine Alexandra Roberts Tunney 11 years ago
parent
commit
5fe4effdb8
10 changed files with 52 additions and 255 deletions
  1. +6
    -6
      sip/addr.go
  2. +2
    -2
      sip/addr_test.go
  3. +12
    -38
      sip/escape.go
  4. +9
    -12
      sip/escape_test.go
  5. +4
    -4
      sip/params.go
  6. +9
    -20
      sip/quote.go
  7. +4
    -1
      sip/quote_test.go
  8. +1
    -1
      sip/sip.rl
  9. +5
    -5
      sip/uri.go
  10. +0
    -166
      util/escape.go

+ 6
- 6
sip/addr.go View File

@ -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


+ 2
- 2
sip/addr_test.go View File

@ -134,7 +134,7 @@ var addrTests = []addrTest{
},
addrTest{
s: `<sip:pokemon.com>,"Ditto" <sip:ditto@pokemon.com>`,
s: `<sip:pokemon.com>, Ditto <sip:ditto@pokemon.com>`,
addr: sip.Addr{
Uri: &sip.URI{
Scheme: "sip",
@ -152,7 +152,7 @@ var addrTests = []addrTest{
},
addrTest{
s: `<sip:1.2.3.4>,<sip:1.2.3.5>,<sip:[666::dead:beef]>`,
s: `<sip:1.2.3.4>, <sip:1.2.3.5>, <sip:[666::dead:beef]>`,
addr: sip.Addr{
Uri: &sip.URI{
Scheme: "sip",


+ 12
- 38
sip/escape.go View File

@ -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
}

+ 9
- 12
sip/escape_test.go View File

@ -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"))
}
}

+ 4
- 4
sip/params.go View File

@ -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))
}
}
}


+ 9
- 20
sip/quote.go View File

@ -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()
}

+ 4
- 1
sip/quote_test.go View File

@ -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)
}


+ 1
- 1
sip/sip.rl View File

@ -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";


+ 5
- 5
sip/uri.go View File

@ -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)
}
}
}


+ 0
- 166
util/escape.go View File

@ -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
}

Loading…
Cancel
Save