Skip to content

Commit a4aabbe

Browse files
Making the sip refer to compliant to the RFC (#1232)
* Making the sip refer to compliant to the RFC and adding validation/tests for the same * avoiding regex for performance reasons
1 parent fff85ca commit a4aabbe

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

livekit/sip.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,25 @@ func (p *TransferSIPParticipantRequest) Validate() error {
698698
if p.TransferTo == "" {
699699
return errors.New("missing transfer to")
700700
}
701+
702+
// Validate TransferTo URI format and ensure RFC compliance
703+
var uriToValidate string
704+
if strings.HasPrefix(p.TransferTo, "<") && strings.HasSuffix(p.TransferTo, ">") {
705+
// Extract inner URI for validation
706+
uriToValidate = p.TransferTo[1 : len(p.TransferTo)-1]
707+
} else {
708+
uriToValidate = p.TransferTo
709+
}
710+
711+
if !strings.HasPrefix(uriToValidate, "sip:") && !strings.HasPrefix(uriToValidate, "tel:") {
712+
return errors.New("transfer_to must be a valid SIP or TEL URI (sip: or tel:)")
713+
}
714+
715+
// Ensure RFC compliance by wrapping in angle brackets if not already wrapped
716+
if !strings.HasPrefix(p.TransferTo, "<") || !strings.HasSuffix(p.TransferTo, ">") {
717+
p.TransferTo = fmt.Sprintf("<%s>", p.TransferTo)
718+
}
719+
701720
if err := validateHeaderKeys(p.Headers); err != nil {
702721
return err
703722
}

livekit/sip_test.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,201 @@ func TestSIPDispatchRuleFilter(t *testing.T) {
362362
}
363363
}
364364

365+
func TestTransferSIPParticipantRequestValidate(t *testing.T) {
366+
cases := []struct {
367+
name string
368+
req *TransferSIPParticipantRequest
369+
expectError bool
370+
expectedURI string // Expected TransferTo after validation
371+
}{
372+
{
373+
name: "valid sip URI without brackets",
374+
req: &TransferSIPParticipantRequest{
375+
RoomName: "room1",
376+
ParticipantIdentity: "participant1",
377+
TransferTo: "sip:[email protected]",
378+
},
379+
expectError: false,
380+
expectedURI: "<sip:[email protected]>",
381+
},
382+
{
383+
name: "valid tel URI without brackets",
384+
req: &TransferSIPParticipantRequest{
385+
RoomName: "room1",
386+
ParticipantIdentity: "participant1",
387+
TransferTo: "tel:+15105550100",
388+
},
389+
expectError: false,
390+
expectedURI: "<tel:+15105550100>",
391+
},
392+
{
393+
name: "valid sip URI with brackets",
394+
req: &TransferSIPParticipantRequest{
395+
RoomName: "room1",
396+
ParticipantIdentity: "participant1",
397+
TransferTo: "<sip:[email protected]>",
398+
},
399+
expectError: false,
400+
expectedURI: "<sip:[email protected]>",
401+
},
402+
{
403+
name: "valid tel URI with brackets",
404+
req: &TransferSIPParticipantRequest{
405+
RoomName: "room1",
406+
ParticipantIdentity: "participant1",
407+
TransferTo: "<tel:+15105550100>",
408+
},
409+
expectError: false,
410+
expectedURI: "<tel:+15105550100>",
411+
},
412+
{
413+
name: "invalid URI - http",
414+
req: &TransferSIPParticipantRequest{
415+
RoomName: "room1",
416+
ParticipantIdentity: "participant1",
417+
TransferTo: "http://example.com",
418+
},
419+
expectError: true,
420+
},
421+
{
422+
name: "invalid URI - mailto",
423+
req: &TransferSIPParticipantRequest{
424+
RoomName: "room1",
425+
ParticipantIdentity: "participant1",
426+
TransferTo: "mailto:[email protected]",
427+
},
428+
expectError: true,
429+
},
430+
{
431+
name: "invalid URI - plain text",
432+
req: &TransferSIPParticipantRequest{
433+
RoomName: "room1",
434+
ParticipantIdentity: "participant1",
435+
TransferTo: "just-a-phone-number",
436+
},
437+
expectError: true,
438+
},
439+
{
440+
name: "invalid URI - empty",
441+
req: &TransferSIPParticipantRequest{
442+
RoomName: "room1",
443+
ParticipantIdentity: "participant1",
444+
TransferTo: "",
445+
},
446+
expectError: true,
447+
},
448+
{
449+
name: "missing room name",
450+
req: &TransferSIPParticipantRequest{
451+
RoomName: "",
452+
ParticipantIdentity: "participant1",
453+
TransferTo: "sip:[email protected]",
454+
},
455+
expectError: true,
456+
},
457+
{
458+
name: "missing participant identity",
459+
req: &TransferSIPParticipantRequest{
460+
RoomName: "room1",
461+
ParticipantIdentity: "",
462+
TransferTo: "sip:[email protected]",
463+
},
464+
expectError: true,
465+
},
466+
{
467+
name: "invalid URI with brackets - http",
468+
req: &TransferSIPParticipantRequest{
469+
RoomName: "room1",
470+
ParticipantIdentity: "participant1",
471+
TransferTo: "<http://example.com>",
472+
},
473+
expectError: true,
474+
},
475+
{
476+
name: "complex sip URI with transport",
477+
req: &TransferSIPParticipantRequest{
478+
RoomName: "room1",
479+
ParticipantIdentity: "participant1",
480+
TransferTo: "sip:[email protected];transport=tcp",
481+
},
482+
expectError: false,
483+
expectedURI: "<sip:[email protected];transport=tcp>",
484+
},
485+
{
486+
name: "sip URI with user and parameters",
487+
req: &TransferSIPParticipantRequest{
488+
RoomName: "room1",
489+
ParticipantIdentity: "participant1",
490+
TransferTo: "sip:[email protected],123",
491+
},
492+
expectError: false,
493+
expectedURI: "<sip:[email protected],123>",
494+
},
495+
{
496+
name: "sip URI with multiple parameters",
497+
req: &TransferSIPParticipantRequest{
498+
RoomName: "room1",
499+
ParticipantIdentity: "participant1",
500+
TransferTo: "sip:[email protected];transport=tcp;lr;ttl=60",
501+
},
502+
expectError: false,
503+
expectedURI: "<sip:[email protected];transport=tcp;lr;ttl=60>",
504+
},
505+
{
506+
name: "sip URI with port",
507+
req: &TransferSIPParticipantRequest{
508+
RoomName: "room1",
509+
ParticipantIdentity: "participant1",
510+
TransferTo: "sip:[email protected]:5060",
511+
},
512+
expectError: false,
513+
expectedURI: "<sip:[email protected]:5060>",
514+
},
515+
{
516+
name: "tel URI with extension",
517+
req: &TransferSIPParticipantRequest{
518+
RoomName: "room1",
519+
ParticipantIdentity: "participant1",
520+
TransferTo: "tel:+1234567890;ext=123",
521+
},
522+
expectError: false,
523+
expectedURI: "<tel:+1234567890;ext=123>",
524+
},
525+
{
526+
name: "tel URI with parameters",
527+
req: &TransferSIPParticipantRequest{
528+
RoomName: "room1",
529+
ParticipantIdentity: "participant1",
530+
TransferTo: "tel:+1234567890;phone-context=example.com",
531+
},
532+
expectError: false,
533+
expectedURI: "<tel:+1234567890;phone-context=example.com>",
534+
},
535+
{
536+
name: "sip URI with headers",
537+
req: &TransferSIPParticipantRequest{
538+
RoomName: "room1",
539+
ParticipantIdentity: "participant1",
540+
TransferTo: "sip:[email protected]?subject=test&priority=urgent",
541+
},
542+
expectError: false,
543+
expectedURI: "<sip:[email protected]?subject=test&priority=urgent>",
544+
},
545+
}
546+
547+
for _, c := range cases {
548+
t.Run(c.name, func(t *testing.T) {
549+
err := c.req.Validate()
550+
if c.expectError {
551+
require.Error(t, err, "Expected validation to fail")
552+
} else {
553+
require.NoError(t, err, "Expected validation to pass")
554+
require.Equal(t, c.expectedURI, c.req.TransferTo, "TransferTo should be RFC-compliant after validation")
555+
}
556+
})
557+
}
558+
}
559+
365560
func TestGRPCStatus(t *testing.T) {
366561
e := &SIPStatus{Code: SIPStatusCode_SIP_STATUS_BUSY_HERE}
367562
st, ok := status.FromError(e)

0 commit comments

Comments
 (0)