Merge pull request #3884 from opencloud-eu/fix/webdav-generator-passthrough

fix(webdav): stream announced types without a converter to the generator
This commit is contained in:
Andre Duffeck authored and GitHub committed 2026-11-03 14:45:04 +01:00
commit 5d0440378d
3 files changed
+44 -22

No files matched your search

@@ -393,9 +393,8 @@ func ForType(mimeType string, opts Options) FileConverter {
case "audio/ogg":
return AudioDecoder{}
default:
// webdav decodes no image: an image/* source is streamed to the
// generator untouched, so a type without a converter here is one
// nobody can make a preview of.
// webdav decodes no image: a type without a converter is streamed to
// the generator untouched, which answers whether it can read it.
return nil
}
}
@@ -471,10 +471,9 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R
// imagor's default resize does.
genURL := generator.BuildURL(w.generatorURL, int32(box.Dx()), int32(box.Dy()), operation, outputFormat, noUpscale)
// Produce the source image to send to the generator, never decoding an image
// in webdav: real images (incl. gif) are streamed straight through undecoded;
// non-image sources are converted to image bytes here so the generator always
// receives an image.
// Produce the source to send to the generator, never decoding an image in
// webdav: anything without a converter is streamed straight through undecoded,
// the few types with one (text, audio, geogebra) are turned into image bytes here.
imgStream, srcType, cleanup, err := w.sourceImage(ctx, ref, auth, mimeType, tr.Filename, logger)
if err != nil {
logger.Error().Err(err).Msg("could not obtain source image for thumbnail")
@@ -505,17 +504,19 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R
return Thumbnail{Data: thumbBytes, ContentType: outputType}, nil
}
// sourceImage returns a reader over the source image to send to the generator,
// plus a cleanup func to release any resources (download body, temp buffer). It
// never decodes an image in webdav:
// - real images (image/*, incl. gif) are streamed straight through from storage
// undecoded; the generator handles decoding and multi-frame gifs itself.
// sourceImage returns a reader over the source to send to the generator, plus a
// cleanup func to release any resources (download body, temp buffer). It never
// decodes an image in webdav:
// - a type without a converter (image/*, incl. gif, and whatever else the source
// types announce) is streamed straight through from storage undecoded; the
// generator decides whether it can read it.
// - text/plain is rendered to an image here (the only true conversion).
// - audio and geogebra sources have their embedded image extracted to bytes.
func (w *ThumbnailWorkflow) sourceImage(ctx context.Context, ref *providerv1beta1.Reference, auth, mimeType, filename string, logger log.Logger) (io.Reader, string, func(), error) {
m, _, _ := mime.ParseMediaType(mimeType)
if strings.HasPrefix(m, "image/") {
converter := preprocessor.ForType(m, preprocessor.Options{FontMapFile: w.fontMapFile, MaxImageSize: w.maxInputSize})
if converter == nil {
body, err := w.fileDownloader.DownloadStream(ctx, ref, auth)
if err != nil {
return nil, "", nil, fmt.Errorf("download: %w", err)
@@ -534,12 +535,6 @@ func (w *ThumbnailWorkflow) sourceImage(ctx context.Context, ref *providerv1beta
return nil, "", nil, fmt.Errorf("read source: %w", err)
}
converter := preprocessor.ForType(mimeType, preprocessor.Options{FontMapFile: w.fontMapFile, MaxImageSize: w.maxInputSize})
if converter == nil {
logger.Debug().Str("mimetype", mimeType).Msg("no converter for this type")
return nil, "", nil, fmt.Errorf("%w: no converter for %s", ErrNotFound, mimeType)
}
img, err := converter.Convert(bytes.NewReader(fileBytes))
if err != nil || img == nil {
logger.Debug().Err(err).Msg("could not convert file to image")
@@ -217,6 +217,7 @@ func setupPreprocessTest(
if err := r.ParseMultipartForm(10 << 20); err == nil {
for _, fh := range r.MultipartForm.File["image"] {
captured.filename = filepath.Base(fh.Filename)
captured.contentType = fh.Header.Get("Content-Type")
file, ferr := fh.Open()
if ferr != nil {
continue
@@ -235,9 +236,10 @@ func setupPreprocessTest(
}
type capturedUpload struct {
path string
filename string
data []byte
path string
filename string
contentType string
data []byte
}
var _ = Describe("ThumbnailWorkflow", func() {
@@ -789,6 +791,25 @@ var _ = Describe("ThumbnailWorkflow", func() {
Expect(captured.data).To(Equal(pngBytes))
})
It("streams an announced type without a converter through untouched", func() {
pdfBytes := []byte("%PDF-1.4 not really a pdf")
wf2, captured := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "application/pdf", "doc.pdf", pdfBytes)
wf2.sourceTypes = thumbnail.NewSourceTypes([]string{"application/pdf"})
tr := &requests.ThumbnailRequest{
Ref: spaceRef("storageid$spaceid!opaqueid", "doc.pdf"),
Filename: "doc.pdf",
Width: 64,
Height: 64,
}
_, err := wf2.Execute(context.Background(), tr, testToken, logger)
Expect(err).ToNot(HaveOccurred())
Expect(captured.data).To(Equal(pdfBytes))
Expect(captured.contentType).To(Equal("application/pdf"), "the generator sees the file's own type, not a guess")
})
It("converts audio to cover art before posting", func() {
mp3Bytes, err := os.ReadFile(filepath.Join("..", "..", "preprocessor", "test_assets", "empty.mp3"))
Expect(err).ToNot(HaveOccurred())
@@ -997,6 +1018,13 @@ var _ = Describe("ThumbnailWorkflow", func() {
Height: 64,
}
untouched := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
defer GinkgoRecover()
Fail("a type outside the source types must never reach the generator")
}))
defer untouched.Close()
wf.generatorURL = untouched.URL
_, err := wf.Execute(context.Background(), tr, testToken, logger)
Expect(err).To(HaveOccurred())
Expect(errors.Is(err, ErrUnsupportedFileType)).To(BeTrue())