From 2e0e211b267a2369a48c99081608100d25ef0350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 30 Sep 2024 11:25:46 +0200 Subject: [PATCH] Fix #104338: Error performing BAT pack Use RFC 2047 (aka MIME encoding) to send the original filename when uploading a file to the Shaman server. HTTP headers should be ASCII-only, and some systems use Latin-1 as fallback. That's not suitable in general, though, because almost all characters fall outside the Latin-1 range. --- CHANGELOG.md | 1 + addon/flamenco/bat/shaman.py | 17 ++++++++++++++++- internal/manager/api_impl/shaman.go | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 558fac05..4af90e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ bugs in actually-released versions. - Security updates of dependencies: - [GO-2024-3106: Stack exhaustion in Decoder.Decode in encoding/gob](https://pkg.go.dev/vuln/GO-2024-3106) - Fix bug where database foreign key constraints could be deactivated ([#104305](https://projects.blender.org/studio/flamenco/issues/104305)). +- Fix bug when submitting a file with a non-ASCII name via Shaman ([#104338](https://projects.blender.org/studio/flamenco/issues/104338)). ## 3.5 - released 2024-04-16 diff --git a/addon/flamenco/bat/shaman.py b/addon/flamenco/bat/shaman.py index 4dd4c319..9da2304b 100644 --- a/addon/flamenco/bat/shaman.py +++ b/addon/flamenco/bat/shaman.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later """BAT interface for sending files to the Manager via the Shaman API.""" +import email.header import logging import random import platform @@ -366,6 +367,7 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore ) local_filepath = self._rel_to_local_path[file_spec.path] + filename_header = _encode_original_filename_header(file_spec.path) try: with local_filepath.open("rb") as file_reader: self.shaman_api.shaman_file_store( @@ -373,7 +375,7 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore filesize=file_spec.size, body=file_reader, x_shaman_can_defer_upload=can_defer, - x_shaman_original_filename=file_spec.path, + x_shaman_original_filename=filename_header, ) except ApiException as ex: if ex.status == 425: @@ -527,3 +529,16 @@ def _root_path_strip(path: PurePath) -> PurePosixPath: if path.is_absolute(): return PurePosixPath(*path.parts[1:]) return PurePosixPath(path) + + +def _encode_original_filename_header(filename: str) -> str: + """Encode the 'original filename' as valid HTTP Header. + + See the specs for the X-Shaman-Original-Filename header in the OpenAPI + operation `shamanFileStore`, defined in flamenco-openapi.yaml. + """ + + # This is a no-op when the filename is already in ASCII. + fake_header = email.header.Header() + fake_header.append(filename, charset="utf-8") + return fake_header.encode() diff --git a/internal/manager/api_impl/shaman.go b/internal/manager/api_impl/shaman.go index 0596c203..91069118 100644 --- a/internal/manager/api_impl/shaman.go +++ b/internal/manager/api_impl/shaman.go @@ -3,6 +3,7 @@ package api_impl // SPDX-License-Identifier: GPL-3.0-or-later import ( + "mime" "net/http" "github.com/labstack/echo/v4" @@ -110,8 +111,22 @@ func (f *Flamenco) ShamanFileStore(e echo.Context, checksum string, filesize int canDefer = *params.XShamanCanDeferUpload logCtx = logCtx.Bool("canDefer", canDefer) } + if params.XShamanOriginalFilename != nil { - origFilename = *params.XShamanOriginalFilename + rawHeadervalue := *params.XShamanOriginalFilename + decoder := mime.WordDecoder{} + + var err error // origFilename has to be used from the outer scope. + origFilename, err = decoder.DecodeHeader(rawHeadervalue) + if err != nil { + logger := logCtx.Logger() + logger.Error(). + Str("headerValue", rawHeadervalue). + Err(err). + Msg("shaman: received invalid X-Shaman-Original-Filename header") + return sendAPIError(e, http.StatusBadRequest, "invalid X-Shaman-Original-Filename header: %q", rawHeadervalue) + } + logCtx = logCtx.Str("originalFilename", origFilename) } logger := logCtx.Logger()