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