diff --git a/server/mediaserver.go b/server/mediaserver.go index f2e8fc8b56..16822b7019 100644 --- a/server/mediaserver.go +++ b/server/mediaserver.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "math/big" "mime/multipart" + "net" "net/http" "net/textproto" "net/url" @@ -1660,7 +1661,16 @@ func getRemoteAddr(r *http.Request) string { if proxiedAddr := r.Header.Get("X-Forwarded-For"); proxiedAddr != "" { addr = strings.Split(proxiedAddr, ",")[0] } - return strings.Split(addr, ":")[0] + + // addr is typically in the format "ip:port" + // Need to extract just the IP. Handle IPv6 too. + host, _, err := net.SplitHostPort(strings.TrimSpace(addr)) + if err != nil { + // probably not a real IP + return addr + } + + return host } func mediaCompatible(a, b ffmpeg.MediaFormatInfo) bool { diff --git a/server/mediaserver_test.go b/server/mediaserver_test.go index 70c86759f6..1feca8b512 100644 --- a/server/mediaserver_test.go +++ b/server/mediaserver_test.go @@ -1577,3 +1577,49 @@ func mustParseUrl(t *testing.T, str string) *url.URL { } return url } + +func TestGetRemoteAddr(t *testing.T) { + type tc struct { + name string + remoteAddr string // r.RemoteAddr + xff string // "" = header not present + want string + } + + cases := []tc{ + // --- No X-Forwarded-For (uses RemoteAddr) --- + {"remote IPv4 with port", "203.0.113.9:54321", "", "203.0.113.9"}, + {"remote hostname with port", "example.com:80", "", "example.com"}, + {"remote bracketed IPv6 with port", "[2001:db8::1]:443", "", "2001:db8::1"}, + {"remote IPv6 with zone", "[fe80::1%lo0]:1234", "", "fe80::1%lo0"}, + {"remote IPv4 no port (SplitHostPort error)", "192.0.2.10", "", "192.0.2.10"}, + {"remote bare IPv6 no port (SplitHostPort error)", "2001:db8::2", "", "2001:db8::2"}, + {"remote empty string", "", "", ""}, + {"remote malformed string", "1.2.3.4, nonsense", "", "1.2.3.4, nonsense"}, + + // --- X-Forwarded-For present (takes first comma-delimited token) --- + {"xff single IPv4 no port", "203.0.113.9:54321", "198.51.100.7", "198.51.100.7"}, + {"xff single IPv4 with port", "203.0.113.9:54321", "198.51.100.7:8080", "198.51.100.7"}, + {"xff single hostname with port", "203.0.113.9:54321", "edge.example.net:8443", "edge.example.net"}, + {"xff single bracketed IPv6 with port", "203.0.113.9:54321", "[2001:db8::7]:9443", "2001:db8::7"}, + {"xff IPv4-mapped IPv6 with port", "203.0.113.9:54321", "[::ffff:192.0.2.128]:12345", "::ffff:192.0.2.128"}, + {"xff multiple entries with port", "203.0.113.9:54321", "198.51.100.7:8080, 10.0.0.1:80", "198.51.100.7"}, + {"xff multiple entries no port", "203.0.113.9:54321", "198.51.100.7, 10.0.0.1", "198.51.100.7"}, + {"xff first entry empty", "203.0.113.9:54321", ", 198.51.100.7", ""}, + {"xff first entry has spaces", "203.0.113.9:54321", " \t\r198.51.100.7:8080 ", "198.51.100.7"}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + r := &http.Request{ + RemoteAddr: tt.remoteAddr, + Header: make(http.Header), + } + if tt.xff != "" { + r.Header.Set("X-Forwarded-For", tt.xff) + } + got := getRemoteAddr(r) + assert.Equal(t, tt.want, got) + }) + } +}