// 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 "FromTo", // 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 URI with brackets "Alice ", // display name + URI "\"Alice Smith\" ", // 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 "", // IPv6 URI "\"Alice & Bob\" ", // 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" `, `"Alice \"Ace\" Johnson's device\\" `, `Alice Johnson `, `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) ``, // basic SIP URI with brackets ``, // secure SIP URI with brackets ``, // TEL URI with brackets `Alice `, // display name + SIP URI `"Alice Johnson" `, // quoted display name ``, // SIP URI with transport ``, // SIP URI with flag param ``, // SIP URI with port ``, // SIP URI with multiple params `Alice `, // display name + params `"Alice \"Ace\"" `, // quoted display ``, // IPv6 with params `;expires=60`, // SIPS URI with expires parameter `Alice `, // display name + params `"Alice & Bob" `, // display name with & symbol } // InvalidNameAddrHeaders contains invalid Name-addr format headers var InvalidNameAddrHeaders = []string{ `"Alice "Ace" Johnson" `, // unescaped quotes `"\Alice" `, // unescaped backslashes `"Alice" Johnson `, // unmatched quotes `Alice "Ace" Johnson `, // unescaped quotes in unquoted `"Alice Johnson `, // unterminated quote `Alice Johnson" `, // unmatched quote ``, // missing opening bracket ` `, // multiple URIs `Alice `, // multiple URIs with display `Alice sip:u13@example.com`, // display name without brackets `Alice sips:u14@example.com`, // display name without brackets `Alice & Bob `, // 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 ``, // missing equals sign ``, // 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) } }) } }