Files
djk/internal/app/ui_image_huangguo_test.go

159 lines
7.0 KiB
Go

package app
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"image"
"image/png"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync/atomic"
"testing"
)
func syntheticHuangguoCover(t *testing.T) ([]byte, []byte) {
t.Helper()
var output bytes.Buffer
if err := png.Encode(&output, image.NewRGBA(image.Rect(0, 0, 1, 1))); err != nil {
t.Fatal(err)
}
plain := output.Bytes()
padding := aes.BlockSize - len(plain)%aes.BlockSize
padded := append(append([]byte{}, plain...), bytes.Repeat([]byte{byte(padding)}, padding)...)
encrypted := make([]byte, len(padded))
block, err := aes.NewCipher([]byte("f5d965df75336270"))
if err != nil {
t.Fatal(err)
}
cipher.NewCBCEncrypter(block, []byte("97b60394abc2fbe1")).CryptBlocks(encrypted, padded)
return plain, encrypted
}
func TestHuangguoImageProxyAcceptsKnownCDNAliases(t *testing.T) {
plain, encrypted := syntheticHuangguoCover(t)
for _, host := range []string{"pic.zdmhyg.cn", "pic.tuafjz.cn", "PIC.TUAFJZ.CN", "new-covers.example.org"} {
for _, payload := range []struct {
name string
data []byte
}{{"plain", plain}, {"encrypted", encrypted}} {
t.Run(host+"/"+payload.name, func(t *testing.T) {
address := "https://" + host + "/upload/synthetic.jpg?auth_key=fixture-0-0-test&signature=%2F%2b%3D"
var calls atomic.Int32
downloader := rankingTestDownloader(t, func(request *http.Request) (*http.Response, error) {
calls.Add(1)
if request.URL.String() != address || request.Header.Get("Referer") != "https://huangguoai.com/" || request.Header.Get("User-Agent") == "" {
t.Error("cover request changed the signed address or omitted source headers")
}
return &http.Response{StatusCode: http.StatusOK, Header: http.Header{}, Body: io.NopCloser(bytes.NewReader(payload.data)), Request: request}, nil
})
app := &UIApp{downloader: downloader, cfg: downloader.cfg, dramas: []Drama{{ID: "huangguoai:501", Source: sourceHuangguoAI, CoverURL: address}}}
for range 2 {
request := httptest.NewRequest(http.MethodGet, "/api/ui/image?url="+url.QueryEscape(address), nil)
request = request.WithContext(withSourceScope(context.Background(), accountRecord{Sources: []string{"huangguo"}}))
writer := httptest.NewRecorder()
app.handleImage(writer, request)
if writer.Code != http.StatusOK || !bytes.Equal(writer.Body.Bytes(), plain) || writer.Header().Get("Content-Type") != "image/png" {
t.Fatalf("known Huangguo cover was rejected or decoded incorrectly: status=%d body=%q", writer.Code, writer.Body.String())
}
if writer.Header().Get("Cache-Control") != "private, no-store" {
t.Fatal("restricted-source cover became publicly cacheable")
}
}
if calls.Load() != 1 {
t.Fatal("repeat cover requests bypassed the cache", calls.Load())
}
if host != "new-covers.example.org" && !protectedCDNHost(host) {
t.Fatal("known cover CDN lost its existing DNS and proxy fallback")
}
})
}
}
}
func TestHuangguoImageProxyValidatesRedirects(t *testing.T) {
plain, _ := syntheticHuangguoCover(t)
for _, test := range []struct {
target string
status int
calls int32
}{
{"https://pic.tuafjz.cn/upload/synthetic.jpg?auth_key=fixture", http.StatusOK, 2},
{"https://new-covers.example.org/cover", http.StatusOK, 2},
{"https://pic.tuafjz.cn.example.invalid/cover", http.StatusBadGateway, 1},
{"https://127.0.0.1/cover", http.StatusBadGateway, 1},
} {
t.Run(test.target, func(t *testing.T) {
var calls atomic.Int32
downloader := rankingTestDownloader(t, func(request *http.Request) (*http.Response, error) {
count := calls.Add(1)
if count == 1 {
return &http.Response{StatusCode: http.StatusFound, Header: http.Header{"Location": {test.target}}, Body: io.NopCloser(strings.NewReader("")), Request: request}, nil
}
if test.calls != 2 || request.URL.String() != test.target || request.Header.Get("Referer") != "https://huangguoai.com/" {
t.Error("redirect requested a forbidden host or lost the correct referer")
}
return &http.Response{StatusCode: http.StatusOK, Header: http.Header{}, Body: io.NopCloser(bytes.NewReader(plain)), Request: request}, nil
})
app := &UIApp{downloader: downloader, cfg: downloader.cfg, dramas: []Drama{{ID: "huangguoai:501", Source: sourceHuangguoAI, CoverURL: "https://pic.zdmhyg.cn/upload/synthetic.jpg"}}}
request := httptest.NewRequest(http.MethodGet, "/api/ui/image?url="+url.QueryEscape("https://pic.zdmhyg.cn/upload/synthetic.jpg"), nil)
writer := httptest.NewRecorder()
app.handleImage(writer, request)
if writer.Code != test.status || calls.Load() != test.calls {
t.Fatalf("unexpected redirect result: status=%d calls=%d", writer.Code, calls.Load())
}
})
}
}
func TestHuangguoImageProxyRetainsURLAndSourceRestrictions(t *testing.T) {
var calls atomic.Int32
downloader := rankingTestDownloader(t, func(request *http.Request) (*http.Response, error) {
calls.Add(1)
return rankingHTTPResponse(request, http.StatusOK, "forbidden request"), nil
})
address := "https://pic.tuafjz.cn/upload/synthetic.jpg?auth_key=fixture"
app := &UIApp{downloader: downloader, cfg: downloader.cfg, dramas: []Drama{{ID: "huangguoai:501", Source: sourceHuangguoAI, CoverURL: address}}}
for _, target := range []string{
"https://pic.tuafjz.cn.example.invalid/cover",
"https://pic.tuafjz.cn@127.0.0.1/cover", "https://user@pic.tuafjz.cn/cover", "https://pic.tuafjz.cn:8443/cover",
"http://pic.tuafjz.cn/cover", "https://127.0.0.1/cover", "https://[::1]/cover",
} {
writer := httptest.NewRecorder()
app.handleImage(writer, httptest.NewRequest(http.MethodGet, "/api/ui/image?url="+url.QueryEscape(target), nil))
if writer.Code != http.StatusBadRequest {
t.Errorf("unsupported image URL was accepted: %s (%d)", target, writer.Code)
}
}
for _, target := range []string{"https://tuafjz.cn/cover", "https://other.tuafjz.cn/cover", "https://new-covers.example.org/unregistered"} {
writer := httptest.NewRecorder()
app.handleImage(writer, httptest.NewRequest(http.MethodGet, "/api/ui/image?url="+url.QueryEscape(target), nil))
if writer.Code != http.StatusForbidden {
t.Errorf("unregistered cover was accepted: %s (%d)", target, writer.Code)
}
}
writer := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "/api/ui/image?url="+url.QueryEscape(address), nil)
request = request.WithContext(withSourceScope(context.Background(), accountRecord{Sources: []string{sourceHongguo}}))
app.handleImage(writer, request)
if writer.Code != http.StatusForbidden || calls.Load() != 0 {
t.Fatalf("source-restricted request was not denied before fetching: status=%d calls=%d", writer.Code, calls.Load())
}
}
func TestCoverRefererPreservesHuangdouMirrors(t *testing.T) {
for address, want := range map[string]string{
"https://tideember.cc/cover": "https://tideember.cc/home",
"https://xqjurgek.top/cover": "https://xqjurgek.top/home",
"https://new-covers.example.org/cover": huangdouBaseURL + "/home",
} {
if referer := sourceCoverReferer(sourceHuangdou, address); referer != want {
t.Fatal(address, referer, want)
}
}
}