From e599ac6d7861681e8799fec3b49cede927698cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 3 Sep 2026 17:33:21 +0200 Subject: [PATCH 1/5] feat(thumbnails): add imagor-compatible push-based thumbnail endpoint Add a stateless POST endpoint that accepts an original image as a multipart upload and returns the resized thumbnail, mimicking imagor's /unsafe/ API: POST /unsafe(/fit-in|/stretch)/{width}x{height}(/filters:{filters}) The three forms are fill (center-crop to the exact box, may upscale), fit-in (preserve aspect ratio, fit within the box, never upscale) and stretch (resize to the exact box without preserving aspect). Supported output formats are jpg, png and gif. Image processing is split by build tag: stdlib imaging by default, libvips when built with -tags enable_vips. The endpoint is stateless: no auth, no storage, no source fetching. This commit only adds the /unsafe routes; the legacy /data endpoint is left in place and removed in a later commit. --- services/thumbnails/README.md | 108 +-- .../thumbnails/pkg/service/http/v0/push.go | 270 ++++++ .../pkg/service/http/v0/push_imaging.go | 124 +++ .../pkg/service/http/v0/push_test.go | 819 ++++++++++++++++++ .../pkg/service/http/v0/push_vips.go | 162 ++++ .../thumbnails/pkg/service/http/v0/service.go | 22 +- 6 files changed, 1421 insertions(+), 84 deletions(-) create mode 100644 services/thumbnails/pkg/service/http/v0/push.go create mode 100644 services/thumbnails/pkg/service/http/v0/push_imaging.go create mode 100644 services/thumbnails/pkg/service/http/v0/push_test.go create mode 100644 services/thumbnails/pkg/service/http/v0/push_vips.go diff --git a/services/thumbnails/README.md b/services/thumbnails/README.md index 26b68497d9..8bfaeafb9a 100644 --- a/services/thumbnails/README.md +++ b/services/thumbnails/README.md @@ -1,99 +1,48 @@ # Thumbnails -The thumbnails service provides methods to generate thumbnails for various files and resolutions based on requests. It retrieves the sources at the location where the user files are stored and saves the thumbnails where system files are stored. Those locations have defaults but can be manually defined via environment variables. +The thumbnails service is a stateless image resizer. It exposes an imagor-compatible push endpoint that accepts an original image as a multipart upload and returns the resized thumbnail encoded in the requested format. -## File Locations Overview +## Push Endpoint -The relevant environment variables defining file locations are: +The webdav service (which owns the complete thumbnail workflow) POSTs the source file to this endpoint and receives the processed image back. -- (1) `OC_BASE_DATA_PATH` -- (2) `STORAGE_USERS_DECOMPOSED_ROOT` -- (3) `THUMBNAILS_FILESYSTEMSTORAGE_ROOT` +| Route | Description | +|---|---| +| `POST /unsafe/{width}x{height}` (optionally `/filters:format({format})`) | Fill the exact width x height (center crop, may upscale) — the default | +| `POST /unsafe/fit-in/{width}x{height}` (optionally `/filters:format({format})`) | Scale to fit within width x height, preserving aspect ratio and never upscaling (letterboxed) | +| `POST /unsafe/stretch/{width}x{height}` (optionally `/filters:format({format})`) | Resize to the exact width x height without preserving aspect ratio (distorts) | -(1) ... Having a default set by the OpenCloud code, but if defined, used as base path for other services. -(2) ... Source files, defaults to (1) plus path component, but can be freely defined if required. -(3) ... Target files, defaults to (1) plus path component, but can be freely defined if required. +The filters segment is captured whole and parsed; only the `format` filter is meaningful to this executor, other filters are ignored. The `format` filter is optional: when absent the input's own format is preserved (detected via the imaging library), matching imagor. Inputs we cannot re-encode (e.g. webp, tiff, bmp) fall back to JPEG, mirroring imagor's default for unsavable sources. -For details and defaults for these environment variables see the OpenCloud admin documentation. +The webdav service selects the route based on the requested processor. The full mapping is: -## Thumbnail Location +| Request | Operation | +|---|---| +| `processor=resize` | stretch (distort to the exact box) | +| `processor=fill` or `processor=thumbnail` | fill (center-crop to the exact box, may upscale) | +| `processor=fit` / `processor=fit-in` | fit-in (preserve aspect, fit in box, never upscale) | +| no processor, gif source | stretch (resize for gifs) | +| no processor, other sources | fill (default = thumbnail = fill) | -It may be beneficial to define the location of the thumbnails to be other than the default (with system files). This is due the fact that storing thumbnails can consume a lot of space over time which not necessarily needs to reside on the same partition or mount or expensive drives. +By default (no processor, non-gif) the fill form is used, which center-crops to the exact box and upscales small sources — this matches the legacy `thumbnail` processor behavior (e.g. a 200x100 image requested at 100x100 returns a square 100x100 image). Requesting `processor=fit` switches to the `fit-in` form, which preserves aspect ratio and never upscales, letterboxing a non-square source into the box (the same image returns 100x50). -## Thumbnail Source File Types +### Legacy `a` parameter -Thumbnails can be generated from the following source file types: +The webdav preview endpoint also accepts a legacy `a` flag: `a=1` (or absent) means "preserve aspect" (fit-in), `a=0` means "fill". An explicit `processor` always wins over `a`. When an explicit processor overrides a contradictory `a`, the thumbnail response includes the header `X-OpenCloud-Thumbnail-Aspect-Ignored` so developers can tell their client to send a consistent request. -- png -- jpg -- gif -- tiff -- bmp -- txt +The request body is a `multipart/form-data` upload with a single file field named `image`. Supported output formats are `jpg`, `png`, and `gif`. -The thumbnail service retrieves source files using the information provided by the backend. The Linux backend identifies source files usually based on the extension. +## Configuration -If a file type was not properly assigned or the type identification failed, thumbnail generation will fail and an error will be logged. +| Environment variable | Description | +|---|---| +| `THUMBNAILS_HTTP_ADDR` | Bind address of the HTTP service (default `127.0.0.1:9186`) | +| `THUMBNAILS_LOG_LEVEL` | Log level (`panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`) | -## Thumbnail Target File Types +## Using libvips for Image Processing -Thumbnails can either be generated as `png`, `jpg` or `gif` files. These types are hardcoded and no other types can be requested. A requestor, like another service or a client, can request one of the available types to be generated. If more than one type is required, each type must be requested individually. -## Thumbnail Query String Parameters - -Clients can request thumbnail previews for files by adding `?preview=1` to the file URL. Requests for files with no thumbnail available respond with HTTP status `404`. - -The following query parameters are supported: - -| Parameter | Required | Default Value | Description | -|-----------|----------|------------------------------------------------------|---------------------------------------------------------------------------------| -| preview | YES | 1 | generates preview | -| x | YES | first x-value configured in `THUMBNAILS_RESOLUTIONS` | horizontal target size | -| y | YES | first y-value configured in `THUMBNAILS_RESOLUTIONS` | vertical target size | -| scalingup | NO | 0 | prevents up-scaling of small images | -| a | NO | 1 | aspect ratio | -| c | NO | Caching string | Clients should send the etag, so they get a fresh thumbnail after a file change | -| processor | NO | `resize` for gifs and `thumbnail` for all others | preferred thumbnail processor | - -## Thumbnail Resolution - -Various resolutions can be defined via `THUMBNAILS_RESOLUTIONS`. A requestor can request any arbitrary resolution and the thumbnail service will use the one closest to the requested resolution. If more than one resolution is required, each resolution must be requested individually. - -Example: - -Requested: 18x12\ -Available: 30x20, 15x10, 9x6\ -Returned: 15x10 - -## Thumbnail Processors - -Normally, an image might get cropped when creating a preview, depending on the aspect ratio of the original image. This can have negative -impacts on previews as only a part of the image will be shown. When using an _optional_ processor in the request, cropping can be avoided by defining on how the preview image generation will be done. The following processors are available: - -* `resize` resizes the image to the specified width and height and returns the transformed image. If one of width or height is 0, the image aspect ratio is preserved. -* `fit` scales down the image to fit the specified maximum width and height and returns the transformed image. -* `fill`: creates an image with the specified dimensions and fills it with the scaled source image. To achieve the correct aspect ratio without stretching, the source image will be cropped. -* `thumbnail` scales the image up or down, crops it to the specified width and height and returns the transformed image. - -To apply one of those, a query parameter has to be added to the request, like `?processor=fit`. If no query parameter or processor is added, the default behaviour applies which is `resize` for gifs and `thumbnail` for all others. - -## Deleting Thumbnails - -As of now, there is no automated thumbnail deletion. This is especially true when a source file gets deleted or moved. This situation will be solved at a later stage. For the time being, if you run short on physical thumbnails space, you have to manually delete the thumbnail store to free space. Thumbnails will then be recreated on request. - -## Memory Considerations - -Since source files need to be loaded into memory when generating thumbnails, large source files could potentially crash this service if there is insufficient memory available. For bigger instances when using container orchestration deployment methods, this service can be dedicated to its own server(s) with more memory. -To have more control over memory (and CPU) consumption the maximum number of concurrent requests can be limited by setting the environment variable `THUMBNAILS_MAX_CONCURRENT_REQUESTS`. The default value is 0 which does not apply any restrictions to the number of concurrent requests. As soon as the number of concurrent requests is reached any further request will be responded with `429/Too Many Requests` and the client can retry at a later point in time. - -## Thumbnails and SecureView - -If a resource is shared using SecureView, the share reciever will get a 403 (forbidden) response when requesting a thumbnail. The requesting client needs to decide what to show and usually a placeholder thumbnail is used. - -## Using libvips for Thumbnail Generation - -To improve performance and to support a wider range of images formats, the thumbnails service is able to utilize the [libvips library](https://www.libvips.org/) for thumbnail generation. Support for libvips needs to be -enabled at buildtime and has a couple of implications: +To improve performance and to support a wider range of image formats, the thumbnails service is able to utilize the [libvips library](https://www.libvips.org/) for image processing. Support for libvips needs to be enabled at buildtime and has a couple of implications: * With libvips support enabled, it is not possible to create a statically linked OpenCloud binary. * Therefore, the libvips shared libraries need to be available at runtime in the same release that was used to build the OpenCloud binary. @@ -120,4 +69,3 @@ go build -tags enable_vips -o opencloud -o bin/opencloud ./cmd/opencloud When building a docker image using the Dockerfile in the top-level directory of OpenCloud, libvips support is enabled and the libvips shared libraries are included in the resulting docker image. - diff --git a/services/thumbnails/pkg/service/http/v0/push.go b/services/thumbnails/pkg/service/http/v0/push.go new file mode 100644 index 0000000000..f2e6523e0f --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/push.go @@ -0,0 +1,270 @@ +package svc + +import ( + "bytes" + "fmt" + "image" + "image/gif" + "io" + "net/http" + "strconv" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/kovidgoyal/imaging" +) + +// gifMagic is the leading signature of a GIF file (GIF87a or GIF89a). It guards +// against gif.DecodeAll panicking on non-GIF input. +var gifMagic = []byte("GIF8") + +// Operations understood by the push endpoint. They map to imagor's resize/crop +// modes so a real imagor deployment can replace this service. +const ( + OpFill = "fill" + OpFitIn = "fit-in" + OpStretch = "stretch" +) + +// errInvalid mirrors imagor's ErrInvalid: a syntactically invalid request. +var errInvalid = fmt.Errorf("invalid") + +// pushHandler handles POST requests for the imagor-like push-based thumbnail endpoint. +// Path patterns (operation and format are both optional; absent operation is the default +// center-crop fill, absent format keeps the input's own format): +// - POST /unsafe(/fit-in|/stretch)/{W}x{H}(/filters:{filters}) +// +// The filters segment is captured whole and parsed by outputFormatFromFilters; only the +// format filter is meaningful to this executor, other filters are ignored. When no format +// filter is present the input's own format is preserved (detected via imaging), matching +// imagor. It returns only imagor's defined status codes: 400 for invalid requests or a +// file exceeding the max size, 422 for an image exceeding the max resolution. +func (s Thumbnails) pushHandler(w http.ResponseWriter, r *http.Request) { + width, err := parseDim(r, "width") + if err != nil { + writeInvalid(w, "invalid width") + return + } + height, err := parseDim(r, "height") + if err != nil { + writeInvalid(w, "invalid height") + return + } + + operation := chi.URLParam(r, "operation") + if operation == "" { + operation = OpFill + } + switch operation { + case OpFill, OpFitIn, OpStretch: + default: + writeInvalid(w, fmt.Sprintf("unsupported operation: %s", operation)) + return + } + + if err := r.ParseMultipartForm(32 << 20); err != nil { + writeInvalid(w, "failed to parse multipart form") + return + } + + file, _, err := r.FormFile("image") + if err != nil { + writeInvalid(w, "missing image field in form data") + return + } + defer file.Close() + + imgData, err := io.ReadAll(file) + if err != nil { + writeInvalid(w, "failed to read image") + return + } + + ext, hasFormat, err := outputFormatFromFilters(chi.URLParam(r, "filters")) + if err != nil { + writeInvalid(w, err.Error()) + return + } + if !hasFormat { + // No format filter: preserve the input's own format (imagor default), + // detected via imaging. Only runs on this path, so the common webdav + // request (which always sends a format) pays no decode cost. + ext = inputFormatViaImaging(imgData) + } + + // The box and operation are chosen by webdav (the sizing brain); this service + // is a dumb, imagor-like executor that just fits within the given box. + processed, srcBounds, err := processImage(bytes.NewReader(imgData), width, height, operation) + if err != nil { + writeInvalid(w, "failed to process image") + return + } + + // imagor ErrMaxResolutionExceeded: the decoded input exceeds the configured + // maximum width/height. Checked against the source (pre-resize) bounds because + // this service owns the image; webdav translates the resulting 422 into its + // legacy 403 response. + if s.maxWidth > 0 || s.maxHeight > 0 { + if (s.maxWidth == 0 || srcBounds.Dx() > s.maxWidth) && (s.maxHeight == 0 || srcBounds.Dy() > s.maxHeight) { + writeMaxResolution(w) + return + } + } + + var ( + buf bytes.Buffer + contentType string + encErr error + ) + switch ext { + case "jpg": + contentType = "image/jpeg" + encErr = encodeJPEG(&buf, processed) + case "png": + contentType = "image/png" + encErr = encodePNG(&buf, processed) + default: + contentType = "image/gif" + g, ok := processed.(*gif.GIF) + if !ok { + writeInvalid(w, "processed image is not a gif") + return + } + encErr = gif.EncodeAll(&buf, g) + } + if encErr != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, "failed to encode image") + return + } + + w.Header().Set("Content-Type", contentType) + w.WriteHeader(http.StatusOK) + buf.WriteTo(w) +} + +func parseDim(r *http.Request, name string) (int, error) { + v := chi.URLParam(r, name) + n, err := strconv.Atoi(v) + if err != nil || n <= 0 { + return 0, fmt.Errorf("invalid %s: %s", name, v) + } + return n, nil +} + +// writeInvalid responds with imagor's ErrInvalid status (400 Bad Request). +func writeInvalid(w http.ResponseWriter, msg string) { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, msg) +} + +// writeMaxResolution responds with imagor's ErrMaxResolutionExceeded status +// (422 Unprocessable Entity). +func writeMaxResolution(w http.ResponseWriter) { + w.WriteHeader(http.StatusUnprocessableEntity) + fmt.Fprintln(w, "maximum resolution exceeded") +} + +// mapFormatToExt maps imagor format names to the internal extension used by encoders. +func mapFormatToExt(format string) string { + switch strings.ToLower(format) { + case "jpeg": + return "jpg" + default: + return strings.ToLower(format) + } +} + +// outputFormatFromFilters parses an imagor filters segment (e.g. "format(jpeg)") and +// reports the requested output extension. Only the format filter is meaningful to this +// executor; other filters are ignored because the operation already governs +// sizing/upscaling. It returns hasFormat=false when no format filter is present, in +// which case the caller falls back to inputFormatViaImaging. An explicit but +// unsupported format is an error. +func outputFormatFromFilters(segment string) (ext string, hasFormat bool, err error) { + for _, token := range strings.Split(segment, ":") { + name, args, ok := splitFilter(token) + if !ok || name != "format" { + continue + } + mapped := mapFormatToExt(args) + switch mapped { + case "jpg", "png", "gif": + return mapped, true, nil + default: + return "", false, fmt.Errorf("unsupported output format: %s", args) + } + } + return "", false, nil +} + +// inputFormatViaImaging detects the uploaded image's format with imaging and maps it +// to our output extension. jpeg/png/gif are kept; anything else (webp, tiff, bmp, or +// an undecodable body) falls back to jpeg, mirroring imagor's unsavable-source default. +func inputFormatViaImaging(imgData []byte) string { + img, _, err := imaging.DecodeAll(bytes.NewReader(imgData)) + if err != nil || img == nil || img.Metadata == nil { + return "jpg" + } + switch img.Metadata.Format { + case imaging.PNG: + return "png" + case imaging.GIF: + return "gif" + default: // JPEG, WEBP, TIFF, BMP, UNKNOWN, ... + return "jpg" + } +} + +// splitFilter splits a "name(args)" token into name and args. It returns ok=false for +// tokens that are not in parenthesized form. +func splitFilter(token string) (name, args string, ok bool) { + i := strings.IndexByte(token, '(') + if i < 0 || !strings.HasSuffix(token, ")") { + return "", "", false + } + return token[:i], token[i+1 : len(token)-1], true +} + +// isGifReader reports whether the reader holds GIF data, based on the GIF magic +// bytes. It peeks at the first 4 bytes and rewinds the stream so it can be read +// again. This guard is required because gif.DecodeAll panics (nil dereference) +// when handed a non-GIF image instead of returning an error. +func isGifReader(r io.Reader) bool { + readSeeker, ok := r.(io.ReadSeeker) + if !ok { + return false + } + + saved, err := readSeeker.Seek(0, io.SeekCurrent) + if err != nil { + return false + } + buf := make([]byte, 4) + if _, err := io.ReadFull(readSeeker, buf); err != nil { + return false + } + _, _ = readSeeker.Seek(saved, io.SeekStart) + + return bytes.HasPrefix(buf, gifMagic) +} + +// Backend-specific function set in init() by push_imaging.go or push_vips.go. +// For gif input it returns a *gif.GIF with every frame resized (preserving +// animation); for other inputs it returns a single image.Image. The operation +// selects the resize/crop mode (fill, fit-in, stretch). The +// second return value is the decoded source's pixel bounds, reported before any +// resize so the max-resolution limit can be enforced against the input rather +// than the output. +var processImage func(io.Reader, int, int, string) (any, image.Rectangle, error) + +// encodeJPEG and encodePNG write the processed image to w in the requested +// format. Set by init() in push_imaging.go / push_vips.go. The vips backend +// encodes via libvips (ExportJpeg/ExportPng) so its output matches the legacy +// pipeline (progressive JPEG, quality 80); the imaging backend uses the stdlib +// encoders. GIF encoding stays in the common handler because both backends +// return a *gif.GIF for animated input. +var ( + encodeJPEG func(w io.Writer, processed any) error + encodePNG func(w io.Writer, processed any) error +) diff --git a/services/thumbnails/pkg/service/http/v0/push_imaging.go b/services/thumbnails/pkg/service/http/v0/push_imaging.go new file mode 100644 index 0000000000..69c90be59e --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/push_imaging.go @@ -0,0 +1,124 @@ +//go:build !enable_vips + +package svc + +import ( + "fmt" + "image" + "image/color" + "image/draw" + "image/gif" + "image/jpeg" + "image/png" + "io" + + "github.com/kovidgoyal/imaging" +) + +func init() { + processImage = processImageImaging + encodeJPEG = encodeJPEGImaging + encodePNG = encodePNGImaging +} + +// processImageImaging resizes the input using the imaging backend. The operation +// selects the resize/crop mode: fill (center-crop to the box), fit-in (fit within +// the box without cropping, never upscaling), or stretch (resize to the exact box). +func processImageImaging(r io.Reader, width, height int, operation string) (any, image.Rectangle, error) { + if isGifReader(r) { + g, err := gif.DecodeAll(r) + if err == nil && len(g.Image) > 0 { + srcBounds := g.Image[0].Bounds() + return resizeGIF(g, width, height, operation), srcBounds, nil + } + } + + img, err := imaging.Decode(r, imaging.AutoOrientation(true)) + if err != nil { + return nil, image.Rectangle{}, err + } + + srcBounds := img.Bounds() + + switch operation { + case OpStretch: + return imaging.Resize(img, width, height, imaging.Lanczos), srcBounds, nil + case OpFitIn: + if srcBounds.Dx() > width || srcBounds.Dy() > height { + return imaging.Fit(img, width, height, imaging.Lanczos), srcBounds, nil + } + return img, srcBounds, nil + default: // OpFill + return imaging.Thumbnail(img, width, height, imaging.Lanczos), srcBounds, nil + } +} + +// resizeGIF resizes every frame of an animated gif while preserving the +// animation. It composites each frame onto a running canvas honoring the gif +// disposal method, resizes with the requested processor, and re-pallettes the +// result using Floyd-Steinberg dithering. Code adapted from +// https://github.com/willnorris/gifresize. +func resizeGIF(m *gif.GIF, width, height int, operation string) *gif.GIF { + srcX, srcY := m.Config.Width, m.Config.Height + b := image.Rect(0, 0, srcX, srcY) + tmp := image.NewRGBA(b) + + for i, frame := range m.Image { + frameBounds := frame.Bounds() + prev := tmp + draw.Draw(tmp, frameBounds, frame, frameBounds.Min, draw.Over) + + var processed image.Image + switch operation { + case OpStretch: + processed = imaging.Resize(tmp, width, height, imaging.Lanczos) + case OpFitIn: + if srcX > width || srcY > height { + processed = imaging.Fit(tmp, width, height, imaging.Lanczos) + } else { + processed = tmp + } + default: // OpFill + processed = imaging.Fill(tmp, width, height, imaging.Center, imaging.Lanczos) + } + + m.Image[i] = imageToPaletted(processed, frame.Palette) + + switch m.Disposal[i] { + case gif.DisposalBackground: + tmp = image.NewRGBA(b) + case gif.DisposalPrevious: + tmp = prev + } + } + + m.Config.Width = width + m.Config.Height = height + + return m +} + +func imageToPaletted(img image.Image, p color.Palette) *image.Paletted { + b := img.Bounds() + pm := image.NewPaletted(b, p) + draw.FloydSteinberg.Draw(pm, b, img, image.Point{}) + return pm +} + +// encodeJPEGImaging encodes the processed image as JPEG using the stdlib encoder. +func encodeJPEGImaging(w io.Writer, processed any) error { + img, ok := processed.(image.Image) + if !ok { + return fmt.Errorf("cannot encode %T as jpeg", processed) + } + return jpeg.Encode(w, img, nil) +} + +// encodePNGImaging encodes the processed image as PNG using the stdlib encoder. +func encodePNGImaging(w io.Writer, processed any) error { + img, ok := processed.(image.Image) + if !ok { + return fmt.Errorf("cannot encode %T as png", processed) + } + return png.Encode(w, img) +} diff --git a/services/thumbnails/pkg/service/http/v0/push_test.go b/services/thumbnails/pkg/service/http/v0/push_test.go new file mode 100644 index 0000000000..4b80aa9d84 --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/push_test.go @@ -0,0 +1,819 @@ +package svc + +import ( + "bytes" + "image" + "image/color" + "image/gif" + "image/jpeg" + "image/png" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" + "golang.org/x/image/bmp" +) + +func createTestImage(width, height int) []byte { + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + img.Set(x, y, color.RGBA{R: uint8(x % 256), G: uint8(y % 256), B: 128, A: 255}) + } + } + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + panic(err) + } + return buf.Bytes() +} + +func createMultipartBody(imgBytes []byte) (*bytes.Buffer, string) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("image", "test.png") + if err != nil { + panic(err) + } + if _, err := part.Write(imgBytes); err != nil { + panic(err) + } + if err := writer.Close(); err != nil { + panic(err) + } + return body, writer.FormDataContentType() +} + +func newTestMux() *chi.Mux { + return newTestMuxWithLimits(0, 0) +} + +// newTestMuxWithLimits builds the route table with explicit max width/height so +// tests can exercise the ErrMaxResolutionExceeded (422) path. A limit of 0 +// disables the corresponding bound. +func newTestMuxWithLimits(maxWidth, maxHeight int) *chi.Mux { + mux := chi.NewRouter() + h := Thumbnails{maxWidth: maxWidth, maxHeight: maxHeight}.pushHandler + mux.Post("/unsafe/{operation}/{width}x{height}/filters:{filters}/", h) + mux.Post("/unsafe/{operation}/{width}x{height}/filters:{filters}", h) + mux.Post("/unsafe/{width}x{height}/filters:{filters}/", h) + mux.Post("/unsafe/{width}x{height}/filters:{filters}", h) + return mux +} + +// createTestGIF returns an animated gif with the given number of frames, each +// frame a solid color so that animation preservation can be verified. +func createTestGIF(width, height, frames int) []byte { + pal := color.Palette{color.RGBA{255, 0, 0, 255}, color.RGBA{0, 255, 0, 255}, color.RGBA{0, 0, 255, 255}} + g := &gif.GIF{} + for i := 0; i < frames; i++ { + frame := image.NewPaletted(image.Rect(0, 0, width, height), pal) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + frame.SetColorIndex(x, y, uint8(i%len(pal))) + } + } + g.Image = append(g.Image, frame) + g.Delay = append(g.Delay, 10) + } + var buf bytes.Buffer + if err := gif.EncodeAll(&buf, g); err != nil { + panic(err) + } + return buf.Bytes() +} + +func TestPushEndpoint_FitIn_JPEG(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:format(jpeg)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/jpeg" { + t.Errorf("expected Content-Type image/jpeg, got %s", ct) + } + if rec.Body.Len() == 0 { + t.Error("expected non-empty response body") + } +} + +func TestPushEndpoint_FitIn_PNG(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/png" { + t.Errorf("expected Content-Type image/png, got %s", ct) + } + if rec.Body.Len() == 0 { + t.Error("expected non-empty response body") + } +} + +func TestPushEndpoint_FitIn_GIF(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestGIF(200, 200, 3) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:format(gif)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/gif" { + t.Errorf("expected Content-Type image/gif, got %s", ct) + } + if rec.Body.Len() == 0 { + t.Error("expected non-empty response body") + } + + out, err := gif.DecodeAll(rec.Body) + if err != nil { + t.Fatalf("failed to decode response gif: %v", err) + } + if len(out.Image) != 3 { + t.Errorf("expected 3 frames preserved, got %d", len(out.Image)) + } +} + +func TestPushEndpoint_FitIn_GIFPreservesAnimation(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestGIF(200, 100, 4) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/50x50/filters:format(gif)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + out, err := gif.DecodeAll(rec.Body) + if err != nil { + t.Fatalf("failed to decode response gif: %v", err) + } + if len(out.Image) != 4 { + t.Errorf("expected 4 frames preserved, got %d", len(out.Image)) + } + // fit-in must not enlarge the 200x100 source to 50x50 (fit-in never upscales). + for i, frame := range out.Image { + b := frame.Bounds() + if b.Dx() > 200 || b.Dy() > 100 { + t.Errorf("frame %d exceeds source dimensions: %dx%d", i, b.Dx(), b.Dy()) + } + } +} + +func TestPushEndpoint_Fill_GIF(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestGIF(200, 100, 3) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/64x64/filters:format(gif)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + out, err := gif.DecodeAll(rec.Body) + if err != nil { + t.Fatalf("failed to decode response gif: %v", err) + } + if len(out.Image) != 3 { + t.Errorf("expected 3 frames preserved, got %d", len(out.Image)) + } + for i, frame := range out.Image { + b := frame.Bounds() + if b.Dx() != 64 || b.Dy() != 64 { + t.Errorf("fill should produce exact dimensions, frame %d is %dx%d", i, b.Dx(), b.Dy()) + } + } +} + +func TestPushEndpoint_FitIn_AspectRatio(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(400, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() > 100 || bounds.Dy() > 100 { + t.Errorf("fit-in should not exceed requested dimensions, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +func TestPushEndpoint_FitIn_NoUpscale(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(50, 50) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/200x200/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() > 50 || bounds.Dy() > 50 { + t.Errorf("fit-in should not enlarge the image, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +// TestPushEndpoint_Default_SquareFill pins the default processor's outcome: +// center-crop to fill the requested box exactly (the legacy "thumbnail" +// processor behavior). A 200x100 source requested at 100x100 must come back as +// exactly 100x100 — center-cropped, NOT letterboxed to 100x50. This is the route +// the webdav service uses by default (no fit-in segment). +func TestPushEndpoint_Default_SquareFill(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 100) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/100x100/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() != 100 || bounds.Dy() != 100 { + t.Errorf("default should center-crop 200x100 into a 100x100 box as 100x100, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +// TestPushEndpoint_Default_Upscales documents that the default (fill) processor +// upscales small sources to fill the box exactly, matching the legacy +// "thumbnail" processor. A 50x50 source requested at 200x200 must come back as +// exactly 200x200. +func TestPushEndpoint_Default_Upscales(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(50, 50) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/200x200/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() != 200 || bounds.Dy() != 200 { + t.Errorf("default should upscale 50x50 to fill a 200x200 box as 200x200, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +// TestPushEndpoint_FitIn_LetterboxOutcome pins the opt-in "fit" processor's +// outcome: aspect-preserving fit with no upscaling. A 200x100 source requested +// at 100x100 must come back as exactly 100x50 — centered/letterboxed in the box, +// NOT center-cropped to 100x100. +func TestPushEndpoint_FitIn_LetterboxOutcome(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 100) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() != 100 || bounds.Dy() != 50 { + t.Errorf("fit-in should letterbox 200x100 into a 100x100 box as 100x50, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +func TestPushEndpoint_Fill_JPEG(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/100x100/filters:format(jpeg)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/jpeg" { + t.Errorf("expected Content-Type image/jpeg, got %s", ct) + } + if rec.Body.Len() == 0 { + t.Error("expected non-empty response body") + } +} + +func TestPushEndpoint_Fill_ExactDimensions(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(400, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/100x100/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() != 100 || bounds.Dy() != 100 { + t.Errorf("fill should produce exact dimensions, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +func TestPushEndpoint_BadDimensions(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(100, 100) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/abcxdef/filters:format(jpeg)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", rec.Code) + } +} + +func TestPushEndpoint_ZeroDimensions(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(100, 100) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/0x0/filters:format(jpeg)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400 for zero dimensions, got %d", rec.Code) + } +} + +func TestPushEndpoint_UnsupportedFormat(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(100, 100) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/50x50/filters:format(webp)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400 for unsupported format, got %d", rec.Code) + } +} + +func TestPushEndpoint_InvalidImageData(t *testing.T) { + mux := newTestMux() + + body, contentType := createMultipartBody([]byte("not a valid image")) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/50x50/filters:format(jpeg)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400 for invalid image data, got %d: %s", rec.Code, rec.Body.String()) + } +} + +func TestPushEndpoint_MissingImageField(t *testing.T) { + mux := newTestMux() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + _ = writer.WriteField("other_field", "some value") + if err := writer.Close(); err != nil { + panic(err) + } + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/50x50/filters:format(jpeg)/", body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400 for missing image field, got %d: %s", rec.Code, rec.Body.String()) + } +} + +func TestPushEndpoint_NotMultipart(t *testing.T) { + mux := newTestMux() + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/50x50/filters:format(jpeg)/", bytes.NewReader([]byte("plain text"))) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400 for non-multipart request, got %d: %s", rec.Code, rec.Body.String()) + } +} + +func TestPushEndpoint_NoTrailingSlash(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:format(png)", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200 without trailing slash, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/png" { + t.Errorf("expected Content-Type image/png, got %s", ct) + } +} + +// TestPushEndpoint_Stretch pins the stretch operation: resize to the exact box +// without preserving aspect ratio. A 200x100 source requested at 64x32 must come +// back as exactly 64x32 (distorted), not letterboxed or cropped. +func TestPushEndpoint_Stretch(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 100) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/stretch/64x32/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() != 64 || bounds.Dy() != 32 { + t.Errorf("stretch should produce exactly 64x32, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + +// TestPushEndpoint_MaxResolutionExceeded pins the ErrMaxResolutionExceeded path: +// a decoded input exceeding the configured max width/height returns 422. +func TestPushEndpoint_MaxResolutionExceeded(t *testing.T) { + mux := newTestMuxWithLimits(100, 100) + + imgBytes := createTestImage(500, 500) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/64x64/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusUnprocessableEntity { + t.Errorf("expected status 422 for oversized input, got %d: %s", rec.Code, rec.Body.String()) + } +} + +// TestPushEndpoint_WithinMaxResolution confirms an input within the configured +// limits still succeeds (the 422 check must not over-trigger). +func TestPushEndpoint_WithinMaxResolution(t *testing.T) { + mux := newTestMuxWithLimits(100, 100) + + imgBytes := createTestImage(50, 50) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/32x32/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected status 200 for in-bounds input, got %d: %s", rec.Code, rec.Body.String()) + } +} + +func createTestJPEG(width, height int) []byte { + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + img.Set(x, y, color.RGBA{R: uint8(x % 256), G: uint8(y % 256), B: 128, A: 255}) + } + } + var buf bytes.Buffer + if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 80}); err != nil { + panic(err) + } + return buf.Bytes() +} + +func createTestBMP(width, height int) []byte { + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + img.Set(x, y, color.RGBA{R: uint8(x % 256), G: uint8(y % 256), B: 128, A: 255}) + } + } + var buf bytes.Buffer + if err := bmp.Encode(&buf, img); err != nil { + panic(err) + } + return buf.Bytes() +} + +// TestPushEndpoint_ToleratesExtraFilters proves the generic filters parser ignores +// filters other than format (a real imagor client may send no_upscale or others). +func TestPushEndpoint_ToleratesExtraFilters(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:no_upscale():format(jpeg)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/jpeg" { + t.Errorf("expected Content-Type image/jpeg, got %s", ct) + } +} + +// TestPushEndpoint_NoFormat_FallbackGIF pins the 1c pass-through fallback: a filters +// segment with no format filter (here just no_upscale) keeps a gif input as gif. +func TestPushEndpoint_NoFormat_FallbackGIF(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestGIF(200, 200, 2) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:no_upscale()/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/gif" { + t.Errorf("expected Content-Type image/gif, got %s", ct) + } +} + +// TestPushEndpoint_NoFormat_FallbackPNG pins the 1c pass-through fallback: a filters +// segment with no format filter keeps a png input as png. +func TestPushEndpoint_NoFormat_FallbackPNG(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:no_upscale()/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/png" { + t.Errorf("expected Content-Type image/png, got %s", ct) + } +} + +// TestPushEndpoint_NoFormat_FallbackJPEG pins the 1c pass-through fallback: a filters +// segment with no format filter keeps a jpeg input as jpeg. +func TestPushEndpoint_NoFormat_FallbackJPEG(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestJPEG(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:no_upscale()/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/jpeg" { + t.Errorf("expected Content-Type image/jpeg, got %s", ct) + } +} + +// TestPushEndpoint_NoFormat_UnsupportedInputFallsBackToJPEG pins imagor's unsavable +// source behavior: an input we cannot re-encode (here a BMP) with no format filter is +// returned as JPEG, matching imagor's default for formats outside its savable set. +func TestPushEndpoint_NoFormat_UnsupportedInputFallsBackToJPEG(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestBMP(200, 200) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/fit-in/100x100/filters:no_upscale()/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); ct != "image/jpeg" { + t.Errorf("expected Content-Type image/jpeg, got %s", ct) + } +} + +func TestInputFormatViaImaging(t *testing.T) { + tests := []struct { + name string + data []byte + want string + }{ + {"jpeg", createTestJPEG(16, 16), "jpg"}, + {"png", createTestImage(16, 16), "png"}, + {"gif", createTestGIF(16, 16, 2), "gif"}, + {"bmp falls back to jpeg", createTestBMP(16, 16), "jpg"}, + {"undecodable falls back to jpeg", []byte("not an image at all"), "jpg"}, + {"empty falls back to jpeg", []byte{}, "jpg"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := inputFormatViaImaging(tt.data); got != tt.want { + t.Errorf("inputFormatViaImaging() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestSplitFilter(t *testing.T) { + tests := []struct { + name string + token string + wantName string + wantArgs string + wantOK bool + }{ + {"format", "format(jpeg)", "format", "jpeg", true}, + {"no args name", "no_upscale()", "no_upscale", "", true}, + {"no parens", "plain", "", "", false}, + {"open only", "format(jpeg", "", "", false}, + {"close only", "formatjpeg)", "", "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + name, args, ok := splitFilter(tt.token) + if ok != tt.wantOK || name != tt.wantName || args != tt.wantArgs { + t.Errorf("splitFilter(%q) = (%q,%q,%v), want (%q,%q,%v)", + tt.token, name, args, ok, tt.wantName, tt.wantArgs, tt.wantOK) + } + }) + } +} + +func TestOutputFormatFromFilters(t *testing.T) { + tests := []struct { + name string + segment string + wantExt string + wantHas bool + wantErr bool + }{ + {"format jpeg", "format(jpeg)", "jpg", true, false}, + {"extra filters ignored", "no_upscale():format(png)", "png", true, false}, + {"unsupported format", "format(webp)", "", false, true}, + {"no format filter", "no_upscale()", "", false, false}, + {"empty segment", "", "", false, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, has, err := outputFormatFromFilters(tt.segment) + if (err != nil) != tt.wantErr { + t.Fatalf("outputFormatFromFilters() error = %v, wantErr %v", err, tt.wantErr) + } + if got != tt.wantExt || has != tt.wantHas { + t.Errorf("outputFormatFromFilters() = (%q,%v), want (%q,%v)", got, has, tt.wantExt, tt.wantHas) + } + }) + } +} diff --git a/services/thumbnails/pkg/service/http/v0/push_vips.go b/services/thumbnails/pkg/service/http/v0/push_vips.go new file mode 100644 index 0000000000..dbf7431017 --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/push_vips.go @@ -0,0 +1,162 @@ +//go:build enable_vips + +package svc + +import ( + "fmt" + "image" + "image/color" + "image/draw" + "image/gif" + "io" + + "github.com/davidbyttow/govips/v2/vips" + "github.com/kovidgoyal/imaging" +) + +func init() { + processImage = processImageVips + encodeJPEG = encodeJPEGVips + encodePNG = encodePNGVips +} + +// processImageVips resizes the input using libvips. The operation selects the +// resize/crop mode: fill (center-crop to the box), fit-in (fit within the box +// without cropping, never upscaling), or stretch (resize to the exact box). +func processImageVips(r io.Reader, width, height int, operation string) (any, image.Rectangle, error) { + if isGifReader(r) { + g, err := gif.DecodeAll(r) + if err == nil && len(g.Image) > 0 { + srcBounds := g.Image[0].Bounds() + return resizeGIFVips(g, width, height, operation), srcBounds, nil + } + } + + imgData, err := io.ReadAll(r) + if err != nil { + return nil, image.Rectangle{}, err + } + + m, err := vips.NewImageFromBuffer(imgData) + if err != nil { + return nil, image.Rectangle{}, err + } + + // Note: no defer m.Close() here. The ImageRef is returned to the caller and + // encoded by encodeJPEGVips/encodePNGVips, which own its lifetime and close it + // after exporting. Closing it here would free the underlying C image before the + // export runs (use-after-free). + + srcBounds := image.Rect(0, 0, m.Width(), m.Height()) + + switch operation { + case OpStretch: + // Resize to the exact box without preserving aspect ratio. + hScale := float64(width) / float64(m.Width()) + vScale := float64(height) / float64(m.Height()) + if err := m.ResizeWithVScale(hScale, vScale, vips.KernelLanczos3); err != nil { + return nil, image.Rectangle{}, err + } + case OpFitIn: + // Fit within the box without cropping and never upscale (SizeDown). + if err := m.ThumbnailWithSize(width, height, vips.InterestingNone, vips.SizeDown); err != nil { + return nil, image.Rectangle{}, err + } + default: // OpFill + // Center-crop to fill the box exactly. + if err := m.ThumbnailWithSize(width, height, vips.InterestingAttention, vips.SizeBoth); err != nil { + return nil, image.Rectangle{}, err + } + } + + if err := m.RemoveMetadata(); err != nil { + return nil, image.Rectangle{}, err + } + + // Keep the image in libvips form so the encode hooks can export it via + // ExportJpeg/ExportPng (matching the legacy pipeline's progressive JPEG and + // quality-80 output) instead of a lossy PNG round-trip + stdlib re-encode. + return m, srcBounds, nil +} + +// resizeGIFVips resizes every frame of an animated gif while preserving the +// animation, compositing each frame onto a running canvas honoring the gif +// disposal method and re-palletting with Floyd-Steinberg dithering. +func resizeGIFVips(m *gif.GIF, width, height int, operation string) *gif.GIF { + srcX, srcY := m.Config.Width, m.Config.Height + b := image.Rect(0, 0, srcX, srcY) + tmp := image.NewRGBA(b) + + for i, frame := range m.Image { + frameBounds := frame.Bounds() + prev := tmp + draw.Draw(tmp, frameBounds, frame, frameBounds.Min, draw.Over) + + var processed image.Image + switch operation { + case OpStretch: + processed = imaging.Resize(tmp, width, height, imaging.Lanczos) + case OpFitIn: + if srcX > width || srcY > height { + processed = imaging.Fit(tmp, width, height, imaging.Lanczos) + } else { + processed = tmp + } + default: // OpFill + processed = imaging.Fill(tmp, width, height, imaging.Center, imaging.Lanczos) + } + + m.Image[i] = imageToPalettedVips(processed, frame.Palette) + + switch m.Disposal[i] { + case gif.DisposalBackground: + tmp = image.NewRGBA(b) + case gif.DisposalPrevious: + tmp = prev + } + } + + m.Config.Width = width + m.Config.Height = height + + return m +} + +func imageToPalettedVips(img image.Image, p color.Palette) *image.Paletted { + b := img.Bounds() + pm := image.NewPaletted(b, p) + draw.FloydSteinberg.Draw(pm, b, img, image.Point{}) + return pm +} + +// encodeJPEGVips exports the processed libvips image as JPEG using libvips's +// default export parameters (progressive/interlaced, quality 80), matching the +// legacy pipeline's output byte-for-byte where the resize operation is unchanged. +func encodeJPEGVips(w io.Writer, processed any) error { + m, ok := processed.(*vips.ImageRef) + if !ok { + return fmt.Errorf("cannot encode %T as jpeg", processed) + } + defer m.Close() + buf, _, err := m.ExportJpeg(vips.NewJpegExportParams()) + if err != nil { + return err + } + _, err = w.Write(buf) + return err +} + +// encodePNGVips exports the processed libvips image as PNG via libvips. +func encodePNGVips(w io.Writer, processed any) error { + m, ok := processed.(*vips.ImageRef) + if !ok { + return fmt.Errorf("cannot encode %T as png", processed) + } + defer m.Close() + buf, _, err := m.ExportPng(vips.NewPngExportParams()) + if err != nil { + return err + } + _, err = w.Write(buf) + return err +} diff --git a/services/thumbnails/pkg/service/http/v0/service.go b/services/thumbnails/pkg/service/http/v0/service.go index 24fe0b9190..8b1b3f9e3f 100644 --- a/services/thumbnails/pkg/service/http/v0/service.go +++ b/services/thumbnails/pkg/service/http/v0/service.go @@ -61,6 +61,8 @@ func NewService(opts ...Option) Service { options.Config.Thumbnail.MaxInputWidth, options.Config.Thumbnail.MaxInputHeight, ), + maxWidth: options.Config.Thumbnail.MaxInputWidth, + maxHeight: options.Config.Thumbnail.MaxInputHeight, } m.Route(options.Config.HTTP.Root, func(r chi.Router) { @@ -68,6 +70,16 @@ func NewService(opts ...Option) Service { r.Get("/data", svc.GetThumbnail) }) + // Push-based thumbnail generation endpoint (imagor-compatible). The optional + // operation segment selects the resize/crop mode; its absence is the default + // center-crop fill. The filters segment is captured whole and parsed by the + // handler so any set/order of imagor filters is accepted. + handler := svc.pushHandler + m.Post("/unsafe/{operation}/{width}x{height}/filters:{filters}/", handler) + m.Post("/unsafe/{operation}/{width}x{height}/filters:{filters}", handler) + m.Post("/unsafe/{width}x{height}/filters:{filters}/", handler) + m.Post("/unsafe/{width}x{height}/filters:{filters}", handler) + _ = chi.Walk(m, func(method string, route string, _ http.Handler, middlewares ...func(http.Handler) http.Handler) error { options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint") return nil @@ -78,10 +90,12 @@ func NewService(opts ...Option) Service { // Thumbnails implements the business logic for Service. type Thumbnails struct { - config *config.Config - logger log.Logger - mux *chi.Mux - manager thumbnail.Manager + config *config.Config + logger log.Logger + mux *chi.Mux + manager thumbnail.Manager + maxWidth int + maxHeight int } // ServeHTTP implements the Service interface. From 5c3b7f8890f4c73c970387cb6c393d80761d779d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 3 Sep 2026 17:34:09 +0200 Subject: [PATCH 2/5] refactor(webdav): drive the thumbnail workflow via the push-based generator webdav now owns the complete thumbnail pipeline: stat -> validate -> cache check -> download -> preprocess -> generate -> cache -> respond. The thumbnails service is used only as a stateless imagor-compatible image resizer via its /unsafe push endpoint. - New ThumbnailWorkflow drives the pipeline; space-scoped refs are anchored at the space ResourceId and downloads authenticate with the user's x-access-token. - Preprocessing (audio/geogebra/text/gif) moves from the thumbnails service into webdav (services/webdav/pkg/preprocessor). - Thumbnail caching moves to webdav (memory/file/s3 backends, file cache off by default); resolutions are snapped orientation-aware in webdav. - Generator URL defaults to the local thumbnails service; new WEBDAV_* config for generator, cache and resolutions. - Acceptance tests updated for the new sizing/processor behavior. --- services/webdav/README.md | 51 +- services/webdav/pkg/config/config.go | 25 +- .../pkg/config/defaults/defaultconfig.go | 13 +- services/webdav/pkg/dav/requests/thumbnail.go | 88 +- .../webdav/pkg/dav/requests/thumbnail_test.go | 56 + services/webdav/pkg/generator/extensions.go | 76 + .../webdav/pkg/generator/generator_test.go | 156 ++ services/webdav/pkg/generator/handler.go | 13 + services/webdav/pkg/generator/size.go | 46 + services/webdav/pkg/generator/url.go | 48 + services/webdav/pkg/preprocessor/errors.go | 9 + .../webdav/pkg/preprocessor/fontloader.go | 192 +++ .../webdav/pkg/preprocessor/preprocessor.go | 348 ++++ .../pkg/preprocessor/preprocessor_imaging.go | 22 + .../pkg/preprocessor/preprocessor_test.go | 197 +++ .../pkg/preprocessor/preprocessor_vips.go | 20 + .../pkg/preprocessor/test_assets/empty.mp3 | Bin 0 -> 199530 bytes .../test_assets/empty_no_image.mp3 | Bin 0 -> 4386 bytes .../pkg/preprocessor/test_assets/ggs_test.ggs | Bin 0 -> 7486 bytes .../pkg/preprocessor/test_assets/noise.gif | Bin 0 -> 84386 bytes .../pkg/preprocessor/test_assets/noise.png | Bin 0 -> 184891 bytes .../webdav/pkg/preprocessor/textanalyzer.go | 309 ++++ .../pkg/preprocessor/textanalyzer_test.go | 614 +++++++ services/webdav/pkg/service/v0/config_test.go | 182 +++ services/webdav/pkg/service/v0/search.go | 2 +- services/webdav/pkg/service/v0/service.go | 456 +++--- services/webdav/pkg/thumbnail/cache/cache.go | 61 + .../webdav/pkg/thumbnail/cache/cache_test.go | 587 +++++++ .../webdav/pkg/thumbnail/cache/filecache.go | 78 + .../webdav/pkg/thumbnail/cache/inmemory.go | 39 + services/webdav/pkg/thumbnail/cache/noop.go | 12 + .../webdav/pkg/thumbnail/cache/s3cache.go | 134 ++ services/webdav/pkg/thumbnail/mimetype.go | 13 + services/webdav/pkg/thumbnail/mimetypes.go | 22 + .../webdav/pkg/thumbnail/mimetypes_vips.go | 23 + services/webdav/pkg/thumbnail/resolutions.go | 109 ++ .../webdav/pkg/thumbnail/resolutions_test.go | 156 ++ .../pkg/thumbnail/workflow/encode_imaging.go | 43 + .../pkg/thumbnail/workflow/encode_vips.go | 50 + .../pkg/thumbnail/workflow/suite_test.go | 13 + .../webdav/pkg/thumbnail/workflow/workflow.go | 776 +++++++++ .../pkg/thumbnail/workflow/workflow_test.go | 1414 +++++++++++++++++ .../expected-failures-decomposed-storage.md | 13 - .../expected-failures-posix-storage.md | 14 - .../coreApiWebdavPreviews/previews.feature | 6 +- .../previewsAutoAdustedSizing.feature | 60 +- .../acceptance/fixtures/text-file-fixture.png | Bin 31009 -> 68518 bytes tests/acceptance/fixtures/unicode-fixture.png | Bin 715 -> 10023 bytes 48 files changed, 6193 insertions(+), 353 deletions(-) create mode 100644 services/webdav/pkg/dav/requests/thumbnail_test.go create mode 100644 services/webdav/pkg/generator/extensions.go create mode 100644 services/webdav/pkg/generator/generator_test.go create mode 100644 services/webdav/pkg/generator/handler.go create mode 100644 services/webdav/pkg/generator/size.go create mode 100644 services/webdav/pkg/generator/url.go create mode 100644 services/webdav/pkg/preprocessor/errors.go create mode 100644 services/webdav/pkg/preprocessor/fontloader.go create mode 100644 services/webdav/pkg/preprocessor/preprocessor.go create mode 100644 services/webdav/pkg/preprocessor/preprocessor_imaging.go create mode 100644 services/webdav/pkg/preprocessor/preprocessor_test.go create mode 100644 services/webdav/pkg/preprocessor/preprocessor_vips.go create mode 100644 services/webdav/pkg/preprocessor/test_assets/empty.mp3 create mode 100644 services/webdav/pkg/preprocessor/test_assets/empty_no_image.mp3 create mode 100644 services/webdav/pkg/preprocessor/test_assets/ggs_test.ggs create mode 100644 services/webdav/pkg/preprocessor/test_assets/noise.gif create mode 100644 services/webdav/pkg/preprocessor/test_assets/noise.png create mode 100644 services/webdav/pkg/preprocessor/textanalyzer.go create mode 100644 services/webdav/pkg/preprocessor/textanalyzer_test.go create mode 100644 services/webdav/pkg/service/v0/config_test.go create mode 100644 services/webdav/pkg/thumbnail/cache/cache.go create mode 100644 services/webdav/pkg/thumbnail/cache/cache_test.go create mode 100644 services/webdav/pkg/thumbnail/cache/filecache.go create mode 100644 services/webdav/pkg/thumbnail/cache/inmemory.go create mode 100644 services/webdav/pkg/thumbnail/cache/noop.go create mode 100644 services/webdav/pkg/thumbnail/cache/s3cache.go create mode 100644 services/webdav/pkg/thumbnail/mimetype.go create mode 100644 services/webdav/pkg/thumbnail/mimetypes.go create mode 100644 services/webdav/pkg/thumbnail/mimetypes_vips.go create mode 100644 services/webdav/pkg/thumbnail/resolutions.go create mode 100644 services/webdav/pkg/thumbnail/resolutions_test.go create mode 100644 services/webdav/pkg/thumbnail/workflow/encode_imaging.go create mode 100644 services/webdav/pkg/thumbnail/workflow/encode_vips.go create mode 100644 services/webdav/pkg/thumbnail/workflow/suite_test.go create mode 100644 services/webdav/pkg/thumbnail/workflow/workflow.go create mode 100644 services/webdav/pkg/thumbnail/workflow/workflow_test.go diff --git a/services/webdav/README.md b/services/webdav/README.md index a113fba7d1..a015a2834d 100644 --- a/services/webdav/README.md +++ b/services/webdav/README.md @@ -8,16 +8,57 @@ Currently, the webdav service handles request for two functionalities, which are ### Thumbnails -The webdav service provides various `GET` endpoints to get the thumbnails of a file in authenticated and unauthenticated contexts. It also provides thumbnails for spaces on different endpoints. +The webdav service provides various `GET` endpoints to get the thumbnails of a file in authenticated and unauthenticated contexts. It also provides thumbnails for spaces on different endpoints. -See the [thumbnail](https://github.com/opencloud-eu/opencloud/tree/main/services/thumbnails) service for more information about thumbnails. +Generated thumbnails are cached by the webdav service itself. The cache backend defaults to `file`, storing entries under `$OC_BASE_DATA_PATH/thumbnails/files` (override with `WEBDAV_THUMBNAIL_CACHE_BACKEND` and `WEBDAV_THUMBNAIL_CACHE_DIR`). Use the `s3` backend when running multiple instances behind a load balancer so they share one cache. + +#### Thumbnail Query String Parameters + +Clients can request thumbnail previews for files by adding `?preview=1` to the file URL. Requests for files with no thumbnail available respond with HTTP status `404`. + +The following query parameters are supported: + +| Parameter | Required | Default Value | Description | +|-----------|----------|------------------------------------------------------|---------------------------------------------------------------------------------| +| preview | YES | 1 | generates preview | +| x | YES | first x-value configured in `WEBDAV_THUMBNAIL_RESOLUTIONS` | horizontal target size | +| y | YES | first y-value configured in `WEBDAV_THUMBNAIL_RESOLUTIONS` | vertical target size | +| scalingup | NO | 0 | accepted for compatibility but ignored; the generator never upscales via this flag | +| a | NO | 1 | aspect ratio (legacy; only honored when no explicit `processor` is given) | +| c | NO | Caching string | Clients should send the etag, so they get a fresh thumbnail after a file change | +| processor | NO | `resize` for gifs and `thumbnail` for all others | preferred thumbnail processor | + +#### Thumbnail Resolution + +Various resolutions can be defined via `WEBDAV_THUMBNAIL_RESOLUTIONS`. A requestor can request any arbitrary resolution and the webdav service will use the one closest to the requested resolution. If more than one resolution is required, each resolution must be requested individually. + +Example: + +Requested: 18x12\ +Available: 30x20, 15x10, 9x6\ +Returned: 15x10 + +#### Thumbnail Processors + +Normally, an image might get cropped when creating a preview, depending on the aspect ratio of the original image. This can have negative impacts on previews as only a part of the image will be shown. When using an _optional_ processor in the request, cropping can be avoided by defining on how the preview image generation will be done. The following processors are available: + +* `resize` resizes the image to the specified width and height and returns the transformed image (distorts to the exact box). +* `fit-in` scales the image down to fit within the specified maximum width and height, preserving aspect ratio and never upscaling (letterboxed). +* `fill`: creates an image with the specified dimensions and fills it with the scaled source image. To achieve the correct aspect ratio without stretching, the source image will be cropped. + +The following are **legacy aliases** kept for compatibility. They map onto the processors above but **ignore the `a` and `scalingup` query parameters**; callers that need aspect or upscaling control should use the imagor names directly (`fill`, `fit-in`, `resize`): + +* `thumbnail` is a legacy alias for `fill` (center-crop to the exact box, may upscale). +* `fit` is a legacy alias for `fit-in`. + +To apply one of those, a query parameter has to be added to the request, like `?processor=fit-in`. If no processor is given, the default behaviour applies which is `resize` for gifs and `thumbnail` (i.e. `fill`) for all others. When an explicit processor overrides a contradictory legacy `a`, the thumbnail response includes the header `X-OpenCloud-Thumbnail-Aspect-Ignored` so developers can fix their client to send a consistent request. ### Search -The webdav service provides access to the search functionality. It offers multiple `REPORT` endpoints for getting search results. +The webdav service provides access to the search functionality. It offers multiple `REPORT` endpoints for getting search results. -See the [search](https://github.com/opencloud-eu/opencloud/tree/main/services/search) service for more details about search functionality. +See the [search](https://github.com/opencloud-eu/opencloud/tree/main/services/search) service for more details about search functionality. ## Scalability -The webdav service does not persist any data and does not cache any information. Therefore multiple instances of this service can be spawned in a bigger deployment like when using container orchestration with Kubernetes, without any extra configuration. +The webdav service persists generated thumbnails to its thumbnail cache (file backend by default). When running multiple instances behind a load balancer, point `WEBDAV_THUMBNAIL_CACHE_BACKEND` at a shared `s3` bucket so all instances read and write the same cache; otherwise each instance keeps its own on-disk cache. diff --git a/services/webdav/pkg/config/config.go b/services/webdav/pkg/config/config.go index 324704464c..d7be0a48d5 100644 --- a/services/webdav/pkg/config/config.go +++ b/services/webdav/pkg/config/config.go @@ -21,9 +21,24 @@ type Config struct { HTTP HTTP `yaml:"http"` - DisablePreviews bool `yaml:"disablePreviews" env:"OC_DISABLE_PREVIEWS;WEBDAV_DISABLE_PREVIEWS" desc:"Set this option to 'true' to disable rendering of thumbnails triggered via webdav access. Note that when disabled, all access to preview related webdav paths will return a 404." introductionVersion:"1.0.0"` - OpenCloudPublicURL string `yaml:"opencloud_public_url" env:"OC_URL;OC_PUBLIC_URL" desc:"URL, where OpenCloud is reachable for users." introductionVersion:"1.0.0"` - WebdavNamespace string `yaml:"webdav_namespace" env:"WEBDAV_WEBDAV_NAMESPACE" desc:"CS3 path layout to use when forwarding /webdav requests" introductionVersion:"1.0.0"` - RevaGateway string `yaml:"reva_gateway" env:"OC_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"1.0.0"` - Context context.Context `yaml:"-"` + OpenCloudPublicURL string `yaml:"opencloud_public_url" env:"OC_URL;OC_PUBLIC_URL" desc:"URL, where OpenCloud is reachable for users." introductionVersion:"1.0.0"` + WebdavNamespace string `yaml:"webdav_namespace" env:"WEBDAV_WEBDAV_NAMESPACE" desc:"CS3 path layout to use when forwarding /webdav requests" introductionVersion:"1.0.0"` + RevaGateway string `yaml:"reva_gateway" env:"OC_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"1.0.0"` + + ThumbnailGeneratorURL string `yaml:"thumbnail_generator_url" env:"WEBDAV_THUMBNAIL_GENERATOR_URL" desc:"Base URL of the thumbnail generator service, e.g. http://thumbnails:9130" introductionVersion:"1.0.0"` + ThumbnailGeneratorTimeout string `yaml:"thumbnail_generator_timeout" env:"WEBDAV_THUMBNAIL_GENERATOR_TIMEOUT" desc:"HTTP timeout for requests to the thumbnail generator, e.g. 30s" introductionVersion:"1.0.0"` + ThumbnailGeneratorAuthHeader string `yaml:"thumbnail_generator_auth_header" env:"WEBDAV_THUMBNAIL_GENERATOR_AUTH_HEADER" desc:"Optional auth header value sent to the generator (for external generators behind a reverse proxy)" introductionVersion:"1.0.0"` + MaxInputFileSize string `yaml:"max_input_file_size" env:"WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"Maximum file size of an input image for thumbnail generation. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB], example: 50MB" introductionVersion:"1.0.0"` + + ThumbnailCacheBackend string `yaml:"thumbnail_cache_backend" env:"WEBDAV_THUMBNAIL_CACHE_BACKEND" desc:"Cache backend for imagor thumbnails: 'none', 'memory', 'file', or 's3'. Default: none." introductionVersion:"1.0.0"` + ThumbnailCacheDir string `yaml:"thumbnail_cache_dir" env:"WEBDAV_THUMBNAIL_CACHE_DIR" desc:"Directory for file-based thumbnail cache (Default: $OC_BASE_DATA_PATH/thumbnails/files). Only used when ThumbnailCacheBackend is 'file'." introductionVersion:"1.0.0"` + ThumbnailResolutions []string `yaml:"thumbnail_resolutions" env:"THUMBNAILS_RESOLUTIONS;WEBDAV_THUMBNAIL_RESOLUTIONS" desc:"Supported target resolutions in the format WidthxHeight like 32x32. The requested size is snapped onto one of these (orientation-aware) before being sent to the generator." introductionVersion:"1.0.0"` + ThumbnailCacheS3Bucket string `yaml:"thumbnail_cache_s3_bucket" env:"WEBDAV_THUMBNAIL_CACHE_S3_BUCKET" desc:"S3 bucket name for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"` + ThumbnailCacheS3Region string `yaml:"thumbnail_cache_s3_region" env:"WEBDAV_THUMBNAIL_CACHE_S3_REGION" desc:"S3 region for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"` + ThumbnailCacheS3Endpoint string `yaml:"thumbnail_cache_s3_endpoint" env:"WEBDAV_THUMBNAIL_CACHE_S3_ENDPOINT" desc:"S3 endpoint URL (for MinIO or compatible services) when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"` + ThumbnailCacheS3AccessKey string `yaml:"thumbnail_cache_s3_access_key" env:"WEBDAV_THUMBNAIL_CACHE_S3_ACCESS_KEY" desc:"S3 access key for thumbnail cache authentication." introductionVersion:"1.0.0"` + ThumbnailCacheS3SecretKey string `yaml:"thumbnail_cache_s3_secret_key" env:"WEBDAV_THUMBNAIL_CACHE_S3_SECRET_KEY" desc:"S3 secret key for thumbnail cache authentication." introductionVersion:"1.0.0"` + FontMapFile string `yaml:"font_map_file" env:"WEBDAV_THUMBNAILS_TXT_FONTMAP_FILE;THUMBNAILS_TXT_FONTMAP_FILE" desc:"The path to a font map file for txt thumbnails." introductionVersion:"1.0.0"` + + Context context.Context `yaml:"-"` } diff --git a/services/webdav/pkg/config/defaults/defaultconfig.go b/services/webdav/pkg/config/defaults/defaultconfig.go index 7bf35b7b01..a0fc57cfdc 100644 --- a/services/webdav/pkg/config/defaults/defaultconfig.go +++ b/services/webdav/pkg/config/defaults/defaultconfig.go @@ -1,8 +1,10 @@ package defaults import ( + "path" "strings" + cored "github.com/opencloud-eu/opencloud/pkg/config/defaults" "github.com/opencloud-eu/opencloud/pkg/shared" "github.com/opencloud-eu/opencloud/pkg/structs" "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" @@ -42,6 +44,16 @@ func DefaultConfig() *config.Config { OpenCloudPublicURL: "https://localhost:9200", WebdavNamespace: "/users/{{.Id.OpaqueId}}", RevaGateway: shared.DefaultRevaConfig().Address, + + ThumbnailGeneratorURL: "http://127.0.0.1:9186", + ThumbnailGeneratorTimeout: "30s", + ThumbnailCacheBackend: "none", + ThumbnailCacheDir: path.Join(cored.BaseDataPath(), "thumbnails", "files"), + ThumbnailResolutions: []string{ + "16x16", "32x32", "64x64", "128x128", "320x320", "1024x1024", // square + "280x500", "560x1000", "1080x1920", "2160x3840", "4320x7680", // portrait + "500x280", "1000x560", "1920x1080", "3840x2160", "7680x4320", // landscape + }, } } @@ -66,5 +78,4 @@ func Sanitize(cfg *config.Config) { if cfg.HTTP.Root != "/" { cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") } - } diff --git a/services/webdav/pkg/dav/requests/thumbnail.go b/services/webdav/pkg/dav/requests/thumbnail.go index dbca4ca850..f1a4574767 100644 --- a/services/webdav/pkg/dav/requests/thumbnail.go +++ b/services/webdav/pkg/dav/requests/thumbnail.go @@ -4,8 +4,10 @@ import ( "fmt" "net/http" "net/url" + "path" "path/filepath" "strconv" + "strings" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/go-chi/chi/v5" @@ -22,10 +24,17 @@ const ( DefaultHeight = 32 ) -// ThumbnailRequest combines all parameters provided when requesting a thumbnail +// ThumbnailRequest combines all parameters provided when requesting a thumbnail. type ThumbnailRequest struct { - // The file path of the source file - Filepath string + // Ref is the CS3 reference to the source file. It carries both the space root + // (ResourceId) and the relative path, mirroring reva's ocdav split of + // space-ID-based requests from path-only requests: + // - Space-ID requests (dav/spaces/{id}/...): ResourceId is the parsed space + // ID and Path is the URL-relative path. The workflow uses it directly. + // - Path-only requests (dav/files/{user}/..., /webdav/..., public links): + // ResourceId is nil and Path is an absolute CS3 path. The workflow resolves + // the space root via a lookup (which also works for public links). + Ref *providerv1beta1.Reference // The file name of the source file including the extension Filename string // The file extension @@ -34,21 +43,11 @@ type ThumbnailRequest struct { Width int32 // The requested height of the thumbnail Height int32 - // In case of a public share the public link token. - PublicLinkToken string // Indicates which image processor to use Processor string - // The Identifier from the requested URL - Identifier string -} - -func addMissingStorageID(id string) string { - rid := &providerv1beta1.ResourceId{} - rid.StorageId, rid.SpaceId, rid.OpaqueId, _ = storagespace.SplitID(id) - if rid.StorageId == "" && rid.SpaceId == utils.ShareStorageSpaceID { - rid.StorageId = utils.ShareStorageProviderID - } - return storagespace.FormatResourceID(rid) + // Aspect reports whether the client wants the aspect ratio preserved (the + // legacy ownCloud "a" flag: a=1/absent -> preserve, a=0 -> fill the box). + Aspect bool } // ParseThumbnailRequest extracts all required parameters from a http request. @@ -57,10 +56,33 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { fp := ctx.Value(constants.ContextKeyPath).(string) - id := "" - v := ctx.Value(constants.ContextKeyID) - if v != nil { - id = addMissingStorageID(v.(string)) + var ref *providerv1beta1.Reference + if v := ctx.Value(constants.ContextKeyID); v != nil { + id := v.(string) + if strings.Contains(id, "$") { + // Space-ID based request (dav/spaces/{id}/...): the URL carries the + // space root, so build the reference directly from the parsed ID. + rid, err := storagespace.ParseID(addMissingStorageID(id)) + if err != nil { + return nil, fmt.Errorf("parse space id %q: %w", id, err) + } + ref = &providerv1beta1.Reference{ + ResourceId: &rid, + Path: utils.MakeRelativePath(fp), + } + } else { + // The identifier is a username (dav/files/{username}/...); the workflow + // resolves it to the user's home path. + ref = &providerv1beta1.Reference{Path: fp} + } + } else { + if token := chi.URLParam(r, "token"); token != "" { + ref = &providerv1beta1.Reference{Path: path.Join("/public", token, strings.TrimLeft(fp, "/"))} + } else { + // Path-only request (/webdav/...): the absolute CS3 path is carried in + // ContextKeyPath; the workflow resolves the space root. + ref = &providerv1beta1.Reference{Path: fp} + } } q := r.URL.Query() @@ -70,17 +92,27 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { } return &ThumbnailRequest{ - Filepath: fp, - Filename: filepath.Base(fp), - Extension: filepath.Ext(fp), - Width: int32(width), - Height: int32(height), - Processor: q.Get("processor"), - PublicLinkToken: chi.URLParam(r, "token"), - Identifier: id, + Ref: ref, + Filename: filepath.Base(fp), + Extension: filepath.Ext(fp), + Width: int32(width), + Height: int32(height), + Processor: q.Get("processor"), + Aspect: q.Get("a") != "0", }, nil } +// addMissingStorageID fills in the shares storage provider ID when a share mount +// space ID is missing it, so the parsed reference routes to the right provider. +func addMissingStorageID(id string) string { + rid := &providerv1beta1.ResourceId{} + rid.StorageId, rid.SpaceId, rid.OpaqueId, _ = storagespace.SplitID(id) + if rid.StorageId == "" && rid.SpaceId == utils.ShareStorageSpaceID { + rid.StorageId = utils.ShareStorageProviderID + } + return storagespace.FormatResourceID(rid) +} + func parseDimensions(q url.Values) (int64, int64, error) { width, err := parseDimension(q.Get("x"), "width", DefaultWidth) if err != nil { diff --git a/services/webdav/pkg/dav/requests/thumbnail_test.go b/services/webdav/pkg/dav/requests/thumbnail_test.go new file mode 100644 index 0000000000..aaff37ffe7 --- /dev/null +++ b/services/webdav/pkg/dav/requests/thumbnail_test.go @@ -0,0 +1,56 @@ +package requests + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/opencloud-eu/opencloud/services/webdav/pkg/constants" +) + +func newThumbnailRequest(t *testing.T, rawQuery string) *http.Request { + t.Helper() + req := httptest.NewRequest("GET", "/files/test.png?"+rawQuery, nil) + ctx := context.WithValue(req.Context(), constants.ContextKeyPath, "/files/test.png") + return req.WithContext(ctx) +} + +func TestParseThumbnailRequestAspect(t *testing.T) { + tests := []struct { + name string + query string + wantA bool + }{ + {"a absent defaults to preserve aspect", "x=1024&y=1024", true}, + {"a=1 preserves aspect", "x=1024&y=1024&a=1", true}, + {"a=0 fills the box", "x=1024&y=1024&a=0", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newThumbnailRequest(t, tt.query) + tr, err := ParseThumbnailRequest(r) + if err != nil { + t.Fatalf("ParseThumbnailRequest() error = %v", err) + } + if tr.Aspect != tt.wantA { + t.Errorf("Aspect = %v, want %v", tr.Aspect, tt.wantA) + } + }) + } +} + +func TestParseThumbnailRequestProcessor(t *testing.T) { + r := newThumbnailRequest(t, "x=320&y=320&processor=fit") + tr, err := ParseThumbnailRequest(r) + if err != nil { + t.Fatalf("ParseThumbnailRequest() error = %v", err) + } + if tr.Processor != "fit" { + t.Errorf("Processor = %q, want %q", tr.Processor, "fit") + } + if tr.Width != 320 || tr.Height != 320 { + t.Errorf("dimensions = %dx%d, want 320x320", tr.Width, tr.Height) + } +} diff --git a/services/webdav/pkg/generator/extensions.go b/services/webdav/pkg/generator/extensions.go new file mode 100644 index 0000000000..812302e64d --- /dev/null +++ b/services/webdav/pkg/generator/extensions.go @@ -0,0 +1,76 @@ +package generator + +import ( + "mime" + "strings" +) + +// ExtensionInfo holds the output format and content type for a given file extension. +type ExtensionInfo struct { + OutputFormat string // generator output format (gif, png, jpeg) + ContentType string // HTTP Content-Type header value +} + +var extMap = map[string]ExtensionInfo{ + "gif": {OutputFormat: "gif", ContentType: "image/gif"}, + "png": {OutputFormat: "png", ContentType: "image/png"}, + "jpeg": {OutputFormat: "jpeg", ContentType: "image/jpeg"}, + "jpg": {OutputFormat: "jpeg", ContentType: "image/jpeg"}, +} + +var defaultExtInfo = ExtensionInfo{OutputFormat: "jpeg", ContentType: "image/jpeg"} + +// GetExtensionInfo returns the extension info for a given file extension. +func GetExtensionInfo(ext string) ExtensionInfo { + if info, ok := extMap[strings.ToLower(strings.TrimLeft(ext, "."))]; ok { + return info + } + return defaultExtInfo +} + +// OutputFormat returns the generator output format for a given file extension. +func OutputFormat(ext string) string { + return GetExtensionInfo(ext).OutputFormat +} + +// ContentType returns the HTTP Content-Type for a given file extension. +func ContentType(ext string) string { + return GetExtensionInfo(ext).ContentType +} + +// GuessExtension guesses a file extension from a MIME type. +func GuessExtension(mimeType string) string { + exts := []string{".jpg", ".png", ".gif"} + for _, ext := range exts { + if strings.HasSuffix(mimeType, "/"+strings.TrimLeft(ext, ".")) || + strings.Contains(mimeType, "jpeg") && ext == ".jpg" { + return ext[1:] + } + } + + switch { + case strings.Contains(mimeType, "png"): + return "png" + case strings.Contains(mimeType, "gif"): + return "gif" + default: + return "jpg" + } +} + +// ExtForMime mirrors main's GetExtForMime: returns the output extension for a +// source mime, or "" when the source has no dedicated image type (txt, audio, +// bmp, tiff) so the caller falls back to the requested extension. +func ExtForMime(mimeType string) string { + m, _, _ := mime.ParseMediaType(mimeType) + switch m { + case "image/jpeg", "image/webp": + return "jpg" + case "image/png": + return "png" + case "image/gif": + return "gif" + default: + return "" + } +} diff --git a/services/webdav/pkg/generator/generator_test.go b/services/webdav/pkg/generator/generator_test.go new file mode 100644 index 0000000000..848ad9665c --- /dev/null +++ b/services/webdav/pkg/generator/generator_test.go @@ -0,0 +1,156 @@ +package generator + +import ( + "testing" +) + +func TestParseMaxInputFileSize(t *testing.T) { + tests := []struct { + name string + input string + want uint64 + wantErr bool + }{ + {"empty", "", 0, false}, + {"plain bytes", "1234", 1234, false}, + {"KB", "50KB", 50 * 1024, false}, + {"kB case insensitive", "50kB", 50 * 1024, false}, + {"KiB", "50KiB", 50 * 1024, false}, + {"MB", "5MB", 5 * 1024 * 1024, false}, + {"MiB", "5MiB", 5 * 1024 * 1024, false}, + {"GB", "2GB", 2 * 1024 * 1024 * 1024, false}, + {"GiB", "2GiB", 2 * 1024 * 1024 * 1024, false}, + {"whitespace around value", " 5MB ", 5 * 1024 * 1024, false}, + {"space before suffix", "5 MB", 5 * 1024 * 1024, false}, + {"invalid input", "abc", 0, true}, + {"negative number", "-5MB", 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseMaxInputFileSize(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseMaxInputFileSize() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseMaxInputFileSize() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetExtensionInfo(t *testing.T) { + tests := []struct { + ext string + format string + ct string + }{ + {"gif", "gif", "image/gif"}, + {".gif", "gif", "image/gif"}, + {"png", "png", "image/png"}, + {"jpeg", "jpeg", "image/jpeg"}, + {"jpg", "jpeg", "image/jpeg"}, + {"webp", "jpeg", "image/jpeg"}, + {"", "jpeg", "image/jpeg"}, + } + + for _, tt := range tests { + t.Run(tt.ext, func(t *testing.T) { + info := GetExtensionInfo(tt.ext) + if info.OutputFormat != tt.format { + t.Errorf("OutputFormat = %q, want %q", info.OutputFormat, tt.format) + } + if info.ContentType != tt.ct { + t.Errorf("ContentType = %q, want %q", info.ContentType, tt.ct) + } + }) + } +} + +func TestOutputFormat(t *testing.T) { + if got := OutputFormat("png"); got != "png" { + t.Errorf("OutputFormat(\"png\") = %q, want \"png\"", got) + } + if got := OutputFormat("jpg"); got != "jpeg" { + t.Errorf("OutputFormat(\"jpg\") = %q, want \"jpeg\"", got) + } +} + +func TestContentType(t *testing.T) { + if got := ContentType("gif"); got != "image/gif" { + t.Errorf("ContentType(\"gif\") = %q, want \"image/gif\"", got) + } + if got := ContentType("unknown"); got != "image/jpeg" { + t.Errorf("ContentType(\"unknown\") = %q, want \"image/jpeg\"", got) + } +} + +func TestGuessExtension(t *testing.T) { + tests := []struct { + mime string + want string + }{ + {"image/jpeg", "jpg"}, + {"image/png", "png"}, + {"image/gif", "gif"}, + {"application/octet-stream", "jpg"}, + } + + for _, tt := range tests { + t.Run(tt.mime, func(t *testing.T) { + if got := GuessExtension(tt.mime); got != tt.want { + t.Errorf("GuessExtension(%q) = %q, want %q", tt.mime, got, tt.want) + } + }) + } +} + +func TestBuildURL(t *testing.T) { + tests := []struct { + name string + base string + width int32 + height int32 + operation string + ext string + want string + }{ + { + name: "fill operation center-crops to the box", + base: "http://generator", + width: 128, + height: 128, + operation: OpFill, + ext: "jpeg", + want: "http://generator/unsafe/128x128/filters:format(jpeg)/", + }, + { + name: "fit-in preserves aspect ratio within the box without upscaling", + base: "http://generator", + width: 1024, + height: 1024, + operation: OpFitIn, + ext: "jpeg", + want: "http://generator/unsafe/fit-in/1024x1024/filters:format(jpeg)/", + }, + { + name: "stretch resizes to the exact box", + base: "http://generator/", + width: 64, + height: 32, + operation: OpStretch, + ext: "png", + want: "http://generator/unsafe/stretch/64x32/filters:format(png)/", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := BuildURL(tt.base, tt.width, tt.height, tt.operation, tt.ext) + if got != tt.want { + t.Errorf("BuildURL() = %q, want %q", got, tt.want) + } + }) + } +} diff --git a/services/webdav/pkg/generator/handler.go b/services/webdav/pkg/generator/handler.go new file mode 100644 index 0000000000..42ff457107 --- /dev/null +++ b/services/webdav/pkg/generator/handler.go @@ -0,0 +1,13 @@ +package generator + +import ( + "net/http" +) + +// WriteThumbnailResponse writes the thumbnail bytes to the HTTP response with correct Content-Type. +func WriteThumbnailResponse(w http.ResponseWriter, data []byte, ext string) { + ct := ContentType(ext) + w.Header().Set("Content-Type", ct) + w.WriteHeader(http.StatusOK) + _, _ = w.Write(data) +} diff --git a/services/webdav/pkg/generator/size.go b/services/webdav/pkg/generator/size.go new file mode 100644 index 0000000000..930b2b3666 --- /dev/null +++ b/services/webdav/pkg/generator/size.go @@ -0,0 +1,46 @@ +package generator + +import ( + "fmt" + "strconv" + "strings" +) + +type sizeUnit struct { + suffix string + len int + mul uint64 +} + +var sizeUnits = []sizeUnit{ + {"GIB", 3, 1 << 30}, {"GB", 2, 1 << 30}, + {"MIB", 3, 1 << 20}, {"MB", 2, 1 << 20}, + {"KIB", 3, 1 << 10}, {"KB", 2, 1 << 10}, +} + +// ParseMaxInputFileSize parses a size string like "50MB" into bytes. +// Empty string returns 0 (no limit). +func ParseMaxInputFileSize(s string) (uint64, error) { + if s == "" { + return 0, nil + } + + s = strings.TrimSpace(s) + var unitMultiplier uint64 = 1 + upper := strings.ToUpper(s) + + for _, u := range sizeUnits { + if strings.HasSuffix(upper, u.suffix) { + unitMultiplier = u.mul + s = s[:len(s)-u.len] + break + } + } + + size, err := strconv.ParseUint(strings.TrimSpace(s), 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid file size: %w", err) + } + + return size * unitMultiplier, nil +} diff --git a/services/webdav/pkg/generator/url.go b/services/webdav/pkg/generator/url.go new file mode 100644 index 0000000000..ae745dd0db --- /dev/null +++ b/services/webdav/pkg/generator/url.go @@ -0,0 +1,48 @@ +package generator + +import ( + "fmt" + "strings" +) + +// Operations are the imagor resize/crop operations the generator understands. +const ( + // OpFill center-crops to fill the box exactly (imagor default resize). + OpFill = "fill" + // OpFitIn fits within the box without cropping and never upscales (imagor + // fit-in), preserving the source aspect ratio. + OpFitIn = "fit-in" + // OpStretch resizes to the exact box without preserving aspect ratio. + OpStretch = "stretch" +) + +// operationSegment maps an operation to its imagor URL path segment. The fill +// operation is the bare WxH resize (imagor's default), so it contributes no +// leading segment. +func operationSegment(op string) string { + switch op { + case OpFitIn: + return "fit-in" + case OpStretch: + return "stretch" + default: // OpFill and anything unrecognized fall back to the default resize + return "" + } +} + +// BuildURL constructs the thumbnail generator processing URL for a matched box +// and operation. The operation segment selects the resize/crop mode (which also +// governs upscaling); the image is re-encoded to the requested output format via a +// single imagor filters segment. +func BuildURL(baseURL string, width, height int32, operation, outputExt string) string { + base := strings.TrimRight(baseURL, "/") + + box := fmt.Sprintf("%dx%d", width, height) + filterSegment := fmt.Sprintf("filters:format(%s)", outputExt) + + segment := operationSegment(operation) + if segment == "" { + return fmt.Sprintf("%s/unsafe/%s/%s/", base, box, filterSegment) + } + return fmt.Sprintf("%s/unsafe/%s/%s/%s/", base, segment, box, filterSegment) +} diff --git a/services/webdav/pkg/preprocessor/errors.go b/services/webdav/pkg/preprocessor/errors.go new file mode 100644 index 0000000000..72625bdd2f --- /dev/null +++ b/services/webdav/pkg/preprocessor/errors.go @@ -0,0 +1,9 @@ +package preprocessor + +import "errors" + +var ( + ErrNoImageFromAudioFile = errors.New("preprocessor: could not extract image from audio file") + ErrNoConverterForExtractedImageFromGgsFile = errors.New("preprocessor: could not find converter for image extracted from ggs file") + ErrNoConverterForExtractedImageFromAudioFile = errors.New("preprocessor: could not find converter for image extracted from audio file") +) diff --git a/services/webdav/pkg/preprocessor/fontloader.go b/services/webdav/pkg/preprocessor/fontloader.go new file mode 100644 index 0000000000..91824b611d --- /dev/null +++ b/services/webdav/pkg/preprocessor/fontloader.go @@ -0,0 +1,192 @@ +package preprocessor + +import ( + "encoding/json" + "os" + "path/filepath" + "time" + + "github.com/opencloud-eu/opencloud/pkg/sync" + "golang.org/x/image/font" + "golang.org/x/image/font/gofont/goregular" + "golang.org/x/image/font/opentype" +) + +// FontMap maps a script with the target font to be used for that script +// It also uses a DefaultFont in case there isn't a matching script in the map +// +// For cases like Japanese where multiple scripts are used, we rely on the text +// analyzer to use the script which is unique to japanese (Hiragana or Katakana) +// even if it has to overwrite the "official" detected script (Han). This means +// that "Han" should be used just for chinese while "Hiragana" and "Katakana" +// should be used for japanese +type FontMap struct { + FontMap map[string]string `json:"fontMap"` + DefaultFont string `json:"defaultFont"` +} + +// FontMapData contains the location of the loaded file (in FLoc) and the FontMap loaded +// from the file +type FontMapData struct { + FMap *FontMap + FLoc string +} + +// LoadedFace contains the location of the font used, and the loaded face (font.Face) +// ready to be used +type LoadedFace struct { + FontFile string + Face font.Face +} + +// FontLoader represents a FontLoader. Use the "NewFontLoader" to get a instance +type FontLoader struct { + faceCache sync.Cache + fontMapData *FontMapData + faceOpts *opentype.FaceOptions +} + +// NewFontLoader creates a new FontLoader based on the fontMapFile. The FaceOptions will +// be the same for all the font loaded by this instance. +// Note that only the fonts described in the fontMapFile will be used. +// +// The fontMapFile has the following structure +// +// { +// "fontMap": { +// "Han": "packaged/myFont-CJK.otf", +// "Arabic": "packaged/myFont-Arab.otf", +// "Latin": "/fonts/regular/myFont.otf" +// } +// "defaultFont": "/fonts/regular/myFont.otf" +// } +// +// The fontMapFile contains paths to where the fonts are located in the FS. +// Absolute paths can be used as shown above. If a relative path is used, +// it will be relative to the fontMapFile location. This should make the +// packaging easier since all the fonts can be placed in the same directory +// where the fontMapFile is, or in inner directories. +func NewFontLoader(fontMapFile string, faceOpts *opentype.FaceOptions) (*FontLoader, error) { + fontMap := &FontMap{} + + if fontMapFile != "" { + file, err := os.Open(fontMapFile) + if err != nil { + return nil, err + } + defer file.Close() + + parser := json.NewDecoder(file) + if err = parser.Decode(fontMap); err != nil { + return nil, err + } + } + + return &FontLoader{ + faceCache: sync.NewCache(5), + fontMapData: &FontMapData{ + FMap: fontMap, + FLoc: fontMapFile, + }, + faceOpts: faceOpts, + }, nil +} + +// LoadFaceForScript loads and returns the font face to be used for that script according to the +// FontMap set when the FontLoader was created. If the script doesn't have +// an associated font, a default font will be used. Note that the default font +// might not be able to handle properly the script +func (fl *FontLoader) LoadFaceForScript(script string) (*LoadedFace, error) { + var parsedFont *opentype.Font + var parsingError error + + fontFile := fl.fontMapData.FMap.DefaultFont + if val, ok := fl.fontMapData.FMap.FontMap[script]; ok { + fontFile = val + } + + if fontFile != "" && !filepath.IsAbs(fontFile) { + fontFile = filepath.Join(filepath.Dir(fl.fontMapData.FLoc), fontFile) + } + + // if the face for the script isn't cached, load the font file and create a new face + cachedFace := fl.faceCache.Load(fontFile) + if cachedFace != nil { + return cachedFace.V.(*LoadedFace), nil + } + + if fontFile == "" { + parsedFont, parsingError = opentype.Parse(goregular.TTF) + if parsingError != nil { + return nil, parsingError + } + } else { + // opentype.ParseReaderAt seems to require to keep the file opened + // so read the font file into memory + data, err := os.ReadFile(fontFile) + if err != nil { + return nil, err + } + parsedFont, parsingError = opentype.Parse(data) + if parsingError != nil { + return nil, parsingError + } + } + + face, err := opentype.NewFace(parsedFont, fl.faceOpts) + if err != nil { + return nil, err + } + + loadedFace := &LoadedFace{ + FontFile: fontFile, + Face: face, + } + fl.faceCache.Store(fontFile, loadedFace, time.Now().Add(10*time.Minute)) + return loadedFace, nil +} + +// GetFaceOptSize returns face opt size +func (fl *FontLoader) GetFaceOptSize() float64 { + return fl.faceOpts.Size +} + +// GetFaceOptDPI returns face opt DPI +func (fl *FontLoader) GetFaceOptDPI() float64 { + return fl.faceOpts.DPI +} + +// GetScriptList returns script list +func (fl *FontLoader) GetScriptList() []string { + fontMap := fl.fontMapData.FMap.FontMap + + arePresent := map[string]bool{ + "Common": false, + "Inherited": false, + } + listSize := len(fontMap) + + for key := range arePresent { + if _, inFontMap := fontMap[key]; inFontMap { + arePresent[key] = true + } else { + listSize++ + } + } + + keys := make([]string, listSize) + + i := 0 + for k := range fontMap { + keys[i] = k + i++ + } + + for script, isPresent := range arePresent { + if !isPresent { + keys[i] = script + i++ + } + } + return keys +} diff --git a/services/webdav/pkg/preprocessor/preprocessor.go b/services/webdav/pkg/preprocessor/preprocessor.go new file mode 100644 index 0000000000..5f9799d690 --- /dev/null +++ b/services/webdav/pkg/preprocessor/preprocessor.go @@ -0,0 +1,348 @@ +package preprocessor + +import ( + "archive/zip" + "bufio" + "bytes" + "encoding/base64" + "encoding/json" + "image" + "image/draw" + "image/gif" + "io" + "math" + "mime" + "strings" + + "github.com/pkg/errors" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/math/fixed" + + "github.com/dhowden/tag" +) + +// FileConverter is the interface for the file converter +type FileConverter interface { + Convert(r io.Reader) (any, error) +} + +// GifDecoder is a converter for the gif file +type GifDecoder struct{} + +// Convert reads the gif file and returns the thumbnail image +func (i GifDecoder) Convert(r io.Reader) (any, error) { + img, err := gif.DecodeAll(r) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil +} + +// GgsDecoder is a converter for the geogebra slides file +type GgsDecoder struct{ thumbnailpath string } + +// Convert reads the ggs file and returns the thumbnail image +func (g GgsDecoder) Convert(r io.Reader) (any, error) { + var buf bytes.Buffer + _, err := io.Copy(&buf, r) + if err != nil { + return nil, err + } + zipReader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + if err != nil { + return nil, err + } + for _, file := range zipReader.File { + if file.Name == g.thumbnailpath { + thumbnail, err := file.Open() + if err != nil { + return nil, err + } + converter := ForType("image/png", nil) + if converter == nil { + return nil, ErrNoConverterForExtractedImageFromGgsFile + } + img, err := converter.Convert(thumbnail) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil + } + } + return nil, errors.Errorf("%s not found", g.thumbnailpath) +} + +// AudioDecoder is a converter for the audio file +type AudioDecoder struct{} + +// Convert reads the audio file and extracts the thumbnail image from the id3 tag +func (i AudioDecoder) Convert(r io.Reader) (any, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + m, err := tag.ReadFrom(bytes.NewReader(b)) + if err != nil { + return nil, err + } + + picture := m.Picture() + if picture == nil { + return nil, ErrNoImageFromAudioFile + } + + converter := ForType(picture.MIMEType, nil) + if converter == nil { + return nil, ErrNoConverterForExtractedImageFromAudioFile + } + + return converter.Convert(bytes.NewReader(picture.Data)) +} + +// TxtToImageConverter is a converter for the text file +type TxtToImageConverter struct { + fontLoader *FontLoader +} + +// Convert reads the text file and renders it into a thumbnail image +func (t TxtToImageConverter) Convert(r io.Reader) (any, error) { + img := image.NewRGBA(image.Rect(0, 0, 640, 480)) + + imgBounds := img.Bounds() + draw.Draw(img, imgBounds, image.White, image.Point{}, draw.Src) + + fontSizeAsInt := int(math.Ceil(t.fontLoader.GetFaceOptSize())) + margin := 10 + minX := fixed.I(imgBounds.Min.X + margin) + maxX := fixed.I(imgBounds.Max.X - margin) + maxY := fixed.I(imgBounds.Max.Y - margin) + initialPoint := fixed.P(imgBounds.Min.X+margin, imgBounds.Min.Y+margin+fontSizeAsInt) + canvas := &font.Drawer{ + Dst: img, + Src: image.Black, + Dot: initialPoint, + } + + scriptList := t.fontLoader.GetScriptList() + textAnalyzer := NewTextAnalyzer(scriptList) + taOpts := AnalysisOpts{ + UseMergeMap: true, + MergeMap: DefaultMergeMap, + } + + scanner := bufio.NewScanner(r) +Scan: // Label for the scanner loop, so we can break it easily + for scanner.Scan() { + txt := scanner.Text() + height := fixed.I(fontSizeAsInt) // reset to default height + + textResult := textAnalyzer.AnalyzeString(txt, taOpts) + textResult.MergeCommon(DefaultMergeMap) + + for _, sRange := range textResult.ScriptRanges { + targetFontFace, err := t.fontLoader.LoadFaceForScript(sRange.TargetScript) + if err != nil { + return nil, err + } + + // if the target script is "_unknown" it's expected that the loaded face + // uses the default font + faceHeight := targetFontFace.Face.Metrics().Height + if faceHeight > height { + height = faceHeight + } + + canvas.Face = targetFontFace.Face + initialByte := sRange.Low + for _, sRangeSpace := range sRange.Spaces { + if canvas.Dot.Y > maxY { + break Scan + } + + drawWord(canvas, textResult.Text[initialByte:sRangeSpace], minX, maxX, height, maxY) + initialByte = sRangeSpace + } + + if initialByte <= sRange.High { + // some bytes left to be written + if canvas.Dot.Y > maxY { + break Scan + } + + drawWord(canvas, textResult.Text[initialByte:sRange.High+1], minX, maxX, height, maxY) + } + } + + canvas.Dot.X = minX + canvas.Dot.Y += height.Mul(fixed.Int26_6(1<<6 + 1<<5)) // height * 1.5 + + if canvas.Dot.Y > maxY { + break + } + } + return img, scanner.Err() +} + +// GGPStruct is the layout of a ggp file (which is basically json) +type GGPStruct struct { + Sections []struct { + Cards []struct { + Element struct { + Image struct { + Base64Image string + } + } + } + } +} + +// GgpDecoder is a converter for the geogebra pinboard file +type GgpDecoder struct{} + +// Convert reads the ggp file and returns the first thumbnail image +func (j GgpDecoder) Convert(r io.Reader) (any, error) { + ggp := &GGPStruct{} + err := json.NewDecoder(r).Decode(ggp) + if err != nil { + return nil, err + } + + elem, err := extractBase64ImageFromGGP(ggp) + if err != nil { + return nil, err + } + + b, err := base64.StdEncoding.DecodeString(elem) + if err != nil { + return nil, err + } + + img, _, err := image.Decode(bytes.NewReader(b)) + return img, err +} + +func extractBase64ImageFromGGP(ggp *GGPStruct) (string, error) { + if len(ggp.Sections) < 1 || len(ggp.Sections[0].Cards) < 1 { + return "", errors.New("cant find thumbnail in ggp file") + } + + raw := strings.Split(ggp.Sections[0].Cards[0].Element.Image.Base64Image, "base64,") + if len(raw) < 2 { + return "", errors.New("cant decode ggp thumbnail") + } + + return raw[1], nil +} + +// Draw the word in the canvas. The mixX and maxX defines the drawable range +// (X axis) where the word can be drawn (in case the word is too big and doesn't +// fit in the canvas), and the incY defines the increment in the Y axis if we +// need to draw the word in a new line +// +// Note that the word will likely start with a white space char +func drawWord(canvas *font.Drawer, word string, minX, maxX, incY, maxY fixed.Int26_6) { + // calculate the actual measurement of the string at a given X position + measure := func(s string, dotX fixed.Int26_6) (min, max fixed.Int26_6) { + bbox, _ := canvas.BoundString(s) + return dotX + bbox.Min.X, dotX + bbox.Max.X + } + + // first try to draw the whole word + absMin, absMax := measure(word, canvas.Dot.X) + if absMin >= minX && absMax <= maxX { + canvas.DrawString(word) + return + } + + // try to draw the trimmed word in a new line + trimmed := strings.TrimSpace(word) + oldDot := canvas.Dot + canvas.Dot.X = minX + canvas.Dot.Y += incY + + if canvas.Dot.Y <= maxY { + tMin, tMax := measure(trimmed, canvas.Dot.X) + if tMin >= minX && tMax <= maxX { + canvas.DrawString(trimmed) + return + } + } + + // if the trimmed word is still too big, draw it char by char + canvas.Dot = oldDot + for _, char := range trimmed { + s := string(char) + _, cMax := measure(s, canvas.Dot.X) + + if cMax > maxX { + canvas.Dot.X = minX + canvas.Dot.Y += incY + } + + // stop drawing if we exceed maxY + if canvas.Dot.Y > maxY { + return + } + + // ensure that we don't start drawing before minX + cMin, _ := measure(s, canvas.Dot.X) + if cMin < minX { + canvas.Dot.X += minX - cMin + } + + canvas.DrawString(s) + } +} + +// ForType returns the converter for the specified mimeType +func ForType(mimeType string, opts map[string]any) FileConverter { + // We can ignore the error here because we parse it in IsMimeTypeSupported before and if it fails + // return the service call. So we should only get here when the mimeType parses fine. + mimeType, _, _ = mime.ParseMediaType(mimeType) + switch mimeType { + case "text/plain": + fontFileMap := "" + fontFaceOpts := &opentype.FaceOptions{ + Size: 12, + DPI: 72, + Hinting: font.HintingNone, + } + + if optedFontFileMap, ok := opts["fontFileMap"]; ok { + if stringFontFileMap, ok := optedFontFileMap.(string); ok { + fontFileMap = stringFontFileMap + } + } + + if optedFontFaceOpts, ok := opts["fontFaceOpts"]; ok { + if typedFontFaceOpts, ok := optedFontFaceOpts.(*opentype.FaceOptions); ok { + fontFaceOpts = typedFontFaceOpts + } + } + + fontLoader, err := NewFontLoader(fontFileMap, fontFaceOpts) + if err != nil { + // if it couldn't create the FontLoader with the specified fontFileMap, + // try to use the default font + fontLoader, _ = NewFontLoader("", fontFaceOpts) + } + return TxtToImageConverter{ + fontLoader: fontLoader, + } + case "application/vnd.geogebra.slides": + return GgsDecoder{"_slide0/geogebra_thumbnail.png"} + case "application/vnd.geogebra.pinboard": + return GgpDecoder{} + case "image/gif": + return GifDecoder{} + case "audio/flac": + fallthrough + case "audio/mpeg": + fallthrough + case "audio/ogg": + return AudioDecoder{} + default: + return ImageDecoder{} + } +} diff --git a/services/webdav/pkg/preprocessor/preprocessor_imaging.go b/services/webdav/pkg/preprocessor/preprocessor_imaging.go new file mode 100644 index 0000000000..d946082a06 --- /dev/null +++ b/services/webdav/pkg/preprocessor/preprocessor_imaging.go @@ -0,0 +1,22 @@ +//go:build !enable_vips + +package preprocessor + +import ( + "io" + + "github.com/kovidgoyal/imaging" + "github.com/pkg/errors" +) + +// ImageDecoder is a converter for the image file +type ImageDecoder struct{} + +// Convert reads the image file and returns the thumbnail image +func (i ImageDecoder) Convert(r io.Reader) (any, error) { + img, err := imaging.Decode(r, imaging.AutoOrientation(true)) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil +} diff --git a/services/webdav/pkg/preprocessor/preprocessor_test.go b/services/webdav/pkg/preprocessor/preprocessor_test.go new file mode 100644 index 0000000000..a76a0512c0 --- /dev/null +++ b/services/webdav/pkg/preprocessor/preprocessor_test.go @@ -0,0 +1,197 @@ +package preprocessor + +import ( + "bytes" + "io" + "os" + "testing" + + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestImageDecoder(t *testing.T) { + + RegisterFailHandler(Fail) + RunSpecs(t, "ImageDecoder Suite") +} + +var _ = Describe("ImageDecoder", func() { + Describe("ImageDecoder", func() { + var fileReader io.Reader + BeforeEach(func() { + fileContent, err := os.ReadFile("test_assets/noise.png") + if err != nil { + panic(err) + } + fileReader = bytes.NewReader(fileContent) + }) + + It("should decode an image", func() { + decoder := ImageDecoder{} + img, err := decoder.Convert(fileReader) + Expect(err).ToNot(HaveOccurred()) + Expect(img).ToNot(BeNil()) + }) + + It("should return an error if the image is invalid", func() { + decoder := ImageDecoder{} + img, err := decoder.Convert(bytes.NewReader([]byte("not an image"))) + Expect(err).To(HaveOccurred()) + Expect(img).To(BeNil()) + }) + }) + + Describe("GifDecoder", func() { + var fileReader io.Reader + BeforeEach(func() { + fileContent, err := os.ReadFile("test_assets/noise.gif") + if err != nil { + panic(err) + } + fileReader = bytes.NewReader(fileContent) + }) + + It("should decode a gif", func() { + decoder := GifDecoder{} + img, err := decoder.Convert(fileReader) + Expect(err).ToNot(HaveOccurred()) + Expect(img).ToNot(BeNil()) + }) + + It("should return an error if the gif is invalid", func() { + decoder := GifDecoder{} + img, err := decoder.Convert(bytes.NewReader([]byte("not a gif"))) + Expect(err).To(HaveOccurred()) + Expect(img).To(BeNil()) + }) + }) + + Describe("GgsDecoder", func() { + var fileReader io.Reader + BeforeEach(func() { + fileContent, err := os.ReadFile("test_assets/ggs_test.ggs") + if err != nil { + panic(err) + } + fileReader = bytes.NewReader(fileContent) + }) + + It("should decode a ggs", func() { + decoder := GgsDecoder{"_slide0/geogebra_thumbnail.png"} + img, err := decoder.Convert(fileReader) + Expect(err).ToNot(HaveOccurred()) + Expect(img).ToNot(BeNil()) + }) + + It("should return an error if the ggs is invalid", func() { + decoder := GgsDecoder{"_slide0/geogebra_thumbnail.png"} + img, err := decoder.Convert(bytes.NewReader([]byte("not a ggs"))) + Expect(err).To(HaveOccurred()) + Expect(img).To(BeNil()) + }) + }) + + Describe("should decode audio", func() { + var fileReader io.Reader + It("should decode an audio", func() { + fileContent, err := os.ReadFile("test_assets/empty.mp3") + if err != nil { + panic(err) + } + fileReader = bytes.NewReader(fileContent) + decoder := AudioDecoder{} + img, err := decoder.Convert(fileReader) + Expect(err).ToNot(HaveOccurred()) + Expect(img).ToNot(BeNil()) + }) + It("should decode an audio", func() { + fileContent, err := os.ReadFile("test_assets/empty_no_image.mp3") + if err != nil { + panic(err) + } + fileReader = bytes.NewReader(fileContent) + decoder := AudioDecoder{} + img, err := decoder.Convert(fileReader) + Expect(err).To(HaveOccurred()) + Expect(img).To(BeNil()) + }) + It("should return an error if the audio is invalid", func() { + decoder := AudioDecoder{} + img, err := decoder.Convert(bytes.NewReader([]byte("not an audio"))) + Expect(err).To(HaveOccurred()) + Expect(img).To(BeNil()) + }) + }) + + Describe("should decode text", func() { + var decoder TxtToImageConverter + BeforeEach(func() { + fontFaceOpts := &opentype.FaceOptions{ + Size: 12, + DPI: 72, + Hinting: font.HintingNone, + } + + fontLoader, err := NewFontLoader("", fontFaceOpts) + if err != nil { + fontLoader, _ = NewFontLoader("", fontFaceOpts) + } + decoder = TxtToImageConverter{ + fontLoader: fontLoader, + } + }) + It("should decode a text", func() { + img, err := decoder.Convert(bytes.NewReader([]byte("This is a test text"))) + Expect(err).ToNot(HaveOccurred()) + Expect(img).ToNot(BeNil()) + }) + + It("fails if the font is missing", func() { + decoder.fontLoader.fontMapData = &FontMapData{ + FMap: &FontMap{ + DefaultFont: "/some/unknown/font.otf", + }, + } + img, err := decoder.Convert(bytes.NewReader([]byte("This is a test text"))) + Expect(err).To(HaveOccurred()) + Expect(img).To(BeNil()) + }) + }) + + Describe("test ForType", func() { + It("should return an ImageDecoder for image types", func() { + decoder := ForType("image/png", nil) + Expect(decoder).To(BeAssignableToTypeOf(ImageDecoder{})) + }) + + It("should return an GifDecoder for gif types", func() { + decoder := ForType("image/gif", nil) + Expect(decoder).To(BeAssignableToTypeOf(GifDecoder{})) + }) + + It("should return an GgsDecoder for ggs types", func() { + decoder := ForType("application/vnd.geogebra.ggs", nil) + // This will not return the expected ggsDecoder, but an ImageDecoder since ggs contains an embedded png. + Expect(decoder).To(BeAssignableToTypeOf(ImageDecoder{})) + }) + + It("should return an AudioDecoder for audio types", func() { + decoder := ForType("audio/mpeg", nil) + Expect(decoder).To(BeAssignableToTypeOf(AudioDecoder{})) + }) + + It("should return an TxtToImageConverter for text types", func() { + decoder := ForType("text/plain", nil) + Expect(decoder).To(BeAssignableToTypeOf(TxtToImageConverter{})) + }) + + It("should return an ImageDecoder for unknown types", func() { + decoder := ForType("unknown", nil) + Expect(decoder).To(BeAssignableToTypeOf(ImageDecoder{})) + }) + }) +}) diff --git a/services/webdav/pkg/preprocessor/preprocessor_vips.go b/services/webdav/pkg/preprocessor/preprocessor_vips.go new file mode 100644 index 0000000000..087bafb1b7 --- /dev/null +++ b/services/webdav/pkg/preprocessor/preprocessor_vips.go @@ -0,0 +1,20 @@ +//go:build enable_vips + +package preprocessor + +import ( + "io" + + "github.com/davidbyttow/govips/v2/vips" +) + +func init() { + vips.LoggingSettings(nil, vips.LogLevelError) +} + +type ImageDecoder struct{} + +func (v ImageDecoder) Convert(r io.Reader) (interface{}, error) { + img, err := vips.NewImageFromReader(r) + return img, err +} diff --git a/services/webdav/pkg/preprocessor/test_assets/empty.mp3 b/services/webdav/pkg/preprocessor/test_assets/empty.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ea8a247a77b757ca25628013776de1155246c842 GIT binary patch literal 199530 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 bj{kR={}1mA|8hZJ?feUle>?M^;`YA)=lm>Q literal 0 HcmV?d00001 diff --git a/services/webdav/pkg/preprocessor/test_assets/empty_no_image.mp3 b/services/webdav/pkg/preprocessor/test_assets/empty_no_image.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f5bd92f88d17566216025580c48a5e26cdc2067b GIT binary patch literal 4386 zcmeZtF=k-^0i}@OU{@f`$H2hslUSB!W~gVbXJ}vmmV^-he>)sN;zF37d1?7T7C#Vc zFfa&wV-OG!6O)ydRaVy2)YR8EH8r)ic64;~@bL5V3kiveib_aGOH0eiDJm+etgNrE zZ*A@F?w&Yt`t<2@=Pp^YWYww-8#Zj)wr9_tLx)bBIC1XW)vH%;-+uV;;q&Kj-@g6) z`RC7{|Nnz*2D&jB=te_J5C;ZSSZg3mfp3~W@|S_x|9=Y{VPHPMz_(^b literal 0 HcmV?d00001 diff --git a/services/webdav/pkg/preprocessor/test_assets/ggs_test.ggs b/services/webdav/pkg/preprocessor/test_assets/ggs_test.ggs new file mode 100644 index 0000000000000000000000000000000000000000..c0f2a0aa38a776814700af3038b87bcc40062f80 GIT binary patch literal 7486 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 literal 0 HcmV?d00001 diff --git a/services/webdav/pkg/preprocessor/test_assets/noise.gif b/services/webdav/pkg/preprocessor/test_assets/noise.gif new file mode 100644 index 0000000000000000000000000000000000000000..0b1dacf535dce109ad9fbcb726b0402a9d0b99a6 GIT binary patch literal 84386 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≀ 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

5i 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 literal 0 HcmV?d00001 diff --git a/services/webdav/pkg/preprocessor/textanalyzer.go b/services/webdav/pkg/preprocessor/textanalyzer.go new file mode 100644 index 0000000000..8dddfa642a --- /dev/null +++ b/services/webdav/pkg/preprocessor/textanalyzer.go @@ -0,0 +1,309 @@ +package preprocessor + +import ( + "unicode" + "unicode/utf8" +) + +// Default list of scripts to be analyzed within the string. +// +// Scripts that aren't present in the list will be considered as part +// of the last "known" script. For example, if "Avestan" script (which isn't +// present) is preceeded by "Arabic" script, then the "Avestan" script will +// be considered as "Arabic" +// +// Punctuation symbols are usually considered part of the "Common" script +var DefaultScripts = []string{ + "Arabic", + "Common", + "Devanagari", + "Han", + "Hangul", + "Hiragana", + "Inherited", + "Katakana", + "Latin", +} + +// Convenient map[string]map[string]string type used to merge multiple +// scripts into one. This is mainly used for japanese language which uses +// "Han", "Hiragana" and "Katakana" scripts. +// +// The map contains the expected previous script as first key, the expected +// current script as second key, and the resulting script (if both keys +// match) as value +type MergeMap map[string]map[string]string + +// The default mergeMap containing info for the japanese scripts +var DefaultMergeMap = MergeMap{ + "Han": map[string]string{ + "Hiragana": "Hiragana", + "Katakana": "Katakana", + }, + "Hiragana": map[string]string{ + "Han": "Hiragana", + "Katakana": "Hiragana", + }, + "Katakana": map[string]string{ + "Han": "Katakana", + "Hiragana": "Hiragana", + }, +} + +// Analysis options. +type AnalysisOpts struct { + UseMergeMap bool + MergeMap MergeMap +} + +// A script range. The range should be attached to a string which could contain +// multiple scripts. The "TargetScript" will go from bytes "Low" to "High" +// (both inclusive), and contains a "RuneCount" number of runes or chars +// (mostly for debugging purposes). +// The Space contains the bytes (inside the range) that are considered as +// white space. +type ScriptRange struct { + Low, High int + Spaces []int + TargetScript string + RuneCount int +} + +// The result of a text analysis. It contains the analyzed text, a list of +// script ranges (see the ScriptRange type) and a map containing how many +// runes have been detected for a particular script. +type TextAnalysis struct { + ScriptRanges []ScriptRange + RuneCount map[string]int + Text string +} + +// The TextAnalyzer object contains private members. It should be created via +// "NewTextAnalyzer" function. +type TextAnalyzer struct { + scripts map[string]*unicode.RangeTable + scriptListCache []string +} + +// Create a new TextAnalyzer. A list of scripts must be provided. +// You can use the "DefaultScripts" variable for a default list, +// although it doesn't contain all the available scripts. +// See the unicode.Scripts variable (in the unicode package) for a +// full list. Note that using invalid scripts will cause an undefined +// behavior +func NewTextAnalyzer(scriptList []string) TextAnalyzer { + scriptRanges := make(map[string]*unicode.RangeTable, len(scriptList)) + for _, script := range scriptList { + scriptRanges[script] = unicode.Scripts[script] + } + return TextAnalyzer{ + scripts: scriptRanges, + scriptListCache: scriptList, + } +} + +// Analyze the target string using the specified options. +// A TextAnalysis will be returned with the result of the analysis. +func (ta *TextAnalyzer) AnalyzeString(word string, opts AnalysisOpts) TextAnalysis { + analysis := TextAnalysis{ + ScriptRanges: []ScriptRange{}, + RuneCount: make(map[string]int), + Text: word, + } + + if len(word) < 1 { + return analysis + } + + firstRune, runeLen := utf8.DecodeRuneInString(word) + + lastRange := &ScriptRange{ + Low: 0, + Spaces: make([]int, 0), + TargetScript: ta.chooseScriptFor(firstRune), + } + firstRuneIsWhiteSpace := unicode.Is(unicode.White_Space, firstRune) + if firstRuneIsWhiteSpace { + lastRange.Spaces = append(lastRange.Spaces, 0) + } + + runeCount := 1 + for wordIndex, char := range word[runeLen:] { + wordIndex += runeLen // shifted from the original string + script := ta.chooseScriptFor(char) + + isWhiteSpace := unicode.Is(unicode.White_Space, char) + if script != lastRange.TargetScript { + if mapScript, isOk := ta.getMergeMapValue(opts, lastRange.TargetScript, script); isOk { + lastRange.TargetScript = mapScript + if isWhiteSpace { + // TODO: Check if this is dead code. + // whitespace should be part of the "Common" script, and the Common + // script shouldn't be part of a mergeMap + lastRange.Spaces = append(lastRange.Spaces, wordIndex) + } + runeCount++ + continue + } + + lastRange.High = wordIndex - 1 + lastRange.RuneCount = runeCount + analysis.ScriptRanges = append(analysis.ScriptRanges, *lastRange) + if _, exists := analysis.RuneCount[lastRange.TargetScript]; !exists { + analysis.RuneCount[lastRange.TargetScript] = 0 + } + analysis.RuneCount[lastRange.TargetScript] += runeCount + lastRange = &ScriptRange{ + Low: wordIndex, + Spaces: make([]int, 0), + TargetScript: script, + } + runeCount = 0 + } + runeCount++ + if isWhiteSpace { + lastRange.Spaces = append(lastRange.Spaces, wordIndex) + } + } + + // close the last range + lastRange.High = len(word) - 1 + lastRange.RuneCount = runeCount + analysis.RuneCount[lastRange.TargetScript] += runeCount + analysis.ScriptRanges = append(analysis.ScriptRanges, *lastRange) + + return analysis +} + +func (ta *TextAnalyzer) chooseScriptFor(char rune) string { + script := "_unknown" + for scriptIndex, scriptFound := range ta.scriptListCache { + // if we can't match with a known script, do nothing and jump to the next char + if unicode.Is(ta.scripts[scriptFound], char) { + if scriptIndex > 3 { + // we might expect more chars with the same script + // so move the script first to match it faster next time + ta.reorderScriptList(scriptFound) + } + return scriptFound + } + } + return script +} + +// Reorder the scriptListCache in the TextAnalyzer in order to speed up +// the next script searches. A "Latin" script is expected to be surrounded +// by "Latin" chars, although "Common" script chars might be present too +func (ta *TextAnalyzer) reorderScriptList(matchedScript string) { + for index, script := range ta.scriptListCache { + if script == matchedScript { + if index != 0 { + // move the script to the first position for a faster matching + newList := append([]string{script}, ta.scriptListCache[:index]...) + ta.scriptListCache = append(newList, ta.scriptListCache[index+1:]...) + } + // if index == 0 there is nothing to do: the element is already the first + break + } + } +} + +// Get the value from the merge map based on the previous and current scripts. +// The information about using the merge map and the actual merge map will be +// gotten from the AnalysisOpts passed as parameter +func (ta *TextAnalyzer) getMergeMapValue(opts AnalysisOpts, previous, current string) (string, bool) { + if opts.UseMergeMap { + // This option mainly target japanese chars; multiple scripts can be used + // in the same piece of text (Han, Hiragana and Katakana) + // Instead of starting a new range, adjust the target script of the last range + if expCurrent, currentOk := opts.MergeMap[previous]; currentOk { + if expFinal, finalOk := expCurrent[current]; finalOk { + return expFinal, finalOk + } + } + } + return "", false +} + +// Change the "Common" script to the one used in the previous script range. +// The ranges will be readjusted and merged if they're adjacent. +// This naive approach should be good enough for normal use cases +// +// The MergeMap is needed in case of the japanese language: the ranges +// "Han"-"Common"-"Katakana" might be replaced to "Han"-"Hiragana"-"Katakana" +// However, the ranges should be merged together into a big "Hiragana" range. +// If the MergeMap isn't needed, use an empty one +func (tr *TextAnalysis) MergeCommon(mergeMap MergeMap) { + var finalRanges []ScriptRange + + if len(tr.ScriptRanges) < 1 { + // no ranges -> nothing to do + return + } + + previousRange := &ScriptRange{} + *previousRange = tr.ScriptRanges[0] + for _, sRange := range tr.ScriptRanges[1:] { + if previousRange.TargetScript == sRange.TargetScript { + previousRange.High = sRange.High + previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) + previousRange.RuneCount += sRange.RuneCount + } else if sRange.TargetScript == "Common" || sRange.TargetScript == "Inherited" { + // new range will be absorbed into the previous one + previousRange.High = sRange.High + previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) + previousRange.RuneCount += sRange.RuneCount + tr.RuneCount[previousRange.TargetScript] += sRange.RuneCount + tr.RuneCount[sRange.TargetScript] -= sRange.RuneCount + } else if previousRange.TargetScript == "Common" || previousRange.TargetScript == "Inherited" { + // might happen if the text starts with a Common script + previousRange.High = sRange.High + previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) + tr.RuneCount[sRange.TargetScript] += previousRange.RuneCount + tr.RuneCount[previousRange.TargetScript] -= previousRange.RuneCount + previousRange.RuneCount += sRange.RuneCount + previousRange.TargetScript = sRange.TargetScript + } else { + if mapScript, isOk := tr.getMergeMapValue(mergeMap, previousRange.TargetScript, sRange.TargetScript); isOk { + if sRange.TargetScript == mapScript { + // the previous range has changed the target script + tr.RuneCount[previousRange.TargetScript] -= previousRange.RuneCount + tr.RuneCount[sRange.TargetScript] += previousRange.RuneCount + } else { + // new range has been absorbed + tr.RuneCount[sRange.TargetScript] -= sRange.RuneCount + tr.RuneCount[previousRange.TargetScript] += sRange.RuneCount + } + previousRange.TargetScript = mapScript + previousRange.High = sRange.High + previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) + previousRange.RuneCount += sRange.RuneCount + continue + } + finalRanges = append(finalRanges, *previousRange) + *previousRange = sRange + } + } + + finalRanges = append(finalRanges, *previousRange) + tr.ScriptRanges = finalRanges + delete(tr.RuneCount, "Common") + delete(tr.RuneCount, "Inherited") + for index, rCount := range tr.RuneCount { + if rCount == 0 { + delete(tr.RuneCount, index) + } + } +} + +func (tr *TextAnalysis) getMergeMapValue(mMap MergeMap, previous, current string) (string, bool) { + // This option mainly target japanese chars; multiple scripts can be used + // in the same piece of text (Han, Hiragana and Katakana) + // Instead of starting a new range, adjust the target script of the last range + if expCurrent, currentOk := mMap[previous]; currentOk { + if expFinal, finalOk := expCurrent[current]; finalOk { + return expFinal, finalOk + } + } + return "", false +} diff --git a/services/webdav/pkg/preprocessor/textanalyzer_test.go b/services/webdav/pkg/preprocessor/textanalyzer_test.go new file mode 100644 index 0000000000..3efeea930c --- /dev/null +++ b/services/webdav/pkg/preprocessor/textanalyzer_test.go @@ -0,0 +1,614 @@ +package preprocessor + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + inputs = [18]string{ + "basic latin", + "trailing tab ", + "Small text. \"$\", \"£\" and \"¥\" are currencies.", + "latin with 🖖", + "기본 한국어", + "基本的な日本語", + "ウーロン茶", + "私はエンジニアです", + "ティー私はエンジニアです", + "私はエンジニアです ティー", + "आधारभूत देवनागरी", + "mixed 언어 传入 🚀!", + "/k͜p/", + // ä and a + ¨ + "ä ä", + "базовый русский", // cyrillic script isn't part of our default + "latin русский", // latin + cyrillic (cyrillic not supported) + " space justified ", + "", + } +) + +func TestAnalyzeString(t *testing.T) { + defaultOpts := AnalysisOpts{ + UseMergeMap: true, + MergeMap: DefaultMergeMap, + } + + tables := []struct { + input string + opts AnalysisOpts + eOut TextAnalysis + }{ + { + input: inputs[0], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 10, Spaces: []int{5}, TargetScript: "Latin", RuneCount: 11}, + }, + RuneCount: map[string]int{ + "Latin": 11, + }, + Text: inputs[0], + }, + }, + { + input: inputs[1], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 12, Spaces: []int{8, 12}, TargetScript: "Latin", RuneCount: 13}, + }, + RuneCount: map[string]int{ + "Latin": 13, + }, + Text: inputs[1], + }, + }, + { + input: inputs[2], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 45, Spaces: []int{5, 11, 16, 21, 25, 30, 34}, TargetScript: "Latin", RuneCount: 44}, + }, + RuneCount: map[string]int{ + "Latin": 44, + }, + Text: inputs[2], + }, + }, + { + input: inputs[3], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 14, Spaces: []int{5, 10}, TargetScript: "Latin", RuneCount: 12}, + }, + RuneCount: map[string]int{ + "Latin": 12, + }, + Text: inputs[3], + }, + }, + { + input: inputs[4], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 15, Spaces: []int{6}, TargetScript: "Hangul", RuneCount: 6}, + }, + RuneCount: map[string]int{ + "Hangul": 6, + }, + Text: inputs[4], + }, + }, + { + input: inputs[5], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 20, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 7}, + }, + RuneCount: map[string]int{ + "Hiragana": 7, + }, + Text: inputs[5], + }, + }, + { + input: inputs[6], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 14, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, + }, + RuneCount: map[string]int{ + "Katakana": 5, + }, + Text: inputs[6], + }, + }, + { + input: inputs[7], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 26, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 9}, + }, + RuneCount: map[string]int{ + "Hiragana": 9, + }, + Text: inputs[7], + }, + }, + { + input: inputs[8], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 35, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 12}, + }, + RuneCount: map[string]int{ + "Hiragana": 12, + }, + Text: inputs[8], + }, + }, + { + input: inputs[9], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 36, Spaces: []int{27}, TargetScript: "Hiragana", RuneCount: 13}, + }, + RuneCount: map[string]int{ + "Hiragana": 13, + }, + Text: inputs[9], + }, + }, + { + input: inputs[10], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 45, Spaces: []int{21}, TargetScript: "Devanagari", RuneCount: 16}, + }, + RuneCount: map[string]int{ + "Devanagari": 16, + }, + Text: inputs[10], + }, + }, + { + input: inputs[11], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 5, Spaces: []int{5}, TargetScript: "Latin", RuneCount: 6}, + {Low: 6, High: 12, Spaces: []int{12}, TargetScript: "Hangul", RuneCount: 3}, + {Low: 13, High: 24, Spaces: []int{19}, TargetScript: "Han", RuneCount: 5}, // 🚀 and ! are "Common" script and will be merged with "Han" + }, + RuneCount: map[string]int{ + "Latin": 6, + "Hangul": 3, + "Han": 5, + }, + Text: inputs[11], + }, + }, + { + input: inputs[12], + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 5, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + }, + RuneCount: map[string]int{ + "Latin": 5, + }, + Text: inputs[12], + }, + }, + { + input: inputs[13], // ä and a + ¨ + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 5, Spaces: []int{2}, TargetScript: "Latin", RuneCount: 4}, + }, + RuneCount: map[string]int{ + "Latin": 4, + }, + Text: inputs[13], + }, + }, + { + input: inputs[14], // cyrillic script isn't part of our default + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 28, Spaces: []int{14}, TargetScript: "_unknown", RuneCount: 15}, + }, + RuneCount: map[string]int{ + "_unknown": 15, + }, + Text: inputs[14], + }, + }, + { + input: inputs[15], // latin + cyrillic (cyrillic script isn't part of our default) + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 5, Spaces: []int{5}, TargetScript: "Latin", RuneCount: 6}, + {Low: 6, High: 19, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, + }, + RuneCount: map[string]int{ + "Latin": 6, + "_unknown": 7, + }, + Text: inputs[15], + }, + }, + { + input: inputs[16], // latin + cyrillic (cyrillic script isn't part of our default) + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 16, Spaces: []int{0, 6, 16}, TargetScript: "Latin", RuneCount: 17}, + }, + RuneCount: map[string]int{ + "Latin": 17, + }, + Text: inputs[16], + }, + }, + { + input: inputs[17], // latin + cyrillic (cyrillic script isn't part of our default) + opts: defaultOpts, + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{}, + RuneCount: map[string]int{}, + Text: inputs[17], + }, + }, + } + + for _, table := range tables { + testname := fmt.Sprintf("Analyzing \"%s\" string", table.input) + t.Run(testname, func(t *testing.T) { + ta := NewTextAnalyzer(DefaultScripts) + result := ta.AnalyzeString(table.input, table.opts) + if table.opts.UseMergeMap { + result.MergeCommon(table.opts.MergeMap) + } else { + result.MergeCommon(MergeMap{}) + } + assert.Equal(t, table.eOut, result) + }) + } +} + +func TestAnalyzeStringRaw(t *testing.T) { + tables := []struct { + input string + eOut TextAnalysis + }{ + { + input: inputs[0], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, + {Low: 6, High: 10, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + }, + RuneCount: map[string]int{ + "Latin": 10, + "Common": 1, + }, + Text: inputs[0], + }, + }, + { + input: inputs[1], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 7, Spaces: []int{}, TargetScript: "Latin", RuneCount: 8}, + {Low: 8, High: 8, Spaces: []int{8}, TargetScript: "Common", RuneCount: 1}, + {Low: 9, High: 11, Spaces: []int{}, TargetScript: "Latin", RuneCount: 3}, + {Low: 12, High: 12, Spaces: []int{12}, TargetScript: "Common", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Latin": 11, + "Common": 2, + }, + Text: inputs[1], + }, + }, + { + input: inputs[2], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, + {Low: 6, High: 9, Spaces: []int{}, TargetScript: "Latin", RuneCount: 4}, + {Low: 10, High: 21, Spaces: []int{11, 16, 21}, TargetScript: "Common", RuneCount: 11}, // £ takes 2 bytes + {Low: 22, High: 24, Spaces: []int{}, TargetScript: "Latin", RuneCount: 3}, + {Low: 25, High: 30, Spaces: []int{25, 30}, TargetScript: "Common", RuneCount: 5}, // ¥ takes 2 bytes + {Low: 31, High: 33, Spaces: []int{}, TargetScript: "Latin", RuneCount: 3}, + {Low: 34, High: 34, Spaces: []int{34}, TargetScript: "Common", RuneCount: 1}, + {Low: 35, High: 44, Spaces: []int{}, TargetScript: "Latin", RuneCount: 10}, + {Low: 45, High: 45, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Latin": 25, + "Common": 19, + }, + Text: inputs[2], + }, + }, + { + input: inputs[3], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, + {Low: 6, High: 9, Spaces: []int{}, TargetScript: "Latin", RuneCount: 4}, + {Low: 10, High: 14, Spaces: []int{10}, TargetScript: "Common", RuneCount: 2}, + }, + RuneCount: map[string]int{ + "Latin": 9, + "Common": 3, + }, + Text: inputs[3], + }, + }, + { + input: inputs[4], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 5, Spaces: []int{}, TargetScript: "Hangul", RuneCount: 2}, + {Low: 6, High: 6, Spaces: []int{6}, TargetScript: "Common", RuneCount: 1}, + {Low: 7, High: 15, Spaces: []int{}, TargetScript: "Hangul", RuneCount: 3}, + }, + RuneCount: map[string]int{ + "Hangul": 5, + "Common": 1, + }, + Text: inputs[4], + }, + }, + { + input: inputs[5], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 8, Spaces: []int{}, TargetScript: "Han", RuneCount: 3}, + {Low: 9, High: 11, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, + {Low: 12, High: 20, Spaces: []int{}, TargetScript: "Han", RuneCount: 3}, + }, + RuneCount: map[string]int{ + "Hiragana": 1, + "Han": 6, + }, + Text: inputs[5], + }, + }, + { + input: inputs[6], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 2, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 1}, + {Low: 3, High: 5, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, // ー U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK) seems to be counted as Common + {Low: 6, High: 11, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 2}, + {Low: 12, High: 14, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Katakana": 3, + "Common": 1, + "Han": 1, + }, + Text: inputs[6], + }, + }, + { + input: inputs[7], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 2, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, + {Low: 3, High: 5, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, + {Low: 6, High: 20, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, + {Low: 21, High: 26, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 2}, + }, + RuneCount: map[string]int{ + "Han": 1, + "Hiragana": 3, + "Katakana": 5, + }, + Text: inputs[7], + }, + }, + { + input: inputs[8], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 5, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 2}, + {Low: 6, High: 8, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, + {Low: 9, High: 11, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, + {Low: 12, High: 14, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, + {Low: 15, High: 29, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, + {Low: 30, High: 35, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 2}, + }, + RuneCount: map[string]int{ + "Han": 1, + "Hiragana": 3, + "Katakana": 7, + "Common": 1, + }, + Text: inputs[8], + }, + }, + { + input: inputs[9], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 2, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, + {Low: 3, High: 5, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, + {Low: 6, High: 20, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, + {Low: 21, High: 26, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 2}, + {Low: 27, High: 27, Spaces: []int{27}, TargetScript: "Common", RuneCount: 1}, + {Low: 28, High: 33, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 2}, + {Low: 34, High: 36, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Han": 1, + "Hiragana": 3, + "Katakana": 7, + "Common": 2, + }, + Text: inputs[9], + }, + }, + { + input: inputs[10], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 20, Spaces: []int{}, TargetScript: "Devanagari", RuneCount: 7}, + {Low: 21, High: 21, Spaces: []int{21}, TargetScript: "Common", RuneCount: 1}, + {Low: 22, High: 45, Spaces: []int{}, TargetScript: "Devanagari", RuneCount: 8}, + }, + RuneCount: map[string]int{ + "Devanagari": 15, + "Common": 1, + }, + Text: inputs[10], + }, + }, + { + input: inputs[11], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, + {Low: 6, High: 11, Spaces: []int{}, TargetScript: "Hangul", RuneCount: 2}, + {Low: 12, High: 12, Spaces: []int{12}, TargetScript: "Common", RuneCount: 1}, + {Low: 13, High: 18, Spaces: []int{}, TargetScript: "Han", RuneCount: 2}, + {Low: 19, High: 24, Spaces: []int{19}, TargetScript: "Common", RuneCount: 3}, + }, + RuneCount: map[string]int{ + "Latin": 5, + "Hangul": 2, + "Han": 2, + "Common": 5, + }, + Text: inputs[11], + }, + }, + { + input: inputs[12], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 0, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, + {Low: 1, High: 1, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, + {Low: 2, High: 3, Spaces: []int{}, TargetScript: "Inherited", RuneCount: 1}, + {Low: 4, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, + {Low: 5, High: 5, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Latin": 2, + "Common": 2, + "Inherited": 1, + }, + Text: inputs[12], + }, + }, + { + input: inputs[13], // ä and a + ¨ + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 1, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, + {Low: 2, High: 2, Spaces: []int{2}, TargetScript: "Common", RuneCount: 1}, + {Low: 3, High: 3, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, + {Low: 4, High: 5, Spaces: []int{}, TargetScript: "Inherited", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Latin": 2, + "Common": 1, + "Inherited": 1, + }, + Text: inputs[13], + }, + }, + { + input: inputs[14], // cyrillic script isn't part of our default + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 13, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, + {Low: 14, High: 14, Spaces: []int{14}, TargetScript: "Common", RuneCount: 1}, + {Low: 15, High: 28, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, + }, + RuneCount: map[string]int{ + "_unknown": 14, + "Common": 1, + }, + Text: inputs[14], + }, + }, + { + input: inputs[15], // latin + cyrillic (cyrillic script isn't part of our default) + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, + {Low: 6, High: 19, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, + }, + RuneCount: map[string]int{ + "Latin": 5, + "Common": 1, + "_unknown": 7, + }, + Text: inputs[15], + }, + }, + { + input: inputs[16], + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{ + {Low: 0, High: 0, Spaces: []int{0}, TargetScript: "Common", RuneCount: 1}, + {Low: 1, High: 5, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, + {Low: 6, High: 6, Spaces: []int{6}, TargetScript: "Common", RuneCount: 1}, + {Low: 7, High: 15, Spaces: []int{}, TargetScript: "Latin", RuneCount: 9}, + {Low: 16, High: 16, Spaces: []int{16}, TargetScript: "Common", RuneCount: 1}, + }, + RuneCount: map[string]int{ + "Latin": 14, + "Common": 3, + }, + Text: inputs[16], + }, + }, + { + input: inputs[17], // empty string + eOut: TextAnalysis{ + ScriptRanges: []ScriptRange{}, + RuneCount: map[string]int{}, + Text: inputs[17], + }, + }, + } + + for _, table := range tables { + testname := fmt.Sprintf("Raw-Analyzing \"%s\" string", table.input) + t.Run(testname, func(t *testing.T) { + ta := NewTextAnalyzer(DefaultScripts) + result := ta.AnalyzeString(table.input, AnalysisOpts{}) + + assert.Equal(t, table.eOut, result) + }) + } +} diff --git a/services/webdav/pkg/service/v0/config_test.go b/services/webdav/pkg/service/v0/config_test.go new file mode 100644 index 0000000000..8a632e1b7f --- /dev/null +++ b/services/webdav/pkg/service/v0/config_test.go @@ -0,0 +1,182 @@ +package svc + +import ( + "os" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/opencloud-eu/opencloud/services/webdav/pkg/generator" +) + +var _ = Describe("ParseMaxInputFileSize", func() { + It("should return 0 for empty string", func() { + size, err := generator.ParseMaxInputFileSize("") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(0))) + }) + + It("should parse plain bytes", func() { + size, err := generator.ParseMaxInputFileSize("1234") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(1234))) + }) + + It("should parse KB suffix", func() { + size, err := generator.ParseMaxInputFileSize("50KB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(50 * 1024))) + }) + + It("should parse kB suffix (case insensitive)", func() { + size, err := generator.ParseMaxInputFileSize("50kB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(50 * 1024))) + }) + + It("should parse KiB suffix", func() { + size, err := generator.ParseMaxInputFileSize("50KiB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(50 * 1024))) + }) + + It("should parse MB suffix", func() { + size, err := generator.ParseMaxInputFileSize("5MB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(5 * 1024 * 1024))) + }) + + It("should parse MiB suffix", func() { + size, err := generator.ParseMaxInputFileSize("5MiB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(5 * 1024 * 1024))) + }) + + It("should parse GB suffix", func() { + size, err := generator.ParseMaxInputFileSize("2GB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(2 * 1024 * 1024 * 1024))) + }) + + It("should parse GiB suffix", func() { + size, err := generator.ParseMaxInputFileSize("2GiB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(2 * 1024 * 1024 * 1024))) + }) + + It("should handle leading/trailing whitespace", func() { + size, err := generator.ParseMaxInputFileSize(" 5MB ") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(5 * 1024 * 1024))) + }) + + It("should handle whitespace around suffix", func() { + size, err := generator.ParseMaxInputFileSize("5 MB") + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(5 * 1024 * 1024))) + }) + + It("should return error for invalid input", func() { + _, err := generator.ParseMaxInputFileSize("abc") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid file size")) + }) + + It("should return error for negative number", func() { + _, err := generator.ParseMaxInputFileSize("-5MB") + Expect(err).To(HaveOccurred()) + }) +}) + +// testEnvVarFallback simulates go-micro's ";" separator pattern for env vars. +func testEnvVarFallback(names string) string { + for _, n := range strings.Split(names, ";") { + if v := os.Getenv(strings.TrimSpace(n)); v != "" { + return v + } + } + return "" +} + +var _ = Describe("envVarFallback", func() { + BeforeEach(func() { + os.Unsetenv("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + os.Unsetenv("THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + }) + + AfterEach(func() { + os.Unsetenv("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + os.Unsetenv("THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + }) + + It("should return empty string when neither env var is set", func() { + val := testEnvVarFallback("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + Expect(val).To(Equal("")) + }) + + It("should return primary env var value when only primary is set", func() { + os.Setenv("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE", "50MB") + val := testEnvVarFallback("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + Expect(val).To(Equal("50MB")) + }) + + It("should return fallback env var value when only fallback is set", func() { + os.Setenv("THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE", "100MB") + val := testEnvVarFallback("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + Expect(val).To(Equal("100MB")) + }) + + It("should prefer primary env var over fallback when both are set", func() { + os.Setenv("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE", "50MB") + os.Setenv("THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE", "100MB") + val := testEnvVarFallback("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + Expect(val).To(Equal("50MB")) + }) + + It("should parse fallback value correctly through ParseMaxInputFileSize", func() { + os.Setenv("THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE", "2GB") + val := testEnvVarFallback("WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE") + size, err := generator.ParseMaxInputFileSize(val) + Expect(err).ToNot(HaveOccurred()) + Expect(size).To(Equal(uint64(2 * 1024 * 1024 * 1024))) + }) +}) + +var _ = Describe("ExtensionInfo", func() { + It("should map gif extension correctly", func() { + info := generator.GetExtensionInfo("gif") + Expect(info.OutputFormat).To(Equal("gif")) + Expect(info.ContentType).To(Equal("image/gif")) + }) + + It("should map png extension correctly", func() { + info := generator.GetExtensionInfo("png") + Expect(info.OutputFormat).To(Equal("png")) + Expect(info.ContentType).To(Equal("image/png")) + }) + + It("should map jpeg extension correctly", func() { + info := generator.GetExtensionInfo("jpeg") + Expect(info.OutputFormat).To(Equal("jpeg")) + Expect(info.ContentType).To(Equal("image/jpeg")) + }) + + It("should map jpg to jpeg output format", func() { + info := generator.GetExtensionInfo("jpg") + Expect(info.OutputFormat).To(Equal("jpeg")) + Expect(info.ContentType).To(Equal("image/jpeg")) + }) + + It("should return default info for unknown extension", func() { + info := generator.GetExtensionInfo("webp") + Expect(info.OutputFormat).To(Equal("jpeg")) + Expect(info.ContentType).To(Equal("image/jpeg")) + }) + + It("should return default info for empty extension", func() { + info := generator.GetExtensionInfo("") + Expect(info.OutputFormat).To(Equal("jpeg")) + Expect(info.ContentType).To(Equal("image/jpeg")) + }) +}) diff --git a/services/webdav/pkg/service/v0/search.go b/services/webdav/pkg/service/v0/search.go index e2f6a3f4a4..1b68562db4 100644 --- a/services/webdav/pkg/service/v0/search.go +++ b/services/webdav/pkg/service/v0/search.go @@ -25,8 +25,8 @@ import ( searchmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/search/v0" searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" "github.com/opencloud-eu/opencloud/services/webdav/pkg/constants" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail" "github.com/opencloud-eu/opencloud/services/webdav/pkg/net" "github.com/opencloud-eu/opencloud/services/webdav/pkg/prop" "github.com/opencloud-eu/opencloud/services/webdav/pkg/propfind" diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go index 598728044b..969d44c7c2 100644 --- a/services/webdav/pkg/service/v0/service.go +++ b/services/webdav/pkg/service/v0/service.go @@ -3,15 +3,15 @@ package svc import ( "context" "encoding/xml" + "errors" "fmt" - "io" "math/rand/v2" "net/http" "net/url" "path" - "path/filepath" "strconv" "strings" + "time" gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -20,20 +20,19 @@ import ( "github.com/go-chi/render" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/storage/utils/templates" "github.com/riandyrn/otelchi" - merrors "go-micro.dev/v4/errors" - grpcmetadata "google.golang.org/grpc/metadata" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/tracing" - thumbnailsmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/thumbnails/v0" searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" "github.com/opencloud-eu/opencloud/services/webdav/pkg/constants" "github.com/opencloud-eu/opencloud/services/webdav/pkg/dav/requests" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/generator" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail/cache" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail/workflow" ) var ( @@ -85,61 +84,98 @@ func NewService(opts ...Option) (Service, error) { return nil, err } - svc := Webdav{ - config: conf, - log: options.Logger, - mux: m, - searchClient: searchsvc.NewSearchProviderService("eu.opencloud.api.search", conf.GrpcClient), - thumbnailsClient: thumbnailssvc.NewThumbnailService("eu.opencloud.api.thumbnails", conf.GrpcClient), - gatewaySelector: gatewaySelector, + genTimeout, _ := time.ParseDuration(conf.ThumbnailGeneratorTimeout) + if genTimeout == 0 { + genTimeout = 30 * time.Second } - if svc.config.DisablePreviews { - svc.thumbnailsClient = nil + resolutions, err := thumbnail.ParseResolutions(conf.ThumbnailResolutions) + if err != nil { + return nil, fmt.Errorf("parse thumbnail resolutions: %w", err) } - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - - if !svc.config.DisablePreviews { - r.Group(func(r chi.Router) { - r.Use(svc.DavUserContext()) + maxInputSize, err := generator.ParseMaxInputFileSize(conf.MaxInputFileSize) + if err != nil { + return nil, fmt.Errorf("parse max input file size: %w", err) + } - r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail) - r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) - r.Get("/dav/spaces/{id}", svc.SpacesThumbnail) - r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) - r.MethodFunc("REPORT", "/remote.php/dav/spaces*", svc.Search) - r.MethodFunc("REPORT", "/dav/spaces*", svc.Search) + s3cfg := cache.BuildS3CacheConfig( + conf.ThumbnailCacheS3Bucket, + conf.ThumbnailCacheS3Region, + conf.ThumbnailCacheS3Endpoint, + conf.ThumbnailCacheS3AccessKey, + conf.ThumbnailCacheS3SecretKey, + ) - r.Get("/remote.php/dav/files/{id}", svc.Thumbnail) - r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) - r.Get("/dav/files/{id}", svc.Thumbnail) - r.Get("/dav/files/{id}/*", svc.Thumbnail) + c := cache.NewThumbnailCache(conf.ThumbnailCacheBackend, conf.ThumbnailCacheDir, s3cfg) + + httpClient := &http.Client{Timeout: genTimeout} + + wf, err := workflow.NewWorkflow( + workflow.WithGeneratorURL(conf.ThumbnailGeneratorURL), + workflow.WithAuthHeader(conf.ThumbnailGeneratorAuthHeader), + workflow.WithCache(c), + workflow.WithHTTPClient(httpClient), + workflow.WithMaxInputSize(maxInputSize), + workflow.WithResolutions(resolutions), + workflow.WithWebdavNamespace(conf.WebdavNamespace), + workflow.WithFontMapFile(conf.FontMapFile), + workflow.WithLogger(options.Logger), + workflow.WithStater(workflow.NewGatewayStater(gatewaySelector)), + workflow.WithFileDownloader(workflow.NewGatewayFileDownloader(gatewaySelector, httpClient)), + workflow.WithSpaceLookup(workflow.NewGatewaySpaceLookup(gatewaySelector)), + ) + if err != nil { + return nil, fmt.Errorf("create thumbnail workflow: %w", err) + } - r.MethodFunc("REPORT", "/remote.php/dav/files*", svc.Search) - r.MethodFunc("REPORT", "/dav/files*", svc.Search) - }) + svc := Webdav{ + config: conf, + log: options.Logger, + mux: m, + searchClient: searchsvc.NewSearchProviderService("eu.opencloud.api.search", conf.GrpcClient), + workflow: wf, + gatewaySelector: gatewaySelector, + } - r.Group(func(r chi.Router) { - r.Use(svc.DavPublicContext()) + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.Group(func(r chi.Router) { + r.Use(svc.DavUserContext()) + + r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail) + r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) + r.MethodFunc("REPORT", "/remote.php/dav/spaces*", svc.Search) + r.MethodFunc("REPORT", "/dav/spaces*", svc.Search) + + r.Get("/remote.php/dav/files/{id}", svc.Thumbnail) + r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) + r.Get("/dav/files/{id}", svc.Thumbnail) + r.Get("/dav/files/{id}/*", svc.Thumbnail) + + r.MethodFunc("REPORT", "/remote.php/dav/files*", svc.Search) + r.MethodFunc("REPORT", "/dav/files*", svc.Search) + }) - r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) - r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) + r.Group(func(r chi.Router) { + r.Use(svc.DavPublicContext()) - r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) - r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) - }) + r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) + r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) - r.Group(func(r chi.Router) { - r.Use(svc.WebDAVContext()) - r.Get("/remote.php/webdav/*", svc.Thumbnail) - r.Get("/webdav/*", svc.Thumbnail) + r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) + r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) + }) - r.MethodFunc("REPORT", "/remote.php/webdav*", svc.Search) - r.MethodFunc("REPORT", "/webdav*", svc.Search) - }) - } + r.Group(func(r chi.Router) { + r.Use(svc.WebDAVContext()) + r.Get("/remote.php/webdav/*", svc.Thumbnail) + r.Get("/webdav/*", svc.Thumbnail) + r.MethodFunc("REPORT", "/remote.php/webdav*", svc.Search) + r.MethodFunc("REPORT", "/webdav*", svc.Search) + }) }) _ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { @@ -152,12 +188,12 @@ func NewService(opts ...Option) (Service, error) { // Webdav implements the business logic for Service. type Webdav struct { - config *config.Config - log log.Logger - mux *chi.Mux - searchClient searchsvc.SearchProviderService - thumbnailsClient thumbnailssvc.ThumbnailService - gatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient] + config *config.Config + log log.Logger + mux *chi.Mux + searchClient searchsvc.SearchProviderService + workflow *workflow.ThumbnailWorkflow + gatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient] } // ServeHTTP implements the Service interface. @@ -236,52 +272,18 @@ func (g Webdav) SpacesThumbnail(w http.ResponseWriter, r *http.Request) { renderError(w, r, errBadRequest(err.Error())) return } - t := r.Header.Get(revactx.TokenHeader) - - fullPath := filepath.Join(tr.Identifier, tr.Filepath) - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Processor: tr.Processor, - Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{ - Cs3Source: &thumbnailsmsg.CS3Source{ - Path: fullPath, - Authorization: t, - }, - }, - }) + + auth := r.Header.Get(revactx.TokenHeader) + data, ext, aIgnored, err := g.workflow.Execute(r.Context(), tr, auth, logger) if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusTooEarly: - // StatusTooEarly if file is processing - renderError(w, r, errTooEarly(e.Detail)) - return - case http.StatusTooManyRequests: - addRetryAfterHeader(w) - renderError(w, r, errTooManyRequests(e.Detail)) - case http.StatusBadRequest: - renderError(w, r, errBadRequest(e.Detail)) - case http.StatusForbidden: - renderError(w, r, errPermissionDenied(e.Detail)) - default: - renderError(w, r, errInternalError(err.Error())) - } - logger.Debug().Err(err).Msg("could not get thumbnail") + g.handleWorkflowError(w, r, err, tr, logger) return } - - g.sendThumbnailResponse(rsp, w, r) + setAspectIgnoredHeader(w, aIgnored) + generator.WriteThumbnailResponse(w, data, ext) } func whoami(gatewayClient gatewayv1beta1.GatewayAPIClient, ctx context.Context, token string) (*userv1beta1.User, error) { - // look up user from token via WhoAmI userRes, err := gatewayClient.WhoAmI(ctx, &gatewayv1beta1.WhoAmIRequest{ Token: token, }) @@ -304,83 +306,14 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { return } - t := r.Header.Get(revactx.TokenHeader) - - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - logger.Error().Err(err).Msg("could not get reva gatewayClient") - renderError(w, r, errInternalError("could not get reva gatewayClient")) - return - } - - var user *userv1beta1.User - if tr.Identifier == "" { - user, err = whoami(gatewayClient, r.Context(), t) - if err != nil { - logger.Error().Err(err).Msg("could not get user") - renderError(w, r, errInternalError("could not get user")) - return - } - } else { - // look up user from URL via GetUserByClaim - ctx := grpcmetadata.AppendToOutgoingContext(r.Context(), revactx.TokenHeader, t) - userRes, err := gatewayClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ - Claim: "username", - Value: tr.Identifier, - }) - if err != nil { - logger.Error().Err(err).Msg("could not get user: transport error") - renderError(w, r, errInternalError("could not get user")) - return - } - if userRes.Status.Code != rpcv1beta1.Code_CODE_OK { - logger.Debug().Str("grpcmessage", userRes.GetStatus().GetMessage()).Msg("could not get user") - renderError(w, r, errInternalError("could not get user")) - return - } - user = userRes.GetUser() - } - - fullPath := filepath.Join(templates.WithUser(user, g.config.WebdavNamespace), tr.Filepath) - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Processor: tr.Processor, - Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{ - Cs3Source: &thumbnailsmsg.CS3Source{ - Path: fullPath, - Authorization: t, - }, - }, - }) + auth := r.Header.Get(revactx.TokenHeader) + data, ext, aIgnored, err := g.workflow.Execute(r.Context(), tr, auth, logger) if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusTooEarly: - // StatusTooEarly if file is processing - renderError(w, r, errTooEarly(e.Detail)) - return - case http.StatusTooManyRequests: - addRetryAfterHeader(w) - renderError(w, r, errTooManyRequests(e.Detail)) - case http.StatusBadRequest: - renderError(w, r, errBadRequest(e.Detail)) - case http.StatusForbidden: - renderError(w, r, errPermissionDenied(e.Detail)) - default: - renderError(w, r, errInternalError(err.Error())) - } - g.log.Error().Err(err).Msg("could not get thumbnail") + g.handleWorkflowError(w, r, err, tr, logger) return } - - g.sendThumbnailResponse(rsp, w, r) + setAspectIgnoredHeader(w, aIgnored) + generator.WriteThumbnailResponse(w, data, ext) } func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) { @@ -392,40 +325,19 @@ func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) { return } - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Processor: tr.Processor, - Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{ - WebdavSource: &thumbnailsmsg.WebdavSource{ - Url: g.config.OpenCloudPublicURL + r.URL.RequestURI(), - IsPublicLink: true, - PublicLinkToken: tr.PublicLinkToken, - }, - }, - }) + auth, err := workflow.ResolvePublicLinkAuth(r.Context(), r, chi.URLParam(r, "token"), g.gatewaySelector) if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusBadRequest: - renderError(w, r, errBadRequest(e.Detail)) - case http.StatusTooManyRequests: - addRetryAfterHeader(w) - renderError(w, r, errTooManyRequests(e.Detail)) - default: - renderError(w, r, errInternalError(err.Error())) - } - g.log.Error().Err(err).Msg("could not get thumbnail") + g.handlePublicLinkAuthError(w, r, err, tr.Filename, logger) return } - g.sendThumbnailResponse(rsp, w, r) + data, ext, aIgnored, err := g.workflow.ExecutePublic(r.Context(), tr, auth, logger) + if err != nil { + g.handleWorkflowError(w, r, err, tr, logger) + return + } + setAspectIgnoredHeader(w, aIgnored) + generator.WriteThumbnailResponse(w, data, ext) } func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { @@ -437,91 +349,97 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { return } - _, err = g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Processor: tr.Processor, - Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{ - WebdavSource: &thumbnailsmsg.WebdavSource{ - Url: g.config.OpenCloudPublicURL + r.URL.RequestURI(), - IsPublicLink: true, - PublicLinkToken: tr.PublicLinkToken, - }, - }, - }) + auth, err := workflow.ResolvePublicLinkAuth(r.Context(), r, chi.URLParam(r, "token"), g.gatewaySelector) if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusBadRequest: - renderError(w, r, errBadRequest(e.Detail)) - case http.StatusTooManyRequests: - addRetryAfterHeader(w) - renderError(w, r, errTooManyRequests(e.Detail)) - default: - renderError(w, r, errInternalError(err.Error())) - } - logger.Debug().Err(err).Msg("could not get thumbnail") + g.handlePublicLinkAuthError(w, r, err, tr.Filename, logger) return } + if err := g.workflow.Head(r.Context(), tr, auth, logger); err != nil { + g.handleHeadError(w, r, err, tr, logger) + return + } w.WriteHeader(http.StatusOK) } -func (g Webdav) sendThumbnailResponse(rsp *thumbnailssvc.GetThumbnailResponse, w http.ResponseWriter, r *http.Request) { - logger := g.log.SubloggerWithRequestID(r.Context()) - client := &http.Client{ - // Timeout: time.Second * 5, - } +// aspectIgnoredHeader is set on thumbnail responses when an explicit processor +// overrode the legacy "a" flag with a different behavior, so developers can tell +// their client to send a consistent request (see the thumbnail docs). +const aspectIgnoredHeader = "X-OpenCloud-Thumbnail-Aspect-Ignored" - dlReq, err := http.NewRequest(http.MethodGet, rsp.DataEndpoint, http.NoBody) - if err != nil { - renderError(w, r, errInternalError(err.Error())) - logger.Error().Err(err).Msg("could not create download thumbnail request") +func setAspectIgnoredHeader(w http.ResponseWriter, ignored bool) { + if !ignored { return } - dlReq.Header.Set("Transfer-Token", rsp.TransferToken) + w.Header().Set(aspectIgnoredHeader, "1; an explicit processor overrode the legacy 'a' flag, see the thumbnail docs") +} - dlRsp, err := client.Do(dlReq) - if err != nil { - renderError(w, r, errInternalError(err.Error())) - logger.Error().Err(err).Msg("could not download thumbnail: transport error") +func (g Webdav) handleWorkflowError(w http.ResponseWriter, r *http.Request, err error, tr *requests.ThumbnailRequest, logger log.Logger) { + if errors.Is(err, workflow.ErrFileProcessing) { + addRetryAfterHeader(w) + renderError(w, r, errTooEarly("file is being processed")) return } - defer dlRsp.Body.Close() - - if dlRsp.StatusCode != http.StatusOK { - logger.Debug(). - Str("transfer_token", rsp.GetTransferToken()). - Str("data_endpoint", rsp.GetDataEndpoint()). - Str("response_status", dlRsp.Status). - Msg("could not download thumbnail") - renderError(w, r, newErrResponse(dlRsp.StatusCode, "could not download thumbnail")) + if errors.Is(err, workflow.ErrImageTooLarge) { + logger.Debug().Err(err).Msg("thumbnail input image too large") + renderError(w, r, errPermissionDenied(workflow.ErrImageTooLarge.Error())) + return + } + if errors.Is(err, workflow.ErrPermissionDenied) { + logger.Debug().Err(err).Msg("user lacks download permission for thumbnail") + renderError(w, r, errPermissionDenied(workflow.ErrPermissionDenied.Error())) + return + } + if errors.Is(err, workflow.ErrNotAFile) { + logger.Debug().Err(err).Msg("thumbnail requested for a non-file resource") + renderError(w, r, errBadRequest("Unsupported file type")) return } + logger.Debug().Err(err).Msg("thumbnail workflow failed") + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) +} - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", dlRsp.Header.Get("Content-Type")) - _, err = io.Copy(w, dlRsp.Body) - if err != nil { - logger.Error().Err(err).Msg("failed to write thumbnail to response writer") +func (g Webdav) handleHeadError(w http.ResponseWriter, r *http.Request, err error, tr *requests.ThumbnailRequest, logger log.Logger) { + if errors.Is(err, workflow.ErrFileProcessing) { + addRetryAfterHeader(w) + renderError(w, r, errTooEarly("file is being processed")) + return + } + if errors.Is(err, workflow.ErrImageTooLarge) { + logger.Debug().Err(err).Msg("thumbnail input image too large") + renderError(w, r, errPermissionDenied(workflow.ErrImageTooLarge.Error())) + return + } + if errors.Is(err, workflow.ErrPermissionDenied) { + logger.Debug().Err(err).Msg("user lacks download permission for thumbnail") + renderError(w, r, errPermissionDenied(workflow.ErrPermissionDenied.Error())) + return + } + if errors.Is(err, workflow.ErrNotAFile) { + logger.Debug().Err(err).Msg("thumbnail head check requested for a non-file resource") + renderError(w, r, errBadRequest("Unsupported file type")) + return } + logger.Debug().Err(err).Msg("thumbnail head check failed") + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) } -func extensionToThumbnailType(ext string) thumbnailsmsg.ThumbnailType { - switch strings.ToUpper(ext) { - case "GIF": - return thumbnailsmsg.ThumbnailType_GIF - case "PNG": - return thumbnailsmsg.ThumbnailType_PNG - default: - return thumbnailsmsg.ThumbnailType_JPG +func (g Webdav) handlePublicLinkAuthError(w http.ResponseWriter, r *http.Request, err error, filename string, logger log.Logger) { + errMsg := err.Error() + if strings.Contains(errMsg, "PERMISSION_DENIED") || strings.Contains(errMsg, "password required") { + // A password-protected public link accessed without (or with the wrong) + // password must not reveal that the resource exists, so it is hidden + // behind a 404 rather than a 403. + logger.Debug().Err(err).Msg("public link requires a password") + renderError(w, r, errNotFound(notFoundMsg(filename))) + return + } + if strings.Contains(errMsg, "FAILED_PRECONDITION") || strings.Contains(errMsg, "expired") { + renderError(w, r, newErrResponse(http.StatusGone, "public link has expired")) + return } + logger.Debug().Err(err).Msg("could not authenticate public link") + renderError(w, r, errInternalError("could not authenticate public link")) } // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error @@ -568,8 +486,9 @@ func errTooEarly(msg string) *errResponse { return newErrResponse(http.StatusTooEarly, msg) } -func errTooManyRequests(msg string) *errResponse { - return newErrResponse(http.StatusTooManyRequests, msg) +func addRetryAfterHeader(w http.ResponseWriter) { + after := rand.IntN(14) + 1 + w.Header().Set("Retry-After", strconv.Itoa(after)) } func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) { @@ -580,8 +499,3 @@ func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) { func notFoundMsg(name string) string { return "File with name " + name + " could not be located" } - -func addRetryAfterHeader(w http.ResponseWriter) { - after := rand.IntN(14) + 1 - w.Header().Set("Retry-After", strconv.Itoa(after)) -} diff --git a/services/webdav/pkg/thumbnail/cache/cache.go b/services/webdav/pkg/thumbnail/cache/cache.go new file mode 100644 index 0000000000..a108a3ec7e --- /dev/null +++ b/services/webdav/pkg/thumbnail/cache/cache.go @@ -0,0 +1,61 @@ +package cache + +import ( + "errors" + "path/filepath" + + "github.com/minio/minio-go/v7/pkg/credentials" + + "github.com/opencloud-eu/opencloud/pkg/config/defaults" +) + +var ErrCacheMiss = errors.New("thumbnail cache miss") + +// DefaultFileCacheDir is the on-disk location of file-backend thumbnails, +// mirroring main's $OC_BASE_DATA_PATH/thumbnails/files layout. +func DefaultFileCacheDir() string { + return filepath.Join(defaults.BaseDataPath(), "thumbnails", "files") +} + +// ThumbnailCache provides cached storage for generated thumbnails. +type ThumbnailCache interface { + Get(key string) ([]byte, error) + Put(key string, data []byte) error +} + +// NewThumbnailCache creates a cache instance based on the configured backend. +func NewThumbnailCache(backend string, cacheDir string, s3cfg *S3CacheConfig) ThumbnailCache { + switch backend { + case "none": + return NoopCache{} + case "memory": + return NewInMemoryCache() + case "file": + dir := cacheDir + if dir == "" { + dir = DefaultFileCacheDir() + } + return NewFileCache(dir) + case "s3": + if s3cfg != nil && s3cfg.IsComplete() { + return NewS3Cache(*s3cfg) + } + return NoopCache{} + default: + return NoopCache{} + } +} + +// BuildS3CacheConfig creates an S3CacheConfig from raw config fields. +func BuildS3CacheConfig(bucket, region, endpoint, accessKey, secretKey string) *S3CacheConfig { + cfg := &S3CacheConfig{ + Bucket: bucket, + Region: region, + Endpoint: endpoint, + Secure: true, + } + if accessKey != "" && secretKey != "" { + cfg.Credentials = credentials.NewStaticV4(accessKey, secretKey, "") + } + return cfg +} diff --git a/services/webdav/pkg/thumbnail/cache/cache_test.go b/services/webdav/pkg/thumbnail/cache/cache_test.go new file mode 100644 index 0000000000..162a840259 --- /dev/null +++ b/services/webdav/pkg/thumbnail/cache/cache_test.go @@ -0,0 +1,587 @@ +package cache + +import ( + "bytes" + "context" + "errors" + "io" + "os" + "path/filepath" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + minio "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + + "testing" +) + +func TestCache(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Cache Suite") +} + +var _ = Describe("InMemoryCache", func() { + var cache ThumbnailCache + + BeforeEach(func() { + cache = NewInMemoryCache() + }) + + It("should return ErrCacheMiss for missing key", func() { + data, err := cache.Get("nonexistent") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should store and retrieve data", func() { + err := cache.Put("key1", []byte("thumbnail-data")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("thumbnail-data"))) + }) + + It("should return independent copies on Get", func() { + err := cache.Put("key1", []byte("original")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + data[0] = 'X' + + original, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + Expect(original).To(Equal([]byte("original"))) + }) + + It("should overwrite existing keys", func() { + err := cache.Put("key1", []byte("v1")) + Expect(err).ToNot(HaveOccurred()) + + err = cache.Put("key1", []byte("v2")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("v2"))) + }) + + It("should handle empty data", func() { + err := cache.Put("empty-key", []byte{}) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("empty-key") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(BeEmpty()) + }) +}) + +var _ = Describe("NoopCache", func() { + var cache ThumbnailCache + + BeforeEach(func() { + cache = NoopCache{} + }) + + It("should always return ErrCacheMiss on Get", func() { + data, err := cache.Get("any-key") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should accept Put without error", func() { + err := cache.Put("key1", []byte("data")) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should still miss after Put", func() { + _ = cache.Put("key1", []byte("data")) + data, err := cache.Get("key1") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) +}) + +var _ = Describe("NewThumbnailCache", func() { + It("should create InMemoryCache for 'memory' backend", func() { + cache := NewThumbnailCache("memory", "", nil) + Expect(cache).ToNot(BeNil()) + err := cache.Put("k", []byte("v")) + Expect(err).ToNot(HaveOccurred()) + data, err := cache.Get("k") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("v"))) + }) + + It("should create NoopCache for unknown backend", func() { + cache := NewThumbnailCache("unknown", "", nil) + Expect(cache).ToNot(BeNil()) + data, err := cache.Get("k") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should create NoopCache for empty backend", func() { + cache := NewThumbnailCache("", "", nil) + Expect(cache).ToNot(BeNil()) + data, err := cache.Get("k") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should create S3Cache for 's3' backend with complete config", func() { + cfg := &S3CacheConfig{ + Bucket: "my-bucket", + Region: "us-east-1", + Credentials: credentials.NewStaticV4("ak", "sk", ""), + } + cache := NewThumbnailCache("s3", "", cfg) + Expect(cache).ToNot(BeNil()) + s3cache, ok := cache.(*S3Cache) + Expect(ok).To(BeTrue()) + Expect(s3cache.config.Bucket).To(Equal("my-bucket")) + }) + + It("should create NoopCache for 's3' backend with incomplete config", func() { + cfg := &S3CacheConfig{Bucket: "only-bucket"} + cache := NewThumbnailCache("s3", "", cfg) + Expect(cache).ToNot(BeNil()) + data, err := cache.Get("k") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should create NoopCache for 's3' backend with nil config", func() { + cache := NewThumbnailCache("s3", "", nil) + Expect(cache).ToNot(BeNil()) + data, err := cache.Get("k") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should build S3CacheConfig from env var fields via BuildS3CacheConfig", func() { + cfg := BuildS3CacheConfig("my-bucket", "us-east-1", "http://minio:9000", "ak", "sk") + Expect(cfg.Bucket).To(Equal("my-bucket")) + Expect(cfg.Region).To(Equal("us-east-1")) + Expect(cfg.Endpoint).To(Equal("http://minio:9000")) + Expect(cfg.Secure).To(BeTrue()) + Expect(cfg.Credentials).ToNot(BeNil()) + }) + + It("should build incomplete S3CacheConfig when credentials are missing", func() { + cfg := BuildS3CacheConfig("my-bucket", "us-east-1", "", "", "") + Expect(cfg.IsComplete()).To(BeFalse()) + }) +}) + +// mockS3Object is a closable reader for mock S3 objects. +type mockS3Object struct { + *bytes.Reader +} + +func (m *mockS3Object) Close() error { + return nil +} + +var _ s3Object = (*mockS3Object)(nil) + +// mockS3Client implements s3Client interface for testing. +type mockS3Client struct { + objects map[string][]byte + contentTypes map[string]string + capturedOpts minio.PutObjectOptions + getErr error + putErr error +} + +func newMockS3Client() *mockS3Client { + return &mockS3Client{ + objects: make(map[string][]byte), + contentTypes: make(map[string]string), + } +} + +func (m *mockS3Client) GetObject(_ context.Context, _, objectName string, _ minio.GetObjectOptions) (s3Object, error) { + if m.getErr != nil { + return nil, m.getErr + } + data, ok := m.objects[objectName] + if !ok { + errResp := minio.ErrorResponse{ + Code: "NoSuchKey", + Message: "key does not exist", + StatusCode: 404, + } + return nil, errResp + } + return &mockS3Object{Reader: bytes.NewReader(data)}, nil +} + +func (m *mockS3Client) PutObject(_ context.Context, _, objectName string, reader io.Reader, _ int64, opts minio.PutObjectOptions) (minio.UploadInfo, error) { + if m.putErr != nil { + return minio.UploadInfo{}, m.putErr + } + data, err := io.ReadAll(reader) + if err != nil { + return minio.UploadInfo{}, err + } + m.objects[objectName] = data + m.contentTypes[objectName] = opts.ContentType + m.capturedOpts = opts + return minio.UploadInfo{}, nil +} + +var _ = Describe("S3Cache", func() { + var ( + mockClient *mockS3Client + cache *S3Cache + ) + + BeforeEach(func() { + mockClient = newMockS3Client() + + cfg := S3CacheConfig{ + Bucket: "test-bucket", + Region: "us-east-1", + } + + cache = &S3Cache{config: cfg} + cache.newFunc = func(_ S3CacheConfig) (s3Client, error) { + return mockClient, nil + } + }) + + It("should return ErrCacheMiss for missing key", func() { + data, err := cache.Get("nonexistent-key") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should store and retrieve data", func() { + err := cache.Put("sha256:abc123-400x400.jpg", []byte("thumbnail-data")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("sha256:abc123-400x400.jpg") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("thumbnail-data"))) + }) + + It("should return independent copies on Get", func() { + err := cache.Put("key1", []byte("original")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + data[0] = 'X' + + original, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + Expect(original).To(Equal([]byte("original"))) + }) + + It("should overwrite existing keys", func() { + err := cache.Put("key1", []byte("v1")) + Expect(err).ToNot(HaveOccurred()) + + err = cache.Put("key1", []byte("v2")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("key1") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("v2"))) + }) + + It("should handle empty data", func() { + err := cache.Put("empty-key", []byte{}) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("empty-key") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(BeEmpty()) + }) + + It("should use flat key format", func() { + key := "sha256:abc123-400x400.jpg" + err := cache.Put(key, []byte("data")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("data"))) + }) + + It("should initialize client lazily on first access", func() { + Expect(cache.client).To(BeNil()) + + _ = cache.Put("key1", []byte("data")) + Expect(cache.client).ToNot(BeNil()) + }) + + It("should handle S3 errors gracefully on Get", func() { + mockClient.getErr = errors.New("connection refused") + + data, err := cache.Get("any-key") + Expect(err).To(HaveOccurred()) + Expect(data).To(BeNil()) + }) + + It("should handle S3 errors gracefully on Put", func() { + mockClient.putErr = errors.New("connection refused") + + err := cache.Put("key1", []byte("data")) + Expect(err).To(HaveOccurred()) + }) + + It("should propagate init error from newFunc", func() { + cache.newFunc = func(_ S3CacheConfig) (s3Client, error) { + return nil, errors.New("init failed") + } + + data, err := cache.Get("any-key") + Expect(err).To(HaveOccurred()) + Expect(data).To(BeNil()) + }) + + It("should derive Content-Type from .jpg extension", func() { + err := cache.Put("sha256:abc123-400x400.jpg", []byte("data")) + Expect(err).ToNot(HaveOccurred()) + Expect(mockClient.contentTypes["sha256:abc123-400x400.jpg"]).To(Equal("image/jpeg")) + }) + + It("should derive Content-Type from .png extension", func() { + err := cache.Put("sha256:def456-800x600.png", []byte("data")) + Expect(err).ToNot(HaveOccurred()) + Expect(mockClient.contentTypes["sha256:def456-800x600.png"]).To(Equal("image/png")) + }) + + It("should fallback to application/octet-stream for unknown extension", func() { + err := cache.Put("key-without-ext", []byte("data")) + Expect(err).ToNot(HaveOccurred()) + Expect(mockClient.contentTypes["key-without-ext"]).To(Equal("application/octet-stream")) + }) +}) + +var _ = Describe("FileCache", func() { + var ( + tmpdir string + cache *FileCache + ) + + BeforeEach(func() { + var err error + tmpdir, err = os.MkdirTemp("", "filecache-test") + Expect(err).ToNot(HaveOccurred()) + + cache = NewFileCache(tmpdir) + }) + + AfterEach(func() { + Expect(os.RemoveAll(tmpdir)).To(Succeed()) + }) + + It("should return ErrCacheMiss for missing key", func() { + data, err := cache.Get("ab/cd/ef1234567890/400x400.jpg") + Expect(err).To(MatchError(ErrCacheMiss)) + Expect(data).To(BeNil()) + }) + + It("should store and retrieve data", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + err := cache.Put(key, []byte("thumbnail-data")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("thumbnail-data"))) + }) + + It("should use hierarchical directory layout", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + err := cache.Put(key, []byte("thumbnail-data")) + Expect(err).ToNot(HaveOccurred()) + + expectedPath := filepath.Join(tmpdir, "ab", "cd", "ef1234567890", "400x400.jpg") + content, err := os.ReadFile(expectedPath) + Expect(err).ToNot(HaveOccurred()) + Expect(content).To(Equal([]byte("thumbnail-data"))) + }) + + It("should create parent directories with 0700 permissions", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + err := cache.Put(key, []byte("thumbnail-data")) + Expect(err).ToNot(HaveOccurred()) + + dirPath := filepath.Join(tmpdir, "ab", "cd") + info, err := os.Stat(dirPath) + Expect(err).ToNot(HaveOccurred()) + Expect(info.IsDir()).To(BeTrue()) + Expect(info.Mode().Perm()).To(Equal(os.FileMode(0700))) + }) + + It("should return independent copies on Get", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + err := cache.Put(key, []byte("original")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + data[0] = 'X' + + original, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(original).To(Equal([]byte("original"))) + }) + + It("should be idempotent - Put is no-op if file already exists", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + err := cache.Put(key, []byte("v1")) + Expect(err).ToNot(HaveOccurred()) + + info1, err := os.Stat(filepath.Join(tmpdir, "ab", "cd", "ef1234567890", "400x400.jpg")) + Expect(err).ToNot(HaveOccurred()) + modTime1 := info1.ModTime() + + time.Sleep(10 * time.Millisecond) + + err = cache.Put(key, []byte("v2")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("v1"))) + + info2, err := os.Stat(filepath.Join(tmpdir, "ab", "cd", "ef1234567890", "400x400.jpg")) + Expect(err).ToNot(HaveOccurred()) + Expect(info2.ModTime()).To(Equal(modTime1)) + }) + + It("should handle empty data", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + err := cache.Put(key, []byte{}) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(BeEmpty()) + }) + + It("should use atomic write via temp file + rename", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + + watchDir := filepath.Join(tmpdir, "ab", "cd", "ef1234567890") + Expect(os.MkdirAll(watchDir, 0700)).To(Succeed()) + + done := make(chan struct{}) + sawTempFile := false + go func() { + for { + select { + case <-done: + return + default: + entries, _ := os.ReadDir(watchDir) + for _, e := range entries { + if strings.HasPrefix(e.Name(), "tmpthumb") { + sawTempFile = true + } + } + time.Sleep(1 * time.Millisecond) + } + } + }() + + err := cache.Put(key, bytes.Repeat([]byte("x"), 1024*1024)) + Expect(err).ToNot(HaveOccurred()) + + close(done) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(len(data)).To(Equal(1024 * 1024)) + _ = sawTempFile + }) + + It("should handle disk write error gracefully", func() { + blocker := filepath.Join(tmpdir, "blocker") + Expect(os.WriteFile(blocker, []byte("x"), 0600)).To(Succeed()) + + // The cache dir path passes through a regular file, so MkdirAll cannot + // create the target directory for any user (root or not) -> Put must error. + rc := NewFileCache(filepath.Join(blocker, "sub")) + err := rc.Put("ab/cd/ef1234567890/400x400.jpg", []byte("data")) + Expect(err).To(HaveOccurred()) + }) + + It("should handle corrupt/partial files on Get", func() { + key := "ab/cd/ef1234567890/400x400.jpg" + dirPath := filepath.Join(tmpdir, "ab", "cd", "ef1234567890") + Expect(os.MkdirAll(dirPath, 0700)).To(Succeed()) + + filePath := filepath.Join(dirPath, "400x400.jpg") + Expect(os.WriteFile(filePath, []byte("corrupt-data"), 0600)).To(Succeed()) + + data, err := cache.Get(key) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("corrupt-data"))) + }) +}) + +var _ = Describe("NewThumbnailCache factory", func() { + It("should create FileCache for 'file' backend", func() { + tmpdir, err := os.MkdirTemp("", "factory-test") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(tmpdir) + + cache := NewThumbnailCache("file", tmpdir, nil) + Expect(cache).ToNot(BeNil()) + + fileCache, ok := cache.(*FileCache) + Expect(ok).To(BeTrue()) + Expect(fileCache.dir).To(Equal(tmpdir)) + + err = cache.Put("ab/cd/ef1234567890/400x400.jpg", []byte("file-data")) + Expect(err).ToNot(HaveOccurred()) + + data, err := cache.Get("ab/cd/ef1234567890/400x400.jpg") + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("file-data"))) + }) + + It("should create FileCache with default dir when empty string passed", func() { + cache := NewThumbnailCache("file", "", nil) + fileCache, ok := cache.(*FileCache) + Expect(ok).To(BeTrue()) + Expect(fileCache.dir).To(Equal(DefaultFileCacheDir())) + }) +}) + +var _ = Describe("S3CacheConfig validation", func() { + It("should detect incomplete config", func() { + cfg := S3CacheConfig{Bucket: "test"} + Expect(cfg.IsComplete()).To(BeFalse()) + }) + + It("should detect complete config with credentials", func() { + cfg := S3CacheConfig{ + Bucket: "test-bucket", + Region: "us-east-1", + Credentials: credentials.NewStaticV4("access-key", "secret-key", ""), + } + Expect(cfg.IsComplete()).To(BeTrue()) + }) + + It("should be incomplete without bucket", func() { + cfg := S3CacheConfig{Credentials: credentials.NewStaticV4("access-key", "secret-key", "")} + Expect(cfg.IsComplete()).To(BeFalse()) + }) + + It("should be incomplete without region", func() { + cfg := S3CacheConfig{Bucket: "test-bucket", Credentials: credentials.NewStaticV4("access-key", "secret-key", "")} + Expect(cfg.IsComplete()).To(BeFalse()) + }) +}) diff --git a/services/webdav/pkg/thumbnail/cache/filecache.go b/services/webdav/pkg/thumbnail/cache/filecache.go new file mode 100644 index 0000000000..4701903774 --- /dev/null +++ b/services/webdav/pkg/thumbnail/cache/filecache.go @@ -0,0 +1,78 @@ +package cache + +import ( + "errors" + "io/fs" + "os" + "path/filepath" +) + +// FileCache stores thumbnails on the local filesystem using hierarchical directories. +type FileCache struct { + dir string +} + +func NewFileCache(dir string) *FileCache { + return &FileCache{dir: dir} +} + +// The cache key is already a hierarchical relative path (built to match main's +// BuildKey layout), so the on-disk location is simply the cache dir joined with it. +func (c *FileCache) Get(key string) ([]byte, error) { + path := filepath.Join(c.dir, key) + + data, err := os.ReadFile(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil, ErrCacheMiss + } + return nil, err + } + + result := make([]byte, len(data)) + copy(result, data) + return result, nil +} + +func (c *FileCache) Put(key string, data []byte) error { + imgPath := filepath.Join(c.dir, key) + dir := filepath.Dir(imgPath) + + if _, err := os.Stat(imgPath); err == nil { + return nil + } + + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + + f, err := os.CreateTemp(dir, "tmpthumb") + if err != nil { + return err + } + tempPath := f.Name() + + if _, err := f.Write(data); err != nil { + f.Close() + os.Remove(tempPath) + return err + } + + if err := f.Sync(); err != nil { + f.Close() + os.Remove(tempPath) + return err + } + + if err := f.Close(); err != nil { + os.Remove(tempPath) + return err + } + + if err := os.Rename(tempPath, imgPath); err != nil { + os.Remove(tempPath) + return err + } + + return nil +} diff --git a/services/webdav/pkg/thumbnail/cache/inmemory.go b/services/webdav/pkg/thumbnail/cache/inmemory.go new file mode 100644 index 0000000000..1141a257ec --- /dev/null +++ b/services/webdav/pkg/thumbnail/cache/inmemory.go @@ -0,0 +1,39 @@ +package cache + +import "sync" + +// InMemoryCache is a simple in-memory thumbnail cache using sync.RWMutex. +type InMemoryCache struct { + mu sync.RWMutex + data map[string][]byte +} + +func NewInMemoryCache() *InMemoryCache { + return &InMemoryCache{ + data: make(map[string][]byte), + } +} + +func (c *InMemoryCache) Get(key string) ([]byte, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + data, ok := c.data[key] + if !ok { + return nil, ErrCacheMiss + } + + result := make([]byte, len(data)) + copy(result, data) + return result, nil +} + +func (c *InMemoryCache) Put(key string, data []byte) error { + c.mu.Lock() + defer c.mu.Unlock() + + cp := make([]byte, len(data)) + copy(cp, data) + c.data[key] = cp + return nil +} diff --git a/services/webdav/pkg/thumbnail/cache/noop.go b/services/webdav/pkg/thumbnail/cache/noop.go new file mode 100644 index 0000000000..ac7f500715 --- /dev/null +++ b/services/webdav/pkg/thumbnail/cache/noop.go @@ -0,0 +1,12 @@ +package cache + +// NoopCache always returns a cache miss and discards puts. +type NoopCache struct{} + +func (NoopCache) Get(_ string) ([]byte, error) { + return nil, ErrCacheMiss +} + +func (NoopCache) Put(_ string, _ []byte) error { + return nil +} diff --git a/services/webdav/pkg/thumbnail/cache/s3cache.go b/services/webdav/pkg/thumbnail/cache/s3cache.go new file mode 100644 index 0000000000..687d882f11 --- /dev/null +++ b/services/webdav/pkg/thumbnail/cache/s3cache.go @@ -0,0 +1,134 @@ +package cache + +import ( + "bytes" + "context" + "io" + "mime" + "path/filepath" + "sync" + + minio "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +// S3CacheConfig holds the configuration for an S3 cache backend. +type S3CacheConfig struct { + Bucket string + Region string + Endpoint string + Secure bool + Credentials *credentials.Credentials +} + +// IsComplete returns true if all required fields are set. +func (c S3CacheConfig) IsComplete() bool { + return c.Bucket != "" && c.Region != "" && c.Credentials != nil +} + +// s3Object is the subset of minio.Object used by S3Cache. +type s3Object interface { + io.ReadCloser +} + +// s3Client is the subset of minio.Client used by S3Cache. +type s3Client interface { + GetObject(ctx context.Context, bucketName, objectName string, opts minio.GetObjectOptions) (s3Object, error) + PutObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) +} + +// realS3Client wraps *minio.Client to satisfy s3Client interface. +type realS3Client struct { + *minio.Client +} + +func (r *realS3Client) GetObject(ctx context.Context, bucketName, objectName string, opts minio.GetObjectOptions) (s3Object, error) { + return r.Client.GetObject(ctx, bucketName, objectName, opts) +} + +// S3Cache stores thumbnails in an S3-compatible bucket. +type S3Cache struct { + config S3CacheConfig + client s3Client + once sync.Once + initErr error + newFunc func(cfg S3CacheConfig) (s3Client, error) +} + +func NewS3Cache(cfg S3CacheConfig) *S3Cache { + c := &S3Cache{config: cfg} + c.newFunc = func(cfg S3CacheConfig) (s3Client, error) { + cl, err := minio.New(cfg.Endpoint, &minio.Options{ + Creds: cfg.Credentials, + Secure: cfg.Secure, + Region: cfg.Region, + }) + if err != nil { + return nil, err + } + return &realS3Client{Client: cl}, nil + } + return c +} + +// initClient lazily initializes the S3 client on first access. +func (c *S3Cache) initClient() error { + c.once.Do(func() { + cl, err := c.newFunc(c.config) + if err != nil { + c.initErr = err + return + } + c.client = cl + }) + return c.initErr +} + +func (c *S3Cache) Get(key string) ([]byte, error) { + if err := c.initClient(); err != nil { + return nil, err + } + + obj, err := c.client.GetObject(context.Background(), c.config.Bucket, key, minio.GetObjectOptions{}) + if err != nil { + errResp := minio.ToErrorResponse(err) + if errResp.Code == "NoSuchKey" || errResp.StatusCode == 404 { + return nil, ErrCacheMiss + } + return nil, err + } + defer obj.Close() + + data, err := io.ReadAll(obj) + if err != nil { + return nil, err + } + + result := make([]byte, len(data)) + copy(result, data) + return result, nil +} + +func (c *S3Cache) Put(key string, data []byte) error { + if err := c.initClient(); err != nil { + return err + } + + ext := filepath.Ext(key) + contentType := mime.TypeByExtension(ext) + if contentType == "" { + contentType = "application/octet-stream" + } + + _, err := c.client.PutObject( + context.Background(), + c.config.Bucket, + key, + bytes.NewReader(data), + int64(len(data)), + minio.PutObjectOptions{ + ContentType: contentType, + }, + ) + return err +} diff --git a/services/webdav/pkg/thumbnail/mimetype.go b/services/webdav/pkg/thumbnail/mimetype.go new file mode 100644 index 0000000000..26ce86e794 --- /dev/null +++ b/services/webdav/pkg/thumbnail/mimetype.go @@ -0,0 +1,13 @@ +package thumbnail + +import "mime" + +// IsMimeTypeSupported validates if the mime type is supported. +func IsMimeTypeSupported(m string) bool { + mimeType, _, err := mime.ParseMediaType(m) + if err != nil { + return false + } + _, supported := SupportedMimeTypes[mimeType] + return supported +} diff --git a/services/webdav/pkg/thumbnail/mimetypes.go b/services/webdav/pkg/thumbnail/mimetypes.go new file mode 100644 index 0000000000..a0d596dedd --- /dev/null +++ b/services/webdav/pkg/thumbnail/mimetypes.go @@ -0,0 +1,22 @@ +//go:build !enable_vips + +package thumbnail + +var ( + // SupportedMimeTypes contains all mimetypes which are supported by the thumbnailer. + SupportedMimeTypes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "image/bmp": {}, + "image/x-ms-bmp": {}, + "image/tiff": {}, + "text/plain": {}, + "audio/flac": {}, + "audio/mpeg": {}, + "audio/ogg": {}, + "application/vnd.geogebra.slides": {}, + "application/vnd.geogebra.pinboard": {}, + } +) diff --git a/services/webdav/pkg/thumbnail/mimetypes_vips.go b/services/webdav/pkg/thumbnail/mimetypes_vips.go new file mode 100644 index 0000000000..9fe1a3aef0 --- /dev/null +++ b/services/webdav/pkg/thumbnail/mimetypes_vips.go @@ -0,0 +1,23 @@ +//go:build enable_vips + +package thumbnail + +var ( + // SupportedMimeTypes contains all mimetypes which are supported by the thumbnailer. + SupportedMimeTypes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "image/bmp": {}, + "image/x-ms-bmp": {}, + "image/tiff": {}, + "text/plain": {}, + "audio/flac": {}, + "audio/mpeg": {}, + "audio/ogg": {}, + "application/vnd.geogebra.slides": {}, + "application/vnd.geogebra.pinboard": {}, + "image/webp": {}, + } +) diff --git a/services/webdav/pkg/thumbnail/resolutions.go b/services/webdav/pkg/thumbnail/resolutions.go new file mode 100644 index 0000000000..5f98e49477 --- /dev/null +++ b/services/webdav/pkg/thumbnail/resolutions.go @@ -0,0 +1,109 @@ +package thumbnail + +import ( + "fmt" + "image" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +const ( + _resolutionSeparator = "x" +) + +// ParseResolution returns an image.Rectangle representing the resolution given as a string. +func ParseResolution(s string) (image.Rectangle, error) { + parts := strings.Split(s, _resolutionSeparator) + if len(parts) != 2 { + return image.Rectangle{}, fmt.Errorf("failed to parse resolution: %s. Expected format x", s) + } + width, err := strconv.Atoi(parts[0]) + if err != nil { + return image.Rectangle{}, fmt.Errorf("width: %s has an invalid value. Expected an integer", parts[0]) + } + height, err := strconv.Atoi(parts[1]) + if err != nil { + return image.Rectangle{}, fmt.Errorf("height: %s has an invalid value. Expected an integer", parts[1]) + } + return image.Rect(0, 0, width, height), nil +} + +// Resolutions is a list of supported target resolutions, split at parse time into +// landscape (width >= height, including squares) and portrait (height > width) +// groups so a request can be matched against the group that fits its orientation. +type Resolutions struct { + landscape []image.Rectangle + portrait []image.Rectangle +} + +// ParseResolutions creates a Resolutions instance from resolution strings, +// partitioning each into the landscape or portrait group by its orientation. +func ParseResolutions(strs []string) (*Resolutions, error) { + rs := &Resolutions{} + for _, s := range strs { + r, err := ParseResolution(s) + if err != nil { + return nil, errors.Wrap(err, "could not parse resolutions") + } + if r.Dx() >= r.Dy() { + rs.landscape = append(rs.landscape, r) + } else { + rs.portrait = append(rs.portrait, r) + } + } + return rs, nil +} + +// Match returns the target resolution to request from the generator for a given +// requested size. It is source-less: the choice depends only on the requested +// orientation and dominant dimension, never on the (unknown to webdav) source +// size. The request's orientation selects the group (width >= height -> landscape, +// else portrait); within that group the resolution whose dominant dimension is the +// smallest one still at least the requested dominant dimension is returned (the +// closest fit without going under), or the largest in the group if none qualifies. +// An empty list yields the request itself. +func (rs *Resolutions) Match(requested image.Rectangle) image.Rectangle { + group := rs.portrait + if requested.Dx() >= requested.Dy() { + group = rs.landscape + } + + if len(group) == 0 { + return requested + } + + reqDom := dominant(requested) + + var match image.Rectangle + found := false + for _, current := range group { + cDom := dominant(current) + if cDom < reqDom { + continue + } + if !found || cDom < dominant(match) { + match = current + found = true + } + } + + if !found { + for _, current := range group { + if !found || dominant(current) > dominant(match) { + match = current + found = true + } + } + } + return match +} + +// dominant returns the longer dimension of a rectangle. +func dominant(r image.Rectangle) int { + if r.Dx() > r.Dy() { + return r.Dx() + } + return r.Dy() +} diff --git a/services/webdav/pkg/thumbnail/resolutions_test.go b/services/webdav/pkg/thumbnail/resolutions_test.go new file mode 100644 index 0000000000..6b28bb8f12 --- /dev/null +++ b/services/webdav/pkg/thumbnail/resolutions_test.go @@ -0,0 +1,156 @@ +package thumbnail + +import ( + "image" + "testing" +) + +func TestInitWithEmptyArray(t *testing.T) { + rs, err := ParseResolutions([]string{}) + if err != nil { + t.Errorf("Init with an empty array should not fail. Error: %s.\n", err.Error()) + } + if rs == nil || len(rs.landscape)+len(rs.portrait) != 0 { + t.Error("Init with an empty array should return an empty Resolutions instance.\n") + } +} + +func TestInitWithNil(t *testing.T) { + rs, err := ParseResolutions(nil) + if err != nil { + t.Errorf("Init with nil parameter should not fail. Error: %s.\n", err.Error()) + } + if rs == nil || len(rs.landscape)+len(rs.portrait) != 0 { + t.Error("Init with nil parameter should return an empty Resolutions instance.\n") + } +} + +func TestInitWithInvalidValuesInArray(t *testing.T) { + _, err := ParseResolutions([]string{"invalid"}) + if err == nil { + t.Error("Init with invalid parameter should fail.\n") + } +} + +func TestInit(t *testing.T) { + rs, err := ParseResolutions([]string{"16x16"}) + if err != nil { + t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) + } + if len(rs.landscape) != 1 { + t.Errorf("resolutions has %d landscape entries, expected 1.\n", len(rs.landscape)) + } +} + +func TestInitPartitionsByOrientation(t *testing.T) { + rs, err := ParseResolutions([]string{"16x16", "280x500", "500x280"}) + if err != nil { + t.Fatalf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) + } + if len(rs.landscape) != 2 { // 16x16 (square) + 500x280 + t.Errorf("expected 2 landscape entries, got %d\n", len(rs.landscape)) + } + if len(rs.portrait) != 1 { // 280x500 + t.Errorf("expected 1 portrait entry, got %d\n", len(rs.portrait)) + } +} + +func TestMatchWithEmptyResolutions(t *testing.T) { + rs, _ := ParseResolutions(nil) + want := image.Rect(0, 0, 24, 24) + + r := rs.Match(want) + if r != want { + t.Errorf("Match from empty resolutions should return the given resolution, got %dx%d", r.Dx(), r.Dy()) + } +} + +func TestMatch(t *testing.T) { + rs, _ := ParseResolutions([]string{"16x16", "32x32", "64x64", "128x128", "500x280", "280x500"}) + + testData := []struct { + requested image.Rectangle + expected image.Rectangle + }{ + // landscape requests (width >= height) match the landscape group + {image.Rect(0, 0, 17, 17), image.Rect(0, 0, 32, 32)}, // snap up from 16 + {image.Rect(0, 0, 24, 24), image.Rect(0, 0, 32, 32)}, // exact-ish -> next higher + {image.Rect(0, 0, 80, 20), image.Rect(0, 0, 128, 128)}, // dominant 80 -> 128x128 + {image.Rect(0, 0, 1024, 1), image.Rect(0, 0, 500, 280)}, // exceeds all -> largest landscape + // portrait requests (height > width) match the portrait group + {image.Rect(0, 0, 1, 1024), image.Rect(0, 0, 280, 500)}, // dominant 1024 -> 280x500 + {image.Rect(0, 0, 20, 80), image.Rect(0, 0, 280, 500)}, // dominant 80 -> 280x500 + } + + for _, row := range testData { + match := rs.Match(row.requested) + if match != row.expected { + t.Errorf("Match(%dx%d) = %dx%d, expected %dx%d", + row.requested.Dx(), row.requested.Dy(), match.Dx(), match.Dy(), row.expected.Dx(), row.expected.Dy()) + } + } +} + +// TestMatchSquareResolutions verifies that square requests map onto the square +// resolutions (the boxes the web UI actually requests), and that the closest-fit +// rule picks the smallest resolution still at least as large as requested. +func TestMatchSquareResolutions(t *testing.T) { + rs, _ := ParseResolutions([]string{ + "16x16", "32x32", "64x64", "128x128", + "320x320", + "500x280", "280x500", "1000x560", "560x1000", + "1024x1024", + "512x2048", "1080x1920", "1920x1080", + "2160x3840", "3840x2160", "4320x7680", "7680x4320", + }) + + testData := []struct { + requested image.Rectangle + expected image.Rectangle + }{ + // square requests map onto square resolutions + {image.Rect(0, 0, 16, 16), image.Rect(0, 0, 16, 16)}, + {image.Rect(0, 0, 32, 32), image.Rect(0, 0, 32, 32)}, + {image.Rect(0, 0, 320, 320), image.Rect(0, 0, 320, 320)}, + {image.Rect(0, 0, 1024, 1024), image.Rect(0, 0, 1024, 1024)}, + // closest fit: smallest resolution still >= requested dominant dim + {image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 1920, 1080)}, + {image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 1080, 1920)}, + } + + for _, row := range testData { + match := rs.Match(row.requested) + if match != row.expected { + t.Errorf("Match(%dx%d) = %dx%d, expected %dx%d", + row.requested.Dx(), row.requested.Dy(), match.Dx(), match.Dy(), row.expected.Dx(), row.expected.Dy()) + } + } +} + +func TestParseWithEmptyString(t *testing.T) { + if _, err := ParseResolution(""); err == nil { + t.Error("Parse with empty string should return an error.") + } +} + +func TestParseWithInvalidWidth(t *testing.T) { + _, err := ParseResolution("invalidx42") + if err == nil { + t.Error("Parse with invalid width should return an error.") + } +} + +func TestParseWithInvalidHeight(t *testing.T) { + _, err := ParseResolution("42xinvalid") + if err == nil { + t.Error("Parse with invalid height should return an error.") + } +} + +func TestParseResolution(t *testing.T) { + rStr := "42x23" + r, _ := ParseResolution(rStr) + if r.Dx() != 42 || r.Dy() != 23 { + t.Errorf("Expected resolution %s got %s", rStr, r.String()) + } +} diff --git a/services/webdav/pkg/thumbnail/workflow/encode_imaging.go b/services/webdav/pkg/thumbnail/workflow/encode_imaging.go new file mode 100644 index 0000000000..267322c7de --- /dev/null +++ b/services/webdav/pkg/thumbnail/workflow/encode_imaging.go @@ -0,0 +1,43 @@ +//go:build !enable_vips + +package workflow + +import ( + "bytes" + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + + "github.com/opencloud-eu/opencloud/services/webdav/pkg/generator" +) + +// encodeForUpload turns the value produced by the preprocessing step into +// bytes for upload to the generator. Raw image bytes pass through unchanged; +// converted values are encoded in-process. +func encodeForUpload(v any, mimeType string) ([]byte, string, error) { + switch data := v.(type) { + case []byte: + return data, generator.GuessExtension(mimeType), nil + case image.Image: + var ( + buf bytes.Buffer + err error + ) + if mimeType == "image/jpeg" || mimeType == "image/jpg" { + err = jpeg.Encode(&buf, data, &jpeg.Options{Quality: 85}) + return buf.Bytes(), "jpg", err + } + err = png.Encode(&buf, data) + return buf.Bytes(), "png", err + case *gif.GIF: + var buf bytes.Buffer + if err := gif.EncodeAll(&buf, data); err != nil { + return nil, "", fmt.Errorf("encode gif: %w", err) + } + return buf.Bytes(), "gif", nil + default: + return nil, "", fmt.Errorf("unsupported converted type %T", v) + } +} diff --git a/services/webdav/pkg/thumbnail/workflow/encode_vips.go b/services/webdav/pkg/thumbnail/workflow/encode_vips.go new file mode 100644 index 0000000000..a830c9b902 --- /dev/null +++ b/services/webdav/pkg/thumbnail/workflow/encode_vips.go @@ -0,0 +1,50 @@ +//go:build enable_vips + +package workflow + +import ( + "fmt" + "image" + "image/gif" + + vips "github.com/davidbyttow/govips/v2/vips" + + "github.com/opencloud-eu/opencloud/services/webdav/pkg/generator" +) + +// encodeForUpload turns the value produced by the preprocessing step into +// bytes for upload to the generator. Raw image bytes pass through unchanged; +// converted values are encoded in-process via libvips. +func encodeForUpload(v any, mimeType string) ([]byte, string, error) { + switch data := v.(type) { + case []byte: + return data, generator.GuessExtension(mimeType), nil + case image.Image: + img, err := vips.NewImageFromGoImage(data) + if err != nil { + return nil, "", fmt.Errorf("create vips image: %w", err) + } + defer img.Close() + + if mimeType == "image/jpeg" || mimeType == "image/jpg" { + buf, _, err := img.ExportJpeg(&vips.JpegExportParams{Quality: 85}) + return buf, "jpg", err + } + buf, _, err := img.ExportPng(&vips.PngExportParams{}) + return buf, "png", err + case *gif.GIF: + if len(data.Image) == 0 { + return nil, "", fmt.Errorf("gif has no frames") + } + img, err := vips.NewImageFromGoImage(data.Image[0]) + if err != nil { + return nil, "", fmt.Errorf("create vips image: %w", err) + } + defer img.Close() + + buf, _, err := img.ExportGIF(&vips.GifExportParams{}) + return buf, "gif", err + default: + return nil, "", fmt.Errorf("unsupported converted type %T", v) + } +} diff --git a/services/webdav/pkg/thumbnail/workflow/suite_test.go b/services/webdav/pkg/thumbnail/workflow/suite_test.go new file mode 100644 index 0000000000..0ccb905acc --- /dev/null +++ b/services/webdav/pkg/thumbnail/workflow/suite_test.go @@ -0,0 +1,13 @@ +package workflow + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestWorkflow(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "ThumbnailWorkflow Suite") +} diff --git a/services/webdav/pkg/thumbnail/workflow/workflow.go b/services/webdav/pkg/thumbnail/workflow/workflow.go new file mode 100644 index 0000000000..5b104b22a1 --- /dev/null +++ b/services/webdav/pkg/thumbnail/workflow/workflow.go @@ -0,0 +1,776 @@ +package workflow + +import ( + "bytes" + "context" + "errors" + "fmt" + "image" + "io" + "mime" + "mime/multipart" + "net/http" + "path/filepath" + "strings" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + "github.com/opencloud-eu/reva/v2/pkg/utils" + grpcmetadata "google.golang.org/grpc/metadata" + + "github.com/opencloud-eu/reva/v2/pkg/storage/utils/templates" + + "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/dav/requests" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/generator" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/preprocessor" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail/cache" +) + +// Stater stats a file via the CS3 gateway. +type Stater interface { + Stat(ctx context.Context, ref *providerv1beta1.Reference, auth string) (*providerv1beta1.StatResponse, error) +} + +// FileDownloader downloads file bytes from storage. +type FileDownloader interface { + Download(ctx context.Context, ref *providerv1beta1.Reference, auth string) ([]byte, error) +} + +// SpaceLookup resolves a path-only reference (ResourceId == nil) into a full +// reference by looking up the storage space that owns the absolute path via the +// CS3 gateway's ListStorageSpaces. This mirrors reva's ocdav spacelookup and is +// used for path-only requests (user home paths, /webdav/..., public links), +// which carry an absolute path but no space root. +type SpaceLookup interface { + Resolve(ctx context.Context, ref *providerv1beta1.Reference, auth string) (*providerv1beta1.Reference, error) +} + +// ErrFileProcessing is returned when the file is still being processed by the +// storage backend (e.g. a virus scan or conversion in flight). Callers should +// surface this as HTTP 425 Too Early with a Retry-After header. +var ErrFileProcessing = fmt.Errorf("file is processing") + +// ErrImageTooLarge is returned when the input image exceeds the configured +// maximum width/height. Callers should surface this as HTTP 403 Forbidden, +// matching the legacy thumbnail service error message. +var ErrImageTooLarge = errors.New("thumbnails: image is too large") + +// ErrNotAFile is returned when the requested resource is not a regular file +// (e.g. a folder). Callers should surface this as HTTP 400 Bad Request with an +// "Unsupported file type" message, matching the legacy thumbnail service. +var ErrNotAFile = errors.New("thumbnails: resource is not a file") + +// ErrPermissionDenied is returned when the requesting user lacks the download +// permission on the resource (e.g. a Secure Viewer share). Callers should +// surface this as HTTP 403 Forbidden, matching the legacy thumbnail service. +var ErrPermissionDenied = errors.New("thumbnails: no download permission") + +// fileIsProcessing reports whether the resource carries the "processing" status +// in its opaque map, as set by the storage backend while a file is being handled. +func fileIsProcessing(info *providerv1beta1.ResourceInfo) bool { + return utils.ReadPlainFromOpaque(info.GetOpaque(), "status") == "processing" +} + +// processorID normalizes the requested processor to the identifier used in cache +// keys, matching the legacy behavior where an empty processor resolved to "thumbnail". +func processorID(processor string) string { + if processor == "" { + return "thumbnail" + } + return strings.ToLower(processor) +} + +// thumbnailStorageKey builds the cache key: checksum[:2]/checksum[2:4]/checksum[4:]/wxh-.ext. +// The segment (fit-in, fill, stretch, ...) distinguishes requests that +// share a size but produce different images, so they never share a cache entry. +// The requested resolution is used (not the matched one) so the key can be +// computed before downloading the source. Caches regenerate when the configured +// resolutions or operations change. +func thumbnailStorageKey(checksum string, w, h int, ext, processor string) string { + name := fmt.Sprintf("%dx%d-%s.%s", w, h, processor, ext) + if len(checksum) >= 6 { + return checksum[:2] + "/" + checksum[2:4] + "/" + checksum[4:] + "/" + name + } + return checksum + "/" + name +} + +// ThumbnailWorkflow owns the complete thumbnail generation pipeline: +// stat → validate → cache check → download → preprocess → generate → cache → respond. +type ThumbnailWorkflow struct { + generatorURL string + authHeader string + cache cache.ThumbnailCache + httpClient *http.Client + maxInputSize uint64 + resolutions *thumbnail.Resolutions + webdavNS string + fontMapFile string + log log.Logger + stater Stater + fileDownloader FileDownloader + spaceLookup SpaceLookup +} + +// NewWorkflow creates a ThumbnailWorkflow. generatorURL must be non-empty. +func NewWorkflow(opts ...Option) (*ThumbnailWorkflow, error) { + w := &ThumbnailWorkflow{ + httpClient: &http.Client{}, + log: log.NewLogger(log.Name("webdav")), + } + for _, opt := range opts { + opt(w) + } + + if w.generatorURL == "" { + return nil, fmt.Errorf("generator URL is required") + } + + if w.stater == nil { + return nil, fmt.Errorf("stater is required") + } + + if w.fileDownloader == nil { + return nil, fmt.Errorf("file downloader is required") + } + + return w, nil +} + +// Option configures a ThumbnailWorkflow. +type Option func(*ThumbnailWorkflow) + +// WithGeneratorURL sets the base URL of the thumbnail generator service. +func WithGeneratorURL(url string) Option { + return func(w *ThumbnailWorkflow) { w.generatorURL = url } +} + +// WithAuthHeader sets an optional auth header value sent to the generator (for external generators behind a reverse proxy). +func WithAuthHeader(header string) Option { + return func(w *ThumbnailWorkflow) { w.authHeader = header } +} + +// WithCache sets the thumbnail cache. +func WithCache(c cache.ThumbnailCache) Option { + return func(w *ThumbnailWorkflow) { w.cache = c } +} + +// WithHTTPClient overrides the default HTTP client. +func WithHTTPClient(client *http.Client) Option { + return func(w *ThumbnailWorkflow) { w.httpClient = client } +} + +// WithMaxInputSize sets the maximum input file size in bytes (0 = no limit). +func WithMaxInputSize(size uint64) Option { + return func(w *ThumbnailWorkflow) { w.maxInputSize = size } +} + +// WithResolutions sets the supported target resolutions used to snap a requested +// size onto a generator box. The generator is a dumb executor, so webdav owns the +// configured list and chooses the box; it only needs the list to pick the box. +func WithResolutions(rs *thumbnail.Resolutions) Option { + return func(w *ThumbnailWorkflow) { w.resolutions = rs } +} + +// WithWebdavNamespace sets the CS3 path layout for resolving user paths. +func WithWebdavNamespace(ns string) Option { + return func(w *ThumbnailWorkflow) { w.webdavNS = ns } +} + +// WithFontMapFile sets the font map file used for text thumbnail rendering. +func WithFontMapFile(file string) Option { + return func(w *ThumbnailWorkflow) { w.fontMapFile = file } +} + +// WithLogger sets the logger. +func WithLogger(l log.Logger) Option { + return func(w *ThumbnailWorkflow) { w.log = l } +} + +// WithStater sets the file stater. +func WithStater(s Stater) Option { + return func(w *ThumbnailWorkflow) { w.stater = s } +} + +// WithFileDownloader sets the file downloader. +func WithFileDownloader(d FileDownloader) Option { + return func(w *ThumbnailWorkflow) { w.fileDownloader = d } +} + +// WithSpaceLookup sets the resolver used to look up the storage space that owns +// a path-only reference. When unset, path-only references are passed through as-is. +func WithSpaceLookup(l SpaceLookup) Option { + return func(w *ThumbnailWorkflow) { w.spaceLookup = l } +} + +// Execute handles a user thumbnail request (GET). +func (w *ThumbnailWorkflow) Execute(ctx context.Context, tr *requests.ThumbnailRequest, auth string, logger log.Logger) (data []byte, ext string, aIgnored bool, err error) { + ref, err := w.resolveReference(ctx, tr, auth) + if err != nil { + return nil, "", false, err + } + + data, ext, aIgnored, err = w.generate(ctx, ref, auth, tr, logger) + return data, ext, aIgnored, err +} + +// ExecutePublic handles a public link thumbnail request (GET). +func (w *ThumbnailWorkflow) ExecutePublic(ctx context.Context, tr *requests.ThumbnailRequest, auth string, logger log.Logger) (data []byte, ext string, aIgnored bool, err error) { + ref, err := w.resolveReference(ctx, tr, auth) + if err != nil { + return nil, "", false, err + } + + data, ext, aIgnored, err = w.generate(ctx, ref, auth, tr, logger) + return data, ext, aIgnored, err +} + +// Head performs a cache-only check for a public link thumbnail (HEAD). +// Returns nil error if the file exists and is supported, without triggering generation. +func (w *ThumbnailWorkflow) Head(ctx context.Context, tr *requests.ThumbnailRequest, auth string, logger log.Logger) error { + ref, err := w.resolveReference(ctx, tr, auth) + if err != nil { + return err + } + + statRsp, err := w.stater.Stat(ctx, ref, auth) + if err != nil { + logger.Debug().Err(err).Msg("could not stat file") + return fmt.Errorf("stat: %w", err) + } + + info := statRsp.GetInfo() + + if fileIsProcessing(info) { + return ErrFileProcessing + } + + if !info.GetPermissionSet().GetInitiateFileDownload() { + return ErrPermissionDenied + } + + if !thumbnail.IsMimeTypeSupported(info.GetMimeType()) { + return fmt.Errorf("unsupported mime type: %s", info.GetMimeType()) + } + + if w.maxInputSize > 0 && info.GetSize() > w.maxInputSize { + logger.Debug().Uint64("size", info.GetSize()).Uint64("max", w.maxInputSize).Msg("file too large for thumbnail") + return ErrImageTooLarge + } + + return nil +} + +// resolveReference turns the request's reference into a fully-anchored CS3 +// reference, mirroring reva's ocdav split of space-ID-based requests from +// path-only requests: +// - If Ref carries a ResourceId (dav/spaces/{id}/...), it is used directly — +// the URL already identifies the space root. +// - Otherwise (path-only request) the absolute CS3 path is resolved to the +// user's home-relative path and, when a SpaceLookup is configured, the +// owning storage space is looked up so the reference is anchored at the +// space root. This also covers public links (/public//...). +func (w *ThumbnailWorkflow) resolveReference(ctx context.Context, tr *requests.ThumbnailRequest, auth string) (*providerv1beta1.Reference, error) { + ref := tr.Ref + if ref == nil { + return nil, fmt.Errorf("request carries no reference") + } + + if ref.GetResourceId() != nil { + // Space-ID based request: the URL carries the space root. + return ref, nil + } + + // Path-only request: absolutize the path relative to the requesting user's + // home (the legacy webdav behavior) before resolving the owning space. + absPath := w.absolutizeUserPath(ctx, ref.GetPath(), auth) + + if w.spaceLookup == nil { + return &providerv1beta1.Reference{Path: absPath}, nil + } + + resolved, err := w.spaceLookup.Resolve(ctx, &providerv1beta1.Reference{Path: absPath}, auth) + if err != nil { + return nil, fmt.Errorf("resolve space for path %q: %w", absPath, err) + } + return resolved, nil +} + +// absolutizeUserPath maps a path-only reference to an absolute CS3 path under the +// requesting user's home, matching the legacy webdav handler. Public link paths +// (already absolute under /public) and any path that is not relative to the +// caller are returned unchanged. +func (w *ThumbnailWorkflow) absolutizeUserPath(ctx context.Context, p string, auth string) string { + if strings.HasPrefix(p, "/") { + return p + } + + user, err := w.resolveUser(ctx, auth) + if err != nil { + // Without a resolved user we cannot absolutize; fall back to the raw path. + w.log.Warn().Err(err).Msg("could not resolve user for thumbnail path") + return p + } + return filepath.Join(templates.WithUser(user, w.webdavNS), p) +} + +func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.Reference, auth string, tr *requests.ThumbnailRequest, logger log.Logger) ([]byte, string, bool, error) { + statRsp, err := w.stater.Stat(ctx, ref, auth) + if err != nil { + logger.Debug().Err(err).Msg("could not stat file") + return nil, "", false, fmt.Errorf("stat: %w", err) + } + + info := statRsp.GetInfo() + + if fileIsProcessing(info) { + return nil, "", false, ErrFileProcessing + } + + if !info.GetPermissionSet().GetInitiateFileDownload() { + return nil, "", false, ErrPermissionDenied + } + + if !thumbnail.IsMimeTypeSupported(info.GetMimeType()) { + return nil, "", false, fmt.Errorf("unsupported mime type: %s", info.GetMimeType()) + } + + if w.maxInputSize > 0 && info.GetSize() > w.maxInputSize { + logger.Debug().Uint64("size", info.GetSize()).Uint64("max", w.maxInputSize).Msg("file too large for thumbnail") + return nil, "", false, ErrImageTooLarge + } + + checksum := info.GetChecksum().GetSum() + mimeType := info.GetMimeType() + + // The output type follows the source mime (like main's GetExtForMime); when + // the source has no dedicated image type (txt, audio) it falls back to the + // requested extension. + outputExt := generator.OutputFormat(tr.Extension) + if ext := generator.ExtForMime(mimeType); ext != "" { + outputExt = ext + } + + // The operation is resolved up front so it can be part of the cache key: a + // fill and a fit-in request with the same dimensions produce different images + // and must not share a cache entry. aIgnored reports whether the legacy "a" + // flag was overridden by an explicit processor, so the client can be told. + operation, aIgnored := w.matchOperation(tr, mimeType) + + // The cache key uses the requested resolution (like main's PrepareRequest), + // so it can be computed before downloading. Checking the cache first means a + // hit returns without touching storage, matching main's fast path. + cacheKey := thumbnailStorageKey(checksum, int(tr.Width), int(tr.Height), outputExt, processorID(operation)) + + if w.cache != nil { + if cached, err := w.cache.Get(cacheKey); err == nil { + return cached, outputExt, aIgnored, nil + } + } + + fileBytes, err := w.fileDownloader.Download(ctx, ref, auth) + if err != nil { + logger.Error().Err(err).Msg("could not download file for thumbnail") + return nil, "", false, fmt.Errorf("download: %w", err) + } + + // Direct image types are handed to the generator as-is; everything else is + // converted to an image first (audio cover art, geogebra, text, gif). + var toSend any = fileBytes + if !isDirectImageMime(mimeType) { + ppOpts := map[string]any{ + "fontFileMap": w.fontMapFile, + } + img, err := preprocessor.ForType(mimeType, ppOpts).Convert(bytes.NewReader(fileBytes)) + if img == nil || err != nil { + logger.Debug().Err(err).Msg("could not convert file to image") + return nil, "", false, fmt.Errorf("could not get image") + } + toSend = img + } + + // webdav is the sizing brain: it snaps the requested size onto a configured + // resolution (orientation-aware). It never inspects the image bytes; the + // generator is a dumb executor that just fits within the given box. + reqBox := image.Rect(0, 0, int(tr.Width), int(tr.Height)) + box := reqBox + if w.resolutions != nil { + box = w.resolutions.Match(reqBox) + } + genURL := generator.BuildURL(w.generatorURL, int32(box.Dx()), int32(box.Dy()), operation, outputExt) + thumbBytes, err := w.postToGenerator(ctx, genURL, toSend, mimeType) + if err != nil { + logger.Error().Err(err).Msg("could not generate thumbnail") + return nil, "", false, fmt.Errorf("generate: %w", err) + } + + if w.cache != nil { + if err := w.cache.Put(cacheKey, thumbBytes); err != nil { + logger.Debug().Err(err).Msg("could not cache thumbnail") + } + } + + return thumbBytes, outputExt, aIgnored, nil +} + +// matchOperation resolves the generator resize/crop operation for a request from +// the client's processor and the legacy "a" flag. An explicit processor always +// wins; when no processor is given the default depends on the source type (gifs +// are resized, everything else is filled). webdav has already snapped the box +// onto a configured resolution; this only chooses how to fit within that box: +// - resize -> stretch (distort to the exact box) +// - fill -> fill (center-crop to the box) +// - fit / fit-in -> fit-in (preserve aspect, fit in box, never upscale) +// - thumbnail -> fill (legacy alias; crops to the exact square box) +// - (none), gif -> stretch (resize for gifs) +// - (none), non-gif -> fill (default = thumbnail = fill) +// +// "thumbnail" and "fit" are legacy aliases. They map onto the imagor operations +// above but IGNORE the legacy "a" and "scalingup" query parameters — callers that +// need aspect/upscaling control should use the imagor names directly ("fill", +// "fit-in", "stretch"). The second return value reports whether the legacy "a" +// flag was ignored because an explicit processor overrode it with a different +// behavior, so the caller can tell the client via a response header. +func (w *ThumbnailWorkflow) matchOperation(tr *requests.ThumbnailRequest, mimeType string) (string, bool) { + proc := strings.ToLower(strings.TrimSpace(tr.Processor)) + + var operation string + switch proc { + case "resize": + operation = generator.OpStretch + case "fill", "thumbnail": + operation = generator.OpFill + case "fit", "fit-in": + operation = generator.OpFitIn + default: // no processor: default behavior depends on source type + if strings.HasPrefix(mimeType, "image/gif") { + operation = generator.OpStretch + } else { + operation = generator.OpFill + } + } + + // The legacy "a" flag is a hint (a=1/absent -> preserve aspect / fit-in, + // a=0 -> fill). An explicit processor always wins over it; report when the + // processor's behavior contradicts what "a" asked so the client can be told + // via a response header. stretch is neutral (the box is distorted to fit) so + // "a" has no meaning for it and never counts as ignored. + aIgnored := false + if proc != "" { + switch operation { + case generator.OpFill: + aIgnored = tr.Aspect // a asked for fit-in but we filled + case generator.OpFitIn: + aIgnored = !tr.Aspect // a asked for fill but we fit-in + } + } + + return operation, aIgnored +} + +func (w *ThumbnailWorkflow) postToGenerator(ctx context.Context, url string, file any, mimeType string) ([]byte, error) { + data, ext, err := encodeForUpload(file, mimeType) + if err != nil { + return nil, fmt.Errorf("encode for upload: %w", err) + } + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + part, err := writer.CreateFormFile("image", "file."+ext) + if err != nil { + return nil, fmt.Errorf("create form file: %w", err) + } + if _, err := part.Write(data); err != nil { + return nil, fmt.Errorf("write form data: %w", err) + } + + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close multipart writer: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) + if err != nil { + return nil, fmt.Errorf("create generator request: %w", err) + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + if w.authHeader != "" { + req.Header.Set(revactx.TokenHeader, w.authHeader) + } + + httpRsp, err := w.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("generator request: %w", err) + } + defer httpRsp.Body.Close() + + if httpRsp.StatusCode < 200 || httpRsp.StatusCode >= 300 { + respBody, _ := io.ReadAll(httpRsp.Body) + // The generator reports ErrMaxResolutionExceeded (422) when the decoded + // input exceeds its configured max width/height. Translate it to the + // legacy "image is too large" error so the DAV layer returns 403, matching + // the pre-refactor behavior where webdav enforced the dimension limit. + if httpRsp.StatusCode == http.StatusUnprocessableEntity { + return nil, ErrImageTooLarge + } + return nil, fmt.Errorf("generator returned status %d: %s", httpRsp.StatusCode, string(respBody)) + } + + rspData, err := io.ReadAll(httpRsp.Body) + if err != nil { + return nil, fmt.Errorf("read generator response: %w", err) + } + + return rspData, nil +} + +// directImageMimes are mime types the generator can decode directly; their +// bytes are passed through without preprocessing. +var directImageMimes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/tiff": {}, + "image/bmp": {}, + "image/x-ms-bmp": {}, + "image/webp": {}, +} + +func isDirectImageMime(mimeType string) bool { + m, _, _ := mime.ParseMediaType(mimeType) + _, ok := directImageMimes[m] + return ok +} + +// gatewaySelector is the interface for selecting a gateway client. +type gatewaySelector interface { + Next(...pool.Option) (gatewayv1beta1.GatewayAPIClient, error) +} + +func (w *ThumbnailWorkflow) resolveUser(ctx context.Context, auth string) (*userv1beta1.User, error) { + gs := w.stater.(*gatewayStater) + client, err := gs.selector.Next() + if err != nil { + return nil, fmt.Errorf("get gateway client: %w", err) + } + + userRes, err := client.WhoAmI(ctx, &gatewayv1beta1.WhoAmIRequest{Token: auth}) + if err != nil { + return nil, fmt.Errorf("whoami: %w", err) + } + if userRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + return nil, fmt.Errorf("whoami: %s", userRes.GetStatus().GetMessage()) + } + + return userRes.GetUser(), nil +} + +// gatewayStater implements Stater using the CS3 gateway. +type gatewayStater struct { + selector gatewaySelector +} + +func (g *gatewayStater) Stat(ctx context.Context, ref *providerv1beta1.Reference, auth string) (*providerv1beta1.StatResponse, error) { + client, err := g.selector.Next() + if err != nil { + return nil, fmt.Errorf("get gateway client: %w", err) + } + + ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, auth) + + rsp, err := client.Stat(ctx, &providerv1beta1.StatRequest{Ref: ref}) + if err != nil { + return nil, fmt.Errorf("stat request: %w", err) + } + + if rsp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + return nil, fmt.Errorf("stat failed: %s", rsp.GetStatus().GetMessage()) + } + + if rsp.GetInfo().GetType() != providerv1beta1.ResourceType_RESOURCE_TYPE_FILE { + return nil, ErrNotAFile + } + + return rsp, nil +} + +// gatewayFileDownloader implements FileDownloader using the CS3 gateway. +type gatewayFileDownloader struct { + selector gatewaySelector + httpClient *http.Client +} + +func (g *gatewayFileDownloader) Download(ctx context.Context, ref *providerv1beta1.Reference, auth string) ([]byte, error) { + client, err := g.selector.Next() + if err != nil { + return nil, fmt.Errorf("get gateway client: %w", err) + } + + ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, auth) + + rsp, err := client.InitiateFileDownload(ctx, &providerv1beta1.InitiateFileDownloadRequest{Ref: ref}) + if err != nil { + return nil, fmt.Errorf("initiate download: %w", err) + } + + ep, _ := extractProtocol(rsp.GetProtocols()) + if ep == "" { + return nil, fmt.Errorf("no suitable download protocol") + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ep, http.NoBody) + if err != nil { + return nil, fmt.Errorf("create download request: %w", err) + } + // Authenticate the data request with the user's token; the dataprovider + // (exposed or behind the proxy) validates it directly. + req.Header.Set(revactx.TokenHeader, auth) + + httpRsp, err := g.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("download request: %w", err) + } + defer httpRsp.Body.Close() + + if httpRsp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("download failed with status %d", httpRsp.StatusCode) + } + + data, err := io.ReadAll(httpRsp.Body) + if err != nil { + return nil, fmt.Errorf("read download body: %w", err) + } + + return data, nil +} + +func extractProtocol(protocols []*gatewayv1beta1.FileDownloadProtocol) (endpoint, token string) { + for _, p := range protocols { + if p.GetProtocol() == "spaces" || p.GetProtocol() == "simple" { + return p.GetDownloadEndpoint(), p.GetToken() + } + } + + for _, p := range protocols { + if p.GetDownloadEndpoint() != "" && p.GetToken() != "" { + return p.GetDownloadEndpoint(), p.GetToken() + } + } + return "", "" +} + +// NewGatewayStater creates a Stater backed by the CS3 gateway. +func NewGatewayStater(selector gatewaySelector) Stater { + return &gatewayStater{selector: selector} +} + +// NewGatewayFileDownloader creates a FileDownloader backed by the CS3 gateway. +func NewGatewayFileDownloader(selector gatewaySelector, httpClient *http.Client) FileDownloader { + return &gatewayFileDownloader{selector: selector, httpClient: httpClient} +} + +// gatewaySpaceLookup implements SpaceLookup using the CS3 gateway's +// ListStorageSpaces, mirroring reva's ocdav spacelookup.LookUpStorageSpaceForPath. +type gatewaySpaceLookup struct { + selector gatewaySelector +} + +func (g *gatewaySpaceLookup) Resolve(ctx context.Context, ref *providerv1beta1.Reference, auth string) (*providerv1beta1.Reference, error) { + client, err := g.selector.Next() + if err != nil { + return nil, fmt.Errorf("get gateway client: %w", err) + } + + ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, auth) + + lSSReq := &providerv1beta1.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": {Decoder: "plain", Value: []byte(ref.GetPath())}, + "unique": {Decoder: "plain", Value: []byte("true")}, + }, + }, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil { + return nil, fmt.Errorf("list storage spaces: %w", err) + } + if lSSRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + return nil, fmt.Errorf("list storage spaces: %s", lSSRes.GetStatus().GetMessage()) + } + + switch len(lSSRes.GetStorageSpaces()) { + case 0: + return nil, fmt.Errorf("no storage space found for path %q", ref.GetPath()) + case 1: + space := lSSRes.GetStorageSpaces()[0] + if space.GetRoot() == nil { + return nil, fmt.Errorf("storage space for path %q has no root", ref.GetPath()) + } + return &providerv1beta1.Reference{ + ResourceId: space.GetRoot(), + Path: utils.MakeRelativePath(strings.TrimPrefix(ref.GetPath(), g.spaceMountPath(space))), + }, nil + default: + return nil, fmt.Errorf("too many storage spaces returned for path %q", ref.GetPath()) + } +} + +// spaceMountPath extracts the mount path recorded in a storage space's opaque map, +// used to trim the absolute path down to a space-relative one. It returns "" when +// the space is not mounted at a known path (in which case the full path is kept). +func (g *gatewaySpaceLookup) spaceMountPath(space *providerv1beta1.StorageSpace) string { + if space.GetOpaque() == nil || space.GetOpaque().GetMap()["path"] == nil { + return "" + } + return string(space.GetOpaque().GetMap()["path"].GetValue()) +} + +// NewGatewaySpaceLookup creates a SpaceLookup backed by the CS3 gateway. +func NewGatewaySpaceLookup(selector gatewaySelector) SpaceLookup { + return &gatewaySpaceLookup{selector: selector} +} + +// ResolvePublicLinkAuth authenticates a public link token via the gateway. +func ResolvePublicLinkAuth(ctx context.Context, r *http.Request, publicLinkToken string, selector gatewaySelector) (string, error) { + gatewayClient, err := selector.Next() + if err != nil { + return "", fmt.Errorf("could not get gateway client: %w", err) + } + + q := r.URL.Query() + var rsp *gatewayv1beta1.AuthenticateResponse + + if q.Get("signature") != "" && q.Get("expiration") != "" { + sig := q.Get("signature") + exp := q.Get("expiration") + rsp, err = gatewayClient.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{ + Type: "publicshares", + ClientId: publicLinkToken, + ClientSecret: strings.Join([]string{"signature", sig, exp}, "|"), + }) + } else { + rsp, err = gatewayClient.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{ + Type: "publicshares", + ClientId: publicLinkToken, + ClientSecret: "password|", + }) + } + + if err != nil { + return "", fmt.Errorf("could not authenticate public link: %w", err) + } + + if rsp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + return "", fmt.Errorf("public link authentication failed: code=%s message=%s", rsp.GetStatus().GetCode(), rsp.GetStatus().GetMessage()) + } + + return rsp.GetToken(), nil +} diff --git a/services/webdav/pkg/thumbnail/workflow/workflow_test.go b/services/webdav/pkg/thumbnail/workflow/workflow_test.go new file mode 100644 index 0000000000..cc37926c5c --- /dev/null +++ b/services/webdav/pkg/thumbnail/workflow/workflow_test.go @@ -0,0 +1,1414 @@ +package workflow + +import ( + "bytes" + "context" + "errors" + "image" + "image/jpeg" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/opencloud-eu/reva/v2/pkg/storagespace" + "github.com/opencloud-eu/reva/v2/pkg/utils" + "github.com/go-chi/chi/v5" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/status" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" + + "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/constants" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/dav/requests" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/generator" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail/cache" +) + +// testJPEG returns a valid JPEG of the given dimensions. +func testJPEG(w, h int) []byte { + var buf bytes.Buffer + img := image.NewRGBA(image.Rect(0, 0, w, h)) + _ = jpeg.Encode(&buf, img, nil) + return buf.Bytes() +} + +// spaceRef builds a space-ID based reference (ResourceId + relative path), the +// shape ParseThumbnailRequest produces for dav/spaces/{id}/... requests. +func spaceRef(spaceID, relPath string) *providerv1beta1.Reference { + rid, _ := storagespace.ParseID(spaceID) + return &providerv1beta1.Reference{ + ResourceId: &rid, + Path: utils.MakeRelativePath(relPath), + } +} + +var _ = Describe("processorID", func() { + It("maps the default (empty) processor to the thumbnail id", func() { + Expect(processorID("")).To(Equal("thumbnail")) + }) + + It("maps an explicit thumbnail processor to the same id as the default", func() { + Expect(processorID("thumbnail")).To(Equal("thumbnail")) + }) + + It("gives opt-in processors their own id so they cache separately", func() { + Expect(processorID("fit")).To(Equal("fit")) + Expect(processorID("fill")).To(Equal("fill")) + }) +}) + +var _ = Describe("matchOperation", func() { + w := &ThumbnailWorkflow{} + + It("stretches for the resize processor", func() { + op, aIgnored := w.matchOperation(&requests.ThumbnailRequest{Processor: "resize"}, "image/jpeg") + Expect(op).To(Equal(generator.OpStretch)) + Expect(aIgnored).To(BeFalse()) + }) + + It("fills for the fill processor", func() { + op, _ := w.matchOperation(&requests.ThumbnailRequest{Processor: "fill"}, "image/jpeg") + Expect(op).To(Equal(generator.OpFill)) + }) + + It("treats the thumbnail processor as fill (square tiles)", func() { + op, _ := w.matchOperation(&requests.ThumbnailRequest{Processor: "thumbnail"}, "image/jpeg") + Expect(op).To(Equal(generator.OpFill)) + }) + + It("fits in for the fit processor", func() { + op, _ := w.matchOperation(&requests.ThumbnailRequest{Processor: "fit"}, "image/jpeg") + Expect(op).To(Equal(generator.OpFitIn)) + }) + + It("defaults to fill for non-gif sources without a processor", func() { + op, aIgnored := w.matchOperation(&requests.ThumbnailRequest{}, "image/jpeg") + Expect(op).To(Equal(generator.OpFill)) + Expect(aIgnored).To(BeFalse()) + }) + + It("stretches gifs by default (resize for gifs)", func() { + op, _ := w.matchOperation(&requests.ThumbnailRequest{}, "image/gif") + Expect(op).To(Equal(generator.OpStretch)) + }) + + It("reports aIgnored when an explicit processor overrides the legacy a flag", func() { + // a=0 (Aspect=false) wants fill; fit processor overrides it with fit-in. + op, aIgnored := w.matchOperation(&requests.ThumbnailRequest{Processor: "fit"}, "image/jpeg") + Expect(op).To(Equal(generator.OpFitIn)) + Expect(aIgnored).To(BeTrue()) + + // a=1 (Aspect=true) wants fit-in; fill processor overrides it with fill. + op, aIgnored = w.matchOperation(&requests.ThumbnailRequest{Processor: "fill", Aspect: true}, "image/jpeg") + Expect(op).To(Equal(generator.OpFill)) + Expect(aIgnored).To(BeTrue()) + + // a=0 wants fill; thumbnail processor implies fill -> no contradiction. + op, aIgnored = w.matchOperation(&requests.ThumbnailRequest{Processor: "thumbnail", Aspect: false}, "image/jpeg") + Expect(op).To(Equal(generator.OpFill)) + Expect(aIgnored).To(BeFalse()) + + // resize is neutral: never reports a contradiction. + _, aIgnored = w.matchOperation(&requests.ThumbnailRequest{Processor: "resize", Aspect: false}, "image/jpeg") + Expect(aIgnored).To(BeFalse()) + }) +}) + +func newTestRequest(path string, token string, filePath string) *http.Request { + req := httptest.NewRequest("GET", path, nil) + req.Header.Set(revactx.TokenHeader, token) + + ctx := req.Context() + ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) + ctx = context.WithValue(ctx, constants.ContextKeyID, "storageid$spaceid!opaqueid") + req = req.WithContext(ctx) + + return req +} + +func newPublicLinkRequest(url string, token string) *http.Request { + req := httptest.NewRequest(http.MethodGet, url, nil) + + ctx := req.Context() + filePath := strings.TrimPrefix(url, "/dav/public-files/"+token+"/") + if idx := strings.Index(filePath, "?"); idx >= 0 { + filePath = filePath[:idx] + } + ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) + + rctx := chi.NewRouteContext() + rctx.URLParams.Keys = append(rctx.URLParams.Keys, "token") + rctx.URLParams.Values = append(rctx.URLParams.Values, token) + ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) + + req = req.WithContext(ctx) + + return req +} + +// setupPreprocessTest wires the gateway mock for a file with the given mime +// type and returns a workflow, generator server and a capture of what the +// generator received. +func setupPreprocessTest( + t GinkgoTInterface, + gatewayClient *cs3mocks.GatewayAPIClient, + logger log.Logger, + testCache cache.ThumbnailCache, + mimeType string, + filePath string, + downloadBody []byte, +) (*ThumbnailWorkflow, *capturedUpload) { + wf, err := NewWorkflow( + WithGeneratorURL("http://generator-test"), + WithCache(testCache), + WithHTTPClient(&http.Client{}), + WithWebdavNamespace("/users/{{.Id.OpaqueId}}"), + WithLogger(logger), + WithStater(NewGatewayStater(pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })), + ), + WithFileDownloader(NewGatewayFileDownloader(pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient }), &http.Client{})), + WithSpaceLookup(NewGatewaySpaceLookup(pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient }))), + ) + Expect(err).ToNot(HaveOccurred()) + + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&providerv1beta1.ListStorageSpacesResponse{ + Status: status.NewOK(context.Background()), + StorageSpaces: []*providerv1beta1.StorageSpace{ + { + Id: &providerv1beta1.StorageSpaceId{OpaqueId: "storageid$spaceid"}, + SpaceType: "project", + Root: &providerv1beta1.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}, + }, + }, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, filePath) + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: mimeType, + Size: uint64(len(downloadBody)), + Checksum: &providerv1beta1.ResourceChecksum{Sum: "ppchecksum"}, + }, + }, nil) + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + t.Cleanup(storageServer.Close) + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + captured := &capturedUpload{} + generatorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + captured.path = r.URL.Path + if err := r.ParseMultipartForm(10 << 20); err == nil { + for _, fh := range r.MultipartForm.File["image"] { + captured.filename = filepath.Base(fh.Filename) + file, ferr := fh.Open() + if ferr != nil { + continue + } + captured.data, _ = io.ReadAll(file) + file.Close() + } + } + w.WriteHeader(http.StatusOK) + w.Write([]byte("thumbnail-output")) + })) + t.Cleanup(generatorSrv.Close) + + wf.generatorURL = generatorSrv.URL + return wf, captured +} + +type capturedUpload struct { + path string + filename string + data []byte +} + +var _ = Describe("ThumbnailWorkflow", func() { + var ( + wf *ThumbnailWorkflow + gatewayClient *cs3mocks.GatewayAPIClient + generatorSrv *httptest.Server + logger log.Logger + testToken string + testCache cache.ThumbnailCache + ) + + const publicLinkToken = "abc123public" + + BeforeEach(func() { + pool.RemoveSelector("GatewaySelector" + "test.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector := pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", + "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { + return gatewayClient + }, + ) + + logger = log.NewLogger() + testToken = "test-reva-token" + testCache = cache.NewInMemoryCache() + + var err error + wf, err = NewWorkflow( + WithGeneratorURL("http://generator-test"), + WithCache(testCache), + WithHTTPClient(&http.Client{}), + WithWebdavNamespace("/users/{{.Id.OpaqueId}}"), + WithLogger(logger), + WithStater(NewGatewayStater(gatewaySelector)), + WithFileDownloader(NewGatewayFileDownloader(gatewaySelector, &http.Client{})), + WithSpaceLookup(NewGatewaySpaceLookup(gatewaySelector)), + ) + Expect(err).ToNot(HaveOccurred()) + + // The space lookup resolves path-only references to a space root. Public + // link paths (/public//...) resolve to the public storage provider; + // user home paths resolve to the users storage provider. + gatewayClient.On("WhoAmI", mock.Anything, mock.Anything).Maybe().Return(&gatewayv1beta1.WhoAmIResponse{ + Status: status.NewOK(context.Background()), + User: &userv1beta1.User{ + Id: &userv1beta1.UserId{Idp: "https://opencloud-server:9200", OpaqueId: "test-opaque", Type: userv1beta1.UserType_USER_TYPE_PRIMARY}, + Username: "test", + }, + }, nil) + + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.ListStorageSpacesRequest) bool { + return req.GetOpaque() != nil && req.GetOpaque().GetMap()["path"] != nil + })).Maybe().Return(&providerv1beta1.ListStorageSpacesResponse{ + Status: status.NewOK(context.Background()), + StorageSpaces: []*providerv1beta1.StorageSpace{ + {Id: &providerv1beta1.StorageSpaceId{OpaqueId: "users-space"}, SpaceType: "project", Root: &providerv1beta1.ResourceId{StorageId: "storage-users", SpaceId: "users-space", OpaqueId: "users-opaque"}}, + }, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "test.jpg") || strings.Contains(req.Ref.Path, "test.png") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{ + // SHA-1 of "test.jpg", used as a realistic content checksum so the on-disk cache shard looks like a normal hash. + Sum: "cb56752477cae6405f85b131872c60d21b967c6a", + Type: providerv1beta1.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1, + }, + }, + }, nil) + }) + + AfterEach(func() { + if generatorSrv != nil { + generatorSrv.Close() + } + pool.RemoveSelector("GatewaySelector" + "test.gateway") + }) + + Describe("full miss path", func() { + It("should download file, call generator, cache result, and return thumbnail", func() { + downloadBody := testJPEG(800, 600) + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Expect(r.Header.Get(revactx.TokenHeader)).To(Equal(testToken)) + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + { + Protocol: "spaces", + DownloadEndpoint: storageServer.URL, + Token: "download-token", + }, + }, + }, nil) + + thumbBytes := []byte("thumbnail-output") + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Expect(r.Method).To(Equal(http.MethodPost)) + Expect(r.Header.Get("Content-Type")).To(ContainSubstring("multipart/form-data")) + + body, _ := io.ReadAll(r.Body) + Expect(body).To(ContainElement(downloadBody[0])) + + w.WriteHeader(http.StatusOK) + w.Write(thumbBytes) + })) + defer generatorSrv.Close() + + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "test.jpg"), + Filename: "test.jpg", + Extension: ".jpg", + Width: 128, + Height: 128, + Aspect: true, + } + + data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal(thumbBytes)) + Expect(ext).To(Equal("jpg")) + + cached, err := testCache.Get("cb/56/752477cae6405f85b131872c60d21b967c6a/128x128-fill.jpg") + Expect(err).ToNot(HaveOccurred()) + Expect(cached).To(Equal(thumbBytes)) + }) + }) + + Describe("space-scoped reference", func() { + It("anchors stat and download at the space ResourceId, not a path mount", func() { + const ( + spaceID = "f7a965c2-17ad-476c-893c-f8aa2fbfec3a" + storage = "storage-users-1" + ) + ssid := storage + "$" + spaceID + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + rid := req.GetRef().GetResourceId() + return rid != nil && + rid.StorageId == storage && + rid.SpaceId == spaceID && + req.GetRef().Path == "./photo.jpeg" + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "spacechecksum"}, + }, + }, nil) + + downloadBody := testJPEG(800, 600) + var downloadRef *providerv1beta1.Reference + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.InitiateFileDownloadRequest) bool { + downloadRef = req.GetRef() + rid := downloadRef.GetResourceId() + return rid != nil && rid.StorageId == storage && rid.SpaceId == spaceID + })).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("space-thumbnail")) + })) + defer generatorSrv.Close() + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef(ssid, "photo.jpeg"), + Filename: "photo.jpeg", + Extension: ".jpeg", + Width: 36, + Height: 36, + } + + data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(ext).To(Equal("jpg")) + Expect(data).To(Equal([]byte("space-thumbnail"))) + + Expect(downloadRef.GetResourceId().StorageId).To(Equal(storage)) + Expect(downloadRef.GetResourceId().SpaceId).To(Equal(spaceID)) + }) + + It("uses the shares space ResourceId directly for a shared file (no space lookup)", func() { + // Regression: dav/spaces/{shares-space-id}/... requests must anchor stat + // and download at the shares storage provider's ResourceId, not fall + // back to a path-based space lookup that cannot resolve the share. + const ( + sharesStorage = "a0ca6a90-a365-4782-871e-d44447bbc668" + sharesSpace = "a0ca6a90-a365-4782-871e-d44447bbc668" + shareID = "some-share-opaque-id" + ) + ssid := sharesStorage + "$" + sharesSpace + "!" + shareID + + var statRef *providerv1beta1.Reference + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + statRef = req.GetRef() + rid := req.GetRef().GetResourceId() + return rid != nil && + rid.StorageId == sharesStorage && + rid.SpaceId == sharesSpace && + rid.OpaqueId == shareID && + req.GetRef().Path == "./lorem.txt" + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "sharechecksum"}, + }, + }, nil) + + downloadBody := testJPEG(800, 600) + var downloadRef *providerv1beta1.Reference + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.InitiateFileDownloadRequest) bool { + downloadRef = req.GetRef() + rid := downloadRef.GetResourceId() + return rid != nil && + rid.StorageId == sharesStorage && + rid.SpaceId == sharesSpace && + rid.OpaqueId == shareID + })).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("share-thumbnail")) + })) + defer generatorSrv.Close() + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef(ssid, "lorem.txt"), + Filename: "lorem.txt", + Extension: ".txt", + Width: 36, + Height: 36, + } + + data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(ext).To(Equal("jpg")) + Expect(data).To(Equal([]byte("share-thumbnail"))) + + // The shares space ResourceId is used verbatim; no lookup was needed. + Expect(statRef.GetResourceId().StorageId).To(Equal(sharesStorage)) + Expect(statRef.GetResourceId().SpaceId).To(Equal(sharesSpace)) + Expect(statRef.GetResourceId().OpaqueId).To(Equal(shareID)) + Expect(downloadRef.GetResourceId().StorageId).To(Equal(sharesStorage)) + Expect(downloadRef.GetResourceId().SpaceId).To(Equal(sharesSpace)) + Expect(downloadRef.GetResourceId().OpaqueId).To(Equal(shareID)) + }) + }) + + Describe("username-scoped reference", func() { + It("resolves a username identifier to the user's home path via GetUserByClaim", func() { + var statRef *providerv1beta1.Reference + + gatewayClient.On("GetUserByClaim", mock.Anything, mock.MatchedBy(func(req *userv1beta1.GetUserByClaimRequest) bool { + return req.GetClaim() == "username" && req.GetValue() == "alice" + })).Return(&userv1beta1.GetUserByClaimResponse{ + Status: status.NewOK(context.Background()), + User: &userv1beta1.User{ + Id: &userv1beta1.UserId{ + Idp: "https://opencloud-server:9200", + OpaqueId: "alice-opaque", + Type: userv1beta1.UserType_USER_TYPE_PRIMARY, + }, + Username: "alice", + }, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + statRef = req.GetRef() + return strings.Contains(req.GetRef().GetPath(), "photo.jpeg") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "userchecksum"}, + }, + }, nil) + + downloadBody := testJPEG(800, 600) + var downloadRef *providerv1beta1.Reference + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.InitiateFileDownloadRequest) bool { + downloadRef = req.GetRef() + return strings.Contains(req.GetRef().GetPath(), "photo.jpeg") + })).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("user-thumbnail")) + })) + defer generatorSrv.Close() + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "photo.jpeg"}, + Filename: "photo.jpeg", + Extension: ".jpeg", + Width: 36, + Height: 36, + } + + data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(ext).To(Equal("jpg")) + Expect(data).To(Equal([]byte("user-thumbnail"))) + + // The path-only reference is resolved to a space-anchored reference by + // the space lookup (the users storage provider root from the mock). + Expect(statRef.GetResourceId()).ToNot(BeNil()) + Expect(statRef.GetPath()).To(Equal("./users/test-opaque/photo.jpeg")) + Expect(downloadRef.GetResourceId()).ToNot(BeNil()) + Expect(downloadRef.GetPath()).To(Equal("./users/test-opaque/photo.jpeg")) + }) + }) + + Describe("preprocessing", func() { + It("passes direct image mimes through to the generator unchanged", func() { + pngBytes, err := os.ReadFile(filepath.Join("..", "..", "preprocessor", "test_assets", "noise.png")) + Expect(err).ToNot(HaveOccurred()) + + wf2, captured := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "image/png", "img.png", pngBytes) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "img.png"), + Filename: "img.png", + Extension: ".png", + Width: 64, + Height: 64, + } + + _, _, _, err = wf2.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(captured.data).To(Equal(pngBytes)) + }) + + It("converts audio to cover art before posting", func() { + mp3Bytes, err := os.ReadFile(filepath.Join("..", "..", "preprocessor", "test_assets", "empty.mp3")) + Expect(err).ToNot(HaveOccurred()) + + wf2, captured := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "audio/mpeg", "song.mp3", mp3Bytes) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "song.mp3"), + Filename: "song.mp3", + Extension: ".mp3", + Width: 64, + Height: 64, + } + + _, _, _, err = wf2.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(captured.data).ToNot(BeEmpty()) + Expect(captured.data).ToNot(Equal(mp3Bytes)) + }) + + It("extracts the embedded thumbnail from geogebra slides", func() { + ggsBytes, err := os.ReadFile(filepath.Join("..", "..", "preprocessor", "test_assets", "ggs_test.ggs")) + Expect(err).ToNot(HaveOccurred()) + + wf2, captured := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "application/vnd.geogebra.slides", "deck.ggs", ggsBytes) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "deck.ggs"), + Filename: "deck.ggs", + Extension: ".ggs", + Width: 64, + Height: 64, + } + + _, _, _, err = wf2.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(captured.data).ToNot(BeEmpty()) + Expect(captured.data).ToNot(Equal(ggsBytes)) + }) + + It("renders text files to an image", func() { + text := []byte("This is a test text for thumbnail rendering") + + wf2, captured := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "text/plain", "notes.txt", text) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "notes.txt"), + Filename: "notes.txt", + Extension: ".txt", + Width: 64, + Height: 64, + } + + _, _, _, err := wf2.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(captured.data).ToNot(BeEmpty()) + Expect(captured.data).ToNot(Equal(text)) + }) + + It("re-encodes gif files to gif bytes before posting", func() { + gifBytes, err := os.ReadFile(filepath.Join("..", "..", "preprocessor", "test_assets", "noise.gif")) + Expect(err).ToNot(HaveOccurred()) + + wf2, captured := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "image/gif", "anim.gif", gifBytes) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "anim.gif"), + Filename: "anim.gif", + Extension: ".gif", + Width: 64, + Height: 64, + } + + _, _, _, err = wf2.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(captured.data).ToNot(BeEmpty()) + // The generator must receive valid gif bytes (GIF87a/GIF89a magic). + Expect(string(captured.data[:6])).To(SatisfyAny( + Equal("GIF89a"), + Equal("GIF87a"), + )) + }) + + It("returns an error when the file cannot be converted to an image", func() { + wf2, _ := setupPreprocessTest(GinkgoT(), gatewayClient, logger, testCache, "audio/mpeg", "bad.mp3", []byte("not a real audio file")) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "bad.mp3"), + Filename: "bad.mp3", + Extension: ".mp3", + Width: 64, + Height: 64, + } + + _, _, _, err := wf2.Execute(context.Background(), tr, testToken, logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("could not get image")) + }) + }) + + Describe("cache hit shortcut", func() { + It("should return cached thumbnail without calling generator or downloading the file", func() { + cachedThumb := []byte("cached-thumbnail") + err := testCache.Put("cb/56/752477cae6405f85b131872c60d21b967c6a/128x128-fill.jpg", cachedThumb) + Expect(err).ToNot(HaveOccurred()) + + // No download expectations are set up. If the workflow tried to + // download the source on a cache hit, the gateway mock would fail. + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "test.jpg"), + Filename: "test.jpg", + Extension: ".jpg", + Width: 128, + Height: 128, + Aspect: true, + } + + data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal(cachedThumb)) + Expect(ext).To(Equal("jpg")) + }) + }) + + Describe("mime type rejection", func() { + It("should return error for unsupported mime types", func() { + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "document.pdf") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "application/pdf", + Size: 2048, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "pdfchecksum"}, + }, + }, nil) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "document.pdf"), + Filename: "document.pdf", + Extension: ".pdf", + Width: 64, + Height: 64, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unsupported mime type")) + }) + + It("should return ErrNotAFile when the resource is a folder", func() { + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "myfolder") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_CONTAINER, + MimeType: "application/zip", + Size: 0, + }, + }, nil) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "myfolder/"), + Filename: "myfolder", + Extension: "", + Width: 64, + Height: 64, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).To(HaveOccurred()) + Expect(errors.Is(err, ErrNotAFile)).To(BeTrue()) + }) + }) + + Describe("file size rejection", func() { + It("should return error for files exceeding max input size", func() { + wfWithLimit, err := NewWorkflow( + WithGeneratorURL("http://generator-test"), + WithCache(testCache), + WithHTTPClient(&http.Client{}), + WithMaxInputSize(1024), + WithWebdavNamespace("/users/{{.Id.OpaqueId}}"), + WithLogger(logger), + WithStater(NewGatewayStater(pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })), + ), + WithFileDownloader(NewGatewayFileDownloader(pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient }), &http.Client{})), + WithSpaceLookup(NewGatewaySpaceLookup(pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient }))), + ) + Expect(err).ToNot(HaveOccurred()) + + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&providerv1beta1.ListStorageSpacesResponse{ + Status: status.NewOK(context.Background()), + StorageSpaces: []*providerv1beta1.StorageSpace{ + {Id: &providerv1beta1.StorageSpaceId{OpaqueId: "spaceid"}, SpaceType: "project", Root: &providerv1beta1.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}}, + }, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "large.jpg") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 2048, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "largechecksum"}, + }, + }, nil) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "large.jpg"), + Filename: "large.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + _, _, _, err = wfWithLimit.Execute(context.Background(), tr, testToken, logger) + Expect(errors.Is(err, ErrImageTooLarge)).To(BeTrue()) + }) + }) + + Describe("resolution passthrough", func() { + It("should forward the requested dimensions to the generator unchanged", func() { + thumbBytes := []byte("matched-thumbnail") + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(testJPEG(800, 600)) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + var receivedURL string + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedURL = r.URL.Path + w.WriteHeader(http.StatusOK) + w.Write(thumbBytes) + })) + defer generatorSrv.Close() + + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "test.jpg"), + Filename: "test.jpg", + Extension: ".jpg", + Width: 100, + Height: 100, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(receivedURL).To(ContainSubstring("100x100")) + }) + + It("passes the requested resolution to the generator even when the source is smaller", func() { + // webdav no longer inspects the image to resolve a "matched" size; it + // forwards the requested resolution and the generator owns the + // no-upscale behavior for small sources. + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(testJPEG(32, 32)) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + var receivedURL string + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedURL = r.URL.Path + w.WriteHeader(http.StatusOK) + w.Write([]byte("matched-thumbnail")) + })) + defer generatorSrv.Close() + + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "test.jpg"), + Filename: "test.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(receivedURL).To(ContainSubstring("64x64")) + }) + }) + + Describe("public link thumbnail support", func() { + It("should authenticate via publicshares and return thumbnail", func() { + publicLinkAuth := "public-link-token-123" + + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: publicLinkAuth, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "/public/"+publicLinkToken) + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{ + // Fixed realistic SHA-1-style content checksum so the on-disk cache shard looks like a normal hash. + Sum: "5ba1b086d95a7e0fd8ff4323caee0c412ab07fc3", + Type: providerv1beta1.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1, + }, + }, + }, nil) + + downloadBody := testJPEG(800, 600) + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.InitiateFileDownloadRequest) bool { + return strings.Contains(req.Ref.Path, "/public/"+publicLinkToken) + })).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + thumbBytes := []byte("public-thumbnail-output") + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(thumbBytes) + })) + defer generatorSrv.Close() + + wf.generatorURL = generatorSrv.URL + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/folder/photo.jpg?x=64&y=64", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/folder/photo.jpg"}, + Filename: "photo.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + Aspect: true, + } + + data, ext, _, err := wf.ExecutePublic(req.Context(), tr, auth, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal(thumbBytes)) + Expect(ext).To(Equal("jpg")) + + cached, err := testCache.Get("5b/a1/b086d95a7e0fd8ff4323caee0c412ab07fc3/64x64-fill.jpg") + Expect(err).ToNot(HaveOccurred()) + Expect(cached).To(Equal(thumbBytes)) + }) + + It("should handle pre-signed public links with signature and expiration", func() { + publicLinkAuth := "presigned-link-token" + + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && + req.ClientId == publicLinkToken && + req.ClientSecret == "signature|abc123sig|exp456exp" + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: publicLinkAuth, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "/public/"+publicLinkToken) + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{ + Sum: "5ba1b086d95a7e0fd8ff4323caee0c412ab07fc3", + Type: providerv1beta1.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1, + }, + }, + }, nil) + + downloadBody := testJPEG(800, 600) + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + thumbBytes := []byte("presigned-thumbnail") + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(thumbBytes) + })) + defer generatorSrv.Close() + + wf.generatorURL = generatorSrv.URL + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/photo.jpg?x=64&y=64&signature=abc123sig&expiration=exp456exp", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/photo.jpg"}, + Filename: "photo.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + data, _, _, err := wf.ExecutePublic(req.Context(), tr, auth, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal(thumbBytes)) + }) + + It("should reject password-protected public link when auth fails", func() { + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewPermissionDenied(context.Background(), nil, "password required"), + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/protected.jpg?x=64&y=64", publicLinkToken) + _, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("password required")) + }) + + It("should reject expired token when auth fails", func() { + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewFailedPrecondition(context.Background(), nil, "token expired"), + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/expired.jpg?x=64&y=64", publicLinkToken) + _, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("expired")) + }) + + It("should return nil error on Head when file is accessible", func() { + publicLinkAuth := "public-link-token-head" + + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: publicLinkAuth, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "/public/"+publicLinkToken) + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{ + Sum: "publicchecksum", + Type: providerv1beta1.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1, + }, + }, + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/folder/photo.jpg?x=64&y=64", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/folder/photo.jpg"}, + Filename: "photo.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + err = wf.Head(req.Context(), tr, auth, logger) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should return error on Head when file does not exist", func() { + publicLinkAuth := "public-link-token-head" + + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: publicLinkAuth, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "nonexistent.jpg") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewNotFound(context.Background(), "file not found"), + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/nonexistent.jpg?x=64&y=64", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/nonexistent.jpg"}, + Filename: "nonexistent.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + err = wf.Head(req.Context(), tr, auth, logger) + Expect(err).To(HaveOccurred()) + }) + + It("should return error on Head when mime type is not supported", func() { + publicLinkAuth := "public-link-token-head" + + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: publicLinkAuth, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "document.pdf") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "application/pdf", + Size: 2048, + }, + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/document.pdf?x=64&y=64", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/document.pdf"}, + Filename: "document.pdf", + Extension: ".pdf", + Width: 64, + Height: 64, + } + + err = wf.Head(req.Context(), tr, auth, logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unsupported mime type")) + }) + }) + + Describe("permission check", func() { + It("returns ErrPermissionDenied when the resource lacks download permission", func() { + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "restricted.jpg") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "restrictedchecksum"}, + }, + }, nil) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "restricted.jpg"), + Filename: "restricted.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(errors.Is(err, ErrPermissionDenied)).To(BeTrue()) + }) + + It("returns ErrPermissionDenied on Head when the resource lacks download permission", func() { + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: "public-link-token-head", + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "/public/"+publicLinkToken) + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "restrictedchecksum"}, + }, + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/folder/restricted.jpg?x=64&y=64", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/folder/restricted.jpg"}, + Filename: "restricted.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + err = wf.Head(req.Context(), tr, auth, logger) + Expect(errors.Is(err, ErrPermissionDenied)).To(BeTrue()) + }) + }) + + Describe("file processing", func() { + It("returns ErrFileProcessing when the file is still being processed", func() { + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "processing.jpg") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "processingchecksum"}, + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "status": {Decoder: "plain", Value: []byte("processing")}, + }, + }, + }, + }, nil) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "processing.jpg"), + Filename: "processing.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(errors.Is(err, ErrFileProcessing)).To(BeTrue()) + }) + + It("returns ErrFileProcessing on Head when the file is still being processed", func() { + gatewayClient.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.AuthenticateRequest) bool { + return req.Type == "publicshares" && req.ClientId == publicLinkToken + })).Return(&gatewayv1beta1.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + Token: "public-link-token-head", + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "/public/"+publicLinkToken) + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "processingchecksum"}, + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "status": {Decoder: "plain", Value: []byte("processing")}, + }, + }, + }, + }, nil) + + req := newPublicLinkRequest("/dav/public-files/"+publicLinkToken+"/folder/photo.jpg?x=64&y=64", publicLinkToken) + auth, err := ResolvePublicLinkAuth(req.Context(), req, publicLinkToken, pool.GetSelector[gatewayv1beta1.GatewayAPIClient]( + "GatewaySelector", "test.gateway", + func(cc grpc.ClientConnInterface) gatewayv1beta1.GatewayAPIClient { return gatewayClient })) + Expect(err).ToNot(HaveOccurred()) + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "/public/" + publicLinkToken + "/folder/photo.jpg"}, + Filename: "photo.jpg", + Extension: ".jpg", + Width: 64, + Height: 64, + } + + err = wf.Head(req.Context(), tr, auth, logger) + Expect(errors.Is(err, ErrFileProcessing)).To(BeTrue()) + }) + }) +}) diff --git a/tests/acceptance/expected-failures-decomposed-storage.md b/tests/acceptance/expected-failures-decomposed-storage.md index 228cc5456e..ce65348372 100644 --- a/tests/acceptance/expected-failures-decomposed-storage.md +++ b/tests/acceptance/expected-failures-decomposed-storage.md @@ -322,19 +322,6 @@ _ocdav: api compatibility, return correct status code_ - [coreApiTrashbin/trashbinSharingToShares.feature:277](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L277) -#### [Preview. UTF characters do not display on prievew](https://github.com/opencloud-eu/opencloud/issues/1451) - -- [coreApiWebdavPreviews/previews.feature:249](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L249) -- [coreApiWebdavPreviews/previews.feature:250](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L250) -- [coreApiWebdavPreviews/previews.feature:251](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L251) - -#### [Preview of text file truncated](https://github.com/opencloud-eu/opencloud/issues/1452) - -- [coreApiWebdavPreviews/previews.feature:263](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L263) -- [coreApiWebdavPreviews/previews.feature:264](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L264) -- [coreApiWebdavPreviews/previews.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265) - - ### notification issue #323 - [apiNotification/emailNotification.feature:280](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiNotification/emailNotification.feature#L280) - [apiNotification/emailNotification.feature:298](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiNotification/emailNotification.feature#L298) diff --git a/tests/acceptance/expected-failures-posix-storage.md b/tests/acceptance/expected-failures-posix-storage.md index 049c1c80a6..9c0df2dc63 100644 --- a/tests/acceptance/expected-failures-posix-storage.md +++ b/tests/acceptance/expected-failures-posix-storage.md @@ -325,20 +325,6 @@ _ocdav: api compatibility, return correct status code_ - [coreApiTrashbin/trashbinSharingToShares.feature:277](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L277) -#### [Preview. UTF characters do not display on prievew](https://github.com/opencloud-eu/opencloud/issues/1451) - -- [coreApiWebdavPreviews/previews.feature:249](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L249) -- [coreApiWebdavPreviews/previews.feature:250](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L250) -- [coreApiWebdavPreviews/previews.feature:251](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L251) - -#### [Preview of text file truncated](https://github.com/opencloud-eu/opencloud/issues/1452) - -- [coreApiWebdavPreviews/previews.feature:263](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L263) -- [coreApiWebdavPreviews/previews.feature:264](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L264) -- [coreApiWebdavPreviews/previews.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265) - - - ### Won't fix Not everything needs to be implemented for opencloud. diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previews.feature b/tests/acceptance/features/coreApiWebdavPreviews/previews.feature index dc5f69c939..f997cf3708 100644 --- a/tests/acceptance/features/coreApiWebdavPreviews/previews.feature +++ b/tests/acceptance/features/coreApiWebdavPreviews/previews.feature @@ -241,7 +241,7 @@ Feature: previews of files downloaded through the webdav API Given using DAV path And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/lorem.txt" And user "Alice" has uploaded file with content "ओनक्लाउड फाएल शेरिङ्ग एन्ड सिन्किङ" to "/lorem.txt" - When user "Alice" downloads the preview of "/lorem.txt" with width "1200" and height "1200" using the WebDAV API + When user "Alice" downloads the preview of "/lorem.txt" with width "1024" and height "1024" using the WebDAV API Then the HTTP status code should be "200" And the downloaded preview content should match with "unicode-fixture.png" fixtures preview content Examples: @@ -254,9 +254,9 @@ Feature: previews of files downloaded through the webdav API Scenario Outline: download preview of the text file Given using DAV path And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/lorem.txt" - When user "Alice" downloads the preview of "/lorem.txt" with width "1200" and height "1200" and processor "thumbnail" using the WebDAV API + When user "Alice" downloads the preview of "/lorem.txt" with width "1024" and height "1024" and processor "thumbnail" using the WebDAV API Then the HTTP status code should be "200" - And the downloaded image should be "1200" pixels wide and "1200" pixels high + And the downloaded image should be "1024" pixels wide and "1024" pixels high And the downloaded preview content should match with "text-file-fixture.png" fixtures preview content Examples: | dav-path-version | diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature b/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature index 871df90a3a..14cb6ed375 100644 --- a/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature +++ b/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature @@ -11,28 +11,64 @@ Feature: sizing of previews of files downloaded through the webdav API Given user "Alice" has been created with default attributes - Scenario Outline: download different sizes of previews of file + Scenario Outline: download different sizes of previews of file (default = fill) Given using DAV path And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" When user "Alice" downloads the preview of "/parent.txt" with width and height using the WebDAV API Then the HTTP status code should be "200" And the downloaded image should be pixels wide and pixels high - # the source preview of a text file is always 640x480, - # so a request bigger than that is capped to the source size + # No processor is given, so the default behavior applies: fill. The source + # preview of a text file is always 640x480 (landscape). The requested size is + # snapped onto a configured resolution (the box) and the image is center-cropped + # to fill that box exactly (it may be upscaled or downscaled to cover it), so + # the output is always exactly the box dimensions. Examples: | request-width | request-height | return-width | return-height | dav-path-version | | 1 | 1 | 16 | 16 | old | | 32 | 32 | 32 | 32 | old | - | 1024 | 1024 | 640 | 480 | old | - | 1 | 1024 | 16 | 16 | old | - | 1024 | 1 | 640 | 480 | old | + | 1024 | 1024 | 1024 | 1024 | old | + | 1 | 1024 | 1080 | 1920 | old | + | 1024 | 1 | 1024 | 1024 | old | | 1 | 1 | 16 | 16 | new | | 32 | 32 | 32 | 32 | new | - | 1024 | 1024 | 640 | 480 | new | - | 1 | 1024 | 16 | 16 | new | - | 1024 | 1 | 640 | 480 | new | + | 1024 | 1024 | 1024 | 1024 | new | + | 1 | 1024 | 1080 | 1920 | new | + | 1024 | 1 | 1024 | 1024 | new | | 1 | 1 | 16 | 16 | spaces | | 32 | 32 | 32 | 32 | spaces | - | 1024 | 1024 | 640 | 480 | spaces | - | 1 | 1024 | 16 | 16 | spaces | - | 1024 | 1 | 640 | 480 | spaces | + | 1024 | 1024 | 1024 | 1024 | spaces | + | 1 | 1024 | 1080 | 1920 | spaces | + | 1024 | 1 | 1024 | 1024 | spaces | + + + Scenario Outline: download a preview with an explicit processor + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Alice" downloads the preview of "/parent.txt" with width and height and processor "" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be pixels wide and pixels high + # An explicit processor overrides the default. The source preview of a text + # file is always 640x480 (landscape); the requested size is snapped onto a + # configured resolution (the box) and the operation is applied within it: + # fit -> aspect-fit within the box, never upscaled (keeps source ratio) + # fill -> center-crop to the exact box (may upscale/downscale to cover) + # resize -> stretch to the exact box (distorts aspect ratio) + # + # The OpenCloud web client always sends a processor: list view requests + # processor=thumbnail at 64x64, tiles view requests processor=fit at a dynamic + # square between 320 and 768. It currently also sends the legacy a=1 flag on + # every request; that is redundant now that an explicit processor takes + # precedence over a, so web should stop sending it. + Examples: + | request-width | request-height | processor | return-width | return-height | dav-path-version | + | 1 | 1 | fit | 16 | 12 | spaces | + | 32 | 32 | fit | 32 | 24 | spaces | + | 1024 | 1024 | fit | 640 | 480 | spaces | + | 1 | 1024 | fit | 640 | 480 | spaces | + | 1024 | 1 | fit | 640 | 480 | spaces | + | 1 | 1 | fill | 16 | 16 | spaces | + | 32 | 32 | fill | 32 | 32 | spaces | + | 1024 | 1024 | fill | 1024 | 1024 | spaces | + | 1 | 1 | resize | 16 | 16 | spaces | + | 32 | 32 | resize | 32 | 32 | spaces | + | 1024 | 1024 | resize | 1024 | 1024 | spaces | diff --git a/tests/acceptance/fixtures/text-file-fixture.png b/tests/acceptance/fixtures/text-file-fixture.png index 15eb619914920b65337d09f8b221ef3aecf0e425..bfe5711b46bc0ad09c6da50e0d3faafbb52c1aef 100644 GIT binary patch literal 68518 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 z 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 K_J933pa0qA*CPA? literal 31009 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 EKbE&@ga7~l literal 715 zcmb7BJxjw-6utK)c}YXDFKtL;sx-A!(_$eNMjpP;&JAvw+_Hsj7Yeq!P%4V5>-$yL^Md78J*YL8JTF`io2zT> zPJ6N5jk{6YirSGhLDMvC%dXgVB?`PC`Zv-Ca4ZNR)NlougPMcsBY1!)r_X_zRCnjA zG|C$kH3TL!K^uEM11L!Y0koMsu-qr9MFwcFeP!m%wSZ75G{gk!vfB>=_AL$RF%WUGWY)ar$Hrh)LPJjD&)>_=0-X2}1 FuV26#T^9fV From efb1c648dcf37fe611ffa8f57d652faab0152b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 3 Sep 2026 17:35:26 +0200 Subject: [PATCH 3/5] refactor(thumbnails): remove the pull-based pipeline The thumbnails service is now a stateless image resizer; remove everything the old gRPC-based pipeline needed: - proto definitions and generated code (service + messages) - gRPC server, handler and decorators - JWT transfer token code and the /data HTTP download endpoint - filesystem storage layer and source fetchers (webdav + CS3 imgsource) - duplicated encoding/generator/processor/resolution utilities - the preprocessor packages moved to webdav in the previous commit - dead config fields (Thumbnail struct, GRPC config, unused go-micro client), no-op metrics/instrumentation wrappers, errors package, stale testdata and the Makefile protobuf target Also remove the now-meaningless thumbnails transfer secret generation from opencloud init and point the graph service at the new webdav thumbnail package. The README is rewritten to document the imagor-like push endpoint. --- opencloud/pkg/init/init.go | 15 +- opencloud/pkg/init/structs.go | 13 - .../messages/thumbnails/v0/thumbnails.pb.go | 326 ---------- .../thumbnails/v0/thumbnails.pb.micro.go | 15 - .../thumbnails/v0/thumbnails.swagger.json | 43 -- .../thumbnails/v0/mocks/thumbnail_service.go | 123 ---- .../services/thumbnails/v0/thumbnails.pb.go | 388 ----------- .../thumbnails/v0/thumbnails.pb.micro.go | 91 --- .../thumbnails/v0/thumbnails.swagger.json | 129 ---- .../thumbnails/v0/thumbnails_grpc.pb.go | 127 ---- .../messages/thumbnails/v0/thumbnails.proto | 30 - .../services/thumbnails/v0/thumbnails.proto | 66 -- services/graph/pkg/service/v0/sharedbyme.go | 2 +- services/graph/pkg/service/v0/sharedwithme.go | 2 +- services/thumbnails/Makefile | 7 - services/thumbnails/pkg/command/server.go | 19 - services/thumbnails/pkg/command/version.go | 2 +- services/thumbnails/pkg/config/config.go | 32 +- .../pkg/config/defaults/defaultconfig.go | 36 +- services/thumbnails/pkg/config/grpc.go | 11 - services/thumbnails/pkg/config/http.go | 1 - services/thumbnails/pkg/errors/error.go | 22 - services/thumbnails/pkg/metrics/metrics.go | 33 - .../thumbnails/pkg/preprocessor/fontloader.go | 192 ------ .../pkg/preprocessor/preprocessor.go | 350 ---------- .../pkg/preprocessor/preprocessor_imaging.go | 22 - .../pkg/preprocessor/preprocessor_test.go | 197 ------ .../pkg/preprocessor/preprocessor_vips.go | 20 - .../pkg/preprocessor/test_assets/empty.mp3 | Bin 199530 -> 0 bytes .../test_assets/empty_no_image.mp3 | Bin 4386 -> 0 bytes .../pkg/preprocessor/test_assets/ggs_test.ggs | Bin 7486 -> 0 bytes .../pkg/preprocessor/test_assets/noise.gif | Bin 84386 -> 0 bytes .../pkg/preprocessor/test_assets/noise.png | Bin 184891 -> 0 bytes .../pkg/preprocessor/textanalyzer.go | 309 --------- .../pkg/preprocessor/textanalyzer_test.go | 614 ------------------ .../thumbnails/pkg/server/debug/server.go | 3 +- services/thumbnails/pkg/server/grpc/option.go | 100 --- services/thumbnails/pkg/server/grpc/server.go | 92 --- services/thumbnails/pkg/server/http/server.go | 12 - .../pkg/service/grpc/v0/decorators/base.go | 63 -- .../service/grpc/v0/decorators/instrument.go | 39 -- .../pkg/service/grpc/v0/decorators/logging.go | 53 -- .../pkg/service/grpc/v0/decorators/tracing.go | 46 -- .../thumbnails/pkg/service/grpc/v0/option.go | 79 --- .../thumbnails/pkg/service/grpc/v0/service.go | 331 ---------- .../pkg/service/http/v0/instrument.go | 30 - .../thumbnails/pkg/service/http/v0/logging.go | 30 - .../thumbnails/pkg/service/http/v0/option.go | 17 +- .../thumbnails/pkg/service/http/v0/service.go | 104 +-- services/thumbnails/pkg/service/jwt/jwt.go | 9 - services/thumbnails/pkg/thumbnail/encoding.go | 81 --- .../pkg/thumbnail/encoding_imaging.go | 56 -- .../thumbnails/pkg/thumbnail/encoding_test.go | 22 - .../thumbnails/pkg/thumbnail/encoding_vips.go | 66 -- .../thumbnails/pkg/thumbnail/generator.go | 99 --- .../pkg/thumbnail/generator_simple.go | 46 -- .../pkg/thumbnail/generator_vips.go | 84 --- .../thumbnails/pkg/thumbnail/imgsource/cs3.go | 132 ---- .../pkg/thumbnail/imgsource/imgsource.go | 31 - .../pkg/thumbnail/imgsource/webdav.go | 75 --- .../thumbnails/pkg/thumbnail/mimetypes.go | 34 +- .../pkg/thumbnail/mimetypes_vips.go | 36 +- .../thumbnails/pkg/thumbnail/processor.go | 51 -- .../pkg/thumbnail/processor_test.go | 97 --- .../thumbnails/pkg/thumbnail/resolutions.go | 101 --- .../pkg/thumbnail/resolutions_test.go | 121 ---- .../pkg/thumbnail/storage/filesystem.go | 118 ---- .../pkg/thumbnail/storage/filesystem_test.go | 64 -- .../pkg/thumbnail/storage/storage.go | 32 - .../thumbnails/pkg/thumbnail/thumbnail.go | 131 ---- .../pkg/thumbnail/thumbnail_test.go | 198 ------ services/thumbnails/testdata/test.ggs | Bin 7486 -> 0 bytes services/thumbnails/testdata/test.jpg | Bin 34507 -> 0 bytes services/thumbnails/testdata/test.png | Bin 11150 -> 0 bytes 74 files changed, 59 insertions(+), 5861 deletions(-) delete mode 100644 protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.go delete mode 100644 protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.micro.go delete mode 100644 protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.swagger.json delete mode 100644 protogen/gen/opencloud/services/thumbnails/v0/mocks/thumbnail_service.go delete mode 100644 protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.go delete mode 100644 protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.micro.go delete mode 100644 protogen/gen/opencloud/services/thumbnails/v0/thumbnails.swagger.json delete mode 100644 protogen/gen/opencloud/services/thumbnails/v0/thumbnails_grpc.pb.go delete mode 100644 protogen/proto/opencloud/messages/thumbnails/v0/thumbnails.proto delete mode 100644 protogen/proto/opencloud/services/thumbnails/v0/thumbnails.proto delete mode 100644 services/thumbnails/pkg/config/grpc.go delete mode 100644 services/thumbnails/pkg/errors/error.go delete mode 100644 services/thumbnails/pkg/preprocessor/fontloader.go delete mode 100644 services/thumbnails/pkg/preprocessor/preprocessor.go delete mode 100644 services/thumbnails/pkg/preprocessor/preprocessor_imaging.go delete mode 100644 services/thumbnails/pkg/preprocessor/preprocessor_test.go delete mode 100644 services/thumbnails/pkg/preprocessor/preprocessor_vips.go delete mode 100644 services/thumbnails/pkg/preprocessor/test_assets/empty.mp3 delete mode 100644 services/thumbnails/pkg/preprocessor/test_assets/empty_no_image.mp3 delete mode 100644 services/thumbnails/pkg/preprocessor/test_assets/ggs_test.ggs delete mode 100644 services/thumbnails/pkg/preprocessor/test_assets/noise.gif delete mode 100644 services/thumbnails/pkg/preprocessor/test_assets/noise.png delete mode 100644 services/thumbnails/pkg/preprocessor/textanalyzer.go delete mode 100644 services/thumbnails/pkg/preprocessor/textanalyzer_test.go delete mode 100644 services/thumbnails/pkg/server/grpc/option.go delete mode 100644 services/thumbnails/pkg/server/grpc/server.go delete mode 100644 services/thumbnails/pkg/service/grpc/v0/decorators/base.go delete mode 100644 services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go delete mode 100644 services/thumbnails/pkg/service/grpc/v0/decorators/logging.go delete mode 100644 services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go delete mode 100644 services/thumbnails/pkg/service/grpc/v0/option.go delete mode 100644 services/thumbnails/pkg/service/grpc/v0/service.go delete mode 100644 services/thumbnails/pkg/service/http/v0/instrument.go delete mode 100644 services/thumbnails/pkg/service/http/v0/logging.go delete mode 100644 services/thumbnails/pkg/service/jwt/jwt.go delete mode 100644 services/thumbnails/pkg/thumbnail/encoding.go delete mode 100644 services/thumbnails/pkg/thumbnail/encoding_imaging.go delete mode 100644 services/thumbnails/pkg/thumbnail/encoding_test.go delete mode 100644 services/thumbnails/pkg/thumbnail/encoding_vips.go delete mode 100644 services/thumbnails/pkg/thumbnail/generator.go delete mode 100644 services/thumbnails/pkg/thumbnail/generator_simple.go delete mode 100644 services/thumbnails/pkg/thumbnail/generator_vips.go delete mode 100644 services/thumbnails/pkg/thumbnail/imgsource/cs3.go delete mode 100644 services/thumbnails/pkg/thumbnail/imgsource/imgsource.go delete mode 100644 services/thumbnails/pkg/thumbnail/imgsource/webdav.go delete mode 100644 services/thumbnails/pkg/thumbnail/processor.go delete mode 100644 services/thumbnails/pkg/thumbnail/processor_test.go delete mode 100644 services/thumbnails/pkg/thumbnail/resolutions.go delete mode 100644 services/thumbnails/pkg/thumbnail/resolutions_test.go delete mode 100644 services/thumbnails/pkg/thumbnail/storage/filesystem.go delete mode 100644 services/thumbnails/pkg/thumbnail/storage/filesystem_test.go delete mode 100644 services/thumbnails/pkg/thumbnail/storage/storage.go delete mode 100644 services/thumbnails/pkg/thumbnail/thumbnail.go delete mode 100644 services/thumbnails/pkg/thumbnail/thumbnail_test.go delete mode 100644 services/thumbnails/testdata/test.ggs delete mode 100644 services/thumbnails/testdata/test.jpg delete mode 100644 services/thumbnails/testdata/test.png diff --git a/opencloud/pkg/init/init.go b/opencloud/pkg/init/init.go index 83ae39e453..2293981d89 100644 --- a/opencloud/pkg/init/init.go +++ b/opencloud/pkg/init/init.go @@ -68,7 +68,7 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword systemUserID, adminUserID, graphApplicationID, storageUsersMountID, serviceAccountID string idmServicePassword, idpServicePassword, ocAdminServicePassword, revaServicePassword string tokenManagerJwtSecret, collaborationWOPISecret, machineAuthAPIKey, systemUserAPIKey string - revaTransferSecret, thumbnailsTransferSecret, serviceAccountSecret, urlSigningSecret string + revaTransferSecret, serviceAccountSecret, urlSigningSecret string adminPasswdwordGenerated bool ) @@ -94,7 +94,6 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword machineAuthAPIKey = oldCfg.MachineAuthAPIKey systemUserAPIKey = oldCfg.SystemUserAPIKey revaTransferSecret = oldCfg.TransferSecret - thumbnailsTransferSecret = oldCfg.Thumbnails.Thumbnail.TransferSecret serviceAccountSecret = oldCfg.Graph.ServiceAccount.ServiceAccountSecret urlSigningSecret = oldCfg.URLSigningSecret if urlSigningSecret == "" { @@ -155,10 +154,6 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword if err != nil { return fmt.Errorf("could not generate random secret for urlSigningSecret: %s", err) } - thumbnailsTransferSecret, err = generators.GenerateRandomPassword(passwordLength) - if err != nil { - return fmt.Errorf("could not generate random password for thumbnailsTransferSecret: %s", err) - } serviceAccountSecret, err = generators.GenerateRandomPassword(passwordLength) if err != nil { return fmt.Errorf("could not generate random secret for serviceAccountSecret: %s", err) @@ -230,11 +225,6 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword }, ServiceAccount: serviceAccount, }, - Thumbnails: ThumbnailService{ - Thumbnail: ThumbnailSettings{ - TransferSecret: thumbnailsTransferSecret, - }, - }, Gateway: Gateway{ StorageRegistry: StorageRegistry{ StorageUsersMountID: storageUsersMountID, @@ -302,9 +292,6 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword }, ServiceAccount: serviceAccount, } - - cfg.Thumbnails.Thumbnail.WebdavAllowInsecure = true - cfg.Thumbnails.Thumbnail.Cs3AllowInsecure = true } yamlOutput, err := yaml.Marshal(cfg) if err != nil { diff --git a/opencloud/pkg/init/structs.go b/opencloud/pkg/init/structs.go index 3e6d0ae4f9..3de7dc6e4a 100644 --- a/opencloud/pkg/init/structs.go +++ b/opencloud/pkg/init/structs.go @@ -33,7 +33,6 @@ type OpenCloudConfig struct { Users UsersAndGroupsService `yaml:"users"` Groups UsersAndGroupsService `yaml:"groups"` Ocm OcmService `yaml:"ocm"` - Thumbnails ThumbnailService `yaml:"thumbnails"` Search Search `yaml:"search"` Audit Audit `yaml:"audit"` Settings SettingsService `yaml:"settings"` @@ -220,18 +219,6 @@ type StorageUsers struct { ServiceAccount ServiceAccount `yaml:"service_account"` } -// ThumbnailSettings is the configuration for the thumbnail settings -type ThumbnailSettings struct { - TransferSecret string `yaml:"transfer_secret"` - WebdavAllowInsecure bool `yaml:"webdav_allow_insecure"` - Cs3AllowInsecure bool `yaml:"cs3_allow_insecure"` -} - -// ThumbnailService is the configuration for the thumbnail service -type ThumbnailService struct { - Thumbnail ThumbnailSettings -} - // TokenManager is the configuration for the token manager type TokenManager struct { JWTSecret string `yaml:"jwt_secret"` diff --git a/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.go b/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.go deleted file mode 100644 index 2b37c8987c..0000000000 --- a/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.go +++ /dev/null @@ -1,326 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc (unknown) -// source: opencloud/messages/thumbnails/v0/thumbnails.proto - -package v0 - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// The file types to which the thumbnail can be encoded to. -type ThumbnailType int32 - -const ( - ThumbnailType_PNG ThumbnailType = 0 // Represents PNG type - ThumbnailType_JPG ThumbnailType = 1 // Represents JPG type - ThumbnailType_GIF ThumbnailType = 2 // Represents GIF type -) - -// Enum value maps for ThumbnailType. -var ( - ThumbnailType_name = map[int32]string{ - 0: "PNG", - 1: "JPG", - 2: "GIF", - } - ThumbnailType_value = map[string]int32{ - "PNG": 0, - "JPG": 1, - "GIF": 2, - } -) - -func (x ThumbnailType) Enum() *ThumbnailType { - p := new(ThumbnailType) - *p = x - return p -} - -func (x ThumbnailType) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (ThumbnailType) Descriptor() protoreflect.EnumDescriptor { - return file_opencloud_messages_thumbnails_v0_thumbnails_proto_enumTypes[0].Descriptor() -} - -func (ThumbnailType) Type() protoreflect.EnumType { - return &file_opencloud_messages_thumbnails_v0_thumbnails_proto_enumTypes[0] -} - -func (x ThumbnailType) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use ThumbnailType.Descriptor instead. -func (ThumbnailType) EnumDescriptor() ([]byte, []int) { - return file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{0} -} - -type WebdavSource struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // REQUIRED. - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - // REQUIRED. - IsPublicLink bool `protobuf:"varint,2,opt,name=is_public_link,json=isPublicLink,proto3" json:"is_public_link,omitempty"` - // OPTIONAL. - WebdavAuthorization string `protobuf:"bytes,3,opt,name=webdav_authorization,json=webdavAuthorization,proto3" json:"webdav_authorization,omitempty"` - // OPTIONAL. - RevaAuthorization string `protobuf:"bytes,4,opt,name=reva_authorization,json=revaAuthorization,proto3" json:"reva_authorization,omitempty"` - // OPTIONAL. - PublicLinkToken string `protobuf:"bytes,5,opt,name=public_link_token,json=publicLinkToken,proto3" json:"public_link_token,omitempty"` -} - -func (x *WebdavSource) Reset() { - *x = WebdavSource{} - if protoimpl.UnsafeEnabled { - mi := &file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WebdavSource) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WebdavSource) ProtoMessage() {} - -func (x *WebdavSource) ProtoReflect() protoreflect.Message { - mi := &file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WebdavSource.ProtoReflect.Descriptor instead. -func (*WebdavSource) Descriptor() ([]byte, []int) { - return file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{0} -} - -func (x *WebdavSource) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -func (x *WebdavSource) GetIsPublicLink() bool { - if x != nil { - return x.IsPublicLink - } - return false -} - -func (x *WebdavSource) GetWebdavAuthorization() string { - if x != nil { - return x.WebdavAuthorization - } - return "" -} - -func (x *WebdavSource) GetRevaAuthorization() string { - if x != nil { - return x.RevaAuthorization - } - return "" -} - -func (x *WebdavSource) GetPublicLinkToken() string { - if x != nil { - return x.PublicLinkToken - } - return "" -} - -type CS3Source struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` - Authorization string `protobuf:"bytes,2,opt,name=authorization,proto3" json:"authorization,omitempty"` -} - -func (x *CS3Source) Reset() { - *x = CS3Source{} - if protoimpl.UnsafeEnabled { - mi := &file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CS3Source) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CS3Source) ProtoMessage() {} - -func (x *CS3Source) ProtoReflect() protoreflect.Message { - mi := &file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CS3Source.ProtoReflect.Descriptor instead. -func (*CS3Source) Descriptor() ([]byte, []int) { - return file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{1} -} - -func (x *CS3Source) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -func (x *CS3Source) GetAuthorization() string { - if x != nil { - return x.Authorization - } - return "" -} - -var File_opencloud_messages_thumbnails_v0_thumbnails_proto protoreflect.FileDescriptor - -var file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ - 0x0a, 0x31, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, - 0x76, 0x30, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x22, 0xd4, 0x01, 0x0a, 0x0c, 0x57, 0x65, 0x62, 0x64, 0x61, 0x76, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0c, 0x69, 0x73, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x31, - 0x0a, 0x14, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x65, - 0x62, 0x64, 0x61, 0x76, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x76, 0x61, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, - 0x65, 0x76, 0x61, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x45, 0x0a, 0x09, - 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, - 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2a, 0x2a, 0x0a, 0x0d, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x07, 0x0a, - 0x03, 0x4a, 0x50, 0x47, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x49, 0x46, 0x10, 0x02, 0x42, - 0x51, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, - 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, - 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, - 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescOnce sync.Once - file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescData = file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDesc -) - -func file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescGZIP() []byte { - file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescOnce.Do(func() { - file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescData = protoimpl.X.CompressGZIP(file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescData) - }) - return file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDescData -} - -var file_opencloud_messages_thumbnails_v0_thumbnails_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_opencloud_messages_thumbnails_v0_thumbnails_proto_goTypes = []interface{}{ - (ThumbnailType)(0), // 0: opencloud.messages.thumbnails.v0.ThumbnailType - (*WebdavSource)(nil), // 1: opencloud.messages.thumbnails.v0.WebdavSource - (*CS3Source)(nil), // 2: opencloud.messages.thumbnails.v0.CS3Source -} -var file_opencloud_messages_thumbnails_v0_thumbnails_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_opencloud_messages_thumbnails_v0_thumbnails_proto_init() } -func file_opencloud_messages_thumbnails_v0_thumbnails_proto_init() { - if File_opencloud_messages_thumbnails_v0_thumbnails_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WebdavSource); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CS3Source); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDesc, - NumEnums: 1, - NumMessages: 2, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_opencloud_messages_thumbnails_v0_thumbnails_proto_goTypes, - DependencyIndexes: file_opencloud_messages_thumbnails_v0_thumbnails_proto_depIdxs, - EnumInfos: file_opencloud_messages_thumbnails_v0_thumbnails_proto_enumTypes, - MessageInfos: file_opencloud_messages_thumbnails_v0_thumbnails_proto_msgTypes, - }.Build() - File_opencloud_messages_thumbnails_v0_thumbnails_proto = out.File - file_opencloud_messages_thumbnails_v0_thumbnails_proto_rawDesc = nil - file_opencloud_messages_thumbnails_v0_thumbnails_proto_goTypes = nil - file_opencloud_messages_thumbnails_v0_thumbnails_proto_depIdxs = nil -} diff --git a/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.micro.go b/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.micro.go deleted file mode 100644 index 95871dd887..0000000000 --- a/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.pb.micro.go +++ /dev/null @@ -1,15 +0,0 @@ -// Code generated by protoc-gen-micro. DO NOT EDIT. -// source: opencloud/messages/thumbnails/v0/thumbnails.proto - -package v0 - -import ( - fmt "fmt" - proto "google.golang.org/protobuf/proto" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf diff --git a/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.swagger.json b/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.swagger.json deleted file mode 100644 index 2e4c0f42c7..0000000000 --- a/protogen/gen/opencloud/messages/thumbnails/v0/thumbnails.swagger.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "opencloud/messages/thumbnails/v0/thumbnails.proto", - "version": "version not set" - }, - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": {}, - "definitions": { - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} - }, - "rpcStatus": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - }, - "details": { - "type": "array", - "items": { - "$ref": "#/definitions/protobufAny" - } - } - } - } - } -} diff --git a/protogen/gen/opencloud/services/thumbnails/v0/mocks/thumbnail_service.go b/protogen/gen/opencloud/services/thumbnails/v0/mocks/thumbnail_service.go deleted file mode 100644 index b9672a2c7f..0000000000 --- a/protogen/gen/opencloud/services/thumbnails/v0/mocks/thumbnail_service.go +++ /dev/null @@ -1,123 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package mocks - -import ( - "context" - - "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" - mock "github.com/stretchr/testify/mock" - "go-micro.dev/v4/client" -) - -// NewThumbnailService creates a new instance of ThumbnailService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewThumbnailService(t interface { - mock.TestingT - Cleanup(func()) -}) *ThumbnailService { - mock := &ThumbnailService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// ThumbnailService is an autogenerated mock type for the ThumbnailService type -type ThumbnailService struct { - mock.Mock -} - -type ThumbnailService_Expecter struct { - mock *mock.Mock -} - -func (_m *ThumbnailService) EXPECT() *ThumbnailService_Expecter { - return &ThumbnailService_Expecter{mock: &_m.Mock} -} - -// GetThumbnail provides a mock function for the type ThumbnailService -func (_mock *ThumbnailService) GetThumbnail(ctx context.Context, in *v0.GetThumbnailRequest, opts ...client.CallOption) (*v0.GetThumbnailResponse, error) { - var tmpRet mock.Arguments - if len(opts) > 0 { - tmpRet = _mock.Called(ctx, in, opts) - } else { - tmpRet = _mock.Called(ctx, in) - } - ret := tmpRet - - if len(ret) == 0 { - panic("no return value specified for GetThumbnail") - } - - var r0 *v0.GetThumbnailResponse - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, *v0.GetThumbnailRequest, ...client.CallOption) (*v0.GetThumbnailResponse, error)); ok { - return returnFunc(ctx, in, opts...) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, *v0.GetThumbnailRequest, ...client.CallOption) *v0.GetThumbnailResponse); ok { - r0 = returnFunc(ctx, in, opts...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v0.GetThumbnailResponse) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, *v0.GetThumbnailRequest, ...client.CallOption) error); ok { - r1 = returnFunc(ctx, in, opts...) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// ThumbnailService_GetThumbnail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetThumbnail' -type ThumbnailService_GetThumbnail_Call struct { - *mock.Call -} - -// GetThumbnail is a helper method to define mock.On call -// - ctx context.Context -// - in *v0.GetThumbnailRequest -// - opts ...client.CallOption -func (_e *ThumbnailService_Expecter) GetThumbnail(ctx interface{}, in interface{}, opts ...interface{}) *ThumbnailService_GetThumbnail_Call { - return &ThumbnailService_GetThumbnail_Call{Call: _e.mock.On("GetThumbnail", - append([]interface{}{ctx, in}, opts...)...)} -} - -func (_c *ThumbnailService_GetThumbnail_Call) Run(run func(ctx context.Context, in *v0.GetThumbnailRequest, opts ...client.CallOption)) *ThumbnailService_GetThumbnail_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 *v0.GetThumbnailRequest - if args[1] != nil { - arg1 = args[1].(*v0.GetThumbnailRequest) - } - var arg2 []client.CallOption - var variadicArgs []client.CallOption - if len(args) > 2 { - variadicArgs = args[2].([]client.CallOption) - } - arg2 = variadicArgs - run( - arg0, - arg1, - arg2..., - ) - }) - return _c -} - -func (_c *ThumbnailService_GetThumbnail_Call) Return(getThumbnailResponse *v0.GetThumbnailResponse, err error) *ThumbnailService_GetThumbnail_Call { - _c.Call.Return(getThumbnailResponse, err) - return _c -} - -func (_c *ThumbnailService_GetThumbnail_Call) RunAndReturn(run func(ctx context.Context, in *v0.GetThumbnailRequest, opts ...client.CallOption) (*v0.GetThumbnailResponse, error)) *ThumbnailService_GetThumbnail_Call { - _c.Call.Return(run) - return _c -} diff --git a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.go deleted file mode 100644 index dd0aa1ce92..0000000000 --- a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.go +++ /dev/null @@ -1,388 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc (unknown) -// source: opencloud/services/thumbnails/v0/thumbnails.proto - -package v0 - -import ( - _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" - v0 "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/thumbnails/v0" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// A request to retrieve a thumbnail -type GetThumbnailRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The path to the source image - Filepath string `protobuf:"bytes,1,opt,name=filepath,proto3" json:"filepath,omitempty"` - // The type to which the thumbnail should get encoded to. - ThumbnailType v0.ThumbnailType `protobuf:"varint,2,opt,name=thumbnail_type,json=thumbnailType,proto3,enum=opencloud.messages.thumbnails.v0.ThumbnailType" json:"thumbnail_type,omitempty"` - // The width of the thumbnail - Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` - // The height of the thumbnail - Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` - // Indicates which image processor to use - Processor string `protobuf:"bytes,5,opt,name=processor,proto3" json:"processor,omitempty"` - // Types that are assignable to Source: - // - // *GetThumbnailRequest_WebdavSource - // *GetThumbnailRequest_Cs3Source - Source isGetThumbnailRequest_Source `protobuf_oneof:"source"` -} - -func (x *GetThumbnailRequest) Reset() { - *x = GetThumbnailRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetThumbnailRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetThumbnailRequest) ProtoMessage() {} - -func (x *GetThumbnailRequest) ProtoReflect() protoreflect.Message { - mi := &file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetThumbnailRequest.ProtoReflect.Descriptor instead. -func (*GetThumbnailRequest) Descriptor() ([]byte, []int) { - return file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{0} -} - -func (x *GetThumbnailRequest) GetFilepath() string { - if x != nil { - return x.Filepath - } - return "" -} - -func (x *GetThumbnailRequest) GetThumbnailType() v0.ThumbnailType { - if x != nil { - return x.ThumbnailType - } - return v0.ThumbnailType(0) -} - -func (x *GetThumbnailRequest) GetWidth() int32 { - if x != nil { - return x.Width - } - return 0 -} - -func (x *GetThumbnailRequest) GetHeight() int32 { - if x != nil { - return x.Height - } - return 0 -} - -func (x *GetThumbnailRequest) GetProcessor() string { - if x != nil { - return x.Processor - } - return "" -} - -func (m *GetThumbnailRequest) GetSource() isGetThumbnailRequest_Source { - if m != nil { - return m.Source - } - return nil -} - -func (x *GetThumbnailRequest) GetWebdavSource() *v0.WebdavSource { - if x, ok := x.GetSource().(*GetThumbnailRequest_WebdavSource); ok { - return x.WebdavSource - } - return nil -} - -func (x *GetThumbnailRequest) GetCs3Source() *v0.CS3Source { - if x, ok := x.GetSource().(*GetThumbnailRequest_Cs3Source); ok { - return x.Cs3Source - } - return nil -} - -type isGetThumbnailRequest_Source interface { - isGetThumbnailRequest_Source() -} - -type GetThumbnailRequest_WebdavSource struct { - WebdavSource *v0.WebdavSource `protobuf:"bytes,6,opt,name=webdav_source,json=webdavSource,proto3,oneof"` -} - -type GetThumbnailRequest_Cs3Source struct { - Cs3Source *v0.CS3Source `protobuf:"bytes,7,opt,name=cs3_source,json=cs3Source,proto3,oneof"` -} - -func (*GetThumbnailRequest_WebdavSource) isGetThumbnailRequest_Source() {} - -func (*GetThumbnailRequest_Cs3Source) isGetThumbnailRequest_Source() {} - -// The service response -type GetThumbnailResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The endpoint where the thumbnail can be downloaded. - DataEndpoint string `protobuf:"bytes,1,opt,name=data_endpoint,json=dataEndpoint,proto3" json:"data_endpoint,omitempty"` - // The transfer token to be able to download the thumbnail. - TransferToken string `protobuf:"bytes,2,opt,name=transfer_token,json=transferToken,proto3" json:"transfer_token,omitempty"` - // The mimetype of the thumbnail - Mimetype string `protobuf:"bytes,3,opt,name=mimetype,proto3" json:"mimetype,omitempty"` -} - -func (x *GetThumbnailResponse) Reset() { - *x = GetThumbnailResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetThumbnailResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetThumbnailResponse) ProtoMessage() {} - -func (x *GetThumbnailResponse) ProtoReflect() protoreflect.Message { - mi := &file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetThumbnailResponse.ProtoReflect.Descriptor instead. -func (*GetThumbnailResponse) Descriptor() ([]byte, []int) { - return file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{1} -} - -func (x *GetThumbnailResponse) GetDataEndpoint() string { - if x != nil { - return x.DataEndpoint - } - return "" -} - -func (x *GetThumbnailResponse) GetTransferToken() string { - if x != nil { - return x.TransferToken - } - return "" -} - -func (x *GetThumbnailResponse) GetMimetype() string { - if x != nil { - return x.Mimetype - } - return "" -} - -var File_opencloud_services_thumbnails_v0_thumbnails_proto protoreflect.FileDescriptor - -var file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ - 0x0a, 0x31, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, - 0x76, 0x30, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x1a, 0x31, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x84, 0x03, 0x0a, 0x13, 0x47, 0x65, 0x74, - 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x56, 0x0a, 0x0e, - 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, - 0x12, 0x55, 0x0a, 0x0d, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, - 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x57, 0x65, 0x62, 0x64, 0x61, - 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x65, 0x62, 0x64, 0x61, - 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0a, 0x63, 0x73, 0x33, 0x5f, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x70, - 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, - 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x43, - 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09, 0x63, 0x73, 0x33, 0x53, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, - 0x7e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x5f, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x32, - 0x91, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x7d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, - 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x35, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, - 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x6f, 0x70, - 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, - 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x42, 0xff, 0x02, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f, - 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, - 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xaa, 0x02, 0x12, 0xbb, 0x01, 0x0a, 0x14, - 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x22, 0x51, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6c, 0x6f, 0x75, - 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x29, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x65, 0x75, 0x2a, 0x49, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, - 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x3b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, - 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, - 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, - 0x6e, 0x72, 0x42, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, - 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, - 0x6f, 0x63, 0x73, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x65, 0x75, - 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescOnce sync.Once - file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescData = file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDesc -) - -func file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescGZIP() []byte { - file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescOnce.Do(func() { - file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescData = protoimpl.X.CompressGZIP(file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescData) - }) - return file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDescData -} - -var file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_opencloud_services_thumbnails_v0_thumbnails_proto_goTypes = []interface{}{ - (*GetThumbnailRequest)(nil), // 0: opencloud.services.thumbnails.v0.GetThumbnailRequest - (*GetThumbnailResponse)(nil), // 1: opencloud.services.thumbnails.v0.GetThumbnailResponse - (v0.ThumbnailType)(0), // 2: opencloud.messages.thumbnails.v0.ThumbnailType - (*v0.WebdavSource)(nil), // 3: opencloud.messages.thumbnails.v0.WebdavSource - (*v0.CS3Source)(nil), // 4: opencloud.messages.thumbnails.v0.CS3Source -} -var file_opencloud_services_thumbnails_v0_thumbnails_proto_depIdxs = []int32{ - 2, // 0: opencloud.services.thumbnails.v0.GetThumbnailRequest.thumbnail_type:type_name -> opencloud.messages.thumbnails.v0.ThumbnailType - 3, // 1: opencloud.services.thumbnails.v0.GetThumbnailRequest.webdav_source:type_name -> opencloud.messages.thumbnails.v0.WebdavSource - 4, // 2: opencloud.services.thumbnails.v0.GetThumbnailRequest.cs3_source:type_name -> opencloud.messages.thumbnails.v0.CS3Source - 0, // 3: opencloud.services.thumbnails.v0.ThumbnailService.GetThumbnail:input_type -> opencloud.services.thumbnails.v0.GetThumbnailRequest - 1, // 4: opencloud.services.thumbnails.v0.ThumbnailService.GetThumbnail:output_type -> opencloud.services.thumbnails.v0.GetThumbnailResponse - 4, // [4:5] is the sub-list for method output_type - 3, // [3:4] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { file_opencloud_services_thumbnails_v0_thumbnails_proto_init() } -func file_opencloud_services_thumbnails_v0_thumbnails_proto_init() { - if File_opencloud_services_thumbnails_v0_thumbnails_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetThumbnailRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetThumbnailResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes[0].OneofWrappers = []interface{}{ - (*GetThumbnailRequest_WebdavSource)(nil), - (*GetThumbnailRequest_Cs3Source)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDesc, - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_opencloud_services_thumbnails_v0_thumbnails_proto_goTypes, - DependencyIndexes: file_opencloud_services_thumbnails_v0_thumbnails_proto_depIdxs, - MessageInfos: file_opencloud_services_thumbnails_v0_thumbnails_proto_msgTypes, - }.Build() - File_opencloud_services_thumbnails_v0_thumbnails_proto = out.File - file_opencloud_services_thumbnails_v0_thumbnails_proto_rawDesc = nil - file_opencloud_services_thumbnails_v0_thumbnails_proto_goTypes = nil - file_opencloud_services_thumbnails_v0_thumbnails_proto_depIdxs = nil -} diff --git a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.micro.go b/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.micro.go deleted file mode 100644 index 82fc725b30..0000000000 --- a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.pb.micro.go +++ /dev/null @@ -1,91 +0,0 @@ -// Code generated by protoc-gen-micro. DO NOT EDIT. -// source: opencloud/services/thumbnails/v0/thumbnails.proto - -package v0 - -import ( - fmt "fmt" - _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" - _ "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/thumbnails/v0" - proto "google.golang.org/protobuf/proto" - math "math" -) - -import ( - context "context" - api "go-micro.dev/v4/api" - client "go-micro.dev/v4/client" - server "go-micro.dev/v4/server" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// Reference imports to suppress errors if they are not otherwise used. -var _ api.Endpoint -var _ context.Context -var _ client.Option -var _ server.Option - -// Api Endpoints for ThumbnailService service - -func NewThumbnailServiceEndpoints() []*api.Endpoint { - return []*api.Endpoint{} -} - -// Client API for ThumbnailService service - -type ThumbnailService interface { - // Generates the thumbnail and returns it. - GetThumbnail(ctx context.Context, in *GetThumbnailRequest, opts ...client.CallOption) (*GetThumbnailResponse, error) -} - -type thumbnailService struct { - c client.Client - name string -} - -func NewThumbnailService(name string, c client.Client) ThumbnailService { - return &thumbnailService{ - c: c, - name: name, - } -} - -func (c *thumbnailService) GetThumbnail(ctx context.Context, in *GetThumbnailRequest, opts ...client.CallOption) (*GetThumbnailResponse, error) { - req := c.c.NewRequest(c.name, "ThumbnailService.GetThumbnail", in) - out := new(GetThumbnailResponse) - err := c.c.Call(ctx, req, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for ThumbnailService service - -type ThumbnailServiceHandler interface { - // Generates the thumbnail and returns it. - GetThumbnail(context.Context, *GetThumbnailRequest, *GetThumbnailResponse) error -} - -func RegisterThumbnailServiceHandler(s server.Server, hdlr ThumbnailServiceHandler, opts ...server.HandlerOption) error { - type thumbnailService interface { - GetThumbnail(ctx context.Context, in *GetThumbnailRequest, out *GetThumbnailResponse) error - } - type ThumbnailService struct { - thumbnailService - } - h := &thumbnailServiceHandler{hdlr} - return s.Handle(s.NewHandler(&ThumbnailService{h}, opts...)) -} - -type thumbnailServiceHandler struct { - ThumbnailServiceHandler -} - -func (h *thumbnailServiceHandler) GetThumbnail(ctx context.Context, in *GetThumbnailRequest, out *GetThumbnailResponse) error { - return h.ThumbnailServiceHandler.GetThumbnail(ctx, in, out) -} diff --git a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.swagger.json b/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.swagger.json deleted file mode 100644 index 8f813ff77e..0000000000 --- a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails.swagger.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "OpenCloud thumbnails", - "version": "1.0.0", - "contact": { - "name": "OpenCloud GmbH", - "url": "https://github.com/opencloud-eu/opencloud", - "email": "support@opencloud.eu" - }, - "license": { - "name": "Apache-2.0", - "url": "https://github.com/opencloud-eu/opencloud/blob/main/LICENSE" - } - }, - "tags": [ - { - "name": "ThumbnailService" - } - ], - "schemes": [ - "http", - "https" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": {}, - "definitions": { - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} - }, - "rpcStatus": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - }, - "details": { - "type": "array", - "items": { - "$ref": "#/definitions/protobufAny" - } - } - } - }, - "v0CS3Source": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "authorization": { - "type": "string" - } - } - }, - "v0GetThumbnailResponse": { - "type": "object", - "properties": { - "dataEndpoint": { - "type": "string", - "description": "The endpoint where the thumbnail can be downloaded." - }, - "transferToken": { - "type": "string", - "description": "The transfer token to be able to download the thumbnail." - }, - "mimetype": { - "type": "string", - "title": "The mimetype of the thumbnail" - } - }, - "title": "The service response" - }, - "v0ThumbnailType": { - "type": "string", - "enum": [ - "PNG", - "JPG", - "GIF" - ], - "default": "PNG", - "description": "The file types to which the thumbnail can be encoded to.\n\n - PNG: Represents PNG type\n - JPG: Represents JPG type\n - GIF: Represents GIF type" - }, - "v0WebdavSource": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "REQUIRED." - }, - "isPublicLink": { - "type": "boolean", - "description": "REQUIRED." - }, - "webdavAuthorization": { - "type": "string", - "description": "OPTIONAL." - }, - "revaAuthorization": { - "type": "string", - "description": "OPTIONAL." - }, - "publicLinkToken": { - "type": "string", - "description": "OPTIONAL." - } - } - } - }, - "externalDocs": { - "description": "Developer Manual", - "url": "https://docs.opencloud.eu/services/thumbnails/" - } -} diff --git a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails_grpc.pb.go b/protogen/gen/opencloud/services/thumbnails/v0/thumbnails_grpc.pb.go deleted file mode 100644 index c3757abc8b..0000000000 --- a/protogen/gen/opencloud/services/thumbnails/v0/thumbnails_grpc.pb.go +++ /dev/null @@ -1,127 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.6.2 -// - protoc (unknown) -// source: opencloud/services/thumbnails/v0/thumbnails.proto - -package v0 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - ThumbnailService_GetThumbnail_FullMethodName = "/opencloud.services.thumbnails.v0.ThumbnailService/GetThumbnail" -) - -// ThumbnailServiceClient is the client API for ThumbnailService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -// -// A Service for handling thumbnail generation -type ThumbnailServiceClient interface { - // Generates the thumbnail and returns it. - GetThumbnail(ctx context.Context, in *GetThumbnailRequest, opts ...grpc.CallOption) (*GetThumbnailResponse, error) -} - -type thumbnailServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewThumbnailServiceClient(cc grpc.ClientConnInterface) ThumbnailServiceClient { - return &thumbnailServiceClient{cc} -} - -func (c *thumbnailServiceClient) GetThumbnail(ctx context.Context, in *GetThumbnailRequest, opts ...grpc.CallOption) (*GetThumbnailResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetThumbnailResponse) - err := c.cc.Invoke(ctx, ThumbnailService_GetThumbnail_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ThumbnailServiceServer is the server API for ThumbnailService service. -// All implementations must embed UnimplementedThumbnailServiceServer -// for forward compatibility. -// -// A Service for handling thumbnail generation -type ThumbnailServiceServer interface { - // Generates the thumbnail and returns it. - GetThumbnail(context.Context, *GetThumbnailRequest) (*GetThumbnailResponse, error) - mustEmbedUnimplementedThumbnailServiceServer() -} - -// UnimplementedThumbnailServiceServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedThumbnailServiceServer struct{} - -func (UnimplementedThumbnailServiceServer) GetThumbnail(context.Context, *GetThumbnailRequest) (*GetThumbnailResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetThumbnail not implemented") -} -func (UnimplementedThumbnailServiceServer) mustEmbedUnimplementedThumbnailServiceServer() {} -func (UnimplementedThumbnailServiceServer) testEmbeddedByValue() {} - -// UnsafeThumbnailServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ThumbnailServiceServer will -// result in compilation errors. -type UnsafeThumbnailServiceServer interface { - mustEmbedUnimplementedThumbnailServiceServer() -} - -func RegisterThumbnailServiceServer(s grpc.ServiceRegistrar, srv ThumbnailServiceServer) { - // If the following call panics, it indicates UnimplementedThumbnailServiceServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&ThumbnailService_ServiceDesc, srv) -} - -func _ThumbnailService_GetThumbnail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetThumbnailRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ThumbnailServiceServer).GetThumbnail(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ThumbnailService_GetThumbnail_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ThumbnailServiceServer).GetThumbnail(ctx, req.(*GetThumbnailRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// ThumbnailService_ServiceDesc is the grpc.ServiceDesc for ThumbnailService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ThumbnailService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "opencloud.services.thumbnails.v0.ThumbnailService", - HandlerType: (*ThumbnailServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetThumbnail", - Handler: _ThumbnailService_GetThumbnail_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "opencloud/services/thumbnails/v0/thumbnails.proto", -} diff --git a/protogen/proto/opencloud/messages/thumbnails/v0/thumbnails.proto b/protogen/proto/opencloud/messages/thumbnails/v0/thumbnails.proto deleted file mode 100644 index f2e4692564..0000000000 --- a/protogen/proto/opencloud/messages/thumbnails/v0/thumbnails.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax = "proto3"; - -package opencloud.messages.thumbnails.v0; - -option go_package = "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/thumbnails/v0"; - -message WebdavSource { - // REQUIRED. - string url = 1; - // REQUIRED. - bool is_public_link = 2; - // OPTIONAL. - string webdav_authorization = 3; - // OPTIONAL. - string reva_authorization = 4; - // OPTIONAL. - string public_link_token = 5; -} - -message CS3Source { - string path = 1; - string authorization = 2; -} - -// The file types to which the thumbnail can be encoded to. -enum ThumbnailType { - PNG = 0; // Represents PNG type - JPG = 1; // Represents JPG type - GIF = 2; // Represents GIF type -} diff --git a/protogen/proto/opencloud/services/thumbnails/v0/thumbnails.proto b/protogen/proto/opencloud/services/thumbnails/v0/thumbnails.proto deleted file mode 100644 index 80e5d24099..0000000000 --- a/protogen/proto/opencloud/services/thumbnails/v0/thumbnails.proto +++ /dev/null @@ -1,66 +0,0 @@ -syntax = "proto3"; - -package opencloud.services.thumbnails.v0; - -option go_package = "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0"; - -import "opencloud/messages/thumbnails/v0/thumbnails.proto"; -import "protoc-gen-openapiv2/options/annotations.proto"; - -option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { - info: { - title: "OpenCloud thumbnails"; - version: "1.0.0"; - contact: { - name: "OpenCloud GmbH"; - url: "https://github.com/opencloud-eu/opencloud"; - email: "support@opencloud.eu"; - }; - license: { - name: "Apache-2.0"; - url: "https://github.com/opencloud-eu/opencloud/blob/main/LICENSE"; - }; - }; - schemes: HTTP; - schemes: HTTPS; - consumes: "application/json"; - produces: "application/json"; - external_docs: { - description: "Developer Manual"; - url: "https://docs.opencloud.eu/services/thumbnails/"; - }; -}; - -// A Service for handling thumbnail generation -service ThumbnailService { - // Generates the thumbnail and returns it. - rpc GetThumbnail(GetThumbnailRequest) returns (GetThumbnailResponse); -} - -// A request to retrieve a thumbnail -message GetThumbnailRequest { - // The path to the source image - string filepath = 1; - // The type to which the thumbnail should get encoded to. - opencloud.messages.thumbnails.v0.ThumbnailType thumbnail_type = 2; - // The width of the thumbnail - int32 width = 3; - // The height of the thumbnail - int32 height = 4; - // Indicates which image processor to use - string processor = 5; - oneof source { - opencloud.messages.thumbnails.v0.WebdavSource webdav_source = 6; - opencloud.messages.thumbnails.v0.CS3Source cs3_source = 7; - } -} - -// The service response -message GetThumbnailResponse { - // The endpoint where the thumbnail can be downloaded. - string data_endpoint = 1; - // The transfer token to be able to download the thumbnail. - string transfer_token = 2; - // The mimetype of the thumbnail - string mimetype = 3; -} diff --git a/services/graph/pkg/service/v0/sharedbyme.go b/services/graph/pkg/service/v0/sharedbyme.go index e2b40da15b..2708519ee4 100644 --- a/services/graph/pkg/service/v0/sharedbyme.go +++ b/services/graph/pkg/service/v0/sharedbyme.go @@ -8,7 +8,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/go-chi/render" libregraph "github.com/opencloud-eu/libre-graph-api-go" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" ) diff --git a/services/graph/pkg/service/v0/sharedwithme.go b/services/graph/pkg/service/v0/sharedwithme.go index c9d1ca92cf..f846d2c283 100644 --- a/services/graph/pkg/service/v0/sharedwithme.go +++ b/services/graph/pkg/service/v0/sharedwithme.go @@ -13,7 +13,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/share" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail" ) // ListSharedWithMe lists the files shared with the current user. diff --git a/services/thumbnails/Makefile b/services/thumbnails/Makefile index ed66a59360..2e9bfb1ac2 100644 --- a/services/thumbnails/Makefile +++ b/services/thumbnails/Makefile @@ -9,10 +9,3 @@ include ../../.make/default.mk include ../../.make/go.mk include ../../.make/release.mk include ../../.make/docs.mk -include ../../.make/protobuf.mk - -.PHONY: go-generate -go-generate: protobuf - -.PHONY: protobuf -protobuf: buf-generate diff --git a/services/thumbnails/pkg/command/server.go b/services/thumbnails/pkg/command/server.go index e9a0183f5f..e528119c19 100644 --- a/services/thumbnails/pkg/command/server.go +++ b/services/thumbnails/pkg/command/server.go @@ -8,14 +8,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/runner" - ogrpc "github.com/opencloud-eu/opencloud/pkg/service/grpc" "github.com/opencloud-eu/opencloud/pkg/tracing" "github.com/opencloud-eu/opencloud/pkg/version" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/metrics" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/server/debug" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/server/grpc" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/server/http" "github.com/spf13/cobra" @@ -36,10 +34,6 @@ func Server(cfg *config.Config) *cobra.Command { if err != nil { return err } - cfg.GrpcClient, err = ogrpc.NewClient(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...) - if err != nil { - return err - } var cancel context.CancelFunc if cfg.Context == nil { @@ -53,19 +47,6 @@ func Server(cfg *config.Config) *cobra.Command { gr := runner.NewGroup() - service := grpc.NewService( - grpc.Logger(logger), - grpc.Context(ctx), - grpc.Config(cfg), - grpc.Name(cfg.Service.Name), - grpc.Namespace(cfg.GRPC.Namespace), - grpc.Address(cfg.GRPC.Addr), - grpc.Metrics(m), - grpc.TraceProvider(traceProvider), - grpc.MaxConcurrentRequests(cfg.GRPC.MaxConcurrentRequests), - ) - gr.Add(runner.NewGoMicroGrpcServerRunner(cfg.Service.Name+".grpc", service)) - server, err := debug.Server( debug.Logger(logger), debug.Config(cfg), diff --git a/services/thumbnails/pkg/command/version.go b/services/thumbnails/pkg/command/version.go index 2099bbee24..77ef0c83f3 100644 --- a/services/thumbnails/pkg/command/version.go +++ b/services/thumbnails/pkg/command/version.go @@ -24,7 +24,7 @@ func Version(cfg *config.Config) *cobra.Command { fmt.Println("") reg := registry.GetRegistry() - services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) if err != nil { fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) return err diff --git a/services/thumbnails/pkg/config/config.go b/services/thumbnails/pkg/config/config.go index 408503b718..0f8d8d4c87 100644 --- a/services/thumbnails/pkg/config/config.go +++ b/services/thumbnails/pkg/config/config.go @@ -5,7 +5,6 @@ import ( "context" "github.com/opencloud-eu/opencloud/pkg/shared" - "go-micro.dev/v4/client" ) // Config combines all available configuration parts. @@ -17,33 +16,20 @@ type Config struct { LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;THUMBNAILS_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"` Debug Debug `yaml:"debug"` - GRPC GRPCConfig `yaml:"grpc"` - HTTP HTTP `yaml:"http"` - - GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"` - GrpcClient client.Client `yaml:"-"` + HTTP HTTP `yaml:"http"` Thumbnail Thumbnail `yaml:"thumbnail"` Context context.Context `yaml:"-"` } -// FileSystemStorage defines the available filesystem storage configuration. -type FileSystemStorage struct { - RootDirectory string `yaml:"root_directory" env:"THUMBNAILS_FILESYSTEMSTORAGE_ROOT" desc:"The directory where the filesystem storage will store the thumbnails. If not defined, the root directory derives from $OC_BASE_DATA_PATH/thumbnails." introductionVersion:"1.0.0"` -} - -// Thumbnail defines the available thumbnail related configuration. +// Thumbnail holds the safety limits enforced by the push endpoint. The limits +// mirror the legacy thumbnail service so a request that was rejected before is +// still rejected, but the enforcement now lives at decode time in this service +// (which owns the image) rather than in webdav. type Thumbnail struct { - Resolutions []string `yaml:"resolutions" env:"THUMBNAILS_RESOLUTIONS" desc:"The supported list of target resolutions in the format WidthxHeight like 32x32. You can define any resolution as required. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"` - FileSystemStorage FileSystemStorage `yaml:"filesystem_storage"` - WebdavAllowInsecure bool `yaml:"webdav_allow_insecure" env:"OC_INSECURE;THUMBNAILS_WEBDAVSOURCE_INSECURE" desc:"Ignore untrusted SSL certificates when connecting to the webdav source." introductionVersion:"1.0.0"` - CS3AllowInsecure bool `yaml:"cs3_allow_insecure" env:"OC_INSECURE;THUMBNAILS_CS3SOURCE_INSECURE" desc:"Ignore untrusted SSL certificates when connecting to the CS3 source." introductionVersion:"1.0.0"` - RevaGateway string `yaml:"reva_gateway" env:"OC_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"1.0.0"` - FontMapFile string `yaml:"font_map_file" env:"THUMBNAILS_TXT_FONTMAP_FILE" desc:"The path to a font file for txt thumbnails." introductionVersion:"1.0.0"` - TransferSecret string `yaml:"transfer_secret" env:"THUMBNAILS_TRANSFER_TOKEN" desc:"The secret to sign JWT to download the actual thumbnail file." introductionVersion:"1.0.0"` - DataEndpoint string `yaml:"data_endpoint" env:"THUMBNAILS_DATA_ENDPOINT" desc:"The HTTP endpoint where the actual thumbnail file can be downloaded." introductionVersion:"1.0.0"` - MaxInputWidth int `yaml:"max_input_width" env:"THUMBNAILS_MAX_INPUT_WIDTH" desc:"The maximum width of an input image which is being processed." introductionVersion:"1.0.0"` - MaxInputHeight int `yaml:"max_input_height" env:"THUMBNAILS_MAX_INPUT_HEIGHT" desc:"The maximum height of an input image which is being processed." introductionVersion:"1.0.0"` - MaxInputImageFileSize string `yaml:"max_input_image_file_size" env:"THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"The maximum file size of an input image which is being processed. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, PiB, EB, EiB], example: 2GB." introductionVersion:"1.0.0"` + // MaxInputWidth is the maximum width of an input image which is being processed. + MaxInputWidth int `yaml:"max_input_width" env:"THUMBNAILS_MAX_INPUT_WIDTH" desc:"The maximum width of an input image which is being processed." introductionVersion:"1.0.0"` + // MaxInputHeight is the maximum height of an input image which is being processed. + MaxInputHeight int `yaml:"max_input_height" env:"THUMBNAILS_MAX_INPUT_HEIGHT" desc:"The maximum height of an input image which is being processed." introductionVersion:"1.0.0"` } diff --git a/services/thumbnails/pkg/config/defaults/defaultconfig.go b/services/thumbnails/pkg/config/defaults/defaultconfig.go index 53f5e86f94..67ce0c4c4f 100644 --- a/services/thumbnails/pkg/config/defaults/defaultconfig.go +++ b/services/thumbnails/pkg/config/defaults/defaultconfig.go @@ -1,12 +1,6 @@ package defaults import ( - "path" - "strings" - - "github.com/opencloud-eu/opencloud/pkg/config/defaults" - "github.com/opencloud-eu/opencloud/pkg/shared" - "github.com/opencloud-eu/opencloud/pkg/structs" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" ) @@ -27,14 +21,8 @@ func DefaultConfig() *config.Config { Pprof: false, Zpages: false, }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9185", - Namespace: "eu.opencloud.api", - MaxConcurrentRequests: 0, - }, HTTP: config.HTTP{ Addr: "127.0.0.1:9186", - Root: "/thumbnails", Namespace: "eu.opencloud.web", CORS: config.CORS{ AllowedOrigins: []string{"*"}, @@ -47,17 +35,8 @@ func DefaultConfig() *config.Config { Name: "thumbnails", }, Thumbnail: config.Thumbnail{ - Resolutions: []string{"16x16", "32x32", "64x64", "128x128", "500x280", "280x500", "1000x560", "560x1000", "512x2048", "1080x1920", "1920x1080", "2160x3840", "3840x2160", "4320x7680", "7680x4320"}, - FileSystemStorage: config.FileSystemStorage{ - RootDirectory: path.Join(defaults.BaseDataPath(), "thumbnails"), - }, - WebdavAllowInsecure: false, - RevaGateway: shared.DefaultRevaConfig().Address, - CS3AllowInsecure: false, - DataEndpoint: "http://127.0.0.1:9186/thumbnails/data", - MaxInputWidth: 7680, - MaxInputHeight: 7680, - MaxInputImageFileSize: "50MB", + MaxInputWidth: 7680, + MaxInputHeight: 7680, }, } } @@ -68,13 +47,6 @@ func EnsureDefaults(cfg *config.Config) { cfg.LogLevel = "error" } - if cfg.GRPCClientTLS == nil && cfg.Commons != nil { - cfg.GRPCClientTLS = structs.CopyOrZeroValue(cfg.Commons.GRPCClientTLS) - } - if cfg.GRPC.TLS == nil && cfg.Commons != nil { - cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS) - } - if cfg.Commons != nil { cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS } @@ -82,8 +54,4 @@ func EnsureDefaults(cfg *config.Config) { // Sanitize sanitized the configuration func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm - if len(cfg.Thumbnail.Resolutions) == 1 && strings.Contains(cfg.Thumbnail.Resolutions[0], ",") { - cfg.Thumbnail.Resolutions = strings.Split(cfg.Thumbnail.Resolutions[0], ",") - } } diff --git a/services/thumbnails/pkg/config/grpc.go b/services/thumbnails/pkg/config/grpc.go deleted file mode 100644 index 32c4b4d8df..0000000000 --- a/services/thumbnails/pkg/config/grpc.go +++ /dev/null @@ -1,11 +0,0 @@ -package config - -import "github.com/opencloud-eu/opencloud/pkg/shared" - -// GRPCConfig defines the available grpc configuration. -type GRPCConfig struct { - Addr string `yaml:"addr" env:"THUMBNAILS_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"1.0.0"` - Namespace string `yaml:"-"` - TLS *shared.GRPCServiceTLS `yaml:"tls"` - MaxConcurrentRequests int `yaml:"max_concurrent_requests" env:"THUMBNAILS_MAX_CONCURRENT_REQUESTS" desc:"Number of maximum concurrent thumbnail requests. Default is 0 which is unlimited." introductionVersion:"1.0.0"` -} diff --git a/services/thumbnails/pkg/config/http.go b/services/thumbnails/pkg/config/http.go index f6e81d5509..d082e1cc32 100644 --- a/services/thumbnails/pkg/config/http.go +++ b/services/thumbnails/pkg/config/http.go @@ -14,7 +14,6 @@ type CORS struct { type HTTP struct { Addr string `yaml:"addr" env:"THUMBNAILS_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"` TLS shared.HTTPServiceTLS `yaml:"tls"` - Root string `yaml:"root" env:"THUMBNAILS_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"1.0.0"` Namespace string `yaml:"-"` CORS CORS `yaml:"cors"` } diff --git a/services/thumbnails/pkg/errors/error.go b/services/thumbnails/pkg/errors/error.go deleted file mode 100644 index 15e89bb59d..0000000000 --- a/services/thumbnails/pkg/errors/error.go +++ /dev/null @@ -1,22 +0,0 @@ -package errors - -import "errors" - -var ( - // ErrImageTooLarge defines an error when an input image is too large - ErrImageTooLarge = errors.New("thumbnails: image is too large") - // ErrInvalidType represents the error when a type can't be encoded. - ErrInvalidType = errors.New("thumbnails: can't encode this type") - // ErrNoEncoderForType represents the error when an encoder couldn't be found for a type. - ErrNoEncoderForType = errors.New("thumbnails: no encoder for this type found") - // ErrNoGeneratorForType represents the error when a generator couldn't be found for a type. - ErrNoGeneratorForType = errors.New("thumbnails: no generator for this type found") - // ErrNoImageFromAudioFile defines an error when an image cannot be extracted from an audio file - ErrNoImageFromAudioFile = errors.New("thumbnails: could not extract image from audio file") - // ErrNoConverterForExtractedImageFromGgsFile defines an error when the extracted image from an ggs file could not be converted - ErrNoConverterForExtractedImageFromGgsFile = errors.New("thumbnails: could not find converter for image extracted from ggs file") - // ErrNoConverterForExtractedImageFromAudioFile defines an error when the extracted image from an audio file could not be converted - ErrNoConverterForExtractedImageFromAudioFile = errors.New("thumbnails: could not find converter for image extracted from audio file") - // ErrCS3AuthorizationMissing defines an error when the CS3 authorization is missing - ErrCS3AuthorizationMissing = errors.New("thumbnails: cs3source - authorization missing") -) diff --git a/services/thumbnails/pkg/metrics/metrics.go b/services/thumbnails/pkg/metrics/metrics.go index a9af58a52a..78b9d5f462 100644 --- a/services/thumbnails/pkg/metrics/metrics.go +++ b/services/thumbnails/pkg/metrics/metrics.go @@ -12,33 +12,12 @@ var ( // Metrics defines the available metrics of this service. type Metrics struct { - Counter *prometheus.CounterVec - Latency *prometheus.SummaryVec - Duration *prometheus.HistogramVec BuildInfo *prometheus.GaugeVec } // New initializes the available metrics. func New() *Metrics { m := &Metrics{ - Counter: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: Namespace, - Subsystem: Subsystem, - Name: "getthumbnail_total", - Help: "How many GetThumbnail requests processed", - }, []string{}), - Latency: prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Namespace: Namespace, - Subsystem: Subsystem, - Name: "getthumbnail_latency_microseconds", - Help: "GetThumbnail request latencies in microseconds", - }, []string{}), - Duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: Namespace, - Subsystem: Subsystem, - Name: "getthumbnail_duration_seconds", - Help: "GetThumbnail method requests time in seconds", - }, []string{}), BuildInfo: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: Subsystem, @@ -47,18 +26,6 @@ func New() *Metrics { }, []string{"version"}), } - _ = prometheus.Register( - m.Counter, - ) - - _ = prometheus.Register( - m.Latency, - ) - - _ = prometheus.Register( - m.Duration, - ) - _ = prometheus.Register( m.BuildInfo, ) diff --git a/services/thumbnails/pkg/preprocessor/fontloader.go b/services/thumbnails/pkg/preprocessor/fontloader.go deleted file mode 100644 index 91824b611d..0000000000 --- a/services/thumbnails/pkg/preprocessor/fontloader.go +++ /dev/null @@ -1,192 +0,0 @@ -package preprocessor - -import ( - "encoding/json" - "os" - "path/filepath" - "time" - - "github.com/opencloud-eu/opencloud/pkg/sync" - "golang.org/x/image/font" - "golang.org/x/image/font/gofont/goregular" - "golang.org/x/image/font/opentype" -) - -// FontMap maps a script with the target font to be used for that script -// It also uses a DefaultFont in case there isn't a matching script in the map -// -// For cases like Japanese where multiple scripts are used, we rely on the text -// analyzer to use the script which is unique to japanese (Hiragana or Katakana) -// even if it has to overwrite the "official" detected script (Han). This means -// that "Han" should be used just for chinese while "Hiragana" and "Katakana" -// should be used for japanese -type FontMap struct { - FontMap map[string]string `json:"fontMap"` - DefaultFont string `json:"defaultFont"` -} - -// FontMapData contains the location of the loaded file (in FLoc) and the FontMap loaded -// from the file -type FontMapData struct { - FMap *FontMap - FLoc string -} - -// LoadedFace contains the location of the font used, and the loaded face (font.Face) -// ready to be used -type LoadedFace struct { - FontFile string - Face font.Face -} - -// FontLoader represents a FontLoader. Use the "NewFontLoader" to get a instance -type FontLoader struct { - faceCache sync.Cache - fontMapData *FontMapData - faceOpts *opentype.FaceOptions -} - -// NewFontLoader creates a new FontLoader based on the fontMapFile. The FaceOptions will -// be the same for all the font loaded by this instance. -// Note that only the fonts described in the fontMapFile will be used. -// -// The fontMapFile has the following structure -// -// { -// "fontMap": { -// "Han": "packaged/myFont-CJK.otf", -// "Arabic": "packaged/myFont-Arab.otf", -// "Latin": "/fonts/regular/myFont.otf" -// } -// "defaultFont": "/fonts/regular/myFont.otf" -// } -// -// The fontMapFile contains paths to where the fonts are located in the FS. -// Absolute paths can be used as shown above. If a relative path is used, -// it will be relative to the fontMapFile location. This should make the -// packaging easier since all the fonts can be placed in the same directory -// where the fontMapFile is, or in inner directories. -func NewFontLoader(fontMapFile string, faceOpts *opentype.FaceOptions) (*FontLoader, error) { - fontMap := &FontMap{} - - if fontMapFile != "" { - file, err := os.Open(fontMapFile) - if err != nil { - return nil, err - } - defer file.Close() - - parser := json.NewDecoder(file) - if err = parser.Decode(fontMap); err != nil { - return nil, err - } - } - - return &FontLoader{ - faceCache: sync.NewCache(5), - fontMapData: &FontMapData{ - FMap: fontMap, - FLoc: fontMapFile, - }, - faceOpts: faceOpts, - }, nil -} - -// LoadFaceForScript loads and returns the font face to be used for that script according to the -// FontMap set when the FontLoader was created. If the script doesn't have -// an associated font, a default font will be used. Note that the default font -// might not be able to handle properly the script -func (fl *FontLoader) LoadFaceForScript(script string) (*LoadedFace, error) { - var parsedFont *opentype.Font - var parsingError error - - fontFile := fl.fontMapData.FMap.DefaultFont - if val, ok := fl.fontMapData.FMap.FontMap[script]; ok { - fontFile = val - } - - if fontFile != "" && !filepath.IsAbs(fontFile) { - fontFile = filepath.Join(filepath.Dir(fl.fontMapData.FLoc), fontFile) - } - - // if the face for the script isn't cached, load the font file and create a new face - cachedFace := fl.faceCache.Load(fontFile) - if cachedFace != nil { - return cachedFace.V.(*LoadedFace), nil - } - - if fontFile == "" { - parsedFont, parsingError = opentype.Parse(goregular.TTF) - if parsingError != nil { - return nil, parsingError - } - } else { - // opentype.ParseReaderAt seems to require to keep the file opened - // so read the font file into memory - data, err := os.ReadFile(fontFile) - if err != nil { - return nil, err - } - parsedFont, parsingError = opentype.Parse(data) - if parsingError != nil { - return nil, parsingError - } - } - - face, err := opentype.NewFace(parsedFont, fl.faceOpts) - if err != nil { - return nil, err - } - - loadedFace := &LoadedFace{ - FontFile: fontFile, - Face: face, - } - fl.faceCache.Store(fontFile, loadedFace, time.Now().Add(10*time.Minute)) - return loadedFace, nil -} - -// GetFaceOptSize returns face opt size -func (fl *FontLoader) GetFaceOptSize() float64 { - return fl.faceOpts.Size -} - -// GetFaceOptDPI returns face opt DPI -func (fl *FontLoader) GetFaceOptDPI() float64 { - return fl.faceOpts.DPI -} - -// GetScriptList returns script list -func (fl *FontLoader) GetScriptList() []string { - fontMap := fl.fontMapData.FMap.FontMap - - arePresent := map[string]bool{ - "Common": false, - "Inherited": false, - } - listSize := len(fontMap) - - for key := range arePresent { - if _, inFontMap := fontMap[key]; inFontMap { - arePresent[key] = true - } else { - listSize++ - } - } - - keys := make([]string, listSize) - - i := 0 - for k := range fontMap { - keys[i] = k - i++ - } - - for script, isPresent := range arePresent { - if !isPresent { - keys[i] = script - i++ - } - } - return keys -} diff --git a/services/thumbnails/pkg/preprocessor/preprocessor.go b/services/thumbnails/pkg/preprocessor/preprocessor.go deleted file mode 100644 index 4c46f3fbec..0000000000 --- a/services/thumbnails/pkg/preprocessor/preprocessor.go +++ /dev/null @@ -1,350 +0,0 @@ -package preprocessor - -import ( - "archive/zip" - "bufio" - "bytes" - "encoding/base64" - "encoding/json" - "image" - "image/draw" - "image/gif" - "io" - "math" - "mime" - "strings" - - "github.com/pkg/errors" - "golang.org/x/image/font" - "golang.org/x/image/font/opentype" - "golang.org/x/image/math/fixed" - - "github.com/dhowden/tag" - - thumbnailerErrors "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" -) - -// FileConverter is the interface for the file converter -type FileConverter interface { - Convert(r io.Reader) (any, error) -} - -// GifDecoder is a converter for the gif file -type GifDecoder struct{} - -// Convert reads the gif file and returns the thumbnail image -func (i GifDecoder) Convert(r io.Reader) (any, error) { - img, err := gif.DecodeAll(r) - if err != nil { - return nil, errors.Wrap(err, `could not decode the image`) - } - return img, nil -} - -// GgsDecoder is a converter for the geogebra slides file -type GgsDecoder struct{ thumbnailpath string } - -// Convert reads the ggs file and returns the thumbnail image -func (g GgsDecoder) Convert(r io.Reader) (any, error) { - var buf bytes.Buffer - _, err := io.Copy(&buf, r) - if err != nil { - return nil, err - } - zipReader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) - if err != nil { - return nil, err - } - for _, file := range zipReader.File { - if file.Name == g.thumbnailpath { - thumbnail, err := file.Open() - if err != nil { - return nil, err - } - converter := ForType("image/png", nil) - if converter == nil { - return nil, thumbnailerErrors.ErrNoConverterForExtractedImageFromGgsFile - } - img, err := converter.Convert(thumbnail) - if err != nil { - return nil, errors.Wrap(err, `could not decode the image`) - } - return img, nil - } - } - return nil, errors.Errorf("%s not found", g.thumbnailpath) -} - -// AudioDecoder is a converter for the audio file -type AudioDecoder struct{} - -// Convert reads the audio file and extracts the thumbnail image from the id3 tag -func (i AudioDecoder) Convert(r io.Reader) (any, error) { - b, err := io.ReadAll(r) - if err != nil { - return nil, err - } - m, err := tag.ReadFrom(bytes.NewReader(b)) - if err != nil { - return nil, err - } - - picture := m.Picture() - if picture == nil { - return nil, thumbnailerErrors.ErrNoImageFromAudioFile - } - - converter := ForType(picture.MIMEType, nil) - if converter == nil { - return nil, thumbnailerErrors.ErrNoConverterForExtractedImageFromAudioFile - } - - return converter.Convert(bytes.NewReader(picture.Data)) -} - -// TxtToImageConverter is a converter for the text file -type TxtToImageConverter struct { - fontLoader *FontLoader -} - -// Convert reads the text file and renders it into a thumbnail image -func (t TxtToImageConverter) Convert(r io.Reader) (any, error) { - img := image.NewRGBA(image.Rect(0, 0, 640, 480)) - - imgBounds := img.Bounds() - draw.Draw(img, imgBounds, image.White, image.Point{}, draw.Src) - - fontSizeAsInt := int(math.Ceil(t.fontLoader.GetFaceOptSize())) - margin := 10 - minX := fixed.I(imgBounds.Min.X + margin) - maxX := fixed.I(imgBounds.Max.X - margin) - maxY := fixed.I(imgBounds.Max.Y - margin) - initialPoint := fixed.P(imgBounds.Min.X+margin, imgBounds.Min.Y+margin+fontSizeAsInt) - canvas := &font.Drawer{ - Dst: img, - Src: image.Black, - Dot: initialPoint, - } - - scriptList := t.fontLoader.GetScriptList() - textAnalyzer := NewTextAnalyzer(scriptList) - taOpts := AnalysisOpts{ - UseMergeMap: true, - MergeMap: DefaultMergeMap, - } - - scanner := bufio.NewScanner(r) -Scan: // Label for the scanner loop, so we can break it easily - for scanner.Scan() { - txt := scanner.Text() - height := fixed.I(fontSizeAsInt) // reset to default height - - textResult := textAnalyzer.AnalyzeString(txt, taOpts) - textResult.MergeCommon(DefaultMergeMap) - - for _, sRange := range textResult.ScriptRanges { - targetFontFace, err := t.fontLoader.LoadFaceForScript(sRange.TargetScript) - if err != nil { - return nil, err - } - - // if the target script is "_unknown" it's expected that the loaded face - // uses the default font - faceHeight := targetFontFace.Face.Metrics().Height - if faceHeight > height { - height = faceHeight - } - - canvas.Face = targetFontFace.Face - initialByte := sRange.Low - for _, sRangeSpace := range sRange.Spaces { - if canvas.Dot.Y > maxY { - break Scan - } - - drawWord(canvas, textResult.Text[initialByte:sRangeSpace], minX, maxX, height, maxY) - initialByte = sRangeSpace - } - - if initialByte <= sRange.High { - // some bytes left to be written - if canvas.Dot.Y > maxY { - break Scan - } - - drawWord(canvas, textResult.Text[initialByte:sRange.High+1], minX, maxX, height, maxY) - } - } - - canvas.Dot.X = minX - canvas.Dot.Y += height.Mul(fixed.Int26_6(1<<6 + 1<<5)) // height * 1.5 - - if canvas.Dot.Y > maxY { - break - } - } - return img, scanner.Err() -} - -// GGPStruct is the layout of a ggp file (which is basically json) -type GGPStruct struct { - Sections []struct { - Cards []struct { - Element struct { - Image struct { - Base64Image string - } - } - } - } -} - -// GgpDecoder is a converter for the geogebra pinboard file -type GgpDecoder struct{} - -// Convert reads the ggp file and returns the first thumbnail image -func (j GgpDecoder) Convert(r io.Reader) (any, error) { - ggp := &GGPStruct{} - err := json.NewDecoder(r).Decode(ggp) - if err != nil { - return nil, err - } - - elem, err := extractBase64ImageFromGGP(ggp) - if err != nil { - return nil, err - } - - b, err := base64.StdEncoding.DecodeString(elem) - if err != nil { - return nil, err - } - - img, _, err := image.Decode(bytes.NewReader(b)) - return img, err -} - -func extractBase64ImageFromGGP(ggp *GGPStruct) (string, error) { - if len(ggp.Sections) < 1 || len(ggp.Sections[0].Cards) < 1 { - return "", errors.New("cant find thumbnail in ggp file") - } - - raw := strings.Split(ggp.Sections[0].Cards[0].Element.Image.Base64Image, "base64,") - if len(raw) < 2 { - return "", errors.New("cant decode ggp thumbnail") - } - - return raw[1], nil -} - -// Draw the word in the canvas. The mixX and maxX defines the drawable range -// (X axis) where the word can be drawn (in case the word is too big and doesn't -// fit in the canvas), and the incY defines the increment in the Y axis if we -// need to draw the word in a new line -// -// Note that the word will likely start with a white space char -func drawWord(canvas *font.Drawer, word string, minX, maxX, incY, maxY fixed.Int26_6) { - // calculate the actual measurement of the string at a given X position - measure := func(s string, dotX fixed.Int26_6) (min, max fixed.Int26_6) { - bbox, _ := canvas.BoundString(s) - return dotX + bbox.Min.X, dotX + bbox.Max.X - } - - // first try to draw the whole word - absMin, absMax := measure(word, canvas.Dot.X) - if absMin >= minX && absMax <= maxX { - canvas.DrawString(word) - return - } - - // try to draw the trimmed word in a new line - trimmed := strings.TrimSpace(word) - oldDot := canvas.Dot - canvas.Dot.X = minX - canvas.Dot.Y += incY - - if canvas.Dot.Y <= maxY { - tMin, tMax := measure(trimmed, canvas.Dot.X) - if tMin >= minX && tMax <= maxX { - canvas.DrawString(trimmed) - return - } - } - - // if the trimmed word is still too big, draw it char by char - canvas.Dot = oldDot - for _, char := range trimmed { - s := string(char) - _, cMax := measure(s, canvas.Dot.X) - - if cMax > maxX { - canvas.Dot.X = minX - canvas.Dot.Y += incY - } - - // stop drawing if we exceed maxY - if canvas.Dot.Y > maxY { - return - } - - // ensure that we don't start drawing before minX - cMin, _ := measure(s, canvas.Dot.X) - if cMin < minX { - canvas.Dot.X += minX - cMin - } - - canvas.DrawString(s) - } -} - -// ForType returns the converter for the specified mimeType -func ForType(mimeType string, opts map[string]any) FileConverter { - // We can ignore the error here because we parse it in IsMimeTypeSupported before and if it fails - // return the service call. So we should only get here when the mimeType parses fine. - mimeType, _, _ = mime.ParseMediaType(mimeType) - switch mimeType { - case "text/plain": - fontFileMap := "" - fontFaceOpts := &opentype.FaceOptions{ - Size: 12, - DPI: 72, - Hinting: font.HintingNone, - } - - if optedFontFileMap, ok := opts["fontFileMap"]; ok { - if stringFontFileMap, ok := optedFontFileMap.(string); ok { - fontFileMap = stringFontFileMap - } - } - - if optedFontFaceOpts, ok := opts["fontFaceOpts"]; ok { - if typedFontFaceOpts, ok := optedFontFaceOpts.(*opentype.FaceOptions); ok { - fontFaceOpts = typedFontFaceOpts - } - } - - fontLoader, err := NewFontLoader(fontFileMap, fontFaceOpts) - if err != nil { - // if it couldn't create the FontLoader with the specified fontFileMap, - // try to use the default font - fontLoader, _ = NewFontLoader("", fontFaceOpts) - } - return TxtToImageConverter{ - fontLoader: fontLoader, - } - case "application/vnd.geogebra.slides": - return GgsDecoder{"_slide0/geogebra_thumbnail.png"} - case "application/vnd.geogebra.pinboard": - return GgpDecoder{} - case "image/gif": - return GifDecoder{} - case "audio/flac": - fallthrough - case "audio/mpeg": - fallthrough - case "audio/ogg": - return AudioDecoder{} - default: - return ImageDecoder{} - } -} diff --git a/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go b/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go deleted file mode 100644 index d946082a06..0000000000 --- a/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build !enable_vips - -package preprocessor - -import ( - "io" - - "github.com/kovidgoyal/imaging" - "github.com/pkg/errors" -) - -// ImageDecoder is a converter for the image file -type ImageDecoder struct{} - -// Convert reads the image file and returns the thumbnail image -func (i ImageDecoder) Convert(r io.Reader) (any, error) { - img, err := imaging.Decode(r, imaging.AutoOrientation(true)) - if err != nil { - return nil, errors.Wrap(err, `could not decode the image`) - } - return img, nil -} diff --git a/services/thumbnails/pkg/preprocessor/preprocessor_test.go b/services/thumbnails/pkg/preprocessor/preprocessor_test.go deleted file mode 100644 index a76a0512c0..0000000000 --- a/services/thumbnails/pkg/preprocessor/preprocessor_test.go +++ /dev/null @@ -1,197 +0,0 @@ -package preprocessor - -import ( - "bytes" - "io" - "os" - "testing" - - "golang.org/x/image/font" - "golang.org/x/image/font/opentype" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestImageDecoder(t *testing.T) { - - RegisterFailHandler(Fail) - RunSpecs(t, "ImageDecoder Suite") -} - -var _ = Describe("ImageDecoder", func() { - Describe("ImageDecoder", func() { - var fileReader io.Reader - BeforeEach(func() { - fileContent, err := os.ReadFile("test_assets/noise.png") - if err != nil { - panic(err) - } - fileReader = bytes.NewReader(fileContent) - }) - - It("should decode an image", func() { - decoder := ImageDecoder{} - img, err := decoder.Convert(fileReader) - Expect(err).ToNot(HaveOccurred()) - Expect(img).ToNot(BeNil()) - }) - - It("should return an error if the image is invalid", func() { - decoder := ImageDecoder{} - img, err := decoder.Convert(bytes.NewReader([]byte("not an image"))) - Expect(err).To(HaveOccurred()) - Expect(img).To(BeNil()) - }) - }) - - Describe("GifDecoder", func() { - var fileReader io.Reader - BeforeEach(func() { - fileContent, err := os.ReadFile("test_assets/noise.gif") - if err != nil { - panic(err) - } - fileReader = bytes.NewReader(fileContent) - }) - - It("should decode a gif", func() { - decoder := GifDecoder{} - img, err := decoder.Convert(fileReader) - Expect(err).ToNot(HaveOccurred()) - Expect(img).ToNot(BeNil()) - }) - - It("should return an error if the gif is invalid", func() { - decoder := GifDecoder{} - img, err := decoder.Convert(bytes.NewReader([]byte("not a gif"))) - Expect(err).To(HaveOccurred()) - Expect(img).To(BeNil()) - }) - }) - - Describe("GgsDecoder", func() { - var fileReader io.Reader - BeforeEach(func() { - fileContent, err := os.ReadFile("test_assets/ggs_test.ggs") - if err != nil { - panic(err) - } - fileReader = bytes.NewReader(fileContent) - }) - - It("should decode a ggs", func() { - decoder := GgsDecoder{"_slide0/geogebra_thumbnail.png"} - img, err := decoder.Convert(fileReader) - Expect(err).ToNot(HaveOccurred()) - Expect(img).ToNot(BeNil()) - }) - - It("should return an error if the ggs is invalid", func() { - decoder := GgsDecoder{"_slide0/geogebra_thumbnail.png"} - img, err := decoder.Convert(bytes.NewReader([]byte("not a ggs"))) - Expect(err).To(HaveOccurred()) - Expect(img).To(BeNil()) - }) - }) - - Describe("should decode audio", func() { - var fileReader io.Reader - It("should decode an audio", func() { - fileContent, err := os.ReadFile("test_assets/empty.mp3") - if err != nil { - panic(err) - } - fileReader = bytes.NewReader(fileContent) - decoder := AudioDecoder{} - img, err := decoder.Convert(fileReader) - Expect(err).ToNot(HaveOccurred()) - Expect(img).ToNot(BeNil()) - }) - It("should decode an audio", func() { - fileContent, err := os.ReadFile("test_assets/empty_no_image.mp3") - if err != nil { - panic(err) - } - fileReader = bytes.NewReader(fileContent) - decoder := AudioDecoder{} - img, err := decoder.Convert(fileReader) - Expect(err).To(HaveOccurred()) - Expect(img).To(BeNil()) - }) - It("should return an error if the audio is invalid", func() { - decoder := AudioDecoder{} - img, err := decoder.Convert(bytes.NewReader([]byte("not an audio"))) - Expect(err).To(HaveOccurred()) - Expect(img).To(BeNil()) - }) - }) - - Describe("should decode text", func() { - var decoder TxtToImageConverter - BeforeEach(func() { - fontFaceOpts := &opentype.FaceOptions{ - Size: 12, - DPI: 72, - Hinting: font.HintingNone, - } - - fontLoader, err := NewFontLoader("", fontFaceOpts) - if err != nil { - fontLoader, _ = NewFontLoader("", fontFaceOpts) - } - decoder = TxtToImageConverter{ - fontLoader: fontLoader, - } - }) - It("should decode a text", func() { - img, err := decoder.Convert(bytes.NewReader([]byte("This is a test text"))) - Expect(err).ToNot(HaveOccurred()) - Expect(img).ToNot(BeNil()) - }) - - It("fails if the font is missing", func() { - decoder.fontLoader.fontMapData = &FontMapData{ - FMap: &FontMap{ - DefaultFont: "/some/unknown/font.otf", - }, - } - img, err := decoder.Convert(bytes.NewReader([]byte("This is a test text"))) - Expect(err).To(HaveOccurred()) - Expect(img).To(BeNil()) - }) - }) - - Describe("test ForType", func() { - It("should return an ImageDecoder for image types", func() { - decoder := ForType("image/png", nil) - Expect(decoder).To(BeAssignableToTypeOf(ImageDecoder{})) - }) - - It("should return an GifDecoder for gif types", func() { - decoder := ForType("image/gif", nil) - Expect(decoder).To(BeAssignableToTypeOf(GifDecoder{})) - }) - - It("should return an GgsDecoder for ggs types", func() { - decoder := ForType("application/vnd.geogebra.ggs", nil) - // This will not return the expected ggsDecoder, but an ImageDecoder since ggs contains an embedded png. - Expect(decoder).To(BeAssignableToTypeOf(ImageDecoder{})) - }) - - It("should return an AudioDecoder for audio types", func() { - decoder := ForType("audio/mpeg", nil) - Expect(decoder).To(BeAssignableToTypeOf(AudioDecoder{})) - }) - - It("should return an TxtToImageConverter for text types", func() { - decoder := ForType("text/plain", nil) - Expect(decoder).To(BeAssignableToTypeOf(TxtToImageConverter{})) - }) - - It("should return an ImageDecoder for unknown types", func() { - decoder := ForType("unknown", nil) - Expect(decoder).To(BeAssignableToTypeOf(ImageDecoder{})) - }) - }) -}) diff --git a/services/thumbnails/pkg/preprocessor/preprocessor_vips.go b/services/thumbnails/pkg/preprocessor/preprocessor_vips.go deleted file mode 100644 index 087bafb1b7..0000000000 --- a/services/thumbnails/pkg/preprocessor/preprocessor_vips.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build enable_vips - -package preprocessor - -import ( - "io" - - "github.com/davidbyttow/govips/v2/vips" -) - -func init() { - vips.LoggingSettings(nil, vips.LogLevelError) -} - -type ImageDecoder struct{} - -func (v ImageDecoder) Convert(r io.Reader) (interface{}, error) { - img, err := vips.NewImageFromReader(r) - return img, err -} diff --git a/services/thumbnails/pkg/preprocessor/test_assets/empty.mp3 b/services/thumbnails/pkg/preprocessor/test_assets/empty.mp3 deleted file mode 100644 index ea8a247a77b757ca25628013776de1155246c842..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199530 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 bj{kR={}1mA|8hZJ?feUle>?M^;`YA)=lm>Q diff --git a/services/thumbnails/pkg/preprocessor/test_assets/empty_no_image.mp3 b/services/thumbnails/pkg/preprocessor/test_assets/empty_no_image.mp3 deleted file mode 100644 index f5bd92f88d17566216025580c48a5e26cdc2067b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4386 zcmeZtF=k-^0i}@OU{@f`$H2hslUSB!W~gVbXJ}vmmV^-he>)sN;zF37d1?7T7C#Vc zFfa&wV-OG!6O)ydRaVy2)YR8EH8r)ic64;~@bL5V3kiveib_aGOH0eiDJm+etgNrE zZ*A@F?w&Yt`t<2@=Pp^YWYww-8#Zj)wr9_tLx)bBIC1XW)vH%;-+uV;;q&Kj-@g6) z`RC7{|Nnz*2D&jB=te_J5C;ZSSZg3mfp3~W@|S_x|9=Y{VPHPMz_(^b diff --git a/services/thumbnails/pkg/preprocessor/test_assets/ggs_test.ggs b/services/thumbnails/pkg/preprocessor/test_assets/ggs_test.ggs deleted file mode 100644 index c0f2a0aa38a776814700af3038b87bcc40062f80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7486 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 diff --git a/services/thumbnails/pkg/preprocessor/test_assets/noise.gif b/services/thumbnails/pkg/preprocessor/test_assets/noise.gif deleted file mode 100644 index 0b1dacf535dce109ad9fbcb726b0402a9d0b99a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84386 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≀ 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

5i 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 diff --git a/services/thumbnails/pkg/preprocessor/textanalyzer.go b/services/thumbnails/pkg/preprocessor/textanalyzer.go deleted file mode 100644 index 8dddfa642a..0000000000 --- a/services/thumbnails/pkg/preprocessor/textanalyzer.go +++ /dev/null @@ -1,309 +0,0 @@ -package preprocessor - -import ( - "unicode" - "unicode/utf8" -) - -// Default list of scripts to be analyzed within the string. -// -// Scripts that aren't present in the list will be considered as part -// of the last "known" script. For example, if "Avestan" script (which isn't -// present) is preceeded by "Arabic" script, then the "Avestan" script will -// be considered as "Arabic" -// -// Punctuation symbols are usually considered part of the "Common" script -var DefaultScripts = []string{ - "Arabic", - "Common", - "Devanagari", - "Han", - "Hangul", - "Hiragana", - "Inherited", - "Katakana", - "Latin", -} - -// Convenient map[string]map[string]string type used to merge multiple -// scripts into one. This is mainly used for japanese language which uses -// "Han", "Hiragana" and "Katakana" scripts. -// -// The map contains the expected previous script as first key, the expected -// current script as second key, and the resulting script (if both keys -// match) as value -type MergeMap map[string]map[string]string - -// The default mergeMap containing info for the japanese scripts -var DefaultMergeMap = MergeMap{ - "Han": map[string]string{ - "Hiragana": "Hiragana", - "Katakana": "Katakana", - }, - "Hiragana": map[string]string{ - "Han": "Hiragana", - "Katakana": "Hiragana", - }, - "Katakana": map[string]string{ - "Han": "Katakana", - "Hiragana": "Hiragana", - }, -} - -// Analysis options. -type AnalysisOpts struct { - UseMergeMap bool - MergeMap MergeMap -} - -// A script range. The range should be attached to a string which could contain -// multiple scripts. The "TargetScript" will go from bytes "Low" to "High" -// (both inclusive), and contains a "RuneCount" number of runes or chars -// (mostly for debugging purposes). -// The Space contains the bytes (inside the range) that are considered as -// white space. -type ScriptRange struct { - Low, High int - Spaces []int - TargetScript string - RuneCount int -} - -// The result of a text analysis. It contains the analyzed text, a list of -// script ranges (see the ScriptRange type) and a map containing how many -// runes have been detected for a particular script. -type TextAnalysis struct { - ScriptRanges []ScriptRange - RuneCount map[string]int - Text string -} - -// The TextAnalyzer object contains private members. It should be created via -// "NewTextAnalyzer" function. -type TextAnalyzer struct { - scripts map[string]*unicode.RangeTable - scriptListCache []string -} - -// Create a new TextAnalyzer. A list of scripts must be provided. -// You can use the "DefaultScripts" variable for a default list, -// although it doesn't contain all the available scripts. -// See the unicode.Scripts variable (in the unicode package) for a -// full list. Note that using invalid scripts will cause an undefined -// behavior -func NewTextAnalyzer(scriptList []string) TextAnalyzer { - scriptRanges := make(map[string]*unicode.RangeTable, len(scriptList)) - for _, script := range scriptList { - scriptRanges[script] = unicode.Scripts[script] - } - return TextAnalyzer{ - scripts: scriptRanges, - scriptListCache: scriptList, - } -} - -// Analyze the target string using the specified options. -// A TextAnalysis will be returned with the result of the analysis. -func (ta *TextAnalyzer) AnalyzeString(word string, opts AnalysisOpts) TextAnalysis { - analysis := TextAnalysis{ - ScriptRanges: []ScriptRange{}, - RuneCount: make(map[string]int), - Text: word, - } - - if len(word) < 1 { - return analysis - } - - firstRune, runeLen := utf8.DecodeRuneInString(word) - - lastRange := &ScriptRange{ - Low: 0, - Spaces: make([]int, 0), - TargetScript: ta.chooseScriptFor(firstRune), - } - firstRuneIsWhiteSpace := unicode.Is(unicode.White_Space, firstRune) - if firstRuneIsWhiteSpace { - lastRange.Spaces = append(lastRange.Spaces, 0) - } - - runeCount := 1 - for wordIndex, char := range word[runeLen:] { - wordIndex += runeLen // shifted from the original string - script := ta.chooseScriptFor(char) - - isWhiteSpace := unicode.Is(unicode.White_Space, char) - if script != lastRange.TargetScript { - if mapScript, isOk := ta.getMergeMapValue(opts, lastRange.TargetScript, script); isOk { - lastRange.TargetScript = mapScript - if isWhiteSpace { - // TODO: Check if this is dead code. - // whitespace should be part of the "Common" script, and the Common - // script shouldn't be part of a mergeMap - lastRange.Spaces = append(lastRange.Spaces, wordIndex) - } - runeCount++ - continue - } - - lastRange.High = wordIndex - 1 - lastRange.RuneCount = runeCount - analysis.ScriptRanges = append(analysis.ScriptRanges, *lastRange) - if _, exists := analysis.RuneCount[lastRange.TargetScript]; !exists { - analysis.RuneCount[lastRange.TargetScript] = 0 - } - analysis.RuneCount[lastRange.TargetScript] += runeCount - lastRange = &ScriptRange{ - Low: wordIndex, - Spaces: make([]int, 0), - TargetScript: script, - } - runeCount = 0 - } - runeCount++ - if isWhiteSpace { - lastRange.Spaces = append(lastRange.Spaces, wordIndex) - } - } - - // close the last range - lastRange.High = len(word) - 1 - lastRange.RuneCount = runeCount - analysis.RuneCount[lastRange.TargetScript] += runeCount - analysis.ScriptRanges = append(analysis.ScriptRanges, *lastRange) - - return analysis -} - -func (ta *TextAnalyzer) chooseScriptFor(char rune) string { - script := "_unknown" - for scriptIndex, scriptFound := range ta.scriptListCache { - // if we can't match with a known script, do nothing and jump to the next char - if unicode.Is(ta.scripts[scriptFound], char) { - if scriptIndex > 3 { - // we might expect more chars with the same script - // so move the script first to match it faster next time - ta.reorderScriptList(scriptFound) - } - return scriptFound - } - } - return script -} - -// Reorder the scriptListCache in the TextAnalyzer in order to speed up -// the next script searches. A "Latin" script is expected to be surrounded -// by "Latin" chars, although "Common" script chars might be present too -func (ta *TextAnalyzer) reorderScriptList(matchedScript string) { - for index, script := range ta.scriptListCache { - if script == matchedScript { - if index != 0 { - // move the script to the first position for a faster matching - newList := append([]string{script}, ta.scriptListCache[:index]...) - ta.scriptListCache = append(newList, ta.scriptListCache[index+1:]...) - } - // if index == 0 there is nothing to do: the element is already the first - break - } - } -} - -// Get the value from the merge map based on the previous and current scripts. -// The information about using the merge map and the actual merge map will be -// gotten from the AnalysisOpts passed as parameter -func (ta *TextAnalyzer) getMergeMapValue(opts AnalysisOpts, previous, current string) (string, bool) { - if opts.UseMergeMap { - // This option mainly target japanese chars; multiple scripts can be used - // in the same piece of text (Han, Hiragana and Katakana) - // Instead of starting a new range, adjust the target script of the last range - if expCurrent, currentOk := opts.MergeMap[previous]; currentOk { - if expFinal, finalOk := expCurrent[current]; finalOk { - return expFinal, finalOk - } - } - } - return "", false -} - -// Change the "Common" script to the one used in the previous script range. -// The ranges will be readjusted and merged if they're adjacent. -// This naive approach should be good enough for normal use cases -// -// The MergeMap is needed in case of the japanese language: the ranges -// "Han"-"Common"-"Katakana" might be replaced to "Han"-"Hiragana"-"Katakana" -// However, the ranges should be merged together into a big "Hiragana" range. -// If the MergeMap isn't needed, use an empty one -func (tr *TextAnalysis) MergeCommon(mergeMap MergeMap) { - var finalRanges []ScriptRange - - if len(tr.ScriptRanges) < 1 { - // no ranges -> nothing to do - return - } - - previousRange := &ScriptRange{} - *previousRange = tr.ScriptRanges[0] - for _, sRange := range tr.ScriptRanges[1:] { - if previousRange.TargetScript == sRange.TargetScript { - previousRange.High = sRange.High - previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) - previousRange.RuneCount += sRange.RuneCount - } else if sRange.TargetScript == "Common" || sRange.TargetScript == "Inherited" { - // new range will be absorbed into the previous one - previousRange.High = sRange.High - previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) - previousRange.RuneCount += sRange.RuneCount - tr.RuneCount[previousRange.TargetScript] += sRange.RuneCount - tr.RuneCount[sRange.TargetScript] -= sRange.RuneCount - } else if previousRange.TargetScript == "Common" || previousRange.TargetScript == "Inherited" { - // might happen if the text starts with a Common script - previousRange.High = sRange.High - previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) - tr.RuneCount[sRange.TargetScript] += previousRange.RuneCount - tr.RuneCount[previousRange.TargetScript] -= previousRange.RuneCount - previousRange.RuneCount += sRange.RuneCount - previousRange.TargetScript = sRange.TargetScript - } else { - if mapScript, isOk := tr.getMergeMapValue(mergeMap, previousRange.TargetScript, sRange.TargetScript); isOk { - if sRange.TargetScript == mapScript { - // the previous range has changed the target script - tr.RuneCount[previousRange.TargetScript] -= previousRange.RuneCount - tr.RuneCount[sRange.TargetScript] += previousRange.RuneCount - } else { - // new range has been absorbed - tr.RuneCount[sRange.TargetScript] -= sRange.RuneCount - tr.RuneCount[previousRange.TargetScript] += sRange.RuneCount - } - previousRange.TargetScript = mapScript - previousRange.High = sRange.High - previousRange.Spaces = append(previousRange.Spaces, sRange.Spaces...) - previousRange.RuneCount += sRange.RuneCount - continue - } - finalRanges = append(finalRanges, *previousRange) - *previousRange = sRange - } - } - - finalRanges = append(finalRanges, *previousRange) - tr.ScriptRanges = finalRanges - delete(tr.RuneCount, "Common") - delete(tr.RuneCount, "Inherited") - for index, rCount := range tr.RuneCount { - if rCount == 0 { - delete(tr.RuneCount, index) - } - } -} - -func (tr *TextAnalysis) getMergeMapValue(mMap MergeMap, previous, current string) (string, bool) { - // This option mainly target japanese chars; multiple scripts can be used - // in the same piece of text (Han, Hiragana and Katakana) - // Instead of starting a new range, adjust the target script of the last range - if expCurrent, currentOk := mMap[previous]; currentOk { - if expFinal, finalOk := expCurrent[current]; finalOk { - return expFinal, finalOk - } - } - return "", false -} diff --git a/services/thumbnails/pkg/preprocessor/textanalyzer_test.go b/services/thumbnails/pkg/preprocessor/textanalyzer_test.go deleted file mode 100644 index 3efeea930c..0000000000 --- a/services/thumbnails/pkg/preprocessor/textanalyzer_test.go +++ /dev/null @@ -1,614 +0,0 @@ -package preprocessor - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - inputs = [18]string{ - "basic latin", - "trailing tab ", - "Small text. \"$\", \"£\" and \"¥\" are currencies.", - "latin with 🖖", - "기본 한국어", - "基本的な日本語", - "ウーロン茶", - "私はエンジニアです", - "ティー私はエンジニアです", - "私はエンジニアです ティー", - "आधारभूत देवनागरी", - "mixed 언어 传入 🚀!", - "/k͜p/", - // ä and a + ¨ - "ä ä", - "базовый русский", // cyrillic script isn't part of our default - "latin русский", // latin + cyrillic (cyrillic not supported) - " space justified ", - "", - } -) - -func TestAnalyzeString(t *testing.T) { - defaultOpts := AnalysisOpts{ - UseMergeMap: true, - MergeMap: DefaultMergeMap, - } - - tables := []struct { - input string - opts AnalysisOpts - eOut TextAnalysis - }{ - { - input: inputs[0], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 10, Spaces: []int{5}, TargetScript: "Latin", RuneCount: 11}, - }, - RuneCount: map[string]int{ - "Latin": 11, - }, - Text: inputs[0], - }, - }, - { - input: inputs[1], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 12, Spaces: []int{8, 12}, TargetScript: "Latin", RuneCount: 13}, - }, - RuneCount: map[string]int{ - "Latin": 13, - }, - Text: inputs[1], - }, - }, - { - input: inputs[2], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 45, Spaces: []int{5, 11, 16, 21, 25, 30, 34}, TargetScript: "Latin", RuneCount: 44}, - }, - RuneCount: map[string]int{ - "Latin": 44, - }, - Text: inputs[2], - }, - }, - { - input: inputs[3], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 14, Spaces: []int{5, 10}, TargetScript: "Latin", RuneCount: 12}, - }, - RuneCount: map[string]int{ - "Latin": 12, - }, - Text: inputs[3], - }, - }, - { - input: inputs[4], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 15, Spaces: []int{6}, TargetScript: "Hangul", RuneCount: 6}, - }, - RuneCount: map[string]int{ - "Hangul": 6, - }, - Text: inputs[4], - }, - }, - { - input: inputs[5], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 20, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 7}, - }, - RuneCount: map[string]int{ - "Hiragana": 7, - }, - Text: inputs[5], - }, - }, - { - input: inputs[6], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 14, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, - }, - RuneCount: map[string]int{ - "Katakana": 5, - }, - Text: inputs[6], - }, - }, - { - input: inputs[7], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 26, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 9}, - }, - RuneCount: map[string]int{ - "Hiragana": 9, - }, - Text: inputs[7], - }, - }, - { - input: inputs[8], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 35, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 12}, - }, - RuneCount: map[string]int{ - "Hiragana": 12, - }, - Text: inputs[8], - }, - }, - { - input: inputs[9], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 36, Spaces: []int{27}, TargetScript: "Hiragana", RuneCount: 13}, - }, - RuneCount: map[string]int{ - "Hiragana": 13, - }, - Text: inputs[9], - }, - }, - { - input: inputs[10], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 45, Spaces: []int{21}, TargetScript: "Devanagari", RuneCount: 16}, - }, - RuneCount: map[string]int{ - "Devanagari": 16, - }, - Text: inputs[10], - }, - }, - { - input: inputs[11], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 5, Spaces: []int{5}, TargetScript: "Latin", RuneCount: 6}, - {Low: 6, High: 12, Spaces: []int{12}, TargetScript: "Hangul", RuneCount: 3}, - {Low: 13, High: 24, Spaces: []int{19}, TargetScript: "Han", RuneCount: 5}, // 🚀 and ! are "Common" script and will be merged with "Han" - }, - RuneCount: map[string]int{ - "Latin": 6, - "Hangul": 3, - "Han": 5, - }, - Text: inputs[11], - }, - }, - { - input: inputs[12], - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 5, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - }, - RuneCount: map[string]int{ - "Latin": 5, - }, - Text: inputs[12], - }, - }, - { - input: inputs[13], // ä and a + ¨ - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 5, Spaces: []int{2}, TargetScript: "Latin", RuneCount: 4}, - }, - RuneCount: map[string]int{ - "Latin": 4, - }, - Text: inputs[13], - }, - }, - { - input: inputs[14], // cyrillic script isn't part of our default - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 28, Spaces: []int{14}, TargetScript: "_unknown", RuneCount: 15}, - }, - RuneCount: map[string]int{ - "_unknown": 15, - }, - Text: inputs[14], - }, - }, - { - input: inputs[15], // latin + cyrillic (cyrillic script isn't part of our default) - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 5, Spaces: []int{5}, TargetScript: "Latin", RuneCount: 6}, - {Low: 6, High: 19, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, - }, - RuneCount: map[string]int{ - "Latin": 6, - "_unknown": 7, - }, - Text: inputs[15], - }, - }, - { - input: inputs[16], // latin + cyrillic (cyrillic script isn't part of our default) - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 16, Spaces: []int{0, 6, 16}, TargetScript: "Latin", RuneCount: 17}, - }, - RuneCount: map[string]int{ - "Latin": 17, - }, - Text: inputs[16], - }, - }, - { - input: inputs[17], // latin + cyrillic (cyrillic script isn't part of our default) - opts: defaultOpts, - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{}, - RuneCount: map[string]int{}, - Text: inputs[17], - }, - }, - } - - for _, table := range tables { - testname := fmt.Sprintf("Analyzing \"%s\" string", table.input) - t.Run(testname, func(t *testing.T) { - ta := NewTextAnalyzer(DefaultScripts) - result := ta.AnalyzeString(table.input, table.opts) - if table.opts.UseMergeMap { - result.MergeCommon(table.opts.MergeMap) - } else { - result.MergeCommon(MergeMap{}) - } - assert.Equal(t, table.eOut, result) - }) - } -} - -func TestAnalyzeStringRaw(t *testing.T) { - tables := []struct { - input string - eOut TextAnalysis - }{ - { - input: inputs[0], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, - {Low: 6, High: 10, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - }, - RuneCount: map[string]int{ - "Latin": 10, - "Common": 1, - }, - Text: inputs[0], - }, - }, - { - input: inputs[1], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 7, Spaces: []int{}, TargetScript: "Latin", RuneCount: 8}, - {Low: 8, High: 8, Spaces: []int{8}, TargetScript: "Common", RuneCount: 1}, - {Low: 9, High: 11, Spaces: []int{}, TargetScript: "Latin", RuneCount: 3}, - {Low: 12, High: 12, Spaces: []int{12}, TargetScript: "Common", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Latin": 11, - "Common": 2, - }, - Text: inputs[1], - }, - }, - { - input: inputs[2], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, - {Low: 6, High: 9, Spaces: []int{}, TargetScript: "Latin", RuneCount: 4}, - {Low: 10, High: 21, Spaces: []int{11, 16, 21}, TargetScript: "Common", RuneCount: 11}, // £ takes 2 bytes - {Low: 22, High: 24, Spaces: []int{}, TargetScript: "Latin", RuneCount: 3}, - {Low: 25, High: 30, Spaces: []int{25, 30}, TargetScript: "Common", RuneCount: 5}, // ¥ takes 2 bytes - {Low: 31, High: 33, Spaces: []int{}, TargetScript: "Latin", RuneCount: 3}, - {Low: 34, High: 34, Spaces: []int{34}, TargetScript: "Common", RuneCount: 1}, - {Low: 35, High: 44, Spaces: []int{}, TargetScript: "Latin", RuneCount: 10}, - {Low: 45, High: 45, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Latin": 25, - "Common": 19, - }, - Text: inputs[2], - }, - }, - { - input: inputs[3], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, - {Low: 6, High: 9, Spaces: []int{}, TargetScript: "Latin", RuneCount: 4}, - {Low: 10, High: 14, Spaces: []int{10}, TargetScript: "Common", RuneCount: 2}, - }, - RuneCount: map[string]int{ - "Latin": 9, - "Common": 3, - }, - Text: inputs[3], - }, - }, - { - input: inputs[4], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 5, Spaces: []int{}, TargetScript: "Hangul", RuneCount: 2}, - {Low: 6, High: 6, Spaces: []int{6}, TargetScript: "Common", RuneCount: 1}, - {Low: 7, High: 15, Spaces: []int{}, TargetScript: "Hangul", RuneCount: 3}, - }, - RuneCount: map[string]int{ - "Hangul": 5, - "Common": 1, - }, - Text: inputs[4], - }, - }, - { - input: inputs[5], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 8, Spaces: []int{}, TargetScript: "Han", RuneCount: 3}, - {Low: 9, High: 11, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, - {Low: 12, High: 20, Spaces: []int{}, TargetScript: "Han", RuneCount: 3}, - }, - RuneCount: map[string]int{ - "Hiragana": 1, - "Han": 6, - }, - Text: inputs[5], - }, - }, - { - input: inputs[6], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 2, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 1}, - {Low: 3, High: 5, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, // ー U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK) seems to be counted as Common - {Low: 6, High: 11, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 2}, - {Low: 12, High: 14, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Katakana": 3, - "Common": 1, - "Han": 1, - }, - Text: inputs[6], - }, - }, - { - input: inputs[7], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 2, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, - {Low: 3, High: 5, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, - {Low: 6, High: 20, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, - {Low: 21, High: 26, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 2}, - }, - RuneCount: map[string]int{ - "Han": 1, - "Hiragana": 3, - "Katakana": 5, - }, - Text: inputs[7], - }, - }, - { - input: inputs[8], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 5, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 2}, - {Low: 6, High: 8, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, - {Low: 9, High: 11, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, - {Low: 12, High: 14, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, - {Low: 15, High: 29, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, - {Low: 30, High: 35, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 2}, - }, - RuneCount: map[string]int{ - "Han": 1, - "Hiragana": 3, - "Katakana": 7, - "Common": 1, - }, - Text: inputs[8], - }, - }, - { - input: inputs[9], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 2, Spaces: []int{}, TargetScript: "Han", RuneCount: 1}, - {Low: 3, High: 5, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 1}, - {Low: 6, High: 20, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 5}, - {Low: 21, High: 26, Spaces: []int{}, TargetScript: "Hiragana", RuneCount: 2}, - {Low: 27, High: 27, Spaces: []int{27}, TargetScript: "Common", RuneCount: 1}, - {Low: 28, High: 33, Spaces: []int{}, TargetScript: "Katakana", RuneCount: 2}, - {Low: 34, High: 36, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Han": 1, - "Hiragana": 3, - "Katakana": 7, - "Common": 2, - }, - Text: inputs[9], - }, - }, - { - input: inputs[10], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 20, Spaces: []int{}, TargetScript: "Devanagari", RuneCount: 7}, - {Low: 21, High: 21, Spaces: []int{21}, TargetScript: "Common", RuneCount: 1}, - {Low: 22, High: 45, Spaces: []int{}, TargetScript: "Devanagari", RuneCount: 8}, - }, - RuneCount: map[string]int{ - "Devanagari": 15, - "Common": 1, - }, - Text: inputs[10], - }, - }, - { - input: inputs[11], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, - {Low: 6, High: 11, Spaces: []int{}, TargetScript: "Hangul", RuneCount: 2}, - {Low: 12, High: 12, Spaces: []int{12}, TargetScript: "Common", RuneCount: 1}, - {Low: 13, High: 18, Spaces: []int{}, TargetScript: "Han", RuneCount: 2}, - {Low: 19, High: 24, Spaces: []int{19}, TargetScript: "Common", RuneCount: 3}, - }, - RuneCount: map[string]int{ - "Latin": 5, - "Hangul": 2, - "Han": 2, - "Common": 5, - }, - Text: inputs[11], - }, - }, - { - input: inputs[12], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 0, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, - {Low: 1, High: 1, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, - {Low: 2, High: 3, Spaces: []int{}, TargetScript: "Inherited", RuneCount: 1}, - {Low: 4, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, - {Low: 5, High: 5, Spaces: []int{}, TargetScript: "Common", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Latin": 2, - "Common": 2, - "Inherited": 1, - }, - Text: inputs[12], - }, - }, - { - input: inputs[13], // ä and a + ¨ - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 1, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, - {Low: 2, High: 2, Spaces: []int{2}, TargetScript: "Common", RuneCount: 1}, - {Low: 3, High: 3, Spaces: []int{}, TargetScript: "Latin", RuneCount: 1}, - {Low: 4, High: 5, Spaces: []int{}, TargetScript: "Inherited", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Latin": 2, - "Common": 1, - "Inherited": 1, - }, - Text: inputs[13], - }, - }, - { - input: inputs[14], // cyrillic script isn't part of our default - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 13, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, - {Low: 14, High: 14, Spaces: []int{14}, TargetScript: "Common", RuneCount: 1}, - {Low: 15, High: 28, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, - }, - RuneCount: map[string]int{ - "_unknown": 14, - "Common": 1, - }, - Text: inputs[14], - }, - }, - { - input: inputs[15], // latin + cyrillic (cyrillic script isn't part of our default) - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 4, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - {Low: 5, High: 5, Spaces: []int{5}, TargetScript: "Common", RuneCount: 1}, - {Low: 6, High: 19, Spaces: []int{}, TargetScript: "_unknown", RuneCount: 7}, - }, - RuneCount: map[string]int{ - "Latin": 5, - "Common": 1, - "_unknown": 7, - }, - Text: inputs[15], - }, - }, - { - input: inputs[16], - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{ - {Low: 0, High: 0, Spaces: []int{0}, TargetScript: "Common", RuneCount: 1}, - {Low: 1, High: 5, Spaces: []int{}, TargetScript: "Latin", RuneCount: 5}, - {Low: 6, High: 6, Spaces: []int{6}, TargetScript: "Common", RuneCount: 1}, - {Low: 7, High: 15, Spaces: []int{}, TargetScript: "Latin", RuneCount: 9}, - {Low: 16, High: 16, Spaces: []int{16}, TargetScript: "Common", RuneCount: 1}, - }, - RuneCount: map[string]int{ - "Latin": 14, - "Common": 3, - }, - Text: inputs[16], - }, - }, - { - input: inputs[17], // empty string - eOut: TextAnalysis{ - ScriptRanges: []ScriptRange{}, - RuneCount: map[string]int{}, - Text: inputs[17], - }, - }, - } - - for _, table := range tables { - testname := fmt.Sprintf("Raw-Analyzing \"%s\" string", table.input) - t.Run(testname, func(t *testing.T) { - ta := NewTextAnalyzer(DefaultScripts) - result := ta.AnalyzeString(table.input, AnalysisOpts{}) - - assert.Equal(t, table.eOut, result) - }) - } -} diff --git a/services/thumbnails/pkg/server/debug/server.go b/services/thumbnails/pkg/server/debug/server.go index ee3e29438d..63fa005ad2 100644 --- a/services/thumbnails/pkg/server/debug/server.go +++ b/services/thumbnails/pkg/server/debug/server.go @@ -16,8 +16,7 @@ func Server(opts ...Option) (*http.Server, error) { checkHandler := handlers.NewCheckHandler( handlers.NewCheckHandlerConfiguration(). WithLogger(options.Logger). - WithCheck("web reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr)). - WithCheck("grpc reachability", checks.NewGRPCCheck(options.Config.GRPC.Addr)), + WithCheck("web reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr)), ) return debug.NewService( diff --git a/services/thumbnails/pkg/server/grpc/option.go b/services/thumbnails/pkg/server/grpc/option.go deleted file mode 100644 index 4197aa80d5..0000000000 --- a/services/thumbnails/pkg/server/grpc/option.go +++ /dev/null @@ -1,100 +0,0 @@ -package grpc - -import ( - "context" - - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/metrics" - "go.opentelemetry.io/otel/trace" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Name string - Address string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Namespace string - TraceProvider trace.TracerProvider - MaxConcurrentRequests int -} - -// newOptions initializes the available default options. -func newOptions(opts ...Option) Options { - opt := Options{} - - for _, o := range opts { - o(&opt) - } - - return opt -} - -// Logger provides a function to set the logger option. -func Logger(val log.Logger) Option { - return func(o *Options) { - o.Logger = val - } -} - -// Name provides a name for the service. -func Name(val string) Option { - return func(o *Options) { - o.Name = val - } -} - -// Address provides an address for the service. -func Address(val string) Option { - return func(o *Options) { - o.Address = val - } -} - -// Context provides a function to set the context option. -func Context(val context.Context) Option { - return func(o *Options) { - o.Context = val - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Namespace provides a function to set the namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} - -// TraceProvider provides a function to set the TraceProvider option -func TraceProvider(val trace.TracerProvider) Option { - return func(o *Options) { - o.TraceProvider = val - } -} - -// MaxConcurrentRequests provides a function to set the MaxConcurrentRequests option. -func MaxConcurrentRequests(val int) Option { - return func(o *Options) { - o.MaxConcurrentRequests = val - } -} diff --git a/services/thumbnails/pkg/server/grpc/server.go b/services/thumbnails/pkg/server/grpc/server.go deleted file mode 100644 index 9fb5c489f9..0000000000 --- a/services/thumbnails/pkg/server/grpc/server.go +++ /dev/null @@ -1,92 +0,0 @@ -package grpc - -import ( - "github.com/opencloud-eu/opencloud/pkg/registry" - "github.com/opencloud-eu/opencloud/pkg/service/grpc" - "github.com/opencloud-eu/opencloud/pkg/service/grpc/handler/ratelimiter" - "github.com/opencloud-eu/opencloud/pkg/version" - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" - svc "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/service/grpc/v0" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/service/grpc/v0/decorators" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/imgsource" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" - "github.com/opencloud-eu/reva/v2/pkg/bytesize" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" -) - -// NewService initializes the grpc service and server. -func NewService(opts ...Option) grpc.Service { - options := newOptions(opts...) - - service, err := grpc.NewServiceWithClient( - options.Config.GrpcClient, - grpc.TLSEnabled(options.Config.GRPC.TLS.Enabled), - grpc.TLSCert( - options.Config.GRPC.TLS.Cert, - options.Config.GRPC.TLS.Key, - ), - grpc.Logger(options.Logger), - grpc.Namespace(options.Namespace), - grpc.Name(options.Name), - grpc.Version(version.GetString()), - grpc.Address(options.Address), - grpc.Context(options.Context), - grpc.Version(version.GetString()), - grpc.TraceProvider(options.TraceProvider), - grpc.HandlerWrappers(ratelimiter.NewHandlerWrapper(options.MaxConcurrentRequests)), - ) - if err != nil { - options.Logger.Fatal().Err(err).Msg("Error creating thumbnail service") - return grpc.Service{} - } - - tconf := options.Config.Thumbnail - tm, err := pool.StringToTLSMode(options.Config.GRPCClientTLS.Mode) - if err != nil { - options.Logger.Error().Err(err).Msg("could not get gateway client tls mode") - return grpc.Service{} - } - - gatewaySelector, err := pool.GatewaySelector(tconf.RevaGateway, - pool.WithTLSCACert(options.Config.GRPCClientTLS.CACert), - pool.WithTLSMode(tm), - pool.WithRegistry(registry.GetRegistry()), - pool.WithTracerProvider(options.TraceProvider), - ) - if err != nil { - options.Logger.Error().Err(err).Msg("could not get gateway selector") - return grpc.Service{} - } - b, err := bytesize.Parse(tconf.MaxInputImageFileSize) - if err != nil { - options.Logger.Error().Err(err).Msg("could not parse MaxInputImageFileSize") - return grpc.Service{} - } - - var thumbnail decorators.DecoratedService - { - thumbnail = svc.NewService( - svc.Config(options.Config), - svc.Logger(options.Logger), - svc.ThumbnailSource(imgsource.NewWebDavSource(tconf, b)), - svc.ThumbnailStorage( - storage.NewFileSystemStorage( - tconf.FileSystemStorage, - options.Logger, - ), - ), - svc.CS3Source(imgsource.NewCS3Source(tconf, gatewaySelector, b)), - svc.GatewaySelector(gatewaySelector), - ) - thumbnail = decorators.NewInstrument(thumbnail, options.Metrics) - thumbnail = decorators.NewLogging(thumbnail, options.Logger) - thumbnail = decorators.NewTracing(thumbnail, options.TraceProvider) - } - - _ = thumbnailssvc.RegisterThumbnailServiceHandler( - service.Server(), - thumbnail, - ) - - return service -} diff --git a/services/thumbnails/pkg/server/http/server.go b/services/thumbnails/pkg/server/http/server.go index d9c2a1c1fa..b75249df8f 100644 --- a/services/thumbnails/pkg/server/http/server.go +++ b/services/thumbnails/pkg/server/http/server.go @@ -9,7 +9,6 @@ import ( "github.com/opencloud-eu/opencloud/pkg/service/http" "github.com/opencloud-eu/opencloud/pkg/version" svc "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/service/http/v0" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" "go-micro.dev/v4" ) @@ -53,19 +52,8 @@ func Server(opts ...Option) (http.Service, error) { ), opencloudmiddleware.Logger(options.Logger), ), - svc.ThumbnailStorage( - storage.NewFileSystemStorage( - options.Config.Thumbnail.FileSystemStorage, - options.Logger, - ), - ), ) - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - } - if err := micro.RegisterHandler(service.Server(), handle); err != nil { return http.Service{}, err } diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/base.go b/services/thumbnails/pkg/service/grpc/v0/decorators/base.go deleted file mode 100644 index 614f7b5cf9..0000000000 --- a/services/thumbnails/pkg/service/grpc/v0/decorators/base.go +++ /dev/null @@ -1,63 +0,0 @@ -package decorators - -import ( - "context" - - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" -) - -// DecoratedService is an interface acting as facade, holding all the interfaces that this -// thumbnails microservice is expecting to implement. -// For now, only the thumbnailssvc.ThumbnailServiceHandler is present, -// but a future configsvc.ConfigServiceHandler is expected to be added here -// -// This interface will also act as the base interface to implement -// a decorator pattern. -type DecoratedService interface { - thumbnailssvc.ThumbnailServiceHandler -} - -// Decorator is the base type to implement the decorators. It will provide a basic implementation -// by delegating to the decoratedService -// -// Expected implementations will be like: -// ``` -// -// type MyDecorator struct { -// Decorator -// myCustomOpts *opts -// additionalSrv *srv -// } -// -// func NewMyDecorator(next DecoratedService, customOpts *customOpts) DecoratedService { -// ..... -// return MyDecorator{ -// Decorator: Decorator{next: next}, -// myCustomOpts: opts, -// additionalSrv: srv, -// } -// } -// -// ``` -type Decorator struct { - next DecoratedService -} - -// GetThumbnail is the base implementation for the thumbnailssvc.GetThumbnail. -// It will just delegate to the underlying decoratedService -// -// Your custom decorator is expected to overwrite this function, -// but it MUST call the underlying decoratedService at some point -// ``` -// -// func (d MyDecorator) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, resp *thumbnailssvc.GetThumbnailResponse) error { -// doSomething() -// err := d.next.GetThumbnail(ctx, req, resp) -// doAnotherThing() -// return err -// } -// -// ``` -func (deco Decorator) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, resp *thumbnailssvc.GetThumbnailResponse) error { - return deco.next.GetThumbnail(ctx, req, resp) -} diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go b/services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go deleted file mode 100644 index 3ed25017fe..0000000000 --- a/services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go +++ /dev/null @@ -1,39 +0,0 @@ -package decorators - -import ( - "context" - - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/metrics" - "github.com/prometheus/client_golang/prometheus" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next DecoratedService, metrics *metrics.Metrics) DecoratedService { - return instrument{ - Decorator: Decorator{next: next}, - metrics: metrics, - } -} - -type instrument struct { - Decorator - metrics *metrics.Metrics -} - -// GetThumbnail implements the ThumbnailServiceHandler interface. -func (i instrument) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { - us := v * 1000_000 - i.metrics.Latency.WithLabelValues().Observe(us) - i.metrics.Duration.WithLabelValues().Observe(v) - })) - defer timer.ObserveDuration() - - err := i.next.GetThumbnail(ctx, req, rsp) - - if err != nil { - i.metrics.Counter.WithLabelValues().Inc() - } - return err -} diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/logging.go b/services/thumbnails/pkg/service/grpc/v0/decorators/logging.go deleted file mode 100644 index 3a52c890d1..0000000000 --- a/services/thumbnails/pkg/service/grpc/v0/decorators/logging.go +++ /dev/null @@ -1,53 +0,0 @@ -package decorators - -import ( - "context" - "net/http" - "time" - - "github.com/opencloud-eu/opencloud/pkg/log" - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" - merrors "go-micro.dev/v4/errors" -) - -// NewLogging returns a service that logs messages. -func NewLogging(next DecoratedService, logger log.Logger) DecoratedService { - return logging{ - Decorator: Decorator{next: next}, - logger: logger, - } -} - -type logging struct { - Decorator - logger log.Logger -} - -// GetThumbnail implements the ThumbnailServiceHandler interface. -func (l logging) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - start := time.Now() - err := l.next.GetThumbnail(ctx, req, rsp) - - logger := l.logger.With(). - Str("method", "Thumbnails.GetThumbnail"). - Dur("duration", time.Since(start)). - Logger() - - if err != nil { - fromError := merrors.FromError(err) - switch fromError.GetCode() { - case http.StatusNotFound: - logger.Debug(). - Str("error_detail", fromError.GetDetail()). - Msg("no thumbnail found") - default: - logger.Warn(). - Err(err). - Msg("Failed to execute") - } - } else { - logger.Debug(). - Msg("") - } - return err -} diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go b/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go deleted file mode 100644 index f56e1b3876..0000000000 --- a/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go +++ /dev/null @@ -1,46 +0,0 @@ -package decorators - -import ( - "context" - - "go.opentelemetry.io/otel/trace" - - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" - "go.opentelemetry.io/otel/attribute" -) - -// NewTracing returns a service that instruments traces. -func NewTracing(next DecoratedService, tp trace.TracerProvider) DecoratedService { - return tracing{ - Decorator: Decorator{next: next}, - tp: tp, - } -} - -type tracing struct { - Decorator - tp trace.TracerProvider -} - -// GetThumbnail implements the ThumbnailServiceHandler interface. -func (t tracing) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - var span trace.Span - - if t.tp != nil { - tracer := t.tp.Tracer("thumbnails") - spanOpts := []trace.SpanStartOption{ - trace.WithSpanKind(trace.SpanKindServer), - } - ctx, span = tracer.Start(ctx, "Thumbnails.GetThumbnail", spanOpts...) - defer span.End() - - span.SetAttributes( - attribute.KeyValue{Key: "filepath", Value: attribute.StringValue(req.GetFilepath())}, - attribute.KeyValue{Key: "thumbnail_type", Value: attribute.StringValue(req.GetThumbnailType().String())}, - attribute.KeyValue{Key: "width", Value: attribute.IntValue(int(req.GetWidth()))}, - attribute.KeyValue{Key: "height", Value: attribute.IntValue(int(req.GetHeight()))}, - ) - } - - return t.next.GetThumbnail(ctx, req, rsp) -} diff --git a/services/thumbnails/pkg/service/grpc/v0/option.go b/services/thumbnails/pkg/service/grpc/v0/option.go deleted file mode 100644 index 5614aebcda..0000000000 --- a/services/thumbnails/pkg/service/grpc/v0/option.go +++ /dev/null @@ -1,79 +0,0 @@ -package svc - -import ( - "net/http" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/imgsource" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler - ThumbnailStorage storage.Storage - ImageSource imgsource.Source - CS3Source imgsource.Source - GatewaySelector pool.Selectable[gateway.GatewayAPIClient] -} - -// newOptions initializes the available default options. -func newOptions(opts ...Option) Options { - opt := Options{} - - for _, o := range opts { - o(&opt) - } - - return opt -} - -// Logger provides a function to set the logger option. -func Logger(val log.Logger) Option { - return func(o *Options) { - o.Logger = val - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// ThumbnailStorage provides a function to set the thumbnail storage option. -func ThumbnailStorage(val storage.Storage) Option { - return func(o *Options) { - o.ThumbnailStorage = val - } -} - -// ThumbnailSource provides a function to set the image source option. -func ThumbnailSource(val imgsource.Source) Option { - return func(o *Options) { - o.ImageSource = val - } -} - -// CS3Source provides a function to set the CS3Source option -func CS3Source(val imgsource.Source) Option { - return func(o *Options) { - o.CS3Source = val - } -} - -// GatewaySelector adds a grpc client selector for the gateway service -func GatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option { - return func(o *Options) { - o.GatewaySelector = val - } -} diff --git a/services/thumbnails/pkg/service/grpc/v0/service.go b/services/thumbnails/pkg/service/grpc/v0/service.go deleted file mode 100644 index 8948a254cb..0000000000 --- a/services/thumbnails/pkg/service/grpc/v0/service.go +++ /dev/null @@ -1,331 +0,0 @@ -package svc - -import ( - "context" - "net/http" - "net/url" - "path" - "strings" - "time" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/golang-jwt/jwt/v5" - "github.com/pkg/errors" - merrors "go-micro.dev/v4/errors" - "google.golang.org/grpc/metadata" - - revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/storagespace" - "github.com/opencloud-eu/reva/v2/pkg/utils" - - "github.com/opencloud-eu/opencloud/pkg/log" - thumbnailssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/thumbnails/v0" - terrors "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/preprocessor" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/service/grpc/v0/decorators" - tjwt "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/service/jwt" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/imgsource" -) - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) decorators.DecoratedService { - options := newOptions(opts...) - logger := options.Logger - resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) - if err != nil { - logger.Fatal().Err(err).Msg("resolutions not configured correctly") - } - svc := Thumbnail{ - serviceID: options.Config.GRPC.Namespace + "." + options.Config.Service.Name, - manager: thumbnail.NewSimpleManager( - resolutions, - options.ThumbnailStorage, - logger, - options.Config.Thumbnail.MaxInputWidth, - options.Config.Thumbnail.MaxInputHeight, - ), - webdavSource: options.ImageSource, - cs3Source: options.CS3Source, - logger: logger, - selector: options.GatewaySelector, - preprocessorOpts: PreprocessorOpts{ - TxtFontFileMap: options.Config.Thumbnail.FontMapFile, - }, - dataEndpoint: options.Config.Thumbnail.DataEndpoint, - transferSecret: options.Config.Thumbnail.TransferSecret, - } - - return svc -} - -// Thumbnail implements the GRPC handler. -type Thumbnail struct { - serviceID string - dataEndpoint string - transferSecret string - manager thumbnail.Manager - webdavSource imgsource.Source - cs3Source imgsource.Source - logger log.Logger - selector pool.Selectable[gateway.GatewayAPIClient] - preprocessorOpts PreprocessorOpts -} - -// PreprocessorOpts holds the options for the preprocessor -type PreprocessorOpts struct { - TxtFontFileMap string -} - -// GetThumbnail retrieves a thumbnail for an image -func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - var err error - var key string - switch { - case req.GetWebdavSource() != nil: - key, err = g.handleWebdavSource(ctx, req) - case req.GetCs3Source() != nil: - key, err = g.handleCS3Source(ctx, req) - default: - g.logger.Error().Msg("no image source provided") - return merrors.BadRequest(g.serviceID, "image source is missing") - } - if err != nil { - return err - } - - claims := tjwt.ThumbnailClaims{ - Key: key, - RegisteredClaims: jwt.RegisteredClaims{ - IssuedAt: jwt.NewNumericDate(time.Now()), - ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - transferToken, err := token.SignedString([]byte(g.transferSecret)) - if err != nil { - g.logger.Error(). - Err(err). - Msg("GetThumbnail: failed to sign token") - return merrors.InternalServerError(g.serviceID, "couldn't finish request") - } - rsp.DataEndpoint = g.dataEndpoint - rsp.TransferToken = transferToken - return nil -} - -func (g Thumbnail) checkThumbnail(req *thumbnailssvc.GetThumbnailRequest, sRes *provider.StatResponse) (string, thumbnail.Request, error) { - tr := thumbnail.Request{} - if !sRes.GetInfo().GetPermissionSet().GetInitiateFileDownload() { - return "", tr, merrors.Forbidden(g.serviceID, "no download permission") - } - - tType := thumbnail.GetExtForMime(sRes.GetInfo().GetMimeType()) - if tType == "" { - tType = req.GetThumbnailType().String() - } - tr, err := thumbnail.PrepareRequest(int(req.GetWidth()), int(req.GetHeight()), tType, sRes.GetInfo().GetChecksum().GetSum(), req.GetProcessor()) - if err != nil { - return "", tr, merrors.BadRequest(g.serviceID, "%s", err.Error()) - } - - if key, exists := g.manager.CheckThumbnail(tr); exists { - return key, tr, nil - } - return "", tr, nil -} - -func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest) (string, error) { - src := req.GetCs3Source() - sRes, err := g.stat(src.GetPath(), src.GetAuthorization()) - if err != nil { - return "", err - } - - key, tr, err := g.checkThumbnail(req, sRes) - switch { - case err != nil: - return "", err - case key != "": - // we have matching thumbnail already, use that - return key, nil - } - - ctx = imgsource.ContextSetAuthorization(ctx, src.GetAuthorization()) - r, err := g.cs3Source.Get(ctx, src.GetPath()) - switch { - case errors.Is(err, terrors.ErrImageTooLarge): - return "", merrors.Forbidden(g.serviceID, "%s", err.Error()) - case err != nil: - return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) - } - - defer r.Close() - ppOpts := map[string]any{ - "fontFileMap": g.preprocessorOpts.TxtFontFileMap, - } - pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) - img, err := pp.Convert(r) - if err != nil { - g.logger.Error().Err(err).Msg("failed to convert image") - } - - if img == nil || err != nil { - return "", merrors.NotFound(g.serviceID, "could not get image") - } - - key, err = g.manager.Generate(tr, img) - if errors.Is(err, terrors.ErrImageTooLarge) { - return "", merrors.Forbidden(g.serviceID, "%s", err.Error()) - } - return key, err -} - -func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest) (string, error) { - src := req.GetWebdavSource() - imgURL, err := url.Parse(src.GetUrl()) - if err != nil { - return "", errors.Wrap(err, "source url is invalid") - } - - var auth, statPath string - if src.GetIsPublicLink() { - q := imgURL.Query() - var rsp *gateway.AuthenticateResponse - client, err := g.selector.Next() - if err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not select next gateway client: %s", err.Error()) - } - if q.Get("signature") != "" && q.Get("expiration") != "" { - // Handle pre-signed public links - sig := q.Get("signature") - exp := q.Get("expiration") - rsp, err = client.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "publicshares", - ClientId: src.GetPublicLinkToken(), - ClientSecret: strings.Join([]string{"signature", sig, exp}, "|"), - }) - } else { - rsp, err = client.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "publicshares", - ClientId: src.GetPublicLinkToken(), - // We pass an empty password because we expect non pre-signed public links - // to not be password protected - ClientSecret: "password|", - }) - } - - if err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not authenticate: %s", err.Error()) - } - auth = rsp.GetToken() - statPath = path.Join("/public", src.GetPublicLinkToken(), req.GetFilepath()) - } else { - auth = src.GetRevaAuthorization() - statPath = req.GetFilepath() - } - sRes, err := g.stat(statPath, auth) - if err != nil { - return "", err - } - - key, tr, err := g.checkThumbnail(req, sRes) - switch { - case err != nil: - return "", err - case key != "": - // we have matching thumbnail already, use that - return key, nil - } - - if src.GetWebdavAuthorization() != "" { - ctx = imgsource.ContextSetAuthorization(ctx, src.GetWebdavAuthorization()) - } - - // add signature and expiration to webdav url - signature, expiration := imgURL.Query().Get("signature"), imgURL.Query().Get("expiration") - params := url.Values{} - params.Add("signature", signature) - params.Add("expiration", expiration) - imgURL.RawQuery = params.Encode() - - r, err := g.webdavSource.Get(ctx, imgURL.String()) - switch { - case errors.Is(err, terrors.ErrImageTooLarge): - return "", merrors.Forbidden(g.serviceID, "%s", err.Error()) - case err != nil: - return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) - } - defer r.Close() - ppOpts := map[string]any{ - "fontFileMap": g.preprocessorOpts.TxtFontFileMap, - } - pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) - img, err := pp.Convert(r) - if img == nil || err != nil { - return "", merrors.NotFound(g.serviceID, "could not get image") - } - - key, err = g.manager.Generate(tr, img) - if errors.Is(err, terrors.ErrImageTooLarge) { - return "", merrors.Forbidden(g.serviceID, "%s", err.Error()) - } - return key, err -} - -func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) { - ctx := metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) - - ref, err := storagespace.ParseReference(path) - if err != nil { - // If the path is not a spaces reference try to handle it like a plain - // path reference. - ref = provider.Reference{ - Path: path, - } - } - - client, err := g.selector.Next() - if err != nil { - return nil, merrors.InternalServerError(g.serviceID, "could not select next gateway client: %s", err.Error()) - } - req := &provider.StatRequest{Ref: &ref} - rsp, err := client.Stat(ctx, req) - if err != nil { - g.logger.Error().Err(err).Str("path", path).Msg("could not stat file") - return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", err.Error()) - } - - if rsp.GetStatus().GetCode() != rpc.Code_CODE_OK { - switch rsp.GetStatus().GetCode() { - case rpc.Code_CODE_NOT_FOUND: - return nil, merrors.NotFound(g.serviceID, "could not stat file: %s", rsp.GetStatus().GetMessage()) - default: - g.logger.Error().Str("status_message", rsp.GetStatus().GetMessage()).Str("path", path).Msg("could not stat file") - return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", rsp.GetStatus().GetMessage()) - } - } - if rsp.GetInfo().GetType() != provider.ResourceType_RESOURCE_TYPE_FILE { - return nil, merrors.BadRequest(g.serviceID, "Unsupported file type") - } - if utils.ReadPlainFromOpaque(rsp.GetInfo().GetOpaque(), "status") == "processing" { - return nil, &merrors.Error{ - Id: g.serviceID, - Code: http.StatusTooEarly, - Detail: "File Processing", - Status: http.StatusText(http.StatusTooEarly), - } - } - if rsp.GetInfo().GetChecksum().GetSum() == "" { - g.logger.Error().Msg("resource info is missing checksum") - return nil, merrors.NotFound(g.serviceID, "resource info is missing a checksum") - } - if !thumbnail.IsMimeTypeSupported(rsp.GetInfo().GetMimeType()) { - return nil, merrors.NotFound(g.serviceID, "Unsupported file type") - } - return rsp, nil -} diff --git a/services/thumbnails/pkg/service/http/v0/instrument.go b/services/thumbnails/pkg/service/http/v0/instrument.go deleted file mode 100644 index 59eccda991..0000000000 --- a/services/thumbnails/pkg/service/http/v0/instrument.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// GetThumbnail implements the Service interface. -func (i instrument) GetThumbnail(w http.ResponseWriter, r *http.Request) { - i.next.GetThumbnail(w, r) -} diff --git a/services/thumbnails/pkg/service/http/v0/logging.go b/services/thumbnails/pkg/service/http/v0/logging.go deleted file mode 100644 index e1051bb7ab..0000000000 --- a/services/thumbnails/pkg/service/http/v0/logging.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/opencloud-eu/opencloud/pkg/log" -) - -// NewLogging returns a service that logs messages. -func NewLogging(next Service, logger log.Logger) Service { - return logging{ - next: next, - logger: logger, - } -} - -type logging struct { - next Service - logger log.Logger -} - -// ServeHTTP implements the Service interface. -func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) { - l.next.ServeHTTP(w, r) -} - -// GetThumbnail implements the Service interface. -func (l logging) GetThumbnail(w http.ResponseWriter, r *http.Request) { - l.next.GetThumbnail(w, r) -} diff --git a/services/thumbnails/pkg/service/http/v0/option.go b/services/thumbnails/pkg/service/http/v0/option.go index 3f8e7e9981..237ac444e4 100644 --- a/services/thumbnails/pkg/service/http/v0/option.go +++ b/services/thumbnails/pkg/service/http/v0/option.go @@ -5,7 +5,6 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" "go.opentelemetry.io/otel/trace" ) @@ -14,11 +13,10 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler - ThumbnailStorage storage.Storage - TraceProvider trace.TracerProvider + Logger log.Logger + Config *config.Config + Middleware []func(http.Handler) http.Handler + TraceProvider trace.TracerProvider } // newOptions initializes the available default options. @@ -52,10 +50,3 @@ func Middleware(val ...func(http.Handler) http.Handler) Option { o.Middleware = val } } - -// ThumbnailStorage provides a function to set the ThumbnailStorage option. -func ThumbnailStorage(storage storage.Storage) Option { - return func(o *Options) { - o.ThumbnailStorage = storage - } -} diff --git a/services/thumbnails/pkg/service/http/v0/service.go b/services/thumbnails/pkg/service/http/v0/service.go index 8b1b3f9e3f..99ce58379a 100644 --- a/services/thumbnails/pkg/service/http/v0/service.go +++ b/services/thumbnails/pkg/service/http/v0/service.go @@ -1,32 +1,18 @@ package svc import ( - "context" - "fmt" "net/http" - "strconv" "github.com/go-chi/chi/v5" - "github.com/golang-jwt/jwt/v5" "github.com/riandyrn/otelchi" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/tracing" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - tjwt "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/service/jwt" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" -) - -type contextKey string - -const ( - keyContextKey contextKey = "key" ) // Service defines the service handlers. type Service interface { ServeHTTP(w http.ResponseWriter, r *http.Request) - GetThumbnail(w http.ResponseWriter, r *http.Request) } // NewService returns a service implementation for Service. @@ -45,31 +31,15 @@ func NewService(opts ...Option) Service { ), ) - logger := options.Logger - resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) - if err != nil { - logger.Fatal().Err(err).Msg("resolutions not configured correctly") - } + limits := options.Config.Thumbnail + svc := Thumbnails{ - config: options.Config, - mux: m, - logger: options.Logger, - manager: thumbnail.NewSimpleManager( - resolutions, - options.ThumbnailStorage, - logger, - options.Config.Thumbnail.MaxInputWidth, - options.Config.Thumbnail.MaxInputHeight, - ), - maxWidth: options.Config.Thumbnail.MaxInputWidth, - maxHeight: options.Config.Thumbnail.MaxInputHeight, + logger: options.Logger, + mux: m, + maxWidth: limits.MaxInputWidth, + maxHeight: limits.MaxInputHeight, } - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Use(svc.TransferTokenValidator) - r.Get("/data", svc.GetThumbnail) - }) - // Push-based thumbnail generation endpoint (imagor-compatible). The optional // operation segment selects the resize/crop mode; its absence is the default // center-crop fill. The filters segment is captured whole and parsed by the @@ -90,10 +60,8 @@ func NewService(opts ...Option) Service { // Thumbnails implements the business logic for Service. type Thumbnails struct { - config *config.Config logger log.Logger mux *chi.Mux - manager thumbnail.Manager maxWidth int maxHeight int } @@ -102,63 +70,3 @@ type Thumbnails struct { func (s Thumbnails) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.mux.ServeHTTP(w, r) } - -// GetThumbnail implements the Service interface. -func (s Thumbnails) GetThumbnail(w http.ResponseWriter, r *http.Request) { - logger := s.logger.SubloggerWithRequestID(r.Context()) - key := r.Context().Value(keyContextKey).(string) - - thumbnailBytes, err := s.manager.GetThumbnail(key) - if err != nil { - logger.Debug(). - Err(err). - Str("key", key). - Msg("could not get the thumbnail") - w.WriteHeader(http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Length", strconv.Itoa(len(thumbnailBytes))) - if _, err = w.Write(thumbnailBytes); err != nil { - logger.Error(). - Err(err). - Str("key", key). - Msg("could not write the thumbnail response") - } -} - -// TransferTokenValidator validates a transfer token -func (s Thumbnails) TransferTokenValidator(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tokenString := r.Header.Get("Transfer-Token") - if tokenString == "" { - next.ServeHTTP(w, r) - return - } - - logger := s.logger.SubloggerWithRequestID(r.Context()) - token, err := jwt.ParseWithClaims(tokenString, &tjwt.ThumbnailClaims{}, func(token *jwt.Token) (any, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(s.config.Thumbnail.TransferSecret), nil - }) - if err != nil { - logger.Debug(). - Err(err). - Str("transfer-token", tokenString). - Msg("failed to parse transfer token") - w.WriteHeader(http.StatusUnauthorized) - return - } - - if claims, ok := token.Claims.(*tjwt.ThumbnailClaims); ok && token.Valid { - ctx := context.WithValue(r.Context(), keyContextKey, claims.Key) - next.ServeHTTP(w, r.WithContext(ctx)) - return - } - logger.Debug().Msg("invalid transfer token") - w.WriteHeader(http.StatusUnauthorized) - }) -} diff --git a/services/thumbnails/pkg/service/jwt/jwt.go b/services/thumbnails/pkg/service/jwt/jwt.go deleted file mode 100644 index 14df7560b1..0000000000 --- a/services/thumbnails/pkg/service/jwt/jwt.go +++ /dev/null @@ -1,9 +0,0 @@ -package jwt - -import "github.com/golang-jwt/jwt/v5" - -// ThumbnailClaims defines the claims for thumb-nailing -type ThumbnailClaims struct { - jwt.RegisteredClaims - Key string `json:"key"` -} diff --git a/services/thumbnails/pkg/thumbnail/encoding.go b/services/thumbnails/pkg/thumbnail/encoding.go deleted file mode 100644 index 28f023a9cf..0000000000 --- a/services/thumbnails/pkg/thumbnail/encoding.go +++ /dev/null @@ -1,81 +0,0 @@ -package thumbnail - -import ( - "image/gif" - "io" - "strings" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" -) - -const ( - typePng = "png" - typeJpg = "jpg" - typeJpeg = "jpeg" - typeGif = "gif" - typeGgs = "ggs" - typeGgp = "ggp" - typeWebp = "webp" -) - -// Encoder encodes the thumbnail to a specific format. -type Encoder interface { - // Encode encodes the image to a format. - Encode(w io.Writer, img any) error - // Types returns the formats suffixes. - Types() []string - // MimeType returns the mimetype used by the encoder. - MimeType() string -} - -// GifEncoder encodes to gif -type GifEncoder struct{} - -// Encode encodes the image to a gif format -func (e GifEncoder) Encode(w io.Writer, img any) error { - g, ok := img.(*gif.GIF) - if !ok { - return errors.ErrInvalidType - } - return gif.EncodeAll(w, g) -} - -// Types returns the supported types of the GifEncoder -func (e GifEncoder) Types() []string { - return []string{typeGif} -} - -// MimeType returns the mimetype used by the encoder. -func (e GifEncoder) MimeType() string { - return "image/gif" -} - -// EncoderForType returns the encoder for a given file type -// or nil if the type is not supported. -func EncoderForType(fileType string) (Encoder, error) { - switch strings.ToLower(fileType) { - case typePng, typeGgs, typeGgp: - return PngEncoder{}, nil - case typeJpg, typeJpeg, typeWebp: - return JpegEncoder{}, nil - case typeGif: - return GifEncoder{}, nil - default: - return nil, errors.ErrNoEncoderForType - } -} - -// GetExtForMime return the supported extension by mime -func GetExtForMime(fileType string) string { - ext := strings.TrimPrefix(strings.TrimSpace(strings.ToLower(fileType)), "image/") - switch ext { - case typeJpg, typeJpeg, typePng, typeGif, typeWebp: - return ext - case "application/vnd.geogebra.slides": - return typeGgs - case "application/vnd.geogebra.pinboard": - return typeGgp - default: - return "" - } -} diff --git a/services/thumbnails/pkg/thumbnail/encoding_imaging.go b/services/thumbnails/pkg/thumbnail/encoding_imaging.go deleted file mode 100644 index 7d2d0c1610..0000000000 --- a/services/thumbnails/pkg/thumbnail/encoding_imaging.go +++ /dev/null @@ -1,56 +0,0 @@ -//go:build !enable_vips - -package thumbnail - -import ( - "image" - "image/jpeg" - "image/png" - "io" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" -) - -// PngEncoder encodes to png -type PngEncoder struct{} - -// Encode encodes to png format -func (e PngEncoder) Encode(w io.Writer, img any) error { - m, ok := img.(image.Image) - if !ok { - return errors.ErrInvalidType - } - return png.Encode(w, m) -} - -// Types returns the png suffix -func (e PngEncoder) Types() []string { - return []string{typePng} -} - -// MimeType returns the mimetype for png files. -func (e PngEncoder) MimeType() string { - return "image/png" -} - -// JpegEncoder encodes to jpg -type JpegEncoder struct{} - -// Encode encodes to jpg -func (e JpegEncoder) Encode(w io.Writer, img any) error { - m, ok := img.(image.Image) - if !ok { - return errors.ErrInvalidType - } - return jpeg.Encode(w, m, nil) -} - -// Types returns the jpg suffixes. -func (e JpegEncoder) Types() []string { - return []string{typeJpeg, typeJpg} -} - -// MimeType returns the mimetype for jpg files. -func (e JpegEncoder) MimeType() string { - return "image/jpeg" -} diff --git a/services/thumbnails/pkg/thumbnail/encoding_test.go b/services/thumbnails/pkg/thumbnail/encoding_test.go deleted file mode 100644 index 6dcce72a62..0000000000 --- a/services/thumbnails/pkg/thumbnail/encoding_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package thumbnail - -import "testing" - -func TestEncoderForType(t *testing.T) { - table := map[string]Encoder{ - "jpg": JpegEncoder{}, - "JPG": JpegEncoder{}, - "jpeg": JpegEncoder{}, - "JPEG": JpegEncoder{}, - "png": PngEncoder{}, - "PNG": PngEncoder{}, - "invalid": nil, - } - - for k, v := range table { - e, _ := EncoderForType(k) - if e != v { - t.Fail() - } - } -} diff --git a/services/thumbnails/pkg/thumbnail/encoding_vips.go b/services/thumbnails/pkg/thumbnail/encoding_vips.go deleted file mode 100644 index f9a2c920be..0000000000 --- a/services/thumbnails/pkg/thumbnail/encoding_vips.go +++ /dev/null @@ -1,66 +0,0 @@ -//go:build enable_vips - -package thumbnail - -import ( - "io" - - "github.com/davidbyttow/govips/v2/vips" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" -) - -// PngEncoder encodes to png -type PngEncoder struct{} - -// Encode encodes to png format -func (e PngEncoder) Encode(w io.Writer, img interface{}) error { - m, ok := img.(*vips.ImageRef) - if !ok { - return errors.ErrInvalidType - } - - buf, _, err := m.ExportPng(vips.NewPngExportParams()) - if err != nil { - return err - } - _, err = w.Write(buf) - return err -} - -// Types returns the png suffix -func (e PngEncoder) Types() []string { - return []string{typePng} -} - -// MimeType returns the mimetype for png files. -func (e PngEncoder) MimeType() string { - return "image/png" -} - -// JpegEncoder encodes to jpg -type JpegEncoder struct{} - -// Encode encodes to jpg -func (e JpegEncoder) Encode(w io.Writer, img interface{}) error { - m, ok := img.(*vips.ImageRef) - if !ok { - return errors.ErrInvalidType - } - - buf, _, err := m.ExportJpeg(vips.NewJpegExportParams()) - if err != nil { - return err - } - _, err = w.Write(buf) - return err -} - -// Types returns the jpg suffixes. -func (e JpegEncoder) Types() []string { - return []string{typeJpeg, typeJpg} -} - -// MimeType returns the mimetype for jpg files. -func (e JpegEncoder) MimeType() string { - return "image/jpeg" -} diff --git a/services/thumbnails/pkg/thumbnail/generator.go b/services/thumbnails/pkg/thumbnail/generator.go deleted file mode 100644 index 451b8b9f38..0000000000 --- a/services/thumbnails/pkg/thumbnail/generator.go +++ /dev/null @@ -1,99 +0,0 @@ -package thumbnail - -import ( - "image" - "image/color" - "image/draw" - "image/gif" - "strings" - - "github.com/kovidgoyal/imaging" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" -) - -// Generator generates a web friendly file version. -type Generator interface { - Generate(size image.Rectangle, img any) (any, error) - Dimensions(img any) (image.Rectangle, error) - ProcessorID() string -} - -// GifGenerator is used to create a web friendly version of the provided gif image. -type GifGenerator struct { - processor Processor -} - -func NewGifGenerator(filetype, process string) (GifGenerator, error) { - processor, err := ProcessorFor(process, filetype) - if err != nil { - return GifGenerator{}, err - } - return GifGenerator{processor: processor}, nil - -} - -// ProcessorID returns the processor identification. -func (g GifGenerator) ProcessorID() string { - return g.processor.ID() -} - -// Generate generates a alternative gif version. -func (g GifGenerator) Generate(size image.Rectangle, img any) (any, error) { - // Code inspired by https://github.com/willnorris/gifresize/blob/db93a7e1dcb1c279f7eeb99cc6d90b9e2e23e871/gifresize.go - - m, ok := img.(*gif.GIF) - if !ok { - return nil, errors.ErrInvalidType - } - // Create a new RGBA image to hold the incremental frames. - srcX, srcY := m.Config.Width, m.Config.Height - b := image.Rect(0, 0, srcX, srcY) - tmp := image.NewRGBA(b) - - for i, frame := range m.Image { - bounds := frame.Bounds() - prev := tmp - draw.Draw(tmp, bounds, frame, bounds.Min, draw.Over) - processed := g.processor.Process(tmp, size.Dx(), size.Dy(), imaging.Lanczos) - m.Image[i] = g.imageToPaletted(processed, frame.Palette) - - switch m.Disposal[i] { - case gif.DisposalBackground: - tmp = image.NewRGBA(b) - case gif.DisposalPrevious: - tmp = prev - } - } - m.Config.Width = size.Dx() - m.Config.Height = size.Dy() - - return m, nil -} - -func (g GifGenerator) Dimensions(img any) (image.Rectangle, error) { - m, ok := img.(*gif.GIF) - if !ok { - return image.Rectangle{}, errors.ErrInvalidType - } - return m.Image[0].Bounds(), nil -} - -func (g GifGenerator) imageToPaletted(img image.Image, p color.Palette) *image.Paletted { - b := img.Bounds() - pm := image.NewPaletted(b, p) - draw.FloydSteinberg.Draw(pm, b, img, image.Point{}) - return pm -} - -// GeneratorFor returns the generator for a given file type -// or nil if the type is not supported. -func GeneratorFor(fileType, processorID string) (Generator, error) { - switch strings.ToLower(fileType) { - case typePng, typeJpg, typeJpeg, typeGgs, typeGgp, typeWebp: - return NewSimpleGenerator(fileType, processorID) - case typeGif: - return NewGifGenerator(fileType, processorID) - default: - return nil, errors.ErrNoGeneratorForType - } -} diff --git a/services/thumbnails/pkg/thumbnail/generator_simple.go b/services/thumbnails/pkg/thumbnail/generator_simple.go deleted file mode 100644 index a4287285fd..0000000000 --- a/services/thumbnails/pkg/thumbnail/generator_simple.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build !enable_vips - -package thumbnail - -import ( - "image" - - "github.com/kovidgoyal/imaging" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" -) - -// SimpleGenerator is the default image generator and is used for all image types expect gif. -type SimpleGenerator struct { - processor Processor -} - -func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { - processor, err := ProcessorFor(process, filetype) - if err != nil { - return SimpleGenerator{}, err - } - return SimpleGenerator{processor: processor}, nil -} - -// ProcessorID returns the processor identification. -func (g SimpleGenerator) ProcessorID() string { - return g.processor.ID() -} - -// Generate generates a alternative image version. -func (g SimpleGenerator) Generate(size image.Rectangle, img any) (any, error) { - m, ok := img.(image.Image) - if !ok { - return nil, errors.ErrInvalidType - } - - return g.processor.Process(m, size.Dx(), size.Dy(), imaging.Lanczos), nil -} - -func (g SimpleGenerator) Dimensions(img any) (image.Rectangle, error) { - m, ok := img.(image.Image) - if !ok { - return image.Rectangle{}, errors.ErrInvalidType - } - return m.Bounds(), nil -} diff --git a/services/thumbnails/pkg/thumbnail/generator_vips.go b/services/thumbnails/pkg/thumbnail/generator_vips.go deleted file mode 100644 index 5005004942..0000000000 --- a/services/thumbnails/pkg/thumbnail/generator_vips.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build enable_vips - -package thumbnail - -import ( - "bytes" - "image" - "strings" - - "github.com/davidbyttow/govips/v2/vips" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" - "golang.org/x/image/bmp" -) - -// SimpleGenerator is the default image generator and is used for all image types expect gif. -type SimpleGenerator struct { - crop vips.Interesting - size vips.Size - process string -} - -func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { - switch strings.ToLower(process) { - case "thumbnail": - return SimpleGenerator{crop: vips.InterestingAttention, process: process, size: vips.SizeBoth}, nil - case "fit": - return SimpleGenerator{crop: vips.InterestingNone, process: process, size: vips.SizeBoth}, nil - case "resize": - return SimpleGenerator{crop: vips.InterestingNone, process: process, size: vips.SizeForce}, nil - default: - return SimpleGenerator{crop: vips.InterestingAttention, process: process, size: vips.SizeBoth}, nil - } -} - -// ProcessorID returns the processor identification. -func (g SimpleGenerator) ProcessorID() string { - return g.process -} - -// Generate generates a alternative image version. -func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { - var m *vips.ImageRef - var err error - switch img.(type) { - case *image.RGBA: - // This comes from the txt preprocessor - var buf bytes.Buffer - if err = bmp.Encode(&buf, img.(*image.RGBA)); err != nil { - return nil, err - } - m, err = vips.NewImageFromReader(&buf) - if err != nil { - return nil, err - } - - case *vips.ImageRef: - m = img.(*vips.ImageRef) - default: - return nil, errors.ErrInvalidType - } - - if err := m.ThumbnailWithSize(size.Dx(), size.Dy(), g.crop, g.size); err != nil { - return nil, err - } - - if err := m.RemoveMetadata(); err != nil { - return nil, err - } - - return m, nil -} - -func (g SimpleGenerator) Dimensions(img interface{}) (image.Rectangle, error) { - switch img.(type) { - case *image.RGBA: - m := img.(*image.RGBA) - return m.Bounds(), nil - case *vips.ImageRef: - m := img.(*vips.ImageRef) - return image.Rect(0, 0, m.Width(), m.Height()), nil - default: - return image.Rectangle{}, errors.ErrInvalidType - } -} diff --git a/services/thumbnails/pkg/thumbnail/imgsource/cs3.go b/services/thumbnails/pkg/thumbnail/imgsource/cs3.go deleted file mode 100644 index 69560b0684..0000000000 --- a/services/thumbnails/pkg/thumbnail/imgsource/cs3.go +++ /dev/null @@ -1,132 +0,0 @@ -package imgsource - -import ( - "context" - "crypto/tls" - "fmt" - "io" - "net/http" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" - "github.com/opencloud-eu/reva/v2/pkg/bytesize" - revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/rhttp" - "github.com/opencloud-eu/reva/v2/pkg/storagespace" - "google.golang.org/grpc/metadata" -) - -const ( - // TokenTransportHeader holds the header key for the reva transfer token - // "github.com/opencloud-eu/reva/v2/internal/http/services/datagateway" is internal so we redeclare it here - TokenTransportHeader = "X-Reva-Transfer" -) - -// CS3 implements a CS3 image source -type CS3 struct { - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - insecure bool - maxImageFileSize uint64 -} - -// NewCS3Source configures a new CS3 image source -func NewCS3Source(cfg config.Thumbnail, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], b bytesize.ByteSize) CS3 { - return CS3{ - gatewaySelector: gatewaySelector, - insecure: cfg.CS3AllowInsecure, - maxImageFileSize: b.Bytes(), - } -} - -// Get downloads the file from a cs3 service -// The caller MUST make sure to close the returned ReadCloser -func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) { - auth, ok := ContextGetAuthorization(ctx) - if !ok { - return nil, errors.ErrCS3AuthorizationMissing - } - ref, err := storagespace.ParseReference(path) - if err != nil { - // If the path is not a spaces reference try to handle it like a plain - // path reference. - ref = provider.Reference{ - Path: path, - } - } - - ctx = metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) - err = s.checkImageFileSize(ctx, ref) - if err != nil { - return nil, err - } - - gwc, err := s.gatewaySelector.Next() - if err != nil { - return nil, err - } - rsp, err := gwc.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &ref}) - - if err != nil { - return nil, err - } - - if rsp.GetStatus().GetCode() != rpc.Code_CODE_OK { - return nil, fmt.Errorf("could not load image: %s", rsp.GetStatus().GetMessage()) - } - var ep, tk string - for _, p := range rsp.GetProtocols() { - if p.GetProtocol() == "spaces" { - ep, tk = p.GetDownloadEndpoint(), p.GetToken() - break - } - } - if (ep == "" || tk == "") && len(rsp.GetProtocols()) > 0 { - ep, tk = rsp.GetProtocols()[0].GetDownloadEndpoint(), rsp.GetProtocols()[0].GetToken() - } - - httpReq, err := rhttp.NewRequest(ctx, "GET", ep, nil) - if err != nil { - return nil, err - } - httpReq.Header.Set(revactx.TokenHeader, auth) - httpReq.Header.Set(TokenTransportHeader, tk) - - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ - MinVersion: tls.VersionTLS12, - InsecureSkipVerify: s.insecure, //nolint:gosec - } - client := &http.Client{} - - resp, err := client.Do(httpReq) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", path, resp.StatusCode) - } - - return resp.Body, nil -} - -func (s CS3) checkImageFileSize(ctx context.Context, ref provider.Reference) error { - gwc, err := s.gatewaySelector.Next() - if err != nil { - return err - } - stat, err := gwc.Stat(ctx, &provider.StatRequest{Ref: &ref}) - if err != nil { - return err - } - if stat.GetStatus().GetCode() != rpc.Code_CODE_OK { - return fmt.Errorf("could not stat image: %s", stat.GetStatus().GetMessage()) - } - if stat.GetInfo().GetSize() > s.maxImageFileSize { - return errors.ErrImageTooLarge - } - return nil -} diff --git a/services/thumbnails/pkg/thumbnail/imgsource/imgsource.go b/services/thumbnails/pkg/thumbnail/imgsource/imgsource.go deleted file mode 100644 index 77c274bf2d..0000000000 --- a/services/thumbnails/pkg/thumbnail/imgsource/imgsource.go +++ /dev/null @@ -1,31 +0,0 @@ -package imgsource - -import ( - "context" - "io" -) - -type key int - -const ( - auth key = iota -) - -// Source defines the interface for image sources -type Source interface { - Get(ctx context.Context, path string) (io.ReadCloser, error) -} - -// ContextSetAuthorization puts the authorization in the context. -func ContextSetAuthorization(parent context.Context, authorization string) context.Context { - return context.WithValue(parent, auth, authorization) -} - -// ContextGetAuthorization gets the authorization from the context. -func ContextGetAuthorization(ctx context.Context) (string, bool) { - val := ctx.Value(auth) - if val == nil { - return "", false - } - return val.(string), true -} diff --git a/services/thumbnails/pkg/thumbnail/imgsource/webdav.go b/services/thumbnails/pkg/thumbnail/imgsource/webdav.go deleted file mode 100644 index e7d6cf75c2..0000000000 --- a/services/thumbnails/pkg/thumbnail/imgsource/webdav.go +++ /dev/null @@ -1,75 +0,0 @@ -package imgsource - -import ( - "context" - "crypto/tls" - "fmt" - _ "image/gif" // Import the gif package so that image.Decode can understand gifs - _ "image/jpeg" // Import the jpeg package so that image.Decode can understand jpegs - _ "image/png" // Import the png package so that image.Decode can understand pngs - "io" - "net/http" - "strconv" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - thumbnailerErrors "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" - "github.com/opencloud-eu/reva/v2/pkg/bytesize" - "github.com/pkg/errors" -) - -// NewWebDavSource creates a new webdav instance. -func NewWebDavSource(cfg config.Thumbnail, b bytesize.ByteSize) WebDav { - return WebDav{ - insecure: cfg.WebdavAllowInsecure, - maxImageFileSize: b.Bytes(), - } -} - -// WebDav implements the Source interface for webdav services -type WebDav struct { - insecure bool - maxImageFileSize uint64 -} - -// Get downloads the file from a webdav service -// The caller MUST make sure to close the returned ReadCloser -func (s WebDav) Get(ctx context.Context, url string) (io.ReadCloser, error) { - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, errors.Wrapf(err, `could not get the image "%s"`, url) - } - - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ - MinVersion: tls.VersionTLS12, - InsecureSkipVerify: s.insecure, //nolint:gosec - } - - if auth, ok := ContextGetAuthorization(ctx); ok { - req.Header.Add("Authorization", auth) - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, errors.Wrapf(err, `could not get the image "%s"`, url) - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", url, resp.StatusCode) - } - - contentLength := resp.Header.Get("Content-Length") - if contentLength == "" { - // no size information - let's assume it is too big - return nil, thumbnailerErrors.ErrImageTooLarge - } - c, err := strconv.ParseUint(contentLength, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, `could not parse content length of webdav response "%s"`, url) - } - if c > s.maxImageFileSize { - return nil, thumbnailerErrors.ErrImageTooLarge - } - - return resp.Body, nil -} diff --git a/services/thumbnails/pkg/thumbnail/mimetypes.go b/services/thumbnails/pkg/thumbnail/mimetypes.go index 02a91e22f5..00ced3df0c 100644 --- a/services/thumbnails/pkg/thumbnail/mimetypes.go +++ b/services/thumbnails/pkg/thumbnail/mimetypes.go @@ -2,21 +2,19 @@ package thumbnail -var ( - // SupportedMimeTypes contains an all mimetypes which are supported by the thumbnailer. - SupportedMimeTypes = map[string]struct{}{ - "image/png": {}, - "image/jpg": {}, - "image/jpeg": {}, - "image/gif": {}, - "image/bmp": {}, - "image/x-ms-bmp": {}, - "image/tiff": {}, - "text/plain": {}, - "audio/flac": {}, - "audio/mpeg": {}, - "audio/ogg": {}, - "application/vnd.geogebra.slides": {}, - "application/vnd.geogebra.pinboard": {}, - } -) +// SupportedMimeTypes contains all mimetypes which are supported by the thumbnailer. +var SupportedMimeTypes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "image/bmp": {}, + "image/x-ms-bmp": {}, + "image/tiff": {}, + "text/plain": {}, + "audio/flac": {}, + "audio/mpeg": {}, + "audio/ogg": {}, + "application/vnd.geogebra.slides": {}, + "application/vnd.geogebra.pinboard": {}, +} diff --git a/services/thumbnails/pkg/thumbnail/mimetypes_vips.go b/services/thumbnails/pkg/thumbnail/mimetypes_vips.go index b94fafafaf..4c4e254427 100644 --- a/services/thumbnails/pkg/thumbnail/mimetypes_vips.go +++ b/services/thumbnails/pkg/thumbnail/mimetypes_vips.go @@ -2,22 +2,20 @@ package thumbnail -var ( - // SupportedMimeTypes contains an all mimetypes which are supported by the thumbnailer. - SupportedMimeTypes = map[string]struct{}{ - "image/png": {}, - "image/jpg": {}, - "image/jpeg": {}, - "image/gif": {}, - "image/bmp": {}, - "image/x-ms-bmp": {}, - "image/tiff": {}, - "text/plain": {}, - "audio/flac": {}, - "audio/mpeg": {}, - "audio/ogg": {}, - "application/vnd.geogebra.slides": {}, - "application/vnd.geogebra.pinboard": {}, - "image/webp": {}, - } -) +// SupportedMimeTypes contains all mimetypes which are supported by the thumbnailer. +var SupportedMimeTypes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "image/bmp": {}, + "image/x-ms-bmp": {}, + "image/tiff": {}, + "image/webp": {}, + "text/plain": {}, + "audio/flac": {}, + "audio/mpeg": {}, + "audio/ogg": {}, + "application/vnd.geogebra.slides": {}, + "application/vnd.geogebra.pinboard": {}, +} diff --git a/services/thumbnails/pkg/thumbnail/processor.go b/services/thumbnails/pkg/thumbnail/processor.go deleted file mode 100644 index e4f14df256..0000000000 --- a/services/thumbnails/pkg/thumbnail/processor.go +++ /dev/null @@ -1,51 +0,0 @@ -package thumbnail - -import ( - "image" - "strings" - - "github.com/kovidgoyal/imaging" -) - -// Processor processes the thumbnail by applying different transformations to it. -type Processor interface { - ID() string - Process(img image.Image, width, height int, filter imaging.ResampleFilter) image.Image -} - -// DefinableProcessor is the simplest processor, it holds a replaceable image converter function. -type DefinableProcessor struct { - Slug string - Converter func(img image.Image, width, height int, filter imaging.ResampleFilter) image.Image -} - -// ID returns the processor identification. -func (p DefinableProcessor) ID() string { return p.Slug } - -// Process transforms the given image. -func (p DefinableProcessor) Process(img image.Image, width, height int, filter imaging.ResampleFilter) image.Image { - return p.Converter(img, width, height, filter) -} - -// ProcessorFor returns a matching Processor -func ProcessorFor(id, fileType string) (DefinableProcessor, error) { - switch strings.ToLower(id) { - case "fit": - return DefinableProcessor{Slug: strings.ToLower(id), Converter: imaging.Fit}, nil - case "resize": - return DefinableProcessor{Slug: strings.ToLower(id), Converter: imaging.Resize}, nil - case "fill": - return DefinableProcessor{Slug: strings.ToLower(id), Converter: func(img image.Image, width, height int, filter imaging.ResampleFilter) image.Image { - return imaging.Fill(img, width, height, imaging.Center, filter) - }}, nil - case "thumbnail": - return DefinableProcessor{Slug: strings.ToLower(id), Converter: imaging.Thumbnail}, nil - default: - switch strings.ToLower(fileType) { - case typeGif: - return DefinableProcessor{Converter: imaging.Resize}, nil - default: - return DefinableProcessor{Converter: imaging.Thumbnail}, nil - } - } -} diff --git a/services/thumbnails/pkg/thumbnail/processor_test.go b/services/thumbnails/pkg/thumbnail/processor_test.go deleted file mode 100644 index 5ab73ca33a..0000000000 --- a/services/thumbnails/pkg/thumbnail/processor_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package thumbnail_test - -import ( - "testing" - - "github.com/kovidgoyal/imaging" - tAssert "github.com/stretchr/testify/assert" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" -) - -func TestProcessorFor(t *testing.T) { - tests := []struct { - id string - fileType string - wantP thumbnail.Processor - wantE error - }{ - { - id: "fit", - fileType: "", - wantP: thumbnail.DefinableProcessor{Slug: "fit", Converter: imaging.Fit}, - wantE: nil, - }, - { - id: "fit", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "fit"}, - wantE: nil, - }, - { - id: "FIT", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "fit"}, - wantE: nil, - }, - { - id: "resize", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "resize"}, - wantE: nil, - }, - { - id: "RESIZE", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "resize"}, - wantE: nil, - }, - { - id: "fill", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "fill"}, - wantE: nil, - }, - { - id: "FILL", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "fill"}, - wantE: nil, - }, - { - id: "thumbnail", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "thumbnail"}, - wantE: nil, - }, - { - id: "THUMBNAIL", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{Slug: "thumbnail"}, - wantE: nil, - }, - { - id: "", - fileType: "jpg", - wantP: thumbnail.DefinableProcessor{}, - wantE: nil, - }, - { - id: "", - fileType: "gif", - wantP: thumbnail.DefinableProcessor{}, - wantE: nil, - }, - } - - assert := tAssert.New(t) - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - p, e := thumbnail.ProcessorFor(tt.id, tt.fileType) - assert.Equal(p.ID(), tt.wantP.ID()) - assert.Equal(e, tt.wantE) - }) - } - -} diff --git a/services/thumbnails/pkg/thumbnail/resolutions.go b/services/thumbnails/pkg/thumbnail/resolutions.go deleted file mode 100644 index 0a00054ddc..0000000000 --- a/services/thumbnails/pkg/thumbnail/resolutions.go +++ /dev/null @@ -1,101 +0,0 @@ -package thumbnail - -import ( - "fmt" - "image" - "math" - "strconv" - "strings" - - "github.com/pkg/errors" -) - -const ( - _resolutionSeparator = "x" -) - -// ParseResolution returns an image.Rectangle representing the resolution given as a string -func ParseResolution(s string) (image.Rectangle, error) { - parts := strings.Split(s, _resolutionSeparator) - if len(parts) != 2 { - return image.Rectangle{}, fmt.Errorf("failed to parse resolution: %s. Expected format x", s) - } - width, err := strconv.Atoi(parts[0]) - if err != nil { - return image.Rectangle{}, fmt.Errorf("width: %s has an invalid value. Expected an integer", parts[0]) - } - height, err := strconv.Atoi(parts[1]) - if err != nil { - return image.Rectangle{}, fmt.Errorf("height: %s has an invalid value. Expected an integer", parts[1]) - } - return image.Rect(0, 0, width, height), nil -} - -// Resolutions is a list of image.Rectangle representing resolutions. -type Resolutions []image.Rectangle - -// ParseResolutions creates an instance of Resolutions from resolution strings. -func ParseResolutions(strs []string) (Resolutions, error) { - rs := make(Resolutions, 0, len(strs)) - for _, s := range strs { - r, err := ParseResolution(s) - if err != nil { - return nil, errors.Wrap(err, "could not parse resolutions") - } - rs = append(rs, r) - } - return rs, nil -} - -// ClosestMatch returns the resolution which is closest to the provided resolution. -// If there is no exact match the resolution will be the next higher one. -// If the given resolution is bigger than all available resolutions the biggest available one is used. -func (rs Resolutions) ClosestMatch(requested image.Rectangle, sourceSize image.Rectangle) image.Rectangle { - isLandscape := sourceSize.Dx() > sourceSize.Dy() - sourceLen := dimensionLength(sourceSize, isLandscape) - requestedLen := dimensionLength(requested, isLandscape) - isSourceSmaller := sourceLen < requestedLen - - // We don't want to scale images up. - if isSourceSmaller { - return sourceSize - } - - if len(rs) == 0 { - return requested - } - - var match image.Rectangle - // Since we want to search for the smallest difference we start with the highest possible number - minDiff := math.MaxInt32 - - for _, current := range rs { - cLen := dimensionLength(current, isLandscape) - diff := requestedLen - cLen - if diff > 0 { - // current is smaller - continue - } - - // Convert diff to positive value - // Multiplying by -1 is safe since we aren't getting positive numbers here - // because of the check above - absDiff := diff * -1 - if absDiff < minDiff { - minDiff = absDiff - match = current - } - } - - if (match == image.Rectangle{}) { - match = rs[len(rs)-1] - } - return match -} - -func dimensionLength(rect image.Rectangle, isLandscape bool) int { - if isLandscape { - return rect.Dx() - } - return rect.Dy() -} diff --git a/services/thumbnails/pkg/thumbnail/resolutions_test.go b/services/thumbnails/pkg/thumbnail/resolutions_test.go deleted file mode 100644 index 019cc4d16d..0000000000 --- a/services/thumbnails/pkg/thumbnail/resolutions_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package thumbnail - -import ( - "image" - "testing" -) - -func TestInitWithEmptyArray(t *testing.T) { - rs, err := ParseResolutions([]string{}) - if err != nil { - t.Errorf("Init with an empty array should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != 0 { - t.Error("Init with an empty array should return an empty Resolutions instance.\n") - } -} - -func TestInitWithNil(t *testing.T) { - rs, err := ParseResolutions(nil) - if err != nil { - t.Errorf("Init with nil parameter should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != 0 { - t.Error("Init with nil parameter should return an empty Resolutions instance.\n") - } -} - -func TestInitWithInvalidValuesInArray(t *testing.T) { - _, err := ParseResolutions([]string{"invalid"}) - if err == nil { - t.Error("Init with invalid parameter should fail.\n") - } -} - -func TestInit(t *testing.T) { - rs, err := ParseResolutions([]string{"16x16"}) - if err != nil { - t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != 1 { - t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), 1) - } -} - -func TestInitWithMultipleResolutions(t *testing.T) { - rStrs := []string{"16x16", "32x32", "64x64", "128x128"} - rs, err := ParseResolutions(rStrs) - if err != nil { - t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != len(rStrs) { - t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), len(rStrs)) - } -} - -func TestClosestMatchWithEmptyResolutions(t *testing.T) { - rs, _ := ParseResolutions(nil) - want := image.Rect(0, 0, 24, 24) - imgSize := image.Rect(0, 0, 24, 24) - - r := rs.ClosestMatch(want, imgSize) - if r.Dx() != want.Dx() || r.Dy() != want.Dy() { - t.Errorf("ClosestMatch from empty resolutions should return the given resolution") - } -} - -func TestClosestMatch(t *testing.T) { - rs, _ := ParseResolutions([]string{"16x16", "24x24", "32x32", "64x64", "128x128"}) - - testData := [][]image.Rectangle{ - {image.Rect(0, 0, 17, 17), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 24, 24)}, - {image.Rect(0, 0, 12, 17), image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 24, 24)}, - {image.Rect(0, 0, 24, 24), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 24, 24)}, - {image.Rect(0, 0, 20, 20), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 24, 24)}, - {image.Rect(0, 0, 20, 80), image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 128, 128)}, - {image.Rect(0, 0, 80, 20), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 128, 128)}, - {image.Rect(0, 0, 48, 48), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 64, 64)}, - {image.Rect(0, 0, 1024, 1024), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 128, 128)}, - {image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 256, 36), image.Rect(0, 0, 256, 36)}, - } - - for _, row := range testData { - given := row[0] - imgSize := row[1] - expected := row[2] - - match := rs.ClosestMatch(given, imgSize) - - if match != expected { - t.Errorf("Expected resolution %dx%d got %dx%d", expected.Dx(), expected.Dy(), match.Dx(), match.Dy()) - } - } -} - -func TestParseWithEmptyString(t *testing.T) { - if _, err := ParseResolution(""); err == nil { - t.Error("Parse with empty string should return an error.") - } -} - -func TestParseWithInvalidWidth(t *testing.T) { - _, err := ParseResolution("invalidx42") - if err == nil { - t.Error("Parse with invalid width should return an error.") - } -} - -func TestParseWithInvalidHeight(t *testing.T) { - _, err := ParseResolution("42xinvalid") - if err == nil { - t.Error("Parse with invalid height should return an error.") - } -} - -func TestParseResolution(t *testing.T) { - rStr := "42x23" - r, _ := ParseResolution(rStr) - if r.Dx() != 42 || r.Dy() != 23 { - t.Errorf("Expected resolution %s got %s", rStr, r.String()) - } -} diff --git a/services/thumbnails/pkg/thumbnail/storage/filesystem.go b/services/thumbnails/pkg/thumbnail/storage/filesystem.go deleted file mode 100644 index dd55762d4e..0000000000 --- a/services/thumbnails/pkg/thumbnail/storage/filesystem.go +++ /dev/null @@ -1,118 +0,0 @@ -package storage - -import ( - "io/fs" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/pkg/errors" - - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" -) - -const ( - filesDir = "files" -) - -// NewFileSystemStorage creates a new instance of FileSystem -func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) FileSystem { - return FileSystem{ - root: cfg.RootDirectory, - logger: logger, - } -} - -// FileSystem represents a storage for the thumbnails using the local file system. -type FileSystem struct { - root string - logger log.Logger -} - -// Stat returns if a file for the given key exists on the filesystem -func (s FileSystem) Stat(key string) bool { - img := filepath.Join(s.root, filesDir, key) - if _, err := os.Stat(img); err != nil { - return false - } - return true -} - -// Get returns the file content for the given key -func (s FileSystem) Get(key string) ([]byte, error) { - img := filepath.Join(s.root, filesDir, key) - content, err := os.ReadFile(img) - if err != nil { - if !errors.Is(err, fs.ErrNotExist) { - s.logger.Debug().Str("err", err.Error()).Str("key", key).Msg("could not load thumbnail from store") - } - return nil, err - } - return content, nil -} - -// Put stores image data in the file system for the given key -func (s FileSystem) Put(key string, img []byte) error { - imgPath := filepath.Join(s.root, filesDir, key) - dir := filepath.Dir(imgPath) - if err := os.MkdirAll(dir, 0700); err != nil { - return errors.Wrapf(err, "error while creating directory %s", dir) - } - - if _, err := os.Stat(imgPath); os.IsNotExist(err) { - f, err := os.CreateTemp(dir, "tmpthumb") - if err != nil { - return errors.Wrapf(err, "could not create temporary file for \"%s\"", key) - } - defer f.Close() - - // if there was a problem writing, remove the temporary file - if _, writeErr := f.Write(img); writeErr != nil { - if remErr := os.Remove(f.Name()); remErr != nil { - return errors.Wrapf(remErr, "could not cleanup temporary file for \"%s\"", key) - } - return errors.Wrapf(writeErr, "could not write to temporary file for \"%s\"", key) - } - - // if there wasn't a problem, ensure the data is written into disk - if synErr := f.Sync(); synErr != nil { - return errors.Wrapf(synErr, "could not sync temporary file data into the disk for \"%s\"", key) - } - - // rename the temporary file to the final file - if renErr := os.Rename(f.Name(), imgPath); renErr != nil { - // if we couldn't rename, remove the temporary file - if remErr := os.Remove(f.Name()); remErr != nil { - return errors.Wrapf(remErr, "rename failed and could not cleanup temporary file for \"%s\"", key) - } - return errors.Wrapf(renErr, "could not rename temporary file to \"%s\"", key) - } - } - - return nil -} - -// BuildKey generate the unique key for a thumbnail. -// The key is structure as follows: -// -// ///x. -// -// e.g. 97/9f/4c8db98f7b82e768ef478d3c8612/500x300.png -// -// The key also represents the path to the thumbnail in the filesystem under the configured root directory. -func (s FileSystem) BuildKey(r Request) string { - checksum := r.Checksum - filetype := r.Types[0] - - parts := []string{strconv.Itoa(r.Resolution.Dx()), "x", strconv.Itoa(r.Resolution.Dy())} - - if r.Characteristic != "" { - parts = append(parts, "-", r.Characteristic) - } - - parts = append(parts, ".", filetype) - - return filepath.Join(checksum[:2], checksum[2:4], checksum[4:], strings.Join(parts, "")) -} diff --git a/services/thumbnails/pkg/thumbnail/storage/filesystem_test.go b/services/thumbnails/pkg/thumbnail/storage/filesystem_test.go deleted file mode 100644 index d8d4a78ca5..0000000000 --- a/services/thumbnails/pkg/thumbnail/storage/filesystem_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package storage_test - -import ( - "image" - "testing" - - tAssert "github.com/stretchr/testify/assert" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" -) - -func TestFileSystem_BuildKey(t *testing.T) { - tests := []struct { - r storage.Request - want string - }{ - { - r: storage.Request{ - Checksum: "120EA8A25E5D487BF68B5F7096440019", - Types: []string{"png", "jpg"}, - Resolution: image.Rectangle{ - Min: image.Point{ - X: 1, - Y: 2, - }, - Max: image.Point{ - X: 3, - Y: 4, - }, - }, - Characteristic: "", - }, - want: "12/0E/A8A25E5D487BF68B5F7096440019/2x2.png", - }, - { - r: storage.Request{ - Checksum: "120EA8A25E5D487BF68B5F7096440019", - Types: []string{"png", "jpg"}, - Resolution: image.Rectangle{ - Min: image.Point{ - X: 1, - Y: 2, - }, - Max: image.Point{ - X: 3, - Y: 4, - }, - }, - Characteristic: "fill", - }, - want: "12/0E/A8A25E5D487BF68B5F7096440019/2x2-fill.png", - }, - } - - s := storage.FileSystem{} - assert := tAssert.New(t) - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - assert.Equal(s.BuildKey(tt.r), tt.want) - }) - } - -} diff --git a/services/thumbnails/pkg/thumbnail/storage/storage.go b/services/thumbnails/pkg/thumbnail/storage/storage.go deleted file mode 100644 index 143ab69bc4..0000000000 --- a/services/thumbnails/pkg/thumbnail/storage/storage.go +++ /dev/null @@ -1,32 +0,0 @@ -package storage - -import ( - "image" -) - -// Request combines different attributes needed for storage operations. -type Request struct { - // The checksum of the source file - // Will be used to determine if a thumbnail exists - Checksum string - // Types provided by the encoder. - // Contains the mimetypes of the thumbnail. - // In case of jpg/jpeg it will contain both. - Types []string - // The resolution of the thumbnail - Resolution image.Rectangle - // Characteristic defines the different image characteristics, - // for example, if it's scaled up to fit in the bounding box or not, - // is it a chroma version of the image, and so on... - // the main propose for this is to be able to differentiate between images which have - // the same resolution but different characteristics. - Characteristic string -} - -// Storage defines the interface for a thumbnail store. -type Storage interface { - Stat(key string) bool - Get(key string) ([]byte, error) - Put(key string, img []byte) error - BuildKey(r Request) string -} diff --git a/services/thumbnails/pkg/thumbnail/thumbnail.go b/services/thumbnails/pkg/thumbnail/thumbnail.go deleted file mode 100644 index 1f07d96aae..0000000000 --- a/services/thumbnails/pkg/thumbnail/thumbnail.go +++ /dev/null @@ -1,131 +0,0 @@ -package thumbnail - -import ( - "bytes" - "image" - "mime" - - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" -) - -// Request bundles information needed to generate a thumbnail for a file -type Request struct { - Resolution image.Rectangle - Encoder Encoder - Generator Generator - Checksum string -} - -// Manager is responsible for generating thumbnails -type Manager interface { - // Generate creates a thumbnail and stores it. - // The function returns a key with which the actual file can be retrieved. - Generate(r Request, img any) (string, error) - // CheckThumbnail checks if a thumbnail with the requested attributes exists. - // The function will return a status if the file exists and the key to the file. - CheckThumbnail(r Request) (string, bool) - // GetThumbnail will load the thumbnail from the storage and return its content. - GetThumbnail(key string) ([]byte, error) -} - -// NewSimpleManager creates a new instance of SimpleManager -func NewSimpleManager(resolutions Resolutions, storage storage.Storage, logger log.Logger, maxInputWidth, maxInputHeight int) SimpleManager { - return SimpleManager{ - storage: storage, - logger: logger, - resolutions: resolutions, - maxDimension: image.Point{X: maxInputWidth, Y: maxInputHeight}, - } -} - -// SimpleManager is a simple implementation of Manager -type SimpleManager struct { - storage storage.Storage - logger log.Logger - resolutions Resolutions - maxDimension image.Point -} - -// Generate creates a thumbnail and stores it -func (s SimpleManager) Generate(r Request, img any) (string, error) { - var match image.Rectangle - - inputDimensions, err := r.Generator.Dimensions(img) - if err != nil { - return "", err - } - match = s.resolutions.ClosestMatch(r.Resolution, inputDimensions) - - // validate max input image dimensions - 6016x4000 - if inputDimensions.Size().X > s.maxDimension.X || inputDimensions.Size().Y > s.maxDimension.Y { - return "", errors.ErrImageTooLarge - } - - thumbnail, err := r.Generator.Generate(match, img) - if err != nil { - return "", err - } - - buf := new(bytes.Buffer) - if err := r.Encoder.Encode(buf, thumbnail); err != nil { - return "", err - } - - k := s.storage.BuildKey(mapToStorageRequest(r)) - if err := s.storage.Put(k, buf.Bytes()); err != nil { - s.logger.Error().Err(err).Msg("could not store thumbnail") - return "", err - } - return k, nil -} - -// CheckThumbnail checks if a thumbnail with the requested attributes exists. -func (s SimpleManager) CheckThumbnail(r Request) (string, bool) { - k := s.storage.BuildKey(mapToStorageRequest(r)) - return k, s.storage.Stat(k) -} - -// GetThumbnail will load the thumbnail from the storage and return its content. -func (s SimpleManager) GetThumbnail(key string) ([]byte, error) { - return s.storage.Get(key) -} - -func mapToStorageRequest(r Request) storage.Request { - return storage.Request{ - Checksum: r.Checksum, - Resolution: r.Resolution, - Types: r.Encoder.Types(), - Characteristic: r.Generator.ProcessorID(), - } -} - -// IsMimeTypeSupported validate if the mime type is supported -func IsMimeTypeSupported(m string) bool { - mimeType, _, err := mime.ParseMediaType(m) - if err != nil { - return false - } - _, supported := SupportedMimeTypes[mimeType] - return supported -} - -// PrepareRequest prepare the request based on image parameters -func PrepareRequest(width, height int, tType, checksum, pID string) (Request, error) { - generator, err := GeneratorFor(tType, pID) - if err != nil { - return Request{}, err - } - encoder, err := EncoderForType(tType) - if err != nil { - return Request{}, err - } - - return Request{ - Resolution: image.Rect(0, 0, width, height), - Generator: generator, - Encoder: encoder, - Checksum: checksum, - }, nil -} diff --git a/services/thumbnails/pkg/thumbnail/thumbnail_test.go b/services/thumbnails/pkg/thumbnail/thumbnail_test.go deleted file mode 100644 index 0e8c399273..0000000000 --- a/services/thumbnails/pkg/thumbnail/thumbnail_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package thumbnail - -import ( - "image" - "os" - "path" - "path/filepath" - "reflect" - "testing" - - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/preprocessor" - "github.com/stretchr/testify/assert" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail/storage" -) - -type NoOpManager struct { - storage.Storage -} - -func (m NoOpManager) BuildKey(_ storage.Request) string { - return "" -} - -func (m NoOpManager) Set(_, _ string, _ []byte) error { - return nil -} - -func BenchmarkGet(b *testing.B) { - - sut := NewSimpleManager( - Resolutions{}, - NoOpManager{}, - log.NewLogger(), - 6016, - 4000, - ) - - res, _ := ParseResolution("32x32") - req := Request{ - Resolution: res, - Checksum: "1872ade88f3013edeb33decd74a4f947", - } - cwd, _ := os.Getwd() - p := filepath.Join(cwd, "../../testdata/test.png") - f, _ := os.Open(p) - defer f.Close() - img, ext, _ := image.Decode(f) - req.Encoder, _ = EncoderForType(ext) - for i := 0; i < b.N; i++ { - _, _ = sut.Generate(req, img) - } -} - -func TestPrepareRequest(t *testing.T) { - type args struct { - width int - height int - tType string - checksum string - } - tests := []struct { - name string - args args - want Request - wantErr bool - }{ - { - name: "Test successful prepare the request for jpg", - args: args{ - width: 32, - height: 32, - tType: "jpg", - checksum: "1872ade88f3013edeb33decd74a4f947", - }, - want: Request{ - Resolution: image.Rect(0, 0, 32, 32), - Encoder: JpegEncoder{}, - Generator: SimpleGenerator{}, - Checksum: "1872ade88f3013edeb33decd74a4f947", - }, - }, - { - name: "Test successful prepare the request for png", - args: args{ - width: 32, - height: 32, - tType: "png", - checksum: "1872ade88f3013edeb33decd74a4f947", - }, - want: Request{ - Resolution: image.Rect(0, 0, 32, 32), - Encoder: PngEncoder{}, - Generator: SimpleGenerator{}, - Checksum: "1872ade88f3013edeb33decd74a4f947", - }, - }, - { - name: "Test successful prepare the request for gif", - args: args{ - width: 32, - height: 32, - tType: "gif", - checksum: "1872ade88f3013edeb33decd74a4f947", - }, - want: Request{ - Resolution: image.Rect(0, 0, 32, 32), - Encoder: GifEncoder{}, - Generator: GifGenerator{}, - Checksum: "1872ade88f3013edeb33decd74a4f947", - }, - }, - { - name: "Test error when prepare the request for bmp", - args: args{ - width: 32, - height: 32, - tType: "bmp", - checksum: "1872ade88f3013edeb33decd74a4f947", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := PrepareRequest(tt.args.width, tt.args.height, tt.args.tType, tt.args.checksum, "") - if (err != nil) != tt.wantErr { - t.Errorf("PrepareRequest() error = %v, wantErr %v", err, tt.wantErr) - return - } - // funcs are not reflactable, ignore - if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreFields(Request{}, "Generator")); diff != "" { - t.Errorf("PrepareRequest(): %v", diff) - } - if reflect.TypeOf(got.Generator) != reflect.TypeOf(tt.want.Generator) { - t.Errorf("PrepareRequest() = %v, want %v", reflect.TypeOf(got.Generator), reflect.TypeOf(tt.want.Generator)) - } - }) - } -} - -func TestPreviewGenerationTooBigImage(t *testing.T) { - tests := []struct { - name string - fileName string - mimeType string - }{ - {name: "png", mimeType: "image/png", fileName: "../../testdata/test.png"}, - {name: "jpg", mimeType: "image/jpeg", fileName: "../../testdata/test.jpg"}, - {name: "ggs", mimeType: "application/vnd.geogebra.slides", fileName: "../../testdata/test.ggs"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sut := NewSimpleManager( - Resolutions{}, - NoOpManager{}, - log.NewLogger(), - 1024, - 768, - ) - - res, _ := ParseResolution("32x32") - req := Request{ - Resolution: res, - Checksum: "1872ade88f3013edeb33decd74a4f947", - } - cwd, _ := os.Getwd() - p := filepath.Join(cwd, tt.fileName) - f, _ := os.Open(p) - defer f.Close() - - preproc := preprocessor.ForType(tt.mimeType, nil) - convert, err := preproc.Convert(f) - if err != nil { - return - } - - ext := path.Ext(tt.fileName) - req.Encoder, _ = EncoderForType(ext) - req.Generator, err = GeneratorFor(ext, "fit") - if err != nil { - return - } - generate, err := sut.Generate(req, convert) - if err != nil { - return - } - assert.ErrorIs(t, err, errors.ErrImageTooLarge) - assert.Equal(t, "", generate) - }) - } -} diff --git a/services/thumbnails/testdata/test.ggs b/services/thumbnails/testdata/test.ggs deleted file mode 100644 index c0f2a0aa38a776814700af3038b87bcc40062f80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7486 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 diff --git a/services/thumbnails/testdata/test.jpg b/services/thumbnails/testdata/test.jpg deleted file mode 100644 index 46f42e6eada40e95ce8dc77fe63918e8e09653c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34507 zcmeI1TToPW7{I@C*$WE-3#%#cu&xV%qYk%}d`Vc4wt-w1G_9uE3zuzJ#N8#RsTnnr z@(I*Zd1*n-YI?{nT0Uvfb!tXLx!7d}yV~@Cl6D*C^qm9jGSh5RJ?{5?JGUO#4l}<+nNX%+x~4nO3{YPWD*?a=dLauW%mjRakOMj`2iPj)#`1@R!nqm|K+KNU z_Xa?otH(9f5LT%lv7@mMz|5ckRWc3sa*n$u2$Dd zP?TtRk-sVI!#%QM!3JMPD1!MTn6K@Mbg21Z%q_lFk00~bFkcnI3&eav9rw=WKdX6* zmLn9*Lyc|+_GZD{+Oi<`F35eYp&-_jV~yP(Y{&J*ZL_&w&7V~BU|T0%n-*$^X-(~0 z8t`bxcOKM&6Y9YQR`5b61fUHfuo{o;I1&aop07$UKS!JI!dYIN6#yU3sD>R71dlc! zR0dds5#QTzb6Kf2B&1aV=)P8znS8ui4glQhQhP(Jb>we*?q%v>UlUi z4$z2uRrfi-kpX~$7F_$m1brKELTumuv1V@nlrNwMj9R1{jp>&3^mI$E1tZs%tA?$} zrbW?QlxVSDjmlUErqtBTjLf`@jJ)bK1#7DRb0}Zo^VY4L1+!7EFzk{98;E8>G7HME zfLku;1TECLTYx~%0TmHO<>9)oSCrF3`y zpm9aj$#aX{Bh&UWZ)i`7X~}I%m*wOa++J9;viy#<>niSC@2IZXSbMkgo`%LPTkk_` zK7UhlOKafKaHR9GuI|U5c)I7Ay}i#qw{QP}gU`Qk=5FA^77P`Z@&HR`yYP1`qQ=RGr!!p`Ri}L|ABvV zX4}Efzx89reneT8WP{p|Aa<$Enq}Sc5`9+XW`k#Ewykux(Nfhvc=BAziZb`~VsB`~ zw8UOMm48ibN$cp}HMHk{brfqT){k--(j)!C zW=|#nnLU{RWcFkNklB+7KxR)S0GU0R0A%)L0+8902|#8~CIFc|nE+(=WCD=clL!CW=|#nnLU{RWcFkNklB+7KxR)S0GU0R0A%)L0+8902|#8~ vCIFc|nE+(=WCD=clL!CW=|#nnLU{R;459&x$%kM~sby}!%O(aUvt&ALd3%elsL zeL8O5StU{B%3#Z(+?F{r^4J`er4ORxzhCh!6@MQ2ueLsH{;cg10s@`sGZSwekvWFkYOdX6rAbD%LH#ro=#drqoB3LZ7Q8 zr^GkOt)8>pB73S-_T9<*4JKU=pSII4Tf$Q(iBrf$W5vQAuAC#8*F85*SoXQ4zy8Em z?0wwHyCAZB&imx!ne!M9ra$n? z|Gk)^mS!_$;igd&Urs3oCPQ(S>hT|;+&tGo0?a` z;9QiNSdyBeP@Y+mq2TW68xY>eC(gj2z~JfP7*cWT%}qzf1_g!#4l10nJPH3zCNx)y zC*HSx{r4NtAP{&^70d)-HLx*DKv)Wg7(fXJ2s#WJ7$Gbro&*S^fn!t|C?rM$1e_j5 zQwTU1Mzah!7)A>Ra4?J(4v=6NEgVJ*hta$+nim-8R4@FPstazeCbAho0^Om7kq5#O zNMvA!uv{b#Ko|^yqsl-bF&ZG?^e~!2z`-z@Wx&BOS~x(0VYF} Date: Fri, 4 Sep 2026 09:26:43 +0200 Subject: [PATCH 4/5] address review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/thumbnails/README.md | 7 +- services/thumbnails/pkg/config/config.go | 5 + .../thumbnails/pkg/service/http/v0/push.go | 126 +++++-- .../pkg/service/http/v0/push_imaging.go | 42 ++- .../pkg/service/http/v0/push_test.go | 106 ++++++ .../pkg/service/http/v0/push_vips.go | 55 +-- .../thumbnails/pkg/service/http/v0/service.go | 2 + services/webdav/pkg/config/config.go | 9 +- .../pkg/config/defaults/defaultconfig.go | 3 +- services/webdav/pkg/dav/requests/thumbnail.go | 43 ++- services/webdav/pkg/generator/extensions.go | 20 +- .../webdav/pkg/generator/generator_test.go | 19 +- services/webdav/pkg/generator/url.go | 19 +- .../webdav/pkg/preprocessor/preprocessor.go | 24 +- .../pkg/preprocessor/preprocessor_vips.go | 17 +- services/webdav/pkg/service/v0/service.go | 30 +- .../webdav/pkg/thumbnail/cache/filecache.go | 4 +- .../webdav/pkg/thumbnail/cache/s3cache.go | 9 +- .../pkg/thumbnail/workflow/encode_imaging.go | 2 +- .../pkg/thumbnail/workflow/encode_vips.go | 2 +- .../webdav/pkg/thumbnail/workflow/workflow.go | 316 ++++++++++++------ .../pkg/thumbnail/workflow/workflow_test.go | 114 ++++++- 22 files changed, 721 insertions(+), 253 deletions(-) diff --git a/services/thumbnails/README.md b/services/thumbnails/README.md index 8bfaeafb9a..94f0d765e4 100644 --- a/services/thumbnails/README.md +++ b/services/thumbnails/README.md @@ -2,6 +2,8 @@ The thumbnails service is a stateless image resizer. It exposes an imagor-compatible push endpoint that accepts an original image as a multipart upload and returns the resized thumbnail encoded in the requested format. +> **Warning:** This service performs no authentication or authorization. By default it binds to `127.0.0.1:9186` so only local processes (the webdav service) can reach it. If you change `THUMBNAILS_HTTP_ADDR` to a non-loopback address, put it behind your reverse proxy's auth layer or an internal network boundary — anyone who can reach the endpoint can upload and retrieve arbitrary images through it. + ## Push Endpoint The webdav service (which owns the complete thumbnail workflow) POSTs the source file to this endpoint and receives the processed image back. @@ -12,7 +14,9 @@ The webdav service (which owns the complete thumbnail workflow) POSTs the source | `POST /unsafe/fit-in/{width}x{height}` (optionally `/filters:format({format})`) | Scale to fit within width x height, preserving aspect ratio and never upscaling (letterboxed) | | `POST /unsafe/stretch/{width}x{height}` (optionally `/filters:format({format})`) | Resize to the exact width x height without preserving aspect ratio (distorts) | -The filters segment is captured whole and parsed; only the `format` filter is meaningful to this executor, other filters are ignored. The `format` filter is optional: when absent the input's own format is preserved (detected via the imaging library), matching imagor. Inputs we cannot re-encode (e.g. webp, tiff, bmp) fall back to JPEG, mirroring imagor's default for unsavable sources. +The filters segment is captured whole and parsed; the `format` and `no_upscale` filters are meaningful to this executor, other filters are ignored. The `format` filter is optional: when absent the input's own format is preserved (detected from the image header), matching imagor. Inputs we cannot re-encode (e.g. webp, tiff, bmp) fall back to JPEG, mirroring imagor's default for unsavable sources. + +Like real imagor, the default fill resize **upscales** small sources to fill the box exactly (a 100x100 source requested at `320x320` returns a 320x320 image). Adding the `no_upscale()` filter caps the result at the source size instead (the same request returns 100x100). The `fit-in` route never upscales regardless of filters. The webdav service selects the route based on the requested processor. The full mapping is: @@ -38,6 +42,7 @@ The request body is a `multipart/form-data` upload with a single file field name |---|---| | `THUMBNAILS_HTTP_ADDR` | Bind address of the HTTP service (default `127.0.0.1:9186`) | | `THUMBNAILS_LOG_LEVEL` | Log level (`panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`) | +| `THUMBNAILS_MAX_CONCURRENT_REQUESTS` | Maximum number of thumbnail requests decoded and resized in parallel. Default is 0 (unlimited). Requests arriving while the limit is reached get HTTP 429 with a `Retry-After` header | ## Using libvips for Image Processing diff --git a/services/thumbnails/pkg/config/config.go b/services/thumbnails/pkg/config/config.go index 0f8d8d4c87..6d39e03900 100644 --- a/services/thumbnails/pkg/config/config.go +++ b/services/thumbnails/pkg/config/config.go @@ -32,4 +32,9 @@ type Thumbnail struct { MaxInputWidth int `yaml:"max_input_width" env:"THUMBNAILS_MAX_INPUT_WIDTH" desc:"The maximum width of an input image which is being processed." introductionVersion:"1.0.0"` // MaxInputHeight is the maximum height of an input image which is being processed. MaxInputHeight int `yaml:"max_input_height" env:"THUMBNAILS_MAX_INPUT_HEIGHT" desc:"The maximum height of an input image which is being processed." introductionVersion:"1.0.0"` + // MaxConcurrentRequests limits the number of thumbnail requests that are + // decoded and resized in parallel. 0 (the default) means unlimited, matching + // main's THUMBNAILS_MAX_CONCURRENT_REQUESTS. Requests arriving while the limit + // is reached get HTTP 429 Too Many Requests. + MaxConcurrentRequests int `yaml:"max_concurrent_requests" env:"THUMBNAILS_MAX_CONCURRENT_REQUESTS" desc:"Maximum number of concurrent thumbnail generation requests. Default is 0 which is unlimited." introductionVersion:"1.0.0"` } diff --git a/services/thumbnails/pkg/service/http/v0/push.go b/services/thumbnails/pkg/service/http/v0/push.go index f2e6523e0f..9e5c7490f0 100644 --- a/services/thumbnails/pkg/service/http/v0/push.go +++ b/services/thumbnails/pkg/service/http/v0/push.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/go-chi/chi/v5" - "github.com/kovidgoyal/imaging" ) // gifMagic is the leading signature of a GIF file (GIF87a or GIF89a). It guards @@ -40,6 +39,13 @@ var errInvalid = fmt.Errorf("invalid") // imagor. It returns only imagor's defined status codes: 400 for invalid requests or a // file exceeding the max size, 422 for an image exceeding the max resolution. func (s Thumbnails) pushHandler(w http.ResponseWriter, r *http.Request) { + ok, retryAfter := s.limiter.acquire() + if !ok { + writeTooManyRequests(w, retryAfter) + return + } + defer s.limiter.release() + width, err := parseDim(r, "width") if err != nil { writeInvalid(w, "invalid width") @@ -80,11 +86,13 @@ func (s Thumbnails) pushHandler(w http.ResponseWriter, r *http.Request) { return } - ext, hasFormat, err := outputFormatFromFilters(chi.URLParam(r, "filters")) + filtersSegment := chi.URLParam(r, "filters") + ext, hasFormat, err := outputFormatFromFilters(filtersSegment) if err != nil { writeInvalid(w, err.Error()) return } + noUpscale := filterPresent(filtersSegment, "no_upscale") if !hasFormat { // No format filter: preserve the input's own format (imagor default), // detected via imaging. Only runs on this path, so the common webdav @@ -92,25 +100,29 @@ func (s Thumbnails) pushHandler(w http.ResponseWriter, r *http.Request) { ext = inputFormatViaImaging(imgData) } + // imagor ErrMaxResolutionExceeded: the declared input exceeds the configured + // maximum width or height. Checked against the image header BEFORE any + // decoding so a dimension bomb (a tiny file declaring huge dimensions) is + // rejected without allocating the pixel buffer. webdav translates the + // resulting 422 into its legacy 403 response. + if s.maxWidth > 0 || s.maxHeight > 0 { + cfg, _, err := image.DecodeConfig(bytes.NewReader(imgData)) + if err == nil { + if (s.maxWidth > 0 && cfg.Width > s.maxWidth) || (s.maxHeight > 0 && cfg.Height > s.maxHeight) { + writeMaxResolution(w) + return + } + } + } + // The box and operation are chosen by webdav (the sizing brain); this service // is a dumb, imagor-like executor that just fits within the given box. - processed, srcBounds, err := processImage(bytes.NewReader(imgData), width, height, operation) + processed, err := processImage(bytes.NewReader(imgData), width, height, operation, noUpscale) if err != nil { writeInvalid(w, "failed to process image") return } - // imagor ErrMaxResolutionExceeded: the decoded input exceeds the configured - // maximum width/height. Checked against the source (pre-resize) bounds because - // this service owns the image; webdav translates the resulting 422 into its - // legacy 403 response. - if s.maxWidth > 0 || s.maxHeight > 0 { - if (s.maxWidth == 0 || srcBounds.Dx() > s.maxWidth) && (s.maxHeight == 0 || srcBounds.Dy() > s.maxHeight) { - writeMaxResolution(w) - return - } - } - var ( buf bytes.Buffer contentType string @@ -165,6 +177,51 @@ func writeMaxResolution(w http.ResponseWriter) { fmt.Fprintln(w, "maximum resolution exceeded") } +// concurrencyLimiter bounds the number of requests processed in parallel. It is +// nil when no limit is configured (THUMBNAILS_MAX_CONCURRENT_REQUESTS = 0). +type concurrencyLimiter struct { + tokens chan struct{} +} + +func newConcurrencyLimiter(limit int) *concurrencyLimiter { + if limit <= 0 { + return nil + } + tokens := make(chan struct{}, limit) + for i := 0; i < limit; i++ { + tokens <- struct{}{} + } + return &concurrencyLimiter{tokens: tokens} +} + +// acquire tries to take a slot without blocking. It reports whether the request +// may proceed and, when not, how many seconds to wait before retrying. +func (l *concurrencyLimiter) acquire() (bool, int) { + if l == nil { + return true, 0 + } + select { + case <-l.tokens: + return true, 0 + default: + return false, 1 + } +} + +func (l *concurrencyLimiter) release() { + if l == nil { + return + } + l.tokens <- struct{}{} +} + +// writeTooManyRequests responds with 429 and a short Retry-After header. +func writeTooManyRequests(w http.ResponseWriter, retryAfter int) { + w.Header().Set("Retry-After", strconv.Itoa(retryAfter)) + w.WriteHeader(http.StatusTooManyRequests) + fmt.Fprintln(w, "too many concurrent requests") +} + // mapFormatToExt maps imagor format names to the internal extension used by encoders. func mapFormatToExt(format string) string { switch strings.ToLower(format) { @@ -198,20 +255,22 @@ func outputFormatFromFilters(segment string) (ext string, hasFormat bool, err er return "", false, nil } -// inputFormatViaImaging detects the uploaded image's format with imaging and maps it -// to our output extension. jpeg/png/gif are kept; anything else (webp, tiff, bmp, or -// an undecodable body) falls back to jpeg, mirroring imagor's unsavable-source default. +// inputFormatViaImaging detects the uploaded image's format with the stdlib +// decoder registry (image.DecodeConfig reads only the header) and maps it to our +// output extension. jpeg/png/gif are kept; anything else (webp, tiff, bmp, or an +// undecodable body) falls back to jpeg, mirroring imagor's unsavable-source +// default. func inputFormatViaImaging(imgData []byte) string { - img, _, err := imaging.DecodeAll(bytes.NewReader(imgData)) - if err != nil || img == nil || img.Metadata == nil { + _, format, err := image.DecodeConfig(bytes.NewReader(imgData)) + if err != nil { return "jpg" } - switch img.Metadata.Format { - case imaging.PNG: + switch format { + case "png": return "png" - case imaging.GIF: + case "gif": return "gif" - default: // JPEG, WEBP, TIFF, BMP, UNKNOWN, ... + default: // jpeg, webp, tiff, bmp, ... return "jpg" } } @@ -226,6 +285,17 @@ func splitFilter(token string) (name, args string, ok bool) { return token[:i], token[i+1 : len(token)-1], true } +// filterPresent reports whether a named filter occurs in an imagor filters +// segment (e.g. "no_upscale():format(jpeg)"). +func filterPresent(segment, name string) bool { + for _, token := range strings.Split(segment, ":") { + if n, _, ok := splitFilter(token); ok && n == name { + return true + } + } + return false +} + // isGifReader reports whether the reader holds GIF data, based on the GIF magic // bytes. It peeks at the first 4 bytes and rewinds the stream so it can be read // again. This guard is required because gif.DecodeAll panics (nil dereference) @@ -252,11 +322,11 @@ func isGifReader(r io.Reader) bool { // Backend-specific function set in init() by push_imaging.go or push_vips.go. // For gif input it returns a *gif.GIF with every frame resized (preserving // animation); for other inputs it returns a single image.Image. The operation -// selects the resize/crop mode (fill, fit-in, stretch). The -// second return value is the decoded source's pixel bounds, reported before any -// resize so the max-resolution limit can be enforced against the input rather -// than the output. -var processImage func(io.Reader, int, int, string) (any, image.Rectangle, error) +// selects the resize/crop mode (fill, fit-in, stretch). noUpscale mirrors +// imagor's no_upscale() filter: when set, the default fill resize must not +// enlarge the source (fit-in already never upscales; stretch is exact and +// ignores it). +var processImage func(io.Reader, int, int, string, bool) (any, error) // encodeJPEG and encodePNG write the processed image to w in the requested // format. Set by init() in push_imaging.go / push_vips.go. The vips backend diff --git a/services/thumbnails/pkg/service/http/v0/push_imaging.go b/services/thumbnails/pkg/service/http/v0/push_imaging.go index 69c90be59e..083739c0c5 100644 --- a/services/thumbnails/pkg/service/http/v0/push_imaging.go +++ b/services/thumbnails/pkg/service/http/v0/push_imaging.go @@ -22,34 +22,39 @@ func init() { } // processImageImaging resizes the input using the imaging backend. The operation -// selects the resize/crop mode: fill (center-crop to the box), fit-in (fit within -// the box without cropping, never upscaling), or stretch (resize to the exact box). -func processImageImaging(r io.Reader, width, height int, operation string) (any, image.Rectangle, error) { +// selects the resize/crop mode: fill (center-crop to the box, upscaling by default +// like real imagor's default resize), fit-in (fit within the box without cropping, +// never upscaling), or stretch (resize to the exact box). noUpscale caps the +// default fill at the source size, mirroring imagor's no_upscale() filter. +func processImageImaging(r io.Reader, width, height int, operation string, noUpscale bool) (any, error) { if isGifReader(r) { g, err := gif.DecodeAll(r) if err == nil && len(g.Image) > 0 { - srcBounds := g.Image[0].Bounds() - return resizeGIF(g, width, height, operation), srcBounds, nil + return resizeGIF(g, width, height, operation, noUpscale), nil } } img, err := imaging.Decode(r, imaging.AutoOrientation(true)) if err != nil { - return nil, image.Rectangle{}, err + return nil, err } srcBounds := img.Bounds() switch operation { case OpStretch: - return imaging.Resize(img, width, height, imaging.Lanczos), srcBounds, nil + return imaging.Resize(img, width, height, imaging.Lanczos), nil case OpFitIn: if srcBounds.Dx() > width || srcBounds.Dy() > height { - return imaging.Fit(img, width, height, imaging.Lanczos), srcBounds, nil + return imaging.Fit(img, width, height, imaging.Lanczos), nil } - return img, srcBounds, nil + return img, nil default: // OpFill - return imaging.Thumbnail(img, width, height, imaging.Lanczos), srcBounds, nil + if noUpscale && srcBounds.Dx() <= width && srcBounds.Dy() <= height { + // imagor no_upscale(): never enlarge the source. + return img, nil + } + return imaging.Thumbnail(img, width, height, imaging.Lanczos), nil } } @@ -57,8 +62,9 @@ func processImageImaging(r io.Reader, width, height int, operation string) (any, // animation. It composites each frame onto a running canvas honoring the gif // disposal method, resizes with the requested processor, and re-pallettes the // result using Floyd-Steinberg dithering. Code adapted from -// https://github.com/willnorris/gifresize. -func resizeGIF(m *gif.GIF, width, height int, operation string) *gif.GIF { +// https://github.com/willnorris/gifresize. noUpscale caps the default fill at +// the source size, mirroring imagor's no_upscale() filter. +func resizeGIF(m *gif.GIF, width, height int, operation string, noUpscale bool) *gif.GIF { srcX, srcY := m.Config.Width, m.Config.Height b := image.Rect(0, 0, srcX, srcY) tmp := image.NewRGBA(b) @@ -79,7 +85,11 @@ func resizeGIF(m *gif.GIF, width, height int, operation string) *gif.GIF { processed = tmp } default: // OpFill - processed = imaging.Fill(tmp, width, height, imaging.Center, imaging.Lanczos) + if noUpscale && srcX <= width && srcY <= height { + processed = tmp + } else { + processed = imaging.Fill(tmp, width, height, imaging.Center, imaging.Lanczos) + } } m.Image[i] = imageToPaletted(processed, frame.Palette) @@ -92,8 +102,10 @@ func resizeGIF(m *gif.GIF, width, height int, operation string) *gif.GIF { } } - m.Config.Width = width - m.Config.Height = height + if !noUpscale || srcX > width || srcY > height { + m.Config.Width = width + m.Config.Height = height + } return m } diff --git a/services/thumbnails/pkg/service/http/v0/push_test.go b/services/thumbnails/pkg/service/http/v0/push_test.go index 4b80aa9d84..65c234c0d6 100644 --- a/services/thumbnails/pkg/service/http/v0/push_test.go +++ b/services/thumbnails/pkg/service/http/v0/push_test.go @@ -338,6 +338,35 @@ func TestPushEndpoint_Default_Upscales(t *testing.T) { } } +// TestPushEndpoint_Default_NoUpscaleFilter pins imagor's no_upscale() filter: +// with it, the default resize must not enlarge a small source. A 50x50 source +// requested at 200x200 comes back as 50x50 instead of 200x200. +func TestPushEndpoint_Default_NoUpscaleFilter(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestImage(50, 50) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/200x200/filters:no_upscale():format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + img, err := png.Decode(rec.Body) + if err != nil { + t.Fatalf("failed to decode response image: %v", err) + } + bounds := img.Bounds() + if bounds.Dx() != 50 || bounds.Dy() != 50 { + t.Errorf("no_upscale should keep the source at 50x50, got %dx%d", bounds.Dx(), bounds.Dy()) + } +} + // TestPushEndpoint_FitIn_LetterboxOutcome pins the opt-in "fit" processor's // outcome: aspect-preserving fit with no upscaling. A 200x100 source requested // at 100x100 must come back as exactly 100x50 — centered/letterboxed in the box, @@ -586,6 +615,26 @@ func TestPushEndpoint_MaxResolutionExceeded(t *testing.T) { } } +// TestPushEndpoint_MaxResolutionExceeded_OneAxis pins the per-axis check: a source +// that exceeds only the max width (but is well under the max height) must still be +// rejected with 422. This catches an inverted "both axes" condition. +func TestPushEndpoint_MaxResolutionExceeded_OneAxis(t *testing.T) { + mux := newTestMuxWithLimits(100, 100) + + imgBytes := createTestImage(500, 10) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/64x64/filters:format(png)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusUnprocessableEntity { + t.Errorf("expected status 422 for width-oversized input, got %d: %s", rec.Code, rec.Body.String()) + } +} + // TestPushEndpoint_WithinMaxResolution confirms an input within the configured // limits still succeeds (the 422 check must not over-trigger). func TestPushEndpoint_WithinMaxResolution(t *testing.T) { @@ -605,6 +654,63 @@ func TestPushEndpoint_WithinMaxResolution(t *testing.T) { } } +// TestConcurrencyLimiter pins the THUMBNAILS_MAX_CONCURRENT_REQUESTS behavior: +// up to N requests may proceed in parallel, the (N+1)th is rejected non-blocking +// with a 429. A limit of 0 disables the limiter entirely. +func TestConcurrencyLimiter(t *testing.T) { + t.Run("limit 2 rejects the third concurrent request", func(t *testing.T) { + l := newConcurrencyLimiter(2) + + if ok, _ := l.acquire(); !ok { + t.Fatal("first acquire should succeed") + } + if ok, _ := l.acquire(); !ok { + t.Fatal("second acquire should succeed") + } + ok, retryAfter := l.acquire() + if ok { + t.Fatal("third acquire should fail while the limit is reached") + } + if retryAfter <= 0 { + t.Errorf("expected a positive Retry-After hint, got %d", retryAfter) + } + + l.release() + if ok, _ := l.acquire(); !ok { + t.Fatal("acquire after release should succeed") + } + }) + + t.Run("zero limit is unlimited", func(t *testing.T) { + var l *concurrencyLimiter + for i := 0; i < 100; i++ { + if ok, _ := l.acquire(); !ok { + t.Fatalf("acquire %d should succeed without a limiter", i) + } + } + l.release() + }) + + t.Run("429 is returned when the handler is saturated", func(t *testing.T) { + s := Thumbnails{limiter: newConcurrencyLimiter(1)} + if ok, _ := s.limiter.acquire(); !ok { + t.Fatal("could not saturate the limiter") + } + defer s.limiter.release() + + req := httptest.NewRequest(http.MethodPost, "/unsafe/64x64/filters:format(png)/", nil) + rec := httptest.NewRecorder() + s.pushHandler(rec, req) + + if rec.Code != http.StatusTooManyRequests { + t.Errorf("expected 429 from a saturated limiter, got %d", rec.Code) + } + if rec.Header().Get("Retry-After") == "" { + t.Error("expected a Retry-After header on 429") + } + }) +} + func createTestJPEG(width, height int) []byte { img := image.NewRGBA(image.Rect(0, 0, width, height)) for y := 0; y < height; y++ { diff --git a/services/thumbnails/pkg/service/http/v0/push_vips.go b/services/thumbnails/pkg/service/http/v0/push_vips.go index dbf7431017..821ee2905f 100644 --- a/services/thumbnails/pkg/service/http/v0/push_vips.go +++ b/services/thumbnails/pkg/service/http/v0/push_vips.go @@ -21,25 +21,26 @@ func init() { } // processImageVips resizes the input using libvips. The operation selects the -// resize/crop mode: fill (center-crop to the box), fit-in (fit within the box -// without cropping, never upscaling), or stretch (resize to the exact box). -func processImageVips(r io.Reader, width, height int, operation string) (any, image.Rectangle, error) { +// resize/crop mode: fill (center-crop to the box, upscaling by default like real +// imagor's default resize), fit-in (fit within the box without cropping, never +// upscaling), or stretch (resize to the exact box). noUpscale caps the default +// fill at the source size, mirroring imagor's no_upscale() filter. +func processImageVips(r io.Reader, width, height int, operation string, noUpscale bool) (any, error) { if isGifReader(r) { g, err := gif.DecodeAll(r) if err == nil && len(g.Image) > 0 { - srcBounds := g.Image[0].Bounds() - return resizeGIFVips(g, width, height, operation), srcBounds, nil + return resizeGIFVips(g, width, height, operation, noUpscale), nil } } imgData, err := io.ReadAll(r) if err != nil { - return nil, image.Rectangle{}, err + return nil, err } m, err := vips.NewImageFromBuffer(imgData) if err != nil { - return nil, image.Rectangle{}, err + return nil, err } // Note: no defer m.Close() here. The ImageRef is returned to the caller and @@ -47,42 +48,48 @@ func processImageVips(r io.Reader, width, height int, operation string) (any, im // after exporting. Closing it here would free the underlying C image before the // export runs (use-after-free). - srcBounds := image.Rect(0, 0, m.Width(), m.Height()) - switch operation { case OpStretch: // Resize to the exact box without preserving aspect ratio. hScale := float64(width) / float64(m.Width()) vScale := float64(height) / float64(m.Height()) if err := m.ResizeWithVScale(hScale, vScale, vips.KernelLanczos3); err != nil { - return nil, image.Rectangle{}, err + return nil, err } case OpFitIn: // Fit within the box without cropping and never upscale (SizeDown). if err := m.ThumbnailWithSize(width, height, vips.InterestingNone, vips.SizeDown); err != nil { - return nil, image.Rectangle{}, err + return nil, err } default: // OpFill - // Center-crop to fill the box exactly. - if err := m.ThumbnailWithSize(width, height, vips.InterestingAttention, vips.SizeBoth); err != nil { - return nil, image.Rectangle{}, err + // Center-crop to fill the box exactly. SizeBoth matches real imagor's + // default resize (which upscales small sources); no_upscale() caps it + // at the source size via SizeDown. + size := vips.SizeBoth + if noUpscale { + size = vips.SizeDown + } + if err := m.ThumbnailWithSize(width, height, vips.InterestingAttention, size); err != nil { + return nil, err } } if err := m.RemoveMetadata(); err != nil { - return nil, image.Rectangle{}, err + return nil, err } // Keep the image in libvips form so the encode hooks can export it via // ExportJpeg/ExportPng (matching the legacy pipeline's progressive JPEG and // quality-80 output) instead of a lossy PNG round-trip + stdlib re-encode. - return m, srcBounds, nil + return m, nil } // resizeGIFVips resizes every frame of an animated gif while preserving the // animation, compositing each frame onto a running canvas honoring the gif -// disposal method and re-palletting with Floyd-Steinberg dithering. -func resizeGIFVips(m *gif.GIF, width, height int, operation string) *gif.GIF { +// disposal method and re-palletting with Floyd-Steinberg dithering. noUpscale +// caps the default fill at the source size, mirroring imagor's no_upscale() +// filter. +func resizeGIFVips(m *gif.GIF, width, height int, operation string, noUpscale bool) *gif.GIF { srcX, srcY := m.Config.Width, m.Config.Height b := image.Rect(0, 0, srcX, srcY) tmp := image.NewRGBA(b) @@ -103,7 +110,11 @@ func resizeGIFVips(m *gif.GIF, width, height int, operation string) *gif.GIF { processed = tmp } default: // OpFill - processed = imaging.Fill(tmp, width, height, imaging.Center, imaging.Lanczos) + if noUpscale && srcX <= width && srcY <= height { + processed = tmp + } else { + processed = imaging.Fill(tmp, width, height, imaging.Center, imaging.Lanczos) + } } m.Image[i] = imageToPalettedVips(processed, frame.Palette) @@ -116,8 +127,10 @@ func resizeGIFVips(m *gif.GIF, width, height int, operation string) *gif.GIF { } } - m.Config.Width = width - m.Config.Height = height + if !noUpscale || srcX > width || srcY > height { + m.Config.Width = width + m.Config.Height = height + } return m } diff --git a/services/thumbnails/pkg/service/http/v0/service.go b/services/thumbnails/pkg/service/http/v0/service.go index 99ce58379a..066c76288f 100644 --- a/services/thumbnails/pkg/service/http/v0/service.go +++ b/services/thumbnails/pkg/service/http/v0/service.go @@ -38,6 +38,7 @@ func NewService(opts ...Option) Service { mux: m, maxWidth: limits.MaxInputWidth, maxHeight: limits.MaxInputHeight, + limiter: newConcurrencyLimiter(limits.MaxConcurrentRequests), } // Push-based thumbnail generation endpoint (imagor-compatible). The optional @@ -64,6 +65,7 @@ type Thumbnails struct { mux *chi.Mux maxWidth int maxHeight int + limiter *concurrencyLimiter } // ServeHTTP implements the Service interface. diff --git a/services/webdav/pkg/config/config.go b/services/webdav/pkg/config/config.go index d7be0a48d5..f071bcd187 100644 --- a/services/webdav/pkg/config/config.go +++ b/services/webdav/pkg/config/config.go @@ -25,12 +25,11 @@ type Config struct { WebdavNamespace string `yaml:"webdav_namespace" env:"WEBDAV_WEBDAV_NAMESPACE" desc:"CS3 path layout to use when forwarding /webdav requests" introductionVersion:"1.0.0"` RevaGateway string `yaml:"reva_gateway" env:"OC_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"1.0.0"` - ThumbnailGeneratorURL string `yaml:"thumbnail_generator_url" env:"WEBDAV_THUMBNAIL_GENERATOR_URL" desc:"Base URL of the thumbnail generator service, e.g. http://thumbnails:9130" introductionVersion:"1.0.0"` - ThumbnailGeneratorTimeout string `yaml:"thumbnail_generator_timeout" env:"WEBDAV_THUMBNAIL_GENERATOR_TIMEOUT" desc:"HTTP timeout for requests to the thumbnail generator, e.g. 30s" introductionVersion:"1.0.0"` - ThumbnailGeneratorAuthHeader string `yaml:"thumbnail_generator_auth_header" env:"WEBDAV_THUMBNAIL_GENERATOR_AUTH_HEADER" desc:"Optional auth header value sent to the generator (for external generators behind a reverse proxy)" introductionVersion:"1.0.0"` - MaxInputFileSize string `yaml:"max_input_file_size" env:"WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"Maximum file size of an input image for thumbnail generation. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB], example: 50MB" introductionVersion:"1.0.0"` + ThumbnailGeneratorURL string `yaml:"thumbnail_generator_url" env:"WEBDAV_THUMBNAIL_GENERATOR_URL" desc:"Base URL of the thumbnail generator service, e.g. http://thumbnails:9130" introductionVersion:"1.0.0"` + ThumbnailGeneratorTimeout string `yaml:"thumbnail_generator_timeout" env:"WEBDAV_THUMBNAIL_GENERATOR_TIMEOUT" desc:"HTTP timeout for requests to the thumbnail generator, e.g. 30s" introductionVersion:"1.0.0"` + MaxInputFileSize string `yaml:"max_input_file_size" env:"WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"Maximum file size of an input image for thumbnail generation. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB], example: 50MB" introductionVersion:"1.0.0"` - ThumbnailCacheBackend string `yaml:"thumbnail_cache_backend" env:"WEBDAV_THUMBNAIL_CACHE_BACKEND" desc:"Cache backend for imagor thumbnails: 'none', 'memory', 'file', or 's3'. Default: none." introductionVersion:"1.0.0"` + ThumbnailCacheBackend string `yaml:"thumbnail_cache_backend" env:"WEBDAV_THUMBNAIL_CACHE_BACKEND" desc:"Cache backend for imagor thumbnails: 'none', 'memory', 'file', or 's3'. Default: file." introductionVersion:"1.0.0"` ThumbnailCacheDir string `yaml:"thumbnail_cache_dir" env:"WEBDAV_THUMBNAIL_CACHE_DIR" desc:"Directory for file-based thumbnail cache (Default: $OC_BASE_DATA_PATH/thumbnails/files). Only used when ThumbnailCacheBackend is 'file'." introductionVersion:"1.0.0"` ThumbnailResolutions []string `yaml:"thumbnail_resolutions" env:"THUMBNAILS_RESOLUTIONS;WEBDAV_THUMBNAIL_RESOLUTIONS" desc:"Supported target resolutions in the format WidthxHeight like 32x32. The requested size is snapped onto one of these (orientation-aware) before being sent to the generator." introductionVersion:"1.0.0"` ThumbnailCacheS3Bucket string `yaml:"thumbnail_cache_s3_bucket" env:"WEBDAV_THUMBNAIL_CACHE_S3_BUCKET" desc:"S3 bucket name for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"` diff --git a/services/webdav/pkg/config/defaults/defaultconfig.go b/services/webdav/pkg/config/defaults/defaultconfig.go index a0fc57cfdc..fa852eddde 100644 --- a/services/webdav/pkg/config/defaults/defaultconfig.go +++ b/services/webdav/pkg/config/defaults/defaultconfig.go @@ -47,7 +47,8 @@ func DefaultConfig() *config.Config { ThumbnailGeneratorURL: "http://127.0.0.1:9186", ThumbnailGeneratorTimeout: "30s", - ThumbnailCacheBackend: "none", + MaxInputFileSize: "50MB", + ThumbnailCacheBackend: "file", ThumbnailCacheDir: path.Join(cored.BaseDataPath(), "thumbnails", "files"), ThumbnailResolutions: []string{ "16x16", "32x32", "64x64", "128x128", "320x320", "1024x1024", // square diff --git a/services/webdav/pkg/dav/requests/thumbnail.go b/services/webdav/pkg/dav/requests/thumbnail.go index f1a4574767..af926b9d0f 100644 --- a/services/webdav/pkg/dav/requests/thumbnail.go +++ b/services/webdav/pkg/dav/requests/thumbnail.go @@ -48,6 +48,10 @@ type ThumbnailRequest struct { // Aspect reports whether the client wants the aspect ratio preserved (the // legacy ownCloud "a" flag: a=1/absent -> preserve, a=0 -> fill the box). Aspect bool + // Identifier is the username from /dav/files/{user}/... when present. The + // workflow resolves it via GetUserByClaim so the file is looked up in that + // user's home; empty for space-ID, /webdav/ and public-link requests. + Identifier string } // ParseThumbnailRequest extracts all required parameters from a http request. @@ -56,7 +60,10 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { fp := ctx.Value(constants.ContextKeyPath).(string) - var ref *providerv1beta1.Reference + var ( + ref *providerv1beta1.Reference + identifier string + ) if v := ctx.Value(constants.ContextKeyID); v != nil { id := v.(string) if strings.Contains(id, "$") { @@ -72,19 +79,20 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { } } else { // The identifier is a username (dav/files/{username}/...); the workflow - // resolves it to the user's home path. - ref = &providerv1beta1.Reference{Path: fp} - } - } else { - if token := chi.URLParam(r, "token"); token != "" { - ref = &providerv1beta1.Reference{Path: path.Join("/public", token, strings.TrimLeft(fp, "/"))} - } else { - // Path-only request (/webdav/...): the absolute CS3 path is carried in - // ContextKeyPath; the workflow resolves the space root. + // resolves it to that user's home path via GetUserByClaim. ref = &providerv1beta1.Reference{Path: fp} + identifier = id } } + if token := chi.URLParam(r, "token"); token != "" { + ref = &providerv1beta1.Reference{Path: path.Join("/public", token, strings.TrimLeft(fp, "/"))} + } else if ref == nil { + // Path-only request (/webdav/...): the absolute CS3 path is carried in + // ContextKeyPath; the workflow resolves the space root. + ref = &providerv1beta1.Reference{Path: fp} + } + q := r.URL.Query() width, height, err := parseDimensions(q) if err != nil { @@ -92,13 +100,14 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { } return &ThumbnailRequest{ - Ref: ref, - Filename: filepath.Base(fp), - Extension: filepath.Ext(fp), - Width: int32(width), - Height: int32(height), - Processor: q.Get("processor"), - Aspect: q.Get("a") != "0", + Ref: ref, + Filename: filepath.Base(fp), + Extension: filepath.Ext(fp), + Width: int32(width), + Height: int32(height), + Processor: q.Get("processor"), + Aspect: q.Get("a") != "0", + Identifier: identifier, }, nil } diff --git a/services/webdav/pkg/generator/extensions.go b/services/webdav/pkg/generator/extensions.go index 812302e64d..3cfbf16db2 100644 --- a/services/webdav/pkg/generator/extensions.go +++ b/services/webdav/pkg/generator/extensions.go @@ -38,20 +38,14 @@ func ContentType(ext string) string { return GetExtensionInfo(ext).ContentType } -// GuessExtension guesses a file extension from a MIME type. -func GuessExtension(mimeType string) string { - exts := []string{".jpg", ".png", ".gif"} - for _, ext := range exts { - if strings.HasSuffix(mimeType, "/"+strings.TrimLeft(ext, ".")) || - strings.Contains(mimeType, "jpeg") && ext == ".jpg" { - return ext[1:] - } - } - - switch { - case strings.Contains(mimeType, "png"): +// MimeToExt maps a resource mime type to the file extension used for the on-disk +// thumbnail cache key. Unknown image types fall back to jpg, matching main's +// behavior (only png/gif are treated specially). +func MimeToExt(mimeType string) string { + switch mimeType { + case "image/png": return "png" - case strings.Contains(mimeType, "gif"): + case "image/gif": return "gif" default: return "jpg" diff --git a/services/webdav/pkg/generator/generator_test.go b/services/webdav/pkg/generator/generator_test.go index 848ad9665c..8981317ddf 100644 --- a/services/webdav/pkg/generator/generator_test.go +++ b/services/webdav/pkg/generator/generator_test.go @@ -86,7 +86,7 @@ func TestContentType(t *testing.T) { } } -func TestGuessExtension(t *testing.T) { +func TestMimeToExt(t *testing.T) { tests := []struct { mime string want string @@ -99,8 +99,8 @@ func TestGuessExtension(t *testing.T) { for _, tt := range tests { t.Run(tt.mime, func(t *testing.T) { - if got := GuessExtension(tt.mime); got != tt.want { - t.Errorf("GuessExtension(%q) = %q, want %q", tt.mime, got, tt.want) + if got := MimeToExt(tt.mime); got != tt.want { + t.Errorf("MimeToExt(%q) = %q, want %q", tt.mime, got, tt.want) } }) } @@ -114,6 +114,7 @@ func TestBuildURL(t *testing.T) { height int32 operation string ext string + noUpscale bool want string }{ { @@ -125,6 +126,16 @@ func TestBuildURL(t *testing.T) { ext: "jpeg", want: "http://generator/unsafe/128x128/filters:format(jpeg)/", }, + { + name: "fill with no_upscale filter caps the resize at source size", + base: "http://generator", + width: 128, + height: 128, + operation: OpFill, + ext: "jpeg", + noUpscale: true, + want: "http://generator/unsafe/128x128/filters:no_upscale():format(jpeg)/", + }, { name: "fit-in preserves aspect ratio within the box without upscaling", base: "http://generator", @@ -147,7 +158,7 @@ func TestBuildURL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := BuildURL(tt.base, tt.width, tt.height, tt.operation, tt.ext) + got := BuildURL(tt.base, tt.width, tt.height, tt.operation, tt.ext, tt.noUpscale) if got != tt.want { t.Errorf("BuildURL() = %q, want %q", got, tt.want) } diff --git a/services/webdav/pkg/generator/url.go b/services/webdav/pkg/generator/url.go index ae745dd0db..810a7abe9a 100644 --- a/services/webdav/pkg/generator/url.go +++ b/services/webdav/pkg/generator/url.go @@ -18,7 +18,9 @@ const ( // operationSegment maps an operation to its imagor URL path segment. The fill // operation is the bare WxH resize (imagor's default), so it contributes no -// leading segment. +// leading segment; stretch and fit-in use their imagor names. Note that real +// imagor has no /fill/ route — "fill" is only our internal name for the default +// center-crop resize. func operationSegment(op string) string { switch op { case OpFitIn: @@ -32,13 +34,20 @@ func operationSegment(op string) string { // BuildURL constructs the thumbnail generator processing URL for a matched box // and operation. The operation segment selects the resize/crop mode (which also -// governs upscaling); the image is re-encoded to the requested output format via a -// single imagor filters segment. -func BuildURL(baseURL string, width, height int32, operation, outputExt string) string { +// governs upscaling); the image is re-encoded to the requested output format via +// an imagor filters segment. Like real imagor, the default (fill) resize upscales +// small sources to fill the box exactly; pass noUpscale=true to emit the +// no_upscale() filter so the generator caps the result at the source size. +func BuildURL(baseURL string, width, height int32, operation, outputExt string, noUpscale bool) string { base := strings.TrimRight(baseURL, "/") box := fmt.Sprintf("%dx%d", width, height) - filterSegment := fmt.Sprintf("filters:format(%s)", outputExt) + + filters := "format(" + outputExt + ")" + if noUpscale { + filters = "no_upscale():format(" + outputExt + ")" + } + filterSegment := "filters:" + filters segment := operationSegment(operation) if segment == "" { diff --git a/services/webdav/pkg/preprocessor/preprocessor.go b/services/webdav/pkg/preprocessor/preprocessor.go index 5f9799d690..0bc562f68b 100644 --- a/services/webdav/pkg/preprocessor/preprocessor.go +++ b/services/webdav/pkg/preprocessor/preprocessor.go @@ -42,7 +42,13 @@ func (i GifDecoder) Convert(r io.Reader) (any, error) { // GgsDecoder is a converter for the geogebra slides file type GgsDecoder struct{ thumbnailpath string } -// Convert reads the ggs file and returns the thumbnail image +// Convert reads the ggs file and returns the thumbnail image. +// +// TODO: this parses user-provided bytes in-process (archive/zip streams the +// embedded thumbnail out of the .ggs container, which is then decoded and +// re-encoded here). Tika is the intended out-of-process extractor for embedded +// images; reading/re-encoding user-supplied embedded images in webdav is a +// separate attack vector not addressed by this architecture change. func (g GgsDecoder) Convert(r io.Reader) (any, error) { var buf bytes.Buffer _, err := io.Copy(&buf, r) @@ -76,7 +82,13 @@ func (g GgsDecoder) Convert(r io.Reader) (any, error) { // AudioDecoder is a converter for the audio file type AudioDecoder struct{} -// Convert reads the audio file and extracts the thumbnail image from the id3 tag +// Convert reads the audio file and extracts the thumbnail image from the id3 tag. +// +// TODO: this parses user-provided bytes in-process (dhowden/tag reads the audio +// metadata and pulls the embedded APIC picture, which is then decoded and +// re-encoded here). Tika is the intended out-of-process extractor for embedded +// images; reading/re-encoding user-supplied embedded images in webdav is a +// separate attack vector not addressed by this architecture change. func (i AudioDecoder) Convert(r io.Reader) (any, error) { b, err := io.ReadAll(r) if err != nil { @@ -200,7 +212,13 @@ type GGPStruct struct { // GgpDecoder is a converter for the geogebra pinboard file type GgpDecoder struct{} -// Convert reads the ggp file and returns the first thumbnail image +// Convert reads the ggp file and returns the first thumbnail image. +// +// TODO: this parses user-provided bytes in-process (JSON/base64 extraction of +// the embedded image, which is then decoded here). Tika is the intended +// out-of-process extractor for embedded images; reading/re-encoding user-supplied +// embedded images in webdav is a separate attack vector not addressed by this +// architecture change. func (j GgpDecoder) Convert(r io.Reader) (any, error) { ggp := &GGPStruct{} err := json.NewDecoder(r).Decode(ggp) diff --git a/services/webdav/pkg/preprocessor/preprocessor_vips.go b/services/webdav/pkg/preprocessor/preprocessor_vips.go index 087bafb1b7..15af62e0b7 100644 --- a/services/webdav/pkg/preprocessor/preprocessor_vips.go +++ b/services/webdav/pkg/preprocessor/preprocessor_vips.go @@ -5,16 +5,25 @@ package preprocessor import ( "io" - "github.com/davidbyttow/govips/v2/vips" + vips "github.com/davidbyttow/govips/v2/vips" + "github.com/kovidgoyal/imaging" + "github.com/pkg/errors" ) func init() { vips.LoggingSettings(nil, vips.LogLevelError) } +// ImageDecoder is a converter for the image file. It decodes with imaging so the +// downstream encode step always receives an image.Image (the vips-backed +// encoder re-wraps it into libvips itself). Returning a *vips.ImageRef here +// would leak a C image past the workflow's ownership boundary. type ImageDecoder struct{} -func (v ImageDecoder) Convert(r io.Reader) (interface{}, error) { - img, err := vips.NewImageFromReader(r) - return img, err +func (i ImageDecoder) Convert(r io.Reader) (any, error) { + img, err := imaging.Decode(r, imaging.AutoOrientation(true)) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil } diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go index 969d44c7c2..11f6ff4d03 100644 --- a/services/webdav/pkg/service/v0/service.go +++ b/services/webdav/pkg/service/v0/service.go @@ -113,7 +113,6 @@ func NewService(opts ...Option) (Service, error) { wf, err := workflow.NewWorkflow( workflow.WithGeneratorURL(conf.ThumbnailGeneratorURL), - workflow.WithAuthHeader(conf.ThumbnailGeneratorAuthHeader), workflow.WithCache(c), workflow.WithHTTPClient(httpClient), workflow.WithMaxInputSize(maxInputSize), @@ -124,6 +123,7 @@ func NewService(opts ...Option) (Service, error) { workflow.WithStater(workflow.NewGatewayStater(gatewaySelector)), workflow.WithFileDownloader(workflow.NewGatewayFileDownloader(gatewaySelector, httpClient)), workflow.WithSpaceLookup(workflow.NewGatewaySpaceLookup(gatewaySelector)), + workflow.WithUserResolver(workflow.NewGatewayUserResolver(gatewaySelector)), ) if err != nil { return nil, fmt.Errorf("create thumbnail workflow: %w", err) @@ -395,8 +395,15 @@ func (g Webdav) handleWorkflowError(w http.ResponseWriter, r *http.Request, err renderError(w, r, errBadRequest("Unsupported file type")) return } - logger.Debug().Err(err).Msg("thumbnail workflow failed") - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + if errors.Is(err, workflow.ErrNotFound) { + logger.Debug().Err(err).Msg("thumbnail source could not be located") + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + } + // Anything else (generator down, download failure, timeout, ...) is a server + // error: clients must not cache it as "no preview" the way they do 404s. + logger.Error().Err(err).Msg("thumbnail workflow failed") + renderError(w, r, errInternalError("could not generate thumbnail")) } func (g Webdav) handleHeadError(w http.ResponseWriter, r *http.Request, err error, tr *requests.ThumbnailRequest, logger log.Logger) { @@ -420,13 +427,17 @@ func (g Webdav) handleHeadError(w http.ResponseWriter, r *http.Request, err erro renderError(w, r, errBadRequest("Unsupported file type")) return } - logger.Debug().Err(err).Msg("thumbnail head check failed") - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + if errors.Is(err, workflow.ErrNotFound) { + logger.Debug().Err(err).Msg("thumbnail source could not be located") + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + } + logger.Error().Err(err).Msg("thumbnail head check failed") + renderError(w, r, errInternalError("could not check thumbnail")) } func (g Webdav) handlePublicLinkAuthError(w http.ResponseWriter, r *http.Request, err error, filename string, logger log.Logger) { - errMsg := err.Error() - if strings.Contains(errMsg, "PERMISSION_DENIED") || strings.Contains(errMsg, "password required") { + if errors.Is(err, workflow.ErrPublicLinkPasswordRequired) { // A password-protected public link accessed without (or with the wrong) // password must not reveal that the resource exists, so it is hidden // behind a 404 rather than a 403. @@ -434,11 +445,12 @@ func (g Webdav) handlePublicLinkAuthError(w http.ResponseWriter, r *http.Request renderError(w, r, errNotFound(notFoundMsg(filename))) return } - if strings.Contains(errMsg, "FAILED_PRECONDITION") || strings.Contains(errMsg, "expired") { + if errors.Is(err, workflow.ErrPublicLinkExpired) { + logger.Debug().Err(err).Msg("public link has expired") renderError(w, r, newErrResponse(http.StatusGone, "public link has expired")) return } - logger.Debug().Err(err).Msg("could not authenticate public link") + logger.Error().Err(err).Msg("could not authenticate public link") renderError(w, r, errInternalError("could not authenticate public link")) } diff --git a/services/webdav/pkg/thumbnail/cache/filecache.go b/services/webdav/pkg/thumbnail/cache/filecache.go index 4701903774..ab8ee18c3e 100644 --- a/services/webdav/pkg/thumbnail/cache/filecache.go +++ b/services/webdav/pkg/thumbnail/cache/filecache.go @@ -29,9 +29,7 @@ func (c *FileCache) Get(key string) ([]byte, error) { return nil, err } - result := make([]byte, len(data)) - copy(result, data) - return result, nil + return data, nil } func (c *FileCache) Put(key string, data []byte) error { diff --git a/services/webdav/pkg/thumbnail/cache/s3cache.go b/services/webdav/pkg/thumbnail/cache/s3cache.go index 687d882f11..9c1fedf14c 100644 --- a/services/webdav/pkg/thumbnail/cache/s3cache.go +++ b/services/webdav/pkg/thumbnail/cache/s3cache.go @@ -99,14 +99,7 @@ func (c *S3Cache) Get(key string) ([]byte, error) { } defer obj.Close() - data, err := io.ReadAll(obj) - if err != nil { - return nil, err - } - - result := make([]byte, len(data)) - copy(result, data) - return result, nil + return io.ReadAll(obj) } func (c *S3Cache) Put(key string, data []byte) error { diff --git a/services/webdav/pkg/thumbnail/workflow/encode_imaging.go b/services/webdav/pkg/thumbnail/workflow/encode_imaging.go index 267322c7de..e732dd9d25 100644 --- a/services/webdav/pkg/thumbnail/workflow/encode_imaging.go +++ b/services/webdav/pkg/thumbnail/workflow/encode_imaging.go @@ -19,7 +19,7 @@ import ( func encodeForUpload(v any, mimeType string) ([]byte, string, error) { switch data := v.(type) { case []byte: - return data, generator.GuessExtension(mimeType), nil + return data, generator.MimeToExt(mimeType), nil case image.Image: var ( buf bytes.Buffer diff --git a/services/webdav/pkg/thumbnail/workflow/encode_vips.go b/services/webdav/pkg/thumbnail/workflow/encode_vips.go index a830c9b902..d3bce98a54 100644 --- a/services/webdav/pkg/thumbnail/workflow/encode_vips.go +++ b/services/webdav/pkg/thumbnail/workflow/encode_vips.go @@ -18,7 +18,7 @@ import ( func encodeForUpload(v any, mimeType string) ([]byte, string, error) { switch data := v.(type) { case []byte: - return data, generator.GuessExtension(mimeType), nil + return data, generator.MimeToExt(mimeType), nil case image.Image: img, err := vips.NewImageFromGoImage(data) if err != nil { diff --git a/services/webdav/pkg/thumbnail/workflow/workflow.go b/services/webdav/pkg/thumbnail/workflow/workflow.go index 5b104b22a1..4833cad25e 100644 --- a/services/webdav/pkg/thumbnail/workflow/workflow.go +++ b/services/webdav/pkg/thumbnail/workflow/workflow.go @@ -18,6 +18,7 @@ import ( rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + chimiddleware "github.com/go-chi/chi/v5/middleware" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/utils" @@ -38,9 +39,11 @@ type Stater interface { Stat(ctx context.Context, ref *providerv1beta1.Reference, auth string) (*providerv1beta1.StatResponse, error) } -// FileDownloader downloads file bytes from storage. +// FileDownloader opens a read stream on a file in storage. The returned reader +// must be consumed (and closed by the caller) exactly once; it is the raw +// response body and is never buffered or decoded here. type FileDownloader interface { - Download(ctx context.Context, ref *providerv1beta1.Reference, auth string) ([]byte, error) + DownloadStream(ctx context.Context, ref *providerv1beta1.Reference, auth string) (io.ReadCloser, error) } // SpaceLookup resolves a path-only reference (ResourceId == nil) into a full @@ -52,6 +55,15 @@ type SpaceLookup interface { Resolve(ctx context.Context, ref *providerv1beta1.Reference, auth string) (*providerv1beta1.Reference, error) } +// UserResolver resolves users for path-only thumbnail requests. WhoAmI maps an +// auth token to its user (used for /webdav/... where the URL carries no +// username); GetUserByClaim maps a username from /dav/files/{user}/... to its +// user, mirroring main's behavior. +type UserResolver interface { + WhoAmI(ctx context.Context, auth string) (*userv1beta1.User, error) + GetUserByClaim(ctx context.Context, claim, value string) (*userv1beta1.User, error) +} + // ErrFileProcessing is returned when the file is still being processed by the // storage backend (e.g. a virus scan or conversion in flight). Callers should // surface this as HTTP 425 Too Early with a Retry-After header. @@ -72,6 +84,11 @@ var ErrNotAFile = errors.New("thumbnails: resource is not a file") // surface this as HTTP 403 Forbidden, matching the legacy thumbnail service. var ErrPermissionDenied = errors.New("thumbnails: no download permission") +// ErrNotFound is returned when the requested file could not be located or has no +// usable checksum. Callers should surface this as HTTP 404 Not Found with a +// "File could not be located" message, matching the legacy thumbnail service. +var ErrNotFound = errors.New("thumbnails: file could not be located") + // fileIsProcessing reports whether the resource carries the "processing" status // in its opaque map, as set by the storage backend while a file is being handled. func fileIsProcessing(info *providerv1beta1.ResourceInfo) bool { @@ -105,7 +122,6 @@ func thumbnailStorageKey(checksum string, w, h int, ext, processor string) strin // stat → validate → cache check → download → preprocess → generate → cache → respond. type ThumbnailWorkflow struct { generatorURL string - authHeader string cache cache.ThumbnailCache httpClient *http.Client maxInputSize uint64 @@ -116,6 +132,7 @@ type ThumbnailWorkflow struct { stater Stater fileDownloader FileDownloader spaceLookup SpaceLookup + userResolver UserResolver } // NewWorkflow creates a ThumbnailWorkflow. generatorURL must be non-empty. @@ -151,11 +168,6 @@ func WithGeneratorURL(url string) Option { return func(w *ThumbnailWorkflow) { w.generatorURL = url } } -// WithAuthHeader sets an optional auth header value sent to the generator (for external generators behind a reverse proxy). -func WithAuthHeader(header string) Option { - return func(w *ThumbnailWorkflow) { w.authHeader = header } -} - // WithCache sets the thumbnail cache. func WithCache(c cache.ThumbnailCache) Option { return func(w *ThumbnailWorkflow) { w.cache = c } @@ -209,6 +221,13 @@ func WithSpaceLookup(l SpaceLookup) Option { return func(w *ThumbnailWorkflow) { w.spaceLookup = l } } +// WithUserResolver sets the resolver used to map an auth token to its user for +// path-only requests (/dav/files/{user}/..., /webdav/...). When unset, such +// requests cannot be absolutized and fall back to the raw path. +func WithUserResolver(r UserResolver) Option { + return func(w *ThumbnailWorkflow) { w.userResolver = r } +} + // Execute handles a user thumbnail request (GET). func (w *ThumbnailWorkflow) Execute(ctx context.Context, tr *requests.ThumbnailRequest, auth string, logger log.Logger) (data []byte, ext string, aIgnored bool, err error) { ref, err := w.resolveReference(ctx, tr, auth) @@ -287,9 +306,11 @@ func (w *ThumbnailWorkflow) resolveReference(ctx context.Context, tr *requests.T return ref, nil } - // Path-only request: absolutize the path relative to the requesting user's - // home (the legacy webdav behavior) before resolving the owning space. - absPath := w.absolutizeUserPath(ctx, ref.GetPath(), auth) + // Path-only request: absolutize the path relative to a user's home (the + // legacy webdav behavior) before resolving the owning space. The user is the + // one named in the URL (/dav/files/{user}/...) when present, otherwise the + // token owner (/webdav/...). + absPath := w.absolutizeUserPath(ctx, ref.GetPath(), tr.Identifier, auth) if w.spaceLookup == nil { return &providerv1beta1.Reference{Path: absPath}, nil @@ -302,16 +323,34 @@ func (w *ThumbnailWorkflow) resolveReference(ctx context.Context, tr *requests.T return resolved, nil } -// absolutizeUserPath maps a path-only reference to an absolute CS3 path under the -// requesting user's home, matching the legacy webdav handler. Public link paths -// (already absolute under /public) and any path that is not relative to the -// caller are returned unchanged. -func (w *ThumbnailWorkflow) absolutizeUserPath(ctx context.Context, p string, auth string) string { +// absolutizeUserPath maps a path-only reference to an absolute CS3 path under a +// user's home, matching the legacy webdav handler. The user is resolved from the +// username in the URL (identifier, via GetUserByClaim) when present, otherwise +// from the auth token (via WhoAmI). Public link paths (already absolute under +// /public) and any path that is not relative to the caller are returned +// unchanged. +func (w *ThumbnailWorkflow) absolutizeUserPath(ctx context.Context, p, identifier, auth string) string { if strings.HasPrefix(p, "/") { return p } - user, err := w.resolveUser(ctx, auth) + if w.userResolver == nil { + // No resolver configured: we cannot absolutize the path. + w.log.Warn().Msg("no user resolver configured, using raw path for thumbnail") + return p + } + + var ( + user *userv1beta1.User + err error + ) + if identifier != "" { + // /dav/files/{user}/...: honor the username from the URL. + user, err = w.userResolver.GetUserByClaim(ctx, "username", identifier) + } else { + // /webdav/...: resolve the token owner. + user, err = w.userResolver.WhoAmI(ctx, auth) + } if err != nil { // Without a resolved user we cannot absolutize; fall back to the raw path. w.log.Warn().Err(err).Msg("could not resolve user for thumbnail path") @@ -347,6 +386,12 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R } checksum := info.GetChecksum().GetSum() + if checksum == "" { + // Without a checksum the cache key would be shared across all files, so + // the resource is treated as not found (matching main's grpc service). + logger.Debug().Msg("resource info is missing a checksum") + return nil, "", false, ErrNotFound + } mimeType := info.GetMimeType() // The output type follows the source mime (like main's GetExtForMime); when @@ -374,27 +419,6 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R } } - fileBytes, err := w.fileDownloader.Download(ctx, ref, auth) - if err != nil { - logger.Error().Err(err).Msg("could not download file for thumbnail") - return nil, "", false, fmt.Errorf("download: %w", err) - } - - // Direct image types are handed to the generator as-is; everything else is - // converted to an image first (audio cover art, geogebra, text, gif). - var toSend any = fileBytes - if !isDirectImageMime(mimeType) { - ppOpts := map[string]any{ - "fontFileMap": w.fontMapFile, - } - img, err := preprocessor.ForType(mimeType, ppOpts).Convert(bytes.NewReader(fileBytes)) - if img == nil || err != nil { - logger.Debug().Err(err).Msg("could not convert file to image") - return nil, "", false, fmt.Errorf("could not get image") - } - toSend = img - } - // webdav is the sizing brain: it snaps the requested size onto a configured // resolution (orientation-aware). It never inspects the image bytes; the // generator is a dumb executor that just fits within the given box. @@ -403,8 +427,25 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R if w.resolutions != nil { box = w.resolutions.Match(reqBox) } - genURL := generator.BuildURL(w.generatorURL, int32(box.Dx()), int32(box.Dy()), operation, outputExt) - thumbBytes, err := w.postToGenerator(ctx, genURL, toSend, mimeType) + + // webdav never asks the generator to upscale: it snaps onto a resolution at + // least as large as requested, so the box is always >= the source intent. + // The no_upscale() filter makes the generator behave like real imagor for + // any other client that posts smaller boxes. + genURL := generator.BuildURL(w.generatorURL, int32(box.Dx()), int32(box.Dy()), operation, outputExt, true) + + // 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. + imgStream, 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") + return nil, "", false, err + } + defer cleanup() + + thumbBytes, err := w.postToGenerator(ctx, genURL, imgStream, tr.Filename) if err != nil { logger.Error().Err(err).Msg("could not generate thumbnail") return nil, "", false, fmt.Errorf("generate: %w", err) @@ -419,6 +460,49 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R return thumbBytes, outputExt, aIgnored, 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. +// - 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, func(), error) { + m, _, _ := mime.ParseMediaType(mimeType) + + if strings.HasPrefix(m, "image/") { + body, err := w.fileDownloader.DownloadStream(ctx, ref, auth) + if err != nil { + return nil, nil, fmt.Errorf("download: %w", err) + } + return body, func() { _ = body.Close() }, nil + } + + body, err := w.fileDownloader.DownloadStream(ctx, ref, auth) + if err != nil { + return nil, nil, fmt.Errorf("download: %w", err) + } + defer body.Close() + + fileBytes, err := io.ReadAll(body) + if err != nil { + return nil, nil, fmt.Errorf("read source: %w", err) + } + + ppOpts := map[string]any{"fontFileMap": w.fontMapFile} + img, err := preprocessor.ForType(mimeType, ppOpts).Convert(bytes.NewReader(fileBytes)) + if img == nil || err != nil { + logger.Debug().Err(err).Msg("could not convert file to image") + return nil, nil, fmt.Errorf("could not get image") + } + + data, _, err := encodeForUpload(img, mimeType) + if err != nil { + return nil, nil, fmt.Errorf("encode converted image: %w", err) + } + return bytes.NewReader(data), func() {}, nil +} + // matchOperation resolves the generator resize/crop operation for a request from // the client's processor and the legacy "a" flag. An explicit processor always // wins; when no processor is given the default depends on the source type (gifs @@ -474,42 +558,60 @@ func (w *ThumbnailWorkflow) matchOperation(tr *requests.ThumbnailRequest, mimeTy return operation, aIgnored } -func (w *ThumbnailWorkflow) postToGenerator(ctx context.Context, url string, file any, mimeType string) ([]byte, error) { - data, ext, err := encodeForUpload(file, mimeType) - if err != nil { - return nil, fmt.Errorf("encode for upload: %w", err) - } - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - part, err := writer.CreateFormFile("image", "file."+ext) - if err != nil { - return nil, fmt.Errorf("create form file: %w", err) - } - if _, err := part.Write(data); err != nil { - return nil, fmt.Errorf("write form data: %w", err) - } - - if err := writer.Close(); err != nil { - return nil, fmt.Errorf("close multipart writer: %w", err) - } +// postToGenerator streams the source image to the generator as a multipart +// POST. The body is produced by an io.Pipe so the (potentially large) image is +// never fully buffered in webdav: the writer goroutine copies the reader into +// the form part on demand. No Content-Length is set, so the request uses chunked +// transfer-encoding; both imagor and the thumbnailer parse the multipart body +// without needing it (imagor enforces its size limit after reading the part). +func (w *ThumbnailWorkflow) postToGenerator(ctx context.Context, url string, img io.Reader, filename string) ([]byte, error) { + pr, pw := io.Pipe() + writer := multipart.NewWriter(pw) + + errCh := make(chan error, 1) + go func() { + defer pw.Close() + part, err := writer.CreateFormFile("image", filename) + if err != nil { + errCh <- fmt.Errorf("create form file: %w", err) + return + } + if _, err := io.Copy(part, img); err != nil { + errCh <- fmt.Errorf("write form data: %w", err) + return + } + if err := writer.Close(); err != nil { + errCh <- fmt.Errorf("close multipart writer: %w", err) + return + } + errCh <- nil + }() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, pr) if err != nil { + pr.Close() return nil, fmt.Errorf("create generator request: %w", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if w.authHeader != "" { - req.Header.Set(revactx.TokenHeader, w.authHeader) + // Forward the request ID so the generator's logs can be correlated with this + // request. No auth token is sent: the generator is an unauthenticated + // internal service (bound to loopback by default). + if reqID := chimiddleware.GetReqID(ctx); reqID != "" { + req.Header.Set("X-Request-ID", reqID) } httpRsp, err := w.httpClient.Do(req) if err != nil { + pr.Close() return nil, fmt.Errorf("generator request: %w", err) } defer httpRsp.Body.Close() + if writeErr := <-errCh; writeErr != nil { + io.Copy(io.Discard, httpRsp.Body) + return nil, writeErr + } + if httpRsp.StatusCode < 200 || httpRsp.StatusCode >= 300 { respBody, _ := io.ReadAll(httpRsp.Body) // The generator reports ErrMaxResolutionExceeded (422) when the decoded @@ -530,32 +632,18 @@ func (w *ThumbnailWorkflow) postToGenerator(ctx context.Context, url string, fil return rspData, nil } -// directImageMimes are mime types the generator can decode directly; their -// bytes are passed through without preprocessing. -var directImageMimes = map[string]struct{}{ - "image/png": {}, - "image/jpg": {}, - "image/jpeg": {}, - "image/tiff": {}, - "image/bmp": {}, - "image/x-ms-bmp": {}, - "image/webp": {}, -} - -func isDirectImageMime(mimeType string) bool { - m, _, _ := mime.ParseMediaType(mimeType) - _, ok := directImageMimes[m] - return ok -} - // gatewaySelector is the interface for selecting a gateway client. type gatewaySelector interface { Next(...pool.Option) (gatewayv1beta1.GatewayAPIClient, error) } -func (w *ThumbnailWorkflow) resolveUser(ctx context.Context, auth string) (*userv1beta1.User, error) { - gs := w.stater.(*gatewayStater) - client, err := gs.selector.Next() +// gatewayUserResolver implements UserResolver using the CS3 gateway's WhoAmI. +type gatewayUserResolver struct { + selector gatewaySelector +} + +func (g *gatewayUserResolver) WhoAmI(ctx context.Context, auth string) (*userv1beta1.User, error) { + client, err := g.selector.Next() if err != nil { return nil, fmt.Errorf("get gateway client: %w", err) } @@ -571,6 +659,28 @@ func (w *ThumbnailWorkflow) resolveUser(ctx context.Context, auth string) (*user return userRes.GetUser(), nil } +func (g *gatewayUserResolver) GetUserByClaim(ctx context.Context, claim, value string) (*userv1beta1.User, error) { + client, err := g.selector.Next() + if err != nil { + return nil, fmt.Errorf("get gateway client: %w", err) + } + + userRes, err := client.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{Claim: claim, Value: value}) + if err != nil { + return nil, fmt.Errorf("get user by claim: %w", err) + } + if userRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + return nil, fmt.Errorf("get user by claim: %s", userRes.GetStatus().GetMessage()) + } + + return userRes.GetUser(), nil +} + +// NewGatewayUserResolver creates a UserResolver backed by the CS3 gateway. +func NewGatewayUserResolver(selector gatewaySelector) UserResolver { + return &gatewayUserResolver{selector: selector} +} + // gatewayStater implements Stater using the CS3 gateway. type gatewayStater struct { selector gatewaySelector @@ -606,7 +716,7 @@ type gatewayFileDownloader struct { httpClient *http.Client } -func (g *gatewayFileDownloader) Download(ctx context.Context, ref *providerv1beta1.Reference, auth string) ([]byte, error) { +func (g *gatewayFileDownloader) DownloadStream(ctx context.Context, ref *providerv1beta1.Reference, auth string) (io.ReadCloser, error) { client, err := g.selector.Next() if err != nil { return nil, fmt.Errorf("get gateway client: %w", err) @@ -636,18 +746,15 @@ func (g *gatewayFileDownloader) Download(ctx context.Context, ref *providerv1bet if err != nil { return nil, fmt.Errorf("download request: %w", err) } - defer httpRsp.Body.Close() if httpRsp.StatusCode != http.StatusOK { + httpRsp.Body.Close() return nil, fmt.Errorf("download failed with status %d", httpRsp.StatusCode) } - data, err := io.ReadAll(httpRsp.Body) - if err != nil { - return nil, fmt.Errorf("read download body: %w", err) - } - - return data, nil + // Return the live response body; the caller owns and closes it. The bytes are + // never buffered or decoded in webdav. + return httpRsp.Body, nil } func extractProtocol(protocols []*gatewayv1beta1.FileDownloadProtocol) (endpoint, token string) { @@ -738,7 +845,19 @@ func NewGatewaySpaceLookup(selector gatewaySelector) SpaceLookup { return &gatewaySpaceLookup{selector: selector} } -// ResolvePublicLinkAuth authenticates a public link token via the gateway. +// ErrPublicLinkPasswordRequired is returned when a password-protected public +// link is accessed without (or with the wrong) password. Callers should surface +// this as HTTP 404 Not Found so the resource's existence is not revealed. +var ErrPublicLinkPasswordRequired = errors.New("public link requires a password") + +// ErrPublicLinkExpired is returned when a public link token has expired. +// Callers should surface this as HTTP 410 Gone. +var ErrPublicLinkExpired = errors.New("public link has expired") + +// ResolvePublicLinkAuth authenticates a public link token via the gateway. It +// returns ErrPublicLinkPasswordRequired or ErrPublicLinkExpired (wrapped) when +// the gateway reports the corresponding gRPC status code, so callers can branch +// on the error type instead of matching error text. func ResolvePublicLinkAuth(ctx context.Context, r *http.Request, publicLinkToken string, selector gatewaySelector) (string, error) { gatewayClient, err := selector.Next() if err != nil { @@ -768,9 +887,14 @@ func ResolvePublicLinkAuth(ctx context.Context, r *http.Request, publicLinkToken return "", fmt.Errorf("could not authenticate public link: %w", err) } - if rsp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + switch rsp.GetStatus().GetCode() { + case rpcv1beta1.Code_CODE_OK: + return rsp.GetToken(), nil + case rpcv1beta1.Code_CODE_PERMISSION_DENIED: + return "", fmt.Errorf("%w: %s", ErrPublicLinkPasswordRequired, rsp.GetStatus().GetMessage()) + case rpcv1beta1.Code_CODE_FAILED_PRECONDITION: + return "", fmt.Errorf("%w: %s", ErrPublicLinkExpired, rsp.GetStatus().GetMessage()) + default: return "", fmt.Errorf("public link authentication failed: code=%s message=%s", rsp.GetStatus().GetCode(), rsp.GetStatus().GetMessage()) } - - return rsp.GetToken(), nil } diff --git a/services/webdav/pkg/thumbnail/workflow/workflow_test.go b/services/webdav/pkg/thumbnail/workflow/workflow_test.go index cc37926c5c..1a4f393f82 100644 --- a/services/webdav/pkg/thumbnail/workflow/workflow_test.go +++ b/services/webdav/pkg/thumbnail/workflow/workflow_test.go @@ -17,14 +17,14 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/opencloud-eu/reva/v2/pkg/storagespace" - "github.com/opencloud-eu/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/status" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + "github.com/opencloud-eu/reva/v2/pkg/storagespace" + "github.com/opencloud-eu/reva/v2/pkg/utils" cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks" "github.com/stretchr/testify/mock" "google.golang.org/grpc" @@ -292,6 +292,7 @@ var _ = Describe("ThumbnailWorkflow", func() { WithStater(NewGatewayStater(gatewaySelector)), WithFileDownloader(NewGatewayFileDownloader(gatewaySelector, &http.Client{})), WithSpaceLookup(NewGatewaySpaceLookup(gatewaySelector)), + WithUserResolver(NewGatewayUserResolver(gatewaySelector)), ) Expect(err).ToNot(HaveOccurred()) @@ -398,6 +399,85 @@ var _ = Describe("ThumbnailWorkflow", func() { }) }) + Describe("path-only /webdav/ reference", func() { + It("resolves the token owner via WhoAmI when no username is in the URL", func() { + var statRef *providerv1beta1.Reference + + gatewayClient.On("WhoAmI", mock.Anything, mock.MatchedBy(func(req *gatewayv1beta1.WhoAmIRequest) bool { + return req.GetToken() == testToken + })).Return(&gatewayv1beta1.WhoAmIResponse{ + Status: status.NewOK(context.Background()), + User: &userv1beta1.User{ + Id: &userv1beta1.UserId{ + Idp: "https://opencloud-server:9200", + OpaqueId: "test-opaque", + Type: userv1beta1.UserType_USER_TYPE_PRIMARY, + }, + Username: "test", + }, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + statRef = req.GetRef() + return strings.Contains(req.GetRef().GetPath(), "photo.jpeg") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &providerv1beta1.ResourceInfo{ + Type: providerv1beta1.ResourceType_RESOURCE_TYPE_FILE, + PermissionSet: &providerv1beta1.ResourcePermissions{InitiateFileDownload: true}, + MimeType: "image/jpeg", + Size: 1024, + Checksum: &providerv1beta1.ResourceChecksum{Sum: "webdavchecksum"}, + }, + }, nil) + + downloadBody := testJPEG(800, 600) + var downloadRef *providerv1beta1.Reference + + storageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write(downloadBody) + })) + defer storageServer.Close() + + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.InitiateFileDownloadRequest) bool { + downloadRef = req.GetRef() + return strings.Contains(req.GetRef().GetPath(), "photo.jpeg") + })).Return(&gatewayv1beta1.InitiateFileDownloadResponse{ + Status: status.NewOK(context.Background()), + Protocols: []*gatewayv1beta1.FileDownloadProtocol{ + {Protocol: "spaces", DownloadEndpoint: storageServer.URL, Token: "download-token"}, + }, + }, nil) + + generatorSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("webdav-thumbnail")) + })) + defer generatorSrv.Close() + wf.generatorURL = generatorSrv.URL + + tr := &requests.ThumbnailRequest{ + Ref: &providerv1beta1.Reference{Path: "photo.jpeg"}, + Filename: "photo.jpeg", + Extension: ".jpeg", + Width: 36, + Height: 36, + } + + data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(err).ToNot(HaveOccurred()) + Expect(ext).To(Equal("jpg")) + Expect(data).To(Equal([]byte("webdav-thumbnail"))) + + // No username in the URL: the token owner (test) is resolved via WhoAmI. + Expect(statRef.GetResourceId()).ToNot(BeNil()) + Expect(statRef.GetPath()).To(Equal("./users/test-opaque/photo.jpeg")) + Expect(downloadRef.GetResourceId()).ToNot(BeNil()) + Expect(downloadRef.GetPath()).To(Equal("./users/test-opaque/photo.jpeg")) + }) + }) + Describe("space-scoped reference", func() { It("anchors stat and download at the space ResourceId, not a path mount", func() { const ( @@ -611,11 +691,12 @@ var _ = Describe("ThumbnailWorkflow", func() { wf.generatorURL = generatorSrv.URL tr := &requests.ThumbnailRequest{ - Ref: &providerv1beta1.Reference{Path: "photo.jpeg"}, - Filename: "photo.jpeg", - Extension: ".jpeg", - Width: 36, - Height: 36, + Ref: &providerv1beta1.Reference{Path: "photo.jpeg"}, + Filename: "photo.jpeg", + Extension: ".jpeg", + Width: 36, + Height: 36, + Identifier: "alice", } data, ext, _, err := wf.Execute(context.Background(), tr, testToken, logger) @@ -623,12 +704,12 @@ var _ = Describe("ThumbnailWorkflow", func() { Expect(ext).To(Equal("jpg")) Expect(data).To(Equal([]byte("user-thumbnail"))) - // The path-only reference is resolved to a space-anchored reference by - // the space lookup (the users storage provider root from the mock). + // The username from the URL (alice) is resolved via GetUserByClaim and + // the path is absolutized under alice's home, not the token owner's. Expect(statRef.GetResourceId()).ToNot(BeNil()) - Expect(statRef.GetPath()).To(Equal("./users/test-opaque/photo.jpeg")) + Expect(statRef.GetPath()).To(Equal("./users/alice-opaque/photo.jpeg")) Expect(downloadRef.GetResourceId()).ToNot(BeNil()) - Expect(downloadRef.GetPath()).To(Equal("./users/test-opaque/photo.jpeg")) + Expect(downloadRef.GetPath()).To(Equal("./users/alice-opaque/photo.jpeg")) }) }) @@ -711,7 +792,7 @@ var _ = Describe("ThumbnailWorkflow", func() { Expect(captured.data).ToNot(Equal(text)) }) - It("re-encodes gif files to gif bytes before posting", func() { + It("passes gif files through to the generator undecoded", func() { gifBytes, err := os.ReadFile(filepath.Join("..", "..", "preprocessor", "test_assets", "noise.gif")) Expect(err).ToNot(HaveOccurred()) @@ -727,12 +808,9 @@ var _ = Describe("ThumbnailWorkflow", func() { _, _, _, err = wf2.Execute(context.Background(), tr, testToken, logger) Expect(err).ToNot(HaveOccurred()) - Expect(captured.data).ToNot(BeEmpty()) - // The generator must receive valid gif bytes (GIF87a/GIF89a magic). - Expect(string(captured.data[:6])).To(SatisfyAny( - Equal("GIF89a"), - Equal("GIF87a"), - )) + // Gifs are image/* and must reach the generator byte-for-byte; the + // generator (imagor or thumbnailer) handles multi-frame resizing. + Expect(captured.data).To(Equal(gifBytes)) }) It("returns an error when the file cannot be converted to an image", func() { From 0b3f5688735f5cc0575323c6ac711539b3631e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 4 Sep 2026 17:17:49 +0200 Subject: [PATCH 5/5] fix test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../pkg/service/http/v0/paletted.go | 37 ++++++++++++++++++ .../pkg/service/http/v0/push_imaging.go | 18 ++++----- .../pkg/service/http/v0/push_test.go | 38 +++++++++++++++++++ .../pkg/service/http/v0/push_vips.go | 18 ++++----- services/webdav/pkg/service/v0/service.go | 10 +++++ .../webdav/pkg/thumbnail/workflow/workflow.go | 22 +++++++++-- .../pkg/thumbnail/workflow/workflow_test.go | 26 ++++++++++++- .../previewsAutoAdustedSizing.feature | 23 +++++------ 8 files changed, 155 insertions(+), 37 deletions(-) create mode 100644 services/thumbnails/pkg/service/http/v0/paletted.go diff --git a/services/thumbnails/pkg/service/http/v0/paletted.go b/services/thumbnails/pkg/service/http/v0/paletted.go new file mode 100644 index 0000000000..fbdacd7ba7 --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/paletted.go @@ -0,0 +1,37 @@ +package svc + +import ( + "image" + "image/color" + "image/draw" +) + +// palettedAtOrigin re-pallettes img using Floyd-Steinberg dithering onto a new +// palette whose bounds are anchored at the origin. The source's own (possibly +// offset) bounds are honored so the pixel content lands in the right place, but +// the result always starts at (0,0). This is required for gif frames: after a +// resize the frame rectangle inherits the source frame's offset, and a frame +// whose rect extends past the gif logical screen makes gif.EncodeAll fail with +// "image block is out of bounds". +func palettedAtOrigin(img image.Image, p color.Palette) *image.Paletted { + b := img.Bounds() + pm := image.NewPaletted(image.Rect(0, 0, b.Dx(), b.Dy()), p) + draw.FloydSteinberg.Draw(pm, pm.Bounds(), shiftedImage(img, b.Min), image.Point{}) + return pm +} + +// shiftedImage wraps img so its bounds are shifted by off, letting a draw target +// anchored at the origin receive an image whose content is defined relative to a +// different origin. +type shiftedImageWrapper struct { + img image.Image + off image.Point +} + +func (s shiftedImageWrapper) Bounds() image.Rectangle { return s.img.Bounds().Add(s.off) } +func (s shiftedImageWrapper) At(x, y int) color.Color { return s.img.At(x-s.off.X, y-s.off.Y) } +func (s shiftedImageWrapper) ColorModel() color.Model { return s.img.ColorModel() } + +func shiftedImage(img image.Image, off image.Point) image.Image { + return shiftedImageWrapper{img: img, off: off} +} diff --git a/services/thumbnails/pkg/service/http/v0/push_imaging.go b/services/thumbnails/pkg/service/http/v0/push_imaging.go index 083739c0c5..e889b24047 100644 --- a/services/thumbnails/pkg/service/http/v0/push_imaging.go +++ b/services/thumbnails/pkg/service/http/v0/push_imaging.go @@ -5,7 +5,6 @@ package svc import ( "fmt" "image" - "image/color" "image/draw" "image/gif" "image/jpeg" @@ -92,7 +91,7 @@ func resizeGIF(m *gif.GIF, width, height int, operation string, noUpscale bool) } } - m.Image[i] = imageToPaletted(processed, frame.Palette) + m.Image[i] = palettedAtOrigin(processed, frame.Palette) switch m.Disposal[i] { case gif.DisposalBackground: @@ -102,7 +101,13 @@ func resizeGIF(m *gif.GIF, width, height int, operation string, noUpscale bool) } } - if !noUpscale || srcX > width || srcY > height { + // stretch resizes every frame to exactly width x height, so the logical + // screen must always match the box even when that is larger than the source + // (webdav sends no_upscale(), which would otherwise skip the update and leave + // frames extending past the screen, making gif.EncodeAll fail with "image + // block is out of bounds"). fill/fit-in keep the source size when noUpscale + // caps them at the source, so they only grow the screen when actually resized. + if operation == OpStretch || !noUpscale || srcX > width || srcY > height { m.Config.Width = width m.Config.Height = height } @@ -110,13 +115,6 @@ func resizeGIF(m *gif.GIF, width, height int, operation string, noUpscale bool) return m } -func imageToPaletted(img image.Image, p color.Palette) *image.Paletted { - b := img.Bounds() - pm := image.NewPaletted(b, p) - draw.FloydSteinberg.Draw(pm, b, img, image.Point{}) - return pm -} - // encodeJPEGImaging encodes the processed image as JPEG using the stdlib encoder. func encodeJPEGImaging(w io.Writer, processed any) error { img, ok := processed.(image.Image) diff --git a/services/thumbnails/pkg/service/http/v0/push_test.go b/services/thumbnails/pkg/service/http/v0/push_test.go index 65c234c0d6..5dfe97fe5a 100644 --- a/services/thumbnails/pkg/service/http/v0/push_test.go +++ b/services/thumbnails/pkg/service/http/v0/push_test.go @@ -225,6 +225,44 @@ func TestPushEndpoint_Fill_GIF(t *testing.T) { } } +// TestPushEndpoint_Stretch_GIFUpscaleNoUpscaleFilter pins the fix for stretching a +// gif to a box larger than the source while the no_upscale() filter is set (which +// is exactly what webdav sends). The resized frames are larger than the original +// logical screen, so without updating m.Config the frame rects extend past the +// screen and gif.EncodeAll fails with "image block is out of bounds" (surfaced as +// HTTP 500). The endpoint must return 200 with every frame exactly the requested +// size. +func TestPushEndpoint_Stretch_GIFUpscaleNoUpscaleFilter(t *testing.T) { + mux := newTestMux() + + imgBytes := createTestGIF(100, 80, 3) + body, contentType := createMultipartBody(imgBytes) + + req := httptest.NewRequest(http.MethodPost, "/unsafe/stretch/200x160/filters:no_upscale():format(gif)/", body) + req.Header.Set("Content-Type", contentType) + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + out, err := gif.DecodeAll(rec.Body) + if err != nil { + t.Fatalf("failed to decode response gif: %v", err) + } + if len(out.Image) != 3 { + t.Fatalf("expected 3 frames preserved, got %d", len(out.Image)) + } + for i, frame := range out.Image { + b := frame.Bounds() + if b.Dx() != 200 || b.Dy() != 160 { + t.Errorf("stretch should produce exactly 200x160, frame %d is %dx%d", i, b.Dx(), b.Dy()) + } + } +} + func TestPushEndpoint_FitIn_AspectRatio(t *testing.T) { mux := newTestMux() diff --git a/services/thumbnails/pkg/service/http/v0/push_vips.go b/services/thumbnails/pkg/service/http/v0/push_vips.go index 821ee2905f..deb421aded 100644 --- a/services/thumbnails/pkg/service/http/v0/push_vips.go +++ b/services/thumbnails/pkg/service/http/v0/push_vips.go @@ -5,7 +5,6 @@ package svc import ( "fmt" "image" - "image/color" "image/draw" "image/gif" "io" @@ -117,7 +116,7 @@ func resizeGIFVips(m *gif.GIF, width, height int, operation string, noUpscale bo } } - m.Image[i] = imageToPalettedVips(processed, frame.Palette) + m.Image[i] = palettedAtOrigin(processed, frame.Palette) switch m.Disposal[i] { case gif.DisposalBackground: @@ -127,7 +126,13 @@ func resizeGIFVips(m *gif.GIF, width, height int, operation string, noUpscale bo } } - if !noUpscale || srcX > width || srcY > height { + // stretch resizes every frame to exactly width x height, so the logical + // screen must always match the box even when that is larger than the source + // (webdav sends no_upscale(), which would otherwise skip the update and leave + // frames extending past the screen, making gif.EncodeAll fail with "image + // block is out of bounds"). fill/fit-in keep the source size when noUpscale + // caps them at the source, so they only grow the screen when actually resized. + if operation == OpStretch || !noUpscale || srcX > width || srcY > height { m.Config.Width = width m.Config.Height = height } @@ -135,13 +140,6 @@ func resizeGIFVips(m *gif.GIF, width, height int, operation string, noUpscale bo return m } -func imageToPalettedVips(img image.Image, p color.Palette) *image.Paletted { - b := img.Bounds() - pm := image.NewPaletted(b, p) - draw.FloydSteinberg.Draw(pm, b, img, image.Point{}) - return pm -} - // encodeJPEGVips exports the processed libvips image as JPEG using libvips's // default export parameters (progressive/interlaced, quality 80), matching the // legacy pipeline's output byte-for-byte where the resize operation is unchanged. diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go index 11f6ff4d03..d905d64db2 100644 --- a/services/webdav/pkg/service/v0/service.go +++ b/services/webdav/pkg/service/v0/service.go @@ -400,6 +400,11 @@ func (g Webdav) handleWorkflowError(w http.ResponseWriter, r *http.Request, err renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) return } + if errors.Is(err, workflow.ErrUnsupportedFileType) { + logger.Debug().Err(err).Msg("thumbnail requested for unsupported file type") + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + } // Anything else (generator down, download failure, timeout, ...) is a server // error: clients must not cache it as "no preview" the way they do 404s. logger.Error().Err(err).Msg("thumbnail workflow failed") @@ -432,6 +437,11 @@ func (g Webdav) handleHeadError(w http.ResponseWriter, r *http.Request, err erro renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) return } + if errors.Is(err, workflow.ErrUnsupportedFileType) { + logger.Debug().Err(err).Msg("thumbnail head check requested for unsupported file type") + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + } logger.Error().Err(err).Msg("thumbnail head check failed") renderError(w, r, errInternalError("could not check thumbnail")) } diff --git a/services/webdav/pkg/thumbnail/workflow/workflow.go b/services/webdav/pkg/thumbnail/workflow/workflow.go index 4833cad25e..441daba657 100644 --- a/services/webdav/pkg/thumbnail/workflow/workflow.go +++ b/services/webdav/pkg/thumbnail/workflow/workflow.go @@ -89,6 +89,12 @@ var ErrPermissionDenied = errors.New("thumbnails: no download permission") // "File could not be located" message, matching the legacy thumbnail service. var ErrNotFound = errors.New("thumbnails: file could not be located") +// ErrUnsupportedFileType is returned when the file's mime type is not in the +// supported set (e.g. PDF, ODT, ZIP). Callers should surface this as HTTP 404 +// Not Found with an "Unsupported file type" message, matching the legacy +// thumbnail service. +var ErrUnsupportedFileType = errors.New("unsupported file type") + // fileIsProcessing reports whether the resource carries the "processing" status // in its opaque map, as set by the storage backend while a file is being handled. func fileIsProcessing(info *providerv1beta1.ResourceInfo) bool { @@ -275,7 +281,7 @@ func (w *ThumbnailWorkflow) Head(ctx context.Context, tr *requests.ThumbnailRequ } if !thumbnail.IsMimeTypeSupported(info.GetMimeType()) { - return fmt.Errorf("unsupported mime type: %s", info.GetMimeType()) + return fmt.Errorf("%w: %s", ErrUnsupportedFileType, info.GetMimeType()) } if w.maxInputSize > 0 && info.GetSize() > w.maxInputSize { @@ -377,7 +383,7 @@ func (w *ThumbnailWorkflow) generate(ctx context.Context, ref *providerv1beta1.R } if !thumbnail.IsMimeTypeSupported(info.GetMimeType()) { - return nil, "", false, fmt.Errorf("unsupported mime type: %s", info.GetMimeType()) + return nil, "", false, fmt.Errorf("%w: %s", ErrUnsupportedFileType, info.GetMimeType()) } if w.maxInputSize > 0 && info.GetSize() > w.maxInputSize { @@ -493,7 +499,7 @@ func (w *ThumbnailWorkflow) sourceImage(ctx context.Context, ref *providerv1beta img, err := preprocessor.ForType(mimeType, ppOpts).Convert(bytes.NewReader(fileBytes)) if img == nil || err != nil { logger.Debug().Err(err).Msg("could not convert file to image") - return nil, nil, fmt.Errorf("could not get image") + return nil, nil, fmt.Errorf("%w: could not get image", ErrNotFound) } data, _, err := encodeForUpload(img, mimeType) @@ -699,7 +705,15 @@ func (g *gatewayStater) Stat(ctx context.Context, ref *providerv1beta1.Reference return nil, fmt.Errorf("stat request: %w", err) } - if rsp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + switch rsp.GetStatus().GetCode() { + case rpcv1beta1.Code_CODE_OK: + // fallthrough + case rpcv1beta1.Code_CODE_NOT_FOUND: + // The resource does not exist or is not visible to the caller (e.g. a + // file of another user). Surface it as 404, matching the legacy + // thumbnail service. + return nil, fmt.Errorf("%w: %s", ErrNotFound, rsp.GetStatus().GetMessage()) + default: return nil, fmt.Errorf("stat failed: %s", rsp.GetStatus().GetMessage()) } diff --git a/services/webdav/pkg/thumbnail/workflow/workflow_test.go b/services/webdav/pkg/thumbnail/workflow/workflow_test.go index 1a4f393f82..32352214aa 100644 --- a/services/webdav/pkg/thumbnail/workflow/workflow_test.go +++ b/services/webdav/pkg/thumbnail/workflow/workflow_test.go @@ -826,6 +826,7 @@ var _ = Describe("ThumbnailWorkflow", func() { _, _, _, err := wf2.Execute(context.Background(), tr, testToken, logger) Expect(err).To(HaveOccurred()) + Expect(errors.Is(err, ErrNotFound)).To(BeTrue()) Expect(err.Error()).To(ContainSubstring("could not get image")) }) }) @@ -879,7 +880,7 @@ var _ = Describe("ThumbnailWorkflow", func() { _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unsupported mime type")) + Expect(errors.Is(err, ErrUnsupportedFileType)).To(BeTrue()) }) It("should return ErrNotAFile when the resource is a folder", func() { @@ -1342,7 +1343,7 @@ var _ = Describe("ThumbnailWorkflow", func() { err = wf.Head(req.Context(), tr, auth, logger) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unsupported mime type")) + Expect(errors.Is(err, ErrUnsupportedFileType)).To(BeTrue()) }) }) @@ -1489,4 +1490,25 @@ var _ = Describe("ThumbnailWorkflow", func() { Expect(errors.Is(err, ErrFileProcessing)).To(BeTrue()) }) }) + + Describe("stat not found", func() { + It("returns ErrNotFound when the gateway reports the file as not found", func() { + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *providerv1beta1.StatRequest) bool { + return strings.Contains(req.Ref.Path, "otherusersfile.txt") + })).Return(&providerv1beta1.StatResponse{ + Status: status.NewNotFound(context.Background(), "file not found"), + }, nil) + + tr := &requests.ThumbnailRequest{ + Ref: spaceRef("storageid$spaceid!opaqueid", "otherusersfile.txt"), + Filename: "otherusersfile.txt", + Extension: ".txt", + Width: 64, + Height: 64, + } + + _, _, _, err := wf.Execute(context.Background(), tr, testToken, logger) + Expect(errors.Is(err, ErrNotFound)).To(BeTrue()) + }) + }) }) diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature b/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature index 14cb6ed375..447c21565a 100644 --- a/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature +++ b/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature @@ -20,25 +20,26 @@ Feature: sizing of previews of files downloaded through the webdav API # No processor is given, so the default behavior applies: fill. The source # preview of a text file is always 640x480 (landscape). The requested size is # snapped onto a configured resolution (the box) and the image is center-cropped - # to fill that box exactly (it may be upscaled or downscaled to cover it), so - # the output is always exactly the box dimensions. + # to fill that box, but webdav sends no_upscale() so the source is never + # enlarged: when the 640x480 source already fits inside the box it is returned + # at its native size, and only boxes smaller than the source are downscaled. Examples: | request-width | request-height | return-width | return-height | dav-path-version | | 1 | 1 | 16 | 16 | old | | 32 | 32 | 32 | 32 | old | - | 1024 | 1024 | 1024 | 1024 | old | - | 1 | 1024 | 1080 | 1920 | old | - | 1024 | 1 | 1024 | 1024 | old | + | 1024 | 1024 | 640 | 480 | old | + | 1 | 1024 | 640 | 480 | old | + | 1024 | 1 | 640 | 480 | old | | 1 | 1 | 16 | 16 | new | | 32 | 32 | 32 | 32 | new | - | 1024 | 1024 | 1024 | 1024 | new | - | 1 | 1024 | 1080 | 1920 | new | - | 1024 | 1 | 1024 | 1024 | new | + | 1024 | 1024 | 640 | 480 | new | + | 1 | 1024 | 640 | 480 | new | + | 1024 | 1 | 640 | 480 | new | | 1 | 1 | 16 | 16 | spaces | | 32 | 32 | 32 | 32 | spaces | - | 1024 | 1024 | 1024 | 1024 | spaces | - | 1 | 1024 | 1080 | 1920 | spaces | - | 1024 | 1 | 1024 | 1024 | spaces | + | 1024 | 1024 | 640 | 480 | spaces | + | 1 | 1024 | 640 | 480 | spaces | + | 1024 | 1 | 640 | 480 | spaces | Scenario Outline: download a preview with an explicit processor