adjust code to prevent security scan false positives (#5963)
... about string escaping.
This commit is contained in:
@@ -1,23 +1,58 @@
|
||||
package whip
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
var linkHeaderCredentialReplacer = strings.NewReplacer(
|
||||
`\`, `\\`,
|
||||
`"`, `\"`,
|
||||
)
|
||||
|
||||
func quoteCredential(v string) string {
|
||||
b, _ := json.Marshal(v)
|
||||
s := string(b)
|
||||
return s[1 : len(s)-1]
|
||||
return linkHeaderCredentialReplacer.Replace(v)
|
||||
}
|
||||
|
||||
func unquoteCredential(v string) string {
|
||||
var s string
|
||||
json.Unmarshal([]byte("\""+v+"\""), &s) //nolint:errcheck
|
||||
return s
|
||||
func readQuotedCredential(v string) (string, string, bool) {
|
||||
if len(v) == 0 || v[0] != '"' {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
var ret strings.Builder
|
||||
ret.Grow(len(v) - 1)
|
||||
|
||||
escaped := false
|
||||
|
||||
for i := 1; i < len(v); i++ {
|
||||
switch v[i] {
|
||||
case '\\':
|
||||
if escaped {
|
||||
ret.WriteByte('\\')
|
||||
escaped = false
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
|
||||
case '"':
|
||||
if escaped {
|
||||
ret.WriteByte('"')
|
||||
escaped = false
|
||||
} else {
|
||||
return ret.String(), v[i+1:], true
|
||||
}
|
||||
|
||||
default:
|
||||
if escaped {
|
||||
return "", "", false
|
||||
}
|
||||
ret.WriteByte(v[i])
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// LinkHeaderMarshal encodes a link header.
|
||||
@@ -25,37 +60,73 @@ func LinkHeaderMarshal(iceServers []webrtc.ICEServer) []string {
|
||||
ret := make([]string, len(iceServers))
|
||||
|
||||
for i, server := range iceServers {
|
||||
link := "<" + server.URLs[0] + ">; rel=\"ice-server\""
|
||||
var link strings.Builder
|
||||
|
||||
link.WriteByte('<')
|
||||
link.WriteString(server.URLs[0])
|
||||
link.WriteString(`>; rel="ice-server"`)
|
||||
|
||||
if server.Username != "" {
|
||||
link += "; username=\"" + quoteCredential(server.Username) + "\"" +
|
||||
"; credential=\"" + quoteCredential(server.Credential.(string)) + "\"; credential-type=\"password\""
|
||||
link.WriteString(`; username="`)
|
||||
link.WriteString(quoteCredential(server.Username))
|
||||
link.WriteString(`"; credential="`)
|
||||
link.WriteString(quoteCredential(server.Credential.(string)))
|
||||
link.WriteString(`"; credential-type="password"`)
|
||||
}
|
||||
ret[i] = link
|
||||
|
||||
ret[i] = link.String()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
var reLink = regexp.MustCompile(`^<(.+?)>; rel="ice-server"(; username="(.+?)"` +
|
||||
`; credential="(.+?)"; credential-type="password")?`)
|
||||
|
||||
// LinkHeaderUnmarshal decodes a link header.
|
||||
func LinkHeaderUnmarshal(link []string) ([]webrtc.ICEServer, error) {
|
||||
ret := make([]webrtc.ICEServer, len(link))
|
||||
|
||||
for i, li := range link {
|
||||
m := reLink.FindStringSubmatch(li)
|
||||
if m == nil {
|
||||
var ok bool
|
||||
li, ok = strings.CutPrefix(li, "<")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid link header: '%s'", li)
|
||||
}
|
||||
|
||||
s := webrtc.ICEServer{
|
||||
URLs: []string{m[1]},
|
||||
var url string
|
||||
url, li, ok = strings.Cut(li, `>; rel="ice-server"`)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid link header: '<%s'", li)
|
||||
}
|
||||
|
||||
if m[3] != "" {
|
||||
s.Username = unquoteCredential(m[3])
|
||||
s.Credential = unquoteCredential(m[4])
|
||||
s := webrtc.ICEServer{
|
||||
URLs: []string{url},
|
||||
}
|
||||
|
||||
if li != "" {
|
||||
li, ok = strings.CutPrefix(li, `; username=`)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid link header: '<%s'", li)
|
||||
}
|
||||
|
||||
s.Username, li, ok = readQuotedCredential(li)
|
||||
if !ok || s.Username == "" {
|
||||
return nil, fmt.Errorf("invalid link header: '<%s'", li)
|
||||
}
|
||||
|
||||
li, ok = strings.CutPrefix(li, `; credential=`)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid link header: '<%s'", li)
|
||||
}
|
||||
|
||||
s.Credential, li, ok = readQuotedCredential(li)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid link header: '<%s'", li)
|
||||
}
|
||||
|
||||
li, ok = strings.CutPrefix(li, `; credential-type="password"`)
|
||||
if !ok || li != "" {
|
||||
return nil, fmt.Errorf("invalid link header: '<%s'", li)
|
||||
}
|
||||
|
||||
s.CredentialType = webrtc.ICECredentialTypePassword
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,20 @@ var linkHeaderCases = []struct {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"slashes-and-quotes",
|
||||
[]string{
|
||||
`<turns:turn.example.com>; rel="ice-server"; username="my\\user\"a"; ` +
|
||||
`credential="my\\pwd\"b"; credential-type="password"`,
|
||||
},
|
||||
[]webrtc.ICEServer{
|
||||
{
|
||||
URLs: []string{"turns:turn.example.com"},
|
||||
Username: "my\\user\"a",
|
||||
Credential: "my\\pwd\"b",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLinkHeaderUnmarshal(t *testing.T) {
|
||||
@@ -50,3 +64,30 @@ func TestLinkHeaderMarshal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkHeaderUnmarshalInvalid(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
enc []string
|
||||
}{
|
||||
{
|
||||
"invalid escape in username",
|
||||
[]string{
|
||||
`<turns:turn.example.com>; rel="ice-server"; username="my\nuser"; ` +
|
||||
`credential="mypwd"; credential-type="password"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
"truncated escape in credential",
|
||||
[]string{
|
||||
`<turns:turn.example.com>; rel="ice-server"; username="myuser"; ` +
|
||||
`credential="mypwd\"; credential-type="password"`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
_, err := LinkHeaderUnmarshal(ca.enc)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user