mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
fix(api): handle correctly filename that takes more that 200 bytes
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user