From 8f7c1c98ad93220c576a767919eee37946884249 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 27 Mar 2026 20:55:50 +0100 Subject: [PATCH] fix(api): handle correctly filename that takes more that 200 bytes --- pkg/modules/api/context.go | 68 +++++++++++++----- pkg/modules/api/formdata.go | 35 +++++++-- pkg/modules/chromium/routes.go | 2 +- pkg/modules/libreoffice/routes.go | 6 +- pkg/modules/pdfengines/routes.go | 50 +++++++++---- .../features/chromium_convert_html.feature | 11 +++ .../chromium_convert_markdown.feature | 12 ++++ .../features/libreoffice_convert.feature | 11 +++ .../features/pdfengines_encrypt.feature | 12 ++++ .../features/pdfengines_flatten.feature | 11 +++ .../features/pdfengines_merge.feature | 11 +++ .../features/pdfengines_metadata.feature | 18 +++++ .../features/pdfengines_rotate.feature | 11 +++ .../features/pdfengines_split.feature | 13 ++++ ...scancer_i_ett_randomiserat_kontrollerat_försök.docx | Bin 0 -> 6408 bytes ...dscancer_i_ett_randomiserat_kontrollerat_försök.pdf | Bin 0 -> 4560 bytes 16 files changed, 233 insertions(+), 38 deletions(-) create mode 100644 test/integration/testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.docx create mode 100644 test/integration/testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf diff --git a/pkg/modules/api/context.go b/pkg/modules/api/context.go index 43d298e6..b5336d34 100644 --- a/pkg/modules/api/context.go +++ b/pkg/modules/api/context.go @@ -43,12 +43,13 @@ var ( // Context is the request context for a "multipart/form-data" request. type Context struct { - dirPath string - values map[string][]string - files map[string]string - filesByField map[string][]string - outputPaths []string - cancelled bool + dirPath string + values map[string][]string + files map[string]string + filesByField map[string][]string + diskToOriginal map[string]string + outputPaths []string + cancelled bool logger *slog.Logger echoCtx echo.Context @@ -200,6 +201,7 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys ctx.values = form.Value ctx.files = make(map[string]string) ctx.filesByField = make(map[string][]string) + ctx.diskToOriginal = make(map[string]string) // First, try to download files listed in the "downloadFrom" form field, if // any. @@ -348,7 +350,12 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys // normalized. // See: https://github.com/gotenberg/gotenberg/issues/662. filename = norm.NFC.String(filepath.Base(filename)) - path := fmt.Sprintf("%s/%s", ctx.dirPath, filename) + + // Use a UUID-based name on disk to avoid filesystem + // NAME_MAX limits with long filenames. + // See: https://github.com/gotenberg/gotenberg/issues/1500. + safeName := uuid.New().String() + filepath.Ext(filename) + path := fmt.Sprintf("%s/%s", ctx.dirPath, safeName) out, err := os.Create(path) if err != nil { @@ -381,6 +388,7 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys dlSpan.End() ctx.files[filename] = path + ctx.diskToOriginal[path] = filename // Route the downloaded file to the appropriate field bucket. switch { @@ -422,7 +430,12 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys // normalized. // See: https://github.com/gotenberg/gotenberg/issues/662. filename := norm.NFC.String(filepath.Base(fh.Filename)) - path := fmt.Sprintf("%s/%s", ctx.dirPath, filename) + + // Use a UUID-based name on disk to avoid filesystem + // NAME_MAX limits with long filenames. + // See: https://github.com/gotenberg/gotenberg/issues/1500. + safeName := uuid.New().String() + filepath.Ext(filename) + path := fmt.Sprintf("%s/%s", ctx.dirPath, safeName) out, err := os.Create(path) if err != nil { @@ -441,6 +454,7 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys } ctx.files[filename] = path + ctx.diskToOriginal[path] = filename return nil } @@ -475,13 +489,29 @@ func (ctx *Context) Request() *http.Request { // FormData return a [FormData]. func (ctx *Context) FormData() *FormData { return &FormData{ - values: ctx.values, - files: ctx.files, - filesByField: ctx.filesByField, - errors: nil, + values: ctx.values, + files: ctx.files, + filesByField: ctx.filesByField, + diskToOriginal: ctx.diskToOriginal, + errors: nil, } } +// OriginalFilename returns the original filename associated with a disk path. +// If no mapping exists, it falls back to [filepath.Base]. +func (ctx *Context) OriginalFilename(diskPath string) string { + if original, ok := ctx.diskToOriginal[diskPath]; ok { + return original + } + return filepath.Base(diskPath) +} + +// RegisterDiskPath associates a disk path with an original filename so that +// [Context.OriginalFilename] can resolve it later. +func (ctx *Context) RegisterDiskPath(diskPath, originalFilename string) { + ctx.diskToOriginal[diskPath] = originalFilename +} + // DirPath returns the path to the request's working directory. func (ctx *Context) DirPath() string { return ctx.dirPath @@ -494,10 +524,14 @@ func (ctx *Context) GeneratePath(extension string) string { } // GeneratePathFromFilename generates a path within the context's working -// directory, using the given filename (with extension). It does not create -// a file. +// directory. It uses a UUID-based name on disk to avoid filesystem NAME_MAX +// limits but registers the given filename so that [Context.OriginalFilename] +// can resolve it. It does not create a file. func (ctx *Context) GeneratePathFromFilename(filename string) string { - return fmt.Sprintf("%s/%s", ctx.dirPath, filename) + safeName := uuid.New().String() + filepath.Ext(filename) + path := fmt.Sprintf("%s/%s", ctx.dirPath, safeName) + ctx.diskToOriginal[path] = filename + return path } // CreateSubDirectory creates a subdirectory within the context's working @@ -564,7 +598,7 @@ func (ctx *Context) BuildOutputFile() (string, error) { filesInfo, err := archives.FilesFromDisk(ctx.Context, nil, func() map[string]string { f := make(map[string]string) for _, outputPath := range ctx.outputPaths { - f[outputPath] = "" + f[outputPath] = ctx.OriginalFilename(outputPath) } return f }()) @@ -600,7 +634,7 @@ func (ctx *Context) OutputFilename(outputPath string) string { filename := ctx.echoCtx.Get("outputFilename").(string) if filename == "" { - return filepath.Base(outputPath) + return ctx.OriginalFilename(outputPath) } return fmt.Sprintf("%s%s", filename, filepath.Ext(outputPath)) diff --git a/pkg/modules/api/formdata.go b/pkg/modules/api/formdata.go index f2eca944..007ecc10 100644 --- a/pkg/modules/api/formdata.go +++ b/pkg/modules/api/formdata.go @@ -33,10 +33,11 @@ const ( // // form := ctx.FormData() type FormData struct { - values map[string][]string - files map[string]string - filesByField map[string][]string - errors error + values map[string][]string + files map[string]string + filesByField map[string][]string + diskToOriginal map[string]string + errors error } // Validate returns nil or an error related to the [FormData] values, with a @@ -450,6 +451,15 @@ func (form *FormData) paths(extensions []string, target *[]string) *FormData { watermarks, wmOk := form.filesByField[WatermarkFormField] stamps, stOk := form.filesByField[StampFormField] + // Collect (originalFilename, diskPath) pairs so that we can sort by + // original filename rather than by UUID-based disk name. + // See https://github.com/gotenberg/gotenberg/issues/1500. + type entry struct { + original string + disk string + } + var entries []entry + for filename, path := range form.files { if ok && slices.Contains(embeds, path) { continue @@ -466,13 +476,26 @@ func (form *FormData) paths(extensions []string, target *[]string) *FormData { for _, ext := range extensions { // See https://github.com/gotenberg/gotenberg/issues/228. if strings.ToLower(filepath.Ext(filename)) == ext { - *target = append(*target, path) + entries = append(entries, entry{original: filename, disk: path}) } } } // See https://github.com/gotenberg/gotenberg/issues/139. - sort.Sort(gotenberg.AlphanumericSort(*target)) + originals := make(gotenberg.AlphanumericSort, len(entries)) + for i, e := range entries { + originals[i] = e.original + } + sort.Sort(originals) + + // Build a lookup from original name to disk path. + lookup := make(map[string]string, len(entries)) + for _, e := range entries { + lookup[e.original] = e.disk + } + for _, o := range originals { + *target = append(*target, lookup[o]) + } return form } diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index fa866ab4..995091de 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -667,7 +667,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string) var path string for _, markdownPath := range markdownPaths { - markdownFilename := filepath.Base(markdownPath) + markdownFilename := ctx.OriginalFilename(markdownPath) if filename == markdownFilename { path = markdownPath diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index fcc0b331..f3e996c3 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -420,7 +420,8 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap // document.docx -> document.docx.pdf, so that split naming // document.docx_0.pdf, etc. for i, inputPath := range inputPaths { - outputPath := fmt.Sprintf("%s.pdf", inputPath) + originalName := ctx.OriginalFilename(inputPath) + outputPath := ctx.GeneratePathFromFilename(originalName + ".pdf") err = ctx.Rename(outputPaths[i], outputPath) if err != nil { @@ -502,7 +503,8 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap if len(outputPaths) > 1 && splitMode == zeroValuedSplitMode { // If .zip archive, document.docx -> document.docx.pdf. for i, inputPath := range inputPaths { - outputPath := fmt.Sprintf("%s.pdf", inputPath) + originalName := ctx.OriginalFilename(inputPath) + outputPath := ctx.GeneratePathFromFilename(originalName + ".pdf") err = ctx.Rename(outputPaths[i], outputPath) if err != nil { diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index bdac91e7..b1df9655 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "net/http" + "os" "path/filepath" "strconv" "strings" + "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" @@ -282,9 +284,9 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S var outputPaths []string for _, inputPath := range inputPaths { - inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] - filenameNoExt := filepath.Base(inputPathNoExt) - outputDirPath, err := ctx.CreateSubDirectory(strings.ReplaceAll(filepath.Base(filenameNoExt), ".", "_")) + originalName := ctx.OriginalFilename(inputPath) + originalNameNoExt := strings.TrimSuffix(originalName, filepath.Ext(originalName)) + outputDirPath, err := ctx.CreateSubDirectory(uuid.New().String()) if err != nil { return nil, fmt.Errorf("create subdirectory from input path: %w", err) } @@ -297,15 +299,18 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S // Keep the original filename. for i, path := range paths { var newPath string + var newOriginal string if mode.Unify && mode.Mode == gotenberg.SplitModePages { + newOriginal = fmt.Sprintf("%s.pdf", originalNameNoExt) newPath = fmt.Sprintf( - "%s/%s.pdf", - outputDirPath, filenameNoExt, + "%s/%s", + outputDirPath, uuid.New().String()+".pdf", ) } else { + newOriginal = fmt.Sprintf("%s_%d.pdf", originalNameNoExt, i) newPath = fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, i, + "%s/%s", + outputDirPath, uuid.New().String()+".pdf", ) } @@ -314,6 +319,7 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S return nil, fmt.Errorf("rename path: %w", err) } + ctx.RegisterDiskPath(newPath, newOriginal) outputPaths = append(outputPaths, newPath) if mode.Unify && mode.Mode == gotenberg.SplitModePages { @@ -413,7 +419,7 @@ func WriteBookmarksStub(ctx *api.Context, engine gotenberg.PdfEngine, bookmarks } case map[string][]gotenberg.Bookmark: for _, inputPath := range inputPaths { - filename := filepath.Base(inputPath) + filename := ctx.OriginalFilename(inputPath) if specificBookmarks, ok := b[filename]; ok { err := engine.WriteBookmarks(ctx, ctx.Log(), inputPath, specificBookmarks) if err != nil { @@ -466,8 +472,28 @@ func EmbedFilesStub(ctx *api.Context, engine gotenberg.PdfEngine, embedPaths []s return nil } + // Engines like pdfcpu use filepath.Base(path) as the attachment name + // inside the PDF. Since disk filenames are now UUID-based, we create + // symlinks with the original names so that embeds are named correctly. + // See: https://github.com/gotenberg/gotenberg/issues/1500. + embedDir, err := ctx.CreateSubDirectory(uuid.New().String()) + if err != nil { + return fmt.Errorf("create embed subdirectory: %w", err) + } + + resolvedPaths := make([]string, len(embedPaths)) + for i, embedPath := range embedPaths { + originalName := ctx.OriginalFilename(embedPath) + resolvedPath := fmt.Sprintf("%s/%s", embedDir, originalName) + err := os.Symlink(embedPath, resolvedPath) + if err != nil { + return fmt.Errorf("symlink embed file '%s': %w", originalName, err) + } + resolvedPaths[i] = resolvedPath + } + for _, inputPath := range inputPaths { - err := engine.EmbedFiles(ctx, ctx.Log(), embedPaths, inputPath) + err := engine.EmbedFiles(ctx, ctx.Log(), resolvedPaths, inputPath) if err != nil { return fmt.Errorf("embed files into PDF '%s': %w", inputPath, err) } @@ -683,7 +709,7 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { if bMap != nil || autoIndexBookmarks { offset := 0 for _, inputPath := range inputPaths { - filename := filepath.Base(inputPath) + filename := ctx.OriginalFilename(inputPath) var fileBookmarks []gotenberg.Bookmark if bMap != nil { @@ -973,7 +999,7 @@ func readMetadataRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("read metadata: %w", err) } - res[filepath.Base(inputPath)] = metadata + res[ctx.OriginalFilename(inputPath)] = metadata } err = c.JSON(http.StatusOK, res) @@ -1051,7 +1077,7 @@ func readBookmarksRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("read bookmarks: %w", err) } - res[filepath.Base(inputPath)] = bookmarks + res[ctx.OriginalFilename(inputPath)] = bookmarks } err = c.JSON(http.StatusOK, res) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index 516d8a7f..6d32c495 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -1152,3 +1152,14 @@ Feature: /forms/chromium/convert/html | files | testdata/page-1-html/index.html | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/chromium/convert/html (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s): + | files | testdata/page-1-html/index.html | 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 the "foo.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index dfb3d116..170682c6 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -1128,3 +1128,15 @@ Feature: /forms/chromium/convert/markdown | files | testdata/page-1-markdown/page_1.md | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/chromium/convert/markdown (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/markdown" endpoint with the following form data and header(s): + | files | testdata/page-1-markdown/index.html | file | + | files | testdata/page-1-markdown/page_1.md | 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 the "foo.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature index 60c478bc..2a74613d 100644 --- a/test/integration/features/libreoffice_convert.feature +++ b/test/integration/features/libreoffice_convert.feature @@ -781,3 +781,14 @@ Feature: /forms/libreoffice/convert | files | testdata/page_1.docx | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/libreoffice/convert (Long Filename) + 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/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.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 the "foo.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/pdfengines_encrypt.feature b/test/integration/features/pdfengines_encrypt.feature index c397054c..62bc6a34 100644 --- a/test/integration/features/pdfengines_encrypt.feature +++ b/test/integration/features/pdfengines_encrypt.feature @@ -180,3 +180,15 @@ Feature: /forms/pdfengines/encrypt | userPassword | foo | field | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/encrypt (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | file | + | userPassword | foo | field | + | ownerPassword | bar | field | + | 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 diff --git a/test/integration/features/pdfengines_flatten.feature b/test/integration/features/pdfengines_flatten.feature index 10bff853..56f2b79a 100644 --- a/test/integration/features/pdfengines_flatten.feature +++ b/test/integration/features/pdfengines_flatten.feature @@ -118,3 +118,14 @@ Feature: /forms/pdfengines/flatten | files | testdata/page_1.pdf | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/flatten (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | 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 the response PDF(s) should be flatten diff --git a/test/integration/features/pdfengines_merge.feature b/test/integration/features/pdfengines_merge.feature index f418591a..56930468 100644 --- a/test/integration/features/pdfengines_merge.feature +++ b/test/integration/features/pdfengines_merge.feature @@ -664,3 +664,14 @@ Feature: /forms/pdfengines/merge | pdfa | PDF/A-3b | field | | embeds | testdata/embed_1.xml | file | Then the response status code should be 200 + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/merge (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | file | + | files | testdata/page_2.pdf | 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 diff --git a/test/integration/features/pdfengines_metadata.feature b/test/integration/features/pdfengines_metadata.feature index de2a430e..24ceb44f 100644 --- a/test/integration/features/pdfengines_metadata.feature +++ b/test/integration/features/pdfengines_metadata.feature @@ -293,3 +293,21 @@ Feature: /forms/pdfengines/metadata/{write|read} | files | teststore/foo.pdf | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/json" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/metadata/read (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | file | + Then the response status code should be 200 + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/metadata/write (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/write" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | file | + | metadata | {"Author":"Test"} | field | + | 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 diff --git a/test/integration/features/pdfengines_rotate.feature b/test/integration/features/pdfengines_rotate.feature index 358e19a0..0400aad2 100644 --- a/test/integration/features/pdfengines_rotate.feature +++ b/test/integration/features/pdfengines_rotate.feature @@ -162,3 +162,14 @@ Feature: /forms/pdfengines/rotate | rotateAngle | 90 | field | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/rotate (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/rotate" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | file | + | rotateAngle | 90 | field | + | 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 diff --git a/test/integration/features/pdfengines_split.feature b/test/integration/features/pdfengines_split.feature index f9b17646..87963489 100644 --- a/test/integration/features/pdfengines_split.feature +++ b/test/integration/features/pdfengines_split.feature @@ -764,3 +764,16 @@ Feature: /forms/pdfengines/split | pdfa | PDF/A-3b | field | | embeds | testdata/embed_1.xml | file | Then the response status code should be 200 + + # See: https://github.com/gotenberg/gotenberg/issues/1500. + Scenario: POST /forms/pdfengines/split (Long Filename) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.pdf | file | + | splitMode | pages | field | + | splitSpan | 1 | field | + | splitUnify | true | field | + | 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 diff --git a/test/integration/testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.docx b/test/integration/testdata/Longitudinell_jämförelse_mellan_laserkirurgi_och_strålbehandling_gällande_röstkvalitet_och_självskattad_kommunikation_upp_till_två_år_efter_tidig_stämbandscancer_i_ett_randomiserat_kontrollerat_försök.docx new file mode 100644 index 0000000000000000000000000000000000000000..1868509c5fe654e5dc4edb66f210459c88aa3f25 GIT binary patch literal 6408 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 Qw+aLS