From be3145aae97b5c4df0930b7a6e4e50d2d55c3b9d Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 13 May 2025 16:09:51 +0200 Subject: [PATCH] chore(libreoffice): switch to env var for handling non-basic latin characters in filenames --- build/Dockerfile | 7 ++ pkg/modules/libreoffice/api/libreoffice.go | 67 ------------------ .../features/libreoffice_convert.feature | 19 +++++ .../integration/testdata/Special_Chars_ß.docx | Bin 0 -> 6408 bytes 4 files changed, 26 insertions(+), 67 deletions(-) create mode 100644 test/integration/testdata/Special_Chars_ß.docx diff --git a/build/Dockerfile b/build/Dockerfile index 88dcd64a..69940546 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -214,6 +214,13 @@ RUN \ # Cleanup. rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Set default characterset encoding to UTF-8. +# See: +# https://github.com/gotenberg/gotenberg/issues/104 +# https://github.com/gotenberg/gotenberg/issues/730 +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + RUN \ # Install LibreOffice & unoconverter. echo "deb http://deb.debian.org/debian bookworm-backports main" >> /etc/apt/sources.list &&\ diff --git a/pkg/modules/libreoffice/api/libreoffice.go b/pkg/modules/libreoffice/api/libreoffice.go index b9dfe607..9ee9e353 100644 --- a/pkg/modules/libreoffice/api/libreoffice.go +++ b/pkg/modules/libreoffice/api/libreoffice.go @@ -4,16 +4,13 @@ import ( "context" "errors" "fmt" - "io" "net" "os" - "path/filepath" "strings" "sync" "sync/atomic" "time" - "github.com/google/uuid" "go.uber.org/zap" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" @@ -333,11 +330,6 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP ) } - inputPath, err := nonBasicLatinCharactersGuard(logger, inputPath) - if err != nil { - return fmt.Errorf("non-basic latin characters guard: %w", err) - } - args = append(args, "--output", outputPath, inputPath) cmd, err := gotenberg.CommandContext(ctx, logger, p.arguments.unoBinPath, args...) @@ -374,65 +366,6 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP return fmt.Errorf("convert to PDF: %w", err) } -// LibreOffice cannot convert a file with a name containing non-basic Latin -// characters. -// See: -// https://github.com/gotenberg/gotenberg/issues/104 -// https://github.com/gotenberg/gotenberg/issues/730 -func nonBasicLatinCharactersGuard(logger *zap.Logger, inputPath string) (string, error) { - hasNonBasicLatinChars := func(str string) bool { - for _, r := range str { - // Check if the character is outside basic Latin. - if r != '.' && (r < ' ' || r > '~') { - return true - } - } - return false - } - - filename := filepath.Base(inputPath) - if !hasNonBasicLatinChars(filename) { - logger.Debug("no non-basic latin characters in filename, skip copy") - return inputPath, nil - } - - logger.Warn("non-basic latin characters in filename, copy to a file with a valid filename") - basePath := filepath.Dir(inputPath) - ext := filepath.Ext(inputPath) - newInputPath := filepath.Join(basePath, fmt.Sprintf("%s%s", uuid.NewString(), ext)) - - in, err := os.Open(inputPath) - if err != nil { - return "", fmt.Errorf("open file: %w", err) - } - - defer func() { - err := in.Close() - if err != nil { - logger.Error(fmt.Sprintf("close file: %s", err)) - } - }() - - out, err := os.Create(newInputPath) - if err != nil { - return "", fmt.Errorf("create new file: %w", err) - } - - defer func() { - err := out.Close() - if err != nil { - logger.Error(fmt.Sprintf("close new file: %s", err)) - } - }() - - _, err = io.Copy(out, in) - if err != nil { - return "", fmt.Errorf("copy file to new file: %w", err) - } - - return newInputPath, nil -} - // Interface guards. var ( _ gotenberg.Process = (*libreOfficeProcess)(nil) diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature index 91a4fcb9..8a96a936 100644 --- a/test/integration/features/libreoffice_convert.feature +++ b/test/integration/features/libreoffice_convert.feature @@ -40,6 +40,25 @@ Feature: /forms/libreoffice/convert Page 2 """ + # See: + # https://github.com/gotenberg/gotenberg/issues/104 + # https://github.com/gotenberg/gotenberg/issues/730 + Scenario: POST /forms/libreoffice/convert (Non-basic Latin Characters) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/Special_Chars_ß.docx | file | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 1 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Scenario: POST /forms/libreoffice/convert (Protected) Given I have a default Gotenberg container When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): diff --git a/test/integration/testdata/Special_Chars_ß.docx b/test/integration/testdata/Special_Chars_ß.docx new file mode 100644 index 0000000000000000000000000000000000000000..1868509c5fe654e5dc4edb66f210459c88aa3f25 GIT binary patch literal 6408 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 q!tYG&_a%NUj)(j9mr35Q`2XBKRYl-^XaNA^`