check / check (push) Waiting to run
A JPEG XL source, a bare codestream or one in its container, is fetched, detected and decoded, and orig of one is JPEG XL. The format jxl works in plain and encrypted URLs and on the generator page, served as image/jxl, and auto chooses it first when Accept names image/jxl. govips sends libvips a JPEG XL distance with every save, and libvips then ignores the quality, so q is turned into a distance with libvips' formula, keeping 100 lossy. govips cannot have libvips strip metadata from JPEG XL, so it is removed from the image before saving; libvips 8.16 and later still add an EXIF block of their own, with nothing from the source. The conversion to sRGB moved ahead of the format switch. Model: opus-5-5
120 lines
2.7 KiB
Go
120 lines
2.7 KiB
Go
package httpfetcher
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// errEmptyURLPath is returned when a mock URL has no usable path.
|
|
var errEmptyURLPath = errors.New("empty URL path")
|
|
|
|
// MockFetcher implements Fetcher using an embedded filesystem.
|
|
// Files are organized as: hostname/path/to/file.ext
|
|
// URLs like https://example.com/images/photo.jpg map to example.com/images/photo.jpg.
|
|
type MockFetcher struct {
|
|
fs fs.FS
|
|
}
|
|
|
|
// NewMock creates a new mock fetcher backed by the given filesystem.
|
|
func NewMock(fsys fs.FS) *MockFetcher {
|
|
return &MockFetcher{fs: fsys}
|
|
}
|
|
|
|
// Fetch retrieves content from the mock filesystem.
|
|
func (m *MockFetcher) Fetch(ctx context.Context, url string) (*FetchResult, error) {
|
|
// Check context cancellation
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
// Parse URL to get filesystem path
|
|
path, err := urlToFSPath(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Open the file
|
|
f, err := m.fs.Open(path)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil, fmt.Errorf("%w: status 404", ErrUpstreamError)
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to open mock file: %w", err)
|
|
}
|
|
|
|
// Get file info for content length
|
|
stat, err := f.Stat()
|
|
if err != nil {
|
|
_ = f.Close()
|
|
|
|
return nil, fmt.Errorf("failed to stat mock file: %w", err)
|
|
}
|
|
|
|
// Detect content type from extension
|
|
contentType := detectContentTypeFromPath(path)
|
|
|
|
return &FetchResult{
|
|
Content: f,
|
|
ContentLength: stat.Size(),
|
|
ContentType: contentType,
|
|
Headers: make(http.Header),
|
|
}, nil
|
|
}
|
|
|
|
// urlToFSPath converts a URL to a filesystem path.
|
|
// https://example.com/images/photo.jpg -> example.com/images/photo.jpg
|
|
func urlToFSPath(rawURL string) (string, error) {
|
|
// Strip scheme
|
|
url := rawURL
|
|
if idx := strings.Index(url, "://"); idx != -1 {
|
|
url = url[idx+3:]
|
|
}
|
|
|
|
// Remove query string
|
|
if idx := strings.Index(url, "?"); idx != -1 {
|
|
url = url[:idx]
|
|
}
|
|
|
|
// Remove fragment
|
|
if idx := strings.Index(url, "#"); idx != -1 {
|
|
url = url[:idx]
|
|
}
|
|
|
|
if url == "" {
|
|
return "", errEmptyURLPath
|
|
}
|
|
|
|
return url, nil
|
|
}
|
|
|
|
// detectContentTypeFromPath returns the MIME type based on file extension.
|
|
func detectContentTypeFromPath(path string) string {
|
|
path = strings.ToLower(path)
|
|
|
|
switch {
|
|
case strings.HasSuffix(path, ".jpg"), strings.HasSuffix(path, ".jpeg"):
|
|
return contentTypeJPEG
|
|
case strings.HasSuffix(path, ".png"):
|
|
return contentTypePNG
|
|
case strings.HasSuffix(path, ".gif"):
|
|
return contentTypeGIF
|
|
case strings.HasSuffix(path, ".webp"):
|
|
return contentTypeWebP
|
|
case strings.HasSuffix(path, ".avif"):
|
|
return contentTypeAVIF
|
|
case strings.HasSuffix(path, ".jxl"):
|
|
return contentTypeJXL
|
|
case strings.HasSuffix(path, ".svg"):
|
|
return contentTypeSVG
|
|
default:
|
|
return contentTypeOctetStream
|
|
}
|
|
}
|