🏡 index : quine-global/quintodrome.git

package artwork

import (
	"bytes"
	"errors"
	"io"
	"io/fs"
	"net/http"
	"net/http/httptest"
	"net/netip"
	"net/url"
	"os"
	"strings"
	"sync/atomic"
	"testing/fstest"
	"time"

	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/utils/httpclient"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("fromURL", func() {
	var (
		hits   atomic.Int32
		target *httptest.Server
	)

	BeforeEach(func() {
		hits.Store(0)
		target = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			hits.Add(1)
			_, _ = w.Write([]byte("image-bytes"))
		}))
		DeferCleanup(target.Close)
	})

	useClient := func(c *http.Client) {
		prev := remoteImageClient
		remoteImageClient = c
		DeferCleanup(func() { remoteImageClient = prev })
	}
	fetch := func(rawURL string) ([]byte, error) {
		u, err := url.Parse(rawURL)
		Expect(err).ToNot(HaveOccurred())
		r, _, err := fromURL(GinkgoT().Context(), u)
		if err != nil {
			return nil, err
		}
		defer r.Close()
		return io.ReadAll(r)
	}
	// Stand-in for a public host: only 127.0.0.1 is allowed, so every other private address stays refused.
	onlyLocalhostV4 := func() *http.Client {
		return httpclient.NewExternal(5*time.Second, netip.MustParsePrefix("127.0.0.1/32"))
	}

	DescribeTable("refuses private and loopback targets as a definitive miss",
		func(rawURL string) {
			useClient(productionImageClient)
			u, _ := url.Parse(target.URL)
			_, err := fetch(strings.ReplaceAll(rawURL, "PORT", u.Port()))
			Expect(err).To(MatchError(model.ErrNotFound))
			Expect(hits.Load()).To(BeZero())
		},
		Entry("IPv4 loopback", "http://127.0.0.1:PORT/x"),
		Entry("localhost", "http://localhost:PORT/x"),
		Entry("cloud metadata", "http://169.254.169.254/"),
		Entry("IPv6 loopback", "http://[::1]/"),
	)

	It("refuses a redirect from an allowed host to a loopback address", func() {
		useClient(onlyLocalhostV4())
		redirector := httptest.NewServer(http.RedirectHandler(strings.Replace(target.URL, "127.0.0.1", "127.0.0.2", 1), http.StatusFound))
		DeferCleanup(redirector.Close)

		_, err := fetch(redirector.URL)
		Expect(err).To(MatchError(model.ErrNotFound))
		Expect(hits.Load()).To(BeZero())
	})

	It("fetches from an allowed address", func() {
		useClient(onlyLocalhostV4())
		Expect(fetch(target.URL + "/cover.jpg")).To(Equal([]byte("image-bytes")))
	})
})

var _ = Describe("fromExternalFile", func() {
	It("opens a matching file via the library FS", func() {
		fsys := fstest.MapFS{
			"Artist/Album/cover.jpg": &fstest.MapFile{Data: []byte("cover-bytes")},
		}
		f := fromExternalFile(GinkgoT().Context(), fsys, []string{"Artist/Album/cover.jpg"}, "cover.*")
		r, path, err := f()
		Expect(err).ToNot(HaveOccurred())
		defer r.Close()
		b, _ := io.ReadAll(r)
		Expect(b).To(Equal([]byte("cover-bytes")))
		Expect(path).To(Equal("Artist/Album/cover.jpg"))
	})

	It("returns an error when no file matches", func() {
		fsys := fstest.MapFS{
			"Artist/Album/something.txt": &fstest.MapFile{Data: []byte("x")},
		}
		f := fromExternalFile(GinkgoT().Context(), fsys, []string{"Artist/Album/something.txt"}, "cover.*")
		_, _, err := f()
		Expect(err).To(HaveOccurred())
	})

	It("skips files that fail to open and tries the next match", func() {
		fsys := fstest.MapFS{
			"a/cover.jpg": &fstest.MapFile{Data: []byte("a")},
		}
		// "missing/cover.jpg" is in candidates but not in the FS — should be skipped.
		f := fromExternalFile(GinkgoT().Context(), fsys, []string{"missing/cover.jpg", "a/cover.jpg"}, "cover.*")
		r, path, err := f()
		Expect(err).ToNot(HaveOccurred())
		defer r.Close()
		b, _ := io.ReadAll(r)
		Expect(b).To(Equal([]byte("a")))
		Expect(path).To(Equal("a/cover.jpg"))
	})

	It("skips a matching file that is not an image", func() {
		fsys := fstest.MapFS{
			"a/cover.ini": &fstest.MapFile{Data: []byte("password=secret")},
			"a/cover.jpg": &fstest.MapFile{Data: []byte("a")},
		}
		f := fromExternalFile(GinkgoT().Context(), fsys, []string{"a/cover.ini", "a/cover.jpg"}, "cover.*")
		r, path, err := f()
		Expect(err).ToNot(HaveOccurred())
		defer r.Close()
		Expect(path).To(Equal("a/cover.jpg"))
	})
})

var _ = Describe("fromTag", func() {
	It("opens an embedded image via fs.FS", func() {
		fsys := os.DirFS("tests/fixtures/artist/an-album")
		f := fromTag(GinkgoT().Context(), fsys, "test.mp3")
		r, path, err := f()
		Expect(err).ToNot(HaveOccurred())
		defer r.Close()
		Expect(path).To(Equal("test.mp3"))
		b, _ := io.ReadAll(r)
		Expect(b).ToNot(BeEmpty())
	})

	It("returns nil reader when the relative path is empty", func() {
		f := fromTag(GinkgoT().Context(), os.DirFS("."), "")
		r, _, err := f()
		Expect(err).ToNot(HaveOccurred())
		Expect(r).To(BeNil())
	})

	It("errors when the FS file is not seekable", func() {
		fsys := nonSeekableFS{data: []byte("garbage")}
		f := fromTag(GinkgoT().Context(), fsys, "x.mp3")
		_, _, err := f()
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("not seekable"))
	})
})

// nonSeekableFS is a single-file fs.FS whose Open returns a non-seekable file.
type nonSeekableFS struct{ data []byte }

func (n nonSeekableFS) Open(name string) (fs.File, error) {
	return &nonSeekableFile{r: bytes.NewReader(n.data)}, nil
}

type nonSeekableFile struct{ r *bytes.Reader }

func (n *nonSeekableFile) Read(p []byte) (int, error) { return n.r.Read(p) }
func (n *nonSeekableFile) Close() error               { return nil }
func (n *nonSeekableFile) Stat() (fs.FileInfo, error) { return nil, errors.New("not implemented") }