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.
This commit is contained in:
Sybren A. Stüvel 2024-09-30 11:25:46 +02:00
parent 1f562b3cbc
commit 2e0e211b26
3 changed files with 33 additions and 2 deletions

View File

@ -17,6 +17,7 @@ bugs in actually-released versions.
- Security updates of dependencies: - Security updates of dependencies:
- [GO-2024-3106: Stack exhaustion in Decoder.Decode in encoding/gob](https://pkg.go.dev/vuln/GO-2024-3106) - [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 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 ## 3.5 - released 2024-04-16

View File

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
"""BAT interface for sending files to the Manager via the Shaman API.""" """BAT interface for sending files to the Manager via the Shaman API."""
import email.header
import logging import logging
import random import random
import platform import platform
@ -366,6 +367,7 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore
) )
local_filepath = self._rel_to_local_path[file_spec.path] local_filepath = self._rel_to_local_path[file_spec.path]
filename_header = _encode_original_filename_header(file_spec.path)
try: try:
with local_filepath.open("rb") as file_reader: with local_filepath.open("rb") as file_reader:
self.shaman_api.shaman_file_store( self.shaman_api.shaman_file_store(
@ -373,7 +375,7 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore
filesize=file_spec.size, filesize=file_spec.size,
body=file_reader, body=file_reader,
x_shaman_can_defer_upload=can_defer, x_shaman_can_defer_upload=can_defer,
x_shaman_original_filename=file_spec.path, x_shaman_original_filename=filename_header,
) )
except ApiException as ex: except ApiException as ex:
if ex.status == 425: if ex.status == 425:
@ -527,3 +529,16 @@ def _root_path_strip(path: PurePath) -> PurePosixPath:
if path.is_absolute(): if path.is_absolute():
return PurePosixPath(*path.parts[1:]) return PurePosixPath(*path.parts[1:])
return PurePosixPath(path) 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()

View File

@ -3,6 +3,7 @@ package api_impl
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import ( import (
"mime"
"net/http" "net/http"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -110,8 +111,22 @@ func (f *Flamenco) ShamanFileStore(e echo.Context, checksum string, filesize int
canDefer = *params.XShamanCanDeferUpload canDefer = *params.XShamanCanDeferUpload
logCtx = logCtx.Bool("canDefer", canDefer) logCtx = logCtx.Bool("canDefer", canDefer)
} }
if params.XShamanOriginalFilename != nil { 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) logCtx = logCtx.Str("originalFilename", origFilename)
} }
logger := logCtx.Logger() logger := logCtx.Logger()