You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

140 lines
4.5 KiB

package sip
import (
"bytes"
"errors"
"fmt"
"strconv"
)
%% machine uri;
%% write data;
// ParseURI turns a a SIP URI into a data structure.
func ParseURI(s string) (uri *URI, err error) {
if s == "" {
return nil, errors.New("Empty URI")
}
return ParseURIBytes([]byte(s))
}
// ParseURI turns a a SIP URI byte slice into a data structure.
func ParseURIBytes(data []byte) (uri *URI, err error) {
if data == nil {
return nil, nil
}
uri = new(URI)
cs := 0
p := 0
pe := len(data)
eof := len(data)
buf := make([]byte, len(data))
amt := 0
var b1, b2 string
var hex byte
%%{
action strStart { amt = 0 }
action strChar { buf[amt] = fc; amt++ }
action strLower { buf[amt] = fc + 0x20; amt++ }
action hexHi { hex = unhex(fc) * 16 }
action hexLo { hex += unhex(fc)
buf[amt] = hex; amt++ }
action user { uri.User = string(buf[0:amt]) }
action pass { uri.Pass = string(buf[0:amt]) }
action host { uri.Host = string(buf[0:amt]) }
action scheme { uri.Scheme = string(buf[0:amt]) }
action b1 { b1 = string(buf[0:amt]); amt = 0 }
action b2 { b2 = string(buf[0:amt]); amt = 0 }
action port {
if amt > 0 {
port, err := strconv.ParseUint(string(buf[0:amt]), 10, 16)
if err != nil { goto fail }
uri.Port = uint16(port)
}
}
action param {
if uri.Params == nil {
uri.Params = Params{}
}
uri.Params[b1] = b2
}
action header {
if uri.Headers == nil {
uri.Headers = URIHeaders{}
}
uri.Headers[b1] = b2
}
# TODO(jart): Use BNF from SIP RFC: https://tools.ietf.org/html/rfc3261#section-25.1
# Define what a single character is allowed to be.
toxic = ( cntrl | 127 ) ;
scary = ( toxic | space | "\"" | "#" | "%" | "<" | ">" | "=" ) ;
schmchars = ( lower | digit | "+" | "-" | "." ) ;
authdelims = ( "/" | "?" | "#" | ":" | "@" | ";" | "[" | "]" | "&" ) ;
userchars = any -- ( authdelims | scary ) ;
passchars = userchars ;
hostchars = passchars -- upper;
hostcharsEsc = ( hostchars | ":" ) -- upper;
portchars = digit ;
paramchars = userchars -- upper ;
headerchars = userchars ;
# Define how characters trigger actions.
escape = "%" xdigit xdigit ;
unescape = "%" ( xdigit @hexHi ) ( xdigit @hexLo ) ;
schmfirst = ( upper @strLower ) | ( lower @strChar ) ;
schmchar = ( upper @strLower ) | ( schmchars @strChar ) ;
userchar = unescape | ( userchars @strChar ) ;
passchar = unescape | ( passchars @strChar ) ;
hostchar = unescape | ( upper @strLower ) | ( hostchars @strChar ) ;
hostcharEsc = unescape | ( upper @strLower ) | ( hostcharsEsc @strChar ) ;
portchar = unescape | ( portchars @strChar ) ;
paramchar = unescape | ( upper @strLower ) | ( paramchars @strChar ) ;
headerchar = unescape | ( headerchars @strChar ) ;
# Define multi-character patterns.
scheme = ( schmfirst schmchar* ) >strStart %scheme ;
user = userchar+ >strStart %user ;
pass = passchar+ >strStart %pass ;
hostPlain = hostchar+ >strStart %host ;
hostQuoted = "[" ( hostcharEsc+ >strStart %host ) "]" ;
host = hostQuoted | hostPlain ;
port = portchar* >strStart %port ;
paramkey = paramchar+ >strStart >b2 %b1 ;
paramval = paramchar+ >strStart %b2 ;
param = space* ";" paramkey ( "=" paramval )? %param ;
headerkey = headerchar+ >strStart >b2 %b1 ;
headerval = headerchar+ >strStart %b2 ;
header = headerkey ( "=" headerval )? %header ;
headers = "?" header ( "&" header )* ;
userpass = user ( ":" pass )? ;
hostport = host ( ":" port )? ;
uriSansUser := space* scheme ":" hostport param* space* headers? space* ;
uriWithUser := space* scheme ":" userpass "@" hostport param* space* headers? space* ;
}%%
%% write init;
if bytes.IndexByte(data, '@') == -1 {
cs = uri_en_uriSansUser;
} else {
cs = uri_en_uriWithUser;
}
%% write exec;
if cs < uri_first_final {
if p == pe {
return nil, errors.New(fmt.Sprintf("Unexpected EOF: %s", data))
} else {
return nil, errors.New(fmt.Sprintf("Error in URI at pos %d: %s", p, data))
}
}
return uri, nil
fail:
return nil, errors.New(fmt.Sprintf("Bad URI: %s", data))
}