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