Files
djk/internal/app/ui_request_origin_test.go

101 lines
4.6 KiB
Go

package app
import (
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"strings"
"testing"
"time"
)
func TestPlaybackRequestOriginPolicy(t *testing.T) {
for _, test := range []struct {
name, method, host, site, origin, forwarded string
want int
}{
{"proxy same origin", "POST", "127.0.0.1:8998", "same-origin", "https://library.example.invalid:9443", "", 204},
{"direct navigation", "POST", "127.0.0.1:8998", "none", "https://library.example.invalid", "", 204},
{"direct older browser", "POST", "library.example.invalid:8998", "", "http://library.example.invalid:8998", "", 204},
{"LAN IP direct", "POST", "192.168.1.5:8998", "", "http://192.168.1.5:8998", "", 204},
{"LAN IP proxy same origin", "POST", "127.0.0.1:8998", "same-origin", "http://192.168.1.5:8998", "", 204},
{"default https port", "POST", "library.example.invalid:443", "", "https://library.example.invalid", "", 204},
{"default http port", "POST", "library.example.invalid:80", "", "http://library.example.invalid", "", 204},
{"ipv6 default port", "POST", "[::1]:80", "", "http://[::1]", "", 204},
{"case insensitive host", "POST", "LIBRARY.EXAMPLE.INVALID:8998", "", "http://library.example.invalid:8998", "", 204},
{"API client", "POST", "127.0.0.1:8998", "", "", "", 204},
{"cross site", "POST", "library.example.invalid", "cross-site", "https://library.example.invalid", "", 403},
{"same site is not same origin", "POST", "library.example.invalid", "same-site", "https://library.example.invalid", "", 403},
{"unrecognized fetch metadata", "POST", "library.example.invalid", "unrecognized", "https://library.example.invalid", "", 403},
{"foreign older browser", "POST", "library.example.invalid", "", "https://other.example.invalid", "", 403},
{"untrusted forwarded host", "POST", "library.example.invalid", "", "https://other.example.invalid", "other.example.invalid", 403},
{"different port", "POST", "library.example.invalid:8998", "", "http://library.example.invalid:8999", "", 403},
{"default port wrong scheme", "POST", "library.example.invalid:443", "", "http://library.example.invalid", "", 403},
{"opaque origin", "POST", "library.example.invalid", "", "null", "", 403},
{"credentials in origin", "POST", "library.example.invalid", "", "https://user@library.example.invalid", "", 403},
{"wrong method", "GET", "127.0.0.1:8998", "same-origin", "", "", 405},
} {
t.Run(test.name, func(t *testing.T) {
request := httptest.NewRequest(test.method, "http://localhost/api/ui/playback/open", nil)
request.Host = test.host
request.Header.Set("Sec-Fetch-Site", test.site)
request.Header.Set("Origin", test.origin)
request.Header.Set("X-Forwarded-Host", test.forwarded)
request.Header.Set("Forwarded", "host="+test.forwarded+";proto=https")
writer := httptest.NewRecorder()
if playbackRequestAllowed(writer, request, http.MethodPost) {
writer.WriteHeader(http.StatusNoContent)
}
if writer.Code != test.want {
t.Fatalf("got %d, want %d: %s", writer.Code, test.want, writer.Body.String())
}
})
}
}
func TestPlaybackRequestThroughHostRewritingProxy(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
var input struct {
DramaID string `json:"dramaId"`
}
if !readPlaybackRequest(writer, request, &input) {
return
}
writer.WriteHeader(http.StatusNoContent)
}))
defer upstream.Close()
target, _ := url.Parse(upstream.URL)
proxy := httputil.NewSingleHostReverseProxy(target)
director := proxy.Director
proxy.Director = func(request *http.Request) {
director(request)
request.Header.Set("X-Forwarded-Host", request.Host)
request.Host = target.Host
}
public := httptest.NewServer(proxy)
defer public.Close()
client := &http.Client{Transport: &http.Transport{Proxy: nil}, Timeout: 3 * time.Second}
defer client.CloseIdleConnections()
for _, site := range []string{"same-origin", "cross-site", "same-site"} {
request, _ := http.NewRequest(http.MethodPost, public.URL+"/api/ui/playback/open", strings.NewReader(`{"dramaId":"hongguo:7000000000000000001"}`))
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Origin", "https://library.example.invalid:9443")
request.Header.Set("Sec-Fetch-Site", site)
response, err := client.Do(request)
if err != nil {
t.Fatal(err)
}
body, _ := io.ReadAll(response.Body)
response.Body.Close()
want := http.StatusForbidden
if site == "same-origin" {
want = http.StatusNoContent
}
if response.StatusCode != want {
t.Fatalf("proxy %s got %d, want %d: %s", site, response.StatusCode, want, body)
}
}
}