| @ -1,214 +0,0 @@ | |||
| // -*-go-*- | |||
| package sip | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "strings" | |||
| ) | |||
| %% machine addr; | |||
| %% write data; | |||
| // ParseAddr turns a SIP address into a data structure. | |||
| func ParseAddr(s string) (addr *Addr, err error) { | |||
| if s == "" { | |||
| return nil, errors.New("Empty SIP message") | |||
| } | |||
| return ParseAddrBytes([]byte(s), nil) | |||
| } | |||
| // ParseAddr turns a SIP address slice into a data structure. | |||
| func ParseAddrBytes(data []byte, next *Addr) (addr *Addr, err error) { | |||
| if data == nil { | |||
| return nil, nil | |||
| } | |||
| addr = new(Addr) | |||
| result := addr | |||
| cs := 0 | |||
| p := 0 | |||
| pe := len(data) | |||
| eof := len(data) | |||
| buf := make([]byte, len(data)) | |||
| amt := 0 | |||
| mark := 0 | |||
| var name string | |||
| %%{ | |||
| action mark { | |||
| mark = p | |||
| } | |||
| action start { | |||
| amt = 0 | |||
| } | |||
| action append { | |||
| buf[amt] = fc | |||
| amt++ | |||
| } | |||
| action name { | |||
| name = string(data[mark:p]) | |||
| } | |||
| action display { | |||
| // TODO: Collapse this string. | |||
| addr.Display = strings.TrimRight(string(buf[0:amt]), " \t\r\n") | |||
| } | |||
| action qdisplay { | |||
| addr.Display = string(buf[0:amt]) | |||
| } | |||
| action uri { | |||
| addr.Uri, err = ParseURIBytes(data[mark:p]) | |||
| if err != nil { return nil, err } | |||
| } | |||
| action param { | |||
| if addr.Params == nil { | |||
| addr.Params = Params{} | |||
| } | |||
| addr.Params[name] = string(buf[0:amt]) | |||
| } | |||
| action lookAheadWSP { p + 2 < pe && (data[p+2] == ' ' || data[p+2] == '\t') } | |||
| action goto_name_addr { | |||
| p = mark | |||
| fhold; | |||
| fgoto name_addr; | |||
| } | |||
| action goto_bare_addr { | |||
| p = mark | |||
| fhold; | |||
| fgoto bare_addr; | |||
| } | |||
| action goto_params { | |||
| fhold; | |||
| fgoto params; | |||
| } | |||
| action goto_addr { | |||
| addr.Next = new(Addr) | |||
| addr = addr.Next | |||
| fhold; | |||
| fgoto addr; | |||
| } | |||
| # https://tools.ietf.org/html/rfc2234 | |||
| SP = " "; | |||
| HTAB = "\t"; | |||
| CR = "\r"; | |||
| LF = "\n"; | |||
| DQUOTE = "\""; | |||
| CRLF = CR LF; | |||
| WSP = SP | HTAB; | |||
| LWS = ( WSP* ( CR when lookAheadWSP ) LF )? WSP+; | |||
| SWS = LWS?; | |||
| UTF8_CONT = 0x80..0xBF @append; | |||
| UTF8_NONASCII = 0xC0..0xDF @append UTF8_CONT {1} | |||
| | 0xE0..0xEF @append UTF8_CONT {2} | |||
| | 0xF0..0xF7 @append UTF8_CONT {3} | |||
| | 0xF8..0xFb @append UTF8_CONT {4} | |||
| | 0xFC..0xFD @append UTF8_CONT {5}; | |||
| UTF8 = 0x21..0x7F @append | UTF8_NONASCII; | |||
| LWSCRLF_append = ( CR when lookAheadWSP ) @append LF @append; | |||
| LWS_append = ( WSP* @append LWSCRLF_append )? WSP+ @append; | |||
| # https://tools.ietf.org/html/rfc3261#section-25.1 | |||
| reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," ; | |||
| mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" ; | |||
| unreserved = alnum | mark ; | |||
| tokenc = alnum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`" | |||
| | "'" | "~" ; | |||
| separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | |||
| | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | |||
| | HTAB ; | |||
| wordc = alnum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`" | |||
| | "'" | "~" | "(" | ")" | "<" | ">" | ":" | "\\" | "\"" | |||
| | "/" | "[" | "]" | "?" | "{" | "}" ; | |||
| word = wordc+; | |||
| STAR = SWS "*" SWS; | |||
| SLASH = SWS "/" SWS; | |||
| EQUAL = SWS "=" SWS; | |||
| LPAREN = SWS "(" SWS; | |||
| RPAREN = SWS ")" SWS; | |||
| RAQUOT = ">" SWS; | |||
| LAQUOT = SWS "<"; | |||
| COMMA = SWS "," SWS; | |||
| SEMI = SWS ";" SWS; | |||
| COLON = SWS ":" SWS; | |||
| HCOLON = WSP* ":" SWS; | |||
| LDQUOT = SWS "\""; | |||
| RDQUOT = "\"" SWS; | |||
| # The RFC BNF for SIP addresses is a bit nuts; we'll try our best. | |||
| token = tokenc+; | |||
| tokenhost = ( tokenc | "[" | "]" | ":" )+; | |||
| schemec = alnum | "+" | "-" | "."; | |||
| scheme = alpha schemec*; | |||
| uric = reserved | unreserved | "%" | "[" | "]"; | |||
| uri = scheme ":" uric+; | |||
| # Quoted strings can have just about anything, including backslash escapes, | |||
| # which aren't quite as fancy as the ones you'd see in programming. | |||
| qdtextc = 0x21 | 0x23..0x5B | 0x5D..0x7E; | |||
| qdtext = UTF8_NONASCII | LWS_append | qdtextc @append; | |||
| quoted_pair = "\\" ( 0x00..0x09 | 0x0B..0x0C | 0x0E..0x7F ) @append; | |||
| quoted_content = ( qdtext | quoted_pair )* >start; | |||
| quoted_string = SWS DQUOTE quoted_content DQUOTE; | |||
| # The display name can either be quoted or unquoted. The unquoted form is | |||
| # much more limited in what it may contain, although it does permit spaces. | |||
| display_name = quoted_string %qdisplay | |||
| | SWS ( token @append LWS_append )* >start %display | |||
| ; | |||
| # Parameter parsing is pretty straightforward. Unlike URI parameters, the | |||
| # values on these can be quoted. The only thing that's weird is if the URI | |||
| # doesn't appear in angle brackets, the parameters need to be owned by the | |||
| # Addr object, not the URI. This has been placed in its own machine so it | |||
| # doesn't have to be duplicated in the two machines below. | |||
| param_name = token >mark >start %name; | |||
| param_content = tokenhost @append; | |||
| param_value = param_content | quoted_string; | |||
| param = ( param_name ( EQUAL param_value )? ) %param; | |||
| params := ( SEMI param )* ( COMMA <: any @goto_addr )?; | |||
| # Now we're going to define two separate machines. The first is for | |||
| # addresses with angle brackets and the second is for ones without. | |||
| addr_spec = uri >mark %uri; | |||
| name_addr := display_name? LAQUOT addr_spec RAQUOT <: ( any @goto_params )?; | |||
| bare_addr := ( addr_spec -- ";" ) <: ( any @goto_params )?; | |||
| # Now we perform lookahead to determine which machine we should use. | |||
| look = [^;<] | |||
| | ";" @goto_bare_addr | |||
| | "<" @goto_name_addr | |||
| ; | |||
| addr := look+ >mark $eof(goto_name_addr); | |||
| write init; | |||
| write exec; | |||
| }%% | |||
| if cs < addr_first_final { | |||
| if p == pe { | |||
| return nil, errors.New(fmt.Sprintf("Incomplete SIP address: %s", data)) | |||
| } else { | |||
| return nil, errors.New(fmt.Sprintf("Error in SIP address at offset %d: %s", p, string(data))) | |||
| } | |||
| } | |||
| if next != nil { | |||
| next.Last().Next = result | |||
| return next, nil | |||
| } | |||
| return result, nil | |||
| } | |||