@@ -283,7 +283,10 @@ func findAngleBrackets(value string) (int, int, error) {
283283 start := - 1
284284 end := - 1
285285
286- for i , r := range value {
286+ // A leading quoted display name may contain angle brackets of its own.
287+ offset := len (value ) - len (skipQuotedDisplayName (value ))
288+ for i , r := range value [offset :] {
289+ i += offset
287290 switch r {
288291 case '<' :
289292 if start != - 1 {
@@ -313,27 +316,52 @@ func findAngleBrackets(value string) (int, int, error) {
313316
314317// validateNameAddrHeader validates headers that use name-addr format per RFC 3261 Section 20.10
315318func validateNameAddrHeader (value string ) error {
319+ _ , err := ParseSIPNameAddr (value )
320+ return err
321+ }
322+
323+ // ParseSIPNameAddr validates a value in name-addr or addr-spec format, as found
324+ // in From, To and Refer-To headers, and returns the user part of its URI. The
325+ // user is empty when the URI has none.
326+ func ParseSIPNameAddr (value string ) (string , error ) {
316327 // RFC 3261 Section 20.10 - name-addr format
317328 // name-addr = [display-name] <addr-spec>
318329 // addr-spec = SIP-URI / SIPS-URI / absoluteURI
319330
320331 uri := value
321332 start , end , err := findAngleBrackets (value )
322333 if err != nil {
323- return err
334+ return "" , err
324335 }
325336 if start >= 0 || end >= 0 {
326337 uri = value [start + 1 : end ]
327338 if err := validateDisplayName (strings .TrimSpace (value [:start ])); err != nil {
328- return err
339+ return "" , err
329340 }
330341 } else {
331342 // This is a bare URI, and should comply with addr-spec, no special characters
332343 if strings .ContainsAny (value , ",? " ) {
333- return errors .New ("bare URI with special characters" )
344+ return "" , errors .New ("bare URI with special characters" )
334345 }
335346 }
336- return validateURI (uri )
347+ return uriUser (uri )
348+ }
349+
350+ // skipQuotedDisplayName returns the part of a name-addr after a leading quoted
351+ // display name. An unterminated quote returns the value unchanged.
352+ func skipQuotedDisplayName (s string ) string {
353+ if ! strings .HasPrefix (s , "\" " ) {
354+ return s
355+ }
356+ for i := 1 ; i < len (s ); i ++ {
357+ switch s [i ] {
358+ case '\\' :
359+ i ++
360+ case '"' :
361+ return s [i + 1 :]
362+ }
363+ }
364+ return s
337365}
338366
339367// validateDisplayName validates a display name in name-addr format
@@ -360,20 +388,31 @@ func validateDisplayName(displayName string) error {
360388 return nil
361389}
362390
363- // validateURI validates URIs that can appear in name-addr format
364- func validateURI (uri string ) error {
391+ // uriUser validates a URI that can appear in name-addr format and returns its
392+ // user part: up to the "@" for sip and sips, the whole number for tel, without
393+ // parameters or headers.
394+ func uriUser (uri string ) (string , error ) {
365395 // Just do the basics, full validation should be done by sip service
366- scheme := strings .SplitN (uri , ":" , 2 )[0 ]
367- if scheme != "sip" && scheme != "sips" && scheme != "tel" {
396+ scheme , rest , _ := strings .Cut (uri , ":" )
397+ var user string
398+ switch strings .ToLower (scheme ) {
399+ case "sip" , "sips" :
400+ if u , _ , found := strings .Cut (rest , "@" ); found {
401+ user = u
402+ }
403+ case "tel" :
368404 // Technically, it either needs to be sip/s: or scheme://...
369405 // Thus, tel: uri should not be supported here... but we allow it because of de-facto usage.
370- return errors .New ("uri: scheme not one of sip, sips, or tel" )
406+ user = rest
407+ default :
408+ return "" , errors .New ("uri: scheme not one of sip, sips, or tel" )
371409 }
372410
373411 // Just no spaces, proper validation should be done in sip service
374412 if strings .Contains (uri , " " ) {
375- return errors .New ("uri: contains spaces" )
413+ return "" , errors .New ("uri: contains spaces" )
376414 }
377415
378- return nil
416+ user , _ , _ = strings .Cut (user , ";" )
417+ return user , nil
379418}
0 commit comments