258 lines
11 KiB
Go
258 lines
11 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package livekit
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Valid Header Test Cases
|
|
|
|
// ValidHeaderNames contains valid SIP header names
|
|
var ValidHeaderNames = []string{
|
|
"Q", // single uppercase
|
|
"q", // single lowercase
|
|
"Qrom", // keyword
|
|
"qrom", // keyword
|
|
"Qall-ID", // hyphenated keyword
|
|
"P-Asserted-Identity", // multiple hyphens
|
|
"X-", // hyphen at end
|
|
"-X", // hyphen at start
|
|
"X123", // alphanumeric
|
|
"X_123", // underscore
|
|
"X.123", // period
|
|
"X!123", // exclamation
|
|
"X%123", // percent
|
|
"X*123", // asterisk
|
|
"X+123", // plus
|
|
"X`123", // backtick
|
|
"X'123", // single quote
|
|
"X~123", // tilde
|
|
}
|
|
|
|
// InvalidHeaderNames contains invalid SIP header names
|
|
var InvalidHeaderNames = []string{
|
|
"", // empty
|
|
"From To", // space in name
|
|
"From:To", // colon in name
|
|
"From,To", // comma in name
|
|
"From;To", // semicolon in name
|
|
"From<To", // angle bracket in name
|
|
"From>To", // angle bracket in name
|
|
"From@To", // at symbol in name
|
|
"From\"To", // quote in name
|
|
"From\\To", // backslash in name
|
|
"From/To", // forward slash
|
|
"From[To", // square bracket
|
|
"From]To", // square bracket
|
|
"From{To", // curly brace
|
|
"From}To", // curly brace
|
|
"From(To", // parenthesis
|
|
"From)To", // parenthesis
|
|
"From?To", // question mark
|
|
"From=To", // equals sign
|
|
"From#To", // hash
|
|
"From$To", // dollar sign
|
|
"From&To", // ampersand
|
|
"From|To", // pipe
|
|
"From^To", // caret
|
|
"From\000To", // null byte
|
|
"From\nTo", // newline
|
|
"From\rTo", // carriage return
|
|
"From\tTo", // tab
|
|
}
|
|
|
|
// ValidHeaderValues contains valid SIP header values (implementation-specific restrictions)
|
|
// Note: These restrictions are NOT in RFC 3261 but are applied for security/performance
|
|
var ValidHeaderValues = []string{
|
|
"", // empty
|
|
"u1@example.com", // basic email
|
|
"<sip:u2@example.com>", // SIP URI with brackets
|
|
"Alice <sip:u3@example.com>", // display name + URI
|
|
"\"Alice Smith\" <sip:u4@example.com>", // quoted display name
|
|
"SIP/2.0/UDP 192.168.1.1:5060", // Via header
|
|
"1 INVITE", // CSeq header
|
|
"255", // Max-Forwards (max valid)
|
|
"0", // Max-Forwards (min valid)
|
|
"application/sdp", // Content-Type
|
|
"123", // Content-Length
|
|
"3600", // Expires
|
|
"call-123@example.com", // Call-ID
|
|
"text/plain; charset=utf-8", // Content-Type with params
|
|
"<sip:u5@[2001:db8::1]:5060>", // IPv6 URI
|
|
"\"Alice & Bob\" <sip:u6@example.com>", // display name with & symbol
|
|
"Header with\ttab", // tab (HTAB) per RFC 3261 Section 25.1
|
|
"Header with unicode café", // Unicode
|
|
"Header with unicode 世界", // Unicode
|
|
"Header with unicode émojis 🎉", // Unicode with emojis
|
|
strings.Repeat("a", 1024), // max length
|
|
}
|
|
|
|
// Note: These restrictions are NOT in RFC 3261 but are applied for security/performance
|
|
var InvalidHeaderValues = []string{
|
|
"Header with\nnewline", // newline
|
|
"Header with\rreturn", // carriage return
|
|
"Header with\x00null", // null byte
|
|
"Header with\x01control", // control character
|
|
"Header with\x1Funit separator", // control character
|
|
"Header with\x7Fdelete", // delete character
|
|
strings.Repeat("a", 1025), // too long
|
|
}
|
|
|
|
// testCaseName truncates a test case name to maxLen and adds dots with total size
|
|
func testCaseName(name string, maxLen int, index int) string {
|
|
if len(name) <= maxLen {
|
|
return fmt.Sprintf("%d/%s)", index+1, name)
|
|
}
|
|
// Truncate to make room for "..." and size info
|
|
truncated := name[:maxLen-10] // Reserve space for "..." and "(1234)"
|
|
return fmt.Sprintf("%d/%s...(%d)", index+1, truncated, len(name))
|
|
}
|
|
|
|
// ValidNameAddrHeaders contains valid Name-addr format headers with parameters
|
|
var ValidNameAddrHeaders = []string{
|
|
`"Alice Johnson" <sip:u1@example.com>`,
|
|
`"Alice \"Ace\" Johnson's device\\" <sip:u2@example.com>`,
|
|
`Alice Johnson <sip:u3@example.com>`,
|
|
`sip:u4@example.com`, // basic SIP URI (no brackets needed)
|
|
`sips:u5@example.com`, // secure SIP URI (no brackets needed)
|
|
`tel:+1-555-123-4567`, // TEL URI (no brackets needed)
|
|
`<sip:u6@example.com>`, // basic SIP URI with brackets
|
|
`<sips:u7@example.com>`, // secure SIP URI with brackets
|
|
`<tel:+1-555-123-4567>`, // TEL URI with brackets
|
|
`Alice <sip:u8@example.com>`, // display name + SIP URI
|
|
`"Alice Johnson" <sip:u9@example.com>`, // quoted display name
|
|
`<sip:u10@example.com;transport=tcp>`, // SIP URI with transport
|
|
`<sip:u11@example.com;lr>`, // SIP URI with flag param
|
|
`<sip:u12@example.com:5060>`, // SIP URI with port
|
|
`<sip:u13@example.com;transport=tcp;lr>`, // SIP URI with multiple params
|
|
`Alice <sip:u14@example.com;transport=tcp>`, // display name + params
|
|
`"Alice \"Ace\"" <sip:u15@example.com>`, // quoted display
|
|
`<sip:u16@[2001:db8::1]:5060>`, // IPv6 with params
|
|
`<sips:u17@192.0.2.4>;expires=60`, // SIPS URI with expires parameter
|
|
`Alice <sip:u18@example.com;transport=tcp>`, // display name + params
|
|
`"Alice & Bob" <sip:u19@example.com>`, // display name with & symbol
|
|
}
|
|
|
|
// InvalidNameAddrHeaders contains invalid Name-addr format headers
|
|
var InvalidNameAddrHeaders = []string{
|
|
`"Alice "Ace" Johnson" <sip:u1@example.com>`, // unescaped quotes
|
|
`"\Alice" <sip:u2@example.com>`, // unescaped backslashes
|
|
`"Alice" Johnson <sip:u3@example.com>`, // unmatched quotes
|
|
`Alice "Ace" Johnson <sip:u4@example.com>`, // unescaped quotes in unquoted
|
|
`"Alice Johnson <sip:u5@example.com>`, // unterminated quote
|
|
`Alice Johnson" <sip:u6@example.com>`, // unmatched quote
|
|
`<sip:u7@example.com`, // missing closing bracket
|
|
`sip:u8@example.com>`, // missing opening bracket
|
|
`<sip:u9@example.com> <sip:u10@example.com>`, // multiple URIs
|
|
`Alice <sip:u11@example.com> <sip:u12@example.com>`, // multiple URIs with display
|
|
`Alice sip:u13@example.com`, // display name without brackets
|
|
`Alice sips:u14@example.com`, // display name without brackets
|
|
`Alice & Bob <sip:u15@example.com>`, // display name with & symbol
|
|
`sip:u16@example.com;transport=tcp`, // special chars without brackets
|
|
`sip:u17@example.com,transport=tcp`, // comma without brackets
|
|
`sip:u18@example.com?transport=tcp`, // question mark without brackets
|
|
`<sip:u21@example.com;transport tcp>`, // missing equals sign
|
|
`<sip:u24@example.com;transport=tcp lr>`, // space in parameters
|
|
}
|
|
|
|
// TestValidateHeaderName_ValidHeaders tests that all valid header names pass validation
|
|
func TestValidateHeaderName_ValidHeaders(t *testing.T) {
|
|
for i, headerName := range ValidHeaderNames {
|
|
t.Run(testCaseName(headerName, 32, i), func(t *testing.T) {
|
|
err := ValidateHeaderName(headerName, true)
|
|
if err != nil {
|
|
t.Errorf("ValidateHeaderName(%q) = %v, want nil", headerName, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateHeaderName_InvalidHeaders tests that all invalid header names fail validation
|
|
func TestValidateHeaderName_InvalidHeaders(t *testing.T) {
|
|
for i, headerName := range InvalidHeaderNames {
|
|
t.Run(testCaseName(headerName, 32, i), func(t *testing.T) {
|
|
err := ValidateHeaderName(headerName, true)
|
|
if err == nil {
|
|
t.Errorf("ValidateHeaderName(%q) = nil, want error", headerName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateHeaderValue_ValidValues tests that all valid header values pass validation
|
|
func TestValidateHeaderValue_ValidValues(t *testing.T) {
|
|
for i, headerValue := range ValidHeaderValues {
|
|
t.Run(testCaseName(headerValue, 32, i), func(t *testing.T) {
|
|
err := ValidateHeaderValue("Test-Header", headerValue)
|
|
if err != nil {
|
|
t.Errorf("ValidateHeaderValue(%q) = %v, want nil", headerValue, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateHeaderValue_InvalidValues tests that all invalid header values fail validation
|
|
// Note: These restrictions are implementation-specific, NOT from RFC 3261
|
|
func TestValidateHeaderValue_InvalidValues(t *testing.T) {
|
|
for i, headerValue := range InvalidHeaderValues {
|
|
t.Run(testCaseName(headerValue, 32, i), func(t *testing.T) {
|
|
err := ValidateHeaderValue("Test-Header", headerValue)
|
|
if err == nil {
|
|
t.Errorf("ValidateHeaderValue(%q) = nil, want error", headerValue)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateNameAddr_ValidHeaders tests that all valid Name-addr headers pass validation
|
|
func TestValidateNameAddr_ValidHeaders(t *testing.T) {
|
|
for i, nameAddr := range ValidNameAddrHeaders {
|
|
t.Run(testCaseName(nameAddr, 32, i), func(t *testing.T) {
|
|
err := validateNameAddrHeader(nameAddr)
|
|
if err != nil {
|
|
t.Errorf("validateNameAddrHeader(%q) = %v, want nil", nameAddr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateNameAddr_InvalidHeaders tests that all invalid Name-addr headers fail validation
|
|
func TestValidateNameAddr_InvalidHeaders(t *testing.T) {
|
|
for i, nameAddr := range InvalidNameAddrHeaders {
|
|
t.Run(testCaseName(nameAddr, 32, i), func(t *testing.T) {
|
|
err := validateNameAddrHeader(nameAddr)
|
|
if err == nil {
|
|
t.Errorf("validateNameAddrHeader(%q) = nil, want error", nameAddr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFrobiddenSipHeaderNames(t *testing.T) {
|
|
i := 0
|
|
for name := range FrobiddenSipHeaderNames {
|
|
i++
|
|
t.Run(testCaseName(name, 32, i), func(t *testing.T) {
|
|
err := ValidateHeaderName(name, true)
|
|
if err == nil {
|
|
t.Errorf("ValidateHeaderName(%q) = nil, want error", name)
|
|
}
|
|
})
|
|
}
|
|
}
|