From c30da805b3af537111dd1accd8f027b243f61b46 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 20 Dec 2024 15:51:57 +0100 Subject: [PATCH 001/254] feat(pdfengines): add split feature --- Makefile | 2 + pkg/gotenberg/fs.go | 67 ++- pkg/gotenberg/fs_test.go | 195 +++++- pkg/gotenberg/mocks.go | 17 + pkg/gotenberg/mocks_test.go | 22 + pkg/gotenberg/pdfengine.go | 27 + pkg/modules/api/api.go | 2 +- pkg/modules/api/api_test.go | 2 +- pkg/modules/api/context.go | 27 +- pkg/modules/api/context_test.go | 97 ++- pkg/modules/api/middlewares.go | 4 + pkg/modules/api/middlewares_test.go | 7 +- pkg/modules/api/mocks.go | 8 + pkg/modules/api/mocks_test.go | 17 +- pkg/modules/chromium/browser.go | 2 +- pkg/modules/chromium/browser_test.go | 122 ++-- pkg/modules/chromium/routes.go | 41 +- pkg/modules/chromium/routes_test.go | 60 +- pkg/modules/exiftool/exiftool.go | 5 + pkg/modules/exiftool/exiftool_test.go | 11 +- pkg/modules/libreoffice/api/libreoffice.go | 2 +- .../libreoffice/api/libreoffice_test.go | 26 +- .../libreoffice/pdfengine/pdfengine.go | 5 + .../libreoffice/pdfengine/pdfengine_test.go | 25 +- pkg/modules/libreoffice/routes.go | 48 +- pkg/modules/libreoffice/routes_test.go | 213 ++++++- pkg/modules/pdfcpu/doc.go | 1 + pkg/modules/pdfcpu/pdfcpu.go | 33 + pkg/modules/pdfcpu/pdfcpu_test.go | 91 ++- pkg/modules/pdfengines/multi.go | 41 ++ pkg/modules/pdfengines/multi_test.go | 235 +++++--- pkg/modules/pdfengines/pdfengines.go | 12 + pkg/modules/pdfengines/pdfengines_test.go | 18 +- pkg/modules/pdfengines/routes.go | 205 ++++++- pkg/modules/pdfengines/routes_test.go | 569 +++++++++++++++++- pkg/modules/pdftk/doc.go | 1 + pkg/modules/pdftk/pdftk.go | 26 + pkg/modules/pdftk/pdftk_test.go | 84 ++- pkg/modules/qpdf/doc.go | 1 + pkg/modules/qpdf/qpdf.go | 28 +- pkg/modules/qpdf/qpdf_test.go | 84 ++- 41 files changed, 2145 insertions(+), 338 deletions(-) diff --git a/Makefile b/Makefile index def2bb19b..b8f0f10e4 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ LOG_FORMAT=auto LOG_FIELDS_PREFIX= PDFENGINES_ENGINES= PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk +PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine PDFENGINES_READ_METADATA_ENGINES=exiftool PDFENGINES_WRITE_METADATA_ENGINES=exiftool @@ -141,6 +142,7 @@ run: ## Start a Gotenberg container --log-fields-prefix=$(LOG_FIELDS_PREFIX) \ --pdfengines-engines=$(PDFENGINES_ENGINES) \ --pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \ + --pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \ --pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \ --pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \ --pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \ diff --git a/pkg/gotenberg/fs.go b/pkg/gotenberg/fs.go index 8c2d0a98c..99b403eb0 100644 --- a/pkg/gotenberg/fs.go +++ b/pkg/gotenberg/fs.go @@ -3,22 +3,56 @@ package gotenberg import ( "fmt" "os" + "path/filepath" + "strings" "github.com/google/uuid" ) +// MkdirAll defines the method signature for create a directory. Implement this +// interface if you don't want to rely on [os.MkdirAll], notably for testing +// purpose. +type MkdirAll interface { + // MkdirAll uses the same signature as [os.MkdirAll]. + MkdirAll(path string, perm os.FileMode) error +} + +// OsMkdirAll implements the [MkdirAll] interface with [os.MkdirAll]. +type OsMkdirAll struct{} + +// MkdirAll is a wrapper around [os.MkdirAll]. +func (o *OsMkdirAll) MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, perm) } + +// PathRename defines the method signature for renaming files. Implement this +// interface if you don't want to rely on [os.Rename], notably for testing +// purpose. +type PathRename interface { + // Rename uses the same signature as [os.Rename]. + Rename(oldpath, newpath string) error +} + +// OsPathRename implements the [PathRename] interface with [os.Rename]. +type OsPathRename struct{} + +// Rename is a wrapper around [os.Rename]. +func (o *OsPathRename) Rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} + // FileSystem provides utilities for managing temporary directories. It creates // unique directory names based on UUIDs to ensure isolation of temporary files // for different modules. type FileSystem struct { workingDir string + mkdirAll MkdirAll } // NewFileSystem initializes a new [FileSystem] instance with a unique working // directory. -func NewFileSystem() *FileSystem { +func NewFileSystem(mkdirAll MkdirAll) *FileSystem { return &FileSystem{ workingDir: uuid.NewString(), + mkdirAll: mkdirAll, } } @@ -44,7 +78,7 @@ func (fs *FileSystem) NewDirPath() string { func (fs *FileSystem) MkdirAll() (string, error) { path := fs.NewDirPath() - err := os.MkdirAll(path, 0o755) + err := fs.mkdirAll.MkdirAll(path, 0o755) if err != nil { return "", fmt.Errorf("create directory %s: %w", path, err) } @@ -52,10 +86,27 @@ func (fs *FileSystem) MkdirAll() (string, error) { return path, nil } -// PathRename defines the method signature for renaming files. Implement this -// interface if you don't want to rely on [os.Rename], notably for testing -// purpose. -type PathRename interface { - // Rename uses the same signature as [os.Rename]. - Rename(oldpath, newpath string) error +// WalkDir walks through the root level of a directory and returns a list of +// files paths that match the specified file extension. +func WalkDir(dir, ext string) ([]string, error) { + var files []string + err := filepath.Walk(dir, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if info.IsDir() { + return nil + } + if strings.EqualFold(filepath.Ext(info.Name()), ext) { + files = append(files, path) + } + return nil + }) + return files, err } + +// Interface guards. +var ( + _ MkdirAll = (*OsMkdirAll)(nil) + _ PathRename = (*OsPathRename)(nil) +) diff --git a/pkg/gotenberg/fs_test.go b/pkg/gotenberg/fs_test.go index f074acb66..d7f641204 100644 --- a/pkg/gotenberg/fs_test.go +++ b/pkg/gotenberg/fs_test.go @@ -1,14 +1,84 @@ package gotenberg import ( + "errors" "fmt" + "io" "os" + "path/filepath" + "reflect" "strings" "testing" + + "github.com/google/uuid" ) +func TestOsMkdirAll_MkdirAll(t *testing.T) { + dirPath, err := NewFileSystem(new(OsMkdirAll)).MkdirAll() + if err != nil { + t.Fatalf("create working directory: %v", err) + } + + err = os.RemoveAll(dirPath) + if err != nil { + t.Fatalf("remove working directory: %v", err) + } +} + +func TestOsPathRename_Rename(t *testing.T) { + dirPath, err := NewFileSystem(new(OsMkdirAll)).MkdirAll() + if err != nil { + t.Fatalf("create working directory: %v", err) + } + + path := "/tests/test/testdata/api/sample1.txt" + copyPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString())) + + in, err := os.Open(path) + if err != nil { + t.Fatalf("open file: %v", err) + } + + defer func() { + err := in.Close() + if err != nil { + t.Fatalf("close file: %v", err) + } + }() + + out, err := os.Create(copyPath) + if err != nil { + t.Fatalf("create new file: %v", err) + } + + defer func() { + err := out.Close() + if err != nil { + t.Fatalf("close new file: %v", err) + } + }() + + _, err = io.Copy(out, in) + if err != nil { + t.Fatalf("copy file to new file: %v", err) + } + + rename := new(OsPathRename) + newPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString())) + + err = rename.Rename(copyPath, newPath) + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + + err = os.RemoveAll(dirPath) + if err != nil { + t.Fatalf("remove working directory: %v", err) + } +} + func TestFileSystem_WorkingDir(t *testing.T) { - fs := NewFileSystem() + fs := NewFileSystem(new(MkdirAllMock)) dirName := fs.WorkingDir() if dirName == "" { @@ -17,7 +87,7 @@ func TestFileSystem_WorkingDir(t *testing.T) { } func TestFileSystem_WorkingDirPath(t *testing.T) { - fs := NewFileSystem() + fs := NewFileSystem(new(MkdirAllMock)) expectedPath := fmt.Sprintf("%s/%s", os.TempDir(), fs.WorkingDir()) if fs.WorkingDirPath() != expectedPath { @@ -26,7 +96,7 @@ func TestFileSystem_WorkingDirPath(t *testing.T) { } func TestFileSystem_NewDirPath(t *testing.T) { - fs := NewFileSystem() + fs := NewFileSystem(new(MkdirAllMock)) newDir := fs.NewDirPath() expectedPrefix := fs.WorkingDirPath() @@ -36,20 +106,117 @@ func TestFileSystem_NewDirPath(t *testing.T) { } func TestFileSystem_MkdirAll(t *testing.T) { - fs := NewFileSystem() + for _, tc := range []struct { + scenario string + mkdirAll MkdirAll + expectError bool + }{ + { + scenario: "error", + mkdirAll: &MkdirAllMock{ + MkdirAllMock: func(path string, perm os.FileMode) error { + return errors.New("foo") + }, + }, + expectError: true, + }, + { + scenario: "success", + mkdirAll: &MkdirAllMock{ + MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }, + }, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + fs := NewFileSystem(tc.mkdirAll) - newPath, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } + _, err := fs.MkdirAll() + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } - _, err = os.Stat(newPath) - if os.IsNotExist(err) { - t.Errorf("expected directory '%s' to exist but it doesn't", newPath) + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + }) } +} - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) +func TestWalkDir(t *testing.T) { + for _, tc := range []struct { + scenario string + dir string + ext string + expectError bool + expectFiles []string + }{ + { + scenario: "directory does not exist", + dir: uuid.NewString(), + ext: ".pdf", + expectError: true, + }, + { + scenario: "find PDF files", + dir: func() string { + path := fmt.Sprintf("%s/a_directory", os.TempDir()) + + err := os.MkdirAll(path, 0o755) + if err != nil { + t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + } + + err = os.WriteFile(fmt.Sprintf("%s/a_foo_file.pdf", path), []byte{1}, 0o755) + if err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + err = os.WriteFile(fmt.Sprintf("%s/a_bar_file.PDF", path), []byte{1}, 0o755) + if err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + err = os.WriteFile(fmt.Sprintf("%s/a_baz_file.txt", path), []byte{1}, 0o755) + if err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + return path + }(), + ext: ".pdf", + expectError: false, + expectFiles: []string{"/tmp/a_directory/a_bar_file.PDF", "/tmp/a_directory/a_foo_file.pdf"}, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + defer func() { + err := os.RemoveAll(tc.dir) + if err != nil { + t.Fatalf("expected no error while cleaning up but got: %v", err) + } + }() + + files, err := WalkDir(tc.dir, tc.ext) + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + + if tc.expectError && err != nil { + return + } + + if !reflect.DeepEqual(files, tc.expectFiles) { + t.Errorf("expected files %+v, but got %+v", tc.expectFiles, files) + } + }) } } diff --git a/pkg/gotenberg/mocks.go b/pkg/gotenberg/mocks.go index 49154c32d..2ade89525 100644 --- a/pkg/gotenberg/mocks.go +++ b/pkg/gotenberg/mocks.go @@ -2,6 +2,7 @@ package gotenberg import ( "context" + "os" "go.uber.org/zap" ) @@ -36,6 +37,7 @@ func (mod *ValidatorMock) Validate() error { // PdfEngineMock is a mock for the [PdfEngine] interface. type PdfEngineMock struct { MergeMock func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error + SplitMock func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) ConvertMock func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error ReadMetadataMock func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error @@ -45,6 +47,10 @@ func (engine *PdfEngineMock) Merge(ctx context.Context, logger *zap.Logger, inpu return engine.MergeMock(ctx, logger, inputPaths, outputPath) } +func (engine *PdfEngineMock) Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) { + return engine.SplitMock(ctx, logger, mode, inputPath, outputDirPath) +} + func (engine *PdfEngineMock) Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error { return engine.ConvertMock(ctx, logger, formats, inputPath, outputPath) } @@ -137,6 +143,15 @@ func (provider *MetricsProviderMock) Metrics() ([]Metric, error) { return provider.MetricsMock() } +// MkdirAllMock is a mock for the [MkdirAll] interface. +type MkdirAllMock struct { + MkdirAllMock func(path string, perm os.FileMode) error +} + +func (mkdirAll *MkdirAllMock) MkdirAll(path string, perm os.FileMode) error { + return mkdirAll.MkdirAllMock(path, perm) +} + // PathRenameMock is a mock for the [PathRename] interface. type PathRenameMock struct { RenameMock func(oldpath, newpath string) error @@ -156,4 +171,6 @@ var ( _ ProcessSupervisor = (*ProcessSupervisorMock)(nil) _ LoggerProvider = (*LoggerProviderMock)(nil) _ MetricsProvider = (*MetricsProviderMock)(nil) + _ MkdirAll = (*MkdirAllMock)(nil) + _ PathRename = (*PathRenameMock)(nil) ) diff --git a/pkg/gotenberg/mocks_test.go b/pkg/gotenberg/mocks_test.go index 1be6c658c..953a6ec28 100644 --- a/pkg/gotenberg/mocks_test.go +++ b/pkg/gotenberg/mocks_test.go @@ -2,6 +2,7 @@ package gotenberg import ( "context" + "os" "testing" "go.uber.org/zap" @@ -52,6 +53,9 @@ func TestPDFEngineMock(t *testing.T) { MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return nil }, + SplitMock: func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, nil + }, ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error { return nil }, @@ -68,6 +72,11 @@ func TestPDFEngineMock(t *testing.T) { t.Errorf("expected no error from PdfEngineMock.Merge, but got: %v", err) } + _, err = mock.Split(context.Background(), zap.NewNop(), SplitMode{}, "", "") + if err != nil { + t.Errorf("expected no error from PdfEngineMock.Split, but got: %v", err) + } + err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "") if err != nil { t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err) @@ -205,6 +214,19 @@ func TestMetricsProviderMock(t *testing.T) { } } +func TestMkdirAllMock(t *testing.T) { + mock := &MkdirAllMock{ + MkdirAllMock: func(dir string, perm os.FileMode) error { + return nil + }, + } + + err := mock.MkdirAll("/foo", 0o755) + if err != nil { + t.Errorf("expected no error from MkdirAllMock.MkdirAll, but got: %v", err) + } +} + func TestPathRenameMock(t *testing.T) { mock := &PathRenameMock{ RenameMock: func(oldpath, newpath string) error { diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index 87c32c158..bc74f09f2 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -12,6 +12,10 @@ var ( // PdfEngine interface is not supported by its current implementation. ErrPdfEngineMethodNotSupported = errors.New("method not supported") + // ErrPdfSplitModeNotSupported is returned when the Split method of the + // PdfEngine interface does not sumport a requested PDF split mode. + ErrPdfSplitModeNotSupported = errors.New("split mode not supported") + // ErrPdfFormatNotSupported is returned when the Convert method of the // PdfEngine interface does not support a requested PDF format conversion. ErrPdfFormatNotSupported = errors.New("PDF format not supported") @@ -21,6 +25,26 @@ var ( ErrPdfEngineMetadataValueNotSupported = errors.New("metadata value not supported") ) +const ( + // SplitModeIntervals represents a mode where a PDF is split at specific + // intervals. + SplitModeIntervals string = "intervals" + + // SplitModePages represents a mode where a PDF is split at specific page + // ranges. + SplitModePages string = "pages" +) + +// SplitMode gathers the data required to split a PDF into multiple parts. +type SplitMode struct { + // Mode is either "intervals" or "pages". + Mode string + + // Span is either the intervals or the page ranges to extract, depending on + // the selected mode. + Span string +} + const ( // PdfA1a represents the PDF/A-1a format. PdfA1a string = "PDF/A-1a" @@ -65,6 +89,9 @@ type PdfEngine interface { // is determined by the order of files provided in inputPaths. Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error + // Split splits a given PDF file. + Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) + // Convert transforms a given PDF to the specified formats defined in // PdfFormats. If no format, it does nothing. Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index 3d6cb4598..7daa18925 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -318,7 +318,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error { a.logger = logger // File system. - a.fs = gotenberg.NewFileSystem() + a.fs = gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) return nil } diff --git a/pkg/modules/api/api_test.go b/pkg/modules/api/api_test.go index 885076af4..b32eace84 100644 --- a/pkg/modules/api/api_test.go +++ b/pkg/modules/api/api_test.go @@ -850,7 +850,7 @@ func TestApi_Start(t *testing.T) { }, } mod.readyFn = tc.readyFn - mod.fs = gotenberg.NewFileSystem() + mod.fs = gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) mod.logger = zap.NewNop() err := mod.Start() diff --git a/pkg/modules/api/context.go b/pkg/modules/api/context.go index 7d895810d..d761248b2 100644 --- a/pkg/modules/api/context.go +++ b/pkg/modules/api/context.go @@ -47,6 +47,7 @@ type Context struct { logger *zap.Logger echoCtx echo.Context + mkdirAll gotenberg.MkdirAll pathRename gotenberg.PathRename context.Context } @@ -81,12 +82,6 @@ type downloadFrom struct { ExtraHttpHeaders map[string]string `json:"extraHttpHeaders"` } -type osPathRename struct{} - -func (o *osPathRename) Rename(oldpath, newpath string) error { - return os.Rename(oldpath, newpath) -} - // newContext returns a [Context] by parsing a "multipart/form-data" request. func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSystem, timeout time.Duration, bodyLimit int64, downloadFromCfg downloadFromConfig, traceHeader, trace string) (*Context, context.CancelFunc, error) { processCtx, processCancel := context.WithTimeout(context.Background(), timeout) @@ -112,7 +107,8 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst cancelled: false, logger: logger, echoCtx: echoCtx, - pathRename: new(osPathRename), + mkdirAll: new(gotenberg.OsMkdirAll), + pathRename: new(gotenberg.OsPathRename), Context: processCtx, } @@ -414,9 +410,21 @@ func (ctx *Context) GeneratePath(extension string) string { return fmt.Sprintf("%s/%s%s", ctx.dirPath, uuid.New().String(), extension) } +// CreateSubDirectory creates a subdirectory within the context's working +// directory. +func (ctx *Context) CreateSubDirectory(dirName string) (string, error) { + path := fmt.Sprintf("%s/%s", ctx.dirPath, dirName) + err := ctx.mkdirAll.MkdirAll(path, 0o755) + if err != nil { + return "", fmt.Errorf("create sub-directory %s: %w", path, err) + } + return path, nil +} + // Rename is just a wrapper around [os.Rename], as we need to mock this // behavior in our tests. func (ctx *Context) Rename(oldpath, newpath string) error { + ctx.Log().Debug(fmt.Sprintf("rename %s to %s", oldpath, newpath)) err := ctx.pathRename.Rename(oldpath, newpath) if err != nil { return fmt.Errorf("rename path: %w", err) @@ -496,8 +504,3 @@ func (ctx *Context) OutputFilename(outputPath string) string { return fmt.Sprintf("%s%s", filename, filepath.Ext(outputPath)) } - -// Interface guard. -var ( - _ gotenberg.PathRename = (*osPathRename)(nil) -) diff --git a/pkg/modules/api/context_test.go b/pkg/modules/api/context_test.go index ddb7e9f35..05649df8a 100644 --- a/pkg/modules/api/context_test.go +++ b/pkg/modules/api/context_test.go @@ -4,78 +4,22 @@ import ( "bytes" "context" "errors" - "fmt" - "io" "mime/multipart" "net/http" "net/http/httptest" "os" - "path/filepath" "reflect" "strings" "testing" "time" "github.com/dlclark/regexp2" - "github.com/google/uuid" "github.com/labstack/echo/v4" "go.uber.org/zap" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" ) -func TestOsPathRename_Rename(t *testing.T) { - dirPath, err := gotenberg.NewFileSystem().MkdirAll() - if err != nil { - t.Fatalf("create working directory: %v", err) - } - - path := "/tests/test/testdata/api/sample1.txt" - copyPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString())) - - in, err := os.Open(path) - if err != nil { - t.Fatalf("open file: %v", err) - } - - defer func() { - err := in.Close() - if err != nil { - t.Fatalf("close file: %v", err) - } - }() - - out, err := os.Create(copyPath) - if err != nil { - t.Fatalf("create new file: %v", err) - } - - defer func() { - err := out.Close() - if err != nil { - t.Fatalf("close new file: %v", err) - } - }() - - _, err = io.Copy(out, in) - if err != nil { - t.Fatalf("copy file to new file: %v", err) - } - - rename := new(osPathRename) - newPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString())) - - err = rename.Rename(copyPath, newPath) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - - err = os.RemoveAll(dirPath) - if err != nil { - t.Fatalf("remove working directory: %v", err) - } -} - func TestNewContext(t *testing.T) { defaultAllowList, err := regexp2.Compile("", 0) if err != nil { @@ -548,7 +492,7 @@ func TestNewContext(t *testing.T) { } handler := func(c echo.Context) error { - ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(), time.Duration(10)*time.Second, tc.bodyLimit, tc.downloadFromCfg, "Gotenberg-Trace", "123") + ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, tc.bodyLimit, tc.downloadFromCfg, "Gotenberg-Trace", "123") defer cancel() // Context already cancelled. defer cancel() @@ -647,6 +591,42 @@ func TestContext_FormData(t *testing.T) { } } +func TestContext_CreateSubDirectory(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *Context + expectError bool + }{ + { + scenario: "failure", + ctx: &Context{mkdirAll: &gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return errors.New("cannot rename") + }}}, + expectError: true, + }, + { + scenario: "success", + ctx: &Context{mkdirAll: &gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}}, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.logger = zap.NewNop() + _, err := tc.ctx.CreateSubDirectory("foo") + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + }) + } +} + func TestContext_GeneratePath(t *testing.T) { ctx := &Context{ dirPath: "/foo", @@ -680,6 +660,7 @@ func TestContext_Rename(t *testing.T) { }, } { t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.logger = zap.NewNop() err := tc.ctx.Rename("", "") if tc.expectError && err == nil { @@ -788,7 +769,7 @@ func TestContext_BuildOutputFile(t *testing.T) { }, } { t.Run(tc.scenario, func(t *testing.T) { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) dirPath, err := fs.MkdirAll() if err != nil { t.Fatalf("expected no erro but got: %v", err) diff --git a/pkg/modules/api/middlewares.go b/pkg/modules/api/middlewares.go index c3939b5ac..78bcb8542 100644 --- a/pkg/modules/api/middlewares.go +++ b/pkg/modules/api/middlewares.go @@ -48,6 +48,10 @@ func ParseError(err error) (int, string) { return http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests) } + if errors.Is(err, gotenberg.ErrPdfSplitModeNotSupported) { + return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues" + } + if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) { return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues" } diff --git a/pkg/modules/api/middlewares_test.go b/pkg/modules/api/middlewares_test.go index 6edd1e2e4..8bef12682 100644 --- a/pkg/modules/api/middlewares_test.go +++ b/pkg/modules/api/middlewares_test.go @@ -38,6 +38,11 @@ func TestParseError(t *testing.T) { expectStatus: http.StatusTooManyRequests, expectMessage: http.StatusText(http.StatusTooManyRequests), }, + { + err: gotenberg.ErrPdfSplitModeNotSupported, + expectStatus: http.StatusBadRequest, + expectMessage: "At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues", + }, { err: gotenberg.ErrPdfFormatNotSupported, expectStatus: http.StatusBadRequest, @@ -462,7 +467,7 @@ func TestContextMiddleware(t *testing.T) { c.Set("trace", "foo") c.Set("startTime", time.Now()) - err := contextMiddleware(gotenberg.NewFileSystem(), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c) + err := contextMiddleware(gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c) if tc.expectErr && err == nil { t.Errorf("test %d: expected error but got: %v", i, err) diff --git a/pkg/modules/api/mocks.go b/pkg/modules/api/mocks.go index 667cd14e9..6d6c2a5f7 100644 --- a/pkg/modules/api/mocks.go +++ b/pkg/modules/api/mocks.go @@ -86,6 +86,14 @@ func (ctx *ContextMock) SetEchoContext(c echo.Context) { ctx.Context.echoCtx = c } +// SetMkdirAll sets the [gotenberg.MkdirAll]. +// +// ctx := &api.ContextMock{Context: &api.Context{}} +// ctx.SetMkdirAll(mkdirAll) +func (ctx *ContextMock) SetMkdirAll(mkdirAll gotenberg.MkdirAll) { + ctx.Context.mkdirAll = mkdirAll +} + // SetPathRename sets the [gotenberg.PathRename]. // // ctx := &api.ContextMock{Context: &api.Context{}} diff --git a/pkg/modules/api/mocks_test.go b/pkg/modules/api/mocks_test.go index 5910726c2..ecc2a19cf 100644 --- a/pkg/modules/api/mocks_test.go +++ b/pkg/modules/api/mocks_test.go @@ -7,6 +7,8 @@ import ( "github.com/alexliesenfeld/health" "github.com/labstack/echo/v4" "go.uber.org/zap" + + "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" ) func TestContextMock_SetDirPath(t *testing.T) { @@ -117,10 +119,23 @@ func TestContextMock_SetEchoContext(t *testing.T) { } } +func TestContextMock_SetMkdirAll(t *testing.T) { + mock := ContextMock{&Context{}} + + expect := new(gotenberg.OsMkdirAll) + mock.SetMkdirAll(expect) + + actual := mock.mkdirAll + + if actual != expect { + t.Errorf("expected %v but got %v", expect, actual) + } +} + func TestContextMock_SetPathRename(t *testing.T) { mock := ContextMock{&Context{}} - expect := new(osPathRename) + expect := new(gotenberg.OsPathRename) mock.SetPathRename(expect) actual := mock.pathRename diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index 380dcccfb..1da24d877 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -62,7 +62,7 @@ func newChromiumBrowser(arguments browserArguments) browser { b := &chromiumBrowser{ initialCtx: context.Background(), arguments: arguments, - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), } b.isStarted.Store(false) diff --git a/pkg/modules/chromium/browser_test.go b/pkg/modules/chromium/browser_test.go index 3ba608b4b..a5698eaf7 100644 --- a/pkg/modules/chromium/browser_test.go +++ b/pkg/modules/chromium/browser_test.go @@ -263,7 +263,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { b.isStarted.Store(false) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: false, start: false, expectError: true, @@ -275,7 +275,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { b.isStarted.Store(true) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: true, start: false, expectError: true, @@ -291,7 +291,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { b.isStarted.Store(true) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: false, start: false, expectError: true, @@ -308,7 +308,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { b.isStarted.Store(true) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: false, start: false, expectError: true, @@ -325,7 +325,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -357,7 +357,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -389,7 +389,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -424,7 +424,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -457,7 +457,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -495,7 +495,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -528,7 +528,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -554,7 +554,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -588,7 +588,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -621,7 +621,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -654,7 +654,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -688,7 +688,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -723,7 +723,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -758,7 +758,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -812,7 +812,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -845,7 +845,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -881,7 +881,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -914,7 +914,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -949,7 +949,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -984,7 +984,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1019,7 +1019,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1065,7 +1065,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1100,7 +1100,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1146,7 +1146,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1181,7 +1181,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1217,7 +1217,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1255,7 +1255,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1288,7 +1288,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1421,7 +1421,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { b.isStarted.Store(false) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: false, start: false, expectError: true, @@ -1437,7 +1437,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { b.isStarted.Store(true) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: true, start: false, expectError: true, @@ -1453,7 +1453,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { b.isStarted.Store(true) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: false, start: false, expectError: true, @@ -1470,7 +1470,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { b.isStarted.Store(true) return b }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), noDeadline: false, start: false, expectError: true, @@ -1487,7 +1487,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1519,7 +1519,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1551,7 +1551,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1586,7 +1586,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1619,7 +1619,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1657,7 +1657,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1690,7 +1690,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1716,7 +1716,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1750,7 +1750,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1783,7 +1783,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1816,7 +1816,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1850,7 +1850,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1885,7 +1885,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1920,7 +1920,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -1974,7 +1974,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2009,7 +2009,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2042,7 +2042,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2077,7 +2077,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2112,7 +2112,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2147,7 +2147,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2193,7 +2193,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2228,7 +2228,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2274,7 +2274,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2317,7 +2317,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -2365,7 +2365,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index 2ae33b250..ee330a26a 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -326,8 +326,9 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) form, options := FormDataChromiumPdfOptions(ctx) + mode := pdfengines.FormDataPdfSplitMode(form, false) pdfFormats := pdfengines.FormDataPdfFormats(form) - metadata := pdfengines.FormDataPdfMetadata(form) + metadata := pdfengines.FormDataPdfMetadata(form, false) var url string err := form. @@ -337,7 +338,7 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("validate form data: %w", err) } - err = convertUrl(ctx, chromium, engine, url, options, pdfFormats, metadata) + err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata) if err != nil { return fmt.Errorf("convert URL to PDF: %w", err) } @@ -386,8 +387,9 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) form, options := FormDataChromiumPdfOptions(ctx) + mode := pdfengines.FormDataPdfSplitMode(form, false) pdfFormats := pdfengines.FormDataPdfFormats(form) - metadata := pdfengines.FormDataPdfMetadata(form) + metadata := pdfengines.FormDataPdfMetadata(form, false) var inputPath string err := form. @@ -398,7 +400,7 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { } url := fmt.Sprintf("file://%s", inputPath) - err = convertUrl(ctx, chromium, engine, url, options, pdfFormats, metadata) + err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata) if err != nil { return fmt.Errorf("convert HTML to PDF: %w", err) } @@ -448,8 +450,9 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) form, options := FormDataChromiumPdfOptions(ctx) + mode := pdfengines.FormDataPdfSplitMode(form, false) pdfFormats := pdfengines.FormDataPdfFormats(form) - metadata := pdfengines.FormDataPdfMetadata(form) + metadata := pdfengines.FormDataPdfMetadata(form, false) var ( inputPath string @@ -469,7 +472,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("transform markdown file(s) to HTML: %w", err) } - err = convertUrl(ctx, chromium, engine, url, options, pdfFormats, metadata) + err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata) if err != nil { return fmt.Errorf("convert markdown to PDF: %w", err) } @@ -593,7 +596,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string) return fmt.Sprintf("file://%s", inputPath), nil } -func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, pdfFormats gotenberg.PdfFormats, metadata map[string]interface{}) error { +func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]interface{}) error { outputPath := ctx.GeneratePath(".pdf") err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options) @@ -632,16 +635,34 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url return fmt.Errorf("convert to PDF: %w", err) } - outputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, []string{outputPath}) + outputPaths, err := pdfengines.SplitPdfStub(ctx, engine, mode, []string{outputPath}) if err != nil { - return fmt.Errorf("convert PDF: %w", err) + return fmt.Errorf("split PDF: %w", err) } - err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) + convertOutputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths) + if err != nil { + return fmt.Errorf("convert PDF(s): %w", err) + } + + err = pdfengines.WriteMetadataStub(ctx, engine, metadata, convertOutputPaths) if err != nil { return fmt.Errorf("write metadata: %w", err) } + zeroValuedSplitMode := gotenberg.SplitMode{} + zeroValuedPdfFormats := gotenberg.PdfFormats{} + if mode != zeroValuedSplitMode && pdfFormats != zeroValuedPdfFormats { + // The PDF has been split and split parts have been converted to a + // specific format. We want to keep the split naming. + for i, convertOutputPath := range convertOutputPaths { + err = ctx.Rename(convertOutputPath, outputPaths[i]) + if err != nil { + return fmt.Errorf("rename output path: %w", err) + } + } + } + err = ctx.AddOutputPaths(outputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) diff --git a/pkg/modules/chromium/routes_test.go b/pkg/modules/chromium/routes_test.go index 1e7363ba6..933b833ed 100644 --- a/pkg/modules/chromium/routes_test.go +++ b/pkg/modules/chromium/routes_test.go @@ -1428,6 +1428,7 @@ func TestConvertUrl(t *testing.T) { api Api engine gotenberg.PdfEngine options PdfOptions + splitMode gotenberg.SplitMode pdfFormats gotenberg.PdfFormats metadata map[string]interface{} expectError bool @@ -1570,6 +1571,36 @@ func TestConvertUrl(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 0, }, + { + scenario: "PDF engine split error", + ctx: &api.ContextMock{Context: new(api.Context)}, + api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { + return nil + }}, + engine: &gotenberg.PdfEngineMock{SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") + }}, + options: DefaultPdfOptions(), + splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "success with split mode", + ctx: &api.ContextMock{Context: new(api.Context)}, + api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { + return nil + }}, + engine: &gotenberg.PdfEngineMock{SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }}, + options: DefaultPdfOptions(), + splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 1, + }, { scenario: "PDF engine convert error", ctx: &api.ContextMock{Context: new(api.Context)}, @@ -1600,6 +1631,27 @@ func TestConvertUrl(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 1, }, + { + scenario: "success with split mode and PDF formats", + ctx: &api.ContextMock{Context: new(api.Context)}, + api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { + return nil + }}, + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return nil + }, + }, + options: DefaultPdfOptions(), + splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, + pdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b}, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 1, + }, { scenario: "PDF engine write metadata error", ctx: &api.ContextMock{Context: new(api.Context)}, @@ -1659,7 +1711,13 @@ func TestConvertUrl(t *testing.T) { } { t.Run(tc.scenario, func(t *testing.T) { tc.ctx.SetLogger(zap.NewNop()) - err := convertUrl(tc.ctx.Context, tc.api, tc.engine, "", tc.options, tc.pdfFormats, tc.metadata) + tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { + return nil + }}) + err := convertUrl(tc.ctx.Context, tc.api, tc.engine, "", tc.options, tc.splitMode, tc.pdfFormats, tc.metadata) if tc.expectError && err == nil { t.Fatal("expected error but got none", err) diff --git a/pkg/modules/exiftool/exiftool.go b/pkg/modules/exiftool/exiftool.go index 7d2cb8d97..aeffc6a99 100644 --- a/pkg/modules/exiftool/exiftool.go +++ b/pkg/modules/exiftool/exiftool.go @@ -58,6 +58,11 @@ func (engine *ExifTool) Merge(ctx context.Context, logger *zap.Logger, inputPath return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// Split is not available in this implementation. +func (engine *ExifTool) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, fmt.Errorf("split PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // Convert is not available in this implementation. func (engine *ExifTool) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/exiftool/exiftool_test.go b/pkg/modules/exiftool/exiftool_test.go index 949087ec8..52c8f31d7 100644 --- a/pkg/modules/exiftool/exiftool_test.go +++ b/pkg/modules/exiftool/exiftool_test.go @@ -82,6 +82,15 @@ func TestExiftool_Merge(t *testing.T) { } } +func TestExiftool_Split(t *testing.T) { + engine := new(ExifTool) + _, err := engine.Split(context.Background(), zap.NewNop(), gotenberg.SplitMode{}, "", "") + + if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { + t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) + } +} + func TestExiftool_Convert(t *testing.T) { engine := new(ExifTool) err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") @@ -257,7 +266,7 @@ func TestExiftool_WriteMetadata(t *testing.T) { var destinationPath string if tc.createCopy { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) outputDir, err := fs.MkdirAll() if err != nil { t.Fatalf("expected error no but got: %v", err) diff --git a/pkg/modules/libreoffice/api/libreoffice.go b/pkg/modules/libreoffice/api/libreoffice.go index f8c4415b2..ee332ad48 100644 --- a/pkg/modules/libreoffice/api/libreoffice.go +++ b/pkg/modules/libreoffice/api/libreoffice.go @@ -44,7 +44,7 @@ type libreOfficeProcess struct { func newLibreOfficeProcess(arguments libreOfficeArguments) libreOffice { p := &libreOfficeProcess{ arguments: arguments, - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), } p.isStarted.Store(false) diff --git a/pkg/modules/libreoffice/api/libreoffice_test.go b/pkg/modules/libreoffice/api/libreoffice_test.go index 953cb908b..fce85515c 100644 --- a/pkg/modules/libreoffice/api/libreoffice_test.go +++ b/pkg/modules/libreoffice/api/libreoffice_test.go @@ -230,7 +230,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { p.isStarted.Store(false) return p }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), cancelledCtx: false, start: false, expectError: true, @@ -243,7 +243,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { p.isStarted.Store(true) return p }(), - fs: gotenberg.NewFileSystem(), + fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: "foo"}}, cancelledCtx: false, start: false, @@ -261,7 +261,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { ), options: Options{PageRanges: "foo"}, fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -291,7 +291,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { ), options: Options{Password: "foo"}, fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -344,7 +344,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -372,7 +372,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -400,7 +400,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -452,7 +452,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -481,7 +481,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -510,7 +510,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -539,7 +539,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { }, ), fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -625,7 +625,7 @@ func TestNonBasicLatinCharactersGuard(t *testing.T) { { scenario: "basic latin characters", fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { @@ -646,7 +646,7 @@ func TestNonBasicLatinCharactersGuard(t *testing.T) { { scenario: "non-basic latin characters", fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine.go b/pkg/modules/libreoffice/pdfengine/pdfengine.go index b478c21be..5416ab357 100644 --- a/pkg/modules/libreoffice/pdfengine/pdfengine.go +++ b/pkg/modules/libreoffice/pdfengine/pdfengine.go @@ -51,6 +51,11 @@ func (engine *LibreOfficePdfEngine) Merge(ctx context.Context, logger *zap.Logge return fmt.Errorf("merge PDFs with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// Split is not available in this implementation. +func (engine *LibreOfficePdfEngine) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, fmt.Errorf("split PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // Convert converts the given PDF to a specific PDF format. Currently, only the // PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/UA formats are available. If another // PDF format is requested, it returns a [gotenberg.ErrPdfFormatNotSupported] diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine_test.go b/pkg/modules/libreoffice/pdfengine/pdfengine_test.go index 8353954d6..1dc4ed737 100644 --- a/pkg/modules/libreoffice/pdfengine/pdfengine_test.go +++ b/pkg/modules/libreoffice/pdfengine/pdfengine_test.go @@ -118,11 +118,21 @@ func TestLibreOfficePdfEngine_Merge(t *testing.T) { } } +func TestLibreOfficePdfEngine_Split(t *testing.T) { + engine := new(LibreOfficePdfEngine) + _, err := engine.Split(context.Background(), zap.NewNop(), gotenberg.SplitMode{}, "", "") + + if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { + t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) + } +} + func TestLibreOfficePdfEngine_Convert(t *testing.T) { for _, tc := range []struct { - scenario string - api api.Uno - expectError bool + scenario string + api api.Uno + expectError bool + expectedError error }{ { scenario: "convert success", @@ -134,13 +144,14 @@ func TestLibreOfficePdfEngine_Convert(t *testing.T) { expectError: false, }, { - scenario: "invalid PDF format", + scenario: "ErrInvalidPdfFormats", api: &api.ApiMock{ PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error { return api.ErrInvalidPdfFormats }, }, - expectError: true, + expectError: true, + expectedError: gotenberg.ErrPdfFormatNotSupported, }, { scenario: "convert fail", @@ -163,6 +174,10 @@ func TestLibreOfficePdfEngine_Convert(t *testing.T) { if tc.expectError && err == nil { t.Fatal("expected error but got none") } + + if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { + t.Fatalf("expected error %v but got: %v", tc.expectedError, err) + } }) } } diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index b49677d64..86165833b 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -28,8 +28,11 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap defaultOptions := libreofficeapi.DefaultOptions() form := ctx.FormData() + splitMode := pdfengines.FormDataPdfSplitMode(form, false) pdfFormats := pdfengines.FormDataPdfFormats(form) - metadata := pdfengines.FormDataPdfMetadata(form) + metadata := pdfengines.FormDataPdfMetadata(form, false) + + zeroValuedSplitMode := gotenberg.SplitMode{} var ( inputPaths []string @@ -165,7 +168,9 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap MaxImageResolution: maxImageResolution, } - if nativePdfFormats { + if nativePdfFormats && splitMode == zeroValuedSplitMode { + // Only apply natively given PDF formats if we're not + // splitting the PDF later. options.PdfFormats = pdfFormats } @@ -209,11 +214,44 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap outputPaths = []string{outputPath} } - if !nativePdfFormats { - outputPaths, err = pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths) + if splitMode != zeroValuedSplitMode { + if !merge { + // 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) + + err = ctx.Rename(outputPaths[i], outputPath) + if err != nil { + return fmt.Errorf("rename output path: %w", err) + } + + outputPaths[i] = outputPath + } + } + + outputPaths, err = pdfengines.SplitPdfStub(ctx, engine, splitMode, outputPaths) + if err != nil { + return fmt.Errorf("split PDFs: %w", err) + } + } + + if !nativePdfFormats || (nativePdfFormats && splitMode != zeroValuedSplitMode) { + convertOutputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths) if err != nil { return fmt.Errorf("convert PDFs: %w", err) } + + if splitMode != zeroValuedSplitMode { + // The PDF has been split and split parts have been converted to + // specific formats. We want to keep the split naming. + for i, convertOutputPath := range convertOutputPaths { + err = ctx.Rename(convertOutputPath, outputPaths[i]) + if err != nil { + return fmt.Errorf("rename output path: %w", err) + } + } + } } err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) @@ -221,7 +259,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap return fmt.Errorf("write metadata: %w", err) } - if len(outputPaths) > 1 { + 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) diff --git a/pkg/modules/libreoffice/routes_test.go b/pkg/modules/libreoffice/routes_test.go index 041e41655..139f57489 100644 --- a/pkg/modules/libreoffice/routes_test.go +++ b/pkg/modules/libreoffice/routes_test.go @@ -3,7 +3,10 @@ package libreoffice import ( "context" "errors" + "fmt" "net/http" + "os" + "path/filepath" "slices" "testing" @@ -301,18 +304,18 @@ func TestConvertRoute(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "PDF engine convert error", + scenario: "PDF engine split error", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ "document.docx": "/document.docx", }) ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, + "splitMode": { + gotenberg.SplitModeIntervals, }, - "nativePdfFormats": { - "false", + "splitSpan": { + "1", }, }) return ctx @@ -326,8 +329,8 @@ func TestConvertRoute(t *testing.T) { }, }, engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") }, }, expectError: true, @@ -335,15 +338,18 @@ func TestConvertRoute(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "PDF engine write metadata error", + scenario: "PDF engine convert error", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ "document.docx": "/document.docx", }) ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + "pdfa": { + gotenberg.PdfA1b, + }, + "nativePdfFormats": { + "false", }, }) return ctx @@ -357,7 +363,7 @@ func TestConvertRoute(t *testing.T) { }, }, engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return errors.New("foo") }, }, @@ -366,17 +372,17 @@ func TestConvertRoute(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "cannot rename many files", + scenario: "PDF engine write metadata error", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - "document2.doc": "/document2.doc", + "document.docx": "/document.docx", + }) + ctx.SetValues(map[string][]string{ + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, }) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return errors.New("cannot rename") - }}) return ctx }(), libreOffice: &libreofficeapi.ApiMock{ @@ -384,7 +390,12 @@ func TestConvertRoute(t *testing.T) { return nil }, ExtensionsMock: func() []string { - return []string{".docx", ".doc"} + return []string{".docx"} + }, + }, + engine: &gotenberg.PdfEngineMock{ + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return errors.New("foo") }, }, expectError: true, @@ -550,9 +561,173 @@ func TestConvertRoute(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 1, }, + { + scenario: "success with split (many files)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "document.docx": "/document.docx", + "document2.docx": "/document2.docx", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + }) + return ctx + }(), + libreOffice: &libreofficeapi.ApiMock{ + PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { + return nil + }, + ExtensionsMock: func() []string { + return []string{".docx"} + }, + }, + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] + filenameNoExt := filepath.Base(inputPathNoExt) + return []string{ + fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, 0, + ), + fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, 1, + ), + }, nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 4, + expectOutputPaths: []string{"/document_docx/document.docx_0.pdf", "/document_docx/document.docx_1.pdf", "/document2_docx/document2.docx_0.pdf", "/document2_docx/document2.docx_1.pdf"}, + }, + { + scenario: "success with merge and split", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "document.docx": "/document.docx", + "document2.docx": "/document2.docx", + }) + ctx.SetValues(map[string][]string{ + "merge": { + "true", + }, + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + }) + return ctx + }(), + libreOffice: &libreofficeapi.ApiMock{ + PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { + return nil + }, + ExtensionsMock: func() []string { + return []string{".docx"} + }, + }, + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] + filenameNoExt := filepath.Base(inputPathNoExt) + return []string{ + fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, 0, + ), + fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, 1, + ), + }, nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 2, + }, + { + scenario: "success with split and native PDF/A & PDF/UA (many files)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "document.docx": "/document.docx", + "document2.docx": "/document2.docx", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + "pdfa": { + gotenberg.PdfA1b, + }, + "pdfua": { + "true", + }, + }) + return ctx + }(), + libreOffice: &libreofficeapi.ApiMock{ + PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { + return nil + }, + ExtensionsMock: func() []string { + return []string{".docx"} + }, + }, + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] + filenameNoExt := filepath.Base(inputPathNoExt) + return []string{ + fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, 0, + ), + fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, 1, + ), + }, nil + }, + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 4, + expectOutputPaths: []string{"/document_docx/document.docx_0.pdf", "/document_docx/document.docx_1.pdf", "/document2_docx/document2.docx_0.pdf", "/document2_docx/document2.docx_1.pdf"}, + }, } { t.Run(tc.scenario, func(t *testing.T) { tc.ctx.SetLogger(zap.NewNop()) + tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { + return nil + }}) c := echo.New().NewContext(nil, nil) c.Set("context", tc.ctx.Context) diff --git a/pkg/modules/pdfcpu/doc.go b/pkg/modules/pdfcpu/doc.go index e68e2a61f..6856a27ac 100644 --- a/pkg/modules/pdfcpu/doc.go +++ b/pkg/modules/pdfcpu/doc.go @@ -2,6 +2,7 @@ // interface using the pdfcpu command-line tool. This package allows for: // // 1. The merging of PDF files. +// 2. The splitting of PDF files. // // See: https://github.com/pdfcpu/pdfcpu. package pdfcpu diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index ac2d53589..b59573c1e 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "go.uber.org/zap" @@ -70,6 +71,38 @@ func (engine *PdfCpu) Merge(ctx context.Context, logger *zap.Logger, inputPaths return fmt.Errorf("merge PDFs with pdfcpu: %w", err) } +// Split splits a given PDF file. +func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + var args []string + + switch mode.Mode { + case gotenberg.SplitModeIntervals: + args = append(args, "split", "-mode", "span", inputPath, outputDirPath, mode.Span) + case gotenberg.SplitModePages: + outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath)) + args = append(args, "trim", "-pages", mode.Span, inputPath, outputPath) + default: + return nil, fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) + } + + cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) + if err != nil { + return nil, fmt.Errorf("create command: %w", err) + } + + _, err = cmd.Exec() + if err != nil { + return nil, fmt.Errorf("split PDFs with pdfcpu: %w", err) + } + + outputPaths, err := gotenberg.WalkDir(outputDirPath, ".pdf") + if err != nil { + return nil, fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err) + } + + return outputPaths, nil +} + // Convert is not available in this implementation. func (engine *PdfCpu) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/pdfcpu/pdfcpu_test.go b/pkg/modules/pdfcpu/pdfcpu_test.go index f009218a2..e962fc698 100644 --- a/pkg/modules/pdfcpu/pdfcpu_test.go +++ b/pkg/modules/pdfcpu/pdfcpu_test.go @@ -116,7 +116,7 @@ func TestPdfCpu_Merge(t *testing.T) { t.Fatalf("expected error but got: %v", err) } - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) outputDir, err := fs.MkdirAll() if err != nil { t.Fatalf("expected error but got: %v", err) @@ -142,6 +142,95 @@ func TestPdfCpu_Merge(t *testing.T) { } } +func TestPdfCpu_Split(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx context.Context + mode gotenberg.SplitMode + inputPath string + expectError bool + expectedError error + expectOutputPathsCount int + }{ + { + scenario: "ErrPdfSplitModeNotSupported", + expectError: true, + expectedError: gotenberg.ErrPdfSplitModeNotSupported, + expectOutputPathsCount: 0, + }, + { + scenario: "invalid context", + ctx: nil, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + expectError: true, + expectOutputPathsCount: 0, + }, + { + scenario: "invalid input path", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + inputPath: "", + expectError: true, + expectOutputPathsCount: 0, + }, + { + scenario: "success (intervals)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + expectError: false, + expectOutputPathsCount: 3, + }, + { + scenario: "success (pages)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, + inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + expectError: false, + expectOutputPathsCount: 1, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + engine := new(PdfCpu) + err := engine.Provision(nil) + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) + outputDir, err := fs.MkdirAll() + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + defer func() { + err = os.RemoveAll(fs.WorkingDirPath()) + if err != nil { + t.Fatalf("expected no error while cleaning up but got: %v", err) + } + }() + + outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir) + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + + if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { + t.Fatalf("expected error %v but got: %v", tc.expectedError, err) + } + + if tc.expectOutputPathsCount != len(outputPaths) { + t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths)) + } + }) + } +} + func TestPdfCpu_Convert(t *testing.T) { mod := new(PdfCpu) err := mod.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index 4cbbc3eac..c6c9514d6 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -13,6 +13,7 @@ import ( type multiPdfEngines struct { mergeEngines []gotenberg.PdfEngine + splitEngines []gotenberg.PdfEngine convertEngines []gotenberg.PdfEngine readMedataEngines []gotenberg.PdfEngine writeMedataEngines []gotenberg.PdfEngine @@ -20,12 +21,14 @@ type multiPdfEngines struct { func newMultiPdfEngines( mergeEngines, + splitEngines, convertEngines, readMetadataEngines, writeMedataEngines []gotenberg.PdfEngine, ) *multiPdfEngines { return &multiPdfEngines{ mergeEngines: mergeEngines, + splitEngines: splitEngines, convertEngines: convertEngines, readMedataEngines: readMetadataEngines, writeMedataEngines: writeMedataEngines, @@ -57,6 +60,44 @@ func (multi *multiPdfEngines) Merge(ctx context.Context, logger *zap.Logger, inp return fmt.Errorf("merge PDFs with multi PDF engines: %w", err) } +type splitResult struct { + outputPaths []string + err error +} + +// Split tries to split at intervals a given PDF thanks to its children. If the +// context is done, it stops and returns an error. +func (multi *multiPdfEngines) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + var err error + var mu sync.Mutex // to safely append errors. + + resultChan := make(chan splitResult, len(multi.splitEngines)) + + for _, engine := range multi.splitEngines { + go func(engine gotenberg.PdfEngine) { + outputPaths, err := engine.Split(ctx, logger, mode, inputPath, outputDirPath) + resultChan <- splitResult{outputPaths: outputPaths, err: err} + }(engine) + } + + for range multi.splitEngines { + select { + case result := <-resultChan: + if result.err != nil { + mu.Lock() + err = multierr.Append(err, result.err) + mu.Unlock() + } else { + return result.outputPaths, nil + } + case <-ctx.Done(): + return nil, ctx.Err() + } + } + + return nil, fmt.Errorf("split PDF with multi PDF engines: %w", err) +} + // Convert converts the given PDF to a specific PDF format. thanks to its // children. If the context is done, it stops and returns an error. func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { diff --git a/pkg/modules/pdfengines/multi_test.go b/pkg/modules/pdfengines/multi_test.go index 00e706d78..6e5686c0c 100644 --- a/pkg/modules/pdfengines/multi_test.go +++ b/pkg/modules/pdfengines/multi_test.go @@ -19,25 +19,22 @@ func TestMultiPdfEngines_Merge(t *testing.T) { }{ { scenario: "nominal behavior", - engine: newMultiPdfEngines( - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + mergeEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return nil }, }, }, - nil, - nil, - nil, - ), + }, ctx: context.Background(), expectError: false, }, { scenario: "at least one engine does not return an error", - engine: newMultiPdfEngines( - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + mergeEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return errors.New("foo") @@ -49,17 +46,14 @@ func TestMultiPdfEngines_Merge(t *testing.T) { }, }, }, - nil, - nil, - nil, - ), + }, ctx: context.Background(), expectError: false, }, { scenario: "all engines return an error", - engine: newMultiPdfEngines( - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + mergeEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return errors.New("foo") @@ -71,27 +65,21 @@ func TestMultiPdfEngines_Merge(t *testing.T) { }, }, }, - nil, - nil, - nil, - ), + }, ctx: context.Background(), expectError: true, }, { scenario: "context expired", - engine: newMultiPdfEngines( - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + mergeEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return nil }, }, }, - nil, - nil, - nil, - ), + }, ctx: func() context.Context { ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -115,6 +103,97 @@ func TestMultiPdfEngines_Merge(t *testing.T) { } } +func TestMultiPdfEngines_Split(t *testing.T) { + for _, tc := range []struct { + scenario string + engine *multiPdfEngines + ctx context.Context + expectError bool + }{ + { + scenario: "nominal behavior", + engine: &multiPdfEngines{ + splitEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, nil + }, + }, + }, + }, + ctx: context.Background(), + }, + { + scenario: "at least one engine does not return an error", + engine: &multiPdfEngines{ + splitEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") + }, + }, + &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, nil + }, + }, + }, + }, + ctx: context.Background(), + }, + { + scenario: "all engines return an error", + engine: &multiPdfEngines{ + splitEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") + }, + }, + &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") + }, + }, + }, + }, + ctx: context.Background(), + expectError: true, + }, + { + scenario: "context expired", + engine: &multiPdfEngines{ + splitEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, nil + }, + }, + }, + }, + ctx: func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + return ctx + }(), + expectError: true, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + _, err := tc.engine.Split(tc.ctx, zap.NewNop(), gotenberg.SplitMode{}, "", "") + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + }) + } +} + func TestMultiPdfEngines_Convert(t *testing.T) { for _, tc := range []struct { scenario string @@ -124,25 +203,21 @@ func TestMultiPdfEngines_Convert(t *testing.T) { }{ { scenario: "nominal behavior", - engine: newMultiPdfEngines( - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + convertEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return nil }, }, }, - nil, - nil, - ), + }, ctx: context.Background(), }, { scenario: "at least one engine does not return an error", - engine: newMultiPdfEngines( - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + convertEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return errors.New("foo") @@ -154,16 +229,13 @@ func TestMultiPdfEngines_Convert(t *testing.T) { }, }, }, - nil, - nil, - ), + }, ctx: context.Background(), }, { scenario: "all engines return an error", - engine: newMultiPdfEngines( - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + convertEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return errors.New("foo") @@ -175,26 +247,21 @@ func TestMultiPdfEngines_Convert(t *testing.T) { }, }, }, - nil, - nil, - ), + }, ctx: context.Background(), expectError: true, }, { scenario: "context expired", - engine: newMultiPdfEngines( - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + convertEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return nil }, }, }, - nil, - nil, - ), + }, ctx: func() context.Context { ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -227,26 +294,21 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { }{ { scenario: "nominal behavior", - engine: newMultiPdfEngines( - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + readMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return make(map[string]interface{}), nil }, }, }, - nil, - ), + }, ctx: context.Background(), }, { scenario: "at least one engine does not return an error", - engine: newMultiPdfEngines( - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + readMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return nil, errors.New("foo") @@ -258,16 +320,13 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { }, }, }, - nil, - ), + }, ctx: context.Background(), }, { scenario: "all engines return an error", - engine: newMultiPdfEngines( - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + readMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return nil, errors.New("foo") @@ -279,25 +338,21 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { }, }, }, - nil, - ), + }, ctx: context.Background(), expectError: true, }, { scenario: "context expired", - engine: newMultiPdfEngines( - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + readMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return make(map[string]interface{}), nil }, }, }, - nil, - ), + }, ctx: func() context.Context { ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -330,27 +385,21 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { }{ { scenario: "nominal behavior", - engine: newMultiPdfEngines( - nil, - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + writeMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, }, }, - ), + }, ctx: context.Background(), }, { scenario: "at least one engine does not return an error", - engine: newMultiPdfEngines( - nil, - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + writeMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return errors.New("foo") @@ -362,16 +411,13 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { }, }, }, - ), + }, ctx: context.Background(), }, { scenario: "all engines return an error", - engine: newMultiPdfEngines( - nil, - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + writeMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return errors.New("foo") @@ -383,24 +429,21 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { }, }, }, - ), + }, ctx: context.Background(), expectError: true, }, { scenario: "context expired", - engine: newMultiPdfEngines( - nil, - nil, - nil, - []gotenberg.PdfEngine{ + engine: &multiPdfEngines{ + writeMedataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, }, }, - ), + }, ctx: func() context.Context { ctx, cancel := context.WithCancel(context.Background()) cancel() diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index 7bd000187..4f07f83ea 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -28,6 +28,7 @@ func init() { // enabled. type PdfEngines struct { mergeNames []string + splitNames []string convertNames []string readMetadataNames []string writeMedataNames []string @@ -42,6 +43,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor { FlagSet: func() *flag.FlagSet { fs := flag.NewFlagSet("pdfengines", flag.ExitOnError) fs.StringSlice("pdfengines-merge-engines", []string{"qpdf", "pdfcpu", "pdftk"}, "Set the PDF engines and their order for the merge feature - empty means all") + fs.StringSlice("pdfengines-split-engines", []string{"pdfcpu", "qpdf", "pdftk"}, "Set the PDF engines and their order for the split feature - empty means all") fs.StringSlice("pdfengines-convert-engines", []string{"libreoffice-pdfengine"}, "Set the PDF engines and their order for the convert feature - empty means all") fs.StringSlice("pdfengines-read-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the read metadata feature - empty means all") fs.StringSlice("pdfengines-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature - empty means all") @@ -64,6 +66,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor { func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { flags := ctx.ParsedFlags() mergeNames := flags.MustStringSlice("pdfengines-merge-engines") + splitNames := flags.MustStringSlice("pdfengines-split-engines") convertNames := flags.MustStringSlice("pdfengines-convert-engines") readMetadataNames := flags.MustStringSlice("pdfengines-read-metadata-engines") writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines") @@ -98,6 +101,11 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { mod.mergeNames = mergeNames } + mod.splitNames = defaultNames + if len(splitNames) > 0 { + mod.splitNames = splitNames + } + mod.convertNames = defaultNames if len(convertNames) > 0 { mod.convertNames = convertNames @@ -161,6 +169,7 @@ func (mod *PdfEngines) Validate() error { } findNonExistingEngines(mod.mergeNames) + findNonExistingEngines(mod.splitNames) findNonExistingEngines(mod.convertNames) findNonExistingEngines(mod.readMetadataNames) findNonExistingEngines(mod.writeMedataNames) @@ -177,6 +186,7 @@ func (mod *PdfEngines) Validate() error { func (mod *PdfEngines) SystemMessages() []string { return []string{ fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")), + fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")), @@ -201,6 +211,7 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) { return newMultiPdfEngines( engines(mod.mergeNames), + engines(mod.splitNames), engines(mod.convertNames), engines(mod.readMetadataNames), engines(mod.writeMedataNames), @@ -222,6 +233,7 @@ func (mod *PdfEngines) Routes() ([]api.Route, error) { return []api.Route{ mergeRoute(engine), + splitRoute(engine), convertRoute(engine), readMetadataRoute(engine), writeMetadataRoute(engine), diff --git a/pkg/modules/pdfengines/pdfengines_test.go b/pkg/modules/pdfengines/pdfengines_test.go index fe999432d..505a229f2 100644 --- a/pkg/modules/pdfengines/pdfengines_test.go +++ b/pkg/modules/pdfengines/pdfengines_test.go @@ -26,6 +26,7 @@ func TestPdfEngines_Provision(t *testing.T) { scenario string ctx *gotenberg.Context expectedMergePdfEngines []string + expectedSplitPdfEngines []string expectedConvertPdfEngines []string expectedReadMetadataPdfEngines []string expectedWriteMetadataPdfEngines []string @@ -66,6 +67,7 @@ func TestPdfEngines_Provision(t *testing.T) { ) }(), expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"}, + expectedSplitPdfEngines: []string{"pdfcpu", "qpdf", "pdftk"}, expectedConvertPdfEngines: []string{"libreoffice-pdfengine"}, expectedReadMetadataPdfEngines: []string{"exiftool"}, expectedWriteMetadataPdfEngines: []string{"exiftool"}, @@ -107,7 +109,7 @@ func TestPdfEngines_Provision(t *testing.T) { } fs := new(PdfEngines).Descriptor().FlagSet - err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"}) + err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"}) if err != nil { t.Fatalf("expected no error but got: %v", err) } @@ -125,6 +127,7 @@ func TestPdfEngines_Provision(t *testing.T) { }(), expectedMergePdfEngines: []string{"b"}, + expectedSplitPdfEngines: []string{"a"}, expectedConvertPdfEngines: []string{"b"}, expectedReadMetadataPdfEngines: []string{"a"}, expectedWriteMetadataPdfEngines: []string{"a"}, @@ -200,6 +203,12 @@ func TestPdfEngines_Provision(t *testing.T) { } } + for index, name := range mod.splitNames { + if name != tc.expectedSplitPdfEngines[index] { + t.Fatalf("expected split name at index %d to be %s, but got: %s", index, name, tc.expectedSplitPdfEngines[index]) + } + } + for index, name := range mod.convertNames { if name != tc.expectedConvertPdfEngines[index] { t.Fatalf("expected convert name at index %d to be %s, but got: %s", index, name, tc.expectedConvertPdfEngines[index]) @@ -303,17 +312,19 @@ func TestPdfEngines_Validate(t *testing.T) { func TestPdfEngines_SystemMessages(t *testing.T) { mod := new(PdfEngines) mod.mergeNames = []string{"foo", "bar"} + mod.splitNames = []string{"foo", "bar"} mod.convertNames = []string{"foo", "bar"} mod.readMetadataNames = []string{"foo", "bar"} mod.writeMedataNames = []string{"foo", "bar"} messages := mod.SystemMessages() - if len(messages) != 4 { + if len(messages) != 5 { t.Errorf("expected one and only one message, but got %d", len(messages)) } expect := []string{ fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")), + fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")), @@ -329,6 +340,7 @@ func TestPdfEngines_SystemMessages(t *testing.T) { func TestPdfEngines_PdfEngine(t *testing.T) { mod := PdfEngines{ mergeNames: []string{"foo", "bar"}, + splitNames: []string{"foo", "bar"}, convertNames: []string{"foo", "bar"}, readMetadataNames: []string{"foo", "bar"}, writeMedataNames: []string{"foo", "bar"}, @@ -370,7 +382,7 @@ func TestPdfEngines_Routes(t *testing.T) { }{ { scenario: "routes not disabled", - expectRoutes: 4, + expectRoutes: 5, disableRoutes: false, }, { diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index a0ddb756e..2a76274b7 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "path/filepath" + "strconv" + "strings" "github.com/labstack/echo/v4" @@ -13,6 +15,63 @@ import ( "github.com/gotenberg/gotenberg/v8/pkg/modules/api" ) +// FormDataPdfSplitMode creates a [gotenberg.SplitMode] from the form data. +func FormDataPdfSplitMode(form *api.FormData, mandatory bool) gotenberg.SplitMode { + var ( + mode string + span string + ) + + splitModeFunc := func(value string) error { + if value != "" && value != gotenberg.SplitModeIntervals && value != gotenberg.SplitModePages { + return fmt.Errorf("wrong value, expected either '%s' or '%s'", gotenberg.SplitModeIntervals, gotenberg.SplitModePages) + } + mode = value + return nil + } + + splitSpanFunc := func(value string) error { + value = strings.Join(strings.Fields(value), "") + + if mode == gotenberg.SplitModeIntervals { + intValue, err := strconv.Atoi(value) + if err != nil { + return err + } + if intValue < 1 { + return errors.New("value is inferior to 1") + } + } + + span = value + + return nil + } + + if mandatory { + form. + MandatoryCustom("splitMode", func(value string) error { + return splitModeFunc(value) + }). + MandatoryCustom("splitSpan", func(value string) error { + return splitSpanFunc(value) + }) + } else { + form. + Custom("splitMode", func(value string) error { + return splitModeFunc(value) + }). + Custom("splitSpan", func(value string) error { + return splitSpanFunc(value) + }) + } + + return gotenberg.SplitMode{ + Mode: mode, + Span: span, + } +} + // FormDataPdfFormats creates [gotenberg.PdfFormats] from the form data. // Fallback to default value if the considered key is not present. func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats { @@ -32,9 +91,10 @@ func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats { } // FormDataPdfMetadata creates metadata object from the form data. -func FormDataPdfMetadata(form *api.FormData) map[string]interface{} { +func FormDataPdfMetadata(form *api.FormData, mandatory bool) map[string]interface{} { var metadata map[string]interface{} - form.Custom("metadata", func(value string) error { + + metadataFunc := func(value string) error { if len(value) > 0 { err := json.Unmarshal([]byte(value), &metadata) if err != nil { @@ -42,7 +102,18 @@ func FormDataPdfMetadata(form *api.FormData) map[string]interface{} { } } return nil - }) + } + + if mandatory { + form.MandatoryCustom("metadata", func(value string) error { + return metadataFunc(value) + }) + } else { + form.Custom("metadata", func(value string) error { + return metadataFunc(value) + }) + } + return metadata } @@ -66,6 +137,52 @@ func MergeStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string return outputPath, nil } +// SplitPdfStub splits a list of PDF files based on [gotenberg.SplitMode]. +// It returns a list of output paths or the list of provided input paths if no +// split requested. +func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.SplitMode, inputPaths []string) ([]string, error) { + zeroValued := gotenberg.SplitMode{} + if mode == zeroValued { + return inputPaths, nil + } + + 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), ".", "_")) + if err != nil { + return nil, fmt.Errorf("create subdirectory from input path: %w", err) + } + + paths, err := engine.Split(ctx, ctx.Log(), mode, inputPath, outputDirPath) + if err != nil { + return nil, fmt.Errorf("split PDF '%s': %w", inputPath, err) + } + + if mode.Mode == gotenberg.SplitModePages { + return paths, nil + } + + // Keep the original filename. + for i, path := range paths { + newPath := fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, i, + ) + + err = ctx.Rename(path, newPath) + if err != nil { + return nil, fmt.Errorf("rename path: %w", err) + } + + outputPaths = append(outputPaths, newPath) + } + } + + return outputPaths, nil +} + // ConvertStub transforms a given PDF to the specified formats defined in // [gotenberg.PdfFormats]. If no format, it does nothing and returns the input // paths. @@ -116,7 +233,7 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { form := ctx.FormData() pdfFormats := FormDataPdfFormats(form) - metadata := FormDataPdfMetadata(form) + metadata := FormDataPdfMetadata(form, false) var inputPaths []string err := form. @@ -152,6 +269,65 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { } } +// splitRoute returns an [api.Route] which can extract pages from a PDF. +func splitRoute(engine gotenberg.PdfEngine) api.Route { + return api.Route{ + Method: http.MethodPost, + Path: "/forms/pdfengines/split", + IsMultipart: true, + Handler: func(c echo.Context) error { + ctx := c.Get("context").(*api.Context) + + form := ctx.FormData() + mode := FormDataPdfSplitMode(form, true) + pdfFormats := FormDataPdfFormats(form) + metadata := FormDataPdfMetadata(form, false) + + var inputPaths []string + err := form. + MandatoryPaths([]string{".pdf"}, &inputPaths). + Validate() + if err != nil { + return fmt.Errorf("validate form data: %w", err) + } + + outputPaths, err := SplitPdfStub(ctx, engine, mode, inputPaths) + if err != nil { + return fmt.Errorf("split PDFs: %w", err) + } + + convertOutputPaths, err := ConvertStub(ctx, engine, pdfFormats, outputPaths) + if err != nil { + return fmt.Errorf("convert PDFs: %w", err) + } + + err = WriteMetadataStub(ctx, engine, metadata, convertOutputPaths) + if err != nil { + return fmt.Errorf("write metadata: %w", err) + } + + zeroValuedSplitMode := gotenberg.SplitMode{} + zeroValuedPdfFormats := gotenberg.PdfFormats{} + if mode != zeroValuedSplitMode && pdfFormats != zeroValuedPdfFormats { + // Rename the files to keep the split naming. + for i, convertOutputPath := range convertOutputPaths { + err = ctx.Rename(convertOutputPath, outputPaths[i]) + if err != nil { + return fmt.Errorf("rename output path: %w", err) + } + } + } + + err = ctx.AddOutputPaths(outputPaths...) + if err != nil { + return fmt.Errorf("add output paths: %w", err) + } + + return nil + }, + } +} + // convertRoute returns an [api.Route] which can convert PDFs to a specific ODF // format. func convertRoute(engine gotenberg.PdfEngine) api.Route { @@ -258,25 +434,12 @@ func writeMetadataRoute(engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) - var ( - inputPaths []string - metadata map[string]interface{} - ) + form := ctx.FormData() + metadata := FormDataPdfMetadata(form, true) - err := ctx.FormData(). + var inputPaths []string + err := form. MandatoryPaths([]string{".pdf"}, &inputPaths). - MandatoryCustom("metadata", func(value string) error { - if len(value) > 0 { - err := json.Unmarshal([]byte(value), &metadata) - if err != nil { - return fmt.Errorf("unmarshal metadata: %w", err) - } - } - if len(metadata) == 0 { - return errors.New("no metadata") - } - return nil - }). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go index 94df1688d..6e0e5e3e6 100644 --- a/pkg/modules/pdfengines/routes_test.go +++ b/pkg/modules/pdfengines/routes_test.go @@ -3,13 +3,16 @@ package pdfengines import ( "context" "errors" + "fmt" "net/http" "net/http/httptest" + "os" "reflect" "slices" "strings" "testing" + "github.com/google/uuid" "github.com/labstack/echo/v4" "go.uber.org/zap" @@ -17,6 +20,156 @@ import ( "github.com/gotenberg/gotenberg/v8/pkg/modules/api" ) +func TestFormDataPdfSplitMode(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *api.ContextMock + mandatory bool + expectedSplitMode gotenberg.SplitMode + expectValidationError bool + }{ + { + scenario: "no custom form fields", + ctx: &api.ContextMock{Context: new(api.Context)}, + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{}, + expectValidationError: false, + }, + { + scenario: "no custom form fields (mandatory)", + ctx: &api.ContextMock{Context: new(api.Context)}, + mandatory: true, + expectedSplitMode: gotenberg.SplitMode{}, + expectValidationError: true, + }, + { + scenario: "invalid splitMode", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "foo", + }, + }) + return ctx + }(), + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{}, + expectValidationError: true, + }, + { + scenario: "invalid splitSpan (intervals)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "intervals", + }, + "splitSpan": { + "1-2", + }, + }) + return ctx + }(), + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals}, + expectValidationError: true, + }, + { + scenario: "splitSpan inferior to 1 (intervals)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "intervals", + }, + "splitSpan": { + "-1", + }, + }) + return ctx + }(), + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals}, + expectValidationError: true, + }, + { + scenario: "valid form fields (intervals)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "intervals", + }, + "splitSpan": { + "1", + }, + }) + return ctx + }(), + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + expectValidationError: false, + }, + { + scenario: "valid form fields (pages)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "pages", + }, + "splitSpan": { + "1-2", + }, + }) + return ctx + }(), + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + expectValidationError: false, + }, + { + scenario: "valid form fields (mandatory)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "intervals", + }, + "splitSpan": { + "1", + }, + }) + return ctx + }(), + mandatory: true, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + expectValidationError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.SetLogger(zap.NewNop()) + form := tc.ctx.Context.FormData() + actual := FormDataPdfSplitMode(form, tc.mandatory) + + if !reflect.DeepEqual(actual, tc.expectedSplitMode) { + t.Fatalf("expected %+v but got: %+v", tc.expectedSplitMode, actual) + } + + err := form.Validate() + + if tc.expectValidationError && err == nil { + t.Fatal("expected validation error but got none", err) + } + + if !tc.expectValidationError && err != nil { + t.Fatalf("expected no validation error but got: %v", err) + } + }) + } +} + func TestFormDataPdfFormats(t *testing.T) { for _, tc := range []struct { scenario string @@ -74,15 +227,24 @@ func TestFormDataPdfMetadata(t *testing.T) { for _, tc := range []struct { scenario string ctx *api.ContextMock + mandatory bool expectedMetadata map[string]interface{} expectValidationError bool }{ { scenario: "no metadata form field", ctx: &api.ContextMock{Context: new(api.Context)}, + mandatory: false, expectedMetadata: nil, expectValidationError: false, }, + { + scenario: "no metadata form field (mandatory)", + ctx: &api.ContextMock{Context: new(api.Context)}, + mandatory: true, + expectedMetadata: nil, + expectValidationError: true, + }, { scenario: "invalid metadata form field", ctx: func() *api.ContextMock { @@ -94,6 +256,7 @@ func TestFormDataPdfMetadata(t *testing.T) { }) return ctx }(), + mandatory: false, expectedMetadata: nil, expectValidationError: true, }, @@ -108,6 +271,7 @@ func TestFormDataPdfMetadata(t *testing.T) { }) return ctx }(), + mandatory: false, expectedMetadata: map[string]interface{}{ "foo": "bar", }, @@ -117,7 +281,7 @@ func TestFormDataPdfMetadata(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) { tc.ctx.SetLogger(zap.NewNop()) form := tc.ctx.Context.FormData() - actual := FormDataPdfMetadata(form) + actual := FormDataPdfMetadata(form, tc.mandatory) if !reflect.DeepEqual(actual, tc.expectedMetadata) { t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual) @@ -193,6 +357,128 @@ func TestMergeStub(t *testing.T) { } } +func TestSplitPdfStub(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *api.ContextMock + engine gotenberg.PdfEngine + mode gotenberg.SplitMode + expectError bool + }{ + { + scenario: "no split mode", + mode: gotenberg.SplitMode{}, + ctx: &api.ContextMock{Context: new(api.Context)}, + expectError: false, + }, + { + scenario: "cannot create subdirectory", + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return errors.New("cannot create subdirectory") + }}) + return ctx + }(), + expectError: true, + }, + { + scenario: "split error", + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") + }, + }, + expectError: true, + }, + { + scenario: "rename error", + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { + return errors.New("cannot rename") + }}) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + }, + expectError: true, + }, + { + scenario: "success (intervals)", + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { + return nil + }}) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + }, + expectError: false, + }, + { + scenario: "success (pages)", + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { + return nil + }}) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + }, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) + tc.ctx.SetDirPath(dirPath) + tc.ctx.SetLogger(zap.NewNop()) + + _, err := SplitPdfStub(tc.ctx.Context, tc.engine, tc.mode, []string{"my.pdf", "my2.pdf"}) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + }) + } +} + func TestConvertStub(t *testing.T) { for _, tc := range []struct { scenario string @@ -503,6 +789,287 @@ func TestMergeHandler(t *testing.T) { } } +func TestSplitHandler(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *api.ContextMock + engine gotenberg.PdfEngine + expectError bool + expectHttpError bool + expectHttpStatus int + expectOutputPathsCount int + expectOutputPaths []string + }{ + { + scenario: "missing at least one mandatory file", + ctx: &api.ContextMock{Context: new(api.Context)}, + expectError: true, + expectHttpError: true, + expectHttpStatus: http.StatusBadRequest, + expectOutputPathsCount: 0, + }, + { + scenario: "no split mode", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + return ctx + }(), + expectError: true, + expectHttpError: true, + expectHttpStatus: http.StatusBadRequest, + expectOutputPathsCount: 0, + }, + { + scenario: "error from PDF engine (split)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return nil, errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "error from PDF engine (convert)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + "pdfua": { + "true", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "error from PDF engine (write metadata)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "cannot add output paths", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + }) + ctx.SetCancelled(true) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "success (intervals)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + "pdfua": { + "true", + }, + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{"file_split_1.pdf", "file_split_2.pdf"}, nil + }, + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return nil + }, + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 2, + expectOutputPaths: []string{"/file/file_0.pdf", "/file/file_1.pdf"}, + }, + { + scenario: "success (pages)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModePages, + }, + "splitSpan": { + "1-2", + }, + "pdfua": { + "true", + }, + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{"/file/file.pdf"}, nil + }, + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return nil + }, + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 1, + expectOutputPaths: []string{"/file/file.pdf"}, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.SetLogger(zap.NewNop()) + tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { + return nil + }}) + tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { + return nil + }}) + c := echo.New().NewContext(nil, nil) + c.Set("context", tc.ctx.Context) + + err := splitRoute(tc.engine).Handler(c) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + var httpErr api.HttpError + isHttpError := errors.As(err, &httpErr) + + if tc.expectHttpError && !isHttpError { + t.Errorf("expected an HTTP error but got: %v", err) + } + + if !tc.expectHttpError && isHttpError { + t.Errorf("expected no HTTP error but got one: %v", httpErr) + } + + if err != nil && tc.expectHttpError && isHttpError { + status, _ := httpErr.HttpError() + if status != tc.expectHttpStatus { + t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) + } + } + + if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { + t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) + } + + for _, path := range tc.expectOutputPaths { + if !slices.Contains(tc.ctx.OutputPaths(), path) { + t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) + } + } + }) + } +} + func TestConvertHandler(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/pdftk/doc.go b/pkg/modules/pdftk/doc.go index 3a01ae417..c65403f72 100644 --- a/pkg/modules/pdftk/doc.go +++ b/pkg/modules/pdftk/doc.go @@ -2,6 +2,7 @@ // interface using the PDFtk command-line tool. This package allows for: // // 1. The merging of PDF files. +// 2. The splitting of PDF files. // // The path to the PDFtk binary must be specified using the PDFTK_BIN_PATH // environment variable. diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index 9846ee9df..d8870a26d 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "go.uber.org/zap" @@ -51,6 +52,31 @@ func (engine *PdfTk) Validate() error { return nil } +// Split splits a given PDF file. +func (engine *PdfTk) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + var args []string + outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath)) + + switch mode.Mode { + case gotenberg.SplitModePages: + args = append(args, inputPath, "cat", mode.Span, "output", outputPath) + default: + return nil, fmt.Errorf("split PDFs using mode '%s' with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) + } + + cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) + if err != nil { + return nil, fmt.Errorf("create command: %w", err) + } + + _, err = cmd.Exec() + if err != nil { + return nil, fmt.Errorf("split PDFs with PDFtk: %w", err) + } + + return []string{outputPath}, nil +} + // Merge combines multiple PDFs into a single PDF. func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { var args []string diff --git a/pkg/modules/pdftk/pdftk_test.go b/pkg/modules/pdftk/pdftk_test.go index c7b864eca..d5dcd573e 100644 --- a/pkg/modules/pdftk/pdftk_test.go +++ b/pkg/modules/pdftk/pdftk_test.go @@ -116,7 +116,7 @@ func TestPdfTk_Merge(t *testing.T) { t.Fatalf("expected error but got: %v", err) } - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) outputDir, err := fs.MkdirAll() if err != nil { t.Fatalf("expected error but got: %v", err) @@ -142,6 +142,88 @@ func TestPdfTk_Merge(t *testing.T) { } } +func TestPdfCpu_Split(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx context.Context + mode gotenberg.SplitMode + inputPath string + expectError bool + expectedError error + expectOutputPathsCount int + expectOutputPaths []string + }{ + { + scenario: "ErrPdfSplitModeNotSupported", + expectError: true, + expectedError: gotenberg.ErrPdfSplitModeNotSupported, + expectOutputPathsCount: 0, + }, + { + scenario: "invalid context", + ctx: nil, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + expectError: true, + expectOutputPathsCount: 0, + }, + { + scenario: "invalid input path", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + inputPath: "", + expectError: true, + expectOutputPathsCount: 0, + }, + { + scenario: "success (pages)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + expectError: false, + expectOutputPathsCount: 1, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + engine := new(PdfTk) + err := engine.Provision(nil) + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) + outputDir, err := fs.MkdirAll() + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + defer func() { + err = os.RemoveAll(fs.WorkingDirPath()) + if err != nil { + t.Fatalf("expected no error while cleaning up but got: %v", err) + } + }() + + outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir) + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + + if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { + t.Fatalf("expected error %v but got: %v", tc.expectedError, err) + } + + if tc.expectOutputPathsCount != len(outputPaths) { + t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths)) + } + }) + } +} + func TestPdfTk_Convert(t *testing.T) { engine := new(PdfTk) err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") diff --git a/pkg/modules/qpdf/doc.go b/pkg/modules/qpdf/doc.go index 31f61b361..f0d54a548 100644 --- a/pkg/modules/qpdf/doc.go +++ b/pkg/modules/qpdf/doc.go @@ -2,6 +2,7 @@ // interface using the QPDF command-line tool. This package allows for: // // 1. The merging of PDF files. +// 2. The splitting of PDF files. // // The path to the QPDF binary must be specified using the QPDK_BIN_PATH // environment variable. diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 57698281d..2c010d2ec 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "go.uber.org/zap" @@ -45,12 +46,37 @@ func (engine *QPdf) Provision(ctx *gotenberg.Context) error { func (engine *QPdf) Validate() error { _, err := os.Stat(engine.binPath) if os.IsNotExist(err) { - return fmt.Errorf("QPdf binary path does not exist: %w", err) + return fmt.Errorf("QPDF binary path does not exist: %w", err) } return nil } +// Split splits a given PDF file. +func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + var args []string + outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath)) + + switch mode.Mode { + case gotenberg.SplitModePages: + args = append(args, inputPath, "--pages", ".", mode.Span, "--", outputPath) + default: + return nil, fmt.Errorf("split PDFs using mode '%s' with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) + } + + cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) + if err != nil { + return nil, fmt.Errorf("create command: %w", err) + } + + _, err = cmd.Exec() + if err != nil { + return nil, fmt.Errorf("split PDFs with QPDF: %w", err) + } + + return []string{outputPath}, nil +} + // Merge combines multiple PDFs into a single PDF. func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { var args []string diff --git a/pkg/modules/qpdf/qpdf_test.go b/pkg/modules/qpdf/qpdf_test.go index a966928d0..b32976cbf 100644 --- a/pkg/modules/qpdf/qpdf_test.go +++ b/pkg/modules/qpdf/qpdf_test.go @@ -116,7 +116,7 @@ func TestQPdf_Merge(t *testing.T) { t.Fatalf("expected error but got: %v", err) } - fs := gotenberg.NewFileSystem() + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) outputDir, err := fs.MkdirAll() if err != nil { t.Fatalf("expected error but got: %v", err) @@ -142,6 +142,88 @@ func TestQPdf_Merge(t *testing.T) { } } +func TestQPdf_Split(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx context.Context + mode gotenberg.SplitMode + inputPath string + expectError bool + expectedError error + expectOutputPathsCount int + expectOutputPaths []string + }{ + { + scenario: "ErrPdfSplitModeNotSupported", + expectError: true, + expectedError: gotenberg.ErrPdfSplitModeNotSupported, + expectOutputPathsCount: 0, + }, + { + scenario: "invalid context", + ctx: nil, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + expectError: true, + expectOutputPathsCount: 0, + }, + { + scenario: "invalid input path", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + inputPath: "", + expectError: true, + expectOutputPathsCount: 0, + }, + { + scenario: "success (pages)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + expectError: false, + expectOutputPathsCount: 1, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + engine := new(QPdf) + err := engine.Provision(nil) + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) + outputDir, err := fs.MkdirAll() + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + defer func() { + err = os.RemoveAll(fs.WorkingDirPath()) + if err != nil { + t.Fatalf("expected no error while cleaning up but got: %v", err) + } + }() + + outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir) + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + + if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { + t.Fatalf("expected error %v but got: %v", tc.expectedError, err) + } + + if tc.expectOutputPathsCount != len(outputPaths) { + t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths)) + } + }) + } +} + func TestQPdf_Convert(t *testing.T) { engine := new(QPdf) err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") From 51a913a5e4032d7ed89e62fc64352a38cbbc49f0 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 20 Dec 2024 15:59:51 +0100 Subject: [PATCH 002/254] chore(deps): update Go depencies --- go.mod | 25 ++++++++++++------------- go.sum | 54 ++++++++++++++++++++++++++---------------------------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 02b01da49..4edbc38b4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20241110205750-a72e6703cd9b + github.com/chromedp/cdproto v0.0.0-20241208230723-d1c7de7e5dd2 github.com/chromedp/chromedp v0.11.2 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 @@ -14,25 +14,25 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/labstack/echo/v4 v4.12.0 + github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/mattn/go-isatty v0.0.20 // indirect github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.27 github.com/nwaples/rardecode v1.1.3 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/prometheus/client_golang v1.20.5 github.com/russross/blackfriday/v2 v2.1.0 github.com/spf13/pflag v1.0.5 github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/net v0.31.0 - golang.org/x/sync v0.9.0 - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.26.0 - golang.org/x/text v0.20.0 + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 + golang.org/x/text v0.21.0 ) require github.com/dlclark/regexp2 v1.11.4 @@ -46,18 +46,17 @@ require ( github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/gorilla/css v1.0.1 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect golang.org/x/time v0.8.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.0 // indirect ) diff --git a/go.sum b/go.sum index 6f328e5f4..cc6a4a12e 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20241110205750-a72e6703cd9b h1:md1Gk5jkNE91SZxFDCMHmKqX0/GsEr1/VTejht0sCbY= -github.com/chromedp/cdproto v0.0.0-20241110205750-a72e6703cd9b/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= +github.com/chromedp/cdproto v0.0.0-20241208230723-d1c7de7e5dd2 h1:fJob5N/Eprtd427U84kFpQhAHIEqJYuDzveaL6T4Xsk= +github.com/chromedp/cdproto v0.0.0-20241208230723-d1c7de7e5dd2/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0= github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -33,8 +33,6 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -63,14 +61,14 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= -github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -88,16 +86,16 @@ github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWk github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= -github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -108,8 +106,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= @@ -128,24 +126,24 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 8bc29ad92deef5207bf3c78e7c5d2455f21e58fe Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 21 Dec 2024 12:14:23 +0100 Subject: [PATCH 003/254] feat(split): add splitUnify form field --- pkg/gotenberg/pdfengine.go | 4 ++ pkg/modules/pdfcpu/pdfcpu.go | 8 +++- pkg/modules/pdfcpu/pdfcpu_test.go | 8 ++++ pkg/modules/pdfengines/routes.go | 43 ++++++++++++----- pkg/modules/pdfengines/routes_test.go | 69 +++++++++++---------------- pkg/modules/pdftk/pdftk.go | 3 ++ pkg/modules/pdftk/pdftk_test.go | 16 +++++-- pkg/modules/qpdf/qpdf.go | 3 ++ pkg/modules/qpdf/qpdf_test.go | 16 +++++-- 9 files changed, 106 insertions(+), 64 deletions(-) diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index bc74f09f2..788c07c7a 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -43,6 +43,10 @@ type SplitMode struct { // Span is either the intervals or the page ranges to extract, depending on // the selected mode. Span string + + // Unify specifies whether to put extracted pages into a single file or as + // many files as there are page ranges. Only works with "pages" mode. + Unify bool } const ( diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index b59573c1e..29803cb38 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -79,8 +79,12 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenb case gotenberg.SplitModeIntervals: args = append(args, "split", "-mode", "span", inputPath, outputDirPath, mode.Span) case gotenberg.SplitModePages: - outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath)) - args = append(args, "trim", "-pages", mode.Span, inputPath, outputPath) + if mode.Unify { + outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath)) + args = append(args, "trim", "-pages", mode.Span, inputPath, outputPath) + break + } + args = append(args, "extract", "-mode", "page", "-pages", mode.Span, inputPath, outputDirPath) default: return nil, fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) } diff --git a/pkg/modules/pdfcpu/pdfcpu_test.go b/pkg/modules/pdfcpu/pdfcpu_test.go index e962fc698..e996aed8b 100644 --- a/pkg/modules/pdfcpu/pdfcpu_test.go +++ b/pkg/modules/pdfcpu/pdfcpu_test.go @@ -189,6 +189,14 @@ func TestPdfCpu_Split(t *testing.T) { expectError: false, expectOutputPathsCount: 1, }, + { + scenario: "success (pages & unify)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, + inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + expectError: false, + expectOutputPathsCount: 1, + }, } { t.Run(tc.scenario, func(t *testing.T) { engine := new(PdfCpu) diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index 2a76274b7..d164bc77f 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -18,8 +18,9 @@ import ( // FormDataPdfSplitMode creates a [gotenberg.SplitMode] from the form data. func FormDataPdfSplitMode(form *api.FormData, mandatory bool) gotenberg.SplitMode { var ( - mode string - span string + mode string + span string + unify bool ) splitModeFunc := func(value string) error { @@ -66,9 +67,19 @@ func FormDataPdfSplitMode(form *api.FormData, mandatory bool) gotenberg.SplitMod }) } + form. + Bool("splitUnify", &unify, false). + Custom("splitUnify", func(value string) error { + if value != "" && unify && mode != gotenberg.SplitModePages { + return fmt.Errorf("unify is not available for split mode '%s'", mode) + } + return nil + }) + return gotenberg.SplitMode{ - Mode: mode, - Span: span, + Mode: mode, + Span: span, + Unify: unify, } } @@ -160,16 +171,20 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S return nil, fmt.Errorf("split PDF '%s': %w", inputPath, err) } - if mode.Mode == gotenberg.SplitModePages { - return paths, nil - } - // Keep the original filename. for i, path := range paths { - newPath := fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, i, - ) + var newPath string + if mode.Unify && mode.Mode == gotenberg.SplitModePages { + newPath = fmt.Sprintf( + "%s/%s.pdf", + outputDirPath, filenameNoExt, + ) + } else { + newPath = fmt.Sprintf( + "%s/%s_%d.pdf", + outputDirPath, filenameNoExt, i, + ) + } err = ctx.Rename(path, newPath) if err != nil { @@ -177,6 +192,10 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S } outputPaths = append(outputPaths, newPath) + + if mode.Unify && mode.Mode == gotenberg.SplitModePages { + break + } } } diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go index 6e0e5e3e6..a0b004fb4 100644 --- a/pkg/modules/pdfengines/routes_test.go +++ b/pkg/modules/pdfengines/routes_test.go @@ -93,6 +93,27 @@ func TestFormDataPdfSplitMode(t *testing.T) { expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals}, expectValidationError: true, }, + { + scenario: "invalid splitUnify (intervals)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "splitMode": { + "intervals", + }, + "splitSpan": { + "1", + }, + "splitUnify": { + "true", + }, + }) + return ctx + }(), + mandatory: false, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1", Unify: true}, + expectValidationError: true, + }, { scenario: "valid form fields (intervals)", ctx: func() *api.ContextMock { @@ -122,11 +143,14 @@ func TestFormDataPdfSplitMode(t *testing.T) { "splitSpan": { "1-2", }, + "splitUnify": { + "true", + }, }) return ctx }(), mandatory: false, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, expectValidationError: false, }, { @@ -442,7 +466,7 @@ func TestSplitPdfStub(t *testing.T) { }, { scenario: "success (pages)", - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { @@ -940,7 +964,7 @@ func TestSplitHandler(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "success (intervals)", + scenario: "success", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -978,45 +1002,6 @@ func TestSplitHandler(t *testing.T) { expectOutputPathsCount: 2, expectOutputPaths: []string{"/file/file_0.pdf", "/file/file_1.pdf"}, }, - { - scenario: "success (pages)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModePages, - }, - "splitSpan": { - "1-2", - }, - "pdfua": { - "true", - }, - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{"/file/file.pdf"}, nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - expectOutputPaths: []string{"/file/file.pdf"}, - }, } { t.Run(tc.scenario, func(t *testing.T) { tc.ctx.SetLogger(zap.NewNop()) diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index d8870a26d..c3f63d173 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -59,6 +59,9 @@ func (engine *PdfTk) Split(ctx context.Context, logger *zap.Logger, mode gotenbe switch mode.Mode { case gotenberg.SplitModePages: + if !mode.Unify { + return nil, fmt.Errorf("split PDFs using mode '%s' without unify with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) + } args = append(args, inputPath, "cat", mode.Span, "output", outputPath) default: return nil, fmt.Errorf("split PDFs using mode '%s' with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) diff --git a/pkg/modules/pdftk/pdftk_test.go b/pkg/modules/pdftk/pdftk_test.go index d5dcd573e..73311f725 100644 --- a/pkg/modules/pdftk/pdftk_test.go +++ b/pkg/modules/pdftk/pdftk_test.go @@ -159,25 +159,33 @@ func TestPdfCpu_Split(t *testing.T) { expectedError: gotenberg.ErrPdfSplitModeNotSupported, expectOutputPathsCount: 0, }, + { + scenario: "ErrPdfSplitModeNotSupported (no unify with pages)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1", Unify: false}, + expectError: true, + expectedError: gotenberg.ErrPdfSplitModeNotSupported, + expectOutputPathsCount: 0, + }, { scenario: "invalid context", ctx: nil, - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, expectError: true, expectOutputPathsCount: 0, }, { scenario: "invalid input path", ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, inputPath: "", expectError: true, expectOutputPathsCount: 0, }, { - scenario: "success (pages)", + scenario: "success (pages & unify)", ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", expectError: false, expectOutputPathsCount: 1, diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 2c010d2ec..34785adef 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -59,6 +59,9 @@ func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenber switch mode.Mode { case gotenberg.SplitModePages: + if !mode.Unify { + return nil, fmt.Errorf("split PDFs using mode '%s' without unify with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) + } args = append(args, inputPath, "--pages", ".", mode.Span, "--", outputPath) default: return nil, fmt.Errorf("split PDFs using mode '%s' with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) diff --git a/pkg/modules/qpdf/qpdf_test.go b/pkg/modules/qpdf/qpdf_test.go index b32976cbf..9c79721b1 100644 --- a/pkg/modules/qpdf/qpdf_test.go +++ b/pkg/modules/qpdf/qpdf_test.go @@ -159,25 +159,33 @@ func TestQPdf_Split(t *testing.T) { expectedError: gotenberg.ErrPdfSplitModeNotSupported, expectOutputPathsCount: 0, }, + { + scenario: "ErrPdfSplitModeNotSupported (no unify with pages)", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1", Unify: false}, + expectError: true, + expectedError: gotenberg.ErrPdfSplitModeNotSupported, + expectOutputPathsCount: 0, + }, { scenario: "invalid context", ctx: nil, - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, expectError: true, expectOutputPathsCount: 0, }, { scenario: "invalid input path", ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, inputPath: "", expectError: true, expectOutputPathsCount: 0, }, { - scenario: "success (pages)", + scenario: "success (pages & unify)", ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"}, + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", expectError: false, expectOutputPathsCount: 1, From 16807bd57fcc7235c0b7e65896de17a2b1d90df3 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 23 Dec 2024 10:04:24 +0100 Subject: [PATCH 004/254] fix(split): wrong output paths when converting to PDF/A & PDF/UA --- pkg/modules/chromium/routes.go | 2 ++ pkg/modules/libreoffice/routes.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index ee330a26a..2c5791719 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -661,6 +661,8 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url return fmt.Errorf("rename output path: %w", err) } } + } else { + outputPaths = convertOutputPaths } err = ctx.AddOutputPaths(outputPaths...) diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index 86165833b..b6786189b 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -251,6 +251,8 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap return fmt.Errorf("rename output path: %w", err) } } + } else { + outputPaths = convertOutputPaths } } From 910eb9b770fad7a7aac74add8b2573b880a10961 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 25 Dec 2024 17:04:25 +0100 Subject: [PATCH 005/254] feat(state): improve clean up when LibreOffice and Chromium are restarted --- go.mod | 16 ++++++-- go.sum | 28 +++++++++++-- pkg/gotenberg/gc.go | 7 ++-- pkg/gotenberg/gc_test.go | 3 +- pkg/modules/chromium/browser.go | 48 ++++++++++++++++++++-- pkg/modules/libreoffice/api/libreoffice.go | 11 ++--- 6 files changed, 93 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 4edbc38b4..582f6d7fb 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20241208230723-d1c7de7e5dd2 + github.com/chromedp/cdproto v0.0.0-20241222144035-c16d098c0fb6 github.com/chromedp/chromedp v0.11.2 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 @@ -35,7 +35,10 @@ require ( golang.org/x/text v0.21.0 ) -require github.com/dlclark/regexp2 v1.11.4 +require ( + github.com/dlclark/regexp2 v1.11.4 + github.com/shirou/gopsutil/v4 v4.24.11 +) require ( github.com/aymerick/douceur v0.2.0 // indirect @@ -43,20 +46,27 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/ebitengine/purego v0.8.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/time v0.8.0 // indirect - google.golang.org/protobuf v1.36.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect ) diff --git a/go.sum b/go.sum index cc6a4a12e..209cf1a7a 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20241208230723-d1c7de7e5dd2 h1:fJob5N/Eprtd427U84kFpQhAHIEqJYuDzveaL6T4Xsk= -github.com/chromedp/cdproto v0.0.0-20241208230723-d1c7de7e5dd2/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= +github.com/chromedp/cdproto v0.0.0-20241222144035-c16d098c0fb6 h1:dAUcp/W5RpJSZW/HksEHfAAoMBIvSFFIwslAFEte+6g= +github.com/chromedp/cdproto v0.0.0-20241222144035-c16d098c0fb6/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0= github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -25,8 +25,13 @@ github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= +github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -67,6 +72,8 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -90,6 +97,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -100,6 +109,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil/v4 v4.24.11 h1:WaU9xqGFKvFfsUv94SXcUPD7rCkU0vr/asVdQOBZNj8= +github.com/shirou/gopsutil/v4 v4.24.11/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -108,6 +119,10 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= +github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= @@ -120,6 +135,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -132,7 +149,10 @@ golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -143,7 +163,7 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= -google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/gotenberg/gc.go b/pkg/gotenberg/gc.go index 3de80a4cc..2f53cc385 100644 --- a/pkg/gotenberg/gc.go +++ b/pkg/gotenberg/gc.go @@ -5,13 +5,14 @@ import ( "os" "path/filepath" "strings" + "time" "go.uber.org/zap" ) // GarbageCollect scans the root path and deletes files or directories with -// names containing specific substrings. -func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string) error { +// names containing specific substrings and before a given experiation time. +func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string, expirationTime time.Time) error { logger = logger.Named("gc") // To make sure that the next Walk method stays on @@ -36,7 +37,7 @@ func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string) } for _, substr := range includeSubstr { - if strings.Contains(info.Name(), substr) || path == substr { + if (strings.Contains(info.Name(), substr) || path == substr) && info.ModTime().Before(expirationTime) { err := os.RemoveAll(path) if err != nil { return fmt.Errorf("garbage collect '%s': %w", path, err) diff --git a/pkg/gotenberg/gc_test.go b/pkg/gotenberg/gc_test.go index 4dd58e4fb..0ed089a55 100644 --- a/pkg/gotenberg/gc_test.go +++ b/pkg/gotenberg/gc_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "testing" + "time" "github.com/google/uuid" "go.uber.org/zap" @@ -64,7 +65,7 @@ func TestGarbageCollect(t *testing.T) { } }() - err := GarbageCollect(zap.NewNop(), tc.rootPath, tc.includeSubstr) + err := GarbageCollect(zap.NewNop(), tc.rootPath, tc.includeSubstr, time.Now()) if !tc.expectError && err != nil { t.Fatalf("expected no error but got: %v", err) diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index 1da24d877..b1fd38c75 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -15,6 +15,7 @@ import ( "github.com/chromedp/cdproto/runtime" "github.com/chromedp/chromedp" "github.com/dlclark/regexp2" + "github.com/shirou/gopsutil/v4/process" "go.uber.org/zap" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" @@ -162,21 +163,60 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error { // Always remove the user profile directory created by Chromium. copyUserProfileDirPath := b.userProfileDirPath - defer func(userProfileDirPath string) { + expirationTime := time.Now() + defer func(userProfileDirPath string, expirationTime time.Time) { + // See: + // https://github.com/SeleniumHQ/docker-selenium/blob/7216d060d86872afe853ccda62db0dfab5118dc7/NodeChrome/chrome-cleanup.sh + // https://github.com/SeleniumHQ/docker-selenium/blob/7216d060d86872afe853ccda62db0dfab5118dc7/NodeChromium/chrome-cleanup.sh go func() { + // Clean up stuck processes. + ps, err := process.Processes() + if err != nil { + logger.Error(fmt.Sprintf("list processes: %v", err)) + } else { + for _, p := range ps { + func() { + cmdline, err := p.Cmdline() + if err != nil { + return + } + + if !strings.Contains(cmdline, "chromium/chromium") && !strings.Contains(cmdline, "chrome/chrome") { + return + } + + killCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + err = p.KillWithContext(killCtx) + if err != nil { + logger.Error(fmt.Sprintf("kill process: %v", err)) + } else { + logger.Info(fmt.Sprintf("Chromium process %d killed", p.Pid)) + } + }() + } + } + // FIXME: Chromium seems to recreate the user profile directory // right after its deletion if we do not wait a certain amount // of time before deleting it. <-time.After(10 * time.Second) - err := os.RemoveAll(userProfileDirPath) + err = os.RemoveAll(userProfileDirPath) if err != nil { logger.Error(fmt.Sprintf("remove Chromium's user profile directory: %s", err)) + } else { + logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath)) } - logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath)) + // Also remove Chromium specific files in the temporary directory. + err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{".org.chromium.Chromium", ".com.google.Chrome"}, expirationTime) + if err != nil { + logger.Error(err.Error()) + } }() - }(copyUserProfileDirPath) + }(copyUserProfileDirPath, expirationTime) b.ctxMu.Lock() defer b.ctxMu.Unlock() diff --git a/pkg/modules/libreoffice/api/libreoffice.go b/pkg/modules/libreoffice/api/libreoffice.go index ee332ad48..652d0e05b 100644 --- a/pkg/modules/libreoffice/api/libreoffice.go +++ b/pkg/modules/libreoffice/api/libreoffice.go @@ -190,22 +190,23 @@ func (p *libreOfficeProcess) Stop(logger *zap.Logger) error { // Always remove the user profile directory created by LibreOffice. copyUserProfileDirPath := p.userProfileDirPath - defer func(userProfileDirPath string) { + expirationTime := time.Now() + defer func(userProfileDirPath string, expirationTime time.Time) { go func() { err := os.RemoveAll(userProfileDirPath) if err != nil { logger.Error(fmt.Sprintf("remove LibreOffice's user profile directory: %v", err)) + } else { + logger.Debug(fmt.Sprintf("'%s' LibreOffice's user profile directory removed", userProfileDirPath)) } - logger.Debug(fmt.Sprintf("'%s' LibreOffice's user profile directory removed", userProfileDirPath)) - // Also remove LibreOffice specific files in the temporary directory. - err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{"OSL_PIPE", ".tmp"}) + err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{"OSL_PIPE", ".tmp"}, expirationTime) if err != nil { logger.Error(err.Error()) } }() - }(copyUserProfileDirPath) + }(copyUserProfileDirPath, expirationTime) p.cfgMu.Lock() defer p.cfgMu.Unlock() From 1762596fced0545226c74535bef3785cb493f486 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 2 Jan 2025 17:18:53 +0100 Subject: [PATCH 006/254] chore(dep)s: update dependencies --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 582f6d7fb..cbb32756c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20241222144035-c16d098c0fb6 + github.com/chromedp/cdproto v0.0.0-20250101192427-60a0ca35cb84 github.com/chromedp/chromedp v0.11.2 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 @@ -37,7 +37,7 @@ require ( require ( github.com/dlclark/regexp2 v1.11.4 - github.com/shirou/gopsutil/v4 v4.24.11 + github.com/shirou/gopsutil/v4 v4.24.12 ) require ( diff --git a/go.sum b/go.sum index 209cf1a7a..ab8ef6064 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20241222144035-c16d098c0fb6 h1:dAUcp/W5RpJSZW/HksEHfAAoMBIvSFFIwslAFEte+6g= -github.com/chromedp/cdproto v0.0.0-20241222144035-c16d098c0fb6/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= +github.com/chromedp/cdproto v0.0.0-20250101192427-60a0ca35cb84 h1:NXWP4iSVz4BPGk7Z/fPXL0c44hiWbrbyhBY0LwKKZnY= +github.com/chromedp/cdproto v0.0.0-20250101192427-60a0ca35cb84/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0= github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -109,8 +109,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v4 v4.24.11 h1:WaU9xqGFKvFfsUv94SXcUPD7rCkU0vr/asVdQOBZNj8= -github.com/shirou/gopsutil/v4 v4.24.11/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= +github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= +github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 984cf77ee0bfc46c69b127c3d4f6877a5657502c Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 2 Jan 2025 17:22:13 +0100 Subject: [PATCH 007/254] fix(chromium): info log into debug log + slight improvement of the killing of stuck processes --- pkg/modules/chromium/browser.go | 57 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index b1fd38c75..d8a2d724f 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -168,36 +168,37 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error { // See: // https://github.com/SeleniumHQ/docker-selenium/blob/7216d060d86872afe853ccda62db0dfab5118dc7/NodeChrome/chrome-cleanup.sh // https://github.com/SeleniumHQ/docker-selenium/blob/7216d060d86872afe853ccda62db0dfab5118dc7/NodeChromium/chrome-cleanup.sh - go func() { - // Clean up stuck processes. - ps, err := process.Processes() - if err != nil { - logger.Error(fmt.Sprintf("list processes: %v", err)) - } else { - for _, p := range ps { - func() { - cmdline, err := p.Cmdline() - if err != nil { - return - } - - if !strings.Contains(cmdline, "chromium/chromium") && !strings.Contains(cmdline, "chrome/chrome") { - return - } - - killCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - err = p.KillWithContext(killCtx) - if err != nil { - logger.Error(fmt.Sprintf("kill process: %v", err)) - } else { - logger.Info(fmt.Sprintf("Chromium process %d killed", p.Pid)) - } - }() - } + + // Clean up stuck processes. + ps, err := process.Processes() + if err != nil { + logger.Error(fmt.Sprintf("list processes: %v", err)) + } else { + for _, p := range ps { + func() { + cmdline, err := p.Cmdline() + if err != nil { + return + } + + if !strings.Contains(cmdline, "chromium/chromium") && !strings.Contains(cmdline, "chrome/chrome") { + return + } + + killCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + err = p.KillWithContext(killCtx) + if err != nil { + logger.Error(fmt.Sprintf("kill process: %v", err)) + } else { + logger.Debug(fmt.Sprintf("Chromium process %d killed", p.Pid)) + } + }() } + } + go func() { // FIXME: Chromium seems to recreate the user profile directory // right after its deletion if we do not wait a certain amount // of time before deleting it. From 2f69af1a52720e8fffa7f34bdb9a795562659a88 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 6 Jan 2025 09:17:13 +0100 Subject: [PATCH 008/254] chore(LICENSE): remove year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0f9e9ccfa..5a6f068cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Julien Neuhart +Copyright (c) Julien Neuhart Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From bdb3f11ac706976e89ee757b89886a46f7c2aa6a Mon Sep 17 00:00:00 2001 From: Hugo Tacla <87522622+omni-htg@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:14:53 +0100 Subject: [PATCH 009/254] fix(typo): medata -> metadata (#1092) * Update pdfengines.go * Update pdfengines_test.go * Update multi.go * Update multi_test.go --- pkg/modules/pdfengines/multi.go | 24 +++++++++---------- pkg/modules/pdfengines/multi_test.go | 16 ++++++------- pkg/modules/pdfengines/pdfengines.go | 24 +++++++++---------- pkg/modules/pdfengines/pdfengines_test.go | 28 +++++++++++------------ 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index c6c9514d6..a78d6466d 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -12,11 +12,11 @@ import ( ) type multiPdfEngines struct { - mergeEngines []gotenberg.PdfEngine - splitEngines []gotenberg.PdfEngine - convertEngines []gotenberg.PdfEngine - readMedataEngines []gotenberg.PdfEngine - writeMedataEngines []gotenberg.PdfEngine + mergeEngines []gotenberg.PdfEngine + splitEngines []gotenberg.PdfEngine + convertEngines []gotenberg.PdfEngine + readMetadataEngines []gotenberg.PdfEngine + writeMetadataEngines []gotenberg.PdfEngine } func newMultiPdfEngines( @@ -24,14 +24,14 @@ func newMultiPdfEngines( splitEngines, convertEngines, readMetadataEngines, - writeMedataEngines []gotenberg.PdfEngine, + writeMetadataEngines []gotenberg.PdfEngine, ) *multiPdfEngines { return &multiPdfEngines{ mergeEngines: mergeEngines, splitEngines: splitEngines, convertEngines: convertEngines, - readMedataEngines: readMetadataEngines, - writeMedataEngines: writeMedataEngines, + readMetadataEngines: readMetadataEngines, + writeMetadataEngines: writeMetadataEngines, } } @@ -132,16 +132,16 @@ func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logg var err error var mu sync.Mutex // to safely append errors. - resultChan := make(chan readMetadataResult, len(multi.readMedataEngines)) + resultChan := make(chan readMetadataResult, len(multi.readMetadataEngines)) - for _, engine := range multi.readMedataEngines { + for _, engine := range multi.readMetadataEngines { go func(engine gotenberg.PdfEngine) { metadata, err := engine.ReadMetadata(ctx, logger, inputPath) resultChan <- readMetadataResult{metadata: metadata, err: err} }(engine) } - for range multi.readMedataEngines { + for range multi.readMetadataEngines { select { case result := <-resultChan: if result.err != nil { @@ -163,7 +163,7 @@ func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Log var err error errChan := make(chan error, 1) - for _, engine := range multi.writeMedataEngines { + for _, engine := range multi.writeMetadataEngines { go func(engine gotenberg.PdfEngine) { errChan <- engine.WriteMetadata(ctx, logger, metadata, inputPath) }(engine) diff --git a/pkg/modules/pdfengines/multi_test.go b/pkg/modules/pdfengines/multi_test.go index 6e5686c0c..7dddac05e 100644 --- a/pkg/modules/pdfengines/multi_test.go +++ b/pkg/modules/pdfengines/multi_test.go @@ -295,7 +295,7 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { { scenario: "nominal behavior", engine: &multiPdfEngines{ - readMedataEngines: []gotenberg.PdfEngine{ + readMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return make(map[string]interface{}), nil @@ -308,7 +308,7 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { { scenario: "at least one engine does not return an error", engine: &multiPdfEngines{ - readMedataEngines: []gotenberg.PdfEngine{ + readMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return nil, errors.New("foo") @@ -326,7 +326,7 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { { scenario: "all engines return an error", engine: &multiPdfEngines{ - readMedataEngines: []gotenberg.PdfEngine{ + readMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return nil, errors.New("foo") @@ -345,7 +345,7 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { { scenario: "context expired", engine: &multiPdfEngines{ - readMedataEngines: []gotenberg.PdfEngine{ + readMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { return make(map[string]interface{}), nil @@ -386,7 +386,7 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "nominal behavior", engine: &multiPdfEngines{ - writeMedataEngines: []gotenberg.PdfEngine{ + writeMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil @@ -399,7 +399,7 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "at least one engine does not return an error", engine: &multiPdfEngines{ - writeMedataEngines: []gotenberg.PdfEngine{ + writeMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return errors.New("foo") @@ -417,7 +417,7 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "all engines return an error", engine: &multiPdfEngines{ - writeMedataEngines: []gotenberg.PdfEngine{ + writeMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return errors.New("foo") @@ -436,7 +436,7 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "context expired", engine: &multiPdfEngines{ - writeMedataEngines: []gotenberg.PdfEngine{ + writeMetadataEngines: []gotenberg.PdfEngine{ &gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index 4f07f83ea..4536ff7b5 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -27,13 +27,13 @@ func init() { // the [api.Router] interface to expose relevant PDF processing routes if // enabled. type PdfEngines struct { - mergeNames []string - splitNames []string - convertNames []string - readMetadataNames []string - writeMedataNames []string - engines []gotenberg.PdfEngine - disableRoutes bool + mergeNames []string + splitNames []string + convertNames []string + readMetadataNames []string + writeMetadataNames []string + engines []gotenberg.PdfEngine + disableRoutes bool } // Descriptor returns a PdfEngines' module descriptor. @@ -116,9 +116,9 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { mod.readMetadataNames = readMetadataNames } - mod.writeMedataNames = defaultNames + mod.writeMetadataNames = defaultNames if len(writeMetadataNames) > 0 { - mod.writeMedataNames = writeMetadataNames + mod.writeMetadataNames = writeMetadataNames } return nil @@ -172,7 +172,7 @@ func (mod *PdfEngines) Validate() error { findNonExistingEngines(mod.splitNames) findNonExistingEngines(mod.convertNames) findNonExistingEngines(mod.readMetadataNames) - findNonExistingEngines(mod.writeMedataNames) + findNonExistingEngines(mod.writeMetadataNames) if len(nonExistingEngines) == 0 { return nil @@ -189,7 +189,7 @@ func (mod *PdfEngines) SystemMessages() []string { fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), - fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")), + fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")), } } @@ -214,7 +214,7 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) { engines(mod.splitNames), engines(mod.convertNames), engines(mod.readMetadataNames), - engines(mod.writeMedataNames), + engines(mod.writeMetadataNames), ), nil } diff --git a/pkg/modules/pdfengines/pdfengines_test.go b/pkg/modules/pdfengines/pdfengines_test.go index 505a229f2..8aeb042cd 100644 --- a/pkg/modules/pdfengines/pdfengines_test.go +++ b/pkg/modules/pdfengines/pdfengines_test.go @@ -193,7 +193,7 @@ func TestPdfEngines_Provision(t *testing.T) { t.Fatalf("expected %d read metadata names but got %d", len(tc.expectedReadMetadataPdfEngines), len(mod.readMetadataNames)) } - if len(tc.expectedWriteMetadataPdfEngines) != len(mod.writeMedataNames) { + if len(tc.expectedWriteMetadataPdfEngines) != len(mod.writeMetadataNames) { t.Fatalf("expected %d write metadata names but got %d", len(tc.expectedWriteMetadataPdfEngines), len(mod.writeMedataNames)) } @@ -221,7 +221,7 @@ func TestPdfEngines_Provision(t *testing.T) { } } - for index, name := range mod.writeMedataNames { + for index, name := range mod.writeMetadataNames { if name != tc.expectedWriteMetadataPdfEngines[index] { t.Fatalf("expected write metadat name at index %d to be %s, but got: %s", index, name, tc.expectedWriteMetadataPdfEngines[index]) } @@ -289,11 +289,11 @@ func TestPdfEngines_Validate(t *testing.T) { } { t.Run(tc.scenario, func(t *testing.T) { mod := PdfEngines{ - mergeNames: tc.names, - convertNames: tc.names, - readMetadataNames: tc.names, - writeMedataNames: tc.names, - engines: tc.engines, + mergeNames: tc.names, + convertNames: tc.names, + readMetadataNames: tc.names, + writeMetadataNames: tc.names, + engines: tc.engines, } err := mod.Validate() @@ -315,7 +315,7 @@ func TestPdfEngines_SystemMessages(t *testing.T) { mod.splitNames = []string{"foo", "bar"} mod.convertNames = []string{"foo", "bar"} mod.readMetadataNames = []string{"foo", "bar"} - mod.writeMedataNames = []string{"foo", "bar"} + mod.writeMetadataNames = []string{"foo", "bar"} messages := mod.SystemMessages() if len(messages) != 5 { @@ -327,7 +327,7 @@ func TestPdfEngines_SystemMessages(t *testing.T) { fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), - fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")), + fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")), } for i, message := range messages { @@ -339,11 +339,11 @@ func TestPdfEngines_SystemMessages(t *testing.T) { func TestPdfEngines_PdfEngine(t *testing.T) { mod := PdfEngines{ - mergeNames: []string{"foo", "bar"}, - splitNames: []string{"foo", "bar"}, - convertNames: []string{"foo", "bar"}, - readMetadataNames: []string{"foo", "bar"}, - writeMedataNames: []string{"foo", "bar"}, + mergeNames: []string{"foo", "bar"}, + splitNames: []string{"foo", "bar"}, + convertNames: []string{"foo", "bar"}, + readMetadataNames: []string{"foo", "bar"}, + writeMetadataNames: []string{"foo", "bar"}, engines: func() []gotenberg.PdfEngine { engine1 := &struct { gotenberg.ModuleMock From f72428056ae285fcfb6630d053c7d3bd59d5764b Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 8 Jan 2025 17:15:30 +0100 Subject: [PATCH 010/254] chore(code): fmt --- pkg/modules/pdfengines/multi.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index a78d6466d..a01515b52 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -27,9 +27,9 @@ func newMultiPdfEngines( writeMetadataEngines []gotenberg.PdfEngine, ) *multiPdfEngines { return &multiPdfEngines{ - mergeEngines: mergeEngines, - splitEngines: splitEngines, - convertEngines: convertEngines, + mergeEngines: mergeEngines, + splitEngines: splitEngines, + convertEngines: convertEngines, readMetadataEngines: readMetadataEngines, writeMetadataEngines: writeMetadataEngines, } From 1ca27fdba2933555480e06e4f68906b1e1c1b222 Mon Sep 17 00:00:00 2001 From: Hugo Tacla <87522622+omni-htg@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:24:43 +0100 Subject: [PATCH 011/254] fix(pdfengines): wrong variable name (#1093) --- pkg/modules/pdfengines/pdfengines_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/modules/pdfengines/pdfengines_test.go b/pkg/modules/pdfengines/pdfengines_test.go index 8aeb042cd..66546ebe4 100644 --- a/pkg/modules/pdfengines/pdfengines_test.go +++ b/pkg/modules/pdfengines/pdfengines_test.go @@ -194,7 +194,7 @@ func TestPdfEngines_Provision(t *testing.T) { } if len(tc.expectedWriteMetadataPdfEngines) != len(mod.writeMetadataNames) { - t.Fatalf("expected %d write metadata names but got %d", len(tc.expectedWriteMetadataPdfEngines), len(mod.writeMedataNames)) + t.Fatalf("expected %d write metadata names but got %d", len(tc.expectedWriteMetadataPdfEngines), len(mod.writeMetadataNames)) } for index, name := range mod.mergeNames { From e3a961623b2baa204bb558a1085f2cc7343a223a Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 10 Jan 2025 16:58:26 +0100 Subject: [PATCH 012/254] feat(logging): add new field log_type with access or application as values --- pkg/modules/api/middlewares.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/modules/api/middlewares.go b/pkg/modules/api/middlewares.go index 78bcb8542..f93545103 100644 --- a/pkg/modules/api/middlewares.go +++ b/pkg/modules/api/middlewares.go @@ -161,9 +161,12 @@ func loggerMiddleware(logger *zap.Logger, disableLoggingForPaths []string) echo. trace := c.Get("trace").(string) rootPath := c.Get("rootPath").(string) - // Create the request logger and add it to our locals. - reqLogger := logger.With(zap.String("trace", trace)) - c.Set("logger", reqLogger.Named(func() string { + // Create the application logger and add it to our locals. + appLogger := logger. + With(zap.String("log_type", "application")). + With(zap.String("trace", trace)) + + c.Set("logger", appLogger.Named(func() string { return strings.ReplaceAll( strings.ReplaceAll(c.Request().URL.Path, rootPath, ""), "/", @@ -177,6 +180,11 @@ func loggerMiddleware(logger *zap.Logger, disableLoggingForPaths []string) echo. c.Error(err) } + // Create the access logger. + accessLogger := logger. + With(zap.String("log_type", "access")). + With(zap.String("trace", trace)) + for _, path := range disableLoggingForPaths { URI := fmt.Sprintf("%s%s", rootPath, path) @@ -212,9 +220,9 @@ func loggerMiddleware(logger *zap.Logger, disableLoggingForPaths []string) echo. fields[11] = zap.Int64("bytes_out", c.Response().Size) if err != nil { - reqLogger.Error(err.Error(), fields...) + accessLogger.Error(err.Error(), fields...) } else { - reqLogger.Info("request handled", fields...) + accessLogger.Info("request handled", fields...) } return nil From e639a1dd73b3cc2e81844530b9821a56e807e93f Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 10 Jan 2025 16:59:01 +0100 Subject: [PATCH 013/254] chore(deps): update Go dependencies --- go.mod | 18 +++++++++--------- go.sum | 38 ++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index cbb32756c..161291263 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250101192427-60a0ca35cb84 + github.com/chromedp/cdproto v0.0.0-20250109193942-1ec2f6cf5d86 github.com/chromedp/chromedp v0.11.2 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 @@ -27,11 +27,11 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 golang.org/x/sync v0.10.0 - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 golang.org/x/text v0.21.0 ) @@ -46,7 +46,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect - github.com/ebitengine/purego v0.8.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect @@ -55,7 +55,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/mailru/easyjson v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect @@ -67,6 +67,6 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/time v0.8.0 // indirect - google.golang.org/protobuf v1.36.1 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/protobuf v1.36.2 // indirect ) diff --git a/go.sum b/go.sum index ab8ef6064..3e6c00516 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250101192427-60a0ca35cb84 h1:NXWP4iSVz4BPGk7Z/fPXL0c44hiWbrbyhBY0LwKKZnY= -github.com/chromedp/cdproto v0.0.0-20250101192427-60a0ca35cb84/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= +github.com/chromedp/cdproto v0.0.0-20250109193942-1ec2f6cf5d86 h1:FGN/TKeWgmhrgZVyq5crllFY0MlEHX16fUOd2oq6uzs= +github.com/chromedp/cdproto v0.0.0-20250109193942-1ec2f6cf5d86/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0= github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -25,8 +25,8 @@ github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= -github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -76,9 +76,8 @@ github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMD github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= @@ -143,27 +142,26 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 291a4d39fed9ddc9fbffeed1b6e274017b309345 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 10 Jan 2025 17:02:16 +0100 Subject: [PATCH 014/254] chore(deps): upgrade to golangci-lint v1.63.4 --- .github/workflows/continuous_integration.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 35b5df5ad..9d51ff004 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -24,7 +24,7 @@ jobs: - name: Run linters uses: golangci/golangci-lint-action@v6 with: - version: v1.61.0 + version: v1.63.4 tests: needs: diff --git a/Makefile b/Makefile index b8f0f10e4..6678a8b08 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ GOTENBERG_USER_UID=1001 NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. -GOLANGCI_LINT_VERSION=v1.61.0 # See https://github.com/golangci/golangci-lint/releases. +GOLANGCI_LINT_VERSION=v1.63.4 # See https://github.com/golangci/golangci-lint/releases. .PHONY: build build: ## Build the Gotenberg's Docker image From a6f4c5651cdbb2953ff44f3f3eb78f81526e5667 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 11 Jan 2025 11:08:19 +0100 Subject: [PATCH 015/254] feat(chromium): set the default value of the flag --chromium-restart-after to 10 --- Makefile | 2 +- pkg/modules/chromium/chromium.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6678a8b08..ed69207cb 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ API-DOWNLOAD-FROM-DENY-LIST= API-DOWNLOAD-FROM-FROM-MAX-RETRY=4 API-DISABLE-DOWNLOAD-FROM=false API_DISABLE_HEALTH_CHECK_LOGGING=false -CHROMIUM_RESTART_AFTER=0 +CHROMIUM_RESTART_AFTER=10 CHROMIUM_MAX_QUEUE_SIZE=0 CHROMIUM_AUTO_START=false CHROMIUM_START_TIMEOUT=20s diff --git a/pkg/modules/chromium/chromium.go b/pkg/modules/chromium/chromium.go index b31c5e707..7dfa8fa04 100644 --- a/pkg/modules/chromium/chromium.go +++ b/pkg/modules/chromium/chromium.go @@ -352,7 +352,7 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor { ID: "chromium", FlagSet: func() *flag.FlagSet { fs := flag.NewFlagSet("chromium", flag.ExitOnError) - fs.Int64("chromium-restart-after", 0, "Number of conversions after which Chromium will automatically restart. Set to 0 to disable this feature") + fs.Int64("chromium-restart-after", 10, "Number of conversions after which Chromium will automatically restart. Set to 0 to disable this feature") fs.Int64("chromium-max-queue-size", 0, "Maximum request queue size for Chromium. Set to 0 to disable this feature") fs.Bool("chromium-auto-start", false, "Automatically launch Chromium upon initialization if set to true; otherwise, Chromium will start at the time of the first conversion") fs.Duration("chromium-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for Chromium to start or restart") From 6dc7d70a1f9e26162ddb1e370cf393e35ea91ba1 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 11 Jan 2025 11:17:50 +0100 Subject: [PATCH 016/254] ci(continous integration): add concurrency to avoid concurrent runs --- .github/workflows/continuous_integration.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 9d51ff004..eded7994e 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -8,6 +8,10 @@ on: branches: - main +concurrency: + group: ${{ (github.event_name == 'pull_request' && github.event.pull_request.number) || 'main' }} + cancel-in-progress: true + jobs: lint: From 3e3b24f61147f4a91ca138874c8476e001b15634 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 11 Jan 2025 11:28:27 +0100 Subject: [PATCH 017/254] ci(workflows): add permissions --- .github/workflows/continuous_delivery.yml | 3 +++ .github/workflows/continuous_integration.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/continuous_delivery.yml b/.github/workflows/continuous_delivery.yml index b6b5e70d0..ca229fe0c 100644 --- a/.github/workflows/continuous_delivery.yml +++ b/.github/workflows/continuous_delivery.yml @@ -4,6 +4,9 @@ on: release: types: [ published ] +permissions: + contents: read + jobs: release: name: Release Docker image diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index eded7994e..51cd4eeb8 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -12,6 +12,9 @@ concurrency: group: ${{ (github.event_name == 'pull_request' && github.event.pull_request.number) || 'main' }} cancel-in-progress: true +permissions: + contents: write + jobs: lint: From 6478528ebca11b87b77974ec7a2867e890c86fb2 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:58:58 +1100 Subject: [PATCH 018/254] feat(api): add dummy root route pointing to docs (#1099) * feat(api): added dummy root route pointing to docs * test(api): root request test added * feat(api): added NoContent return for favicon from @gulien Co-authored-by: Julien Neuhart * test(api): added favicon * lint(api): fix format * refactor(api): use %s for favicon.ico for consistency with /health * test(api): added new recorder for each check * docs(api): consistent comment for favicon --------- Co-authored-by: Julien Neuhart --- pkg/modules/api/api.go | 16 ++++++++++++++++ pkg/modules/api/api_test.go | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index 7daa18925..7e4302d55 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -483,6 +483,22 @@ func (a *Api) Start() error { ) } + // Root route. + a.srv.GET( + a.rootPath, + func(c echo.Context) error { + return c.HTML(http.StatusOK, `Hey, this Gotenberg has no UI, it's an API. Head to the documentation to learn how to interact with it 🚀`) + }, + ) + + // Favicon route. + a.srv.GET( + fmt.Sprintf("%s%s", a.rootPath, "favicon.ico"), + func(c echo.Context) error { + return c.NoContent(http.StatusNoContent) + }, + ) + // Let's not forget the health check routes... checks := append(a.healthChecks, health.WithTimeout(a.timeout)) checker := health.NewChecker(checks...) diff --git a/pkg/modules/api/api_test.go b/pkg/modules/api/api_test.go index b32eace84..9a1231766 100644 --- a/pkg/modules/api/api_test.go +++ b/pkg/modules/api/api_test.go @@ -866,15 +866,31 @@ func TestApi_Start(t *testing.T) { return } - // health requests. + // root request. recorder := httptest.NewRecorder() + rootRequest := httptest.NewRequest(http.MethodGet, "/", nil) + mod.srv.ServeHTTP(recorder, rootRequest) + if recorder.Code != http.StatusOK { + t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) + } + // favicon request. + recorder = httptest.NewRecorder() + faviconRequest := httptest.NewRequest(http.MethodGet, "/favicon.ico", nil) + mod.srv.ServeHTTP(recorder, faviconRequest) + if recorder.Code != http.StatusNoContent { + t.Errorf("expected %d status code but got %d", http.StatusNoContent, recorder.Code) + } + + // health requests. + recorder = httptest.NewRecorder() healthGetRequest := httptest.NewRequest(http.MethodGet, "/health", nil) mod.srv.ServeHTTP(recorder, healthGetRequest) if recorder.Code != http.StatusOK { t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) } + recorder = httptest.NewRecorder() healthHeadRequest := httptest.NewRequest(http.MethodHead, "/health", nil) mod.srv.ServeHTTP(recorder, healthHeadRequest) if recorder.Code != http.StatusOK { @@ -882,6 +898,7 @@ func TestApi_Start(t *testing.T) { } // version request. + recorder = httptest.NewRecorder() versionRequest := httptest.NewRequest(http.MethodGet, "/version", nil) mod.srv.ServeHTTP(recorder, versionRequest) if recorder.Code != http.StatusOK { From 8e0cc7dd2f7c45b97b9ed2cc5edd0a760cb8698a Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 17 Jan 2025 10:00:08 +0100 Subject: [PATCH 019/254] fix(typo): root route HTML --- pkg/modules/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index 7e4302d55..4ec91d9f0 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -487,7 +487,7 @@ func (a *Api) Start() error { a.srv.GET( a.rootPath, func(c echo.Context) error { - return c.HTML(http.StatusOK, `Hey, this Gotenberg has no UI, it's an API. Head to the documentation to learn how to interact with it 🚀`) + return c.HTML(http.StatusOK, `Hey, Gotenberg has no UI, it's an API. Head to the documentation to learn how to interact with it 🚀`) }, ) From 19c91aff194accad9d45ea12be58209b674a0363 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 17 Jan 2025 14:08:01 +0100 Subject: [PATCH 020/254] feat(api): add root, favicon and version routes to basic auth middleware if enabled --- Makefile | 2 +- pkg/modules/api/api.go | 21 ++++++++++++++++----- pkg/modules/api/api_test.go | 3 +++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ed69207cb..de2e0dbdf 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ API_BIND_IP= API_START_TIMEOUT=30s API_TIMEOUT=30s API_BODY_LIMIT= -API_ROOT_PATH=/ +API_ROOT_PATH="/" API_TRACE_HEADER=Gotenberg-Trace API_ENABLE_BASIC_AUTH=false GOTENBERG_API_BASIC_AUTH_USERNAME= diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index 4ec91d9f0..e7aaf492c 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -456,14 +456,22 @@ func (a *Api) Start() error { hardTimeout := a.timeout + (time.Duration(5) * time.Second) + // Basic auth? + var securityMiddleware echo.MiddlewareFunc + if a.basicAuthUsername != "" { + securityMiddleware = basicAuthMiddleware(a.basicAuthUsername, a.basicAuthPassword) + } else { + securityMiddleware = func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + return next(c) + } + } + } + // Add the modules' routes and their specific middlewares. for _, route := range a.routes { var middlewares []echo.MiddlewareFunc - - // Basic auth? - if a.basicAuthUsername != "" { - middlewares = append(middlewares, basicAuthMiddleware(a.basicAuthUsername, a.basicAuthPassword)) - } + middlewares = append(middlewares, securityMiddleware) if route.IsMultipart { middlewares = append(middlewares, contextMiddleware(a.fs, a.timeout, a.bodyLimit, a.downloadFromCfg)) @@ -489,6 +497,7 @@ func (a *Api) Start() error { func(c echo.Context) error { return c.HTML(http.StatusOK, `Hey, Gotenberg has no UI, it's an API. Head to the documentation to learn how to interact with it 🚀`) }, + securityMiddleware, ) // Favicon route. @@ -497,6 +506,7 @@ func (a *Api) Start() error { func(c echo.Context) error { return c.NoContent(http.StatusNoContent) }, + securityMiddleware, ) // Let's not forget the health check routes... @@ -525,6 +535,7 @@ func (a *Api) Start() error { func(c echo.Context) error { return c.String(http.StatusOK, gotenberg.Version) }, + securityMiddleware, ) // Wait for all modules to be ready. diff --git a/pkg/modules/api/api_test.go b/pkg/modules/api/api_test.go index 9a1231766..cd5c3e3a2 100644 --- a/pkg/modules/api/api_test.go +++ b/pkg/modules/api/api_test.go @@ -869,6 +869,7 @@ func TestApi_Start(t *testing.T) { // root request. recorder := httptest.NewRecorder() rootRequest := httptest.NewRequest(http.MethodGet, "/", nil) + rootRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) mod.srv.ServeHTTP(recorder, rootRequest) if recorder.Code != http.StatusOK { t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) @@ -877,6 +878,7 @@ func TestApi_Start(t *testing.T) { // favicon request. recorder = httptest.NewRecorder() faviconRequest := httptest.NewRequest(http.MethodGet, "/favicon.ico", nil) + faviconRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) mod.srv.ServeHTTP(recorder, faviconRequest) if recorder.Code != http.StatusNoContent { t.Errorf("expected %d status code but got %d", http.StatusNoContent, recorder.Code) @@ -900,6 +902,7 @@ func TestApi_Start(t *testing.T) { // version request. recorder = httptest.NewRecorder() versionRequest := httptest.NewRequest(http.MethodGet, "/version", nil) + versionRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) mod.srv.ServeHTTP(recorder, versionRequest) if recorder.Code != http.StatusOK { t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) From ecb5d974c57e2495c75b390cb9ae9bd59e7933d5 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 17 Jan 2025 15:24:37 +0100 Subject: [PATCH 021/254] chore(Dockerfile): add apt-get upgrade -yqq --- build/Dockerfile | 7 +++++++ test/Dockerfile | 1 + 2 files changed, 8 insertions(+) diff --git a/build/Dockerfile b/build/Dockerfile index 38771906b..5ec44d214 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -82,6 +82,7 @@ RUN \ # Install system dependencies required for the next instructions or debugging. # Note: tini is a helper for reaping zombie processes. apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends curl gnupg tini python3 default-jre-headless &&\ # Cleanup. # Note: the Debian image does automatically a clean after each install thanks to a hook. @@ -96,6 +97,7 @@ RUN \ # https://help.accusoft.com/PrizmDoc/v12.1/HTML/Installing_Asian_Fonts_on_Ubuntu_and_Debian.html. curl -o ./ttf-mscorefonts-installer_3.8.1_all.deb http://httpredir.debian.org/debian/pool/contrib/m/msttcorefonts/ttf-mscorefonts-installer_3.8.1_all.deb &&\ apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \ ./ttf-mscorefonts-installer_3.8.1_all.deb \ culmus \ @@ -154,10 +156,12 @@ RUN \ curl https://dl.google.com/linux/linux_signing_key.pub | apt-key add - &&\ echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | tee /etc/apt/sources.list.d/google-chrome.list &&\ apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends --allow-unauthenticated google-chrome-stable &&\ mv /usr/bin/google-chrome-stable /usr/bin/chromium; \ elif [[ "$(dpkg --print-architecture)" == "armhf" ]]; then \ apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends devscripts &&\ debsnap chromium-common "$TMP_CHOMIUM_VERSION_ARMHF" -v --force --binary --architecture armhf &&\ debsnap chromium "$TMP_CHOMIUM_VERSION_ARMHF" -v --force --binary --architecture armhf &&\ @@ -166,6 +170,7 @@ RUN \ rm -rf ./binary-chromium-common/* ./binary-chromium/*; \ else \ apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends chromium; \ fi' &&\ # Verify installation. @@ -177,6 +182,7 @@ RUN \ # Install LibreOffice & unoconverter. echo "deb http://deb.debian.org/debian bookworm-backports main" >> /etc/apt/sources.list &&\ apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends -t bookworm-backports libreoffice &&\ curl -Ls https://raw.githubusercontent.com/gotenberg/unoconverter/v0.0.1/unoconv -o /usr/bin/unoconverter &&\ chmod +x /usr/bin/unoconverter &&\ @@ -196,6 +202,7 @@ RUN \ echo '#!/bin/bash\n\nexec java -jar /usr/bin/pdftk-all.jar "$@"' > /usr/bin/pdftk && \ chmod +x /usr/bin/pdftk &&\ apt-get update -qq &&\ + apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends qpdf exiftool &&\ # See https://github.com/nextcloud/docker/issues/380. mkdir -p /usr/share/man/man1 &&\ diff --git a/test/Dockerfile b/test/Dockerfile index 5011f5066..a0dad3ff4 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -19,6 +19,7 @@ ENV CGO_ENABLED=1 COPY --from=golang /usr/local/go /usr/local/go RUN apt-get update -qq &&\ + apt-get upgrade -yqq &&\ apt-get install -y -qq --no-install-recommends \ sudo \ # gcc for cgo. From 51b907bcf57ed8ee4990e2ff6db1bfe46d54a897 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 17 Jan 2025 15:25:17 +0100 Subject: [PATCH 022/254] chore(deps): update Go dependencies --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 161291263..a8cb2abc6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250109193942-1ec2f6cf5d86 + github.com/chromedp/cdproto v0.0.0-20250113203156-3ff4b409e0d4 github.com/chromedp/chromedp v0.11.2 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 @@ -59,7 +59,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect @@ -68,5 +68,5 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/time v0.9.0 // indirect - google.golang.org/protobuf v1.36.2 // indirect + google.golang.org/protobuf v1.36.3 // indirect ) diff --git a/go.sum b/go.sum index 3e6c00516..6c8ffc22f 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250109193942-1ec2f6cf5d86 h1:FGN/TKeWgmhrgZVyq5crllFY0MlEHX16fUOd2oq6uzs= -github.com/chromedp/cdproto v0.0.0-20250109193942-1ec2f6cf5d86/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= +github.com/chromedp/cdproto v0.0.0-20250113203156-3ff4b409e0d4 h1:xO38R20PvryeuBgQYnRU3WsNXFtr/iMyQVJednQVoZw= +github.com/chromedp/cdproto v0.0.0-20250113203156-3ff4b409e0d4/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0= github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -102,8 +102,8 @@ github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -161,7 +161,7 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= -google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 8fd1fa203e1faba535ee7d26f1e292c75346ef2b Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 17 Jan 2025 18:01:44 +0100 Subject: [PATCH 023/254] fix(Dockerfile): remove apt-get upgrade -yqq from armv7 Chromium install --- build/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 5ec44d214..492cb6bc9 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -161,7 +161,6 @@ RUN \ mv /usr/bin/google-chrome-stable /usr/bin/chromium; \ elif [[ "$(dpkg --print-architecture)" == "armhf" ]]; then \ apt-get update -qq &&\ - apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends devscripts &&\ debsnap chromium-common "$TMP_CHOMIUM_VERSION_ARMHF" -v --force --binary --architecture armhf &&\ debsnap chromium "$TMP_CHOMIUM_VERSION_ARMHF" -v --force --binary --architecture armhf &&\ From c0bf81ad7ceb00e711a18f90c72b5e2183238fd4 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 23 Jan 2025 17:04:28 +0100 Subject: [PATCH 024/254] ci(workflows): multiple jobs multiple architectures build and push --- .env | 14 ++ .github/workflows/continuous-delivery.yml | 58 ++++++ .github/workflows/continuous-integration.yml | 190 +++++++++++++++++++ .github/workflows/continuous_delivery.yml | 29 --- .github/workflows/continuous_integration.yml | 102 ---------- .github/workflows/gotenberg-build-push.yml | 51 +++++ .github/workflows/gotenberg-merge-clean.yml | 40 ++++ Makefile | 32 +--- scripts/release.sh | 84 -------- 9 files changed, 356 insertions(+), 244 deletions(-) create mode 100644 .env create mode 100644 .github/workflows/continuous-delivery.yml create mode 100644 .github/workflows/continuous-integration.yml delete mode 100644 .github/workflows/continuous_delivery.yml delete mode 100644 .github/workflows/continuous_integration.yml create mode 100644 .github/workflows/gotenberg-build-push.yml create mode 100644 .github/workflows/gotenberg-merge-clean.yml delete mode 100755 scripts/release.sh diff --git a/.env b/.env new file mode 100644 index 000000000..2c3e2685d --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +GOLANG_VERSION=1.23 +DOCKER_REGISTRY=gotenberg +DOCKER_REPOSITORY=gotenberg +GOTENBERG_VERSION=snapshot +GOTENBERG_USER_GID=1001 +GOTENBERG_USER_UID=1001 +NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. +PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. +PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. +GOLANGCI_LINT_VERSION=v1.63.4 # See https://github.com/golangci/golangci-lint/releases. +GOTENBERG_VERSION=snapshot +DOCKERFILE=build/Dockerfile +DOCKERFILE_CLOUDRUN=build/Dockerfile.cloudrun +DOCKER_BUILD_CONTEXT='.' \ No newline at end of file diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml new file mode 100644 index 000000000..449008acd --- /dev/null +++ b/.github/workflows/continuous-delivery.yml @@ -0,0 +1,58 @@ +name: Continuous Delivery + +on: + release: + types: [ published ] + +permissions: + contents: read + +jobs: + release_amd64: + name: Release linux/amd64 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-latest' + platform: 'linux/amd64' + gotenberg_version: ${{ github.event.release.tag_name }} + + release_386: + name: Release linux/386 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-latest' + platform: 'linux/386' + gotenberg_version: ${{ github.event.release.tag_name }} + + release_arm64: + name: Release linux/arm64 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-24.04-arm' + platform: 'linux/arm64' + gotenberg_version: ${{ github.event.release.tag_name }} + + release_arm_v7: + name: Release linux/arm/v7 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-24.04-arm' + platform: 'linux/arm/v7' + gotenberg_version: ${{ github.event.release.tag_name }} + + merge_clean_release_tags: + needs: + - release_amd64 + - release_386 + - release_arm64 + - release_arm_v7 + name: Merge and clean release tags + uses: ./.github/workflows/gotenberg-merge-clean.yml + secrets: inherit + with: + tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }}" + alternate_registry: 'thecodingmachine' diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 000000000..e073bc6cc --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,190 @@ +name: Continuous Integration + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ (github.event_name == 'pull_request' && github.event.pull_request.number) || 'main' }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: false + + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Run linters + uses: golangci/golangci-lint-action@v6 + with: + version: v1.63.4 + + tests: + needs: + - lint + name: Tests + # TODO: once arm64 actions are available, also run the tests on this architecture. + # See: https://github.com/actions/virtual-environments/issues/2552#issuecomment-771478000. + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build testing environment + run: make build build-tests + + - name: Run tests + run: make tests-once + + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + snapshot_amd64: + if: github.event_name == 'pull_request' + needs: + - tests + name: Snapshot linux/amd64 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-latest' + platform: 'linux/amd64' + gotenberg_version: pr-${{ github.event.pull_request.number }} + alternate_repository: 'snapshot' + + snapshot_386: + if: github.event_name == 'pull_request' + needs: + - tests + name: Snapshot linux/386 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-latest' + platform: 'linux/386' + gotenberg_version: pr-${{ github.event.pull_request.number }} + alternate_repository: 'snapshot' + + snapshot_arm64: + if: github.event_name == 'pull_request' + needs: + - tests + name: Snapshot linux/arm64 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-24.04-arm' + platform: 'linux/arm64' + gotenberg_version: pr-${{ github.event.pull_request.number }} + alternate_repository: 'snapshot' + + snapshot_arm_v7: + if: github.event_name == 'pull_request' + needs: + - tests + name: Snapshot linux/arm/v7 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-24.04-arm' + platform: 'linux/arm/v7' + gotenberg_version: pr-${{ github.event.pull_request.number }} + alternate_repository: 'snapshot' + + merge_clean_snapshot_tags: + needs: + - snapshot_amd64 + - snapshot_386 + - snapshot_arm64 + - snapshot_arm_v7 + name: Merge and clean snapshot tags + uses: ./.github/workflows/gotenberg-merge-clean.yml + secrets: inherit + with: + tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }}" + + edge_amd64: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - tests + name: Edge linux/amd64 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-latest' + platform: 'linux/amd64' + gotenberg_version: 'edge' + + edge_386: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - tests + name: Edge linux/386 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-latest' + platform: 'linux/386' + gotenberg_version: 'edge' + + edge_arm64: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - tests + name: Edge linux/arm64 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-24.04-arm' + platform: 'linux/arm64' + gotenberg_version: 'edge' + + edge_arm_v7: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - tests + name: Edge linux/arm/v7 + uses: ./.github/workflows/gotenberg-build-push.yml + secrets: inherit + with: + runs_on: 'ubuntu-24.04-arm' + platform: 'linux/arm/v7' + gotenberg_version: 'edge' + + merge_clean_edge_tags: + needs: + - edge_amd64 + - edge_386 + - edge_arm64 + - edge_arm_v7 + name: Merge and clean edge tags + uses: ./.github/workflows/gotenberg-merge-clean.yml + secrets: inherit + with: + tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }}" + alternate_registry: 'thecodingmachine' diff --git a/.github/workflows/continuous_delivery.yml b/.github/workflows/continuous_delivery.yml deleted file mode 100644 index ca229fe0c..000000000 --- a/.github/workflows/continuous_delivery.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Continuous Delivery - -on: - release: - types: [ published ] - -permissions: - contents: read - -jobs: - release: - name: Release Docker image - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Checkout source code - uses: actions/checkout@v4 - - name: Log in to Docker Hub Container Registry - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push Docker image for release - run: | - make release GOTENBERG_VERSION=${{ github.event.release.tag_name }} - make release GOTENBERG_VERSION=${{ github.event.release.tag_name }} DOCKER_REGISTRY=thecodingmachine diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml deleted file mode 100644 index 51cd4eeb8..000000000 --- a/.github/workflows/continuous_integration.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Continuous Integration - -on: - push: - branches: - - main - pull_request: - branches: - - main - -concurrency: - group: ${{ (github.event_name == 'pull_request' && github.event.pull_request.number) || 'main' }} - cancel-in-progress: true - -permissions: - contents: write - -jobs: - - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '1.23' - cache: false - - name: Checkout source code - uses: actions/checkout@v4 - - name: Run linters - uses: golangci/golangci-lint-action@v6 - with: - version: v1.63.4 - - tests: - needs: - - lint - name: Tests - # TODO: once arm64 actions are available, also run the tests on this architecture. - # See: https://github.com/actions/virtual-environments/issues/2552#issuecomment-771478000. - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Checkout source code - uses: actions/checkout@v4 - - name: Build testing environment - run: make build build-tests - - name: Run tests - run: make tests-once - - name: Upload to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - verbose: true - - snapshot_release: - if: github.event_name == 'pull_request' - needs: - - tests - name: Snapshot release - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Checkout source code - uses: actions/checkout@v4 - - name: Log in to Docker Hub Container Registry - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push snapshot Docker image (linux/amd64) - run: make release GOTENBERG_VERSION=${{ github.head_ref }} DOCKER_REPOSITORY=snapshot LINUX_AMD64_RELEASE=true - - edge_release: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: - - tests - name: Edge release - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Checkout source code - uses: actions/checkout@v4 - - name: Log in to Docker Hub Container Registry - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push Docker image for main branch - run: | - make release GOTENBERG_VERSION=edge - make release GOTENBERG_VERSION=edge DOCKER_REGISTRY=thecodingmachine diff --git a/.github/workflows/gotenberg-build-push.yml b/.github/workflows/gotenberg-build-push.yml new file mode 100644 index 000000000..82384e8d7 --- /dev/null +++ b/.github/workflows/gotenberg-build-push.yml @@ -0,0 +1,51 @@ +on: + workflow_call: + inputs: + runs_on: + type: string + required: true + platform: + type: string + required: true + gotenberg_version: + type: string + required: true + alternate_repository: + type: string + default: '' + outputs: + tags: + value: ${{ jobs.gotenberg_build_push.outputs.tags }} + +permissions: + contents: read + +jobs: + gotenberg_build_push: + runs-on: ${{ inputs.runs_on }} + outputs: + tags: ${{ steps.build_push.outputs.tags }} + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Check out code + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push ${{ inputs.platform }} + id: build_push + uses: gotenberg/gotenberg-release-action@main + with: + step: build_push + version: ${{ inputs.gotenberg_version }} + platform: ${{ inputs.platform }} + alternate_repository: ${{ inputs.alternate_repository }} diff --git a/.github/workflows/gotenberg-merge-clean.yml b/.github/workflows/gotenberg-merge-clean.yml new file mode 100644 index 000000000..e2ddf51f7 --- /dev/null +++ b/.github/workflows/gotenberg-merge-clean.yml @@ -0,0 +1,40 @@ +on: + workflow_call: + inputs: + tags: + type: string + required: true + alternate_registry: + type: string + default: '' + +permissions: + contents: read + +jobs: + gotenberg_merge_clean: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Check out code + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Merge and clean + uses: gotenberg/gotenberg-release-action@main + with: + step: merge_clean + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: ${{ inputs.tags }} + alternate_registry: ${{ inputs.alternate_registry }} diff --git a/Makefile b/Makefile index de2e0dbdf..6066e4f73 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +include .env + .PHONY: help help: ## Show the help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -5,17 +7,6 @@ help: ## Show the help .PHONY: it it: build build-tests ## Initialize the development environment -GOLANG_VERSION=1.23 -DOCKER_REGISTRY=gotenberg -DOCKER_REPOSITORY=gotenberg -GOTENBERG_VERSION=snapshot -GOTENBERG_USER_GID=1001 -GOTENBERG_USER_UID=1001 -NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. -PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. -PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. -GOLANGCI_LINT_VERSION=v1.63.4 # See https://github.com/golangci/golangci-lint/releases. - .PHONY: build build: ## Build the Gotenberg's Docker image docker build \ @@ -27,7 +18,7 @@ build: ## Build the Gotenberg's Docker image --build-arg PDFTK_VERSION=$(PDFTK_VERSION) \ --build-arg PDFCPU_VERSION=$(PDFCPU_VERSION) \ -t $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION) \ - -f build/Dockerfile . + -f $(DOCKERFILE) $(DOCKER_BUILD_CONTEXT) GOTENBERG_GRACEFUL_SHUTDOWN_DURATION=30s API_PORT=3000 @@ -199,20 +190,3 @@ fmt: ## Format the code and "optimize" the dependencies godoc: ## Run a webserver with Gotenberg godoc $(info http://localhost:6060/pkg/github.com/gotenberg/gotenberg/v8) godoc -http=:6060 - -LINUX_AMD64_RELEASE=false - -.PHONY: release -release: ## Build the Gotenberg's Docker image and push it to a Docker repository - ./scripts/release.sh \ - $(GOLANG_VERSION) \ - $(GOTENBERG_VERSION) \ - $(GOTENBERG_USER_GID) \ - $(GOTENBERG_USER_UID) \ - $(NOTO_COLOR_EMOJI_VERSION) \ - $(PDFTK_VERSION) \ - $(PDFCPU_VERSION) \ - $(DOCKER_REGISTRY) \ - $(DOCKER_REPOSITORY) \ - $(LINUX_AMD64_RELEASE) - diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index f9c014ff9..000000000 --- a/scripts/release.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -set -e - -# Args. -GOLANG_VERSION="$1" -GOTENBERG_VERSION="$2" -GOTENBERG_USER_GID="$3" -GOTENBERG_USER_UID="$4" -NOTO_COLOR_EMOJI_VERSION="$5" -PDFTK_VERSION="$6" -PDFCPU_VERSION="$7" -DOCKER_REGISTRY="$8" -DOCKER_REPOSITORY="$9" -LINUX_AMD64_RELEASE="${10}" - -# Find out if given version is "semver". -GOTENBERG_VERSION="${GOTENBERG_VERSION//v}" -IFS='.' read -ra SEMVER <<< "$GOTENBERG_VERSION" -VERSION_LENGTH=${#SEMVER[@]} -TAGS=() -TAGS_CLOUD_RUN=() - -if [ "$VERSION_LENGTH" -eq 3 ]; then - MAJOR="${SEMVER[0]}" - MINOR="${SEMVER[1]}" - PATCH="${SEMVER[2]}" - - TAGS+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest") - TAGS+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$MAJOR") - TAGS+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$MAJOR.$MINOR") - TAGS+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$MAJOR.$MINOR.$PATCH") - - TAGS_CLOUD_RUN+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest-cloudrun") - TAGS_CLOUD_RUN+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$MAJOR-cloudrun") - TAGS_CLOUD_RUN+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$MAJOR.$MINOR-cloudrun") - TAGS_CLOUD_RUN+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$MAJOR.$MINOR.$PATCH-cloudrun") -else - # Normalizes version. - GOTENBERG_VERSION="${GOTENBERG_VERSION// /-}" - GOTENBERG_VERSION="$(echo "$GOTENBERG_VERSION" | tr -cd '[:alnum:]._\-')" - - if [[ "$GOTENBERG_VERSION" =~ ^[\.\-] ]]; then - GOTENBERG_VERSION="_${GOTENBERG_VERSION#?}" - fi - - if [ "${#GOTENBERG_VERSION}" -gt 128 ]; then - GOTENBERG_VERSION="${GOTENBERG_VERSION:0:128}" - fi - - TAGS+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$GOTENBERG_VERSION") - TAGS_CLOUD_RUN+=("-t" "$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$GOTENBERG_VERSION-cloudrun") -fi - -# Multi-arch build takes a lot of time. -if [ "$LINUX_AMD64_RELEASE" = true ]; then - PLATFORM_FLAG="--platform linux/amd64" -else - PLATFORM_FLAG="--platform linux/amd64,linux/arm64,linux/386,linux/arm/v7" -fi - -docker buildx build \ - --build-arg GOLANG_VERSION="$GOLANG_VERSION" \ - --build-arg GOTENBERG_VERSION="$GOTENBERG_VERSION" \ - --build-arg GOTENBERG_USER_GID="$GOTENBERG_USER_GID" \ - --build-arg GOTENBERG_USER_UID="$GOTENBERG_USER_UID" \ - --build-arg NOTO_COLOR_EMOJI_VERSION="$NOTO_COLOR_EMOJI_VERSION" \ - --build-arg PDFTK_VERSION="$PDFTK_VERSION" \ - --build-arg PDFCPU_VERSION="$PDFCPU_VERSION" \ - $PLATFORM_FLAG \ - "${TAGS[@]}" \ - --push \ - -f build/Dockerfile . - -# Cloud Run variant. -# Only linux/amd64! See https://github.com/gotenberg/gotenberg/issues/505#issuecomment-1264679278. -docker buildx build \ - --build-arg DOCKER_REGISTRY="$DOCKER_REGISTRY" \ - --build-arg DOCKER_REPOSITORY="$DOCKER_REPOSITORY" \ - --build-arg GOTENBERG_VERSION="$GOTENBERG_VERSION" \ - --platform linux/amd64 \ - "${TAGS_CLOUD_RUN[@]}" \ - --push \ - -f build/Dockerfile.cloudrun . From 8631a15933745eb74f70cdd825abf28221d0790c Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 23 Jan 2025 19:46:16 +0100 Subject: [PATCH 025/254] ci(comment): remove comment about arm64 testing --- .github/workflows/continuous-integration.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index e073bc6cc..71c16278d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -39,8 +39,6 @@ jobs: needs: - lint name: Tests - # TODO: once arm64 actions are available, also run the tests on this architecture. - # See: https://github.com/actions/virtual-environments/issues/2552#issuecomment-771478000. runs-on: ubuntu-latest steps: - name: Set up QEMU From d96058d5894976e4769872b6967158816fddd5af Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 23 Jan 2025 19:47:28 +0100 Subject: [PATCH 026/254] fix(Dockerfile): use latest Chromium for armhf --- build/Dockerfile | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 492cb6bc9..0e0756535 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -61,7 +61,6 @@ ARG GOTENBERG_USER_GID ARG GOTENBERG_USER_UID ARG NOTO_COLOR_EMOJI_VERSION ARG PDFTK_VERSION -ARG TMP_CHOMIUM_VERSION_ARMHF="116.0.5845.180-1~deb12u1" LABEL org.opencontainers.image.title="Gotenberg" \ org.opencontainers.image.description="A Docker-powered stateless API for PDF files." \ @@ -147,9 +146,6 @@ RUN \ # Install either Google Chrome stable on amd64 architecture or # Chromium on other architectures. # See https://github.com/gotenberg/gotenberg/issues/328. - # FIXME: - # armhf is currently not working with the latest version of Chromium. - # See: https://github.com/gotenberg/gotenberg/issues/709. /bin/bash -c \ 'set -e &&\ if [[ "$(dpkg --print-architecture)" == "amd64" ]]; then \ @@ -159,14 +155,6 @@ RUN \ apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends --allow-unauthenticated google-chrome-stable &&\ mv /usr/bin/google-chrome-stable /usr/bin/chromium; \ - elif [[ "$(dpkg --print-architecture)" == "armhf" ]]; then \ - apt-get update -qq &&\ - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends devscripts &&\ - debsnap chromium-common "$TMP_CHOMIUM_VERSION_ARMHF" -v --force --binary --architecture armhf &&\ - debsnap chromium "$TMP_CHOMIUM_VERSION_ARMHF" -v --force --binary --architecture armhf &&\ - DEBIAN_FRONTEND=noninteractive apt-get install --fix-broken -y -qq --no-install-recommends "./binary-chromium-common/chromium-common_${TMP_CHOMIUM_VERSION_ARMHF}_armhf.deb" "./binary-chromium/chromium_${TMP_CHOMIUM_VERSION_ARMHF}_armhf.deb" &&\ - DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq devscripts &&\ - rm -rf ./binary-chromium-common/* ./binary-chromium/*; \ else \ apt-get update -qq &&\ apt-get upgrade -yqq &&\ From 66ade27383258ecb2f4f6c9b14e6db08778e239e Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 23 Jan 2025 20:18:18 +0100 Subject: [PATCH 027/254] docs(README): fix continuous integration badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30a702754..39e2e5281 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Total downloads (gotenberg/gotenberg) Total downloads (thecodingmachine/gotenberg)
- Continuous Integration + Continuous Integration Go Reference Code coverage

From 09e511bc4994b14230ec736648e575bfaf82e8e6 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 24 Jan 2025 09:41:31 +0100 Subject: [PATCH 028/254] ci(workflows): use sub-actions --- .env | 2 +- ...enberg-build-push.yml => __build-push.yml} | 7 +- ...berg-merge-clean.yml => __merge-clean.yml} | 5 +- .github/workflows/continuous-delivery.yml | 28 ++++---- .github/workflows/continuous-integration.yml | 70 +++++++++---------- 5 files changed, 55 insertions(+), 57 deletions(-) rename .github/workflows/{gotenberg-build-push.yml => __build-push.yml} (87%) rename .github/workflows/{gotenberg-merge-clean.yml => __merge-clean.yml} (89%) diff --git a/.env b/.env index 2c3e2685d..4516acecf 100644 --- a/.env +++ b/.env @@ -11,4 +11,4 @@ GOLANGCI_LINT_VERSION=v1.63.4 # See https://github.com/golangci/golangci-lint/re GOTENBERG_VERSION=snapshot DOCKERFILE=build/Dockerfile DOCKERFILE_CLOUDRUN=build/Dockerfile.cloudrun -DOCKER_BUILD_CONTEXT='.' \ No newline at end of file +DOCKER_BUILD_CONTEXT='.' diff --git a/.github/workflows/gotenberg-build-push.yml b/.github/workflows/__build-push.yml similarity index 87% rename from .github/workflows/gotenberg-build-push.yml rename to .github/workflows/__build-push.yml index 82384e8d7..6dba616d4 100644 --- a/.github/workflows/gotenberg-build-push.yml +++ b/.github/workflows/__build-push.yml @@ -15,13 +15,13 @@ on: default: '' outputs: tags: - value: ${{ jobs.gotenberg_build_push.outputs.tags }} + value: ${{ jobs.build_push.outputs.tags }} permissions: contents: read jobs: - gotenberg_build_push: + build_push: runs-on: ${{ inputs.runs_on }} outputs: tags: ${{ steps.build_push.outputs.tags }} @@ -43,9 +43,8 @@ jobs: - name: Build and push ${{ inputs.platform }} id: build_push - uses: gotenberg/gotenberg-release-action@main + uses: gotenberg/gotenberg-release-action/actions/build-push@main with: - step: build_push version: ${{ inputs.gotenberg_version }} platform: ${{ inputs.platform }} alternate_repository: ${{ inputs.alternate_repository }} diff --git a/.github/workflows/gotenberg-merge-clean.yml b/.github/workflows/__merge-clean.yml similarity index 89% rename from .github/workflows/gotenberg-merge-clean.yml rename to .github/workflows/__merge-clean.yml index e2ddf51f7..79c4a1a42 100644 --- a/.github/workflows/gotenberg-merge-clean.yml +++ b/.github/workflows/__merge-clean.yml @@ -12,7 +12,7 @@ permissions: contents: read jobs: - gotenberg_merge_clean: + merge_clean: runs-on: ubuntu-latest steps: - name: Set up QEMU @@ -31,9 +31,8 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Merge and clean - uses: gotenberg/gotenberg-release-action@main + uses: gotenberg/gotenberg-release-action/actions/merge-clean@main with: - step: merge_clean docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} tags: ${{ inputs.tags }} diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 449008acd..e2d8c402a 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -10,38 +10,38 @@ permissions: jobs: release_amd64: name: Release linux/amd64 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-latest' - platform: 'linux/amd64' + runs_on: ubuntu-latest + platform: linux/amd64 gotenberg_version: ${{ github.event.release.tag_name }} release_386: name: Release linux/386 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-latest' - platform: 'linux/386' + runs_on: ubuntu-latest + platform: linux/386 gotenberg_version: ${{ github.event.release.tag_name }} release_arm64: name: Release linux/arm64 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-24.04-arm' - platform: 'linux/arm64' + runs_on: ubuntu-24.04-arm + platform: linux/arm64 gotenberg_version: ${{ github.event.release.tag_name }} release_arm_v7: name: Release linux/arm/v7 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-24.04-arm' - platform: 'linux/arm/v7' + runs_on: ubuntu-24.04-arm + platform: linux/arm/v7 gotenberg_version: ${{ github.event.release.tag_name }} merge_clean_release_tags: @@ -51,8 +51,8 @@ jobs: - release_arm64 - release_arm_v7 name: Merge and clean release tags - uses: ./.github/workflows/gotenberg-merge-clean.yml + uses: ./.github/workflows/__merge-clean.yml secrets: inherit with: tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }}" - alternate_registry: 'thecodingmachine' + alternate_registry: thecodingmachine diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 71c16278d..f65d9d7b0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -67,52 +67,52 @@ jobs: needs: - tests name: Snapshot linux/amd64 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-latest' - platform: 'linux/amd64' + runs_on: ubuntu-latest + platform: linux/amd64 gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: 'snapshot' + alternate_repository: snapshot snapshot_386: if: github.event_name == 'pull_request' needs: - tests name: Snapshot linux/386 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-latest' - platform: 'linux/386' + runs_on: ubuntu-latest + platform: linux/386 gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: 'snapshot' + alternate_repository: snapshot snapshot_arm64: if: github.event_name == 'pull_request' needs: - tests name: Snapshot linux/arm64 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-24.04-arm' - platform: 'linux/arm64' + runs_on: ubuntu-24.04-arm + platform: linux/arm64 gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: 'snapshot' + alternate_repository: snapshot snapshot_arm_v7: if: github.event_name == 'pull_request' needs: - tests name: Snapshot linux/arm/v7 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-24.04-arm' - platform: 'linux/arm/v7' + runs_on: ubuntu-24.04-arm + platform: linux/arm/v7 gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: 'snapshot' + alternate_repository: snapshot merge_clean_snapshot_tags: needs: @@ -121,7 +121,7 @@ jobs: - snapshot_arm64 - snapshot_arm_v7 name: Merge and clean snapshot tags - uses: ./.github/workflows/gotenberg-merge-clean.yml + uses: ./.github/workflows/__merge-clean.yml secrets: inherit with: tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }}" @@ -131,48 +131,48 @@ jobs: needs: - tests name: Edge linux/amd64 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-latest' - platform: 'linux/amd64' - gotenberg_version: 'edge' + runs_on: ubuntu-latest + platform: linux/amd64 + gotenberg_version: edge edge_386: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - tests name: Edge linux/386 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-latest' - platform: 'linux/386' - gotenberg_version: 'edge' + runs_on: ubuntu-latest + platform: linux/386 + gotenberg_version: edge edge_arm64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - tests name: Edge linux/arm64 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-24.04-arm' - platform: 'linux/arm64' - gotenberg_version: 'edge' + runs_on: ubuntu-24.04-arm + platform: linux/arm64 + gotenberg_version: edge edge_arm_v7: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - tests name: Edge linux/arm/v7 - uses: ./.github/workflows/gotenberg-build-push.yml + uses: ./.github/workflows/__build-push.yml secrets: inherit with: - runs_on: 'ubuntu-24.04-arm' - platform: 'linux/arm/v7' - gotenberg_version: 'edge' + runs_on: ubuntu-24.04-arm + platform: linux/arm/v7 + gotenberg_version: edge merge_clean_edge_tags: needs: @@ -181,8 +181,8 @@ jobs: - edge_arm64 - edge_arm_v7 name: Merge and clean edge tags - uses: ./.github/workflows/gotenberg-merge-clean.yml + uses: ./.github/workflows/__merge-clean.yml secrets: inherit with: tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }}" - alternate_registry: 'thecodingmachine' + alternate_registry: thecodingmachine From 6ce47e3a20c9a565588b16faab8417f8f13ce1d7 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 24 Jan 2025 10:25:25 +0100 Subject: [PATCH 029/254] ci(workflows): cleanup PR Docker images --- .github/workflows/__delete.yml | 24 ++++++++++++++++++++++ .github/workflows/pull-request-cleanup.yml | 18 ++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/workflows/__delete.yml create mode 100644 .github/workflows/pull-request-cleanup.yml diff --git a/.github/workflows/__delete.yml b/.github/workflows/__delete.yml new file mode 100644 index 000000000..4938b8f30 --- /dev/null +++ b/.github/workflows/__delete.yml @@ -0,0 +1,24 @@ +on: + workflow_call: + inputs: + gotenberg_version: + type: string + required: true + alternate_repository: + type: string + default: '' + +permissions: + contents: read + +jobs: + delete: + runs-on: ubuntu-latest + steps: + - name: Delete + uses: gotenberg/gotenberg-release-action/actions/delete@main + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: ${{ inputs.gotenberg_version }} + alternate_repository: ${{ inputs.alternate_repository }} diff --git a/.github/workflows/pull-request-cleanup.yml b/.github/workflows/pull-request-cleanup.yml new file mode 100644 index 000000000..1815466d7 --- /dev/null +++ b/.github/workflows/pull-request-cleanup.yml @@ -0,0 +1,18 @@ +name: Pull Request Cleanup + +on: + pull_request: + types: [ closed ] + +permissions: + contents: read + +jobs: + + cleanup: + name: Cleanup + uses: ./.github/workflows/__delete.yml + secrets: inherit + with: + gotenberg_version: pr-${{ github.event.pull_request.number }} + alternate_repository: snapshot From cb4835589835124f4e0ca53855c5f603acbebda5 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 24 Jan 2025 10:32:52 +0100 Subject: [PATCH 030/254] fix(ci): missing checkout step on reusable workflow delete --- .github/workflows/__build-push.yml | 1 + .github/workflows/__delete.yml | 4 ++++ .github/workflows/__merge-clean.yml | 1 + 3 files changed, 6 insertions(+) diff --git a/.github/workflows/__build-push.yml b/.github/workflows/__build-push.yml index 6dba616d4..301ae0587 100644 --- a/.github/workflows/__build-push.yml +++ b/.github/workflows/__build-push.yml @@ -22,6 +22,7 @@ permissions: jobs: build_push: + name: Build and push Docker images runs-on: ${{ inputs.runs_on }} outputs: tags: ${{ steps.build_push.outputs.tags }} diff --git a/.github/workflows/__delete.yml b/.github/workflows/__delete.yml index 4938b8f30..96d2156e9 100644 --- a/.github/workflows/__delete.yml +++ b/.github/workflows/__delete.yml @@ -13,8 +13,12 @@ permissions: jobs: delete: + name: Delete Docker images runs-on: ubuntu-latest steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Delete uses: gotenberg/gotenberg-release-action/actions/delete@main with: diff --git a/.github/workflows/__merge-clean.yml b/.github/workflows/__merge-clean.yml index 79c4a1a42..baf484447 100644 --- a/.github/workflows/__merge-clean.yml +++ b/.github/workflows/__merge-clean.yml @@ -13,6 +13,7 @@ permissions: jobs: merge_clean: + name: Merge and clean Docker images runs-on: ubuntu-latest steps: - name: Set up QEMU From a9b44ee39ef2a2ab6f5de1bd3ebd7de3e46fd2ee Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 27 Jan 2025 16:05:52 +0100 Subject: [PATCH 031/254] chore(lint): remove rule G115 (gosec) exception --- .golangci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d4f38a3a2..8fd432f6c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,10 +6,6 @@ linters-settings: - prefix(github.com/gotenberg/gotenberg/v8) skip-generated: true custom-order: true - # Until https://github.com/securego/gosec/issues/1187 is resolved. - gosec: - excludes: - - G115 linters: disable-all: true From b418f1eb05f67f68a8c1f4d7316b93826695ad9c Mon Sep 17 00:00:00 2001 From: Peter Chakalov Date: Tue, 28 Jan 2025 15:38:27 +0200 Subject: [PATCH 032/254] feat(pdfengines): add support for flattening annotations (#1105) * initial changes * Add tests * Fix edge case when we need to regenerate appearances * Fix comments * Add missing comment * Add missing comment * Add missing comment * Add flatten option to the merge route * Add flatten option to the libreoffice convert route * Add flatten option to the chromium convert route * Revert "Add flatten option to the chromium convert route" This reverts commit cdab8b4e6bd9254af5192d235f83176a13668724. * Ignore lint false positives * Add missing tests * Add flatten route tests * Replace input instead of creating a new file * create copy before flatten in tests --------- Co-authored-by: Peter Chakalov --- Makefile | 2 + pkg/gotenberg/mocks.go | 7 + pkg/gotenberg/mocks_test.go | 8 + pkg/gotenberg/pdfengine.go | 7 + pkg/modules/exiftool/exiftool.go | 5 + pkg/modules/exiftool/exiftool_test.go | 9 + .../libreoffice/pdfengine/pdfengine.go | 5 + .../libreoffice/pdfengine/pdfengine_test.go | 9 + pkg/modules/libreoffice/routes.go | 9 + pkg/modules/libreoffice/routes_test.go | 44 +++++ pkg/modules/pdfcpu/pdfcpu.go | 5 + pkg/modules/pdfcpu/pdfcpu_test.go | 9 + pkg/modules/pdfengines/multi.go | 29 +++ pkg/modules/pdfengines/multi_test.go | 91 +++++++++ pkg/modules/pdfengines/pdfengines.go | 12 ++ pkg/modules/pdfengines/pdfengines_test.go | 17 +- pkg/modules/pdfengines/routes.go | 58 ++++++ pkg/modules/pdfengines/routes_test.go | 180 ++++++++++++++++++ pkg/modules/pdftk/pdftk.go | 5 + pkg/modules/pdftk/pdftk_test.go | 9 + pkg/modules/qpdf/qpdf.go | 21 ++ pkg/modules/qpdf/qpdf_test.go | 97 ++++++++++ test/testdata/pdfengines/sample3.pdf | Bin 0 -> 224175 bytes 23 files changed, 634 insertions(+), 4 deletions(-) create mode 100644 test/testdata/pdfengines/sample3.pdf diff --git a/Makefile b/Makefile index 6066e4f73..0a9b212bf 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ LOG_FIELDS_PREFIX= PDFENGINES_ENGINES= PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk +PDFENGINES_FLATTEN_ENGINES=qpdf PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine PDFENGINES_READ_METADATA_ENGINES=exiftool PDFENGINES_WRITE_METADATA_ENGINES=exiftool @@ -134,6 +135,7 @@ run: ## Start a Gotenberg container --pdfengines-engines=$(PDFENGINES_ENGINES) \ --pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \ --pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \ + --pdfengines-convert-engines=$(PDFENGINES_FLATTEN_ENGINES) \ --pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \ --pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \ --pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \ diff --git a/pkg/gotenberg/mocks.go b/pkg/gotenberg/mocks.go index 2ade89525..a771c8c3d 100644 --- a/pkg/gotenberg/mocks.go +++ b/pkg/gotenberg/mocks.go @@ -35,9 +35,12 @@ func (mod *ValidatorMock) Validate() error { } // PdfEngineMock is a mock for the [PdfEngine] interface. +// +//nolint:dupl type PdfEngineMock struct { MergeMock func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error SplitMock func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) + FlattenMock func(ctx context.Context, logger *zap.Logger, inputPath string) error ConvertMock func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error ReadMetadataMock func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error @@ -51,6 +54,10 @@ func (engine *PdfEngineMock) Split(ctx context.Context, logger *zap.Logger, mode return engine.SplitMock(ctx, logger, mode, inputPath, outputDirPath) } +func (engine *PdfEngineMock) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + return engine.FlattenMock(ctx, logger, inputPath) +} + func (engine *PdfEngineMock) Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error { return engine.ConvertMock(ctx, logger, formats, inputPath, outputPath) } diff --git a/pkg/gotenberg/mocks_test.go b/pkg/gotenberg/mocks_test.go index 953a6ec28..0608f2c2e 100644 --- a/pkg/gotenberg/mocks_test.go +++ b/pkg/gotenberg/mocks_test.go @@ -56,6 +56,9 @@ func TestPDFEngineMock(t *testing.T) { SplitMock: func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) { return nil, nil }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error { return nil }, @@ -77,6 +80,11 @@ func TestPDFEngineMock(t *testing.T) { t.Errorf("expected no error from PdfEngineMock.Split, but got: %v", err) } + err = mock.Flatten(context.Background(), zap.NewNop(), "") + if err != nil { + t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err) + } + err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "") if err != nil { t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err) diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index 788c07c7a..dc4a4f7ce 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -88,6 +88,8 @@ type PdfFormats struct { // PdfEngine provides an interface for operations on PDFs. Implementations // can utilize various tools like PDFtk, or implement functionality directly in // Go. +// +//nolint:dupl type PdfEngine interface { // Merge combines multiple PDFs into a single PDF. The resulting page order // is determined by the order of files provided in inputPaths. @@ -96,6 +98,11 @@ type PdfEngine interface { // Split splits a given PDF file. Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) + // Flatten merges existing annotation appearances with page content, effectively deleting the original annotations. + // This process can flatten forms as well, as forms share a relationship with annotations. + // Note that this operation is irreversible. + Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error + // Convert transforms a given PDF to the specified formats defined in // PdfFormats. If no format, it does nothing. Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error diff --git a/pkg/modules/exiftool/exiftool.go b/pkg/modules/exiftool/exiftool.go index aeffc6a99..8fafc27d7 100644 --- a/pkg/modules/exiftool/exiftool.go +++ b/pkg/modules/exiftool/exiftool.go @@ -63,6 +63,11 @@ func (engine *ExifTool) Split(ctx context.Context, logger *zap.Logger, mode gote return nil, fmt.Errorf("split PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// Flatten is not available in this implementation. +func (engine *ExifTool) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + return fmt.Errorf("flatten PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // Convert is not available in this implementation. func (engine *ExifTool) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/exiftool/exiftool_test.go b/pkg/modules/exiftool/exiftool_test.go index 52c8f31d7..00fca5a2b 100644 --- a/pkg/modules/exiftool/exiftool_test.go +++ b/pkg/modules/exiftool/exiftool_test.go @@ -91,6 +91,15 @@ func TestExiftool_Split(t *testing.T) { } } +func TestExiftool_Flatten(t *testing.T) { + engine := new(ExifTool) + err := engine.Flatten(context.Background(), zap.NewNop(), "") + + if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { + t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) + } +} + func TestExiftool_Convert(t *testing.T) { engine := new(ExifTool) err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine.go b/pkg/modules/libreoffice/pdfengine/pdfengine.go index 5416ab357..72ae31122 100644 --- a/pkg/modules/libreoffice/pdfengine/pdfengine.go +++ b/pkg/modules/libreoffice/pdfengine/pdfengine.go @@ -56,6 +56,11 @@ func (engine *LibreOfficePdfEngine) Split(ctx context.Context, logger *zap.Logge return nil, fmt.Errorf("split PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// Flatten is not available in this implementation. +func (engine *LibreOfficePdfEngine) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + return fmt.Errorf("Flatten PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // Convert converts the given PDF to a specific PDF format. Currently, only the // PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/UA formats are available. If another // PDF format is requested, it returns a [gotenberg.ErrPdfFormatNotSupported] diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine_test.go b/pkg/modules/libreoffice/pdfengine/pdfengine_test.go index 1dc4ed737..d7034c2e3 100644 --- a/pkg/modules/libreoffice/pdfengine/pdfengine_test.go +++ b/pkg/modules/libreoffice/pdfengine/pdfengine_test.go @@ -127,6 +127,15 @@ func TestLibreOfficePdfEngine_Split(t *testing.T) { } } +func TestLibreOfficePdfEngine_Flatten(t *testing.T) { + engine := new(LibreOfficePdfEngine) + err := engine.Flatten(context.Background(), zap.NewNop(), "") + + if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { + t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) + } +} + func TestLibreOfficePdfEngine_Convert(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index b6786189b..cf712c0ee 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -60,6 +60,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap maxImageResolution int nativePdfFormats bool merge bool + flatten bool ) err := form. @@ -135,6 +136,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } return nil }). + Bool("flatten", &flatten, false). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) @@ -261,6 +263,13 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap return fmt.Errorf("write metadata: %w", err) } + if flatten { + err = pdfengines.FlattenStub(ctx, engine, outputPaths) + if err != nil { + return fmt.Errorf("flatten PDFs: %w", err) + } + } + if len(outputPaths) > 1 && splitMode == zeroValuedSplitMode { // If .zip archive, document.docx -> document.docx.pdf. for i, inputPath := range inputPaths { diff --git a/pkg/modules/libreoffice/routes_test.go b/pkg/modules/libreoffice/routes_test.go index 139f57489..c033591de 100644 --- a/pkg/modules/libreoffice/routes_test.go +++ b/pkg/modules/libreoffice/routes_test.go @@ -402,6 +402,38 @@ func TestConvertRoute(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 0, }, + { + scenario: "PDF engine flatten error", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "document.docx": "/document.docx", + }) + ctx.SetValues(map[string][]string{ + "flatten": { + "true", + }, + }) + ctx.SetCancelled(true) + return ctx + }(), + libreOffice: &libreofficeapi.ApiMock{ + PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { + return nil + }, + ExtensionsMock: func() []string { + return []string{".docx"} + }, + }, + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, { scenario: "cannot add output paths", ctx: func() *api.ContextMock { @@ -454,6 +486,9 @@ func TestConvertRoute(t *testing.T) { "metadata": { "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", }, + "flatten": { + "true", + }, }) return ctx }(), @@ -475,6 +510,9 @@ func TestConvertRoute(t *testing.T) { WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, }, expectError: false, expectHttpError: false, @@ -502,6 +540,9 @@ func TestConvertRoute(t *testing.T) { "metadata": { "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", }, + "flatten": { + "true", + }, }) ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { return nil @@ -526,6 +567,9 @@ func TestConvertRoute(t *testing.T) { WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, }, expectError: false, expectHttpError: false, diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index 29803cb38..2e82f8793 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -107,6 +107,11 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenb return outputPaths, nil } +// Flatten is not available in this implementation. +func (engine *PdfCpu) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + return fmt.Errorf("flatten PDF with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // Convert is not available in this implementation. func (engine *PdfCpu) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/pdfcpu/pdfcpu_test.go b/pkg/modules/pdfcpu/pdfcpu_test.go index e996aed8b..063f42e6f 100644 --- a/pkg/modules/pdfcpu/pdfcpu_test.go +++ b/pkg/modules/pdfcpu/pdfcpu_test.go @@ -239,6 +239,15 @@ func TestPdfCpu_Split(t *testing.T) { } } +func TestPdfCpu_Flatten(t *testing.T) { + mod := new(PdfCpu) + err := mod.Flatten(context.TODO(), zap.NewNop(), "") + + if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { + t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) + } +} + func TestPdfCpu_Convert(t *testing.T) { mod := new(PdfCpu) err := mod.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index a01515b52..20ae2cdc0 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -14,6 +14,7 @@ import ( type multiPdfEngines struct { mergeEngines []gotenberg.PdfEngine splitEngines []gotenberg.PdfEngine + flattenEngines []gotenberg.PdfEngine convertEngines []gotenberg.PdfEngine readMetadataEngines []gotenberg.PdfEngine writeMetadataEngines []gotenberg.PdfEngine @@ -22,6 +23,7 @@ type multiPdfEngines struct { func newMultiPdfEngines( mergeEngines, splitEngines, + flattenEngines, convertEngines, readMetadataEngines, writeMetadataEngines []gotenberg.PdfEngine, @@ -29,6 +31,7 @@ func newMultiPdfEngines( return &multiPdfEngines{ mergeEngines: mergeEngines, splitEngines: splitEngines, + flattenEngines: flattenEngines, convertEngines: convertEngines, readMetadataEngines: readMetadataEngines, writeMetadataEngines: writeMetadataEngines, @@ -98,6 +101,32 @@ func (multi *multiPdfEngines) Split(ctx context.Context, logger *zap.Logger, mod return nil, fmt.Errorf("split PDF with multi PDF engines: %w", err) } +// Flatten merges existing annotation appearances with page content, effectively deleting the original annotations. +// This process can flatten forms as well, as forms share a relationship with annotations. +// Note that this operation is irreversible. +func (multi *multiPdfEngines) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + var err error + errChan := make(chan error, 1) + + for _, engine := range multi.flattenEngines { + go func(engine gotenberg.PdfEngine) { + errChan <- engine.Flatten(ctx, logger, inputPath) + }(engine) + + select { + case mergeErr := <-errChan: + errored := multierr.AppendInto(&err, mergeErr) + if !errored { + return nil + } + case <-ctx.Done(): + return ctx.Err() + } + } + + return fmt.Errorf("flatten PDF with multi PDF engines: %w", err) +} + // Convert converts the given PDF to a specific PDF format. thanks to its // children. If the context is done, it stops and returns an error. func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { diff --git a/pkg/modules/pdfengines/multi_test.go b/pkg/modules/pdfengines/multi_test.go index 7dddac05e..f5d5b2111 100644 --- a/pkg/modules/pdfengines/multi_test.go +++ b/pkg/modules/pdfengines/multi_test.go @@ -194,6 +194,97 @@ func TestMultiPdfEngines_Split(t *testing.T) { } } +func TestMultiPdfEngines_Flatten(t *testing.T) { + for _, tc := range []struct { + scenario string + engine *multiPdfEngines + ctx context.Context + expectError bool + }{ + { + scenario: "nominal behavior", + engine: &multiPdfEngines{ + flattenEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + }, + }, + ctx: context.Background(), + }, + { + scenario: "at least one engine does not return an error", + engine: &multiPdfEngines{ + flattenEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + }, + }, + ctx: context.Background(), + }, + { + scenario: "all engines return an error", + engine: &multiPdfEngines{ + flattenEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + }, + }, + ctx: context.Background(), + expectError: true, + }, + { + scenario: "context expired", + engine: &multiPdfEngines{ + flattenEngines: []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + }, + }, + ctx: func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + return ctx + }(), + expectError: true, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + err := tc.engine.Flatten(tc.ctx, zap.NewNop(), "") + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + }) + } +} + func TestMultiPdfEngines_Convert(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index 4536ff7b5..f029f0af6 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -29,6 +29,7 @@ func init() { type PdfEngines struct { mergeNames []string splitNames []string + flattenNames []string convertNames []string readMetadataNames []string writeMetadataNames []string @@ -44,6 +45,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor { fs := flag.NewFlagSet("pdfengines", flag.ExitOnError) fs.StringSlice("pdfengines-merge-engines", []string{"qpdf", "pdfcpu", "pdftk"}, "Set the PDF engines and their order for the merge feature - empty means all") fs.StringSlice("pdfengines-split-engines", []string{"pdfcpu", "qpdf", "pdftk"}, "Set the PDF engines and their order for the split feature - empty means all") + fs.StringSlice("pdfengines-flatten-engines", []string{"qpdf"}, "Set the PDF engines and their order for the flatten feature - empty means all") fs.StringSlice("pdfengines-convert-engines", []string{"libreoffice-pdfengine"}, "Set the PDF engines and their order for the convert feature - empty means all") fs.StringSlice("pdfengines-read-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the read metadata feature - empty means all") fs.StringSlice("pdfengines-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature - empty means all") @@ -67,6 +69,7 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { flags := ctx.ParsedFlags() mergeNames := flags.MustStringSlice("pdfengines-merge-engines") splitNames := flags.MustStringSlice("pdfengines-split-engines") + flattenNames := flags.MustStringSlice("pdfengines-flatten-engines") convertNames := flags.MustStringSlice("pdfengines-convert-engines") readMetadataNames := flags.MustStringSlice("pdfengines-read-metadata-engines") writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines") @@ -106,6 +109,11 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { mod.splitNames = splitNames } + mod.flattenNames = defaultNames + if len(flattenNames) > 0 { + mod.flattenNames = flattenNames + } + mod.convertNames = defaultNames if len(convertNames) > 0 { mod.convertNames = convertNames @@ -170,6 +178,7 @@ func (mod *PdfEngines) Validate() error { findNonExistingEngines(mod.mergeNames) findNonExistingEngines(mod.splitNames) + findNonExistingEngines(mod.flattenNames) findNonExistingEngines(mod.convertNames) findNonExistingEngines(mod.readMetadataNames) findNonExistingEngines(mod.writeMetadataNames) @@ -187,6 +196,7 @@ func (mod *PdfEngines) SystemMessages() []string { return []string{ fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")), fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), + fmt.Sprintf("flatten engines - %s", strings.Join(mod.flattenNames[:], " ")), fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")), @@ -212,6 +222,7 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) { return newMultiPdfEngines( engines(mod.mergeNames), engines(mod.splitNames), + engines(mod.flattenNames), engines(mod.convertNames), engines(mod.readMetadataNames), engines(mod.writeMetadataNames), @@ -234,6 +245,7 @@ func (mod *PdfEngines) Routes() ([]api.Route, error) { return []api.Route{ mergeRoute(engine), splitRoute(engine), + flattenRoute(engine), convertRoute(engine), readMetadataRoute(engine), writeMetadataRoute(engine), diff --git a/pkg/modules/pdfengines/pdfengines_test.go b/pkg/modules/pdfengines/pdfengines_test.go index 66546ebe4..b0beed3ea 100644 --- a/pkg/modules/pdfengines/pdfengines_test.go +++ b/pkg/modules/pdfengines/pdfengines_test.go @@ -27,6 +27,7 @@ func TestPdfEngines_Provision(t *testing.T) { ctx *gotenberg.Context expectedMergePdfEngines []string expectedSplitPdfEngines []string + expectedFlattenPdfEngines []string expectedConvertPdfEngines []string expectedReadMetadataPdfEngines []string expectedWriteMetadataPdfEngines []string @@ -68,6 +69,7 @@ func TestPdfEngines_Provision(t *testing.T) { }(), expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"}, expectedSplitPdfEngines: []string{"pdfcpu", "qpdf", "pdftk"}, + expectedFlattenPdfEngines: []string{"qpdf"}, expectedConvertPdfEngines: []string{"libreoffice-pdfengine"}, expectedReadMetadataPdfEngines: []string{"exiftool"}, expectedWriteMetadataPdfEngines: []string{"exiftool"}, @@ -109,7 +111,7 @@ func TestPdfEngines_Provision(t *testing.T) { } fs := new(PdfEngines).Descriptor().FlagSet - err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"}) + err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-flatten-engines=c", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"}) if err != nil { t.Fatalf("expected no error but got: %v", err) } @@ -128,6 +130,7 @@ func TestPdfEngines_Provision(t *testing.T) { expectedMergePdfEngines: []string{"b"}, expectedSplitPdfEngines: []string{"a"}, + expectedFlattenPdfEngines: []string{"c"}, expectedConvertPdfEngines: []string{"b"}, expectedReadMetadataPdfEngines: []string{"a"}, expectedWriteMetadataPdfEngines: []string{"a"}, @@ -185,6 +188,10 @@ func TestPdfEngines_Provision(t *testing.T) { t.Fatalf("expected %d merge names but got %d", len(tc.expectedMergePdfEngines), len(mod.mergeNames)) } + if len(tc.expectedFlattenPdfEngines) != len(mod.flattenNames) { + t.Fatalf("expected %d flatten names but got %d", len(tc.expectedFlattenPdfEngines), len(mod.flattenNames)) + } + if len(tc.expectedConvertPdfEngines) != len(mod.convertNames) { t.Fatalf("expected %d convert names but got %d", len(tc.expectedConvertPdfEngines), len(mod.convertNames)) } @@ -317,14 +324,16 @@ func TestPdfEngines_SystemMessages(t *testing.T) { mod.readMetadataNames = []string{"foo", "bar"} mod.writeMetadataNames = []string{"foo", "bar"} + expectedMessages := 6 messages := mod.SystemMessages() - if len(messages) != 5 { - t.Errorf("expected one and only one message, but got %d", len(messages)) + if len(messages) != expectedMessages { + t.Errorf("expected %d message(s), but got %d", expectedMessages, len(messages)) } expect := []string{ fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")), fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), + fmt.Sprintf("flatten engines - %s", strings.Join(mod.flattenNames[:], " ")), fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")), @@ -382,7 +391,7 @@ func TestPdfEngines_Routes(t *testing.T) { }{ { scenario: "routes not disabled", - expectRoutes: 5, + expectRoutes: 6, disableRoutes: false, }, { diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index d164bc77f..d90dd65e2 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -202,6 +202,21 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S return outputPaths, nil } +// FlattenStub merges annotation appearances with page content for each given PDF +// in the input paths, effectively deleting the original annotations. It generates +// new output paths for the flattened PDFs and returns them. If an error occurs +// during the flattening process, it returns the error. +func FlattenStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string) error { + for _, inputPath := range inputPaths { + err := engine.Flatten(ctx, ctx.Log(), inputPath) + if err != nil { + return fmt.Errorf("flatten '%s': %w", inputPath, err) + } + } + + return nil +} + // ConvertStub transforms a given PDF to the specified formats defined in // [gotenberg.PdfFormats]. If no format, it does nothing and returns the input // paths. @@ -255,8 +270,10 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { metadata := FormDataPdfMetadata(form, false) var inputPaths []string + var flatten bool err := form. MandatoryPaths([]string{".pdf"}, &inputPaths). + Bool("flatten", &flatten, false). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) @@ -278,6 +295,13 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("write metadata: %w", err) } + if flatten { + err = FlattenStub(ctx, engine, outputPaths) + if err != nil { + return fmt.Errorf("flatten PDFs: %w", err) + } + } + err = ctx.AddOutputPaths(outputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) @@ -347,6 +371,40 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route { } } +// flattenRoute returns an [api.Route] which can flatten PDFs. +func flattenRoute(engine gotenberg.PdfEngine) api.Route { + return api.Route{ + Method: http.MethodPost, + Path: "/forms/pdfengines/flatten", + IsMultipart: true, + Handler: func(c echo.Context) error { + ctx := c.Get("context").(*api.Context) + + form := ctx.FormData() + + var inputPaths []string + err := form. + MandatoryPaths([]string{".pdf"}, &inputPaths). + Validate() + if err != nil { + return fmt.Errorf("validate form data: %w", err) + } + + err = FlattenStub(ctx, engine, inputPaths) + if err != nil { + return fmt.Errorf("convert PDFs: %w", err) + } + + err = ctx.AddOutputPaths(inputPaths...) + if err != nil { + return fmt.Errorf("add output paths: %w", err) + } + + return nil + }, + } +} + // convertRoute returns an [api.Route] which can convert PDFs to a specific ODF // format. func convertRoute(engine gotenberg.PdfEngine) api.Route { diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go index a0b004fb4..405b5d053 100644 --- a/pkg/modules/pdfengines/routes_test.go +++ b/pkg/modules/pdfengines/routes_test.go @@ -719,6 +719,39 @@ func TestMergeHandler(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 0, }, + { + scenario: "PDF engine flatten error", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + "file2.pdf": "/file2.pdf", + }) + ctx.SetValues(map[string][]string{ + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, + "flatten": { + "true", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return nil + }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, { scenario: "cannot add output paths", ctx: func() *api.ContextMock { @@ -754,6 +787,9 @@ func TestMergeHandler(t *testing.T) { "metadata": { "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", }, + "flatten": { + "true", + }, }) return ctx }(), @@ -767,6 +803,9 @@ func TestMergeHandler(t *testing.T) { WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, }, expectError: false, expectHttpError: false, @@ -1055,6 +1094,147 @@ func TestSplitHandler(t *testing.T) { } } +func TestFlattenHandler(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *api.ContextMock + engine gotenberg.PdfEngine + expectError bool + expectHttpError bool + expectHttpStatus int + expectOutputPathsCount int + expectOutputPaths []string + }{ + { + scenario: "missing at least one mandatory file", + ctx: &api.ContextMock{Context: new(api.Context)}, + expectError: true, + expectHttpError: true, + expectHttpStatus: http.StatusBadRequest, + expectOutputPathsCount: 0, + }, + { + scenario: "error from PDF engine", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "cannot add output paths", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetCancelled(true) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "success with single file", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 1, + }, + { + scenario: "success (many files)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + "file2.pdf": "/file2.pdf", + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + expectError: false, + expectHttpError: false, + expectOutputPathsCount: 2, + expectOutputPaths: []string{"/file.pdf", "/file2.pdf"}, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.SetLogger(zap.NewNop()) + c := echo.New().NewContext(nil, nil) + c.Set("context", tc.ctx.Context) + + err := flattenRoute(tc.engine).Handler(c) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + var httpErr api.HttpError + isHttpError := errors.As(err, &httpErr) + + if tc.expectHttpError && !isHttpError { + t.Errorf("expected an HTTP error but got: %v", err) + } + + if !tc.expectHttpError && isHttpError { + t.Errorf("expected no HTTP error but got one: %v", httpErr) + } + + if err != nil && tc.expectHttpError && isHttpError { + status, _ := httpErr.HttpError() + if status != tc.expectHttpStatus { + t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) + } + } + + if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { + t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) + } + + for _, path := range tc.expectOutputPaths { + if !slices.Contains(tc.ctx.OutputPaths(), path) { + t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) + } + } + }) + } +} + func TestConvertHandler(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index c3f63d173..39094fa81 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -99,6 +99,11 @@ func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths [ return fmt.Errorf("merge PDFs with PDFtk: %w", err) } +// Flatten is not available in this implementation. +func (engine *PdfTk) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + return fmt.Errorf("flatten PDF with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // Convert is not available in this implementation. func (engine *PdfTk) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return fmt.Errorf("convert PDF to '%+v' with PDFtk: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/pdftk/pdftk_test.go b/pkg/modules/pdftk/pdftk_test.go index 73311f725..4ca0415ec 100644 --- a/pkg/modules/pdftk/pdftk_test.go +++ b/pkg/modules/pdftk/pdftk_test.go @@ -232,6 +232,15 @@ func TestPdfCpu_Split(t *testing.T) { } } +func TestPdfTk_Flatten(t *testing.T) { + engine := new(PdfTk) + err := engine.Flatten(context.TODO(), zap.NewNop(), "") + + if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { + t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) + } +} + func TestPdfTk_Convert(t *testing.T) { engine := new(PdfTk) err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 34785adef..760b65124 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -101,6 +101,27 @@ func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths [] return fmt.Errorf("merge PDFs with QPDF: %w", err) } +// Flatten merges annotation appearances with page content, deleting the original annotations. +func (engine *QPdf) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { + var args []string + args = append(args, "--generate-appearances") + args = append(args, "--flatten-annotations=all") + args = append(args, "--replace-input") + args = append(args, inputPath) + + cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) + if err != nil { + return fmt.Errorf("create command: %w", err) + } + + _, err = cmd.Exec() + if err == nil { + return nil + } + + return fmt.Errorf("flatten PDFs with QPDF: %w", err) +} + // Convert is not available in this implementation. func (engine *QPdf) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { return fmt.Errorf("convert PDF to '%+v' with QPDF: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/qpdf/qpdf_test.go b/pkg/modules/qpdf/qpdf_test.go index 9c79721b1..b7eed3920 100644 --- a/pkg/modules/qpdf/qpdf_test.go +++ b/pkg/modules/qpdf/qpdf_test.go @@ -3,6 +3,8 @@ package qpdf import ( "context" "errors" + "fmt" + "io" "os" "reflect" "testing" @@ -232,6 +234,101 @@ func TestQPdf_Split(t *testing.T) { } } +func TestQPdf_Flatten(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx context.Context + inputPath string + createCopy bool + expectError bool + }{ + { + scenario: "invalid context", + ctx: nil, + expectError: true, + }, + { + scenario: "invalid input path", + ctx: context.TODO(), + inputPath: "foo.pdf", + expectError: true, + }, + { + scenario: "success", + ctx: context.TODO(), + inputPath: "/tests/test/testdata/pdfengines/sample3.pdf", + createCopy: true, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + engine := new(QPdf) + err := engine.Provision(nil) + if err != nil { + t.Fatalf("expected error but got: %v", err) + } + + var destinationPath string + if tc.createCopy { + fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) + outputDir, err := fs.MkdirAll() + if err != nil { + t.Fatalf("expected error no but got: %v", err) + } + + defer func() { + err = os.RemoveAll(fs.WorkingDirPath()) + if err != nil { + t.Fatalf("expected no error while cleaning up but got: %v", err) + } + }() + + destinationPath = fmt.Sprintf("%s/copy_temp.pdf", outputDir) + source, err := os.Open(tc.inputPath) + if err != nil { + t.Fatalf("open source file: %v", err) + } + + defer func(source *os.File) { + err := source.Close() + if err != nil { + t.Fatalf("close file: %v", err) + } + }(source) + + destination, err := os.Create(destinationPath) + if err != nil { + t.Fatalf("create destination file: %v", err) + } + + defer func(destination *os.File) { + err := destination.Close() + if err != nil { + t.Fatalf("close file: %v", err) + } + }(destination) + + _, err = io.Copy(destination, source) + if err != nil { + t.Fatalf("copy source into destination: %v", err) + } + } else { + destinationPath = tc.inputPath + } + + err = engine.Flatten(tc.ctx, zap.NewNop(), destinationPath) + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + if tc.expectError && err == nil { + t.Fatal("expected error but got none") + } + }) + } +} + func TestQPdf_Convert(t *testing.T) { engine := new(QPdf) err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") diff --git a/test/testdata/pdfengines/sample3.pdf b/test/testdata/pdfengines/sample3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d079b89242260e3967ad2e2a43d6940e58e9fdc8 GIT binary patch literal 224175 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�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n 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%0A6FCOd%gjUpnyH_`i+2)^I0D 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 literal 0 HcmV?d00001 From 1d1b4fb04d6c18be449865e8ed29b52dccebfe99 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 28 Jan 2025 17:15:14 +0100 Subject: [PATCH 033/254] feat(pdfengines): add flatten form field to split route --- pkg/gotenberg/pdfengine.go | 7 ++- pkg/modules/pdfengines/multi.go | 5 +- pkg/modules/pdfengines/routes.go | 17 +++-- pkg/modules/pdfengines/routes_test.go | 91 ++++++++++++++++++++++++--- pkg/modules/qpdf/doc.go | 1 + pkg/modules/qpdf/qpdf.go | 3 +- 6 files changed, 102 insertions(+), 22 deletions(-) diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index dc4a4f7ce..02c55a418 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -98,9 +98,10 @@ type PdfEngine interface { // Split splits a given PDF file. Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) - // Flatten merges existing annotation appearances with page content, effectively deleting the original annotations. - // This process can flatten forms as well, as forms share a relationship with annotations. - // Note that this operation is irreversible. + // Flatten merges existing annotation appearances with page content, + // effectively deleting the original annotations. This process can flatten + // forms as well, as forms share a relationship with annotations. Note that + // this operation is irreversible. Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error // Convert transforms a given PDF to the specified formats defined in diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index 20ae2cdc0..b56ecef3e 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -101,9 +101,8 @@ func (multi *multiPdfEngines) Split(ctx context.Context, logger *zap.Logger, mod return nil, fmt.Errorf("split PDF with multi PDF engines: %w", err) } -// Flatten merges existing annotation appearances with page content, effectively deleting the original annotations. -// This process can flatten forms as well, as forms share a relationship with annotations. -// Note that this operation is irreversible. +// Flatten merges existing annotation appearances with page content, thanks to +// its children. If the context is done, it stops and returns an error func (multi *multiPdfEngines) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { var err error errChan := make(chan error, 1) diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index d90dd65e2..ac800a237 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -202,10 +202,8 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S return outputPaths, nil } -// FlattenStub merges annotation appearances with page content for each given PDF -// in the input paths, effectively deleting the original annotations. It generates -// new output paths for the flattened PDFs and returns them. If an error occurs -// during the flattening process, it returns the error. +// FlattenStub merges annotation appearances with page content for each given +// PDF, effectively deleting the original annotations. func FlattenStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string) error { for _, inputPath := range inputPaths { err := engine.Flatten(ctx, ctx.Log(), inputPath) @@ -327,8 +325,10 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route { metadata := FormDataPdfMetadata(form, false) var inputPaths []string + var flatten bool err := form. MandatoryPaths([]string{".pdf"}, &inputPaths). + Bool("flatten", &flatten, false). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) @@ -349,6 +349,13 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("write metadata: %w", err) } + if flatten { + err = FlattenStub(ctx, engine, convertOutputPaths) + if err != nil { + return fmt.Errorf("flatten PDFs: %w", err) + } + } + zeroValuedSplitMode := gotenberg.SplitMode{} zeroValuedPdfFormats := gotenberg.PdfFormats{} if mode != zeroValuedSplitMode && pdfFormats != zeroValuedPdfFormats { @@ -392,7 +399,7 @@ func flattenRoute(engine gotenberg.PdfEngine) api.Route { err = FlattenStub(ctx, engine, inputPaths) if err != nil { - return fmt.Errorf("convert PDFs: %w", err) + return fmt.Errorf("flatten PDFs: %w", err) } err = ctx.AddOutputPaths(inputPaths...) diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go index 405b5d053..c50d470e5 100644 --- a/pkg/modules/pdfengines/routes_test.go +++ b/pkg/modules/pdfengines/routes_test.go @@ -503,6 +503,45 @@ func TestSplitPdfStub(t *testing.T) { } } +func TestFlattenStub(t *testing.T) { + for _, tc := range []struct { + scenario string + engine gotenberg.PdfEngine + expectError bool + }{ + { + scenario: "flatten error", + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + }, + { + scenario: "flatten success", + engine: &gotenberg.PdfEngineMock{ + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, + }, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + err := FlattenStub(new(api.Context), tc.engine, []string{"my.pdf", "my2.pdf"}) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + }) + } +} + func TestConvertStub(t *testing.T) { for _, tc := range []struct { scenario string @@ -647,7 +686,7 @@ func TestMergeHandler(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "PDF engine merge error", + scenario: "error from PDF engine (merge)", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -666,7 +705,7 @@ func TestMergeHandler(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "PDF engine convert error", + scenario: "error from PDF engine (convert)", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -693,7 +732,7 @@ func TestMergeHandler(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "PDF engine write metadata error", + scenario: "error from PDF engine (write metadata)", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -720,7 +759,7 @@ func TestMergeHandler(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "PDF engine flatten error", + scenario: "error from PDF engine (flatten)", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -728,9 +767,6 @@ func TestMergeHandler(t *testing.T) { "file2.pdf": "/file2.pdf", }) ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, "flatten": { "true", }, @@ -741,9 +777,6 @@ func TestMergeHandler(t *testing.T) { MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return nil }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { return errors.New("foo") }, @@ -975,6 +1008,38 @@ func TestSplitHandler(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 0, }, + { + scenario: "error from PDF engine (flatten)", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + }) + ctx.SetValues(map[string][]string{ + "splitMode": { + gotenberg.SplitModeIntervals, + }, + "splitSpan": { + "1", + }, + "flatten": { + "true", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { + return []string{inputPath}, nil + }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, { scenario: "cannot add output paths", ctx: func() *api.ContextMock { @@ -1022,6 +1087,9 @@ func TestSplitHandler(t *testing.T) { "metadata": { "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", }, + "flatten": { + "true", + }, }) return ctx }(), @@ -1035,6 +1103,9 @@ func TestSplitHandler(t *testing.T) { WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, + FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { + return nil + }, }, expectError: false, expectHttpError: false, diff --git a/pkg/modules/qpdf/doc.go b/pkg/modules/qpdf/doc.go index f0d54a548..5f494a1f0 100644 --- a/pkg/modules/qpdf/doc.go +++ b/pkg/modules/qpdf/doc.go @@ -3,6 +3,7 @@ // // 1. The merging of PDF files. // 2. The splitting of PDF files. +// 3. Flattening of PDF files // // The path to the QPDF binary must be specified using the QPDK_BIN_PATH // environment variable. diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 760b65124..7271a2fd8 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -101,7 +101,8 @@ func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths [] return fmt.Errorf("merge PDFs with QPDF: %w", err) } -// Flatten merges annotation appearances with page content, deleting the original annotations. +// Flatten merges annotation appearances with page content, deleting the +// original annotations. func (engine *QPdf) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { var args []string args = append(args, "--generate-appearances") From 651922194ffffb12f01ca99330c0e46f809055da Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 29 Jan 2025 20:48:43 +0100 Subject: [PATCH 034/254] chore(deps): update Go dependencies --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index a8cb2abc6..a68ab0c53 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250113203156-3ff4b409e0d4 - github.com/chromedp/chromedp v0.11.2 + github.com/chromedp/cdproto v0.0.0-20250126231910-1730200a0f74 + github.com/chromedp/chromedp v0.12.1 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -23,7 +23,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/prometheus/client_golang v1.20.5 github.com/russross/blackfriday/v2 v2.1.0 - github.com/spf13/pflag v1.0.5 + github.com/spf13/pflag v1.0.6 github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 @@ -68,5 +68,5 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/time v0.9.0 // indirect - google.golang.org/protobuf v1.36.3 // indirect + google.golang.org/protobuf v1.36.4 // indirect ) diff --git a/go.sum b/go.sum index 6c8ffc22f..bff67030a 100644 --- a/go.sum +++ b/go.sum @@ -11,10 +11,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250113203156-3ff4b409e0d4 h1:xO38R20PvryeuBgQYnRU3WsNXFtr/iMyQVJednQVoZw= -github.com/chromedp/cdproto v0.0.0-20250113203156-3ff4b409e0d4/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM= -github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0= -github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8= +github.com/chromedp/cdproto v0.0.0-20250126231910-1730200a0f74 h1:oul0R+ZyorVxVNr7bALbPolKvbXlpIefB4lDv5sjt00= +github.com/chromedp/cdproto v0.0.0-20250126231910-1730200a0f74/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU= +github.com/chromedp/chromedp v0.12.1 h1:kBMblXk7xH5/6j3K9uk8d7/c+fzXWiUsCsPte0VMwOA= +github.com/chromedp/chromedp v0.12.1/go.mod h1:F6+wdq9LKFDMoyxhq46ZLz4VLXrsrCAR3sFqJz4Nqc0= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -110,8 +110,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -161,7 +161,7 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7afeefe84fd351c00ee6a77cce091a042b6a0b1a Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 31 Jan 2025 14:23:47 +0100 Subject: [PATCH 035/254] ci(actions): add local actions --- .github/actions/build-push/action.yml | 67 +++++ .github/actions/build-push/build-push.sh | 173 +++++++++++++ .github/actions/clean/action.yml | 31 +++ .github/actions/clean/clean.sh | 122 +++++++++ .github/actions/merge/action.yml | 48 ++++ .github/actions/merge/merge.sh | 109 ++++++++ .github/dependabot.yml | 3 +- .github/workflows/__build-push.yml | 51 ---- .github/workflows/__delete.yml | 28 --- .github/workflows/__merge-clean.yml | 40 --- .github/workflows/continuous-delivery.yml | 116 ++++++--- .github/workflows/continuous-integration.yml | 247 +++++++++++++------ .github/workflows/pull-request-cleanup.yml | 21 +- 13 files changed, 827 insertions(+), 229 deletions(-) create mode 100644 .github/actions/build-push/action.yml create mode 100755 .github/actions/build-push/build-push.sh create mode 100644 .github/actions/clean/action.yml create mode 100755 .github/actions/clean/clean.sh create mode 100644 .github/actions/merge/action.yml create mode 100755 .github/actions/merge/merge.sh delete mode 100644 .github/workflows/__build-push.yml delete mode 100644 .github/workflows/__delete.yml delete mode 100644 .github/workflows/__merge-clean.yml diff --git a/.github/actions/build-push/action.yml b/.github/actions/build-push/action.yml new file mode 100644 index 000000000..af94b0401 --- /dev/null +++ b/.github/actions/build-push/action.yml @@ -0,0 +1,67 @@ +name: Build and Push +description: Build and push Docker images +author: Julien Neuhart + +inputs: + github_token: + description: The GitHub token + required: true + default: ${{ github.token }} + docker_hub_username: + description: The Docker Hub username + required: true + docker_hub_password: + description: The Docker Hub password + required: true + platform: + description: linux/amd64, linux/386, linux/arm64, linux/arm/v7 + required: true + version: + description: Gotenberg version + required: true + alternate_repository: + description: Alternate repository to push the tags to + dry_run: + description: Dry run this action + +outputs: + tags: + description: Comma separated list of tag + value: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: + description: Comma separated list of Cloud Run tags (linux/amd64 only) + value: ${{ steps.build_push.outputs.tags_cloud_run }} + +runs: + using: composite + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Check out code + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ inputs.docker_hub_username }} + password: ${{ inputs.docker_hub_password }} + + - name: Build and push ${{ inputs.platform }} + id: build_push + shell: bash + run: | + .github/actions/build-push/build-push.sh \ + --version "${{ inputs.version }}" \ + --platform "${{ inputs.platform }}" \ + --alternate-repository "${{ inputs.alternate_repository }}" \ + --dry-run "${{ inputs.dry_run }}" + + - name: Outputs + shell: bash + run: | + echo "tags=${{ steps.build_push.outputs.tags }}" + echo "tags_cloud_run=${{ steps.build_push.outputs.tags_cloud_run }}" diff --git a/.github/actions/build-push/build-push.sh b/.github/actions/build-push/build-push.sh new file mode 100755 index 000000000..fd3e341fb --- /dev/null +++ b/.github/actions/build-push/build-push.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Exit early. +# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin. +set -e + +# Source dot env file. +source .env + +# Arguments. +version="" +platform="" +alternate_repository="" +dry_run="" + +while [[ $# -gt 0 ]]; do + case $1 in + --version) + version="${2//v}" + shift 2 + ;; + --platform) + platform="$2" + shift 2 + ;; + --alternate-repository) + alternate_repository="$2" + shift 2 + ;; + --dry-run) + dry_run="$2" + shift 2 + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac +done + +echo "Build and push 👷" +echo + +echo "Gotenberg version: $version" +echo "Target platform: $platform" + +if [ -n "$alternate_repository" ]; then + DOCKER_REPOSITORY=$alternate_repository + echo "⚠️ Using $alternate_repository for DOCKER_REPOSITORY" + fi + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" + fi + +# Build tags arrays. +tags=() +tags_cloud_run=() + +IFS='/' read -ra arch <<< "$platform" +IFS='.' read -ra semver <<< "$version" + +if [ "${#semver[@]}" -eq 3 ]; then + echo + echo "Semver version detected" + + major="${semver[0]}" + minor="${semver[1]}" + patch="${semver[2]}" + + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest-${arch[1]})") + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major-${arch[1]}") + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor-${arch[1]}r") + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor.$patch-${arch[1]}") + + if [ "$platform" = "linux/amd64" ]; then + tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest-cloudrun") + tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major-cloudrun") + tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor-cloudrun") + tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor.$patch-cloudrun") + fi +else + echo + echo "Non-semver version detected, fallback to $version" + + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-${arch[1]}") + if [ "$platform" = "linux/amd64" ]; then + tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-cloudrun") + fi +fi + +tags_flags=() +tags_cloud_run_flags=() + +echo "Will push the following tags:" +for tag in "${tags[@]}"; do + tags_flags+=("-t" "$tag") + echo "- $tag" +done +for tag in "${tags_cloud_run[@]}"; do + tags_cloud_run_flags+=("-t" "$tag") + echo "- $tag" +done +echo + +# Build and push images. +run_cmd() { + local cmd="$1" + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run - would run the following command:" + echo "$cmd" + echo + else + echo "⚙️ Running command:" + echo "$cmd" + eval "$cmd" + echo + fi +} + +join() { + local delimiter="$1" + shift + local IFS="$delimiter" + echo "$*" +} + +cmd="docker buildx build \ + --build-arg GOLANG_VERSION=$GOLANG_VERSION \ + --build-arg GOTENBERG_VERSION=$version \ + --build-arg GOTENBERG_USER_GID=$GOTENBERG_USER_GID \ + --build-arg GOTENBERG_USER_UID=$GOTENBERG_USER_UID \ + --build-arg NOTO_COLOR_EMOJI_VERSION=$NOTO_COLOR_EMOJI_VERSION \ + --build-arg PDFTK_VERSION=$PDFTK_VERSION \ + --build-arg PDFCPU_VERSION=$PDFCPU_VERSION \ + --push \ + --platform $platform \ + ${tags_flags[*]} \ + -f $DOCKERFILE $DOCKER_BUILD_CONTEXT +" +run_cmd "$cmd" + +if [ "$platform" != "linux/amd64" ]; then + echo "⚠️ Skip Cloud Run variant(s)" + echo "✅ Done!" + echo "tags=$(join "," "${tags[@]}")" >> "$GITHUB_OUTPUT" + echo "tags_cloud_run=$(join "," "${tags_cloud_run[@]}")" >> "$GITHUB_OUTPUT" + exit 0 +fi + +source_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-${arch[1]}" +cmd="docker pull $source_tag_cloud_run" +run_cmd "$cmd" + + target_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version" + cmd="docker image tag $source_tag_cloud_run $target_tag_cloud_run" + run_cmd "$cmd" + +cmd="docker build \ + --build-arg DOCKER_REGISTRY=$DOCKER_REGISTRY \ + --build-arg DOCKER_REPOSITORY=$DOCKER_REPOSITORY \ + --build-arg GOTENBERG_VERSION=$version \ + --push \ + ${tags_cloud_run_flags[*]} \ + -f $DOCKERFILE_CLOUDRUN $DOCKER_BUILD_CONTEXT +" +run_cmd "$cmd" + +echo "✅ Done!" +echo "tags=$(join "," "${tags[@]}")" >> "$GITHUB_OUTPUT" +echo "tags_cloud_run=$(join "," "${tags_cloud_run[@]}")" >> "$GITHUB_OUTPUT" +exit 0 diff --git a/.github/actions/clean/action.yml b/.github/actions/clean/action.yml new file mode 100644 index 000000000..4dae20bf4 --- /dev/null +++ b/.github/actions/clean/action.yml @@ -0,0 +1,31 @@ +name: Clean +description: Clean tags from Docker Hub +author: Julien Neuhart + +inputs: + docker_hub_username: + description: The Docker Hub username + required: true + docker_hub_password: + description: The Docker Hub password + required: true + tags: + description: Comma separated list of tags to clean + snapshot_version: + description: Snapshot version to clean + dry_run: + description: Dry run this action + +runs: + using: composite + steps: + - name: Clean tags from Docker Hub + env: + DOCKERHUB_USERNAME: ${{ inputs.docker_hub_username }} + DOCKERHUB_TOKEN: ${{ inputs.docker_hub_password }} + shell: bash + run: | + .github/actions/clean/clean.sh \ + --tags "${{ inputs.tags }}" \ + --snapshot-version "${{ inputs.snapshot_version }}" \ + --dry-run "${{ inputs.dry_run }}" diff --git a/.github/actions/clean/clean.sh b/.github/actions/clean/clean.sh new file mode 100755 index 000000000..4cfbc1dcd --- /dev/null +++ b/.github/actions/clean/clean.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +# Exit early. +# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin. +set -e + +# Source dot env file. +source .env + +# Arguments. +tags="" +snapshot_version="" +dry_run="" + +while [[ $# -gt 0 ]]; do + case $1 in + --tags) + tags="$2" + shift 2 + ;; + --snapshot-version) + snapshot_version="${2//v}" + shift 2 + ;; + --dry-run) + dry_run="$2" + shift 2 + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac +done + +echo "Clean tag(s) from Docker Hub 🧹" +echo + +IFS=',' read -ra tags_to_delete <<< "$tags" +if [ -n "$snapshot_version" ]; then + tags_to_delete+=("$DOCKER_REGISTRY/snapshot:$snapshot_version") + tags_to_delete+=("$DOCKER_REGISTRY/snapshot:$snapshot_version-cloudrun") +fi + +echo "Will delete the following tag(s):" +for tag in "${tags_to_delete[@]}"; do + echo "- $tag" +done + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" + fi +echo + +# Delete tags. +base_url="https://hub.docker.com/v2" +token="" + +if [ "$dry_run" = "true" ]; then + token="placeholder" + echo "🚧 Dry run - would call $base_url to get a token" + echo +else + echo "🌐 Get token from $base_url" + + readarray -t lines < <( + curl -s -X POST \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$DOCKERHUB_USERNAME\", \"password\":\"$DOCKERHUB_TOKEN\"}" \ + -w "\n%{http_code}" \ + "$base_url/users/login" + ) + + http_code="${lines[-1]}" + unset 'lines[-1]' + json_body=$(printf "%s\n" "${lines[@]}") + + if [ "$http_code" -ne "200" ]; then + echo "❌ Wrong HTTP status - $http_code" + echo "$json_body" + exit 1 + fi + + token=$(jq -r '.token' <<< "$json_body") + echo +fi + +if [ -z "$token" ]; then + echo "❌ No token from Docker Hub" + exit 1 +fi + +for tag in "${tags_to_delete[@]}"; do + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run - would call $base_url to delete tag $tag" + echo + else + echo "🌐 Delete tag $tag" + IFS=':' read -ra tag_parts <<< "$tag" + + readarray -t lines < <( + curl -s -X DELETE \ + -H "Authorization: Bearer $token" \ + -w "\n%{http_code}" \ + "$base_url/repositories/${tag_parts[0]}/tags/${tag_parts[1]}/" + ) + + http_code="${lines[-1]}" + unset 'lines[-1]' + + if [ "$http_code" -ne "200" ] && [ "$http_code" -ne "204" ]; then + echo "❌ Wrong HTTP status - $http_code" + printf '%s\n' "${lines[@]}" + exit 1 + fi + + echo + fi +done + +echo "✅ Done!" +exit 0 diff --git a/.github/actions/merge/action.yml b/.github/actions/merge/action.yml new file mode 100644 index 000000000..941b37fdf --- /dev/null +++ b/.github/actions/merge/action.yml @@ -0,0 +1,48 @@ +name: Merge +description: Merge tags to single multi-platform tags +author: Julien Neuhart + +inputs: + github_token: + description: The GitHub token + required: true + default: ${{ github.token }} + docker_hub_username: + description: The Docker Hub username + required: true + docker_hub_password: + description: The Docker Hub password + required: true + tags: + description: Comma separated tags to merge + required: true + alternate_registry: + description: Alternate registry to also push resulting tags + dry_run: + description: Dry run this action + +runs: + using: composite + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Check out code + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ inputs.docker_hub_username }} + password: ${{ inputs.docker_hub_password }} + + - name: Merge + shell: bash + run: | + .github/actions/merge/merge.sh \ + --tags "${{ inputs.tags }}" \ + --alternate-registry "${{ inputs.alternate_registry }}" \ + --dry-run "${{ inputs.dry_run }}" diff --git a/.github/actions/merge/merge.sh b/.github/actions/merge/merge.sh new file mode 100755 index 000000000..c6bdfd4dc --- /dev/null +++ b/.github/actions/merge/merge.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Exit early. +# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin. +set -e + +# Source dot env file. +source .env + +# Arguments. +tags="" +alternate_registry="" +dry_run="" + +while [[ $# -gt 0 ]]; do + case $1 in + --tags) + tags="$2" + shift 2 + ;; + --alternate-registry) + alternate_registry="$2" + shift 2 + ;; + --dry-run) + dry_run=$2 + shift 2 + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac +done + +echo "Merge tag(s) 👷" +echo + +echo "Tag(s) to merge:" +IFS=',' read -ra tags_to_merge <<< "$tags" +for tag in "${tags_to_merge[@]}"; do + echo "- $tag" +done + +if [ -n "$alternate_registry" ]; then + echo "⚠️ Will also push to $alternate_registry registry" +fi + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" + fi + + echo + + # Build merge map. + declare -A merge_map + + for tag in "${tags_to_merge[@]}"; do + target_tag="${tag//-amd64/}" + target_tag="${target_tag//-arm64/}" + target_tag="${target_tag//-arm/}" + target_tag="${target_tag//-386/}" + + merge_map["$target_tag"]+="$tag " + done + +# Merge tags. +run_cmd() { + local cmd="$1" + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run - would run the following command:" + echo "$cmd" + echo + else + echo "⚙️ Running command:" + echo "$cmd" + eval "$cmd" + echo + fi +} + + for target in "${!merge_map[@]}"; do + IFS=' ' read -ra source_tags <<< "${merge_map[$target]}" + + cmd="docker buildx imagetools create \ + -t $target \ + ${source_tags[*]} + " + run_cmd "$cmd" + + echo "➡️ $target pushed" + echo + if [ -n "$alternate_registry" ]; then + alternate_target="${target//$DOCKER_REGISTRY/$alternate_registry}" + cmd="docker buildx imagetools create \ + -t $target \ + $alternate_target + " + run_cmd "$cmd" + + echo "➡️ $alternate_target pushed" + ecno + fi + done + + +echo "✅ Done!" +exit 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2b9f8833a..3bad94f94 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,7 @@ version: 2 updates: - # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/__build-push.yml b/.github/workflows/__build-push.yml deleted file mode 100644 index 301ae0587..000000000 --- a/.github/workflows/__build-push.yml +++ /dev/null @@ -1,51 +0,0 @@ -on: - workflow_call: - inputs: - runs_on: - type: string - required: true - platform: - type: string - required: true - gotenberg_version: - type: string - required: true - alternate_repository: - type: string - default: '' - outputs: - tags: - value: ${{ jobs.build_push.outputs.tags }} - -permissions: - contents: read - -jobs: - build_push: - name: Build and push Docker images - runs-on: ${{ inputs.runs_on }} - outputs: - tags: ${{ steps.build_push.outputs.tags }} - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Check out code - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push ${{ inputs.platform }} - id: build_push - uses: gotenberg/gotenberg-release-action/actions/build-push@main - with: - version: ${{ inputs.gotenberg_version }} - platform: ${{ inputs.platform }} - alternate_repository: ${{ inputs.alternate_repository }} diff --git a/.github/workflows/__delete.yml b/.github/workflows/__delete.yml deleted file mode 100644 index 96d2156e9..000000000 --- a/.github/workflows/__delete.yml +++ /dev/null @@ -1,28 +0,0 @@ -on: - workflow_call: - inputs: - gotenberg_version: - type: string - required: true - alternate_repository: - type: string - default: '' - -permissions: - contents: read - -jobs: - delete: - name: Delete Docker images - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - - - name: Delete - uses: gotenberg/gotenberg-release-action/actions/delete@main - with: - docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: ${{ inputs.gotenberg_version }} - alternate_repository: ${{ inputs.alternate_repository }} diff --git a/.github/workflows/__merge-clean.yml b/.github/workflows/__merge-clean.yml deleted file mode 100644 index baf484447..000000000 --- a/.github/workflows/__merge-clean.yml +++ /dev/null @@ -1,40 +0,0 @@ -on: - workflow_call: - inputs: - tags: - type: string - required: true - alternate_registry: - type: string - default: '' - -permissions: - contents: read - -jobs: - merge_clean: - name: Merge and clean Docker images - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Check out code - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Merge and clean - uses: gotenberg/gotenberg-release-action/actions/merge-clean@main - with: - docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - tags: ${{ inputs.tags }} - alternate_registry: ${{ inputs.alternate_registry }} diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index e2d8c402a..74e151f9f 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -2,7 +2,7 @@ name: Continuous Delivery on: release: - types: [ published ] + types: [published] permissions: contents: read @@ -10,39 +10,81 @@ permissions: jobs: release_amd64: name: Release linux/amd64 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-latest - platform: linux/amd64 - gotenberg_version: ${{ github.event.release.tag_name }} + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: ${{ github.event.release.tag_name }} + platform: linux/amd64 release_386: name: Release linux/386 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-latest - platform: linux/386 - gotenberg_version: ${{ github.event.release.tag_name }} + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: ${{ github.event.release.tag_name }} + platform: linux/386 release_arm64: name: Release linux/arm64 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-24.04-arm - platform: linux/arm64 - gotenberg_version: ${{ github.event.release.tag_name }} + runs-on: ubuntu-24.04-arm + outputs: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: ${{ github.event.release.tag_name }} + platform: linux/arm64 release_arm_v7: name: Release linux/arm/v7 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-24.04-arm - platform: linux/arm/v7 - gotenberg_version: ${{ github.event.release.tag_name }} + runs-on: ubuntu-24.04-arm + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: ${{ github.event.release.tag_name }} + platform: linux/arm/v7 merge_clean_release_tags: needs: @@ -51,8 +93,22 @@ jobs: - release_arm64 - release_arm_v7 name: Merge and clean release tags - uses: ./.github/workflows/__merge-clean.yml - secrets: inherit - with: - tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }}" - alternate_registry: thecodingmachine + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Merge + uses: ./.github/actions/merge + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }}" + alternate_registry: thecodingmachine + + - name: Clean + uses: ./.github/actions/clean + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }}" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f65d9d7b0..02f7b5ad6 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,7 +16,6 @@ permissions: contents: write jobs: - lint: name: Lint runs-on: ubuntu-latest @@ -24,7 +23,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: "1.23" cache: false - name: Checkout source code @@ -65,54 +64,94 @@ jobs: snapshot_amd64: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/amd64 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-latest - platform: linux/amd64 - gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: snapshot + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: pr-${{ github.event.pull_request.number }} + platform: linux/amd64 + alternate_repository: snapshot snapshot_386: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/386 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-latest - platform: linux/386 - gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: snapshot + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: pr-${{ github.event.pull_request.number }} + platform: linux/386 + alternate_repository: snapshot snapshot_arm64: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/arm64 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-24.04-arm - platform: linux/arm64 - gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: snapshot + runs-on: ubuntu-24.04-arm + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: pr-${{ github.event.pull_request.number }} + platform: linux/arm64 + alternate_repository: snapshot snapshot_arm_v7: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/arm/v7 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-24.04-arm - platform: linux/arm/v7 - gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: snapshot + runs-on: ubuntu-24.04-arm + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: pr-${{ github.event.pull_request.number }} + platform: linux/arm/v7 + alternate_repository: snapshot merge_clean_snapshot_tags: needs: @@ -121,58 +160,112 @@ jobs: - snapshot_arm64 - snapshot_arm_v7 name: Merge and clean snapshot tags - uses: ./.github/workflows/__merge-clean.yml - secrets: inherit - with: - tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }}" + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Merge + uses: ./.github/actions/merge + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }}" + + - name: Clean + uses: ./.github/actions/clean + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }}" edge_amd64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - tests name: Edge linux/amd64 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-latest - platform: linux/amd64 - gotenberg_version: edge + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: edge + platform: linux/amd64 edge_386: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - tests name: Edge linux/386 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-latest - platform: linux/386 - gotenberg_version: edge + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: edge + platform: linux/386 edge_arm64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - tests name: Edge linux/arm64 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-24.04-arm - platform: linux/arm64 - gotenberg_version: edge + runs-on: ubuntu-24.04-arm + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: edge + platform: linux/arm64 edge_arm_v7: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - tests name: Edge linux/arm/v7 - uses: ./.github/workflows/__build-push.yml - secrets: inherit - with: - runs_on: ubuntu-24.04-arm - platform: linux/arm/v7 - gotenberg_version: edge + runs-on: ubuntu-24.04-arm + outputs: + tags: ${{ steps.build_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Build and push + id: build_push + uses: ./.github/actions/build-push + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + version: edge + platform: linux/arm/v7 merge_clean_edge_tags: needs: @@ -181,8 +274,22 @@ jobs: - edge_arm64 - edge_arm_v7 name: Merge and clean edge tags - uses: ./.github/workflows/__merge-clean.yml - secrets: inherit - with: - tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }}" - alternate_registry: thecodingmachine + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Merge + uses: ./.github/actions/merge + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }}" + alternate_registry: thecodingmachine + + - name: Clean + uses: ./.github/actions/clean + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }}" diff --git a/.github/workflows/pull-request-cleanup.yml b/.github/workflows/pull-request-cleanup.yml index 1815466d7..dad5e1c33 100644 --- a/.github/workflows/pull-request-cleanup.yml +++ b/.github/workflows/pull-request-cleanup.yml @@ -2,17 +2,22 @@ name: Pull Request Cleanup on: pull_request: - types: [ closed ] + types: [closed] permissions: contents: read jobs: - cleanup: - name: Cleanup - uses: ./.github/workflows/__delete.yml - secrets: inherit - with: - gotenberg_version: pr-${{ github.event.pull_request.number }} - alternate_repository: snapshot + name: Cleanup Docker images + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Cleanup + uses: ./.github/actions/clean + with: + docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} + snapshot_version: pr-${{ github.event.pull_request.number }} From 27ecb4fe49d64b03180a1ca8e5a664134bc876d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:40:30 +0000 Subject: [PATCH 036/254] chore(deps): bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 02f7b5ad6..e1ab68b67 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -56,7 +56,7 @@ jobs: run: make tests-once - name: Upload to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true From 85c7f2806f0787a01238b3a81c5ad0ccb2c64cdb Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 31 Jan 2025 15:38:14 +0100 Subject: [PATCH 037/254] ci(merge): fix alternate registry target image --- .github/actions/merge/merge.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/merge/merge.sh b/.github/actions/merge/merge.sh index c6bdfd4dc..0b648f494 100755 --- a/.github/actions/merge/merge.sh +++ b/.github/actions/merge/merge.sh @@ -92,7 +92,7 @@ run_cmd() { echo "➡️ $target pushed" echo if [ -n "$alternate_registry" ]; then - alternate_target="${target//$DOCKER_REGISTRY/$alternate_registry}" + alternate_target="${target//$DOCKER_REGISTRY:$DOCKER_REPOSITORY/$alternate_registry:$DOCKER_REPOSITORY}" cmd="docker buildx imagetools create \ -t $target \ $alternate_target From 229330018296802a7dfb4e8513af1f3ad79ce885 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 31 Jan 2025 15:58:06 +0100 Subject: [PATCH 038/254] ci(merge): fix alternate registry target image #2 --- .github/actions/merge/merge.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/merge/merge.sh b/.github/actions/merge/merge.sh index 0b648f494..1317ddcff 100755 --- a/.github/actions/merge/merge.sh +++ b/.github/actions/merge/merge.sh @@ -92,7 +92,7 @@ run_cmd() { echo "➡️ $target pushed" echo if [ -n "$alternate_registry" ]; then - alternate_target="${target//$DOCKER_REGISTRY:$DOCKER_REPOSITORY/$alternate_registry:$DOCKER_REPOSITORY}" + alternate_target="${target/$DOCKER_REGISTRY/$alternate_registry}" cmd="docker buildx imagetools create \ -t $target \ $alternate_target @@ -100,7 +100,7 @@ run_cmd() { run_cmd "$cmd" echo "➡️ $alternate_target pushed" - ecno + echo fi done From 71d571a25c4f7e80e68b01b01007511c46309a57 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 3 Feb 2025 11:36:05 +0100 Subject: [PATCH 039/254] fix(Makefile): wrong arg for --pdfengines-convert-engines --- Makefile | 2 +- pkg/gotenberg/{version.go => debug.go} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pkg/gotenberg/{version.go => debug.go} (100%) diff --git a/Makefile b/Makefile index 0a9b212bf..f7f401166 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,7 @@ run: ## Start a Gotenberg container --pdfengines-engines=$(PDFENGINES_ENGINES) \ --pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \ --pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \ - --pdfengines-convert-engines=$(PDFENGINES_FLATTEN_ENGINES) \ + --pdfengines-flatten-engines=$(PDFENGINES_FLATTEN_ENGINES) \ --pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \ --pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \ --pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \ diff --git a/pkg/gotenberg/version.go b/pkg/gotenberg/debug.go similarity index 100% rename from pkg/gotenberg/version.go rename to pkg/gotenberg/debug.go From 740e701ad3fd190d1996875d0f71c5a3d233e6ed Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 3 Feb 2025 11:53:51 +0100 Subject: [PATCH 040/254] chore(deps): update Go dependencies --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a68ab0c53..72cbffae8 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250126231910-1730200a0f74 + github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730 github.com/chromedp/chromedp v0.12.1 github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 @@ -37,7 +37,7 @@ require ( require ( github.com/dlclark/regexp2 v1.11.4 - github.com/shirou/gopsutil/v4 v4.24.12 + github.com/shirou/gopsutil/v4 v4.25.1 ) require ( diff --git a/go.sum b/go.sum index bff67030a..d3f60a0a7 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250126231910-1730200a0f74 h1:oul0R+ZyorVxVNr7bALbPolKvbXlpIefB4lDv5sjt00= -github.com/chromedp/cdproto v0.0.0-20250126231910-1730200a0f74/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU= +github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730 h1:IEa+Va47x06CJQaLKFoce5iPTRRR5uI/GbeZbxdnYdc= +github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU= github.com/chromedp/chromedp v0.12.1 h1:kBMblXk7xH5/6j3K9uk8d7/c+fzXWiUsCsPte0VMwOA= github.com/chromedp/chromedp v0.12.1/go.mod h1:F6+wdq9LKFDMoyxhq46ZLz4VLXrsrCAR3sFqJz4Nqc0= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -108,8 +108,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= -github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= +github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= +github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From a1596f7c62935e0161dba827dcc0fa2ecf72cb54 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 4 Feb 2025 10:20:29 +0100 Subject: [PATCH 041/254] feat(api): add debug route --- Makefile | 2 + cmd/gotenberg.go | 3 ++ pkg/gotenberg/debug.go | 65 +++++++++++++++++++++- pkg/gotenberg/debug_test.go | 72 +++++++++++++++++++++++++ pkg/gotenberg/mocks.go | 8 +++ pkg/gotenberg/mocks_test.go | 15 ++++++ pkg/gotenberg/modules.go | 6 +++ pkg/gotenberg/version.go | 4 ++ pkg/modules/api/api.go | 19 ++++++- pkg/modules/api/api_test.go | 10 ++++ pkg/modules/chromium/chromium.go | 21 ++++++++ pkg/modules/chromium/chromium_test.go | 48 +++++++++++++++++ pkg/modules/exiftool/exiftool.go | 21 ++++++++ pkg/modules/exiftool/exiftool_test.go | 44 +++++++++++++++ pkg/modules/libreoffice/api/api.go | 21 ++++++++ pkg/modules/libreoffice/api/api_test.go | 48 +++++++++++++++++ pkg/modules/pdfcpu/pdfcpu.go | 30 +++++++++++ pkg/modules/pdfcpu/pdfcpu_test.go | 53 ++++++++++++++++++ pkg/modules/pdftk/pdftk.go | 27 ++++++++++ pkg/modules/pdftk/pdftk_test.go | 44 +++++++++++++++ pkg/modules/qpdf/qpdf.go | 27 ++++++++++ pkg/modules/qpdf/qpdf_test.go | 44 +++++++++++++++ 22 files changed, 628 insertions(+), 4 deletions(-) create mode 100644 pkg/gotenberg/debug_test.go create mode 100644 pkg/gotenberg/version.go diff --git a/Makefile b/Makefile index f7f401166..72741812d 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ API-DOWNLOAD-FROM-DENY-LIST= API-DOWNLOAD-FROM-FROM-MAX-RETRY=4 API-DISABLE-DOWNLOAD-FROM=false API_DISABLE_HEALTH_CHECK_LOGGING=false +API_ENABLE_DEBUG_ROUTE=false CHROMIUM_RESTART_AFTER=10 CHROMIUM_MAX_QUEUE_SIZE=0 CHROMIUM_AUTO_START=false @@ -107,6 +108,7 @@ run: ## Start a Gotenberg container --api-download-from-max-retry=$(API-DOWNLOAD-FROM-FROM-MAX-RETRY) \ --api-disable-download-from=$(API-DISABLE-DOWNLOAD-FROM) \ --api-disable-health-check-logging=$(API_DISABLE_HEALTH_CHECK_LOGGING) \ + --api-enable-debug-route=$(API_ENABLE_DEBUG_ROUTE) \ --chromium-restart-after=$(CHROMIUM_RESTART_AFTER) \ --chromium-auto-start=$(CHROMIUM_AUTO_START) \ --chromium-max-queue-size=$(CHROMIUM_MAX_QUEUE_SIZE) \ diff --git a/cmd/gotenberg.go b/cmd/gotenberg.go index 7c3d37da7..69aa381b8 100644 --- a/cmd/gotenberg.go +++ b/cmd/gotenberg.go @@ -108,6 +108,9 @@ func Run() { }(l.(gotenberg.SystemLogger)) } + // Build the debug data. + gotenberg.BuildDebug(ctx) + quit := make(chan os.Signal, 1) // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) or SIGTERM (Kubernetes). diff --git a/pkg/gotenberg/debug.go b/pkg/gotenberg/debug.go index 36940b8ee..a89a9b0af 100644 --- a/pkg/gotenberg/debug.go +++ b/pkg/gotenberg/debug.go @@ -1,4 +1,65 @@ package gotenberg -// Version is the... version of the Gotenberg application. -var Version = "snapshot" +import ( + "runtime" + "sync" + + flag "github.com/spf13/pflag" +) + +// DebugInfo gathers data for debugging. +type DebugInfo struct { + Version string `json:"version"` + Architecture string `json:"architecture"` + Modules []string `json:"modules"` + ModulesAdditionalData map[string]map[string]interface{} `json:"modules_additional_data"` + Flags map[string]interface{} `json:"flags"` +} + +// BuildDebug builds the debug data from modules. +func BuildDebug(ctx *Context) { + debugMu.Lock() + defer debugMu.Unlock() + + debug = &DebugInfo{ + Version: Version, + Architecture: runtime.GOARCH, + Modules: make([]string, len(ctx.moduleInstances)), + ModulesAdditionalData: make(map[string]map[string]interface{}), + Flags: make(map[string]interface{}), + } + + i := 0 + for ID, mod := range ctx.moduleInstances { + debug.Modules[i] = ID + i++ + + debuggable, ok := mod.(Debuggable) + if !ok { + continue + } + + debug.ModulesAdditionalData[ID] = debuggable.Debug() + } + + ctx.ParsedFlags().VisitAll(func(f *flag.Flag) { + debug.Flags[f.Name] = f.Value.String() + }) +} + +// Debug returns the debug data. +func Debug() DebugInfo { + debugMu.Lock() + defer debugMu.Unlock() + + if debug == nil { + return DebugInfo{} + } + + return *debug +} + +var ( + debug *DebugInfo + debugMu sync.Mutex +) diff --git a/pkg/gotenberg/debug_test.go b/pkg/gotenberg/debug_test.go new file mode 100644 index 000000000..604060842 --- /dev/null +++ b/pkg/gotenberg/debug_test.go @@ -0,0 +1,72 @@ +package gotenberg + +import ( + "reflect" + "runtime" + "testing" + + flag "github.com/spf13/pflag" +) + +func TestBuildDebug(t *testing.T) { + if !reflect.DeepEqual(Debug(), DebugInfo{}) { + t.Errorf("Debug() should return empty debug data") + } + + fs := flag.NewFlagSet("gotenberg", flag.ExitOnError) + fs.String("foo", "bar", "Set foo") + ctx := NewContext(ParsedFlags{ + FlagSet: fs, + }, func() []ModuleDescriptor { + mod1 := &struct { + ModuleMock + }{} + mod1.DescriptorMock = func() ModuleDescriptor { + return ModuleDescriptor{ID: "foo", New: func() Module { return mod1 }} + } + mod2 := &struct { + ModuleMock + DebuggableMock + }{} + mod2.DescriptorMock = func() ModuleDescriptor { + return ModuleDescriptor{ID: "bar", New: func() Module { return mod2 }} + } + mod2.DebugMock = func() map[string]interface{} { + return map[string]interface{}{ + "foo": "bar", + } + } + + return []ModuleDescriptor{mod1.Descriptor(), mod2.Descriptor()} + }()) + + // Load modules. + _, err := ctx.Modules(new(Module)) + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + + // Build debug data. + BuildDebug(ctx) + + expect := DebugInfo{ + Version: Version, + Architecture: runtime.GOARCH, + Modules: []string{ + "foo", + "bar", + }, + ModulesAdditionalData: map[string]map[string]interface{}{ + "bar": { + "foo": "bar", + }, + }, + Flags: map[string]interface{}{ + "foo": "bar", + }, + } + + if !reflect.DeepEqual(expect, Debug()) { + t.Errorf("expected '%+v', bug got '%+v'", expect, Debug()) + } +} diff --git a/pkg/gotenberg/mocks.go b/pkg/gotenberg/mocks.go index a771c8c3d..458216f91 100644 --- a/pkg/gotenberg/mocks.go +++ b/pkg/gotenberg/mocks.go @@ -34,6 +34,14 @@ func (mod *ValidatorMock) Validate() error { return mod.ValidateMock() } +type DebuggableMock struct { + DebugMock func() map[string]interface{} +} + +func (mod *DebuggableMock) Debug() map[string]interface{} { + return mod.DebugMock() +} + // PdfEngineMock is a mock for the [PdfEngine] interface. // //nolint:dupl diff --git a/pkg/gotenberg/mocks_test.go b/pkg/gotenberg/mocks_test.go index 0608f2c2e..d813499af 100644 --- a/pkg/gotenberg/mocks_test.go +++ b/pkg/gotenberg/mocks_test.go @@ -48,6 +48,21 @@ func TestValidatorMock(t *testing.T) { } } +func TestDebuggableMock(t *testing.T) { + mock := &DebuggableMock{ + DebugMock: func() map[string]interface{} { + return map[string]interface{}{ + "foo": "bar", + } + }, + } + + d := mock.Debug() + if d == nil { + t.Errorf("expected debug data, but got nil") + } +} + func TestPDFEngineMock(t *testing.T) { mock := &PdfEngineMock{ MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { diff --git a/pkg/gotenberg/modules.go b/pkg/gotenberg/modules.go index e6ee875ce..3df314595 100644 --- a/pkg/gotenberg/modules.go +++ b/pkg/gotenberg/modules.go @@ -75,6 +75,12 @@ type SystemLogger interface { SystemMessages() []string } +// Debuggable is a module interface for modules which want to provide +// additional debug data. +type Debuggable interface { + Debug() map[string]interface{} +} + // MustRegisterModule registers a module. // // To register a module, create an init() method in the module main go file: diff --git a/pkg/gotenberg/version.go b/pkg/gotenberg/version.go new file mode 100644 index 000000000..36940b8ee --- /dev/null +++ b/pkg/gotenberg/version.go @@ -0,0 +1,4 @@ +package gotenberg + +// Version is the... version of the Gotenberg application. +var Version = "snapshot" diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index e7aaf492c..b31d0ebd4 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -42,6 +42,7 @@ type Api struct { basicAuthPassword string downloadFromCfg downloadFromConfig disableHealthCheckLogging bool + enableDebugRoute bool routes []Route externalMiddlewares []Middleware @@ -187,6 +188,7 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor { fs.Int("api-download-from-max-retry", 4, "Set the maximum number of retries for the download from feature") fs.Bool("api-disable-download-from", false, "Disable the download from feature") fs.Bool("api-disable-health-check-logging", false, "Disable health check logging") + fs.Bool("api-enable-debug-route", false, "Enable the debug route") return fs }(), New: func() gotenberg.Module { return new(Api) }, @@ -212,6 +214,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error { disable: flags.MustBool("api-disable-download-from"), } a.disableHealthCheckLogging = flags.MustBool("api-disable-health-check-logging") + a.enableDebugRoute = flags.MustBool("api-enable-debug-route") // Port from env? portEnvVar := flags.MustString("api-port-from-env") @@ -365,9 +368,10 @@ func (a *Api) Validate() error { return err } - routesMap := make(map[string]string, len(a.routes)+2) + routesMap := make(map[string]string, len(a.routes)+3) routesMap["/health"] = "/health" routesMap["/version"] = "/version" + routesMap["/debug"] = "/debug" for _, route := range a.routes { if route.Path == "" { @@ -529,7 +533,7 @@ func (a *Api) Start() error { hardTimeoutMiddleware(hardTimeout), ) - // ...and the version route. + // ...the version route. a.srv.GET( fmt.Sprintf("%s%s", a.rootPath, "version"), func(c echo.Context) error { @@ -538,6 +542,17 @@ func (a *Api) Start() error { securityMiddleware, ) + // ...and the debug route. + if a.enableDebugRoute { + a.srv.GET( + fmt.Sprintf("%s%s", a.rootPath, "debug"), + func(c echo.Context) error { + return c.JSONPretty(http.StatusOK, gotenberg.Debug(), " ") + }, + securityMiddleware, + ) + } + // Wait for all modules to be ready. ctx, cancel := context.WithTimeout(context.Background(), a.startTimeout) defer cancel() diff --git a/pkg/modules/api/api_test.go b/pkg/modules/api/api_test.go index cd5c3e3a2..465adce38 100644 --- a/pkg/modules/api/api_test.go +++ b/pkg/modules/api/api_test.go @@ -786,6 +786,7 @@ func TestApi_Start(t *testing.T) { mod.basicAuthUsername = "foo" mod.basicAuthPassword = "bar" mod.disableHealthCheckLogging = true + mod.enableDebugRoute = true mod.routes = []Route{ { Method: http.MethodPost, @@ -908,6 +909,15 @@ func TestApi_Start(t *testing.T) { t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) } + // debug request. + recorder = httptest.NewRecorder() + debugRequest := httptest.NewRequest(http.MethodGet, "/debug", nil) + debugRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) + mod.srv.ServeHTTP(recorder, debugRequest) + if recorder.Code != http.StatusOK { + t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) + } + // "multipart/form-data" request. multipartRequest := func(url string) *http.Request { body := &bytes.Buffer{} diff --git a/pkg/modules/chromium/chromium.go b/pkg/modules/chromium/chromium.go index 7dfa8fa04..b356f7d94 100644 --- a/pkg/modules/chromium/chromium.go +++ b/pkg/modules/chromium/chromium.go @@ -5,6 +5,9 @@ import ( "errors" "fmt" "os" + "os/exec" + "strings" + "syscall" "time" "github.com/alexliesenfeld/health" @@ -484,6 +487,23 @@ func (mod *Chromium) Stop(ctx context.Context) error { return fmt.Errorf("stop Chromium: %w", err) } +// Debug returns additional debug data. +func (mod *Chromium) Debug() map[string]interface{} { + debug := make(map[string]interface{}) + + cmd := exec.Command(mod.args.binPath, "--version") //nolint:gosec + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + output, err := cmd.Output() + if err != nil { + debug["version"] = err.Error() + return debug + } + + debug["version"] = strings.TrimSpace(string(output)) + return debug +} + // Metrics returns the metrics. func (mod *Chromium) Metrics() ([]gotenberg.Metric, error) { return []gotenberg.Metric{ @@ -593,6 +613,7 @@ var ( _ gotenberg.Provisioner = (*Chromium)(nil) _ gotenberg.Validator = (*Chromium)(nil) _ gotenberg.App = (*Chromium)(nil) + _ gotenberg.Debuggable = (*Chromium)(nil) _ gotenberg.MetricsProvider = (*Chromium)(nil) _ api.HealthChecker = (*Chromium)(nil) _ api.Router = (*Chromium)(nil) diff --git a/pkg/modules/chromium/chromium_test.go b/pkg/modules/chromium/chromium_test.go index 38f28c1ef..720bc9433 100644 --- a/pkg/modules/chromium/chromium_test.go +++ b/pkg/modules/chromium/chromium_test.go @@ -336,6 +336,54 @@ func TestChromium_Stop(t *testing.T) { } } +func TestChromium_Debug(t *testing.T) { + for _, tc := range []struct { + scenario string + mod *Chromium + expect map[string]interface{} + doNotExpect map[string]interface{} + }{ + { + scenario: "cannot determine version", + mod: &Chromium{ + args: browserArguments{ + binPath: "foo", + }, + }, + expect: map[string]interface{}{ + "version": `exec: "foo": executable file not found in $PATH`, + }, + }, + { + scenario: "success", + mod: &Chromium{ + args: browserArguments{ + binPath: "echo", + }, + }, + doNotExpect: map[string]interface{}{ + "version": `exec: "echo": executable file not found in $PATH`, + }, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + d := tc.mod.Debug() + + if tc.expect != nil { + if !reflect.DeepEqual(d, tc.expect) { + t.Errorf("expected '%v' but got '%v'", tc.expect, d) + } + } + + if tc.doNotExpect != nil { + if reflect.DeepEqual(d, tc.doNotExpect) { + t.Errorf("did not expect '%v'", d) + } + } + }) + } +} + func TestChromium_Metrics(t *testing.T) { mod := new(Chromium) mod.supervisor = &gotenberg.ProcessSupervisorMock{ diff --git a/pkg/modules/exiftool/exiftool.go b/pkg/modules/exiftool/exiftool.go index 8fafc27d7..f1e07cdf1 100644 --- a/pkg/modules/exiftool/exiftool.go +++ b/pkg/modules/exiftool/exiftool.go @@ -5,7 +5,10 @@ import ( "errors" "fmt" "os" + "os/exec" "reflect" + "strings" + "syscall" "github.com/barasher/go-exiftool" "go.uber.org/zap" @@ -53,6 +56,23 @@ func (engine *ExifTool) Validate() error { return nil } +// Debug returns additional debug data. +func (engine *ExifTool) Debug() map[string]interface{} { + debug := make(map[string]interface{}) + + cmd := exec.Command(engine.binPath, "-ver") //nolint:gosec + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + output, err := cmd.Output() + if err != nil { + debug["version"] = err.Error() + return debug + } + + debug["version"] = strings.TrimSpace(string(output)) + return debug +} + // Merge is not available in this implementation. func (engine *ExifTool) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported) @@ -161,5 +181,6 @@ var ( _ gotenberg.Module = (*ExifTool)(nil) _ gotenberg.Provisioner = (*ExifTool)(nil) _ gotenberg.Validator = (*ExifTool)(nil) + _ gotenberg.Debuggable = (*ExifTool)(nil) _ gotenberg.PdfEngine = (*ExifTool)(nil) ) diff --git a/pkg/modules/exiftool/exiftool_test.go b/pkg/modules/exiftool/exiftool_test.go index 00fca5a2b..87576b697 100644 --- a/pkg/modules/exiftool/exiftool_test.go +++ b/pkg/modules/exiftool/exiftool_test.go @@ -73,6 +73,50 @@ func TestExifTool_Validate(t *testing.T) { } } +func TestExifTool_Debug(t *testing.T) { + for _, tc := range []struct { + scenario string + engine *ExifTool + expect map[string]interface{} + doNotExpect map[string]interface{} + }{ + { + scenario: "cannot determine version", + engine: &ExifTool{ + binPath: "foo", + }, + expect: map[string]interface{}{ + "version": `exec: "foo": executable file not found in $PATH`, + }, + }, + { + scenario: "success", + engine: &ExifTool{ + binPath: "echo", + }, + doNotExpect: map[string]interface{}{ + "version": `exec: "echo": executable file not found in $PATH`, + }, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + d := tc.engine.Debug() + + if tc.expect != nil { + if !reflect.DeepEqual(d, tc.expect) { + t.Errorf("expected '%v' but got '%v'", tc.expect, d) + } + } + + if tc.doNotExpect != nil { + if reflect.DeepEqual(d, tc.doNotExpect) { + t.Errorf("did not expect '%v'", d) + } + } + }) + } +} + func TestExiftool_Merge(t *testing.T) { engine := new(ExifTool) err := engine.Merge(context.Background(), zap.NewNop(), nil, "") diff --git a/pkg/modules/libreoffice/api/api.go b/pkg/modules/libreoffice/api/api.go index e32712cff..ca8b482d5 100644 --- a/pkg/modules/libreoffice/api/api.go +++ b/pkg/modules/libreoffice/api/api.go @@ -5,6 +5,9 @@ import ( "errors" "fmt" "os" + "os/exec" + "strings" + "syscall" "time" "github.com/alexliesenfeld/health" @@ -305,6 +308,23 @@ func (a *Api) Stop(ctx context.Context) error { return fmt.Errorf("stop LibreOffice: %w", err) } +// Debug returns additional debug data. +func (a *Api) Debug() map[string]interface{} { + debug := make(map[string]interface{}) + + cmd := exec.Command(a.args.binPath, "--version") //nolint:gosec + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + output, err := cmd.Output() + if err != nil { + debug["version"] = err.Error() + return debug + } + + debug["version"] = strings.TrimSpace(string(output)) + return debug +} + // Metrics returns the metrics. func (a *Api) Metrics() ([]gotenberg.Metric, error) { return []gotenberg.Metric{ @@ -536,6 +556,7 @@ var ( _ gotenberg.Provisioner = (*Api)(nil) _ gotenberg.Validator = (*Api)(nil) _ gotenberg.App = (*Api)(nil) + _ gotenberg.Debuggable = (*Api)(nil) _ gotenberg.MetricsProvider = (*Api)(nil) _ api.HealthChecker = (*Api)(nil) _ Uno = (*Api)(nil) diff --git a/pkg/modules/libreoffice/api/api_test.go b/pkg/modules/libreoffice/api/api_test.go index b9480b852..7df16d9ca 100644 --- a/pkg/modules/libreoffice/api/api_test.go +++ b/pkg/modules/libreoffice/api/api_test.go @@ -277,6 +277,54 @@ func TestApi_Stop(t *testing.T) { } } +func TestPdfTk_Debug(t *testing.T) { + for _, tc := range []struct { + scenario string + a *Api + expect map[string]interface{} + doNotExpect map[string]interface{} + }{ + { + scenario: "cannot determine version", + a: &Api{ + args: libreOfficeArguments{ + binPath: "foo", + }, + }, + expect: map[string]interface{}{ + "version": `exec: "foo": executable file not found in $PATH`, + }, + }, + { + scenario: "success", + a: &Api{ + args: libreOfficeArguments{ + binPath: "echo", + }, + }, + doNotExpect: map[string]interface{}{ + "version": `exec: "echo": executable file not found in $PATH`, + }, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + d := tc.a.Debug() + + if tc.expect != nil { + if !reflect.DeepEqual(d, tc.expect) { + t.Errorf("expected '%v' but got '%v'", tc.expect, d) + } + } + + if tc.doNotExpect != nil { + if reflect.DeepEqual(d, tc.doNotExpect) { + t.Errorf("did not expect '%v'", d) + } + } + }) + } +} + func TestApi_Metrics(t *testing.T) { a := new(Api) a.supervisor = &gotenberg.ProcessSupervisorMock{ diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index 2e82f8793..4a468c1aa 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -5,7 +5,10 @@ import ( "errors" "fmt" "os" + "os/exec" "path/filepath" + "strings" + "syscall" "go.uber.org/zap" @@ -52,6 +55,32 @@ func (engine *PdfCpu) Validate() error { return nil } +// Debug returns additional debug data. +func (engine *PdfCpu) Debug() map[string]interface{} { + debug := make(map[string]interface{}) + + cmd := exec.Command(engine.binPath, "version") //nolint:gosec + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + output, err := cmd.Output() + if err != nil { + debug["version"] = err.Error() + return debug + } + + debug["version"] = "Unable to determine pdfcpu version" + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "pdfcpu:") { + debug["version"] = strings.TrimSpace(strings.TrimPrefix(line, "pdfcpu:")) + break + } + } + + return debug +} + // Merge combines multiple PDFs into a single PDF. func (engine *PdfCpu) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { var args []string @@ -132,5 +161,6 @@ var ( _ gotenberg.Module = (*PdfCpu)(nil) _ gotenberg.Provisioner = (*PdfCpu)(nil) _ gotenberg.Validator = (*PdfCpu)(nil) + _ gotenberg.Debuggable = (*PdfCpu)(nil) _ gotenberg.PdfEngine = (*PdfCpu)(nil) ) diff --git a/pkg/modules/pdfcpu/pdfcpu_test.go b/pkg/modules/pdfcpu/pdfcpu_test.go index 063f42e6f..b44003c32 100644 --- a/pkg/modules/pdfcpu/pdfcpu_test.go +++ b/pkg/modules/pdfcpu/pdfcpu_test.go @@ -71,6 +71,59 @@ func TestPdfCpu_Validate(t *testing.T) { } } +func TestPdfCpu_Debug(t *testing.T) { + for _, tc := range []struct { + scenario string + engine *PdfCpu + expect map[string]interface{} + doNotExpect map[string]interface{} + }{ + { + scenario: "cannot determine version (command error)", + engine: &PdfCpu{ + binPath: "foo", + }, + expect: map[string]interface{}{ + "version": `exec: "foo": executable file not found in $PATH`, + }, + }, + { + scenario: "cannot determine version (no pdfcpu)", + engine: &PdfCpu{ + binPath: "echo", + }, + expect: map[string]interface{}{ + "version": "Unable to determine pdfcpu version", + }, + }, + { + scenario: "success", + engine: &PdfCpu{ + binPath: "pdfcpu", + }, + doNotExpect: map[string]interface{}{ + "version": "Unable to determine pdfcpu version", + }, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + d := tc.engine.Debug() + + if tc.expect != nil { + if !reflect.DeepEqual(d, tc.expect) { + t.Errorf("expected '%v' but got '%v'", tc.expect, d) + } + } + + if tc.doNotExpect != nil { + if reflect.DeepEqual(d, tc.doNotExpect) { + t.Errorf("did not expect '%v'", d) + } + } + }) + } +} + func TestPdfCpu_Merge(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index 39094fa81..d43951739 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -1,11 +1,14 @@ package pdftk import ( + "bytes" "context" "errors" "fmt" "os" + "os/exec" "path/filepath" + "syscall" "go.uber.org/zap" @@ -52,6 +55,29 @@ func (engine *PdfTk) Validate() error { return nil } +// Debug returns additional debug data. +func (engine *PdfTk) Debug() map[string]interface{} { + debug := make(map[string]interface{}) + + cmd := exec.Command(engine.binPath, "--version") //nolint:gosec + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + output, err := cmd.Output() + if err != nil { + debug["version"] = err.Error() + return debug + } + + lines := bytes.SplitN(output, []byte("\n"), 2) + if len(lines) > 0 { + debug["version"] = string(lines[0]) + } else { + debug["version"] = "Unable to determine PDFtk version" + } + + return debug +} + // Split splits a given PDF file. func (engine *PdfTk) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { var args []string @@ -124,5 +150,6 @@ var ( _ gotenberg.Module = (*PdfTk)(nil) _ gotenberg.Provisioner = (*PdfTk)(nil) _ gotenberg.Validator = (*PdfTk)(nil) + _ gotenberg.Debuggable = (*PdfTk)(nil) _ gotenberg.PdfEngine = (*PdfTk)(nil) ) diff --git a/pkg/modules/pdftk/pdftk_test.go b/pkg/modules/pdftk/pdftk_test.go index 4ca0415ec..0a49aeda4 100644 --- a/pkg/modules/pdftk/pdftk_test.go +++ b/pkg/modules/pdftk/pdftk_test.go @@ -71,6 +71,50 @@ func TestPdfTk_Validate(t *testing.T) { } } +func TestPdfTk_Debug(t *testing.T) { + for _, tc := range []struct { + scenario string + engine *PdfTk + expect map[string]interface{} + doNotExpect map[string]interface{} + }{ + { + scenario: "cannot determine version", + engine: &PdfTk{ + binPath: "foo", + }, + expect: map[string]interface{}{ + "version": `exec: "foo": executable file not found in $PATH`, + }, + }, + { + scenario: "success", + engine: &PdfTk{ + binPath: "echo", + }, + doNotExpect: map[string]interface{}{ + "version": `exec: "echo": executable file not found in $PATH`, + }, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + d := tc.engine.Debug() + + if tc.expect != nil { + if !reflect.DeepEqual(d, tc.expect) { + t.Errorf("expected '%v' but got '%v'", tc.expect, d) + } + } + + if tc.doNotExpect != nil { + if reflect.DeepEqual(d, tc.doNotExpect) { + t.Errorf("did not expect '%v'", d) + } + } + }) + } +} + func TestPdfTk_Merge(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 7271a2fd8..95294002a 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -1,11 +1,14 @@ package qpdf import ( + "bytes" "context" "errors" "fmt" "os" + "os/exec" "path/filepath" + "syscall" "go.uber.org/zap" @@ -52,6 +55,29 @@ func (engine *QPdf) Validate() error { return nil } +// Debug returns additional debug data. +func (engine *QPdf) Debug() map[string]interface{} { + debug := make(map[string]interface{}) + + cmd := exec.Command(engine.binPath, "--version") //nolint:gosec + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + output, err := cmd.Output() + if err != nil { + debug["version"] = err.Error() + return debug + } + + lines := bytes.SplitN(output, []byte("\n"), 2) + if len(lines) > 0 { + debug["version"] = string(lines[0]) + } else { + debug["version"] = "Unable to determine QPDF version" + } + + return debug +} + // Split splits a given PDF file. func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { var args []string @@ -142,5 +168,6 @@ var ( _ gotenberg.Module = (*QPdf)(nil) _ gotenberg.Provisioner = (*QPdf)(nil) _ gotenberg.Validator = (*QPdf)(nil) + _ gotenberg.Debuggable = (*QPdf)(nil) _ gotenberg.PdfEngine = (*QPdf)(nil) ) diff --git a/pkg/modules/qpdf/qpdf_test.go b/pkg/modules/qpdf/qpdf_test.go index b7eed3920..539724292 100644 --- a/pkg/modules/qpdf/qpdf_test.go +++ b/pkg/modules/qpdf/qpdf_test.go @@ -73,6 +73,50 @@ func TestQPdf_Validate(t *testing.T) { } } +func TestQPdf_Debug(t *testing.T) { + for _, tc := range []struct { + scenario string + engine *QPdf + expect map[string]interface{} + doNotExpect map[string]interface{} + }{ + { + scenario: "cannot determine version", + engine: &QPdf{ + binPath: "foo", + }, + expect: map[string]interface{}{ + "version": `exec: "foo": executable file not found in $PATH`, + }, + }, + { + scenario: "success", + engine: &QPdf{ + binPath: "echo", + }, + doNotExpect: map[string]interface{}{ + "version": `exec: "echo": executable file not found in $PATH`, + }, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + d := tc.engine.Debug() + + if tc.expect != nil { + if !reflect.DeepEqual(d, tc.expect) { + t.Errorf("expected '%v' but got '%v'", tc.expect, d) + } + } + + if tc.doNotExpect != nil { + if reflect.DeepEqual(d, tc.doNotExpect) { + t.Errorf("did not expect '%v'", d) + } + } + }) + } +} + func TestQPdf_Merge(t *testing.T) { for _, tc := range []struct { scenario string From a1848d3c7530f7089508b38411415f34d1b7766e Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 5 Feb 2025 09:51:38 +0100 Subject: [PATCH 042/254] feat(conf): override flags' values with their corresponding environment variables' values --- cmd/gotenberg.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/cmd/gotenberg.go b/cmd/gotenberg.go index 69aa381b8..942e9aa55 100644 --- a/cmd/gotenberg.go +++ b/cmd/gotenberg.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/signal" + "strings" "syscall" "time" @@ -50,14 +51,42 @@ func Run() { fmt.Printf("[SYSTEM] modules: %s\n", modsInfo) - // Parse the flags... + // Parse the flags. err := fs.Parse(os.Args[1:]) if err != nil { fmt.Println(err) os.Exit(1) } - // ...and create a wrapper around those. + // Override their values if the corresponding environment variables are + // set. + fs.VisitAll(func(f *flag.Flag) { + envName := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")) + val, ok := os.LookupEnv(envName) + if !ok { + return + } + + sliceVal, ok := f.Value.(flag.SliceValue) + if ok { + // We don't want to append the values (default pflag behavior). + items := strings.Split(val, ",") + err = sliceVal.Replace(items) + if err != nil { + fmt.Printf("[FATAL] invalid overriding value '%s' from %s: %v\n", val, envName, err) + os.Exit(1) + } + return + } + + err = f.Value.Set(val) + if err != nil { + fmt.Printf("[FATAL] invalid overriding value '%s' from %s: %v\n", val, envName, err) + os.Exit(1) + } + }) + + // Create a wrapper around our flags. parsedFlags := gotenberg.ParsedFlags{FlagSet: fs} // Get the graceful shutdown duration. From 6b0c01bd3e853b5d3610d1362bb0be8536db6ee8 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 5 Feb 2025 09:51:58 +0100 Subject: [PATCH 043/254] chore(deps): update Go dependencies --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 72cbffae8..08d830217 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,10 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/crypto v0.32.0 // indirect golang.org/x/net v0.34.0 - golang.org/x/sync v0.10.0 - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 - golang.org/x/text v0.21.0 + golang.org/x/sync v0.11.0 + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 + golang.org/x/text v0.22.0 ) require ( @@ -67,6 +67,6 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/time v0.9.0 // indirect + golang.org/x/time v0.10.0 // indirect google.golang.org/protobuf v1.36.4 // indirect ) diff --git a/go.sum b/go.sum index d3f60a0a7..e4b889307 100644 --- a/go.sum +++ b/go.sum @@ -146,20 +146,20 @@ golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= From 198d9115ccc53cda4e827c11e9e966bd7541fa9a Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 5 Feb 2025 17:00:48 +0100 Subject: [PATCH 044/254] chore(deps): switch to github.com/mholt/archives --- go.mod | 18 ++- go.sum | 280 ++++++++++++++++++++++++++++++++++--- pkg/modules/api/context.go | 31 ++-- 3 files changed, 295 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 08d830217..adae65418 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/barasher/go-exiftool v1.10.0 github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730 github.com/chromedp/chromedp v0.12.1 - github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 @@ -17,9 +16,7 @@ require ( github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/nwaples/rardecode v1.1.3 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/prometheus/client_golang v1.20.5 github.com/russross/blackfriday/v2 v2.1.0 @@ -37,36 +34,47 @@ require ( require ( github.com/dlclark/regexp2 v1.11.4 + github.com/mholt/archives v0.1.0 github.com/shirou/gopsutil/v4 v4.25.1 ) require ( + github.com/STARRY-S/zip v0.2.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bodgit/plumbing v1.3.0 // indirect + github.com/bodgit/sevenzip v1.6.0 // indirect + github.com/bodgit/windows v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect - github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect github.com/gorilla/css v1.0.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nwaples/rardecode/v2 v2.1.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/sorairolake/lzip-go v0.3.5 // indirect + github.com/therootcompany/xz v1.0.1 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/time v0.10.0 // indirect google.golang.org/protobuf v1.36.4 // indirect ) diff --git a/go.sum b/go.sum index e4b889307..5b5c99d28 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,26 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= +github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= github.com/alexliesenfeld/health v0.8.0 h1:lCV0i+ZJPTbqP7LfKG7p3qZBl5VhelwUFCIVWl77fgk= github.com/alexliesenfeld/health v0.8.0/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= -github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -9,6 +29,13 @@ github.com/barasher/go-exiftool v1.10.0 h1:f5JY5jc42M7tzR6tbL9508S2IXdIcG9QyieEX github.com/barasher/go-exiftool v1.10.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= +github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= +github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A= +github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= +github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730 h1:IEa+Va47x06CJQaLKFoce5iPTRRR5uI/GbeZbxdnYdc= @@ -17,18 +44,26 @@ github.com/chromedp/chromedp v0.12.1 h1:kBMblXk7xH5/6j3K9uk8d7/c+fzXWiUsCsPte0VM github.com/chromedp/chromedp v0.12.1/go.mod h1:F6+wdq9LKFDMoyxhq46ZLz4VLXrsrCAR3sFqJz4Nqc0= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -38,32 +73,68 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= @@ -80,18 +151,16 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= -github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q= +github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= -github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= +github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= -github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -100,68 +169,245 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= +github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= +github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/modules/api/context.go b/pkg/modules/api/context.go index d761248b2..7af15d0f0 100644 --- a/pkg/modules/api/context.go +++ b/pkg/modules/api/context.go @@ -1,7 +1,6 @@ package api import ( - "compress/flate" "context" "encoding/json" "errors" @@ -19,7 +18,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/go-retryablehttp" "github.com/labstack/echo/v4" - "github.com/mholt/archiver/v3" + "github.com/mholt/archives" "go.uber.org/zap" "golang.org/x/sync/errgroup" "golang.org/x/text/unicode/norm" @@ -468,22 +467,30 @@ func (ctx *Context) BuildOutputFile() (string, error) { if len(ctx.outputPaths) == 1 { ctx.logger.Debug(fmt.Sprintf("only one output file '%s', skip archive creation", ctx.outputPaths[0])) - return ctx.outputPaths[0], nil } - z := archiver.Zip{ - CompressionLevel: flate.DefaultCompression, - MkdirAll: true, - SelectiveCompression: true, - ContinueOnError: false, - OverwriteExisting: false, - ImplicitTopLevelFolder: false, - } + filesInfo, err := archives.FilesFromDisk(ctx.Context, nil, func() map[string]string { + f := make(map[string]string) + for _, outputPath := range ctx.outputPaths { + f[outputPath] = "" + } + return f + }()) archivePath := ctx.GeneratePath(".zip") + out, err := os.Create(archivePath) + if err != nil { + return "", fmt.Errorf("create zip file: %w", err) + } + defer func(out *os.File) { + err := out.Close() + if err != nil { + ctx.logger.Error(fmt.Sprintf("close zip file: %s", err)) + } + }(out) - err := z.Archive(ctx.outputPaths, archivePath) + err = archives.Zip{}.Archive(ctx.Context, out, filesInfo) if err != nil { return "", fmt.Errorf("archive output files: %w", err) } From f0c1952dfe47ce347cf1ccb1b80f54fafd102141 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 6 Feb 2025 08:51:38 +0100 Subject: [PATCH 045/254] ci(lint): fix missing error handling --- pkg/modules/api/context.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/modules/api/context.go b/pkg/modules/api/context.go index 7af15d0f0..37c3a3707 100644 --- a/pkg/modules/api/context.go +++ b/pkg/modules/api/context.go @@ -477,6 +477,9 @@ func (ctx *Context) BuildOutputFile() (string, error) { } return f }()) + if err != nil { + return "", fmt.Errorf("create files info: %w", err) + } archivePath := ctx.GeneratePath(".zip") out, err := os.Create(archivePath) From 08daa867666cefb7ebc603a67ad6ee1d4c72f6e0 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 6 Feb 2025 09:07:09 +0100 Subject: [PATCH 046/254] test(api): add missing context --- pkg/modules/api/context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/modules/api/context_test.go b/pkg/modules/api/context_test.go index 05649df8a..b69bc0a58 100644 --- a/pkg/modules/api/context_test.go +++ b/pkg/modules/api/context_test.go @@ -783,6 +783,7 @@ func TestContext_BuildOutputFile(t *testing.T) { }() tc.ctx.dirPath = dirPath + tc.ctx.Context = context.Background() tc.ctx.logger = zap.NewNop() _, err = tc.ctx.BuildOutputFile() From d5aa880336c5900106ea9e73022adec5c00f9dac Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 7 Feb 2025 08:41:04 +0100 Subject: [PATCH 047/254] ci(build-push): fix typo in tag name --- .github/actions/build-push/build-push.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-push/build-push.sh b/.github/actions/build-push/build-push.sh index fd3e341fb..de239a3f4 100755 --- a/.github/actions/build-push/build-push.sh +++ b/.github/actions/build-push/build-push.sh @@ -68,7 +68,7 @@ if [ "${#semver[@]}" -eq 3 ]; then minor="${semver[1]}" patch="${semver[2]}" - tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest-${arch[1]})") + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest-${arch[1]}") tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major-${arch[1]}") tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor-${arch[1]}r") tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor.$patch-${arch[1]}") From 679715f896d3acc6ae082f79f13f91812cb3f654 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 7 Feb 2025 08:43:44 +0100 Subject: [PATCH 048/254] chore(deps): update Go dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index adae65418..8a7b527fe 100644 --- a/go.mod +++ b/go.mod @@ -76,5 +76,5 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/time v0.10.0 // indirect - google.golang.org/protobuf v1.36.4 // indirect + google.golang.org/protobuf v1.36.5 // indirect ) diff --git a/go.sum b/go.sum index 5b5c99d28..b71c6c8fe 100644 --- a/go.sum +++ b/go.sum @@ -394,8 +394,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= From 7676d7a2396f3dd400ea057bff3912957a938bc9 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 7 Feb 2025 09:47:47 +0100 Subject: [PATCH 049/254] ci(build-push): fix typo in tag --- .github/actions/build-push/build-push.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-push/build-push.sh b/.github/actions/build-push/build-push.sh index de239a3f4..2aba17564 100755 --- a/.github/actions/build-push/build-push.sh +++ b/.github/actions/build-push/build-push.sh @@ -70,7 +70,7 @@ if [ "${#semver[@]}" -eq 3 ]; then tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:latest-${arch[1]}") tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major-${arch[1]}") - tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor-${arch[1]}r") + tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor-${arch[1]}") tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$major.$minor.$patch-${arch[1]}") if [ "$platform" = "linux/amd64" ]; then From 0a6a97cf07e65088f3d80d2c0c520c1faf1dcb8e Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 7 Feb 2025 09:59:08 +0100 Subject: [PATCH 050/254] ci(merge): fix thecodingmachine target --- .github/actions/merge/merge.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/merge/merge.sh b/.github/actions/merge/merge.sh index 1317ddcff..50ef2fa2d 100755 --- a/.github/actions/merge/merge.sh +++ b/.github/actions/merge/merge.sh @@ -94,8 +94,8 @@ run_cmd() { if [ -n "$alternate_registry" ]; then alternate_target="${target/$DOCKER_REGISTRY/$alternate_registry}" cmd="docker buildx imagetools create \ - -t $target \ - $alternate_target + -t $alternate_target \ + $target " run_cmd "$cmd" From 3dc8c188495cd045594338a2ba3e1e752223bb17 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 11 Feb 2025 17:00:18 +0100 Subject: [PATCH 051/254] fix(fs): get file by ext sorted by mod time --- pkg/gotenberg/fs.go | 29 ++++++++++++++++++++++++++--- pkg/gotenberg/fs_test.go | 20 +++++++++++++++----- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/pkg/gotenberg/fs.go b/pkg/gotenberg/fs.go index 99b403eb0..d1e14a7d4 100644 --- a/pkg/gotenberg/fs.go +++ b/pkg/gotenberg/fs.go @@ -4,7 +4,9 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" + "time" "github.com/google/uuid" ) @@ -86,10 +88,15 @@ func (fs *FileSystem) MkdirAll() (string, error) { return path, nil } +type fileWithModTime struct { + path string + modTime time.Time +} + // WalkDir walks through the root level of a directory and returns a list of // files paths that match the specified file extension. func WalkDir(dir, ext string) ([]string, error) { - var files []string + var files []fileWithModTime err := filepath.Walk(dir, func(path string, info os.FileInfo, pathErr error) error { if pathErr != nil { return pathErr @@ -98,11 +105,27 @@ func WalkDir(dir, ext string) ([]string, error) { return nil } if strings.EqualFold(filepath.Ext(info.Name()), ext) { - files = append(files, path) + files = append(files, fileWithModTime{ + path: path, + modTime: info.ModTime(), + }) } return nil }) - return files, err + if err != nil { + return nil, err + } + + sort.Slice(files, func(i, j int) bool { + return files[i].modTime.Before(files[j].modTime) + }) + + sortedPaths := make([]string, len(files)) + for i, f := range files { + sortedPaths[i] = f.path + } + + return sortedPaths, nil } // Interface guards. diff --git a/pkg/gotenberg/fs_test.go b/pkg/gotenberg/fs_test.go index d7f641204..f6c15a352 100644 --- a/pkg/gotenberg/fs_test.go +++ b/pkg/gotenberg/fs_test.go @@ -161,7 +161,7 @@ func TestWalkDir(t *testing.T) { expectError: true, }, { - scenario: "find PDF files", + scenario: "find PDF files, sorted by mod time", dir: func() string { path := fmt.Sprintf("%s/a_directory", os.TempDir()) @@ -170,17 +170,27 @@ func TestWalkDir(t *testing.T) { t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) } - err = os.WriteFile(fmt.Sprintf("%s/a_foo_file.pdf", path), []byte{1}, 0o755) + err = os.WriteFile(fmt.Sprintf("%s/2.pdf", path), []byte{1}, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } - err = os.WriteFile(fmt.Sprintf("%s/a_bar_file.PDF", path), []byte{1}, 0o755) + err = os.WriteFile(fmt.Sprintf("%s/1.PDF", path), []byte{1}, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } - err = os.WriteFile(fmt.Sprintf("%s/a_baz_file.txt", path), []byte{1}, 0o755) + err = os.WriteFile(fmt.Sprintf("%s/3.txt", path), []byte{1}, 0o755) + if err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + err = os.WriteFile(fmt.Sprintf("%s/10.pdf", path), []byte{1}, 0o755) + if err != nil { + t.Fatalf("expected no error but got: %v", err) + } + + err = os.WriteFile(fmt.Sprintf("%s/11.pdf", path), []byte{1}, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } @@ -189,7 +199,7 @@ func TestWalkDir(t *testing.T) { }(), ext: ".pdf", expectError: false, - expectFiles: []string{"/tmp/a_directory/a_bar_file.PDF", "/tmp/a_directory/a_foo_file.pdf"}, + expectFiles: []string{"/tmp/a_directory/2.pdf", "/tmp/a_directory/1.PDF", "/tmp/a_directory/10.pdf", "/tmp/a_directory/11.pdf"}, }, } { t.Run(tc.scenario, func(t *testing.T) { From 80f3f89a8921d8d2d1463a1ce14d56cca4b965f4 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 12 Feb 2025 11:47:45 +0100 Subject: [PATCH 052/254] fix(pdfcpu): correct sorting for the output paths of the split method --- pkg/gotenberg/fs.go | 44 -------------- pkg/gotenberg/fs_test.go | 86 --------------------------- pkg/gotenberg/sort.go | 69 ++++++++++++++------- pkg/gotenberg/sort_test.go | 10 ++++ pkg/modules/pdfcpu/pdfcpu.go | 17 +++++- pkg/modules/pdfcpu/pdfcpu_test.go | 8 +-- test/testdata/pdfengines/sample4.pdf | Bin 0 -> 8025 bytes 7 files changed, 77 insertions(+), 157 deletions(-) create mode 100644 test/testdata/pdfengines/sample4.pdf diff --git a/pkg/gotenberg/fs.go b/pkg/gotenberg/fs.go index d1e14a7d4..ec58c8299 100644 --- a/pkg/gotenberg/fs.go +++ b/pkg/gotenberg/fs.go @@ -3,10 +3,6 @@ package gotenberg import ( "fmt" "os" - "path/filepath" - "sort" - "strings" - "time" "github.com/google/uuid" ) @@ -88,46 +84,6 @@ func (fs *FileSystem) MkdirAll() (string, error) { return path, nil } -type fileWithModTime struct { - path string - modTime time.Time -} - -// WalkDir walks through the root level of a directory and returns a list of -// files paths that match the specified file extension. -func WalkDir(dir, ext string) ([]string, error) { - var files []fileWithModTime - err := filepath.Walk(dir, func(path string, info os.FileInfo, pathErr error) error { - if pathErr != nil { - return pathErr - } - if info.IsDir() { - return nil - } - if strings.EqualFold(filepath.Ext(info.Name()), ext) { - files = append(files, fileWithModTime{ - path: path, - modTime: info.ModTime(), - }) - } - return nil - }) - if err != nil { - return nil, err - } - - sort.Slice(files, func(i, j int) bool { - return files[i].modTime.Before(files[j].modTime) - }) - - sortedPaths := make([]string, len(files)) - for i, f := range files { - sortedPaths[i] = f.path - } - - return sortedPaths, nil -} - // Interface guards. var ( _ MkdirAll = (*OsMkdirAll)(nil) diff --git a/pkg/gotenberg/fs_test.go b/pkg/gotenberg/fs_test.go index f6c15a352..ee57d1b9b 100644 --- a/pkg/gotenberg/fs_test.go +++ b/pkg/gotenberg/fs_test.go @@ -6,7 +6,6 @@ import ( "io" "os" "path/filepath" - "reflect" "strings" "testing" @@ -145,88 +144,3 @@ func TestFileSystem_MkdirAll(t *testing.T) { }) } } - -func TestWalkDir(t *testing.T) { - for _, tc := range []struct { - scenario string - dir string - ext string - expectError bool - expectFiles []string - }{ - { - scenario: "directory does not exist", - dir: uuid.NewString(), - ext: ".pdf", - expectError: true, - }, - { - scenario: "find PDF files, sorted by mod time", - dir: func() string { - path := fmt.Sprintf("%s/a_directory", os.TempDir()) - - err := os.MkdirAll(path, 0o755) - if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) - } - - err = os.WriteFile(fmt.Sprintf("%s/2.pdf", path), []byte{1}, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/1.PDF", path), []byte{1}, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/3.txt", path), []byte{1}, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/10.pdf", path), []byte{1}, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/11.pdf", path), []byte{1}, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return path - }(), - ext: ".pdf", - expectError: false, - expectFiles: []string{"/tmp/a_directory/2.pdf", "/tmp/a_directory/1.PDF", "/tmp/a_directory/10.pdf", "/tmp/a_directory/11.pdf"}, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - defer func() { - err := os.RemoveAll(tc.dir) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - files, err := WalkDir(tc.dir, tc.ext) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectError && err != nil { - return - } - - if !reflect.DeepEqual(files, tc.expectFiles) { - t.Errorf("expected files %+v, but got %+v", tc.expectFiles, files) - } - }) - } -} diff --git a/pkg/gotenberg/sort.go b/pkg/gotenberg/sort.go index bd18da697..0a64ec2fe 100644 --- a/pkg/gotenberg/sort.go +++ b/pkg/gotenberg/sort.go @@ -7,7 +7,8 @@ import ( ) // AlphanumericSort implements sort.Interface and helps to sort strings -// alphanumerically. +// alphanumerically by either a numeric prefix or, if missing, a numeric +// suffix. // // See: https://github.com/gotenberg/gotenberg/issues/805. type AlphanumericSort []string @@ -21,20 +22,20 @@ func (s AlphanumericSort) Swap(i, j int) { } func (s AlphanumericSort) Less(i, j int) bool { - numI, restI := extractPrefix(s[i]) - numJ, restJ := extractPrefix(s[j]) + numI, restI := extractNumber(s[i]) + numJ, restJ := extractNumber(s[j]) - // Compares numerical prefixes if they exist. + // If both strings contain a number, compare them numerically. if numI != -1 && numJ != -1 { if numI != numJ { return numI < numJ } - // If numbers are equal, falls back to string comparison of the rest. + // If the numbers are equal, compare the "rest" strings. return restI < restJ } - // If one has a numerical prefix and the other doesn't, the one with the - // number comes first. + // If one contains a number and the other doesn't, the one with the number + // comes first. if numI != -1 { return true } @@ -42,28 +43,52 @@ func (s AlphanumericSort) Less(i, j int) bool { return false } - // If neither has a numerical prefix, compare as strings + // Neither has a number; fall back to lexicographical order. return s[i] < s[j] } -// extractPrefix attempts to extract a numerical prefix and the rest of the filename -func extractPrefix(filename string) (int, string) { - matches := numPrefixRegexp.FindStringSubmatch(filename) - if len(matches) > 2 { - prefix, err := strconv.Atoi(matches[1]) - if err == nil { - return prefix, matches[2] +// extractNumber attempts to extract a numeric portion from the filename. +// It first checks for a numeric prefix (digits at the beginning). +// If none is found, it next attempts to match a number immediately before the +// extension (for filenames such as "sample1_1.pdf"). +// If that fails, it then attempts a trailing numeric pattern. +// If no number is found, it returns -1 and the original string. +func extractNumber(str string) (int, string) { + // Check for a numeric prefix. + if matches := prefixRegexp.FindStringSubmatch(str); len(matches) > 2 { + if num, err := strconv.Atoi(matches[1]); err == nil { + return num, matches[2] } } - // Returns -1 if no numerical prefix is found, indicating to just compare - // as strings. - return -1, filename -} + // Check for a number immediately before an extension. + if matches := extensionSuffixRegexp.FindStringSubmatch(str); len(matches) > 3 { + if num, err := strconv.Atoi(matches[2]); err == nil { + // Remove the numeric block but keep the extension. + return num, matches[1] + matches[3] + } + } + + // Check for a trailing number (with no extension following). + if matches := suffixRegexp.FindStringSubmatch(str); len(matches) > 2 { + if num, err := strconv.Atoi(matches[2]); err == nil { + return num, matches[1] + } + } -var numPrefixRegexp = regexp.MustCompile(`^(\d+)(.*)$`) + // No numeric portion found. + return -1, str +} -// Interface guard. +// Regular expressions used by extractNumber. var ( - _ sort.Interface = (*AlphanumericSort)(nil) + // Matches a numeric prefix: one or more digits at the start. + prefixRegexp = regexp.MustCompile(`^(\d+)(.*)$`) + // Matches a numeric block immediately before a file extension. + extensionSuffixRegexp = regexp.MustCompile(`^(.*?)(\d+)(\.[^.]+)$`) + // Matches a trailing numeric sequence when there is no extension. + suffixRegexp = regexp.MustCompile(`^(.*?)(\d+)$`) ) + +// Interface guard. +var _ sort.Interface = (*AlphanumericSort)(nil) diff --git a/pkg/gotenberg/sort_test.go b/pkg/gotenberg/sort_test.go index 94b291660..c797ce7f1 100644 --- a/pkg/gotenberg/sort_test.go +++ b/pkg/gotenberg/sort_test.go @@ -17,6 +17,16 @@ func TestAlphanumericSort(t *testing.T) { values: []string{"10qux.pdf", "2_baz.txt", "2_aza.txt", "1bar.pdf", "Afoo.txt", "Bbar.docx", "25zeta.txt", "3.pdf", "4_foo.pdf"}, expectedSort: []string{"1bar.pdf", "2_aza.txt", "2_baz.txt", "3.pdf", "4_foo.pdf", "10qux.pdf", "25zeta.txt", "Afoo.txt", "Bbar.docx"}, }, + { + scenario: "numeric suffixes with extensions", + values: []string{"sample1_10.pdf", "sample1_11.pdf", "sample1_4.pdf", "sample1_3.pdf", "sample1_1.pdf", "sample1_2.pdf"}, + expectedSort: []string{"sample1_1.pdf", "sample1_2.pdf", "sample1_3.pdf", "sample1_4.pdf", "sample1_10.pdf", "sample1_11.pdf"}, + }, + { + scenario: "numeric suffixes", + values: []string{"sample1_10", "sample1_11", "sample1_4", "sample1_3", "sample1_1", "sample1_2"}, + expectedSort: []string{"sample1_1", "sample1_2", "sample1_3", "sample1_4", "sample1_10", "sample1_11"}, + }, { scenario: "hrtime (PHP library)", values: []string{"245654773395259", "245654773395039", "245654773395149", "245654773394919", "245654773394369"}, diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index 4a468c1aa..609e955d1 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "sort" "strings" "syscall" @@ -128,11 +129,25 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenb return nil, fmt.Errorf("split PDFs with pdfcpu: %w", err) } - outputPaths, err := gotenberg.WalkDir(outputDirPath, ".pdf") + var outputPaths []string + err = filepath.Walk(outputDirPath, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if info.IsDir() { + return nil + } + if strings.EqualFold(filepath.Ext(info.Name()), ".pdf") { + outputPaths = append(outputPaths, path) + } + return nil + }) if err != nil { return nil, fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err) } + sort.Sort(gotenberg.AlphanumericSort(outputPaths)) + return outputPaths, nil } diff --git a/pkg/modules/pdfcpu/pdfcpu_test.go b/pkg/modules/pdfcpu/pdfcpu_test.go index b44003c32..b1253cfd6 100644 --- a/pkg/modules/pdfcpu/pdfcpu_test.go +++ b/pkg/modules/pdfcpu/pdfcpu_test.go @@ -230,15 +230,15 @@ func TestPdfCpu_Split(t *testing.T) { scenario: "success (intervals)", ctx: context.TODO(), mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + inputPath: "/tests/test/testdata/pdfengines/sample4.pdf", expectError: false, - expectOutputPathsCount: 3, + expectOutputPathsCount: 20, }, { scenario: "success (pages)", ctx: context.TODO(), mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + inputPath: "/tests/test/testdata/pdfengines/sample4.pdf", expectError: false, expectOutputPathsCount: 1, }, @@ -246,7 +246,7 @@ func TestPdfCpu_Split(t *testing.T) { scenario: "success (pages & unify)", ctx: context.TODO(), mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", + inputPath: "/tests/test/testdata/pdfengines/sample4.pdf", expectError: false, expectOutputPathsCount: 1, }, diff --git a/test/testdata/pdfengines/sample4.pdf b/test/testdata/pdfengines/sample4.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5fc57f1403a293b2f5145fe3e39d172f1a796422 GIT binary patch literal 8025 zcmc&(O>g5i5Z%M}qWj)?$#Em`(-IK)5T{ubZPCEVo)$SMHl4a~tw2_aZ2!Xo{Q-OG zspy~B>jM2jy%psQ?X|2Su+hPy4-D`^@$r%K$QkM6>f-V>KOH3}pZ@*xzl5`lRd>H6 zXJ;(ED(~%v35QHsdh`ClvUE~5<*K@8<8flwbLa^F-aRm>9@h;M8B4!k&Nu8GllZK{ zR%2_h9bubeTVTsG{|ES|jC)e&C7dC)abPZ|dstfx$9Nu336ve_PJ%^vyX-zhh1P=$68vH+8w*Je0Lv&))Ay z{Bz?mOMkTUWqDpbxpjbPDP>NDW=8T;&D@->;5oNiIOB9`H`QZ3gZ1`m(`!dFYkaSb z>$Keg5YbwG=u9>Em5Qjr@6j_gz-*}ozrPVR_{e&u1`td&_^6DhL3%$tQv<9x(ICB- zB5K$=yW8F$bnApZPy^M`@ z^iIU(a@E+HrI)L+u@`n$&0PWggI(V@KQmE4IlF0UTmG6neUvxZi}TwFD;KN3?*IPd z<3Dddzxw>m3w`tQgL~t!?Y4XO(5im-WxXr1_xba&m|GV}i@ALwAB(wlV78dsC(x0Y zv6$TM=$;wt!z1srgvg`Ja7i4SIT&GbfC$5Ki)tPq!mvi7%mYLi7B++#OO+$n4b~mr`j%(%zbu9vM)bow03vcf_=f@+Y0>HdXE`* z_jND8A^#LyiifLl!yFp?FSoC=tM9 zMtGG9VS?yYI)uSFdZ Date: Wed, 12 Feb 2025 16:24:16 +0100 Subject: [PATCH 053/254] chore(deps): update Go dependencies --- .env | 2 +- .github/workflows/continuous-integration.yml | 2 +- .golangci.yml | 2 +- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.env b/.env index 4516acecf..d8293a627 100644 --- a/.env +++ b/.env @@ -7,7 +7,7 @@ GOTENBERG_USER_UID=1001 NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. -GOLANGCI_LINT_VERSION=v1.63.4 # See https://github.com/golangci/golangci-lint/releases. +GOLANGCI_LINT_VERSION=v1.64.2 # See https://github.com/golangci/golangci-lint/releases. GOTENBERG_VERSION=snapshot DOCKERFILE=build/Dockerfile DOCKERFILE_CLOUDRUN=build/Dockerfile.cloudrun diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index e1ab68b67..bc2d55fa0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -32,7 +32,7 @@ jobs: - name: Run linters uses: golangci/golangci-lint-action@v6 with: - version: v1.63.4 + version: v1.64.2 tests: needs: diff --git a/.golangci.yml b/.golangci.yml index 8fd432f6c..aad2389a5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -37,12 +37,12 @@ linters: - promlinter #- sloglint - staticcheck - - tenv - testableexamples - tparallel - typecheck - unconvert - unused + - usetesting - wastedassign - whitespace diff --git a/go.mod b/go.mod index 8a7b527fe..49226f281 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730 + github.com/chromedp/cdproto v0.0.0-20250210231439-aea867ea8506 github.com/chromedp/chromedp v0.12.1 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -24,8 +24,8 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 @@ -33,7 +33,7 @@ require ( ) require ( - github.com/dlclark/regexp2 v1.11.4 + github.com/dlclark/regexp2 v1.11.5 github.com/mholt/archives v0.1.0 github.com/shirou/gopsutil/v4 v4.25.1 ) diff --git a/go.sum b/go.sum index b71c6c8fe..36a3ead61 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730 h1:IEa+Va47x06CJQaLKFoce5iPTRRR5uI/GbeZbxdnYdc= -github.com/chromedp/cdproto v0.0.0-20250203011601-a3c71a042730/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU= +github.com/chromedp/cdproto v0.0.0-20250210231439-aea867ea8506 h1:OfjMcN8R6eUWZfKyJaTnlyiZh1BGgmEKmRkCZuDtGRw= +github.com/chromedp/cdproto v0.0.0-20250210231439-aea867ea8506/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU= github.com/chromedp/chromedp v0.12.1 h1:kBMblXk7xH5/6j3K9uk8d7/c+fzXWiUsCsPte0VMwOA= github.com/chromedp/chromedp v0.12.1/go.mod h1:F6+wdq9LKFDMoyxhq46ZLz4VLXrsrCAR3sFqJz4Nqc0= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -51,8 +51,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= -github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= @@ -232,8 +232,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -276,8 +276,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 8d7cc55c286a85a9b7c2539c366d29b92c204f4b Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 13 Feb 2025 08:44:58 +0100 Subject: [PATCH 054/254] chore(go): upgrade to 1.24 --- .env | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index d8293a627..1a590e556 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -GOLANG_VERSION=1.23 +GOLANG_VERSION=1.24 DOCKER_REGISTRY=gotenberg DOCKER_REPOSITORY=gotenberg GOTENBERG_VERSION=snapshot diff --git a/go.mod b/go.mod index 49226f281..d19fb35de 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gotenberg/gotenberg/v8 -go 1.23.0 +go 1.24.0 require ( github.com/alexliesenfeld/health v0.8.0 From 81cc4831669eb7889bc3eafcd9fc60b40c0f84ba Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 13 Feb 2025 09:21:38 +0100 Subject: [PATCH 055/254] test: fix issues with go 1.24 --- pkg/gotenberg/debug.go | 3 + pkg/gotenberg/debug_test.go | 2 +- pkg/gotenberg/gc_test.go | 2 +- pkg/modules/chromium/browser_test.go | 106 +++++++++--------- pkg/modules/chromium/routes_test.go | 16 +-- .../libreoffice/api/libreoffice_test.go | 32 +++--- 6 files changed, 82 insertions(+), 79 deletions(-) diff --git a/pkg/gotenberg/debug.go b/pkg/gotenberg/debug.go index a89a9b0af..9a83a6991 100644 --- a/pkg/gotenberg/debug.go +++ b/pkg/gotenberg/debug.go @@ -2,6 +2,7 @@ package gotenberg import ( "runtime" + "sort" "sync" flag "github.com/spf13/pflag" @@ -42,6 +43,8 @@ func BuildDebug(ctx *Context) { debug.ModulesAdditionalData[ID] = debuggable.Debug() } + sort.Sort(AlphanumericSort(debug.Modules)) + ctx.ParsedFlags().VisitAll(func(f *flag.Flag) { debug.Flags[f.Name] = f.Value.String() }) diff --git a/pkg/gotenberg/debug_test.go b/pkg/gotenberg/debug_test.go index 604060842..5c50fde5d 100644 --- a/pkg/gotenberg/debug_test.go +++ b/pkg/gotenberg/debug_test.go @@ -53,8 +53,8 @@ func TestBuildDebug(t *testing.T) { Version: Version, Architecture: runtime.GOARCH, Modules: []string{ - "foo", "bar", + "foo", }, ModulesAdditionalData: map[string]map[string]interface{}{ "bar": { diff --git a/pkg/gotenberg/gc_test.go b/pkg/gotenberg/gc_test.go index 0ed089a55..c8a4c09ed 100644 --- a/pkg/gotenberg/gc_test.go +++ b/pkg/gotenberg/gc_test.go @@ -31,7 +31,7 @@ func TestGarbageCollect(t *testing.T) { err := os.MkdirAll(path, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/a_foo_file", path), []byte{1}, 0o755) diff --git a/pkg/modules/chromium/browser_test.go b/pkg/modules/chromium/browser_test.go index a5698eaf7..a46364743 100644 --- a/pkg/modules/chromium/browser_test.go +++ b/pkg/modules/chromium/browser_test.go @@ -329,7 +329,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -361,7 +361,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -393,7 +393,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Skip networkIdle event

"), 0o755) @@ -428,7 +428,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidHttpStatusCode

"), 0o755) @@ -461,7 +461,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/style.css", fs.WorkingDirPath()), []byte("body{font-family: Arial, Helvetica, sans-serif;}"), 0o755) @@ -499,7 +499,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -532,7 +532,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } return fs @@ -558,7 +558,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrResourceLoadingFailed

"), 0o755) @@ -592,7 +592,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cache

"), 0o755) @@ -625,7 +625,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cookies

"), 0o755) @@ -658,7 +658,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -692,7 +692,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Set cookies

"), 0o755) @@ -727,7 +727,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

User-Agent override

"), 0o755) @@ -762,7 +762,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Extra HTTP headers

"), 0o755) @@ -816,7 +816,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrOmitBackgroundWithoutPrintBackground

"), 0o755) @@ -849,7 +849,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Hide default white background

"), 0o755) @@ -885,7 +885,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEmulatedMediaType

"), 0o755) @@ -918,7 +918,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Screen media type

"), 0o755) @@ -953,7 +953,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -988,7 +988,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -1023,7 +1023,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } html := ` @@ -1069,7 +1069,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEvaluationExpression

"), 0o755) @@ -1104,7 +1104,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } html := ` @@ -1150,7 +1150,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Custom header and footer

"), 0o755) @@ -1185,7 +1185,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Custom header and footer

"), 0o755) @@ -1221,7 +1221,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidPrinterSettings

"), 0o755) @@ -1259,7 +1259,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrPageRangesSyntaxError

"), 0o755) @@ -1292,7 +1292,7 @@ func TestChromiumBrowser_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) @@ -1491,7 +1491,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -1523,7 +1523,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -1555,7 +1555,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Skip networkIdle event

"), 0o755) @@ -1590,7 +1590,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidHttpStatusCode

"), 0o755) @@ -1623,7 +1623,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/style.css", fs.WorkingDirPath()), []byte("body{font-family: Arial, Helvetica, sans-serif;}"), 0o755) @@ -1661,7 +1661,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -1694,7 +1694,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } return fs @@ -1720,7 +1720,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrResourceLoadingFailed

"), 0o755) @@ -1754,7 +1754,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cache

"), 0o755) @@ -1787,7 +1787,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cookies

"), 0o755) @@ -1820,7 +1820,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -1854,7 +1854,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Set cookies

"), 0o755) @@ -1889,7 +1889,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

User-Agent override

"), 0o755) @@ -1924,7 +1924,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Extra HTTP headers

"), 0o755) @@ -1978,7 +1978,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrOmitBackgroundWithoutPrintBackground

"), 0o755) @@ -2013,7 +2013,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEmulatedMediaType

"), 0o755) @@ -2046,7 +2046,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Screen media type

"), 0o755) @@ -2081,7 +2081,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -2116,7 +2116,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) @@ -2151,7 +2151,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } html := ` @@ -2197,7 +2197,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEvaluationExpression

"), 0o755) @@ -2232,7 +2232,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } html := ` @@ -2278,7 +2278,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) @@ -2321,7 +2321,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) @@ -2369,7 +2369,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) diff --git a/pkg/modules/chromium/routes_test.go b/pkg/modules/chromium/routes_test.go index 933b833ed..552e55ca9 100644 --- a/pkg/modules/chromium/routes_test.go +++ b/pkg/modules/chromium/routes_test.go @@ -1052,7 +1052,7 @@ func TestConvertMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1080,7 +1080,7 @@ func TestConvertMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1107,7 +1107,7 @@ func TestConvertMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1142,7 +1142,7 @@ func TestConvertMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1259,7 +1259,7 @@ func TestScreenshotMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1287,7 +1287,7 @@ func TestScreenshotMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1314,7 +1314,7 @@ func TestScreenshotMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) @@ -1349,7 +1349,7 @@ func TestScreenshotMarkdownRoute(t *testing.T) { err := os.MkdirAll(dirPath, 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) diff --git a/pkg/modules/libreoffice/api/libreoffice_test.go b/pkg/modules/libreoffice/api/libreoffice_test.go index fce85515c..348d6c18b 100644 --- a/pkg/modules/libreoffice/api/libreoffice_test.go +++ b/pkg/modules/libreoffice/api/libreoffice_test.go @@ -265,7 +265,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Context done"), 0o755) @@ -295,36 +295,36 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } in, err := os.Open("/tests/test/testdata/libreoffice/protected.docx") if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } defer func() { err := in.Close() if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } }() out, err := os.Create(fmt.Sprintf("%s/protected.docx", fs.WorkingDirPath())) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } defer func() { err := out.Close() if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } }() _, err = io.Copy(out, in) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } return fs @@ -348,7 +348,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Context done"), 0o755) @@ -376,7 +376,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755) @@ -404,7 +404,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755) @@ -456,7 +456,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) @@ -485,7 +485,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) @@ -514,7 +514,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) @@ -543,7 +543,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) @@ -629,7 +629,7 @@ func TestNonBasicLatinCharactersGuard(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Basic latin characters"), 0o755) @@ -650,7 +650,7 @@ func TestNonBasicLatinCharactersGuard(t *testing.T) { err := os.MkdirAll(fs.WorkingDirPath(), 0o755) if err != nil { - t.Fatalf(fmt.Sprintf("expected no error but got: %v", err)) + t.Fatalf("expected no error but got: %v", err) } err = os.WriteFile(fmt.Sprintf("%s/éèßàùä.txt", fs.WorkingDirPath()), []byte("Non-basic latin characters"), 0o755) From 28e07bad54dbb09d847d158fd22e8127a2d708cc Mon Sep 17 00:00:00 2001 From: Kyohei Fukuda Date: Fri, 14 Feb 2025 22:28:53 +0900 Subject: [PATCH 056/254] docs(README): add pdfme sponsor (#1131) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 39e2e5281..2ba56c431 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ Head to the [documentation](https://gotenberg.dev/docs/getting-started/introduct
Zolsec Logo + + pdfme Logo +

Sponsorships help maintaining and improving Gotenberg - [become a sponsor](https://github.com/sponsors/gulien) ❤️ From b13a8094eaf0e4d665107aeeae9e65e407c67b57 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 19 Feb 2025 19:42:30 +0100 Subject: [PATCH 057/254] ci(continous-delivery): remove wrong outputs --- .github/workflows/continuous-delivery.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 74e151f9f..ab40aa6ea 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -50,8 +50,6 @@ jobs: name: Release linux/arm64 runs-on: ubuntu-24.04-arm outputs: - docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} tags: ${{ steps.build_push.outputs.tags }} tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} steps: From d8603f4f341596a84b8e5f8bf023522541521931 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Feb 2025 11:22:22 +0100 Subject: [PATCH 058/254] feat: update short description --- build/Dockerfile | 2 +- cmd/gotenberg.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 0e0756535..3f65ed029 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -63,7 +63,7 @@ ARG NOTO_COLOR_EMOJI_VERSION ARG PDFTK_VERSION LABEL org.opencontainers.image.title="Gotenberg" \ - org.opencontainers.image.description="A Docker-powered stateless API for PDF files." \ + org.opencontainers.image.description="A containerized API for seamless PDF conversion." \ org.opencontainers.image.version="$GOTENBERG_VERSION" \ org.opencontainers.image.authors="Julien Neuhart " \ org.opencontainers.image.documentation="https://gotenberg.dev" \ diff --git a/cmd/gotenberg.go b/cmd/gotenberg.go index 942e9aa55..b687974fe 100644 --- a/cmd/gotenberg.go +++ b/cmd/gotenberg.go @@ -24,7 +24,7 @@ const banner = ` \___/\___/\__/\__/_//_/_.__/\__/_/ \_, / /___/ -A Docker-powered stateless API for PDF files. +A containerized API for seamless PDF conversion. Version: %s ------------------------------------------------------- ` From d41de5d270574cee20430aef5e0c01f8fe7857c7 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Feb 2025 11:55:12 +0100 Subject: [PATCH 059/254] docs(README): switch to new short description [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ba56c431..51e60422c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Gotenberg Logo

Gotenberg

-

A Docker-powered stateless API for PDF files

+

A containerized API for seamless PDF conversion

Total downloads (gotenberg/gotenberg) Total downloads (thecodingmachine/gotenberg) From 70953aa66e6e561296a6878dcbbeb4fb9aa7d689 Mon Sep 17 00:00:00 2001 From: Zdravko Date: Fri, 28 Feb 2025 11:43:38 +0200 Subject: [PATCH 060/254] fix(qpdf): allow warnings (#1135) * Optionally allow warnings in QPDF operations * warnings are not errors by default * Apply suggestions from code review Co-authored-by: Julien Neuhart * fix some merge artifacts * follow the args convention --------- Co-authored-by: Julien Neuhart --- pkg/modules/qpdf/qpdf.go | 14 +++++++++++--- pkg/modules/qpdf/qpdf_test.go | 24 ++++++++++++++++++++++++ test/testdata/pdfengines/sample5.pdf | Bin 0 -> 224175 bytes 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 test/testdata/pdfengines/sample5.pdf diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 95294002a..45123bbb0 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -22,7 +22,8 @@ func init() { // QPdf abstracts the CLI tool QPDF and implements the [gotenberg.PdfEngine] // interface. type QPdf struct { - binPath string + binPath string + globalArgs []string } // Descriptor returns a [QPdf]'s module descriptor. @@ -41,6 +42,8 @@ func (engine *QPdf) Provision(ctx *gotenberg.Context) error { } engine.binPath = binPath + // Warnings should not cause errors. + engine.globalArgs = []string{"--warning-exit-0"} return nil } @@ -88,7 +91,10 @@ func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenber if !mode.Unify { return nil, fmt.Errorf("split PDFs using mode '%s' without unify with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) } - args = append(args, inputPath, "--pages", ".", mode.Span, "--", outputPath) + args = append(args, inputPath) + args = append(args, engine.globalArgs...) + args = append(args, "--pages", ".", mode.Span) + args = append(args, "--", outputPath) default: return nil, fmt.Errorf("split PDFs using mode '%s' with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported) } @@ -110,6 +116,7 @@ func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenber func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { var args []string args = append(args, "--empty") + args = append(args, engine.globalArgs...) args = append(args, "--pages") args = append(args, inputPaths...) args = append(args, "--", outputPath) @@ -131,10 +138,11 @@ func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths [] // original annotations. func (engine *QPdf) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { var args []string + args = append(args, inputPath) args = append(args, "--generate-appearances") args = append(args, "--flatten-annotations=all") args = append(args, "--replace-input") - args = append(args, inputPath) + args = append(args, engine.globalArgs...) cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) if err != nil { diff --git a/pkg/modules/qpdf/qpdf_test.go b/pkg/modules/qpdf/qpdf_test.go index 539724292..212a2cc4c 100644 --- a/pkg/modules/qpdf/qpdf_test.go +++ b/pkg/modules/qpdf/qpdf_test.go @@ -154,6 +154,15 @@ func TestQPdf_Merge(t *testing.T) { }, expectError: false, }, + { + scenario: "success even with warnings", + ctx: context.TODO(), + inputPaths: []string{ + "/tests/test/testdata/pdfengines/sample1.pdf", + "/tests/test/testdata/pdfengines/sample5.pdf", + }, + expectError: false, + }, } { t.Run(tc.scenario, func(t *testing.T) { engine := new(QPdf) @@ -236,6 +245,14 @@ func TestQPdf_Split(t *testing.T) { expectError: false, expectOutputPathsCount: 1, }, + { + scenario: "success even with warnings", + ctx: context.TODO(), + mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, + inputPath: "/tests/test/testdata/pdfengines/sample5.pdf", + expectError: false, + expectOutputPathsCount: 1, + }, } { t.Run(tc.scenario, func(t *testing.T) { engine := new(QPdf) @@ -304,6 +321,13 @@ func TestQPdf_Flatten(t *testing.T) { createCopy: true, expectError: false, }, + { + scenario: "success even with warnings", + ctx: context.TODO(), + inputPath: "/tests/test/testdata/pdfengines/sample5.pdf", + createCopy: true, + expectError: false, + }, } { t.Run(tc.scenario, func(t *testing.T) { engine := new(QPdf) diff --git a/test/testdata/pdfengines/sample5.pdf b/test/testdata/pdfengines/sample5.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a6185f715c2d0f1f31342af661beeb8db46eab5a GIT binary patch literal 224175 zcmaHRRa9I}ur>OjA;E&X4iI2)hu{MQcXxLfAh^rmF2Nbx-Q6Wvut0!d0S1>r1KiyE z^1uCS^}|_xs;leN>9xCRS23tc%YFi~^I$UkI62EHM#_r=BX|1TmYhH2$&`M)M`{XbJQzq(pcbLgs=+gn+9VsdDJ z%{~8b2IORBV};3~V`u4UOU)&~kI5l#WoKjSNzKj0`OhX7M;CVuS2K%$Yo)Ec>@2L* zmVB<(ysRIS{lT%258oUNQasRb}OWbGV1|9usQtm8lXrL8PnEdQxhvU0Zhrg zke5$PjM~HVpF^B5eR5AXIWozYf1q8iETHq?#qPeNrc?cjCLm4!bU3&N^ugxCiQmud zLHc#{ywq867^vcOe`=lTDA}~Za5po1d+WJf*9Ctwu*ki&TR3pqFVM z=j}I`3&kUBx!U`6AywHIrDgB3|MjVQze4)Y^FLAZ#b%qiHsk>KvW|($ll#I|Rudq{ zeOrd&H5PnK$Ib|4t^C?np$s=y$56AYyI?e04rYi1ztbs&LsP{9)@5#{I9Usrb4tUj z4a?PO88luDz`DIAnAHz&kI+Q{c-IINTG^+2;iD$0>9P|0WMIN}pjCeoo7*a+lGpRf z#ii&|zn%nt9XHPW=LBjNF^+6VKFiM2L8b}1CNL;q={adUGne5wEH6HLUyF6MHWM{kw=+5yI57h za?0`9S{;z{oZEm>-e)`{@2@~!jOce1mVQKpK>*p%TJ`X}ng^IL1+Z(jk>_&+T9*^v zXV?-{XBsY*UwL~4YFAcW*Ge-@ZIyV3qn*frtYUOu6c2!?CoSMM_g`5Na5&dpR_ub+ zf*v^E*9h&xztOi}nbJ%gnSv=mh`^amBmSJ30$8>Pz-5-J%*vK&FA*}%sDgsR8*u0V zLpbowluV%@V9;d5-iaf_XZWXLHOYO}7@ehlb~@Z=6j6n)li-qT$$QN*ro3BVrIOf+ z{>$j&=uWAX!n<_tgRBWt40+I(0R5*@U89x;OM2W$U$sMksFpRf(1gO!T~2pOf$e7{ zGRn=QPCzxzxf$e%HpWW{!|sKhrizH6)~yfbsOHh<&QhN$hRafy5Xvn3X%Mhd%kLs= z{8{F>b#|CX4MP$nXP4xwR?dP!p|JwX#bL)vtps(z@`SP$D-#9u;DHpXhaAZ@KURPY zi41@Gj5A?iilk}f!jg(Ucg$7L8{v*9h?V9_n$Z4GTwD5=cC;e?B#2N^gGx;lEYZtx z#wu37D=D2^TvN{}?CqRp8<|zKm=Mm3UKow}QaWLO7_t0Eg7g0lJQ3@xGTVT#JRW3I zm)w}8hPV$JjBuSrKDo>i@<|Osrd6!ANFTs9YcjqxXa$e4SZasS|J%Y<6rR2q#*jYA z%i0QQqjh*OHfPW8{F5ril^#qGwKr^jv3}RBdv==^q*#c;3W+~Kovx+Qwqbs?A+MsT zp*!e-1suMCQ}#~ir#w%H6VwCR`*^K$=|86f1L{)h-A_=64@MP$$=YCTw~nJ$6k@vL ze?JSC+41W)zr}3;AG@{nYx>j?NvZFr231K-$)X%dXm4PsI2gLkIGL#?1$>lj#2Z~p zJ*(o^s9Nx_WC&<+MX80dEhILd-$N6nn&dZTgccY_52v!d5Y!P_SbQ zTH_7RJ_5iRCXWGl9IetmW7Vt(NgEJ`rx)Ur$pt)Ix(3iW({i5JzpR;K|%} z_tGXO>)BM(8DSjA3??U}l%)K6nr>eNw6)e`Bu(_ER&+j%8VT+$B#8d6(p~$y4=?Hp zA-W5=F3{%&D?uyT$I-RYILMp#PV$ouy|mfi_E_SOG*U%g(&mYiE=@@VOJA7)NEu9i zi=;m;K$jLEWfYZnw*#UIg(&`1E9HgA#J=CQVd{sK0mM#SR$K_9-UN=LK=5MA^1!au z+m&`hg8)rh&Wv1jS1S}Q3J-m=_#j5y0-+|H60H+ujm^f|9s~@+PydiRiZdi`)P{UCz_hZo0&BsE{{g- zCMGp|pz6y#l3xPHc>rs5H-hS#Ex;D57#oFJedAHsTg69tx1lHSS*p>IYHx{syI6o* zYD~6rLi~((AMc~i zZeROp{~CuT4Rj~Tcxq5c>;_xdUUJ}ZFEzv^WLT;}gka$_>rEJnX$m5hokHcFUMbVX zu~U+;u{{qNmJ5vo@FJ*P%?Um#BVF0im{pM96~wbAmL~1t=0oBK$A@RSl>SvQz^sSV zr~Q^He^r*=y6UDoVN#HN_yU*0V0o%QQRrrXXL|i12>H<4wT@;Oo|;bzLOzxnf$a1~ zf%z}nJf@776JK4_6+T|Pj;8cc+Hnl4jMR_AC?qoI+~5Y4;yqym2-Ba-t6;aC%D()N z65Kb*renmQVjfMP*Mmr}{=J;#b-K{-EJhV~IrTh{pqJZZ!i>(#l|+huhCyr_!C?veu14+%=w40pvnjp>mA%^}-&Q*Lh#` zY21jd53}6JGPVj*{q=2_8FbaN-KDp(_vUiq|AiCM%Md3B_X%7SD|bu&_czxPi2~() zI`n}oAuAo65dAYk^Ohib}cAR|d~Ycx`_-{)+H|@4{v;LBG^PzULx^ zw3FM;$;C!l%<%GP%P2U9Jlj|yMK=J>8W?&m@Nv0$J3$}MS2p~BZ)YkUieS;AiY*bm zQTO+OgBoB%7snOwm!tZYI^;(xC4Xgi7}tW-yL+Zy8VnQG3V}BAvBD}700|=QUS4;Q z+Q$tCYvXnVVY~KQv9!e3W2Z&W)AK{80G}wm zJT>&C4;{n_{pZiZ%Ie}qaZfl}RIM1~ourEwD^KJ*7TazwG)AU9eCA{-4Ijt*84Te@ zRxE=;0;$tXo3f*yeBTRxwhG=9cO56Jswd9ZGEdR3G?%CK;1yrS7}|NMzdJ`xv)MP* z>U#-QWPukAbRCNueRNz`YLQ&?tAsndiOOe!3N77dJ`7fgMC#_;Y}V@G!v|qmNbjJVso4;GAfKmG4y<_nUa+ zkV+HC740H`d*`X9)&oYxEvbr(YIRfhnU< zQ{%nM*aVk5{{aX9A*2f-SjXa=?7t>~Mexwa39kHLM{5vM;Q)qYsuMP}4c`&Vz3k|sU%I8GO(oi8jnXot>vISXGnY5RnEU{3o6L6j_*dZ< zfhTkj-i_%t((ZxWE`6*l*Ks>?F*gVvz^bV2^Y@f2GZAl@Q1Jm1Zw9WwAZJvSU|aNL01!>z~BL}yC*xJ;mpLQ$7h0L4e!qVjfkxG^|cZ52;# z?b1J7owW;;PPjMP@dc}X2*&UWxIZ-v3|$K08oSnRz^ALd+uvodqxL9bzNlXk5Z8j2AcXSu-r$=8TU$m?RtntMo>H9Q znC0S^5wklPobpXrOcbxW-jano{aUz%-46AdeEWh!tF6*W*9o?4P*k_Tu?N&u zhZgxkgz;lihEv3Rec*kYcPNT3j2anN8k@Es<4cy0I^I%wO`v}li}T0w5$PG|QpTEH ztx60w^%L-uS!v{~)QUn5HYbGgZLQsNalEwQgAY&*6)|KtbpZL=SN`MiWv!p~jwy0<2pvKs+@>E#o$q&TaAu<9B?$D zpoX=Qxa@>tAd}0TZ%o=7peuI16hZeh^@|m^&6gOFUu8vNgDEs5K=k4(%t_6cTGVh_ z1|Wags6oAji+AqUzgr~6v?7zWNPZJZ1ueP-`#6iGjOcXvo4b%1S)QuyG!U14r!O1; z>1OV;NIokEy-Fa$XzEk!cL8&7dupakbY{}2dju9xk*~02k zKOUNXqB6g!dOZ5UkbmsS=U_jsbn}7d9Mh?wnF(~Td-6%VyIxjE1&z~ zbLvnJ52;*-s(mU1&n6*NOj}LEj@_tzq`~Xd`AwcDlh#5GBks8c<%98gkmvn{%p~0i z8@&}`mNoyDS3a?=fqcvD;TXKdg9o?h2M7CjkyRMe`>Vw&Ea_ zzvZibi^KTe-nb6K#y4P)xDf^`DOaU34M$ax60MO!P=+x^p8r+b7+Vw5v>}Sbl?h>c zkW!Tq!#r)PLaS%%L|FmKGw^4f$KrfRk?%@F|I@e2K_zOf<4k7l{W`IO*!Xl#Ep93$ z`eX_DV=$!vf~#%n$)mqR?iS=X-kR4;ORM4OzVWQvkc23XLjJ17StDD~^_ri>R9bYw z&{B#3XdlR+7^MSJ4Zy03`|qos1s;Ey$kV0}$W5(`Ct>^>cYI|oX}Z3UgF*ZI$xodP zKN$Tj3689D=Ps-JafM{uew~>|rhL8;PP!XNksEEto9fCQyWK0ab+19P9b@DXdl2t^ zey{wK=T-r2xnTvLR0|DDYx(cC@g?ZJqNtZzqVcKo{X77GdM494NUAzyvB;miki=jz zGfGf4+>m=YC6-2B0xVos`gt$uk4mm|&N0t~?YHMC%6~X91ANA_WF)5~rovb5 zJAI-%0??>mk-p^sr)f)hngt1Z`^z|&Pv$_THyTl$WF(RHSXGJ#UWhk88QEVziRqfC z#I31_h`}CL_HKYvLt!Vz0B?dL-^Pu=*n(qRuR_f5fqJ0y_RSPNc!^1?zto1?%&E9# zQxcBbbMGBJ8-ixGe!zsH{OmM^w-RB+QVOMv)5!Gp$`mngN_KCFQRywok?b)i;_T7? zk~|^1^g*@6oW?dB#=oC@LTs_O^|tGT1ipgv-)Kv`N?xvM^RiV)V3r!w|LCp#vn}*0 zCX~j$WqsalykzA4$Xg#0?2r`qYjX6*x)F>@?`<8Fhh(6J4TweqcpDslN|q40{KsyV z_g2%D6Ka4ND_5-%1Z{xn>)2CqRpm=nxMI-v6;h`7khR)FP-2W%^BN~jSi@S`YmSDA z%gTxKtCfOI0UCc9RT|)qr)0g;>7%Ne<%NGMh|tQ^tvv1*?dO3qiLORRMYa3Yxv-D4G`{gG$>PCB*W_n?D%!a%KCZk;%h6 zLQi?hqR$Zc<7iYjG!E$#m7P)c$;u1J0Rild2MK~r=n)G#@XQmU0O=r_l28~YhJfz* zul8!L4Vy2dPQPe&+j-MEO^|o0WGX!HCaF)n=!a7&fw<%hPHWaRAIyD%sglJ?2Bsg- z|FL+(2yz77D_9v&ZldPpE8#ERD#g^MVtzI@8V4MCR$ZSdT(0H$lDgJ?$6Y}t9Pd&j zcdJ*eyQA+l#Xo-qzO^mKFE9J%HM2`N=P@n|yY7~Y;ts`!SL-ScqaT!}Bzc?mpE2UE zsH+vO2?zen8f=j$Q-oaof&GmX?2}52;f%g=?M{?%EfkUv<;42*Usl#wvtX5a#voy! z@FW`^FsXXh6vhJh+{+ihtXokHUDx8th7m6R@!H^QN~L5dMfCbM$Z7zT@NwB4-rADs z=#lF1`rdry5I(|P@;Co;C*3kptd51{A9`7HcxY5tTgq*m$QryEff3#wRBg=qtp_&s zGia>3QbV4D@C)X(w{@(98znM($jA<^=|tpZwa%$log1Wh}oc+ z=c@;y=5Y4{1Ost<3d(>DI7kk2ril^#`>W{OvS>gNx3yU=j)nq-sgGzvEe9j@WSTG9 zL3*KdB_|;+65Xs}E>#hJ#WvxEQK^vBzX-D!-EwI+L9ii%P1mjW2LHX)y zfg|tQ?=HJpKy6iF8$C|X-iZ8Ty2m980UY;;BuM?hXAyy!cNS2mO1v%Mu_M4-B3#)Y z@2kCv#WE*gQJ)Cm{Xy#w4RWfPs!j{NP4(aw)KlFHW$_=wDB!v}iOsW&7??=PBj?Oe zieJu}@$ISo*iMAd|LwX9R?~>l`;li2lq#}G(Zra7kqi*CR$=g7y4TN;&j>pS?lxv?K@?;_K}=W*h9qa9V0@8)~LldA+}P|ZtBh~jQ{+i`0kzvh5N0U zr3?OZ;OVKIi)e`Z_Nl-&lV|)4oONH1lRy2pM#zQ&0}F>uHg1ev?-lG6`65$l%`b2~ znIhUxJe*4ni!mAgL&6PasBk9ZsKl)l&VOe)^1LRq``-L`=r|h)rIw!OQi#HObX2wm zW%xVLaSggx{kkNi&1G!e3zg>Fv*X#Uj3SzLMWhg_;;u#aAjSP0f??Hemm0;CBR!sJn`@gls6L zcb&DzZfyhU;uK(=!AiEv3fLb@&~Xx9&$CuGFyws}_xU&cyzJ03bQM2+pk$pjLtE(| zY=Qpnu;{CMSTnVOk$i(Lu(3f43bt!ZrbJ2AGc=rD@GR}a-;c!Dx1 zLJA@86%!i5bD|uNNowX5wYYZ@tSSnJHIs6E2dQFu>iZhB+Qsx;>|wc}zEKvgiS8GZMme_OU3RVse+8$` z*Vy-m0jlJ!pKc*?n*9cuBhn(X>)p>O#PM@~9{%v8pwMhzTx&TTlatrP;m2P>KU}JG zt^=3nb+3TU;pgc77{B5x5leaq)aYsR%BIoE2hh2fQHa4BDd_eGoBz#VJpBY}QkVbu zm#nr(W^Ses3=2w;FvV*%pg?VvWIm#qydH6ld)=Bxx+)8@-Pxyu0r-mq;@Y(j&la{l z$$y``uzEe_7m2&~p2JTrK*i@IATRBq0sx>Vfpvt0c$#ydO~w`oHWtzD}7+1G+ltS4va+*1NV#QDTu zMdG=tawbKJN3+Z{KsM6Hg+%@!#2P*E;W|!AL|w8U&q;R6gz0f>Htp46G2Mq&-9NY| z!Rn!oDFQhu2}xWK9n_ZYkIae}=!GL!BtOzw@iS(IgB|PnyDudfC zeHXHf^NFZxt3oW{#cBM3r6ZSOm-*%bu**?bWncRd=RribmPQ!SAE` z+n|u5Sv##pmXip5?lLVs!@M6&lFR=Eh5OZ?>^t^p5ye3Ug=Z$VY@YJL4@?0omeCk% zhdqGco|AUoQ=gWzO?f#)i7N#9+RAd^};>AT?zDX>7&h68Va{-R8K~(gq zw^dp!&)9^6{OEZ^pV+}kOcL5A&`zV+f-=M2>!XqN$p`j$rnH2z6sLEO?p3;3(Esvf z-SqSBqCjG`{NO^%76)qAyhEvbHaUpmhj49J`AL~l%}ILdi$O7uu$8A?SXSz?JJHM# zKJM)#UeE1o!qK+w?@I>fitJ)C=BJRc^#0Yol>8v6E; z4}W9h)}p%IK-HUn-Cm}{d*?6|-sp0Z^2n@^rghJ&$gp)^euA$pj|J8{UJuAoO>`W8 zrL{MH(1#&d$hG}P&Df@b?ZCuu{8fp2k}VzG9e_g>LKCq{ljo$cSy%olrP{S-OCn=v zo`0^W6$5~obC?GUtHjd*{@9^k+eUlbs|Xd`PB8N-o2BQuTG6XcDguLSeB@^_OUJ5k zFqh>S=Jod)$K%A zhD0`FVA+dB^jwy9F*1*nAQyLN<564Wa~vu;X&aA@Lrq6itr$T6zhz-;<{*B|U{1Lj z=b}B$apxK<+^rg6m{z8x!n-wHqyIE-ylloEq2mfM(`VtVC85`WF`)WLh2Noc+x^bB z!2%)$4i2SZ^Gm(joZ{MF&D=}_q)(E)CyE)CT!fV#1JE(!tpIsgT@YIB94o*R3N0B9 z2G@||3@^`@)0zO^9B7T^L-lHJwZe61@eVsYv?3hwgroFNH|Gj-Azq7lr}2vcww()q z;h-y&vAW^OvKs{y=<-#)SRE3Ku`rS!A1Y8-5em^xxZqXoAZ z>X=yG71N{d_e;0N#Tl>|c)yy&^%}2!8xMrs8Zm|w$Fds7KoT0wse4k)<%bfOUZ&9A zS;hVx?wqn(jW|#~DVdQ4WotwJ>t5E6C2^$MK4{IuS;dF$W+0qUd`54KpPay{^X8JG zQN0X>gogAPjDLf5P(Y|478VjKDjEi(kN}tR&Vl_gNk55c7BYDZNm-g78m1nw6>Q|G zD@hWz9ibWhpX7@I+<#^zZmG`_wMyKPY(>5_As-%>PbLqG<)peP&v&L5?p7c{#Nh}2M ztbVbwhmJgDRo3}NxgLnT_jzFoc!GAY=cncUN)i?^;TW>UZ8@rc>ZN8ei$}~D-ilHLWb!PbE6`IV7YHO2f<+ljz!yjYNthks8&iPQ z_ES!5#YOMkD$y^y7b7euzL05dYo)ME&0G>Hg$%uS#&M-BwlU2Z=Hq)67IpRol=7PO z%)5=XLz`m9emzmH3`5lCI!uYJdi8!akw4mpQ5XT9_$Zwzp9duZtAX!QA{+U{^;veH zD)IZCLl)Ls2fTXn`(`c5X3L)~tVUn~%^?*cL#2*kA{j@xJ1H8A;dXv|x&fLl_6C&G zW;-WNF5ARSB}(-gcE9)`Zuk|nX0(cS^R5Zrui#}Yfn z{a`$FIVz4@=Ntb5Nmlww#@hBLc(+kVcJgFf$R!-$10-RVNw&H?OA%LI-i z7N~csX}RuBKV;}nBEdghn~6KZ8$qQ1Jak&>HJR+_cdJ&M8L2>xsaOe0nZ0oR&Lm1| zP7>MZw`@2W`^kuW>l>zCuk|}Tm6Z|}>0E0u9S$}Q#7ce&6Q;^UW8R{sn{sys>;PvSq2HxT{-O&DZLKOb`);+`gz$TL_D?MM3eeC4u?H`f zlwanbx;9|?&Cpl{;?UWSZG6=rWEEEB;J(9$*x=v!$OI&psoLuXP1Z;#s2lAkyT)tv zyWOAWS8L92z$TRGdYZtU`SKzCMt!iV(DgHoSAt;|rBaR!=kDJ@R#jMXY(rpKtOCZJ zvv7`~VFuE16#T$gT1CWk<4>n7ULk^XhNIy?KxHk#A`xqu|Ad^ycY==)|0qo^)Tx1! zcSG3`{3{}bN|N`n$@M~E*LW%B^j3w7c=JhF6{!s(!l%msRi+qjZuD4?y05X)tJWU~;seiPCH zp=3`Hmlz+Q+LVR!S7Bmm(@UZZ$74C5duJ^dJF0sllp6AvE^w(PmAmHglw}cYxZa$| z+@gQ};7G-~t{N0;R1EiEDgV4S2b9y&U4C|4XG5YrJx)f%lN^8Hn{*a%6A20WC5 z7j0xB-NN!h_Os1>NadCSlz;#+6=secB$L6uB(V*B6X+>kD+8yqPJQaQredUn5+4q+ zGVNcu2Yl2>#*KNcv*lYq!zjSF#(SS2w_$H6ruaik7pjnQ3wK|Bf*OtQ5QCp`2NyrL z0=MiRJp&)S6ZY-^jxhx8tUG+In$c{LCTP-rn|d>WKfML%n7#Z`6VN$QU<}WoKGtZI zztGYTSQh;V5xNU}sl@1<5j;jrQRqp~(N+Jg34T}4xD4;6ht#hW)%CxoLp4|~I1Yr% zEGCq|-+lX#J}~X6noI`IzK72!Kl#gxRrut?io8R2^xi1RG7ie|qP^(frF13iHX8S6 zk_iX0x<$X!E;omsu>-!(?l<;LK*(x8xv@Qto>@#i0|%j1s%Dp22lw-lplYMiI_~BL z#T96y`7P!i@FJV*TNZRe>*mfmCpDjKbbOrhm;}n(aF8Wbt)npEK}eTvPZVzd zE4q@J8eFNC4=kHki%9!3`{E4(TVJF`3o%J2Jaw|4g5QNZ)Zo-q|0XtVk(teIjWKJY zqzO+mRj6}f4MIEo@~S+t6%9%!BNdmyZT0F>9)f2lXp0{Yf?ATu)Ui{Y4RI#2t|Que zO>$Ra{UP7+wm-su13wrgwJht$MeJ+8S;s|pm>cCY`z|=SW8#Tx zk@&8({WQntZwO=<{F?k`S?uryCX_BI81_Z#cQ5*fM&ioQI$yaq_T))4KY6BrHUge$ zYHBt9QKqIJ&5G#77d|iBxiv zf1XoC;+P~|0^woAxh}h?84Qv$YFjq5FAEDw^eMT2HIRv6_)T4YMhvnr85W1zL|GKHa6{XpqRw$dhZs`uCBYvb8cUUw^H$}(3+k{e;fn!j{DV`2ZgGuK?Gu+;=5Rm!*0LlW$b{8%u@sakjdV$ z?8@zpWfy@mfhyffJ!(ikJipai3k(y>*dI^N6p*uq~oF=Hzjg(XIp;*&J~)$Vg_*>G<-uvz;J>b-v; z$l!SQYmp({%(W93Cjbl=ZG?XXrz(@CQbDq597j+!sM8otguM_9MlNIXx>(bS`*MHM zr!`L9`O}ArYHtt?KsFgb66Mp5zZ^3@|G|&{X~ZxrLx|ays0F1-5iffuqOBINn>r=$ zRWWqc8LbjS7)`0JaBVVHbL{U*<20upwBU?cZjM186sB691r-jPa8J}e%K8s9_$swiBVZm?u;AR46OXh6y7pJ^za}4N#;cg2|{}#AE*)KmSXnksWQ62TW ztf8Hw`{OC990;o&vl>o$o@l7(y~KYqLC0Xs6ud;Xbmi-5(S+4l;1~hF)#2B2#h=TF z+sBpLYEz}2Wva}hkG&GrP^kvdizDV3i}cslJ&BFRv@);LxRQfU|E;!!=~b?^n_o0v zGl`m~2x10WyE#h7%*}o$I-)jgY*iODc@~kb1j13@(b8`*RBR;*jNJcJ8~ODCMSQA2 z9l{}N^Bbu)4c&hnk&@MFe)=~_!HYLtTrqy!K%uFd;18}OKR~vYNLyZO#6R%qlA4%i zPO4)JtylaCru)MDyrzhEU#=mf=X=T?mv^kwtucA2POB;m*(d=h$RdSNZ9^ua?QUlY z)O;zkwQDwAB9OE`HCv+vv#YBEXtY0E-yx_S^qI%|ZmGi$<8WAe1oM=8y z*$_4_5aq-s@@ZW;DKVD!&it$aS8e!W2zUHUP`|H!{nr|9c>IGNHG2&&!aQ)rmWK2l zuZy(3dYGC!lu?>*Fp1*|bmrLkIf01-&h|^xu4VDolgb920uQf7h42w!9z7|-0-}>> zy9Umu%&g|&3)NMgDLQ*zflF$a(C;knrO`;Dn z`f~xLEIvxeP11?RTa>;6e?SF*QgE*i-jl-nDVi;5H}@{V(53cTp)HF`!Bn$x&zdn< zd{?a*y{Pv1y+;1vPN)E;q#T-pWt*iv+H5wipGABiLS)CYL#pmUPcHsd3e#R z_V3y}!+@&Iw>OYQYwt-TL;}!)-^c6>)Ls-GHJyrIjL&Ks9%(<(m{qGMUZU=a1$&DU z9&IM6lpyM*7kf!Ekh2fTc$BtU>exueF84N=lC*fwu()eLM5CP|<*4dcpcs0NeK2Q< zvwu|X$jeiB=#xQ@%`L(}=`KHrVf3=+y@Ir~o59N~X6aBy^zO}5)mi!MT|(^s?2=tr z#UCHl0nJ0_6kLT*?51;1U3JyP)%ip}aO8PW+8U`h-IX9rwfPx5Zm(xb?xJ56rkC3o z5tmIa**1o-2s@bnU|f4R%4F>8${Z8pePHgHZxM>@2M|0_nS`~>YuI7v&DzaG0g zkt~X02y02m=Bmm*k>(q{Ju2I4a%G|ktRA><;$n7U{GHztiF;G>{B;qZo+3zzjlpX~ zjyG?iP6A0d^h?+C$&X#pgReZ9gY}K?n?ulU8gS;jlO2xqg3&XlApX$gsyb~lMSm(@ zO@ZylZb=L~ud&D90_rOa2b=%0i2ytlCYL68d)|t^uP}Qe=5qNAUGR|n7SFu`_wQpo0Jm_je^nn zLx&TtkH04VnsnGVhMJROCVsQ}U}zYH3M|o>tnqYBi(Lt<*K_zvVMY+tF_V12*f4UA zM;T*v0#}o)v=(wuVg(|;6CD-Y;QJO^+W+(4Y1@mVQ2gfi4hcv5PWQNtTk|1pvhRzP zrKESUjSt~d-93hKqapT?F;s2F#aA9k*k;2(DRoX>x`EdLeq~qHME>kG`(WY8aP9nYQ&WO@new zKg_h(1gEz5si;Wmr#hGw;h`?+){ofQ6C%zf!(qBvW>h7utar2K$-RLjY;~Jj@ew9x zt(gAKrWC66yq-rK=fwFb=$;=;w~Q7Xwq28@E+SB_V z`0<2N2z%&{d;YDgb?s=J-dpd>`|xB%n_x^z(8D73QD<&TL4VQ!HQU46I*HW6PRb8` z$DIAH@~ivcQU)Y>gKOyH7wjmSDQmBT6kD)XFjd$~Y{jgGhbWHuK(u1CrnL9bSEVp5 ziQJL*7CY#l#!^}SEbTCmvChZ0|4=nyBLX0_9m^2a8Iu(-7p#mnPQg)i-IE<<b_bx)v$>Vu@Kf$d-c=c{8|M>128S|kJFOo`oGi#@bHS1{(BdiXhJsrd5Sv-%1CnB~`?3E%!I3eKVm%8H8tYB|K<2#XGkhvl^4`drb6}R#u z;V#xaRN*IaD(wnmKEr%PwXfL{to(b!V8HVTGn76GHAH!FS1RJfb*X`rVCQ3r(-Wx7 z?+0CplhqhMXv;;CRO`~-;7XE2i;!Y6<0q&PonqY)%WCy0cEEmH)D|Yp0ttbX+EoPK zi^;0#HkjI)Yg6K|WidjY|71@c9Cu^5mk>KrmoFCekma-24ou(7+OYk2!{=VHeQ7;F6|H?7Rsgp=5Gsjk;l6cN5`kz@HNtOvBPD~coLd-Ajt z1UAdk8RfmXFP4LPqxeOycRm263@Vh_CC;bbo`A4eX!sb?v=uE^0gU$s$@=H zDjeP5zYUM+!HdA%h(yN%qQU^4co;0v!`xQs|*1Rax7=MM9V`a+RS zZqD==+j2+jZia`V7ifwjaJ*AtaEho1ihgfFFbZq_0h@u`E?=4B5PS?qCxx+0=`ES> zg8N3*A(`c}YQ+8C6nEB-z{i3~`l&DAyPYcKh#L%@DTi*zv6ecR1i*zW5l!F31Rn3G z5+a^kJt^Uv@jslI)^Ht?!;ex!)2k3A1F$_`Obz={hRLNi2+~;kzzeafU?=iRP%$+TE#Hs{fXBn||S;&!YSj zgYw5Bc8Ke!G>;Y6P9HluJdnrw0}+XJkaBS)vSMrP9e(>UX5w$P7>EFM2GbArthEgE zSp(M=HhmtQH^q@z&{P2w8F>XF_pPKD+<2`q)U z+M1jE+A)3*D*8eYdov7X)G*&u$1P>VU9?7wOI50nv`M__P1g5?U211sS6EgK`9G8T zl4g1TGM)aYtQF_aNv^-x&b1Ocew;R9=#9R-IKQ{O4J7*K|IZl1OT)0?6DhYkR366$ z9|P~6;vYa%?#3&62LWn9vV}EmgioyzWp}@Imjg5t*~@2{7&nOuwv9;BI*Nz6%4}L9 zcZCuIq<((F_f-1YBjh2NpZQ$tT?3N4k617g&-`rhQM=c?zegX9l%<3OhCqR`kgiiO z(dH;=FzQ2-X<@+&K|e^cQ`s3e^RS{V;qUO4M)FDCQw&1CbcaA#HaBfeqp%q*-qh;& z(D<5jo9*!+u0E)fU}FuH+vM3+%d&ffv0Gn-OKfzZhxn0&3sm~leUs_ptv;VA-mNPr_k zhiG_jLQVB1Dtlr1f5ju%nTLeB6yUXo+ z?Y@QCzNvq9g|Jto@kRvXp4D~IV1yAJ$GG^rhA|Mp*kNM=b5+r{{*_sP=uFKPFB%H7 zg#31Cf`jcE%hQ~`5y zX_N`q@~(V$WL~Nm59nbX`>P72^ePrMc2d^HZR;`hd!UsJH0GRBcJPEeLx_1HI6;lh zDwmvDr+RB}@h4bjmir3oG6aq+SualJe#koB$g3PV8gboyCXZm%YokX!72M$Rj#V$g z`ZV%BWHcJ|(@ng?XOd8|D>7Zk+A>UqUw@6$^tbTfPhJP6+!QnzE7Qo4932UBY{A{w znKi_OSVS!IDOV|RNFQ#*XkMn_#4Y6X_o&UBTIlEUw%47izRCw4rv*K>;jKsJg^cyl z4`TiLZIHuWKHPsa$R`>lj#)vK0D1b1nQ8wQ&DF2GbY)wkkLjq z%{y(XU8C5c-jL>uds6lbh}xc9<}3F$-GM>48~E7 znYvY`q+zd)#dSUrr;8KTu~biOubRh{8-;pK1)e~BMk^zRiG$Fc5J~%tjQVxUmlA@N zUA|bQu{cck*5V1%k$8Y`FIKm9j9|{D1d^opH?g#uo+8uirAh&oSV8SdZ2Y0YDsy(M z4lz(;b*$Ioa}tE2cuA-GFr`h)-yr1xLzcZs@wv|b3!jwlJM;2AH-dxF{{crpxW5CU zoBSv)idD%ki@%f3$4mof^NiWjVk~2wv$w$X&gfoPa@7_xMaaF>CFLt!xI!t>A4`=} zc3QnmE$J>EYp~95Wimxw`nY>xnPQpWWk6;OrN)824fYOY5mfsOmkKDWu&T<>Y(Y+9 z{fmIqy)_S=bd5|gP&gzy*E2O^mE15S>Ti(xlFf|a)Ss_sT3nRIf3}}u)Or)VSp7{8 zMR8*Zi11f29;302>|CAnF}$X+$}{x%!-I;qGTPk9v09Z!tA7wb&&kz2rdE)okTuDs z-IktOskB+)e8!$Su5I}^f;noQGpW3HX~n1OpVx6Pkb3)Omz^uA{F3cS(0$f^c;=VJ_+5Z zxP^-v!~%)*L9Eb;Ju&R|j{ zCga+QZ!HU>F0EBB=aA(HPQn_OHTMDO9q^;w!6c+&J1Kt7bJ>-|C(}W`NUl))SU$BK zkD7)ZKJpI3)tQa>@191wp8?`2$q{h5Kss1U`{<)P+qtY9_e zqUl54S@mp;!{D%usd_r#Onp(oc{-ka9b6lw(8HUdj!C#n+IEV#L3EFf%P)pfM?v3V zNNp?(FsZ$$0ES|3dsG3k`(43*YsiK4zD73Rl5>~oNZGK8=tQD=4>ElON^4u*v0aX? zw~dJ3n%ln3@X;w>?@a8mdQ94&@N+G19%ylLy#6{Y%*iz#Q?Di4)lsHBxNPG=9>W0X zfvU$xC-Oc_KZ%i}FMbzx5Q_^YJR#MR98)$Z!=3R!46%Ptq7;JVFBF56b_!@8wcC7&iFwZjJph!(4!+EUxu z6V2UNJ?Z|0)wdZy5KR)H>R9E;;DE^UeujspuBJYPC5t7Pm!u=2=jqG0MlN7%3NPcL zguHTw-DMYp87rBVF@gpYNk01O%mqDSV{YU3>AZ+=QCK^f4PdW;8v{6)_Reoj953uC zB$)F%tk=f(`a`B#elZY^W@sjg&Ki`kh`N_1PH~jAg&CfQ?zH5Kip*eWT3CoCw{V6K z-$*@aW#vhhg4n1Djx)6)jpBVlW?6oZsaVLwUMDA6dDd7W)<{5Fq-gb88g3HDJpRiL zgffmSg4-lUzARAP90}DK%SxU)dHeeM++LUapfu zf_2P4&xk!E?B*B)AID^>B%3%Fo<73^q+S{ZdjvjB>tnTB3X)&eRD~2ZK{jKcYFJJo zN3Xl+1fz*jit*}ONqg)Ms=yRIK4EK?a}4S*(m+(W+Fz(=o?q; zq_Ko0`x_aN>x?Y$<-HEBdw-o`R9;&EZ?_dZEauM>Nkdjbg1ir7bdn6kJb2OtY&TIx zJH>4wn?ZPXgLkB95@5edVK&hZtRAqbD8%6X5~buX>f}mo$4YytX-Ri>%Cv%4)8l~& zs9LTr%YE!@fT8sDN4UCtCDz0rwA6i|GyT=Gfku4t4WBKP0EJ}R>W@h8>PbgR-K@X@ z27vt%%fT_J-_BktjdfYzmW~M!L@y>3G4K=s3<|YfCU(tkY=`YVNb=sP7YV^}=&s(5 zot6cg?c#3NK-b27)+!ckU`u06s=XYmnwR^}j0{X@3})qCwI&h`7M5u@T2jE^q5OJy zI4$8_xDt~bHzO50O6dsA&TP(4cm(kQQ#O{CG&>&1#Qw0vmhZEMPlS5)V7DUd#yvEx zGia=!Q^trS#B{V1Jl|BJ#-i{j)8#iNW7Nr$BNI}_w+cU8m8kx+HK6=zXnm}5^xaHh z;&$6Q#Pq#e24UT}eP`A_UnkFexiI(Su zB~3^~@0fN}KNodl4FL-%?u~Tpq7&rn2#2E8&Z%sMJ8Z7)tVO&@8PF3-)pzfx4VM;s zcUP;U%8)V-$Sxgdh;7+&v1IC_V9z=U3%|aIEyb~W0yH+ipObLu&VH)ji$(hd+)W~u z_lYelu~4)u(Z;_cUj!ioF>CcQRx%@8T{it?opn@=Wg(A+BrxXYX3z2Ky~iYp;DGb+ zm1y~0KQa*Khj2Sq zs5U9!Y)ygp#8!|F8udQ#z+{k<2*aW&>)O`fzrc*7yDjT^%oZN^vulkH~BcJKj$=Gs~4X~J=&L}y#> zHUOn=LIe}Tt{zp@iv-fSzqNwH(K;)mKB56tT)dp)a>ir{iP1XVrv0+bIvEYJgIcT~ zO)x)VxUE6ML`;@#tV*7sEeXC|RZoQLKAWvXs?aDsm2xTJ!3-0Z#U4|C*^V$j-qtna z04DKhtu8ynU6fRU>3iOUTsM}4*2?rUPNsEWv{44ZupEr`b2fR9uEfc#1NesNVu%c? z+)z~|u$kZDLb93doNrv=&z)iiW&-0_Lew9D_jT}jUB|y%6Ctq{(AQn_EQL}h6!2H#WADAcLD3 zLM)=O2A9Wpc4V|C<%Rv8&CWG74|H6dSuI^kzp@Ca^|Asz`IR27v69iF&AQE2Nh z5GZ4^0dotp_48X~2)guFx5u=o%Vbf48i=?XaqCRMqf(hP@MoUQcE#rtRbHfPEZ0w; zi^uAcD8U|QhRu6i6FmRMNEh+1AFcbUpQsB<84{dlO+t>r}kd|0x{7qumg>!3cFhbQUG*eW;TWADLWo9*h%%eloYj<U_SQQ{Ki(vd~J5N$vKw;zTKNV8E%maCL07+ZKnxbB9xg>Qr=h7KhELalpi` z(E@iE%#GXdp%dHoGhIl{L%tOpLc5bS&$x=-03L*aj9Rw0)CmQJx(Z`+ZVM7BDr}p| zT>(i)%1Ur)EafHU$#RI2ij`}VVUs^w6RUfyUh$`=`S<{SGX=GmG#3M~qBFC;OIfen z&TIJ;22UTeOZTN+Yip4@c2WJbPvu0kbjjY*L7cVdFFFDz*psy={7Pk`aU!92kk)u2qqf|=n~Gk40|ZK;M6LU2#^;MR+D}t+YBL{89al@5ZIH71^TKjcsI6k!vBs3l$Ms@o+_&Dq!Lrm@<5d(LZGm93!UK3i!0=Q1a zm(H4<3u$Fi2|w1H&45#B-r0=^PXiswc9-CP;*Crca9F((erp|?wVpcqNk{MQOlre| z=Y3X(?mWP8+bo+27-`zj(rq-Bk?$Jh3rO4%;O;Y}ujk0=S_J!H`i%PAQs#8MqlWxw zCMn(+YY0gHh`}?~Q`?z~X|0tVHpA5V>pUXn;TCqj#}P@ymTK3bMkI8ZqQqQG5Cf7z z+HTsYyIW)Oy>SiAlyqUy(H(IbdX#6a}JI~kTnq-Ai;1NFYe7hF#snu57*3Rc~ANPn>pT(x}%h)I;C#RW`cs z<`~DkX{$`s7umbmIesggHkN@6TZ2#>uLo~j+7!i<4|%L{z##w>rHOCMogq5_uIL3< zDce&HHx5Hu^V9Xj?||v!@5Cl_g+Twt+?mIkotJe;NKMc+H75yW9AhlmPhjKsj|{WO zzj5(W?T>Zi31&b{tb+fonbgFj5711$Th!6M!5Oa~4P4&tVYy(@C!%d*;VBcDboq^& z@&(c=Wz3&fh(5vR&c-Qu&0){yB<>!RNa&mS#IHeP3QPC0t)!*X$<=#&7=HCBp7*e7 zJ$s_FPyHY=+0U(fS93xNbklcjOhYf74NsEId_v9h$XfjRZQ-Ja-)>v^Pm1z5(ofW_!QEXPYnDJb#Vfs`T1dgrL2yRq7{~)1lF>0{#n>UCpgmGs$Kv+nn*{eb zjAO;vRpxiKLVt}F8`NBB{nM?G^NaNEVo+Ej`YzR{>3>O+b$AkX>$WO+K5@%1k21n_ zv<6Y%rTa~>yOOop)?D2{JdKq(3N!b>dq<%Ort4@a#a+-lvB$sG3~o}8x?*Z8bxnG& z(xpyzL>rv&`5a7;r698rE|{F7^-P6!5j>PWN^Sf7{S2b@UALK<)c`s%k8{C<`u8<| z*>`uEh-Qhf^h`mmxw)}p?Qbfp*=#{Ah=Wr%Yhfkhx|v$PYsX6uQTm36r$P=X{_ZfM zpXb#0M8;rk88Z?25>&L>HvHHRkuN64_Q^xj3*|j)IIAqT`*dEQ-Z%kexxd6w5QHM8@ikx#jj$^2?jkV^flpMqNSy znX2Er`|55Wza9Yft}P{A51A633;ZDCs$0df500-z&JJMB13?glt~ko;zS;(v_1E=_ z5^C{Jb8U%FvL)d9vNuuzs9Lg{bFkp-tKbkVuU(?Meo3Q5&-S=z95?efGeX5viyx}9 z#_KoM$$T8@y2cZ+8nPa4`Kpn#qu&oh;Ymt>BFAL)Zeznh_8Ppi*CY$aIu$P_81MKw z4h4xz*Ir__bXic=W0u%AY#<6{ZQZd*;}wn;CdErBV~B7STXs3ho%a11EZbO{fJkG+ z-@?lITA#EMr}96L;2sBVR3p)SsJpw#Qph3Dmj+4QI#GzQ!HHOsVs^`V91M(~<^<{h zsBVW+%#J77+bwMr)~DlajGK`tH%N*D;=N7Bt*)ouk!E#m(uc&_Lev+zIsS%Ag)S2ktaGMHdCZME9rR(&s|AOua7QclNaWeWq<$7~3RiYUkJtxSYYO>j$v82qy3RB#2dHqTj*sNI>j9B|ilda&t&LjD4QSz*`br4_J5T{Zz> zZ)axq#agR8j2|((zLtfpW9H;F-1caqV@cuP3amdBj)|cW|Cr}6SA2eO5EAo|< zSIl4Q=5Z)-v2%WX6Yr5-FznSMTIbrTdPUX?) zGkU$oN=AJ!RPb|es}B&IcO-(3tCRwayvT0WDV@=m)$hEm9D!SKC8`j&Rf?Pd{%FbQ z9(PLrLU6EqVZYl^r9T$t+t#vv*;sV?N15x$?V*mac= zdWS^Fu#iU*4gG^Q5na#bU=Bh?NVs2fMveytWRAP+unog9zD}nrFSsuf$4P{riXLK%_7+N0s(dWa1?iyRg}~Pho|OAD8ZZUl{I50mQn`3|*o%nUZb#}!p3q8rFE%WTp6U(snNY)6 z5k&?PpXj?ea6FrUF9t(CE6H+B@nkQT9Se~@y@$`+Xe3K2Ugt>9BJdqHD5h*wn_dz~ z`+_!?3DK4ktnk&P(O7$X$}sY*ZMnK3dc}T<=aY=vZUVan21nxqJFV}}4@F7uK3`L= z`ujE+o1O){&jlo}7cLYptsQ? zv}2WyJMxz`RZ3+e2H@=2gtwKIhCv;_m^k(e7l~lc?VGMdfLwZ-`ju2QV`wpiEicl z#J>Fy(Dbt2$B9UZb?;g`kCc^RYq{S<#e3R!EDjOe10STKJM0P!ZLD<&Yjv9(*Y%nZ zSDSszl|-_ldIF^aG${tmUlZQ><-~PL#SQwzixHdiM9L@FjBIo4E3NWfu8cn#yQlQdyShTfiP>t3`Tv;{a+&Mdh_XJMpvx>?-B$hWSbJUPC8?0-Yg7cTP-7NLeykkO zSnhw%X)>W-t>G;9L`A+QsRKVs7J(w~Y4W|Bm|n^CWXtVl)tKA$_j4|=NbIiAeat0F z?M=MDeog;E#|15}L z$}ZI!&d?jmLc4fEc7`5Jf{x|1m)YN)A5GgJfkLrSvjVn9chqY(2v@GE&g@dvw2w8| zMf^Mk@OjqbCfK#7(2>1!<{s9=z*?S#vv$suXPr?^I;_JaFW0;V|E8EBW|aq8Khcr6 z*kEEo$!KhiI>63DWtm;cF+AlK-WKH}-6A3YRxI7|NbOm$7gVj>c5OYh)dTpX>%FKjELF1SR?YR{hZ~EVd9fPAQIKSjCBG-C<|iuW|uBC91)yUz@N-Dmr2| zNL%-tlH{U~*hSkaMf-X})FwA1ydP_Xd2FAQQ^>EYasBn?)JP@zW4Zrhc}YQu>1Pmy ze!b9{dRPM~G)g2<=jD;HN498z_skyL3YduG|&s*t=I}xEC zk?TpEuNxNYvzypo^1RGN1mW%VoEM|CSi(AICjOm;6gw%%j}1>Jrb%$D=xFrRWkQIk z43G6oV0(fl6=-kej$I}K%+Xd!_2c~uamP|smK2tL*0x8WarzD^QghsA$Y!q=v zKR|Q=r08*T9`RTa>AJSRZjRRKVRoDVOB3hw9R1>D&d}sS8nBg0s z$COzJ)q&s4HSFBsJiZiNhiMb~+ZyIPtjJzJAS$AkDIgvcrAq>jJmzLjzEU5R=+@Sv za0Hy~5otwEi1q9)tXvsMiTJ-bBI2RZ4 z=8gfvjUw%R59wKq6#ntfvI%?E2^82Z0aQkF@%|Fk?|VxkZ0Xtvxt{2P9WGhCY%LqE zYTR^uau;Jh-^A6kVdjTy6fwZ!-7puifoSJHYXF&$BkRto2vVK&a34v{fD!9PY>yAzp~bY-@80}zA$z}7&9MDHdsJ!; z((%#bBJyfa$R4_q! z+BVNrU!lU1f99kf%#cuwqFJ1fpb9}q#IIxIqF%Aq%d-W*wFV^K9tTHoa$7@?>(;(m zAayFA6>RiEJCPYOCAilO>}0{>Ye#uSCkD(pR!uh8RANWI;3WMr z-nNx2Ue#!gv6lndi5j7!f0xp=Tw4y4I!t{|_qEwVS+QV<2?PGV@UhdgfWc%{Qc>Vh zT1m>zhHTpjhGDL1w7~=~*Tj>{ssc)kdY5-luv+)XeMqrNo>69NGVI4q7&n&BSR+XO znZV#KApL-}j089Fv~SdT0ub$Nb?~nXWmoaxxas2Qvg`)iSpH+;Kkk$Tm29l!dy-S# z@Q`3iG4ojZB1O*I8ji{4m#rgTWTBQN*@4l5MZFRx9EeY%jDoY_fZnr%$NslQ?ml00 z>f`9Cph#m#WbkSmQJ&YXh%|r$Z#(cPm?UGF0V!GOk*FRP9?j3%mB*(O)4xKjV})UR zb>)jW*wJey(5pEvEsa$Cuaw~_ph{?nx_5?bq^{)K37eS;IzJ)P-7mhA8lhb;QdT%F zTmNFu8^&l%(4Iq)gZV+V2tTdqKXn(c)@U=489@V^a6rGe22$EDnCu6j14w=@O#NXJ ztUjVv(i;NR2c$+foOT_Kqv3}~K^y+nO@-eVTJ;xeY%5KnyhpCwQ%fSA%zoR31&mhI zT0aqFJeobeFuBvCeY89Uq7xsBrJ6v8E4OaoUPz zJ9vwIBYP|yt3xf{MH)ZbTt1W}hNYWjjxtZC@zyT`9{n(g)`ASRT`%(n`>quvy}gJ#kk z;dNjrMq|$2FDwYw{i%N^z^vE2=6sa4jukrB$z73G$I1MEi_7#LKj+z0kEx%aBvaFbnBQ{Nw=ls zBMC&`jmCd>xG7L+M+4?4T%&bOS_eu{Q5+8Ws!h@ia)o?Q%1cshjt{*^<7c#< za4Yfejh1%B-EPq3Ygck0d6%M^E`ait>f!A|q-@-rU*Czx$}~-Hk?1H*nmhWo&7nB% zU7ZcK+q?DeAUxa?1ozLcsaMH)0v#|~aKOXrHNZYiAW={{{f{|YH#On6Xi$nJkSDeK zSDdq+aw2;eDS+o)#8|4m*s^Cyx@2{wu%pydrKu!sDM#VrcO;VtBeZ8FzF7cw#cx(v zpjGay^HRqL$hDwb}wHTkC4? z(cRIe;wHwDa9Iy+$sKeHHjp3^Lu?fveYplS-$tsx6#Z%(NB}l>6c=N=rNAkfonzMN zHVSL?_Ko{f9IMxBirE(nGWtk%;Lc;etO2GrYIhCn&inRi9(`&Te_3;^wgU;W*?(-& z4AfxS*k(sd)8w*y?qDp-HpRN36gwSB)~0VtsYj}-+NsO$*YNIqmxCrB$}~#jmC#LM zKD{{HujJxWjrn3ue0Hb7c){Co<^G&V+A74h1{7HpCebSNel$99`!FXq}=gj<8JAj)NBFm=1O^YtxUF3Fi zuKGPmAYyyVi2Y@O&3?7;yPW3^rJ1}E5|>CQGO8!^*BoMHCyA$!2tWW^-1QF&a-0k@ zZ{lt7`0~W--)=Uie>M+7ebmuJ9JfG#hEmFMSuJcq8`~L^G*S0b|6SuCWs%}{KU?UU zNu%$o1r(f*rTt?pMYsemewy<~ERHx2v+dO+za>${JP0H8^0{EXxqui;JwIZf?4<<^ zlJ9QLkaC?d#yUSId)UEoqGgh_&~p7juNrziYTa_gi;jHCkU=Eb!+G=v&;3YF5}|#gdxuX_v1j@EwPg=&=xL zP14z6i>^;4Q2d+)OxPqPqe%>P>XOyhc`O5r{d_g8|7RQgjZ%xa>lag39FLxhJyykU z7HwPTe}M-~Xfz?daWm%s?;7pr*61mPD-5t#Yf@@rn_o>po^3&|Gyirg6MT}f(wFKz zcBYiW>aM+rhW%&KSYEk*Q%ji`3;!sQG;wuXtL$NbDKs*Vxo@YJJTXtNg^)B713i&8D|oMoR$*MT#eQdRt2&C9Xo)M{D2`VW8rBgIF#&MK!N)0dej3 zm2MaGAf1)!|L=diD`9u_+S=U$@^E_xjUu0Ibq8mM%uV52GLlp&lR!d0r|QK3`ckQ+ zY-QVugN@}(y|_6FA?u%vWa!vdHkR^Af=L=k1m@~0)nUgX3gL*pjCAWC69E6k!i}d~ z{#bLeew-60Z7*L==tYIf-z|7F@p++PcpKepLe5Zno5S$t8`ORL07KTgLINyph} zK!490%>FpocuK5OOmc@j-iO}S4n(~xYOD(Fe!wpMon;9prwdY-rDv*P_Vt2*=8SBs zekpP)Yw5M}0K04YRsx_Adr~MY)#g91@sVH6`SShBd*NaynYdVMgiFTl>Jmv}?$-I` z&AD~H4xVMK$+qHMfvM4jfzPpS!hItA;X~;LXI7OoRsc2$h>mvn_rxZ(I7E1?S)V_w z(ON;p$+PYy1b!O!S8CD(oQd|!ITmxY+Ei3`?MmE*GQZKHYVdsADe>QyV@}E>^;~mW z26*KO{(sr<<+c?C{qY(=@s6_FKP>FVedxu4xApJKIT9;9yc!m|Sn-b(rY1+32kr4G zO#QNft8dHt4d|c8{Y9HB$z-zZ`Un6D^)?e(rxdl0Dc}U{*_!d%DY$B#h;3hLbs8cA zg9lG$UwUsGsZ8-eZkBy|#R_EI+%wS-;RDHv`YVhaj^drWr{gU~VG zO#J-O=D9e`YwYyR%?a#x6NgkN;GSQ35ZV$SrYm?;r5fw@dC;tK=fkI$yt-wyZ{k%pp;rU$BSe%n2NWPU$!?Cb&h+%l_GW&^= z)1ka}Xa?EebOG<&QY2RV#~OK=+OrC2c$wY^a7J}k5iMFw}i`_cQuxBe4$%kX}E;-R8BBT)Z!4!{u|O>3WW`Gw zYbdj(FvnBb6p-cZXcE0tqvR{@Jp0F{dRet$HfmMeW5x4bC&T6=n|5zYHevUk!sFgV zf0V4PHESqmx-@WU=}zon|J+4EnIhY&Rep6C4o+c-DoA;VjeI|+{js-|=8|r2>DGL`@VDb| zDNG7z;dcvd*ya#3lK+)G;oCMzxtr^sAPGdcNcG#wb*y=6Nb^fcg0JD#QG^rX(bdm(^h?{SPM;wa5Tp08f# ze@Iz}?EiYN?3UXMQzRKdbJ=Va7;ml7Ch&lskuyiKbUAHn3V?bKAxG{EwI3vf!>97XGmT5ZL_|(Wky>pB`e$@t)APPF=D79$V zW*4^~D|f@XwvA&;m4RK5i@jY;${I_EYLy_1YHo`1O3NPA8C`D~B{8?wz;#Dm5aZ1m z5+3iCZ~#LXayD(ho?MvhWX_Pr>gY{Y^y@jUF@=RZ^l=jat^1zuNqMXyoX1e`Tb)OM z$TinPu`elQArYRDfVSU~s}5?_~MtOdG8kLq>+ z_Zurp76&AEs`@Jrg@)=V0qUueA8~%cfA?~Xhxe^Rl1rq>cSQ?T7JW14uWdJxxUSmd z^^1}2ouRn92D|HMjU+vbG}^SFP7yBj-5RCZyMPt&-)4PXR>>|yIyh`(Gmd1!;LjJr zywVh!@qcSJmOIK;4+%%9!)0Obhdi3@rS>Jdtm6C_{Hc)X%FY3Oeb_bucLmfVFBY`S z=WA#+Z)LRDMSrb!%bU9m=bF?-~4?OXmPAkx2x+JsqdtA zI(;-9IqfQ8*X%HLg?TOWK3c!q9oE+u6J>n|F~L#tI_2?6co8=ygyMXrNE|LtDq74A znby>}x>=}WG;)pzZGzAx(Mi7A^heWBFy_5J}S1ZN|^CA3DZX*8L9kV_An_R*z+< z^c2aC6xr@VSSlHQp0mLCBisi+3Y+@Kx_tj#xZ^T``je>5Wn5Nghi%r?_9Cm>C>a@d zrzCZ>F7+2TXJd7$g<>iQ@)N@jy#l*Q!rhx{WauysPzE2NhelrJOiv9)C zFqCI~)m;rGDYJ7hYQ|G&X=nGxz($njIE9zjqPJkuhRZ^7QN)?(}~}zb$-M zckyyTyar(BRU7Juqohvq96J+^$wgjAHpl9<$@b1+=?cr!#D2Z5V;nXg+rCJ6-h91= zs;fqOHHS=bqSNa2)ivzL%S0+TRx$m((9~~RG{%@F`f=zE-kohs0SPpmrVopm7;>Fw zm$uHzB#G}yLEjCBzg$y~g-6E1{JgA6RYeT)#5;e2*!`mwx^t9PyJFju>+j>TMF@0vu4sJ%{A z(hqBRSv_Tjho2_Au3U&u9I0D|s3U$nI{;VqU*B2@oLpN>-=OkZ_8#I(u_a1;IGy7^ zOw4(oO1$=jb)c2Ur;*`O6YJV6_%2d$Y|RMjpyHoiG@d?O;gQ}=*(Gbc8T})M-D$eY zW3Si9-Oflh+ZPK;noaU@5%zFL#{$AJx5}#)PQAGciz#gDU~xY$40*DA2P#yjfHAU& zep^Gv|7_!HJ3U_*#Xa#I;gS&xBLSQls~2pv2xOXr>}>VZV!wVprB*2y6cY$uZYb=r zLk`@L2ED8mM6cIt@j9r>z`WuZUJi7@>&m9=azJGjW)mjczOKjU2p@KMzmPq5$IpB=s<}c8-%Ig zqj{6IyWCeGAa?tq?LFC88}D(??C+Fh{WjW^k6j#V($HG+_AbUM4}7~{RnK>&TBRLk^@u2=tx%=c7mVg>!=Zuz@5EATR_7Izwi z%YPLKD$XWvZddw#BuacVVEGusR|CAYI6Ke99@wvNhg4>@BS(o0)QV|j9;=|DuAJ;7 z=i9r1q%*qCQvJI%*cn^pZP9~o*9eL4*Emk{n_T&5Wlmk1xAT02;; ztHBI!n~nNtq?2@)?s?Y0vM_KyO`nAjklXBaEMu-@FM9?}6Un;G8dUy~f0-6LI6>PK zhs*Bm`hQuI=)b%*anx*0R^Kb(pPDllooq4zHskr+T+^(wxy(ItcQr^KZ&jIPY);2rV z@WE^*L9xE9&WKctG0%<$)nzuScjYM1+9M(IRwDoWua|S&gR+Mpz3jZLj>g$(S5GCC zaJ67n>~1V&6aWzInU3l8LYGz0TZ@)n;9#N}IkEeRuO~eF?#-BmwG_9-6CiJRof5MD zt--HX`$}M)CA9ToO|{rX<0Fqf_CovbuHmwp({V+*w*q;`U7}pRU&9c`E#s>?;w2#! z?d*M&`DvxypNK)YqpL1M&+}fc4eCl2jWwiS^7MaeqZY;?YSD9Hos<*gk}^uer=ovS zGx9s>GGju#STpIe)c>r>^4(L*`Jp%TH@nX2WKZr@xIJY0ybU1h_hn5q$L+E+wNSg_ z_Fr$#IuAv->WhiG?;hFnHCvWxH`>fxrBGuTPLY%po7tVjkacwKB^ z8^z{~yu=ZsiBbU)+{M_~f49LIly54i$I?^br6lR@xR_6bE(@oqyNU_B@uO8Y?Mg9& zLo$}u%l#@ah>->#{`k{@DMs}yFrbg#9-2 zPIo+GHSNxjy2SWX;U9!i@v}5Hu6Vr=3p?G`pV);fDTbYwnuk>%r7I^@o#w*)Zw(0E zRt@204yN=DBcH9c(_Dg1cDAS(P=vR!^e=0!bnMdZFZ5=XULlrLNDnpJOH*m!ov=ZG z_^w{YCrHoNxJ;Dzb0`~rnnPbUJlk;-$$6(8_;JU??vHH>vm|e4=AGm3aWf7Dx?_Uk zmXw_^Cw7?dV>yi7%KfFyQ10~C0xCc1_gFq@&)+#si4U+>`H2qOd4JGa#x=q}^ky7e z0Gq$90ZFlu)Pan-QG`E#m{aL%g}d_FR#-#TVtp|=jvm$!L`qn%Z!$qj*kB1n4qH_|ayxfIjg3;C2U<{&~>rH;azs`QU{TG!A`^njP{4(n$ghk~fl zhHxd$oLy9tNTO|lMIBSPb`HYJIZnV=K=NXZf!jA}7?lB&gHf?cHVI><8XD7jm zvC4J6UUT=9YV=gsvj)sX`G!3p((mM}ek*Y9J=_U z^P$>IJ}6xlcvml2O0@%${ZYTVE!31=VgyB`!|3&uk4x1_D`JnSMzV-@o<-y(-Z5sd zwK^uIJ~eUG{!EQA?)AuyriE6oI~p!7Ydqpu?Z6$)sg_Z$VQZ}%4&HI2Ku|wz|1`oC zt5s?1ahC>BGL{}pjb+-Iq4+*uIPLMgR0TuZ?y4=+_qU)eApkNxsl7GCn1nBWTXT35 zB$UVWtsL&PO79S4$&) zSQG8X%7R@Mz)R=t;L!RveCt_b+C+Li;;Z;|ynz0GuZQe()OzmXGJDxl&7LX>$~t~W*9e{J7csM1TvDmi?dv&=rhceTC5ZBRIp^I=nMJY~ zhv}{RbxoMH<@We(I;}>FA*V>})fz`KZsO-B-JkG9?^F19PH$n8``vC&jwyy8qQ~Up z(2F$%An)*NL=LS+{8rR$V^PGO;p#^QMX+azdwzMe$21PJ&UM>mQ%yU6GFJ87Z>|km z9XFljaWnnhNn^`6K*8iLqC2v^V20?#f7d9|oyT1q)wd(!d55zQ;ZkCedKh}M-y_)> ztwyxY4Aqbvxzo5*40yX`5RZ4RR%EbC(5^|JivMlG*GKB>Y;H4M#yv==p)b)h5?6n_ zpGXq(biQemO#iq!U++^USd=?fRLGrw<$uTO^Yv!xu0;`(*AuEu$rC)8-cc1E$nz46 zilFsgjNY&3%obc`IpLQSjoY!WN>-h!=b^GW?06ecz|Jia4ai*tG$VdxT~!Zgs8*6x z?H98vLRL(*yMS&tM!1~Ziv2PNCiW&2H$HBTqW)O*%Y$6t>150j~55+L+O6}Rg zBZ@BT0UytF=bK0N|CzJK#!aU?zL<@baI*e_GdWyI^_-bY`|iI%CUOI=N1@pd6T z!29E77wyp<<`?XO8xHPorm-be?!V0GdmRl1Pxc?}Z6mi4K!bjrP;h|_rh->AAnV+{ zo-C1sg~0?~yqM!A;{&Qx6zui8-%fDsDBNcgb!r!2L@(w@sq7mRq9sx#GN#*=y2LD? z?1^vn^mi!v_t=eTmK-Z)^;Yvl{&Tvz)n67wXKG`PPjlRiW2*?wN6C1myfcspc~TwG zNP|zGsPb|Fm2?MI_utma#RUmntwA|E$E0rsf*9Q;%wapbajX`J-J7_@KJ)ED=iVCn z#8vo(UB>Nd(ZyE_1s(D4kb=6Ls>@Msfk0Wngz>dzh-SQ=(_vhjNe~QLK}co*NlBL$ z+1wSQvIe`YsBj`BD?ki-`QR`+5Hn=-~{aMgu!8eKJ;^gB}?Ap8R zs43g-#+6uNJ(M02tk>N*>}=c}qV7ar)?DVT;b4=pV0+fTy%aw!@Ntx`YHB>(S701K zY(l{*to!w??q0=V$qMX>cE+_yh)2v)vCY3GTD};m`;r%~kGk;kTYFc6gvD4%d6!yC zp=fPeGepEd#C~CeZ)@JPTH5Td!zc|XO(v(`IZgqDaFriWFr<7Tld4JA5 zH$N(Sb7(wduFyaMWkD28F#d$X>a@LOSR~E1E{HVl?(Xhxjk~)xRzTrWI5afw?(XjH z(74mMyK6%ujW(C>+vn_k_uMn*-kEu(epF>dM!f5-m9a8oMMlITn)}!=87i%+PM(pE zfNJldpJEF{15aE^O|4$Q@FnG!>h^)3JRy%@9Vw4$Z5BZCQy(Si9DIvl9D1yWUP&^a z5EmS;kZi*>jpW+-7b+7|r2YfW*lVG?;&16He=3`BD%1tT4Zl12(f?f>Rz;qPL4zOh#+SZSqo@Xpbz>{@=Q$W`5FXljyPdwGa zMmIZk_*jFtMrt61?S7!?N}O(yQwO_gIVaBZNlsq6EReaTw@JwyfX2ESAlngyCd0d1 zL?9TGe`0n@7Rf*5srf|1K1`O2@tG72dPTVkv45j^nUXENw8X5ZFIL1u7UuEXLGHu?N7H~ zUg+Xx&I-cDt7&%wA_Nff%|7mC|#b}|0tJ7J~aq&?Kt2_gm75dRvQ4VU$d3L1q}bV zZ5_z&EIbWO&Z`tXX#?GO)jwMyTnbbTRvq0kih!aTd(N$n@8N@1)^NX2qqaC z=tqeLR@zVGib=QXyXR)c39GVurm18)2D2v1S26_s-dhisKS9k0>obyYOm6(VsD)=w zOVgLLSV-Bs4K<(}n1tpV5h91Z!k(E`dY{oMHQbSjd1Y2;c32WJ34gVeg}~niEu*lp zY#a5it?8%a-1{CSXoWH?dds1{rql{f9hxCh=VJxa$u)i|f=aRQLc7ws7(%I4UTRI7 zBi9y~5;B zYQF;xTk#u2Eta%3!BRP<&g3ay2CWIsOFGP&04i z<0tG3%*T<9yH(LY?l!DBENaqkc4;l^bz-`MPNzB56KgA@ zZ=As1GDl9E;0t1O=MJsCBDC;RywGGFV-R^U#I(hJ{J=(&r!s#kt7)>nH_*}SKBu4f zyd07YFm3Q}D}!n&+0{x#bh6MU>|g*0Tz%8*tB#bvpRvl9cg`(>gC?F%1wsk%FT%9U zT+0Sx9cICS{YJqjz;1x&&hRNi_?6iH7{i$mWl%b7@bPvrxcDIe9K*n$M-|6YKuK{* z_BdemOG#XUJLP#ZXR{`8DN{C{(Q@$DAU4OCcK)HLc6bd#XuimfqLMg| zQU93E;I?^eF3EklejOZd%NW$xg&w(K{B1GDo7g4Bv^6TzQ+CUQWepKW&)*62x<)3Y zW+*KcVoBNoN7mtNzjv+4`g)J-PjQADA@8|{SHIfWG25y4PaH6gg}(E*8cVtFmA3D$ zsh`VEeX%nc=s&nsT+&x4(hPUr#AL**jcbGVw1bfzp|6yWN&!&-n*~bq_3O8S&^2E| zuL?!&LlT4qf8tv7*W1=D*}VjNju8PgA|T+uL!Ojnw9p*;m9I<_(YaX1_4DdKV(&iX;=gXIkQ_grhesh2wrW` z&`A9Ziogc6E}Bc8*Yf8tHK9?x@^GH5zjRqbvOLQ4|-jsy>s&UJ15=00^QMYQvL*s?X{BL`w7aCI#}U7zs_%D(hUSI94c0u z;gH-{-_J1RBdKLT)nNLhR2C(1PH?Qpzw+~B(;au7B*-t39X9GLyk~mLot<=}XzlKf zhMDG2v%+{NSP~MudrHZ76<-txh;r%xj;a)C6_Y{lST!M@y%LGdbBH-qy#WLXL&ZwL zBD#dm-g&}?8IOoxQ5k|i-PBgU>%B3EA8?$|Cn}d(z^^A9J7oyI0Y-7}emqV_N4A*) zH?iwzgx2~OGD6$n-9D6ESqUM zjh&l0E`v;o?RG*>I*u;E4{wXxhcRcCW>o!ur!EC0amgQ~tM5A>EExu*Z}Np_8#nJ@ zSK~<4`DW}xj>+?32hoMus^hBZVXq8g=f}=(Fh$CNheY=N+Ujt_d^uD;7-fGD%PK_{ zc&%rYrENS-n+|+2N&9v5B%<%VifoblhKlX{=BiIW2r1u}u!hGhz!ko)AzIZM z`pZ6PZLv6XDo@U@wfPF%pmMPJchFGw|4w0;&{VKG1l!xNa*e18Q-Q? z#h4|KfKzOTvw+Xaezz$wCAq=K9A)Q5CX1#Mf(pvjLqMbg;I3%Ejq<~~^)B*{EDmGv zfmVxQ-#ZXqiFy5hqFdiuToq<95K*5sH2;)rx_UZZ!nTeR8mEVH4C-T)_k!|U-`Q*X z{mme|xi-k!L&%%@+^KTR6$Vqr!&de3%~bR!@kFK{SFz2&?+c+j&epBG6PD8|#zZE* z0H(Sz67m3=eVp1K5Ul{yj`{ho&`OF0K>|K_Z37;KCut&{o<9)6_v@d_)XW9LFV!ip z`YEZEQ5*i)y!K`x#HLY<5>LH+AKJ0=<$QSQjyDZ|L^$jkmbQl;U%6QPB>Q^ajI3l> zD(yK%NM*p0tf-G%LcABfLz75y&;p7zoj1l(79}U&&K8AR8(e9PZ@?5r%Ap~V`9?Wm z&F?^;gfu&uFBo_j{HV`|14GaTuP+s_hEo*CMz2Ny-!Ql!16xH=#NkL%({1JiiIPM} z^9PzlIDHE)W(~pVRH%+qA-#b!MCgYis`orPX~4*N0mqeLtsJ|FuR{Zc44qql*Vn@D zE+waUDPVe4TCiK}ypZy|MXu0`J!TnaL^c@K0b5x%tmS9DapX1Gn4^55FB2_IeKC60 zWM74UT6qeT(OxvbUDwLphA1_H2u9TD{Zc&p&NJqq`&!APST0+HWp6eyN4EigZa60l zjb1P=xQqMl!^{Yg!*008rbJA})Cne{i#}12d%yG+XSNj{%|@rfdUk(pc_x@CCox^z zHXryd!=a?V%q5Megr}bfNT~9v>^}2WEHGgvFWTQbo4c!&Jn{Bl5z`$trXJSogj(8c z4BS%yhE$N~V*363;5& zfzf0o36*_JkQwtWSOv7$RY_PJSB1bIk>k6cwu@l$7jNVAIeS~af2U10Vt$)(Gi(YN zLJs8Wuo~3(#ydy@h#nDWkTnGkdPN)_dn6S%IjrYbb-@lXr19`y#WILetNY|S zuurj*)6~ByMtXp4WpG%g(71OyS*E+Fd1W&U_krBCR5PWz9$5`<<+AdMv+fNq!^f92 zl^rIKBjg0WDYta$Ud5aV70pfewImXtbG^LB!PTUp#}Hl_p2VgG(lFMqxIO*^0}*$VhnWB)I!^`sF;WDaRmEJ*9^C)&khOu$LVn%|c{7E2zPdZMg_YVU$F_ z4Kt1~^-mf*5f#{fYj42+M#2l98ccE?RkqZ=!8B%nDX`s)`FG=i%I%SF-I>phaExMRy)huHWJVuj9M}Se^ zvwDxAU6L+ro~zhJe|Z>?utnScq|nHstw_45{w`&Tgp%Zs8&`4jL+Owq>U_* zfi89(D}7eHUQlx{0PWgrub)m<2N2sW!%Me()UX? z`O}3|-W$+i7f6}c*bDIr*xPfYI7tPgQsNt9h_EKF`d06O3XghyuP#?{Dio%EG@8ZV zs2E!%Y7%cosA(oWI-&pcP3)KA*;lxxlvfZmdNMMd(e4+kF!3xSPgG=}j=|(e)dumzbjZ$4S+@1nhII^S>SwD+HPbF^tbC#1 zyY~0zK!LR6HK;{Mn0@STGo;YIFLAPt{*qel32j77Z*k&AME=fuop8dQxtLEp8zl{9 zieEfi{gbJm!_h%SmXrOXCY4j&j2-LU+A^0T1}!T|v3@L=Cy`2;nzGp-;x_qpwzS-# zs0x!fR-=OnE^-$?^w=$%3Fp^OoLihdU*Gumx=~<%QQVUfK<^6lr45EcnBG(T;AC|~ zv2_*tD*h;9wq>;i53o0P0XYLq?EX4(Ftvu~AY~=}>sL^aMG9yO0yvYhNZFcz01^N* z2Xg>Civqyj5@bcn%fl@!O!^;HJ+c;c*6lIb5PfrNM>)`eS2%EFuBV6XR#YA^Yw*pa zW=d_fAdMk6Hr4d`mm^1(0lm|$ zH?B4fAZT$m=BTz(BD!KewV-1ig$74XhvH#*NDWCUHCKw-vJl58^6l+fiO%KO=%N>8 z7$^EoTa{8+5xrT)G}+2Gu1!|DR8o3gb2{*owj%!;BY&dE@yl+-XKd5BRo%l<*aCO< zVZ!K9J^ABg6fpvAHWar{b|EJRjp&vlmEpY-zT7G|40WM2kL{FMNU@{7WjXxt^W-Wi zu!M|oFxA+Q+EkwFajg<&&~xflw~5tfZaADwCl)%+Sgo!89^W0JwAG^#D%hCdBHcA0 zH}5$$!m?*3E5+>d(b=1kckrzh6yfTn4FsX6FWaIP41QYG%-&VtQ)f{ldfY5g_0*}1(v0`D}#3<;+Pp4{FOk_Mp1DFy_Ko1Q9zMT zCBrQVlGjHLYQai+IA6K$j;l}7aZ!?6R;R5mIC`8u(AitdQ)H%M9Ad%^lmq3%XfZO} zAbfv;*k4~1IIuuOO7MJqVq(#4!T)2A0Q!*99?Crb5;avN!L+{aHnukUy=J#}T;fn5 zNZ4#2DlD&MW_enFhOh7%BU0MgjQ6`>-lK47_aA{J&52#(2|~VM(`K*;+Y6ten?iyN zg51D(Sq`|5jZRQX^xsZS(!%YDIYw>Qz`o@Z2@w}Sr_^p~^D`rN0o*pYvaBtK@b_5M z_J}rvgkW6_LUmf)HooT7tmrO_epouf*UxI&kws=F*Y2>wM_Vbsf2YPHd+nnU2Ux_i zp6`PX1a!xdf@+_x-Kv&7l8=oVIYJH3&1M&eGdJFhK@oF21)b9ITpgoocZDa{KV5;H zuWn&uvN(!-lGdq#pknod22C|t{8B~RA@}MP1{!r_$(1x6tQKhd+@}y;`Z3$f{dMEF zo-WD|2&?vx%W=oW!8lKh7_vg?^`SbnCmp#o^Pc7Gzncw zq@*NqgU}GXpR?X!*im9E7D_uE>Nh#Y`=thTjpE7`4cFaFa1!Zp8P8)}04JJg`N`ej=4%lpRbgZTaL-2l6F<$3U-Dfg?E5CGt7H>=6DGD&FGlLn%Z^ zVxw4Ko_9fVFV02wZ|Ztgb5A(;Z#Sb^yu~j&EM{%Ajt(ah-)J`A^xP7cUd(6c07~6* z(zQDU_76WL#6Oo_$&ShEzP{Wcr&v24G+pf9(n0^u7kFe_>2M1d{e935Y9F`|AUbN} zFO0!$z8z@0-9NC7-!*M?TAVAKGiLK?6_egLB;6{I*!NL)hdbp5GOl4%KEq#g4^VMS ziEtYcv7mY>s1k3%5d}(UgyMM=CEafbpGs|wSi+RWMX{8Eg|#rc)$M>!Q0Ox zB~?d&8Sr|&e~n&RBYTjHiIc)+3=?nS+oC48Cfv5&~N?gof?W`%W2#C1v=#VYIgU_y?8=fsWquD(K07-OsLrI$| z8B}W=SSJkG6L#pZzW$RHF5l+T9rIpwls`E=OtL^W@RQEO0pK)Ru>f ztgN!Y2}C`&1wZBsH=0w4KbFf}cEG8~ZCOPcXGDCGOATNpqFLTW zlWj=hNGNvTjm7OLXBjP1a_nK&jyXvR4@r_5F8Ow|vxP3;;<0F$p<^hQzSZ#p^CdNv z(IuU)o^}Ur(m>zvH;@XEGn74QYtCY#%%8ocpOdDvcyWiXs~;^jV!-j1N}eV9QEERw zOG@?&yS(#9TnK2dicDq9K!~g`_wSf63FEJe<3L)YGt~w!rF&1_4)f_nMDtb&mVk}8 z8FtEskvKdG81wI*VM|XLXgw^YTxk}SWK_B#SwGQUxY$!h1Ebu;mc-T!Z8F(R*pWyf zdvr)-@TfQlVS-HH@Tl-@KHgupO+sFqwDq0#h`tC$n|b@;4)-1D8F*!sTBVTc*lspi^}t#6uh@WI!=khz0{Hs)+e{mGp|T=B8Rt zm85!c?nzT>SH>G?NW%3{bS$Dodjq1pfi}1w4RH<*9K8inLeoXz`HlOK4EtMo#^UlZ z^?ICq#>+awX~jbxtQJ9JfdY%e?1>(#RK$E9!7m3DOcZ!lLOeo@1U9Yh?QO8Dx`alV7 zT)HekoVIEZx;hv9&Xvm67}($iIkjwT0+)=O{I4iiDs>P-3i? zL=Km-7>0M|VQg64SX`HyMxC0@I!rF0d(iR6-~hByM}0y|$MMKYNg6o_xj~0elV9C% z%`3iL0OzR%i~G!DSQjDa$G~34aZm;@VuCUHol&ZDvUYV(aFzcX_j%RVFZMgeqy0}d zM+$*2dOOv?3>m}}Utm6x@t+?twXMD4$#lClVd39f(FF(Y2h9^Fh~4Ul9@d<1U{@-jMMGH zh>SxJ0Q>WDIw%`Tg%9o0P^)8*$xdwpk3k0Gi0rgDt6y^E+_WUkYmzomL)dcChqwUj(QH4k^KE?FT@)2(RW0J~uopkex%XHwa~QRvG+epFG9b+!R``>@)We?<3%mC4XQy)563eCh8xu8a9gKMU z4>Hn9Dx7Fra#<6RIn2j+)e+-`(Ub{|3LARI1hZ?R#Aixv^ZBp+Z}`pWKhcXIZhi1A%|}>=_Z(y2m2+i@cYr8jp53VsWg7TQ z$_Kp+B(TuXpIYJ~nE)DkW{J=4X9w=59tNzGDYex<(6_`Rdo z^!~4%9Ik(Lax^?00i-NC%BI!;GY~wBx~nPZ?;Ke>6Uz?=MH^`T;h%7`a=^350DzWO zAX08Nc6b(X2U`bcbw?AkkGc{7H=r3nOlo*aPfAq`dI|^m`=4 zHU87<`Mb7;vx&Wng@dylDeFJ|AT~BOR(9ThxkB$N@4JwlRv?fgKMRY!3$w|GQw3l) zbFgFaFmYsIV`gOm6BP0IpkV_5k(vT5f%ZZ)KRbJANP*@;G}>H>tcs4}04t!3moq@! zOG(4b%f^h)oJK?#UeJTz!_LtT05T!7xXbi>E!v#DmG+h2jGNp7d`F34n{4GtdzPbg(D=3)95Z!4)J#LjzCxAJEbO z5Xkdy8ZOeZQX2mh^YI^;E&n_@{+FWvWdryJ{f}n_dpg{lm=O*TKZW;9+23VW8n*VPO&A;SrE=QIV05kcqIc(Qqk9s3^%v$jPV~ zcvz_EIO)j9Sw-16`2>W8g{hb&WW@z#c!Y!n{sIAqM?gSELMB8-B^00~rxo~r{CV#O zLxY7dhtP)vCkKN-1BXNde;))R00V=71P2EP`_~5w@)I-!ILyaM%zw-OyTtn%7$PJ% z7z8RLDi|2}s{g;L{l7edNs1hH679G>nozFJrOJBF|8+j{W3uQdPG|D}2yrHbt%8Qs zj-!x;yMuzkE;=7+xGqVN$8u7B3*k#y+_P(L{N)tw{~oGMGQqg-Y=k2T_q)d0PFvEk zc4&n!dXCs`=d^lQuaSF+siC0dlyAM2L){YvXzl6Vibwe?tUeeDVua8DewH6~c%7u!> z=DnRs?8|}DN?HE3{`O}vxfdSy>J8CdwOnhsq;Eg*)$L2ZKJkA|6#f_5#FXi_(=yYL z(JoYkHVXOGH_AI|##L3T4m5(q#ZLSKD%69vCnC9rK{M(eS~fLEeG3 zk<{??^?ym&5%Z@SO|8+>WZXPe40C{77Ivl6>Q~8b?3eOgQ;z%^DbGQE!OMAhYOgVB z+5Y*+WWw0TVJ}Gl?>Ftw91G74lwOh)x^h*q4ImCyvAEJGoB_IlruIDy9e=a(RmM zpJ67uc|7d8=Z_|}r=2#XiY=7X;Hc`Yz6@1Ril{tUgl&U?5qp5dPwA8=BISi@J7np9 zLL1c_Sh)Q7Pr2Ik%4!(v%~z56DG)=*jD#z-r!?6fQp(__C|=0ZvJF+X_=7br)Y7LO z-JZ9bj4n8PLk76{YtZth5l`L}cZ}e!W(1O6I7^X+B?~!4o{0kWYGe0w=mHF5T4~!E z>M8X1-(AuK1d1uCrpu?*>jwerTmhs15Dt#zd7jOZSiqq1DEJFci7z)n_$BJR*Ms6g zDXjRo0p+8U=Z@Q(pM=dnmY`LSCUgXnoVk3U56=+g&zACW2Wq8_o}y2yij!A z13Fx`scLLo(=N*zD`%6>ly@)b0`Pn;LRX(C%#_%sbZRkaVxB&2!sPue9-)#r(S`7U zA)Q4q>ExG)k3de-AjZhs(HMI!ozxWt_Ul zw9))6^|OZ&;b(dA9%b)erd=(&ROQD-@k5Ta$Iis=B$Ar;9K+R{3>Gxf$sRO| zCSUzEo1qG7*-Pl!s&&p57fKS3Nj?uCGtwg`&-PfcDJNk7rxi9FC%-&HLyYC^m&@N# zT*v$bMaK6L-5>sl$OUwa1V@6}@!Sc6cK#xOCXZWippuGHU#5O$J(BHxx=<$}<2idw zbmu*JHe7aVfTE@o_UtO+%U+w{r9W?Bi~Xp)TBM8hiz@a9DU4G@tPKn%k2;4`t17>Y#YNJL zZVqKbqqu1;`?7MbN(*2x^RS~!U#(5TCWi5!&~F^S$>Sa(x%`Q9TXbg>5xE!_F2=|p{+^A9JT_JBjbF>9 zKHiq)Yh|NyGbxJb8p>K)r`FY(_Fu!UK&EKLqiXA_3o=KE|+I2?9g~h zonxg+&WH+M)OSU${teDk=O)Vdtu+%51Tg8M#+qD|j%LkBkO5q$CzO2Zt}=G}n?&XK zmVO0cR-F7GpjZnSV|cS-&e?>}W1!-58sG-W1H3EFm^NYU@Np~39nxbbI5SM<>&(P= z1@fw}2J0}16gM!lvP0S%XbC-W=%6~k6I@Vs%5-sazl?}`m zG2v-GIr9sR7Ux+Wi!~!3#&I!&4+>xq5lczPyl0R?rPIEjPQbu;uPBaOUm6%Qy7S)- zyv0Sn}J8D#ZlvAan{=_5o&R4 zE{tFwvH(t3{X zfPdi#Dh9P*BB#UhUpAt$jccYicaXqvjzk^Hz61Qp2WjX`5D<0M$ZvQ*MIMv$u|MPk zT@w9>Oy{Q??36YwMd{X%uNw=Bh+?xYSRJM<-?Wl2CUi6jqIMcRlGBc{x}!!eDdNB| z_+(bZ!&mkr*qNZ3(yR7p2Z}vfY zrhwMCjLfk-LVa?a-zladxr`gVu?`so<-Qu<)UA(d$*N{BjV!N_yua`v5x9+Av4q*~ z3qFo&1ed+wv_74>h^4CWd`Lk&iis-lS!!fvV)hcf`;vJnEnS!B|MBo2`3l#5s?%Lz z_z`#)O*H`L`*9CEYRfcc<%)k>$<(iuM#NW8q*zyajqfT;mpUrY<+&d5&Jg$5unA46 zV63Qc^M*4ipLx4AmwqE(XPvod<;zAQV!73?{^2B$r6<7y0?S;d5E$%>5ZGSuSuiF4 z-@C3-*k^9>);Ths8cx z!A^x9)%ixrnE=0Z!7|Oo{8>V?mKBHv)>U%;8i_^C0e!6E!cN}>e6k}>R%Q{E;?frtN~ud#re0N~)nao46Qe#bgrAmC zy}7bG8T4+k&d&;Csl8MUUFu#&t3D&AeSa|txkt2=C&^&gaHCAoqyDP2!?Hay&)LSr zjxHa0$Q$!@X!(SL3y6CZJ@AQTJax&@isQA66o~YlGa@5{OOTndui~kD@L|+1`6{2m zwR-%X@}%wF@Rm~5P2g!{URcL!&9X^+jLr}YDy(QK>P?!he8H#nOp@^^k&*F=Zo*(W zD)1zic^l=Ud4QGMsRk|_aY?1ls31IpM#nqba|Eio(>e+_bZ59bQ21)JQ>dZR3~LES z#8!vD+11PHjcJ{|6!U}=;vpg7K__wYfoFZJ7V+woQRxU&63Z^45Z*n|I)&V?&9POD z&SGp60ptD2D794@SaY+H@`b*{>P9aJqB2KL_De^woi)yI-UkW9rBqf+S5G_AC%wav z+OE#{ejMSj0&loX`Bno9@wwtKK?W<1hp7O(>qdFl6_35xayev{EQA(FL}c%ip&><0 zRDC5DluWI%ZaPAWpHcF7n-UdNK)EuE>XkZVhQ-r3IY%tm6`o^8bU0BpDn70-sz{|8 z8(Q;xWn}97bNfnkct4qxSodTU-M(&_ul&&ezNoznrTeAu(dUyUwyK}&zp!JwkT z&)G%-(hn}+W~`&SO-Vqlmqd}RP)rOBXAOT98`DuY9;snA%9ecEMx)6eMr9kKYBs_8 zVcTeKwK6&*1_pd;9V^4nt8y)QJro`#s}18KQbHkXj&6xc=KHm_=CLwCY9$*K)b#x1 zizBv@XAG9;?0HTknHVIDKk$>DhNbJD`_eU{LLIQ0J~(_lNfbZd5X~J6sgc`c1})x3 zH*reb=ij;t=ty98*-`=2%%sakdC40F=-CSM)D)rIs7-n3(#}@?7_elFY?0KKA(Z@ZCT>crPxzh&Add`DHb+uV8&kN$!h?Q`ucGa}( zC5^0BgPS$C;8aarSe;F%-#&MN6h&b=(|p0N)2MSjSz7J|oH$w*igvccQCD(WW3^^Y zCTG|~)AUvN0NYJ;MP&a-^?!F-vFsq&c8tBYqA2)g-7&xLnC-)f%LN<8M=sHuPlM;?)f?&@#*kUj+!8`Q2CO8HlIEU=#|NK+` zKmI~~jCQc2fCspy6nnP~$Eb6%)7myM<5MGQ7^?(|G#cV-0B}b8YOtZidiVkDSzdNrC;V4xdDGD5 zR)Cl)>i#N}-@1r-n$bIBu7~xiV(nOUyBc|SYp5-w;c`hF7;*%)sh@%L4_P!=KZBy{ z5079v7!_c{xeHGK{onnZlE&{(Sqr%|O7u+6#EgcEzo~?;2Way!>2%mp>s1*(n<-JD zK~rDldygG+dNM6kUQIf7At~yq>n@RMm}jJqE+{DY*?b*|RElOmA^R3Bw17=JRv_BW z$@|CH&W61uN^VPz5i(=ol37^v@y2UW)k2+2Uox!)ks##Y$+Wn-`En?qxf2OlRdqi$ zCm&5RVlS&Xn8=&HsrKShn?Q47Y@`QP$pa;d4%j4ckr5AN;PW+f+nsv1}-upTkVs#5FZy0+MhUtB-Ve)OZJ z;8|OG<_KzoQ<`XTB2+R^EuzJjmqifB0Uh7LP80$(ErEMQoT4wu4!yC4iCmYn*0;$+|COP_yYS>1cR8{|>gHk)jg^aHCqdY6y(G zU)8=}X7p&*g2A{gp+#W<71^3kSNvK>Jvw)|eST(qoepIMug$S`X`vQ&b@;;H*#_8+ zx){<~e3q+6R_F9sJz-rfVuvagTrBs>`GmdK+VuUxPLw-1uu)Iz)wiwE5mAR^>2Tp# zE%)|%B-BPfMuWF*?|Wrbtqv~=0wxAP%pVed2NDuhTCG#(Rmd%V`7Ft>Q6f3M;5Oc7 zpG<;*u)dH+KLX$cx2R8D;#f5=nt%$OCc~|Q&3!pIInDEY?lJywd|+q5fd-;(XN8Y; zMSL|i1yR%7DGW-9Wq(CP#TtD+CtX?C?$rQ1Rz634t|E$U!fz3*qAy>mzoAH+Q<*@l zh{bPx;NxAVkklHZN^UkdJ&1SFn5b_`T{Mc>V(JQ@mv{#&g9)9PFVaBYx>=^`0X!AFdDUhtH$4Vi`H_j-Z*`OA{aupuvuvAu-UB*(1 zm~sZ{%RtUO|Uv83@Teryd@VIc$h$LZ- zo85d(R}74{3}CR@xX4BLVkX>1Y3ejxF>~5yE*!RP!Hxx?Vs^I}vwn}pbv{}y9#jET zbw7n#;XV3Phk~XN+b~aJu3M0ry&3$W>-TLj#^2*6k-+k z6{Nvr;Ip*Y05Si3kSFN6fz{L8Kn|=vuek=ZM{cF@QyRXv3Q{oTjqQsKZzkUk!aiIe zxArrUmu}J(%Ek0{??H}n|5;+I>6+wWGn{USwCg9i#J!lBH?A+3F7}*i2m{H2{n}`~ zZx8`>cP=TnJy$o$3C#$Ort;|lHwwA)iCIsQ1Qf}wq#3>dP~Lx zG~Z<&4T(iGC&!Czh$mI^%zAaDcLI9!D~fP?2b-s4)pHIGlAQ9FL_MfZaAvaPOWd)~ ze#soSZQ#?fMRD;rHHzOaquU1~N0)>|U6HivNs9c9ebmTzx}2x|x9+1jpybi#Fo0 z>~>tm!1P~8by>X2VWmtJ ztp;tEdNZTn8T~hDBuX=WcoWQIMCU(T;MRbQBVs|bOZ<)ebKJz zcpoL~O5ih|Y@NCLn|vy(1<3@U-=?Jsj0=DNV(RR~0%s%FKX(-Ki$tWwa=M-e|4|mM z2MMaYG&D$6lCULR3q(bOw^wn-Kj0yo}Gsv7s{R?kl;gmWO zvgD_A%!}nkWb4s|I!Qmug1Qc|K$&J~Nr-lxaJ`xm1gD#xiu;Ae+;5BGjSO`(2MJZ0 zbCWvVTz%Onk2wZK7RpHu@JIU3EnAeNq2o}ca?>Z=M5}?TbY8fM<4N(fFD&)c$+ocr)iRBy_YLPNBw16J6 zWL-x)rHUn&O#Y6J_6`VK*k+0_TON{<`iSHj-Uo~UpeRw!ye+6vxCl(K?MaFSd;m-y3Pfk19Z7KRg9L zsD#=bm)*j@xl0?B1k*W)*p&;S($EY`ZQs+372)BQa%6Ly93x7WCAWY+So8a1UJ6%V zKs&=%9^yT9w%4;1aguGV=(U}UVb{#K+BwEc`(&Nq_#rRfd7A`05p6}v zQa`ydM1qV?9eK{E^_-m_TrwGBlx?|frAcVt!BRP%q$!bIzziPKO`dbSr_#|cV+?Tg zC{nn+f8m2JDsEY`D7smN(k;eclga{)NqUL{5XZiX2`a5EsSU|R=wT5nL{;NSm~H=_ zTHRzaqiWb%*K}YhWizXfz`QiEM2yMl*n~`7hTSZOk**;P=qK-3rlu^gfou->^ciN= zz&vV};Uc@U^N8XP*_vxFNGF6}KIy`MB4Mx}&ROVrOULD$BdkIH$xEpEv^*pbB}wW= zzzu)&!kSNSLE2Nf%hJS%m>iZysU8-Snk0NEYIy`=vVpRW`}3#mS?Hg_^*#$%rD}&c z5w59p>S@sEOHk`DTuGv0>rTI#qQGSkM7`SznY#E3?eBF?(xC{8nZ9`EOCw;Q?HvFG zEl9sryR=)i!%H!iV7D>34y*v$QC}?CgU7G{fr93Pqo3cw0&>V}i2N#3f$|>bj~OCT z(Z;a$yBO@%_(u}D;|fjAaw)cu?WE6-5-%C|9U?!?Q%(53iXLo?jqKBp<5VRaP1rIc zce_h!n8q)Q3M5Yzx=T|##pq(_RMetj)frno!3#F?a_(K?J7_zo<5s)r*XsqF*Bt;? zLjj8P=?TfyZS>o=Jz!Kk=8MLEo~swZm_pH3=lMR%a${%rfz6N4mL`^f2``?<+UXn& zTIn*PF{|Y7=CRSPbkgLC%!}M)%TwWRInBPx)H%W*V{&zAEYwCkp=xv3nV3l;yr?0Q z=bwiKhS9Faw97TwY`~f~r*JzBwKn5VathvEyH2K4xzVOKlyOUCrPDVDkk*T4RhnZE zyLe=i>P74b+vqeAC2Nt6O9EBZymGKcdh=KD zuGj<*pMbH(J_(F-*)%tU29n_i6Dsz9Fpve2Vk-C>zlXy5Ya8Q6JgWS-FwW>j5PoEe> zdJY8FvY=3bbY65%+O}?AhK>zuf@GUwl~ev`uk{aYP>e9;T)KuaWlPx$OIC&@N9gS0 zq~pddQk0&!;}es15B>Y0aa>vapvI~a*z++r8JvmvIE+|rMH8xWl?L;SjrF=33Pc%N zx8RX-W9XGL?wDmcvoUj$TeiBll~w&tt&<ySg^8tGH6yq~^J{!@bh06lQM! zhhbj-OfSxLB4-E7{mStDJ1HzN8DLmtp3m9B^3ef4>NHXX6a4Zq@K@}yE^|in z#-b7gFMor9?BJaHBm>FX^U0mILLxhQyI~g7So|nv`PSvfavJCEsM_E8)_A5JOVUnc z7^@su=XHf6_@i9|u=RE+c}q6Y>3a$&@V{-f7QM0T>$}Vg>onm_gZS5M&g+f2xm_nl zQx9fn+iQe1gt6?_A2^4Jz^{1xx=_yeXweO-dXMQGd!!NQY$bUoF?9crAPOVdO1K1`M=r8oLSBf7Kg?8vRWed3e~?`+hk8 zQbGxMJzG70jTu6tw*Lntp#qBFrl`qlt}DYZyEP$e$}Vx!`I~ zL-|d(c*4iZrwE-eg|#i%9}SXZj>rV9Z(r&&21P&b1*%c++PghyzB^$-s zh_uljHXYJ)8Lp#_*AZx8q~?7n*!!*(+94*(Vk}W*Q%>EL%%v3t0^9$kexahRNI3jJ zREI>v;4-$%ATuQPDwGUv6J?pi-nBWmBkS19V;KjC!7_?I;Nw|YsHL#3<`PW&0eARo zwPfU!PETr3v}Rj_lT?PJVQdx^AB}>FsTRHhTHZGq?>6Rh-AEm7U`NKiR&GYto~X2z z38c|b8vb(Jp5B<07`)PV4V-RYSqW=7^8W$EKs&#GL&Z$gk7N}Rd&vOY&roSTgt&|VUE3=rB&lb)h86&LZLQr>V>-x zP2Kf1X}&s@OTSb6ZXUO>W_n6k58%fM13?Z;0j-BQ&4Dzoz|*mQ=v=u=jMlaX&f+1Z zRoCp+TOO*|yN7X7BThJU%806*q)9u8p8l_`hQn!wrvimXElHHK_N-MfX)P80{_uVT2 zQsrefpRi46Fshj8uA!35XU-z6a%~GXTorP3rKgU@x$w(&om*F*UAOCdcet$ZSjQb@ z>n~%iX_Zx0KupDIVK|CR(t~{LzJpi_SfCP>k7C(mXWU3FX6AgHD=!4ojSxct%a~Pu z^ewWxQ#@9MH)T&6)BgZofQSvt2Ss-g&(~nXc!^ML;s~?C(LtaWB82^@#n*UMarF5> zi~wopA~aGF8oJb-Ntv0awAq3p)nnq#I6D!KY^n(0?A2jbg#c5lP{!-N7a!elpiL+Xj<=tJh!)~0DhT*44 zftLu8ly(BiVCKZEo~cx5y1KofHgJ~U>h+^iugTTV5Sft~u4QFy1B({5iDyFWlx_<> z4)dk6nJKVk^^m~~!PA9Dj<6uEu;!p0wu4=2{xfgtlE5qcEQA{bL-H8!xjD^00RI4c`JA)*IbxwI?UuJ8C>4)X+2#&9MHJ$s~ zq;5cDi>~#Nic1v8jLrEj9&PO>(YHuHqS8 zQX-Kj8(a)3$pMG3MgTqo8x5J5Q{t}5z4*}biuKvz+c{dam6Cy~?*$iZ#~DA+W@Vv> zIhmN$C=Wx)({SSEMyk~NKM<@;cCWOnXtw352y zam-{k-l7`I@3u&BO6fhn!D4%InO|Ep_EcZVCRon zCezT0tyMXR>o0kX*{r!17RZSx>Nu00S}>JejssY_y39kK6@2m^L2t&yq0B2xO|KwU zFw0L}dm(D7M3fR!I^xqx&zya377+36G2NOHhqrF>D=C##`!5``T0Ween3{k6X$}P3 zr4e0YzsuSlJ*rMC6*FoOszcvH7ZVuT5VmH6lwW)rNxa;gsd1@h4OE703$&QVY0MTa9UXc2-ag{$tpk)V0gn z=7~BKwm6t(<;QENa>w0*g7~@fRCSYqF=gj@UWaH2^N_+kyp81MQcqf&$hyb3^e}t1 z4kv14Y&LmVETxl*l&cUnKgZLSX7H`~=|Zy95TMAe}+6-~32v@u~Q)skAKjdQSi%(7nmBW4lsK0S3}V!gG2w)jI0eRdI92qp!z z%6mr2+7G!R?Lb9$JlTXGzg zlXC`WP?!r7jjS3E!?bJ~nrhalShIg2Jy)%rafiZM1KD9}?OKX>Fn0zVOr`)_g(23c zbMcyeNusf3hx-}&voljgGXqR`#iw;@zyw#V2qIt+A5elQh}*`Gt{k~IOBVKhWgLVY zQ>Ma>HIknDc<_~v)p|+>XL+g-I$@!=Oy+O9xk>fwviDceIQ}p&c3~5n;Hc5E`vgKU?r94xSYI|a&BAV!+)Ktsqw+c zggj;gP|o`J0{avJ7L_VP@WH*hf1$yKv0$>S^6fF|Uenpb>x!ll*!#EIYi>K1Oht zH*u1o40xIgTnC>4=kc<#JB$(`aS<`ZqHi`)2&g4BFT3U>FvtU$J^|bHkim~GqU2Sl zF(8V#+QjAkR$V9?@^)`tTrIzC`z9%Bs>Rw{Ib*0v6YkF()-d_wyO+%R?XQUN%AAD? zuu2P-!kQLS}KWRO}~IZ^Id1zMBwiw9eMo zg9qf>kDB!pQDG<@k0r}^yx421$BUV-vn^RJvldyUTMWZBeOX6JnX9W+v1--2>{#jX ztbo1_yCziP-Z$De{SD%kt!zx(4=W>3SLA(Gn<*-_Ury!DF+Ps4-2n9xUCXB>OdQ5X$>F}((62T^3`l@PCjF+hg>0>BF6H*Av1yqbfI3VZyCUvZ>_ClF)()PPGVKS& zinkXp$5wmWXsjbiA_q9Ue7;dEE3Ms@pciI2@+2yI07VG2&dpE<7EaXk)yQ#+0b`DYibH%!@LjFQ3Vyip)E*p@n7Vt$E|8o zRkg7mv1J2bIdJ_a__33f)yAuctq|g7n7~r$%PcqHF}!dc&BP0}E7@Iaaz~BJ=qb<_ z1E8C-Go&V_LhJBx#0U>Bv+H|U&ND+c?!@EHYSyTx z#d!Y!9|I@={{S)YAQR9_{F_9+whICFZNxYZD==#Z7|1Se7;>G5m)7S^WlY65%JK4* z+2pJ?9bIY^HRYt8Sg^XOL&nN3oETf^`IW!4Fx%F_TnvLH8ld6ttQ<<_($NnQ?Is#l zW>^$2xhk}FDRbjLAAE*beHH5!Y?59mwlUdm=ERd@5pOh(1jtfp+kz(J7v$BjGgD5D>;&}O z_i5(L7CLZ3tW|J~&<-_TxN8+KyS4b)E3caMaQu7ThsBDXg8Bzp$4d_e{&lDPZy}?w z3)V~CHp+#bSS4$VYbpbmD#wjO>Qx{st zD;XLN@5|U9aT4#Bw|3RKplY%&iK4_cLy@jztU!wFW-Gd3GOGC!s}^ACcGVb%6{uGa z`zKapHU~7(igi?JngD@Ln&1{|B7nv^(Jt!B4Pd0zx5z%^W( zRG?3nsAE*;joN!8tNQY&xVP#Z$6SO6*9~q>Y_|B}kE3<2t2nyeK1KXnX)c!wcH-V0 zZ}z{_*CI;eP&Q~(dj4^7a_V$C?8;FLSQ+^1cumJJ6#A!}t22}mG5GrtiLvus8N7@%-e`_)$=RfVm~zP{FyR(TLu^~#z7*}NuKSieEBfO$7b zWoB0@t1TtzF};hohNO8lxgJGuQJO~e?bOI=lTOts&=`VIGgXOWD;o>oHT72Li{ZW0 zWI=X{6+oa!&iP{*C4XcfV9%)hQrgE4x9Czsr3v_2iW^ zjBLg2Sp~docDGJGvS+fmi<6}^RLhq9q*vI}_4>I93$Lf2VDjUpvMXw>9oqbJp{>O{ z^xRn_5`#{?n0Z9f@R;)Kti5RpNL>KM9O+I$6-eA7;b)w=H zO*-K=)IL>v+$QLuknC;fT|_1ggbh)U39}z+YFDwnx>#F1dWXd2=`pThkDpiaA$H%_ zaYen>94Tsa3D=(Yu*FN6mDHuTiPZzD#>R)oJmBdi5$Oz$WXbEtJ}chUg-fE@v3IRV zrQ&`VaP*T!iUU{%8JzLoSng#|1j4tH{{W4qHSXr?FuF0xbyL!G=)>1ptvsS#R4o;Y zG?uQwCb5mSYI4jH`x@;y8F@w_W%#BdF2pdo-U&QfV0^lTy|RvqXtB6l0#swy5W3%d z8NsA&?HLTX{sYNK$>P`JWX5`NE-7`#*Cw?Mlr0S+jE{L(Pz{$*KSoTy36TdBVhOn! z5qY zI)`agz?j*Q3(pgO`hS2Tk0|mMgINkrXzsYOeQMBtKCKq#Qg&4u(G;(<-a_lNYujCF zY6Cg=)Se~h?joGQlQ`Pb7yIw1@4of;{}6KZ{7wt`vnW;)LL`Sn;%8XUJf>)_2%LN zjZ!lldQi#CTNO>ahma%$zEwrrc*@{55^inU$k8^PmDBc)^eW&*=XEsdy-@CUd9DXi zBnhm@LnTBl9=TNp6E0gT4a#Fi!Y|fYHxWQZw9dZzzH<1?DlFBj&1YtJW=U;oan}Lz z)^$;}cedU(Y&w;J@mysz+r}m=Se4tS%0|5>EI~l9`!$u1d4@`G_Qn>p*LeiRp$0d( zgFJSA|;+ z#0DBR#{GH7-hc?~x$)_;U6He1xw=#mwdu--DcB|PwMjMC=ph+>eO1d0oyaVGq+YNy zU_@MU5;`^Ur{jZQuE|`Z2z^{yj4ryo6?)yyy4LuIi$HmtgT^8M0BWq+u}vnu*xmJK zV8^Bkt$=JB-AGh~jfCWK)y2xIOf6Qe+KV|>^<&f`46SL6)S32F{{S@8%x_4oHp)tt zHE_ZFq>Od3y2&45f5x?&6`%mCD7-ji_}4*BvyqvEELZNZPHQ<;&|+XCb0%nn*#eGF zky2&~DOOL9TkFn_^!6D}OO1LM`n86+fwfo#YTapxiPHELWj0p--`iq;gER`6lL_2({hz~qV~_Sq>?Mj zf}%tmHw%Z#B)qB?D-s`)V4HE^A!3|1&1@*Zn-?{8dxYuWY+Y~;+iuv|5|z*yhGom| zl5ZVXvy81;S{GSb6{Ogi&{$#Fy^wEhY8@bw$5+tXu0_r?d`My|!L?3!F>OYxSSq$G zG8A#ax=nR3Ej4yll`7PJjDjY`8r-Jk#7*ptg;(#fQ>^Mz9qvTd*Ul>>WBouHoZ<^! zcEt_IDp1PSS$`U?)^@OVkcO$8G?N96Bw|S~UkufE>0hNHwjsEgK58)i9rsuNCr* zO9I3)gE4Bds;gm5DHvN6OeMvK-J}o}KTRreS+#9IWaKYffCyM?Hzszh<7%xc!_ne6 zwh`Q<3Tz_6W^Xrf%0l&GtT-H{ks%JSusK<%d4)}@zMB649dLE=ZUvB3>k{=HD>FVdI;YPj67?>{QK^Y!c-diW6V2Tg??PLYxy5zjyC%jw zOwJ+%T{0BgutUXuEI^RJJymTnZLvdm)vg^FiUgfx&J?>ZTcU`;9J%7AI`ENSahzJQ z;hXBX%OA^IzPXLz)r+9k?gMSCiwT}5XCD*deyTJVNIn8$aK$acIJ!Vz?~w)Kao(^(uL??J?|C z!s{28jTe;zPSe)=2_`^Ns8<_m>Z(I2TgkCFZoMTktv#l#HnnTOcxpNfel>=4@2py- zs$Ty9VkBqiUOW|pE#Y~zUJWKfB#$NSxc*S~H!D(Vm zs@;OmS-!3H90LLnl)f>QWUO9YZr_;1iQIql`cguw9d@F5YOr<4miWmU_3J4@D0v*X zPqbpo*x6Bvu{6A=J#8^fRVU*+>{Oi7N*b}hLT-X_w&^h|f`fgu1I{AIipjPdwnWCz zr%n7tb;te-ScF+*;hGH`RWbp`(+xKt>t#xtsdwn>=E}6H@QjM@)v&tw^ZbCT9%2>F z+U1p|vV=8X+Pu1;ZmG@|YlAoydH2lAD{i#ZxenO=%E%};t=HLDfNbOvwJ=<$Xf~s} z7hPkk75jC_d0B5oXh}gZq{8U|RkH&p-PCtxOIFXUWjj~0kIt!BDp%9(Ccc*IPHsh{ zv{SC7DD9{%YQ>;JYPGIOv>~W?Hf_`v>Hh6;u)E5d(`0drLtFm6+djVy7A>>yQ03JJ<46O9NYm3%W zd>vk1wRr()3t`X10u~>Bcl}AIp`Ah?qRKsM9!@p!$OsIz@ir+~eS0=Aw0g$X`t>w- z@)u&a6zZw~wUt$tj5W#&IQvhs;c?4`$AeLvDBF@?RAE;|hFy!7tFNfewd15$0K|9? z{$|%p;BmbY`fap^*J{q}_5xxpV)4hR1ia1xe6Ot#ZsW7hTF&LYMylu0Ui$x>jROurrA3i0}GMK+OOO!|UT5 zn4Zsx)PuM*!V*og`zd!%P02C1c0000000000000000001>|Jncu z0RsU6KLI&k|HJ?k5di=I0000000000000000HFWc00;pB0RcY&IbZ+808kJB0s#U8 z1p^5M2Lc2H1p)^F0s{aM1QH<=A~8WxGGTEPBSKPvk+H$i5Hq3S@C6_wFhgSTQ*uy( zlA=IFW3m-AbHdU!gH&X2fP|Cs|Jncu0RaF8KLY;%`vcydFnVUI0VDT;Sul~?rk85- z+gma8n@ZTK@ASB>hGZ?*Hk2WW+RG2J#O5dprdCIj*2KWk9^$kdImT#bANDLp!{n=umg_S#r@oB(c))fM!N*9$T3nq z^##8w*Y;i~-hP#*?EFu)?eeex08A(U0P+P4&|{)Guja^LDa=^wx%6aR>S(0x4wO~o z5m+A86YBBSgi;Lgej>9)AYuOi8!^YdH~!jYkzO$M45!<%+xlED1xNih2i ziKX^g&Hc6H?}@}%Xf{xVzvEW&Y}OxpiEQ4>R=H7BcmYl@@=3GJemn&oxY}u;Y(juN z)XHLyAr`vJn)NVf;?Z|+ClTL8R|bJyhGP`NO3cXiy|fqh=}xJO!x&E(=iRj{3>wR( zg)t}RTS=qEMa_?FSMZ)r*@k7+gInKQH^n?Tm$Qfgl>2FnQJ2Za)wB{Nm(kCy1jP^e z#atVM{{R+wJuVC5TV;(IddtF$Z#=K1@+jD+<4PiEQJ0Qbv!8ulTx6VhZ0+!%v3XH! z`G!NrrFgJoK2JHcd+zvF($KBHBwZ4|&YV!!nfORhCcLvn9BgjsCO*SfB|#nN*6qZ6h4rDK?uq z^yQ%x6B8RCeSGHfpKWb$>f<;18sFK>R-g4Wm}q!9)=Zd1u(>M@{#BRC(`1E57G|uU{7MC0sGyTq zKeZH+IF^qHe#_ zoY?+!8Sq;kZqe!}c|IkY{9|O&wZn+p)`jv`SaAz84MIq{TzGY-!OF>)@LWLQ;A>?l z$ldf-`QyIu9}z-Gjlur_hrHIlUIM(Y7b33HQJF(CcnZl1+z(1qb6i#3g}M3|)+xY*a-N%&Uo8{X8}aASS5Hva$J!+(r1+wo!YHXc0$1 zwZ(twq2-upq+X#ltf(MU{mYiNtnwgG0( zL?qnHDY&L{q)2i4M&H!o!1b5>=z-OFxNgI36H2|E6@qT1Z| z*3sccZlqF_Q5}x;hZT4<~(aMJW^a{CpPl;T7%})p3S*+`wix01bB~VsN-xcrIj=Jm-!y* zcERI<^^QOVGQ1_EJRd&_jUdD<%O`3%R>6JrY>0=RLvc}GzvKS^56;(D^_H7sh*Owb zc`NR-4ytLeva979n8O9XiRngUjy1Tj$ti!_(vDUJ#>U^dbHG%RM*(RsuFbD+5ksy1x4`{_ShZF)*C6oeDP}VKr!5XNvyemp96-CK|vs_LSK~Ztaw-@yl7-2=n@O z7B{cC{{XnwSdmb75BQ!Xu?w2-VAkyGL}22&kLt;wq2((ZM92DMiW23@_7@_gg}YbJ zd-3}h28or^Oy`*HtIBg1=33N?hK1&mGrZw9O*C_1@d($K2JZWE!&vyM&(LX9f z6~^xPJ!n~js|?D+n5Qoxjg(ytw7n$rsdi|$z~axdi^P%%H*7a+&AVTmZoA7-;>6*>ATVBF^rM}# zaXczV%OV@_ZaTj^&Me3nbE6)JdA?HI99Yo0V(T*4t^ zZ(6nr3Y~JsPV0ZP_2EsJiDZ(*wX9In5^}iU0MOL1NpyG;*0&yO@ce1w*>lW>{hqak zl@n(uUt@GNG6;a$1DZ0tIneW&WiM@eHtSgzQcW_+Y~ziqWr>|4T)m=$Q%wx5nbU#l zKjN6{{{Uygv*ALaXz~i)iUhr%!2;l|Otd3@`2ol9xD zH1^WRh+`RRYWKZl*(!@@SB*Kt;q$9Umi$OPebgqFKQz9u?NAVqBx_zUI96%jIR}8@ zK~z``0HiZ6XfI|w)4rZe8U;l*$Xi-}JF*8$p2_aLZ?|a(*Y({~N3#-(6d}Gf3{M!# za#89?NhcDc&As{#9_rZ`^x9lMvxnJpWDI@Pu)_g}V%N?P+*3<6mg|=<{2(YLm#ZB| z?rX-b*S9m$gDJQ=kKV-3eF514;X)!ou{O6$)RJXX((J%f%OJ6nh5A!s4;hYcXD`B< z9j3-Ka(I)kS`#ZzW!CILVrgYw3@=;i6!Zr5rX`||+u26G`cEbWR4{G*czvKknVToA zVKQ_Sw__R0Wy;*WlKwtuX8V05-oG%8C?pAMjzBfVr^0?rz)LSGhWNdi{fR zG)JcqL}p{Jq;G1l(0=sy;wOK#f4PE+GRSPAo9zhI_PU8k?f`ef!Ew?Vp}V)y7$qI z#l2Sb{{V9l4udtN9|Q3FaO;~y4CHP4+S?4exmQQpq5& zXI#DkZBUX}Hh(&vuqzy1seOv~w8h@j|hr-7>u1KUDaWz8FQ zl>W4%Zf5nK0kpB+91r=SBtCI?GVnjH>g!4nW{0iyTFec>=UGksM=Dsc2Fy<6i>AL? z@@!s>{W(`l_;fU3#4c-UvJcjVypt8$;*K=U-aG4MHt`%J92@id43aigUM9SxCAV!I z`cRcGmAGH*d|UC9?)NV$Icd_Ez}NP8@JQ3f)MJ;(P8v9M8d zQ(}`M$(uZU+jsm{-8ZCfI~wvA(EKTJ>=mLdk-!~+;aka;2|F^6>7S(qlvo?vTEd>T zx$wOSF%dH$P^Q{)(zfh3BjMx!00kvQ&q_NBXHL>Shr);&GWL}56|}>U4fl}nZ>1I0 zU3*A)igjCN;?&zeH;7+Fy&?3X(b^nUwNvSM^&RwOiFR0#_mMHtzg=rQCo7?|!pE1& zjw1_mubhX!JMCdIQx1%)(On`+CZUR2SOnJv0I{AmL%op{;qt?aB!(IvOKru3G)-|6-$@str^88^nO^AKR^O1U8aHU_JU>Q)OZ`Bu2{+SQRAL6qpngBf zJ3OU_B7gBgOADR@uh=ZxDTiUdTtx__^E3JmzL$s6y!JqEU_tFtl5)80Fyl)M_l|L_ zZoK?HlrZ?c**~W;^Ze^$Htg~5M>38-u41-t=5eu&*5t$KRtL%P(R-Wntmu538y{^? z2Nku8_4q3k% zl*X?V%-ZG}W*n*~iaf>YkOciagR`8D(n=2Jo*pnONw-hY`n* zajXK(c@!pcJC7v?r{rtMak}5`rS-o$7)(p*!_CX< zes$!yvy%|I#VYz#xNK-KVpFjw()5^oV6>mooixAD(8J<9-XL7VD)aqxt(G8cagp{N zdH0Gs2yw9cn0+g20oh_klEmJZ>#b*w_<3*VB0iUWzI5J96tri0Go>`}u~2RVL~wtV zG_oH&zK5&hSX7wpBn-`O6T3<$R@opQ^HRjnZDzQ&&Vx#jfqd7i} z!y%GWa2V;K*W*kPGNU$r``Xo)u_R|k^_Kj7hn-?}%=>h;hZ~xU_J9EAU6jdtYe3=V z5+iWxDmXI@+_3{s3~ZOV4WF`YMtsn+Gk;062c>PJaIGG^X=Pq~M??G9t@{n@Esq*5 z{gKAAC^MsUw-22!laY{K>+Av;ivu;jD)-#Z_PfUxjHcM z2C_2kaGm;?$IMn&Z1_gdUtFJ+1(8RSIJRy>$GW_m8Rsz4XtEw%&Gn-OFV10)VpH?_ z(86PP;(xoF>)%HO`$JUzl>Y#(bTIhwoHyB#Wv1#))|-6{dAy{pC`u0*ne7)F(BdbJQ8H!eZMLYOu;d6??oW= zzg)$Cp^cB+P%Fk<+N5mjcnq- zoMN}?Ink41_r9gHKaF`gOorBNH8r)485FN|jYmErJlSofvfkCVL9$%kj_#Cj(rGk& zdKz?e8{BIox5#)f<|;_cjKrpBO8RuuWzCS=yHIUThc40!ym#s83X2mO2_StKjv|b) zP5eG1de+kQhs5g9I-7&nfvu<+S}WpN~C-MrTq@U(QBJqu=>*+ z84fSlEu!~#SNao3JGJ`P@hNow08&4gph37i{{ZkCd^2rhI_X97tC*?vM zN1nmIVneP^*IE(cy!IUSX6C&w^{t*XZLsIFIO4uPTG?X!{uTQ&pGU{*ONYghMf}YC z$G5Ft7?&__tgEH}09`1_vQ}W(i=G4Q%Li)u>&nLTHhz_rLFGXd+(s>>F&8Ek8A6W# z0P{-^?FA#%em?s9@B2*)Ge7%R>q0y}8;$ZbhqA`A%^4RwZ+d{e*Ea%35Nc#9M^no% z)8T4pt)^VjpY9y15Yn+nj0TLY_kUVc%<=^@uVUVs{)GmCr1GTRw?1{z-gXOE zcQB{IHKSO^O<6Mo@QINk*P;}3%I_S7xuYwn_v&lq1)MjHDKGQ46YzN$18 z6!}*wo!46sb?&8g;KJkV8$)1yE<&%HFgz$&i*wS02O``@S}T1=DiV-llb%z?t#55_ zei~Ls8NuUvv_5h_QQJ}AYan1Gwep@KwwXYCTQ@o@i-=1PuA_&Dq-X$|D|Of3MbHtn z)Q`*g`Ib#MGSg3kI^TXiPxLf#7&h6=G?5#hb5m^m#vxCfQa(LJG?AcbG`PPSIJ_?W zXYXS9_}7(TeC`$Xc*idBM-_o(lM36-9q8jQK5{4f*n9iC>u>upx$oiI^`$l=1~zkp z=b>*(jw=PDe^wKv&(@AR3JF8^(SEtno&mMPKW;I~i{V?!aEFXs*Td;vQGzB+J9gpv zSCnDA<_+~_9*21PP;fSw83v-=kIJ@E9i|FFWq(uh@1cg0O!?nK&nTvSt@XA}m2*QF zEDmhIbW@=C)Fb^gE`Qy7d@VyUuxH|Woof+_knE$DODMHsC7#(F1&s6;`qZwA=8{sE z=~$SyfEaihTr9A~Vwv>b1pck>sqh$?3tGh=oxeh`jT7XIiw5Iiem{+4q=14EwyIcu zx>8KM8m1XLK;*XzAJMEXHB@CbNkEBV~(fF~AwBj#+g-N%Mb3Mpj zVunIL>AguT&e5ps?QNaDbqvX>j_$wWy!L#**~YArZ5NTXsKlYNvTX|H(2lg09gWlW z5@Xs$(FXAP*4qOv>HeMf57MlsV>;?lwf_JjhHTNYo=5e15lBks&>kO+D0O2UwUeif zW=9gnZf}2usiZ(<%gHq~(!nNeJ2&Csc{TuI{2mKcXvAUNv4kT8z-X z-X_;A{s-koTp1j9qdE)xjcMkb@;cg-OO+%(jrjO~H_h8Oe>yf9B^{(cug12h0?b#L z)7w{H;qhoS{c{zl*y7>g(Ee4L*)a;c$$!?A(_w~OLg_3G>3+J@lVD=r&F5h{TiMpU zxAuOje)1pJIyjHn((I)Eq)o%xD7<;FxYQgEKd7yu4UReW( zzP8YL+!URp^*=7^%^#k?$;*yU%9asv96%hWtCJFfFFYFAqtEwT_90YDoz80BdWPeTMgaVYW$Zj6v|r`3i#( zG7!!-XS(!49qc@si*N**esPuai_}!OX$f!`s;^Bi`0MVVRo$K?-M>H1ypCliC*sGI zc`Vl{{TVv zjER~Fd7E+Z>p?Lp5>7;RTf^x_EMnvhpqV=-=|ze1n1H(F)z8+vqX7F@y(mfebfY!_ z_D}stm}%`CtIfW{6h+GNZ>=cAhQmyFN{2@F{b;5#C7Tb;&~Wh-B!RrI4tw|4>p>f1T3qYh)gsG%Mqa!NZ6K1bzA6pBgoWVVF-sg_MgqQZc=J?Hsq z_aLuBL4$1N9hVfbP3Ex!RCVX!^sJbK$tr4+$HVpALJUWn#HO*xJueTXY$o#+JCpR4zzw973 zR&5mb)-r5Dsa1U0PYm=RT)LKa{3;AUd+nXA;54X_(&fVdec!KTJ|_|pUP~eXZ&$GU z5e{?zMX>UeVa5qYgXvNQh+*+9#ZLp@-$1|c zv!6(IWcvJTXNemUbPf*b_Wf$i*X4Ti=QMnz>ErdT@8-#cA)=6QJx5VP4UJt%^K!0$ zpOL1E7nP&`08;k9=UWU!gsW(h-p9cA(8FS!XD7|L(Ek9M@;p=J#7C?$^`*mp(Oj~B zN@UXi0G$jTJGaIemrMN(c_tyU;$zl1Mwjm&O7d($+2bPB9CPm#1fKnCtW5a~Q{~Gd^9PkCcM<07pzTTCd%npX zmvt8R>)l7nH+HvnJvrW|#M^oFPuGd41Vm-Sff+qdJ5n+2N2sYY8=EVE(}?>Kv)d>; zy=plftjBSFKPt(!)VYu{c;2xSw;k8DyF-EaRLIM9%wrekqx5QQ6v!n(=F>HYfcATrVi@c9Gjsm{G%$GG*~i_= zIrvbE_KM{{)5I_9O4$C;w6LDU_O*|HO4wsO<{i5-pP$yelNffmd2~q|;A_Y+X7M-N z$@1}`gT=N<*I1?N-(F3MJ3K<`9HY|jA4(BoJkBjX?p5@>K9sRye}}~X08T~V-XBT{ zCz{2eymAf=`sqPtk0jahxA$9}7nOYLdReb=_F?dsvbikB!j2L*P)dLom|B>ues5Vu zVZ&P`Mp<{5=ly#t5XXSZgWZ3fUfT7jH1zNI8qLpz1}9iuB)4_yNd=ZCA(NJwMPgth zMU|AFRU7lIoa44MJ>yT6>9Gh(y4ag@@2!irVC~lX-D?pHu&txt%f~8{yek=px80qr zW4ruO-z<=eTb;i`p%TTMN)HC$MK_WYhQ#p7PD)%p9zx}NQk`$%S(uVfGfLBWMjBpC zZ9Ho8TQT&MX#Mofw6lZQw;IDtme~axCOZ4;DM09W(;Oq_AL-vfepHCXp_FeF>-7|3 z#a{}?vf;Eo+5;ON(MJj5<=@@xs}&gBD-I*I`O!Eh+M?;Pek0q<+glXPgp`}*%WU5x zLOfFBTiqnTKOR-H#SHjnu+RSh<@KiWhVqOtwo`1x4*CrjpTUu!;>qGcxF_J;a(-3h)A`Iazjpqwb$JFZ)%5KjA9&p#v`fv)*c)|8Nelqjy)^34Xgqv z(FaemBrUq5e$?~jIh8_>4m2TSn>2ytAaERqx|T=NHlcbBRW2iU(si)q%7Z@hfG^%^ z1`WHs3iZqx)5E8T<^KR}7R(YEV*p>!{b@^xn3UT^sNgSc;Alqf zN~Mvn)6s8{sS`F^uN&H>QkI%6;CYeGp~VB|82QkBJuF2*G$5>Qrw(t(a;R9F2_Sf+ zw{ZGeq&S`J&A>0I@uuR&6)$W;@2;Gi>{20_lhVj9TNRKvupUo|Oor8#(nf>PZYxc- zgK917TGbDeO6s7C1Lwtz`YB4tU22)0fhY{{WQ6-JcJg zZEzBYkDM*?=}d5#k0+d+_+;Qs5sQaqeKyhRKPtSJpAW=HH7ny+H^pIJiQ-r4D>NU( z;T(P6HSp`r;SwDxy zCuvVVt$9K&!f~Sm!T$hVXyn{=K5btm00iK zW#P<6f0Y=evtVWH-&~cmm(O6_X#8^c_jlGw&ff<8m*bbmy0%mK+#H-3`W`*q^{~bH z%sc3^pMzG;L!Sj8uwr{1-V`ucv9*F-Ba`H5P_Zhgqp7ICk7cK4rD|#zoDe22D|P4e zsq-5uvy<$~DY5hFW@{>s*L5tm$hk9Hrj;Bms+hpii@#-SA^w=oJNSx@Cbixxt97v= z)h=CSgo&aZ+{fWf6N};H)h)?S5$vFhz{;Adbt0N*Q)P+68x`ZFc3UG1yT^@fWrros z`rAh2YaJFGotEr^{JM&s9WT2t{HOd@Ouz-ZYHBR49E7Ts=-P0tjBywsUDjSdiLJqz zGiV!2)wVduSCYjzAXZ$@h*j<3<{RL3y&Y^hW~_@Sk7zQI+$(DMdu2P5CBGl7Y;m`J zcV<(nR$N+nUL1?8s5SRdY$X0s4Yy-&tsf&kPcru5ZU@=(6=uzX#dY<+{HPi5+hn$` z2CG6`8+lF`@rav|;qCN+_p~GWh&vkIatS{1SN{NY3`O?1Ne1`!clc2&=ddlWU7yreo__-4$0yWQpTyu> z+mq@mZ3msgHl+c+UhzUmzE zO>TA_H1MdMKx31c?y1sSi<74?r-euT(XjYX&QI|27eX~9dRAj5WhRfL&?MV|fx@}@>Rvth|2 z+fgKuZW#6%y3~-7XIJxV!uLL@tnkL^&O|TQFb3 zkYX4UYr~0;=Sj2+b-til^fSg*{4O}uuZ3?g*4jtSC=lD+#kg0Rkb%>c61T!)Nzke7 zeQ8EHb`p-xB0e7aCVaT0UF9gd$JtAW!I>F(!683B6w=_1%Mt0u!q@Q?v;$#`L1L$$ z)KVGIV#R6{-;Dt<*-sy0Md#u4rOtLZ)LY#;eJH~!>@iX5r{+7T3`}MzVr|w3;a*PR z;*~d}f29l_GHIozm;`d z$EE4y!ez~U_^+dV4ID-jNk`s9+Gg?Z@2u!pTSi03_txJGyQl9V>)t!)&wzC|H+Y+Y zKO;vIgc~eee|aBXQI&1PZ<(N?UTUj7&JbkpUgosHC zw)%9xcgm%LGqugaGpeOC9MUzePeDjx&$Swx4ip_4Ly9uq+?(`h%OEk!d+@cXEfI}< z8=gJ&g|NS$9|KY1({$}vpKEAp^Ej23dz90BKBIlpJmoBc?eHD!YrnZMJ; z4SuzjNV4wPqH(+8KFHOj+67%T?9g}{WhZ7-&D-y#W5M`q_H95myZ-pn3(wV@HLh*vq_}KeP$-H`sGvWNsGA$hsKI2RKLkl0AUD=}M zd%S4lFe5s*-owLRt$9`hdNwzb52t;3U+nudXSW9pezYcunM;el91qp1NpSE-h7#&a zbNzT$^za{NkRss-Lf+B>04nSG6}bT<-g@nO@~9+%vgRDlkn6qt>(EZ zt@1yJG#38={KQQkv}z4NP+hX`cYts5snZTv2p@HUvCDeIGokZO8{6Ab-zVKOt@Z0t z;jrs;p2QEvwgn@|JmWKQrBw;!@-TQ=wE?;w<5R<&WX*U-Fnu9e=Ca>`e8TP3oQ>l^iZ1T4-BElk4@NgT{j-ZP=9a@$au9#W85) z(kVV(G$F;fd$!|nC-bb9Iu6%gSU&Wtk1uMd#o7QAKi+rnk0-@~a;) zjErge<|s!e@c7vERva6{sI8(d&lv|VTk@fc!i<=hjhzoZ<3|;NJ2{8m!hJ6q@{Bi{ z#i!mxbiZ->(@BL<9wA8D#>0y?Pn1cdjI?nWuQ?*?ZZ@MUY~e$|bN>J}<#?wh z*>b40tScmg!1SVoZv$&o`FC=RveX+HeDgO8c1`rD_SZA#;y8EJro6GPms6(;>F=e@ z>8@enN+JYhHf!~#1^m*)F1{rFD-#?_w$HL(0cvM)b)FKw2lZ;iXKMyQ5Ir>f>l6?I zG633g)8|k%?P=Fum52wh)3ILah@ua<$gFzi0=$Y|aa>!ukIJ6GZy74*s60LuhW`NS zfA*S=Lbut%%zNt-0c!??Tpl+g{MK@-B$$Z=d)zo%T8(9VxuMJNxTppk71@qsTmFT4 zW*%fv`fRVun6D)xZyoHVv=*YfO&Ko@h4X)H0NBidbjn<<{{Rb8$Z|V!YYUF+it}=0 zZnnKP)QzBP6y}Gjo|GDJ`!mgGMqfH8+$3vif;n=P3v^}XKHJ+$)9hJL*_NEx6l1*r z9KW`Z#U9rT{B5pL;nKX>+TmCoINQUhtno|^6pM@cKH4*RO4)$ZK9%J-CsuDs18DyM zof#tDj$gct{A+6uoWeG?U+MSwQkFU4mf2W(C-bJm<3MQSK5hP9HRO1`_}fUN`5yXg zJ|Jyl^knPf-$ia#{KqOAOJ?r=NQy?Y9aCLz-Cj$HHkfLxG|njD@h4{Ivgx324*u#$ z@h|5GW>pr~>stv>M`CTn+l@qCF(hYyFEh%pi6>^#JN~_u3&7=)RnqkiP8ok5 zb+fVG?KednY9zIeTb~+%x{WEx&Sl4z(j? z%O2FS-=$!%(LQfEpZ@^6=TK)mtd_C%ikNybL>uK;Q~v<22jFTKKFa*8pqIUEqo4`#AM*u!dcJWQKRJLnm>`+l@An5Htt zDIr<8cTj?3o-?95d<_ecuL=O2PY>j*QX4aBgmLyz!{Ym1go8`{4Gcaw21e}7!{5-+ z!{T@2BkvE-zN5n89fZrQQx8gnSg+ynAo;0RgLr)@e59;+g*juHeJ|Ha2{)O=$Jmj5 zFV{*qoKzWS!|*<_?(VG448*qh;coJ4Bzu}!Y&cMsYn6v~Fx=0rMV37M*8;Py$ub+T z9&r2q$qJ!Mw`B4(ti73)x$+!-6*Rb*_1I=yzQ7GbvleNfdyjp7&L+e%I@{k^aOX2d z*={;g8C@2@`>#@YRhmhTs13q_%klhv#>5g#YiA>c*mgju@P)XH^EW}npM4i2dl@hu z03XQHM5fuTe+%nT;dn_Y$=287^QSFuHFNDeKkXllBa!!R6dwRv=}5eU7yN4tr~YAU zZT&n$vi$24kPME?z1&ywsAP)cWWADk3X2hyL#QfE`$P45jgi9@>=UKd{Qm$NFt)aP zAzJ~@FTaIKs>Rt7*CNMFMI^IXN=C>Zr=w9x%&xvaV@$!FEnC_-tSyw^ zk1ENqv!Jn5UX-s69grxrjeCM=Cc{0$4fMAT>WnP8Ax}=+=~-rJuj#isS4?Ka@BsEr zA1Z}om6vvPZPI(|A%Sr)-p!e{HHh+i2;*^~?X5YK3x?%c8(b^tuc7%+!pb(7X*5E8 z9`B_LJneYSkxBAB^|EWQ>Uh_Z&JFmZdfomsMUI^R0FaSO{Z7g6@#{wqjNgsBGM|Cy zYiEf&TsvqTy&v~|Yh>eTggnpm`;T>Ov1@3U`MF0U@~w_MZLr6)Ex367Yh#H!Y&4yR zCx?&Lh8GY?=M2M-`K+w$*myME<12SicOoe5y8`m@KW~w2BN^K**|U5&Qp64I&epwb znX;KQQ~lhjq-C=nG_c=DqmYB*v~GX$UlaM(4-tD9vxBD|G$U-MBOIN=q_-8`t@YYo z(WpGR{Hf&7$s~=DeQj($Rpe{FBTLBVtrq^47}yT4`B9C2@_oTe8t_qJOC`odQ(@m; zQ0==ClW*7STF&cvTYZ4jTFotk8@tra$s-CqwHk9Tf5NcCYC_5bFy&%uUANs&7WcxD zFKF4GE*(aP-$M?nZlsaN+q#8aqc>_PxMuFGh^@37 zx#>li+>>)m65Fa+lhTRIcA2<$RBj{%n)#R2sL)OrFa)1g^y;!LmiA-mQ)1FXnRF~p zwQSq6uC_P0p&V>CbfudD&itj1 zF9S>D{$^=TSt7lGtB`Q`yg#!&3`c7XH{L1NA?NI`CY)x%$)XeJ{{U?aJ|oUX+AA>p z4Q!=*->`c3R?Ur@w6@|q>7$fol{oZ0nRm1r-Udj-kbmk)^cAVm zMrUrlO&nc_Rosjp#Qm#+15E3iznS?{6d9V@4zv-JG^{RHmYTH|{=A)|&oTN_W$Z)< zzlR#brL_?aY8+xH%^BVtZAS$&4>*~VAhuhJ+L%0>L4P}@gdV>7oevRbxZqf5PqbDQ zo@1M1abfLWrD4@rdDUeXrGca|?Cu#)Gt1}ht&3S_k9kXQYgN}r99FRRT_d}*)$s2N zuP}ALwvJUICgPmpaZ8$AkoE0*R#Z~N@)St8SKU4J1Xxwh+cBHHxzUuub~&|GJSn!S zPU*{Tw3%~lHP3z?%81`;x)&C=vY5s!m)!@Y97A@b;od`-1qm?m|5E^#7VK_D`z?e!^yjU zRQJ)s&v|1bc20iY{`xpsnX_AQ$D}^AV|xkX=WadXnNE@?Xn!Ls072wx*4p8CTZuvQ z6|}=KpALVf4a)d^D{BXw!M|xo^!xlfsL6q4o*VtwPXqF!i@~Mu_t5^i(_$lF*hgQw zKDSDIOwz1IJKFX z@ursUzMq9hC#90kda(ShQCJ}@5xE+Jdyj=+@oySPfijTCzokUzRP1CRSax_=eiXB` zV3x{8oO`}CGPGe*2`;-XIZr=#!jdQRGsbemGL0J5z0+MQJ_1ya5IAH%D)Pd)tU#JR z6=p^g7SsK;yOp@Pk+CkiOt%evoL|-!9b-lHz z@vwx7+*gksDb~osboXmfM5~mOl%BfKMHUJyKHE|AJ7x5egZak89X5B~iM%PisuMaE zvgu`C15!q@A0>G&n=Y$Kh{bK4Tkhm1m2Bl^Wtok$a2?f%()RvIlMLa?=+T~kLta&b(Z8z~4BkBrHZKFcbLU|?T>On3PweK|$~$o$ zeZ+FFE5ibk+$_+ZbTG@g8ojKeiRNn3KGR}1QFVMF-N0b?W%JIJm zK>5^|E!>i~o7Y7=*Thyu$sEx(SL9@{)9}3@F|VA_qLH+3qg9aD+d_4t1}Zd<5JlpZ zdH7KD9G&~{zgk>8&z~n|+VQQ7)3kXTvfKqc638R6=dtT;2jf}U%Q@qEUnWxA89Cp+ zw!%^;+FHQ!w;D{jv+JJxJ(VUS3C0@~uR)3kajB?TI5*auq6o$J zq4a1?g(GJF0DFZdo;bFR7M^bTtT|L^I+A1sTTL(1x`b~bkn&<> z91js%*f{Mi_#fn6C(7fm1LHnNI`b^`k6nvc!67-2VVwX`TbI#i-v$=i~LG zGA_#(7in+Dx~0cQnI=Wb=z8+Fr;)CzO`2YQ1H9I;h?MqY<~)1<0DEYjb{R%r1IX78 zvKdn97TK@()+R0kZv{1 zBC2{}3V#{4sP_=+1*l2kjczZz)VT14aD-&X!{#X?k|jx{`^$Gw3cFdxwr<2; zJ@v2eE5yV{KCFarVOy_|^}W8xqsnt#fi~uQH8v+Hvk2twx}AO%h{BsL`y%^w9~xQp zKX*3|S9JWT;U{=V_8r&!=|eV9t_i83&HZePfA9IwM%t;UlQ=IK6FvGi`XKPpW9 zr5R6%(u9%ll?-Dr;ObB3OOEs$j(i)XVkA?c%Wn%*!r(1|4tN?7i8F?SwaD*On5kT| z#>*bOma#FXYc|p`*R5<$jsVSGwAn{@6KgqGcw zC*e|+AdRzX4;_BcO0viX}TGHVpcrfQQC93*l2XVDzJv=}DF z4@G?^??1e9i<@XDx{Rca_4QhmObRDtI*xt)X=66BUdGjATady}`fpNU5=|vCWmDiS zPcSzoIOG@gmyg1pF?_p1Ew6M#^qq#OAC5 z$o685Ww#sFB?%eKsEe1qukx&8815aTO?Vz(I>fTZ8)4taog@wSO&o^Y?Uw%lI(hB6 zA`Slj7Wh<{VsmWQW;_FmfZMXyKaFZEzzh38G-SBQ)2lCWJ*vfFHsKUavoRie2Cqmx?lT8ci5OnlQS;yw;E`KcOp&iZ}|ILIJ(tDSZ9L zf~1={9x&s&nkP{nA~qIoULe(oM?Lg04FP5NIR@JiWppGlMPYVpLp+~gvRM7 z7^Ph=^fc=*2212u9+!W0B4yji+shUAQ@yBWZk)vyQR{DT-Tk$l` zH{>cJOZq&I#-YL^H=W8GUI?{^1lTHH!;N{e?-Vb!m4{wbtt0udD@}jwULTD~6lZ#A zKN4z4?b*V)_uJ`8JE%#=Z=WxAwDBhdQTSGUPVQDD^9Sf_W<{51egfYbUF@wS48)#B z`MxzPM!|MuI34;@%BhtGN4Isss1-E<<&O8_IMYi$BO(VQD(g>nH;t{A{YuXiS+G-R z(Vk89tBc8lm$2N>)*lxPn&;TFTSpv^%7nW`2zi|=Bgz^@A>%gt#X4Bhz6WTMFtY<= z>=XpDvk31Jw>_caeUuP=+@{&OM8991Nsev@P(QG6`qo@QxAn0n;Iu^Cy!Ipi0JSIy zimpSpn4LGz0aKhr+l!dyzO6$#g!NO^UR3gWicK~v7F#F=OZC>Xn=0EkvegM1$fdnSuz%QSner-=SRTakWZ zju#Q;R?`7fgm`X<(-d_1`^Dtjz!8^VhS_GI>#q312-nEP}eT0r-**+9Py+6v=#>KgR1OBrImVpas$ zFE5Qi0P~Q!&!-!A_*No0Uo3Mc9{mkS$0EEVE8V+Cg(71^UQz8lg-d~&O9?T(TOY@?yfy! ztc~)kk><;JusJ6x&|brUsFBft9(l(dc@Ni024xwR_?d@)qAL%Fo%I7W#13|-aV|54 zeIxa)R=#PZ8&^Fqz*LAhvXNdu$(61Eu;2l?y+AVt&*3V{*+tFo9~)G2NCB0_^LG<_QT3SJylGQwvyS(W z_foJKVr|I3u{oNST>`ebugmFAi9>kPaz6g1lrRCfwYu}Ai_nN|8^@JGmh)ieW8uJ5 za$i_tk?roOaZjS6+vaKsX5KEwOox(bVfa=QzPZ`vKd)s^h%1|6HuxOZ^eS1c=BG@O z5IrksM|DbNkY$R+OA+p5Av-Qe^yU1k> z=nor?BD3JJ8Cgj$oO;^Z-${8kGeXuQE}k?JHY3*~)uv+=@_EdsP5%Hydj)Kaws#vf zC!aPyu9Ou;fEE<(doymWJ`)miN`p1W3u#+3V$;c;{{Yy$2g0nYyaxH}N`^LPn&f34 zb~t>fz<1w_u1^nCd%n#1Ms63jwY17JTG#!^@=3s}G9E*y_*UcOZoFv!0812tJd+s3G;1~aWpXVl3R+&Yir%#Ood1* z&#htl=axMtxa4js+)sIZ7n9NL~=uZ3OPWrhcP zU%WrQvU&jT_X^9h+N37ska1IL0yobI8Kc*Lt~yg;qy?8X;&H&d)s|S#gK@KeCT&70~cytxM&ilm!bu~Qw z4$XWAAI6R<%P!sA4EpZU&!6v?BBOe}Xzu7cYtu-e2u$RA3Dq|F>e+&?Anpwi0CiH=SUt;ZvJF`Y_TzLjh1 zSGK>&*r)KOH8ws>{Qzz>B1B4(6uMb%*Z{{VeVfHocX zU;BIfD-gJL1ONy|^v8{5LFSn4BksQ+*L86@@9ec?lg?o!^p~$u-%T43Nai`xMhMx7 zgRQz7QmXKeYZG^CSaLlqHNDpQ)^0&G@cmwaW2Ma#oPQA}ck8qCMKS}b@$RR5_s(yr zspAIQ88hDeIcZVhNU)R*IokgKLHN_5vpUDPxo@fBeboSd(#9pXE1KA(fHH5lvr+ik zmkS>7E0LLbT-^OCEG?GTO?@RdqcLDLn@jN|<)@7l-b^5WZR{{Tjl5r7LOtxXmf{((jIbSn(-RjiB8wO*Qd`Yc}c3~L@&+({nnU&l& z$s@OS8nm(&b~huZ?~=gf=EGc_^t)uqCol~z8RjB&);BzB64F94b|wqSy>ER^lB$?; z-b?X4El&;1m|3+(995{Z_Tr2c1a_0HLn+oshD-gM>6L-8{J)WzjezG~a`NF{nb#-O zRIeL`!{p3^jgQivN5wD)bhBLL@b93QW%67vt?9jFj978;`xXe7KRFHi1NRZ%dk;42 zU(q!jXoa3FQ2NpIpwQixZS`LB`P7Ywx=C+EeJ=XjMdzX?&O|l$irj0jD%DNQL4Bb8 z1y8d!Ln=vg%s(29dj=`pJ&Z0WZL-Q?MO!zer{`NKo6R(1RsQ0CUdqNuo2Hxzq84$Dml;d;cmTS^g8 zK^+J9pwczhQskcEepM=L&-Wizw#@1R%A(iSp^#_;GTXlNqkSzLPdj9@a)Ez6yr#Z+RZNze>hZOgTh}koCf$^!@c>`n? zacokQAbg&zZzEHCy6A#_bI2J5ZgcE4!u)LRth%k zI_v)cE^5TsTVxAs)U#F`$ku3cXbzQ$#jxL==fE8WMJ{t;EO$fHfPQt19mqceSd<-= z*~FdP{#B03`{qIv^yyG$UFi&&wdJU$3bqQu_8;2cTE)5a3Aeu0Ce-VkN;}7A<57nN zoJ6uhFR9(JAB9JY^g!4Bt))g>`N(A1)*~{F+%O~DwF>AmMHAlMH4YvES7Oa_ISkc} zV3&4f!%rS%LAxO0wIIdHF%WPbwfa=4g$0#7!dt&qCZvSBY|ogC zDc}IstTa^=k{;5=*pt%dvtP6DmL<-u-F`HTsgN-y-r9*{)oxh(G^r(ae`OGxD?f(F9@2Q|a{KR|c{cozk_}tYqRh0sBrMD(FTj-M<`h_joNQU5z?14Z5#_{x7eFi+BN*zaU{@b)GUXH z9KORWhfj?xseG;0q-ehT2t$4!N?BW2XI$EHxV0iV^RZqt;5d9~BX+utSDmR^IWu2~ zBE(RICC;brUy;9{dVP^FTPnz-+RJ13iiJQo!%pe!JnIaHQ?$$nfHj5^cA$miYkO-S zEQyrJv5-5c0BQ-a#~ipI{j;^BNW`c@Dq-IY}v*hs0GGq}oqy?%9#U<2&|$`3oR ztVDZD5-d>u2BBkP{;D5DeuYOSAdu0GzF%&Ylrb9!v&sF^H~FFn`;-g+05R3RRUjId z+#cO3WVYkW93#MLYY`~ETs(P4iPPu!QbT(Xmbk0JtmubgzRI-dL{KmhTx!kC*OZw| zZ7H!)=qK(FkCBfCwe3I?n1RQwG|zi4N?BKf3)Ss@>fg7rvX#1#Mm7uO2y@}v)*UNo zPH5)dU^uT@Y%U>(lgX@8Tc283w?x=w-Rjn;q(;o_4t42K&bPiyw>;>ZYIy$uATWr? zzLmIvS+LHng_B&qsqXqyv@Mrj-TNSOt(+H!3w>qa(wY&*xm9=%v9@uRe@*>f@36@s z)2CW|Q|T0Dz;vj_e1)!|x*M905>#LAaVyfLf?_}!l@CM8qmZd5DB270BAuhwHqpl$ zc+)Upb@X}KoL|i`u^|21a&HQ3{j2L!MyPhHX)|A^`KjeU)kx$4b*N=!CuWX#@ZnKM zFPzGwJ@xNDjbmg6;IhO$oN7`9qZ2V(^sHR{RFA@;K)!Za0|D*&Rx7~;OQySEeVIQh z!#;fPCG!r3+Wcz@Z}(#>_iBE$L-wI;%1HD`{d=guMzaE|()O$z%@W2W8DuuK$?IEn zTkS?h`24B@Ui(}-FAjIV#)}18&j^#Hy8I0howA7-H!Q%4SY3{ICAuD}f5k^GA1NuE z4RqZ(z6P#F%}$^bdjrCWRqeMASQ;|6B1b`<87#Lpzh3GAuBu#V{{W{Lq<2wynWvKD zvZD5&>^G%5M)uTqAc#)TuQSr4+OSQU8KnOJbAE+x{{UYXnBSU>ess*ev!V{BrP&;; zA{(vD^FI0*nG{aTVG=0N9;TWnmuWw4PJ{PO_4W5s;p4f;Gd-!HzulcYKEhpwP&m|g z?8s~waG?j?%)9Wvt=&mI)?KFarweybf?`eC55c6>f$}eZw=3{8> zuC%hqHc<}5eEq*lX=LoJZ6xgSTksW_<{#Q0U;VV9$nEKVwC<#A@d#o)&r&UvNgth7 z=cg)ZGVNkxay)6V5ML@A6ZNLZ-f(IY*6^UnqlK6sNPUsSdoOi1?-r=sedm+N19Y*{ zqD9PJX4?>W-_c)vY|%B@UP(_g`n^UnT~0ku=suqwObE?wG^4Cg#Wt~oakr5Rg!QM5nWeuv{w zdNJi1w|M98s1DIrO^Mgi96nVXQcLG!JCfU4o)}jl+WGK(B|Ru%Qs)o?L7%wPracl_ z7Y>%>zh=$GlFk4h+t1)?L$wA;aX~NBx`0v+!B4!Opr)H_0^#F24k6D9FPF|lH}Z{7 zN=~JTXD6!=`k4zjkixm9K}h9!v+Z(apt`%X>AR~$onBDP4P&Uuvw&!=U9wX z*UJsh(yq6&6O&HWxzuX$my&nSkop6!vr1IvcnXZ?(j(dLwI&iBG|udJcT$6{L2D04 z)*5LTna>m)Ez{vlgx!`c?&4~9&1Eb|HseW#GGt*Gy7k=r>j#F-zZ9qsuu(F0S9{%2 zC$4`wgABm3KoJ|IWQY_o{oVZ%wX|*4l`K^HFVpa;P+sM9wI_%UQj4CZw?Y*|`4zv|Hm&M6t>hb4iz};65~D1bIx>&dun1DIp`2&a(Z3&cc@81VMD6@M$*8?FExU=r??%JYS~!blj&YV~(CV$$)rWGjB&z%O z8WH@Fh>Wzbw`nb^_lf~nCd6?*`bWeTNC~>aHF-}{SCS=!dMG)4jv>BW*OpVok4g$G zWq!yf@im%L5I_16U;hAgA3coIfZbYL%Qb-4hV^+kTP?orme;8;*m-V8VM}xFzu3`D z4;|%h1RtdsF<02iy4c)*xKNcwhfPL|I#VQW#w^zW{foWYiJ#qnrAQ{Q+A7L4{@rR$ z8%$0h1L06%aQZTfyK~4ndreJ>k+|_Ekm*;J;(1cwdaKfoZF9EnQNvHNCvr$uIZq>} z8j~4_e>EApsq5&9#A4?Y1)md3#M)Ue8AXM*`|HF5K%#o=thqsrall~N^*aWesw3ml`3OFmO+WXiVn zM-Q_W*UPq*VP9opN@MATq7m9FmM}05p3P_qv_&Ilxt*TQ-8#LH)nmHHJU!84Z zVH7yED%Sp+{=PK;b9p1UYDukTkpVx*MvmJf#9H3kl0r6&TX8*O%h_4c7Zr&-WWMBd zGg~)1eTH|Fa@JVwYnt-RR`kuNUP(qrK2a-hQlyW446%@Tmdc~tD*jIL_s1{GEiA5> zn3)ap2(BriI32uaeM$>3Q%#XwQ;>13WDKFLx(}p!Q(;~#&6{(oo^xjJ9+tT*iRwn+Eow=??q)*}-; zS~(7#$flU#*@y-@R7jqo5KceJsQe64zUv_4H8g8u+}Q%0My!j>vJnrRz? zi<-hCskBPdH+kb&a1$bhGcf8;=+j8t6kOGoRkWviG0N4R*U0;;>&eUALI*plHn@iM zkL%$^R~bWUi_m`JL+O*7z*6}x@1wbxvRL1dzY$6=*d%`-$sB=^hJ(JCYM<&$>ZjDu7;oWli^F$$zSPA=Vi3gE zO{vk3TE5zO0p*8Gia+Kxz56_$?vi@S*jUp1R@bFau>v`5O10}QY)nhZlM$I}2}JH( z42ADvX&u!j$^BG1j^ZiG-#Twwx{Rcp{B9~4m+wflgS>O45VFP{Zf~Gd)9g?WVCEh_ zp-Ub3%zhu8Nh^!w8zi{vf6k5>^#DSi%hn=onI+wLa=EBxEo{zPFI$?LcD6_tW~cEq z_^1AyW>PQA{#Al$8%LLEA?sNDPxTmBYrqr!DWl96Dg)Pbczf8^(d+}gwe?2xd@5-P zyGR|I#E70@S#O?U1(jJ|gGiQ=@-P55!g(_PY|z zV&GrasaZFPlXd!5HU+-v6EBUbRvWU)yGxPjFKK89tg=4xJCD2_4~1c)A9Bt!choYv zYdqzL`$sCzsq%?ghj9H0C=Rj^g0VSwOUSrn?yO9pTYn(%T8j_^QMthD)Inw zqbxXm$VTE?<)rIh#WrGFX(=Z{exJBqjer+4ak#K|n&^jxs_hN`0OmE4BnmsIp(J0L zn;P>|u>SyXy%8tg#@Qdnj!o%c*1+q-hqk<|2G|=bTynPxSea40oRGPe_Y|V{!eX@7 z^~6-TR{Q)u{0;G`6*ps@Hi{3T!;jD3UT=CitQ#=wu>F2C{{a0@m+~ht9Wu8LU27u8 zwst$QTTc^Sa};KoM`cdx{JzD(LHDz9?pjz_2`q1@=k^k-nYjz`tdcws+;HA+5r);NZo8bH_ z_J(fIR14v8LOC0T5wiHUzY3LYZKi8`s{tjw1^f+XWBpRUDnv~Jx5}bBAot9GX3Kp( zhOt5i&HO%JXthGYdm5hsrAh;4+Qg6l0Ci)xtY0c2@5@AE1DLmDW6WRlZ&?tXq$kZc zTX6g+Po0?pwjldJ7WmfE5s_fSmRs^>;Ya@f<#7K1k3apjgA$zuca&z!s z*H7FSNhQf283-SRTMq<|tDrrV_nJ^8!l7^PFIN8m;w|DJ6|e0U_Tj}Xr@={nS%@@4 zslvRem5Dc7`X-Weu$nWHRsDPEp>1&ZjahtZ_=ytOW00-4oDLdNC?-r6BeFT6`qoeU z#t&6KvQdsCJ2_iwmgU#=-CGmor$(**ZtSlg2+rzUxN0g zYS#}Q8V*b88fhHaR9w^Ir&~)g{{W?V3Mi$eap=amtA15h^>1@m0f+mR)YG&|%0{K6 zBjsC7+_$y%hQEi!2w$qpODngPN1K7({{R%u0kiqV%sSI7?(pV1FzoxPTr1g??8?CD z;wxrjB2fLV?v)V$Z4|I!*P;6h=%Q>l;y?cYnvY%6g&&5WTJmhbLnNX=Yn+RERs%2et?O*Fbl;&m`G~t~!U&5l=1#u?nb>ZDdVx{t( z7X9?l#6qjgu~~WzPvUDWzV{K!nk6>3@~NyX>i+U zW3cxAtx-gWdlM^3uOYxx!RAzvG|*^yR3|R5i!C$d%O0;I_tLv2LVX^eW&JkMMF$_m zVwm42YTf6pUli!(`>M`OzvWuqhH{E~77(R0QOAXbG##dtK3md8VE}`yfSvyU3Udsh z6-V`riJ_i7t{eM>!1_|+Qb5}I*ulM%tvqtJXt5{=d-gv@gO$xF7MLjJIs2<+n`?oe z-k+T_NiJzH`?*`wO4&siA}88L{{Y&5%^6^sLPL2Y1BktNx&B7zwrMrB28&rxSj#4h z648{-*r^xNYuS7%I11(j=5f??p|H)eUfR8teWvtdH^c$x$GHfr8CC@KaO-czvNG)j z@8=G0jTn|dTyX-t=&zAHA^C^QKPq%t^GptC(2Eh&Qo^eqLFnwk z%|*iOCLW0Fx6+nb?`m&B8Al{J7y5D4SfpAvoaiU(j=Q!pC7 zG%4^}ft#}|(o9c)rkLpNrS3YlG|JoXNU=nESXSLMP}VPgAFI^H5?c0H!}>gltd9O? z4Xn*>({!M0Mf%f7+A52h^1MyfBiK&rWo1N*pDH^%21RZRH**hlUR1MzYs;_WR#~Fg zlA1-!8z?%5Thoj?0hDg+IOLb9td{{W_-#6*1Jpv-&f z&8`m>!}|~ALu{&ije^L1WxY?eob02teQu3UJW}t**&5o5Qg@T_{zqsFIQGxWN^9<`cC);xfcsmh*P0oPp=SDH~Rg|?Cwl#ZpWXr11#o=<8<{l!IKKXcT+-AKTk-THo zepHzj#0~9upRi_N+bP*ikB8Em4!mQX)qSH+{k7!}5}ho{eJv?)jzD%f_kAup8bx#2 zg7&v&=C=AiXJj4w{{ZLTM-?PHCE4X$@#9co6^UZ`t_LF36R#+k`0o+$rBFWZIYICz zN(^|U<$dGwspIUEvQck!PDI;L6aiW9$Bkg{#Xjkwzj5hFVot(#i;ofhDt(-xeAZ-h z%xFax&5|3i7xZ(3Q)YX~2~o$bVvZd$s^+-mz}ldbFhstl-Fenr>QP10Ta~!fdjP9I zRKMy-PZ?KgFTgi3Qd^5|)PW`geIxcDyHp{IWT`l3APK zP(s{1osa!SqD=yA7XAZYl|pU3BRsF~tX0~%%UbAJkVm4Aq1rhWH5!NRje`!I|D0IK!_*-f)( zB5P=|N5|h=Ii1%ywVQVJ{6-E?E%&gs$I`MRrZPSuAGSYT)OJ}g_SBchiHD+jQb{t~{{SyOzT^7oMnz5+!$>d2!$VQDU72?z9pKzl zaeAZq2zp6F&unn)cjP^}&(fC(4f8&VI?$69Dv_MoS+_6qufH#si3{|r#;ST<{*`$q zi93Wu{Oc_1m-~fhje{xXE6zlX;+Z)D$EyiMgXjyof~J(c9| zBfcH`VV1@-O#vijYyNb}ZN0c$@2`hCu@)oj#H4C_DE{t08lFWKym8CJmY4XN#Hx+x zrB*}!>Me?j<2LcOsBc)jM7(6uFYd6Y_KZMD0F6gcbMvBr{oH!{f5k~F?-t8$>wa~y z+Gp6sZ)rEITd`*lpx3UKH7*(`m?}Qd=Q@s6+(T{Azt~{7&hh8rxhqcDX7@_~hr^L7^vSGOO727Zr|%KF`CAK7=7)Gk<@l=81NN~n�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n 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%0A6FCOd%gjUpnyH_`i+2)^I0D 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 literal 0 HcmV?d00001 From 904e0f61a863a7a33d70304fdf7cf279c1e50444 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 4 Mar 2025 17:36:27 +0100 Subject: [PATCH 061/254] fix(pdfengines): split and read metadata pdf engines calls --- pkg/modules/pdfengines/multi.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index b56ecef3e..4995ccfd3 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -74,16 +74,14 @@ func (multi *multiPdfEngines) Split(ctx context.Context, logger *zap.Logger, mod var err error var mu sync.Mutex // to safely append errors. - resultChan := make(chan splitResult, len(multi.splitEngines)) - for _, engine := range multi.splitEngines { + resultChan := make(chan splitResult, 1) + go func(engine gotenberg.PdfEngine) { outputPaths, err := engine.Split(ctx, logger, mode, inputPath, outputDirPath) resultChan <- splitResult{outputPaths: outputPaths, err: err} }(engine) - } - for range multi.splitEngines { select { case result := <-resultChan: if result.err != nil { @@ -160,16 +158,14 @@ func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logg var err error var mu sync.Mutex // to safely append errors. - resultChan := make(chan readMetadataResult, len(multi.readMetadataEngines)) - for _, engine := range multi.readMetadataEngines { + resultChan := make(chan readMetadataResult, 1) + go func(engine gotenberg.PdfEngine) { metadata, err := engine.ReadMetadata(ctx, logger, inputPath) resultChan <- readMetadataResult{metadata: metadata, err: err} }(engine) - } - for range multi.readMetadataEngines { select { case result := <-resultChan: if result.err != nil { From 16e4c108629abe36cb50b62534a04e08e4d87d57 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 5 Mar 2025 08:57:48 +0100 Subject: [PATCH 062/254] chore(deps): update Go dependencies --- go.mod | 21 ++++++++++----------- go.sum | 46 ++++++++++++++++++++++------------------------ 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index d19fb35de..03d5d137d 100644 --- a/go.mod +++ b/go.mod @@ -6,26 +6,26 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250210231439-aea867ea8506 - github.com/chromedp/chromedp v0.12.1 + github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0 + github.com/chromedp/chromedp v0.13.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/mattn/go-isatty v0.0.20 // indirect github.com/microcosm-cc/bluemonday v1.0.27 github.com/pierrec/lz4/v4 v4.1.22 // indirect - github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_golang v1.21.1 github.com/russross/blackfriday/v2 v2.1.0 github.com/spf13/pflag v1.0.6 github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/net v0.35.0 + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/net v0.36.0 golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 @@ -35,11 +35,11 @@ require ( require ( github.com/dlclark/regexp2 v1.11.5 github.com/mholt/archives v0.1.0 - github.com/shirou/gopsutil/v4 v4.25.1 + github.com/shirou/gopsutil/v4 v4.25.2 ) require ( - github.com/STARRY-S/zip v0.2.1 // indirect + github.com/STARRY-S/zip v0.2.2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bodgit/plumbing v1.3.0 // indirect @@ -49,6 +49,7 @@ require ( github.com/chromedp/sysutil v1.1.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/ebitengine/purego v0.8.2 // indirect + github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect @@ -57,9 +58,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nwaples/rardecode/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 36a3ead61..06c78a5bf 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= -github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= +github.com/STARRY-S/zip v0.2.2 h1:8QeCbIi1Z9U5MgoDARJR1ClbBo9RD46SmVy+dl0woCk= +github.com/STARRY-S/zip v0.2.2/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/alexliesenfeld/health v0.8.0 h1:lCV0i+ZJPTbqP7LfKG7p3qZBl5VhelwUFCIVWl77fgk= github.com/alexliesenfeld/health v0.8.0/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -38,10 +38,10 @@ github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250210231439-aea867ea8506 h1:OfjMcN8R6eUWZfKyJaTnlyiZh1BGgmEKmRkCZuDtGRw= -github.com/chromedp/cdproto v0.0.0-20250210231439-aea867ea8506/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU= -github.com/chromedp/chromedp v0.12.1 h1:kBMblXk7xH5/6j3K9uk8d7/c+fzXWiUsCsPte0VMwOA= -github.com/chromedp/chromedp v0.12.1/go.mod h1:F6+wdq9LKFDMoyxhq46ZLz4VLXrsrCAR3sFqJz4Nqc0= +github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0 h1:eyqx6IwUz4gxJUU5CKiZsuGdNPVRCTWQQN5LpS1T5eo= +github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/chromedp v0.13.0 h1:ydOqt7Y9LkwgutrX5C8bx49D+o63L6WcGUDyIoE0A5M= +github.com/chromedp/chromedp v0.13.0/go.mod h1:O3nO4Lno7iLoVX+7GdqQkehhKG7DtLf/zFRyJo0AhXY= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -64,6 +64,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -92,8 +94,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -121,14 +123,12 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -143,10 +143,8 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844= +github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -167,8 +165,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -180,8 +178,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= -github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk= +github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA= github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -232,8 +230,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -276,8 +274,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 85eaef05ad0cf60917cd234f72433bcf85ef2f27 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 6 Mar 2025 08:55:57 +0100 Subject: [PATCH 063/254] fix(deps): update Go dependencies - chromedp ERROR: unhandled page event *page.EventFrameStartedNavigating --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 03d5d137d..ac1e8ba78 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0 - github.com/chromedp/chromedp v0.13.0 + github.com/chromedp/chromedp v0.13.1 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 @@ -24,12 +24,12 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.36.0 - golang.org/x/sync v0.11.0 - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 - golang.org/x/text v0.22.0 + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 + golang.org/x/sync v0.12.0 + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 + golang.org/x/text v0.23.0 ) require ( @@ -68,12 +68,12 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/therootcompany/xz v1.0.1 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.9.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/time v0.10.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/protobuf v1.36.5 // indirect ) diff --git a/go.sum b/go.sum index 06c78a5bf..2f8234abf 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0 h1:eyqx6IwUz4gxJUU5CKiZsuGdNPVRCTWQQN5LpS1T5eo= github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= -github.com/chromedp/chromedp v0.13.0 h1:ydOqt7Y9LkwgutrX5C8bx49D+o63L6WcGUDyIoE0A5M= -github.com/chromedp/chromedp v0.13.0/go.mod h1:O3nO4Lno7iLoVX+7GdqQkehhKG7DtLf/zFRyJo0AhXY= +github.com/chromedp/chromedp v0.13.1 h1:FDh9CfaAt0w70gl69Hb69M/xgZrWuppH9AW22aGa+iU= +github.com/chromedp/chromedp v0.13.1/go.mod h1:O3nO4Lno7iLoVX+7GdqQkehhKG7DtLf/zFRyJo0AhXY= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -197,10 +197,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= -github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -230,8 +230,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -274,8 +274,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -288,8 +288,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -311,13 +311,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -325,12 +325,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From b31024c362c3aed9f0ca0afefae16b351db0bb2d Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 17 Mar 2025 13:20:00 +0100 Subject: [PATCH 064/254] feat(libreoffice): add updateIndexes form field --- build/Dockerfile | 2 +- pkg/modules/libreoffice/api/api.go | 5 +++++ pkg/modules/libreoffice/api/libreoffice.go | 4 ++++ pkg/modules/libreoffice/routes.go | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 3f65ed029..f8dff9a65 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -171,7 +171,7 @@ RUN \ apt-get update -qq &&\ apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends -t bookworm-backports libreoffice &&\ - curl -Ls https://raw.githubusercontent.com/gotenberg/unoconverter/v0.0.1/unoconv -o /usr/bin/unoconverter &&\ + curl -Ls https://raw.githubusercontent.com/gotenberg/unoconverter/v0.1.1/unoconv -o /usr/bin/unoconverter &&\ chmod +x /usr/bin/unoconverter &&\ # unoconverter will look for the Python binary, which has to be at version 3. ln -s /usr/bin/python3 /usr/bin/python &&\ diff --git a/pkg/modules/libreoffice/api/api.go b/pkg/modules/libreoffice/api/api.go index ca8b482d5..aaacab06d 100644 --- a/pkg/modules/libreoffice/api/api.go +++ b/pkg/modules/libreoffice/api/api.go @@ -62,6 +62,10 @@ type Options struct { // PageRanges allows to select the pages to convert. PageRanges string + // UpdateIndexes specifies whether to update the indexes before conversion + // or not, with the risk of disabling links in documents. + UpdateIndexes bool + // ExportFormFields specifies whether form fields are exported as widgets // or only their fixed print representation is exported. ExportFormFields bool @@ -152,6 +156,7 @@ func DefaultOptions() Options { Password: "", Landscape: false, PageRanges: "", + UpdateIndexes: true, ExportFormFields: true, AllowDuplicateFieldNames: false, ExportBookmarks: true, diff --git a/pkg/modules/libreoffice/api/libreoffice.go b/pkg/modules/libreoffice/api/libreoffice.go index 652d0e05b..a6109b11a 100644 --- a/pkg/modules/libreoffice/api/libreoffice.go +++ b/pkg/modules/libreoffice/api/libreoffice.go @@ -279,6 +279,10 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP args = append(args, "--export", fmt.Sprintf("PageRange=%s", options.PageRanges)) } + if !options.UpdateIndexes { + args = append(args, "--disable-update-indexes") + } + args = append(args, "--export", fmt.Sprintf("ExportFormFields=%t", options.ExportFormFields)) args = append(args, "--export", fmt.Sprintf("AllowDuplicateFieldNames=%t", options.AllowDuplicateFieldNames)) args = append(args, "--export", fmt.Sprintf("ExportBookmarks=%t", options.ExportBookmarks)) diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index cf712c0ee..47a2846fe 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -39,6 +39,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap password string landscape bool nativePageRanges string + updateIndexes bool exportFormFields bool allowDuplicateFieldNames bool exportBookmarks bool @@ -68,6 +69,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap String("password", &password, defaultOptions.Password). Bool("landscape", &landscape, defaultOptions.Landscape). String("nativePageRanges", &nativePageRanges, defaultOptions.PageRanges). + Bool("updateIndexes", &updateIndexes, defaultOptions.UpdateIndexes). Bool("exportFormFields", &exportFormFields, defaultOptions.ExportFormFields). Bool("allowDuplicateFieldNames", &allowDuplicateFieldNames, defaultOptions.AllowDuplicateFieldNames). Bool("exportBookmarks", &exportBookmarks, defaultOptions.ExportBookmarks). @@ -149,6 +151,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap Password: password, Landscape: landscape, PageRanges: nativePageRanges, + UpdateIndexes: updateIndexes, ExportFormFields: exportFormFields, AllowDuplicateFieldNames: allowDuplicateFieldNames, ExportBookmarks: exportBookmarks, From 7d14546d3b0c996d5b474ecb9c6248b13e0fb1fc Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 14 Mar 2025 09:37:36 +0100 Subject: [PATCH 065/254] feat(logging): add GCP severity mapping --- Makefile | 2 ++ pkg/modules/logging/color.go | 49 ++++++++++++++++++++++++++++++++++ pkg/modules/logging/gcp.go | 36 +++++++++++++++++++++++++ pkg/modules/logging/logging.go | 35 ++++++++++++++++-------- 4 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 pkg/modules/logging/color.go create mode 100644 pkg/modules/logging/gcp.go diff --git a/Makefile b/Makefile index 72741812d..334d1ad0b 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ LIBREOFFICE_DISABLE_ROUTES=false LOG_LEVEL=info LOG_FORMAT=auto LOG_FIELDS_PREFIX= +LOG_ENABLE_GCP_SEVERITY=false PDFENGINES_ENGINES= PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk @@ -134,6 +135,7 @@ run: ## Start a Gotenberg container --log-level=$(LOG_LEVEL) \ --log-format=$(LOG_FORMAT) \ --log-fields-prefix=$(LOG_FIELDS_PREFIX) \ + --log-enable-gcp-severity=$(LOG_ENABLE_GCP_SEVERITY) \ --pdfengines-engines=$(PDFENGINES_ENGINES) \ --pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \ --pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \ diff --git a/pkg/modules/logging/color.go b/pkg/modules/logging/color.go new file mode 100644 index 000000000..e9a9d97b3 --- /dev/null +++ b/pkg/modules/logging/color.go @@ -0,0 +1,49 @@ +package logging + +import ( + "fmt" + + "go.uber.org/zap/zapcore" +) + +// Foreground colors. +// Copy pasted from go.uber.org/zap/internal/color/color.go +const ( + black color = iota + 30 + red + green + yellow + blue + magenta + cyan + white +) + +type color uint8 + +func (c color) Add(s string) string { + return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s) +} + +func levelToColor(l zapcore.Level) color { + switch l { + case zapcore.DebugLevel: + return cyan + case zapcore.InfoLevel: + return blue + case zapcore.WarnLevel: + return yellow + case zapcore.ErrorLevel: + return red + case zapcore.DPanicLevel: + return red + case zapcore.PanicLevel: + return red + case zapcore.FatalLevel: + return red + case zapcore.InvalidLevel: + return red + default: + return red + } +} diff --git a/pkg/modules/logging/gcp.go b/pkg/modules/logging/gcp.go new file mode 100644 index 000000000..e9ddc9f60 --- /dev/null +++ b/pkg/modules/logging/gcp.go @@ -0,0 +1,36 @@ +package logging + +import "go.uber.org/zap/zapcore" + +func gcpSeverity(l zapcore.Level) string { + switch l { + case zapcore.DebugLevel: + return "DEBUG" + case zapcore.InfoLevel: + return "INFO" + case zapcore.WarnLevel: + return "WARNING" + case zapcore.ErrorLevel: + return "ERROR" + case zapcore.DPanicLevel: + return "CRITICAL" + case zapcore.PanicLevel: + return "ALERT" + case zapcore.FatalLevel: + return "EMERGENCY" + case zapcore.InvalidLevel: + return "DEFAULT" + default: + return "DEFAULT" + } +} + +func gcpSeverityEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(gcpSeverity(l)) +} + +func gcpSeverityColorEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { + severity := gcpSeverity(l) + c := levelToColor(l) + enc.AppendString(c.Add(severity)) +} diff --git a/pkg/modules/logging/logging.go b/pkg/modules/logging/logging.go index d9d80023d..36fc1ae28 100644 --- a/pkg/modules/logging/logging.go +++ b/pkg/modules/logging/logging.go @@ -34,9 +34,10 @@ const ( // Logging is a module which implements the [gotenberg.LoggerProvider] // interface. type Logging struct { - level string - format string - fieldsPrefix string + level string + format string + fieldsPrefix string + enableGcpSeverity bool } // Descriptor returns a [Logging]'s module descriptor. @@ -48,6 +49,7 @@ func (log *Logging) Descriptor() gotenberg.ModuleDescriptor { fs.String("log-level", infoLoggingLevel, fmt.Sprintf("Choose the level of logging detail. Options include %s, %s, %s, or %s", errorLoggingLevel, warnLoggingLevel, infoLoggingLevel, debugLoggingLevel)) fs.String("log-format", autoLoggingFormat, fmt.Sprintf("Specify the format of logging. Options include %s, %s, or %s", autoLoggingFormat, jsonLoggingFormat, textLoggingFormat)) fs.String("log-fields-prefix", "", "Prepend a specified prefix to each field in the logs") + fs.Bool("log-enable-gcp-severity", false, "Enable Google Cloud Platform severity mapping") return fs }(), @@ -62,6 +64,7 @@ func (log *Logging) Provision(ctx *gotenberg.Context) error { log.level = flags.MustString("log-level") log.format = flags.MustString("log-format") log.fieldsPrefix = flags.MustString("log-fields-prefix") + log.enableGcpSeverity = flags.MustBool("log-enable-gcp-severity") return nil } @@ -101,7 +104,7 @@ func (log *Logging) Logger(mod gotenberg.Module) (*zap.Logger, error) { return nil, fmt.Errorf("get log level: %w", err) } - encoder, err := newLogEncoder(log.format) + encoder, err := newLogEncoder(log.format, log.enableGcpSeverity) if err != nil { return nil, fmt.Errorf("get log encoder: %w", err) } @@ -166,7 +169,7 @@ func newLogLevel(level string) (zapcore.Level, error) { return lvl, nil } -func newLogEncoder(format string) (zapcore.Encoder, error) { +func newLogEncoder(format string, gcpSeverity bool) (zapcore.Encoder, error) { isTerminal := term.IsTerminal(int(os.Stdout.Fd())) encCfg := zap.NewProductionEncoderConfig() @@ -177,15 +180,25 @@ func newLogEncoder(format string) (zapcore.Encoder, error) { encoder.AppendString(ts.Local().Format("2006/01/02 15:04:05.000")) } - if format == textLoggingFormat || format == autoLoggingFormat { + if format == autoLoggingFormat { + format = textLoggingFormat + } + + if format == textLoggingFormat && gcpSeverity { + encCfg.EncodeLevel = gcpSeverityColorEncoder + } else if format == textLoggingFormat { encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder + } else if gcpSeverity { + encCfg.EncodeLevel = gcpSeverityEncoder + } + } else { + if format == autoLoggingFormat { + format = jsonLoggingFormat } - } - if format == autoLoggingFormat && isTerminal { - format = textLoggingFormat - } else if format == autoLoggingFormat { - format = jsonLoggingFormat + if gcpSeverity { + encCfg.EncodeLevel = gcpSeverityEncoder + } } switch format { From 6e2270632e365a19d3d1f4cd25757a30f0a76ee3 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 14 Mar 2025 09:45:23 +0100 Subject: [PATCH 066/254] chore(logging): remove unit tests - integration tests are inc anyway --- pkg/modules/logging/logging_test.go | 347 ---------------------------- 1 file changed, 347 deletions(-) delete mode 100644 pkg/modules/logging/logging_test.go diff --git a/pkg/modules/logging/logging_test.go b/pkg/modules/logging/logging_test.go deleted file mode 100644 index 8d484328b..000000000 --- a/pkg/modules/logging/logging_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package logging - -import ( - "fmt" - "reflect" - "testing" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "go.uber.org/zap/zaptest/observer" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestLogging_Descriptor(t *testing.T) { - descriptor := new(Logging).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(Logging)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestLogging_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - level string - format string - fieldsPrefix string - expectLevel string - expectFormat string - expectFieldsPrefix string - }{ - { - scenario: "default values", - expectLevel: infoLoggingLevel, - expectFormat: autoLoggingFormat, - expectFieldsPrefix: "", - }, - { - scenario: "explicit values", - level: "debug", - format: "json", - fieldsPrefix: "gotenberg", - expectLevel: debugLoggingLevel, - expectFormat: jsonLoggingFormat, - expectFieldsPrefix: "gotenberg", - }, - { - scenario: "wrong values", // no validation at this point. - level: "foo", - format: "foo", - expectLevel: "foo", - expectFormat: "foo", - expectFieldsPrefix: "", - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - var flags []string - - if tc.level != "" { - flags = append(flags, "--log-level", tc.level) - } - - if tc.format != "" { - flags = append(flags, "--log-format", tc.format) - } - - if tc.fieldsPrefix != "" { - flags = append(flags, "--log-fields-prefix", tc.fieldsPrefix) - } - - logging := new(Logging) - fs := logging.Descriptor().FlagSet - - err := fs.Parse(flags) - if err != nil { - t.Fatalf("expected no error while parsing flags but got: %v", err) - } - - ctx := gotenberg.NewContext(gotenberg.ParsedFlags{FlagSet: fs}, nil) - - err = logging.Provision(ctx) - if err != nil { - t.Fatalf("expected no error while provisioning but got: %v", err) - } - - if logging.level != tc.expectLevel { - t.Errorf("expected logging level '%s' but got '%s'", tc.expectLevel, logging.level) - } - - if logging.format != tc.expectFormat { - t.Errorf("expected logging format '%s' but got '%s'", tc.expectFormat, logging.format) - } - - if logging.fieldsPrefix != tc.expectFieldsPrefix { - t.Errorf("expected logging fields prefix '%s' but got '%s'", tc.expectFieldsPrefix, logging.fieldsPrefix) - } - }) - } -} - -func TestLogging_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - level string - format string - expectError bool - }{ - { - scenario: "invalid level", - level: "foo", - expectError: true, - }, - { - scenario: "invalid format", - level: debugLoggingLevel, - format: "foo", - expectError: true, - }, - { - scenario: "valid level and format", - level: debugLoggingLevel, - format: autoLoggingFormat, - }, - } { - logging := new(Logging) - logging.level = tc.level - logging.format = tc.format - - err := logging.Validate() - - if tc.expectError && err == nil { - t.Errorf("%s: expected error but got: %v", tc.scenario, err) - } - - if !tc.expectError && err != nil { - t.Errorf("%s: expected no error but got: %v", tc.scenario, err) - } - } -} - -func TestLogging_Logger(t *testing.T) { - for _, tc := range []struct { - scenario string - level string - format string - fieldsPrefix string - expectError bool - }{ - { - scenario: "invalid level", - level: "foo", - expectError: true, - }, - { - scenario: "invalid format", - level: debugLoggingLevel, - format: "foo", - expectError: true, - }, - { - scenario: "valid level and format", - level: debugLoggingLevel, - format: autoLoggingFormat, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logging := new(Logging) - logging.level = tc.level - logging.format = tc.format - logging.fieldsPrefix = tc.fieldsPrefix - - _, err := logging.Logger(&gotenberg.ModuleMock{ - DescriptorMock: func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "mock", New: nil} - }, - }) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestCustomCore(t *testing.T) { - for _, tc := range []struct { - scenario string - level zapcore.Level - fieldsPrefix string - expectEntry bool - }{ - { - scenario: "level enabled", - level: zapcore.DebugLevel, - fieldsPrefix: "gotenberg", - expectEntry: true, - }, - { - scenario: "no fields prefix", - level: zapcore.DebugLevel, - expectEntry: true, - }, - { - scenario: "level disabled", - level: zapcore.ErrorLevel, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - core, obsvr := observer.New(tc.level) - lgr := zap.New(customCore{ - Core: core, - fieldsPrefix: tc.fieldsPrefix, - }).With(zap.String("a_field", "a value")) - - lgr.Debug("a debug message", zap.String("another_field", "another value")) - - entries := obsvr.TakeAll() - - if tc.expectEntry && len(entries) == 0 { - t.Fatal("expected an entry") - } - - if !tc.expectEntry && len(entries) != 0 { - t.Fatal("expected no entry") - } - - var prefix string - if tc.fieldsPrefix != "" { - prefix = tc.fieldsPrefix + "_" - } - - for _, entry := range entries { - fields := entry.Context - - if len(fields) != 2 { - t.Fatalf("expected 2 fields but got %d", len(fields)) - } - - if fields[0].Key != fmt.Sprintf("%sa_field", prefix) { - t.Errorf("expected 'gotenberg_a_field' but got '%s'", fields[0].Key) - } - - if fields[1].Key != fmt.Sprintf("%sanother_field", prefix) { - t.Errorf("expected 'gotenberg_another_field' but got '%s'", fields[1].Key) - } - } - }) - } -} - -func Test_newLogLevel(t *testing.T) { - for _, tc := range []struct { - scenario string - level string - expectZapLevel zapcore.Level - expectError bool - }{ - { - scenario: "error level", - level: errorLoggingLevel, - expectZapLevel: zapcore.ErrorLevel, - }, - { - scenario: "warning level", - level: warnLoggingLevel, - expectZapLevel: zapcore.WarnLevel, - }, - { - scenario: "info level", - level: infoLoggingLevel, - expectZapLevel: zapcore.InfoLevel, - }, - { - scenario: "debug level", - level: debugLoggingLevel, - expectZapLevel: zapcore.DebugLevel, - }, - { - scenario: "invalid level", - level: "foo", - expectZapLevel: zapcore.InvalidLevel, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - actual, err := newLogLevel(tc.level) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectZapLevel != actual { - t.Errorf("expected %d level but got %d", tc.expectZapLevel, actual) - } - }) - } -} - -func Test_newLogEncoder(t *testing.T) { - for _, tc := range []struct { - scenario string - format string - expectError bool - }{ - { - scenario: "auto format", - format: autoLoggingFormat, - }, - { - scenario: "text format", - format: textLoggingFormat, - }, - { - scenario: "json format", - format: jsonLoggingFormat, - }, - { - scenario: "invalid format", - format: "foo", - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - _, err := newLogEncoder(tc.format) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if !tc.expectError && err != nil { - t.Errorf("expected no error but got: %v", err) - } - }) - } -} From 8252142e654f3c19c11030807811a6978ad26eff Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 17 Mar 2025 14:01:07 +0100 Subject: [PATCH 067/254] chore(deps): update Go dependencies --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index ac1e8ba78..687ccd1e2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0 + github.com/chromedp/cdproto v0.0.0-20250311215558-29dfcc2791de github.com/chromedp/chromedp v0.13.1 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -61,10 +61,10 @@ require ( github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nwaples/rardecode/v2 v2.1.0 // indirect + github.com/nwaples/rardecode/v2 v2.1.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/therootcompany/xz v1.0.1 // indirect diff --git a/go.sum b/go.sum index 2f8234abf..237ed873c 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0 h1:eyqx6IwUz4gxJUU5CKiZsuGdNPVRCTWQQN5LpS1T5eo= -github.com/chromedp/cdproto v0.0.0-20250304222902-64be311ed8b0/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/cdproto v0.0.0-20250311215558-29dfcc2791de h1:tOKSCbB420VENW1Wz10EnSXr4jLnWoq25vnBW4ScmF0= +github.com/chromedp/cdproto v0.0.0-20250311215558-29dfcc2791de/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= github.com/chromedp/chromedp v0.13.1 h1:FDh9CfaAt0w70gl69Hb69M/xgZrWuppH9AW22aGa+iU= github.com/chromedp/chromedp v0.13.1/go.mod h1:O3nO4Lno7iLoVX+7GdqQkehhKG7DtLf/zFRyJo0AhXY= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= @@ -155,8 +155,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= -github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew= +github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= @@ -170,8 +170,8 @@ github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuF github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= From 147e3b11738362b257f4d86512dfa94159206e12 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 17 Mar 2025 14:51:24 +0100 Subject: [PATCH 068/254] docs(godoc): improve the UpdateIndexes godoc --- pkg/modules/libreoffice/api/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/modules/libreoffice/api/api.go b/pkg/modules/libreoffice/api/api.go index aaacab06d..2ca56ed91 100644 --- a/pkg/modules/libreoffice/api/api.go +++ b/pkg/modules/libreoffice/api/api.go @@ -62,8 +62,9 @@ type Options struct { // PageRanges allows to select the pages to convert. PageRanges string - // UpdateIndexes specifies whether to update the indexes before conversion - // or not, with the risk of disabling links in documents. + // UpdateIndexes specifies whether to update the indexes before conversion, + // keeping in mind that doing so might result in missing links in the final + // PDF. UpdateIndexes bool // ExportFormFields specifies whether form fields are exported as widgets From ea6854fadc4bbe315faa98fa69d32b772f163b56 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 18 Mar 2025 08:55:09 +0100 Subject: [PATCH 069/254] docs(godoc): add link to issue 1149 [ci skip] --- pkg/modules/libreoffice/api/libreoffice.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/modules/libreoffice/api/libreoffice.go b/pkg/modules/libreoffice/api/libreoffice.go index a6109b11a..f196e3272 100644 --- a/pkg/modules/libreoffice/api/libreoffice.go +++ b/pkg/modules/libreoffice/api/libreoffice.go @@ -275,6 +275,7 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP args = append(args, "--printer", "PaperOrientation=landscape") } + // See: https://github.com/gotenberg/gotenberg/issues/1149. if options.PageRanges != "" { args = append(args, "--export", fmt.Sprintf("PageRange=%s", options.PageRanges)) } From 2544d824865d1b372359bb1194164dd33688b7df Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 18 Mar 2025 10:08:13 +0100 Subject: [PATCH 070/254] docs(README): add GitHub Trending badge [ci skip] --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 51e60422c..7b29f5355 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ Go Reference Code coverage

+

+ gotenberg%2Fgotenberg | Trendshift +

Documentation · Live Demo 🔥

From 536d3ee70d83a970a48530d1c49b1d33384a4bae Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sun, 2 Mar 2025 17:57:52 +0100 Subject: [PATCH 071/254] feat(formatting): add prettier --- .github/dependabot.yml | 2 +- .github/stale.yml | 2 +- .github/workflows/continuous-integration.yml | 34 ++--- .gitignore | 1 + .prettierignore | 0 .prettierrc | 3 + Makefile | 10 ++ README.md | 2 +- SECURITY.md | 30 ++--- package-lock.json | 129 +++++++++++++++++++ package.json | 6 + 11 files changed, 184 insertions(+), 35 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3bad94f94..6fddca0d6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,4 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" diff --git a/.github/stale.yml b/.github/stale.yml index 8cdadff12..fa0398517 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -15,4 +15,4 @@ markComment: > recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false \ No newline at end of file +closeComment: false diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index bc2d55fa0..75980b6b6 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -64,7 +64,7 @@ jobs: snapshot_amd64: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/amd64 runs-on: ubuntu-latest outputs: @@ -73,7 +73,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -87,7 +87,7 @@ jobs: snapshot_386: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/386 runs-on: ubuntu-latest outputs: @@ -96,7 +96,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -110,7 +110,7 @@ jobs: snapshot_arm64: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/arm64 runs-on: ubuntu-24.04-arm outputs: @@ -119,7 +119,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -133,7 +133,7 @@ jobs: snapshot_arm_v7: if: github.event_name == 'pull_request' needs: - - tests + - tests name: Snapshot linux/arm/v7 runs-on: ubuntu-24.04-arm outputs: @@ -142,7 +142,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -164,7 +164,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Merge uses: ./.github/actions/merge with: @@ -182,7 +182,7 @@ jobs: edge_amd64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - tests name: Edge linux/amd64 runs-on: ubuntu-latest outputs: @@ -191,7 +191,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -213,7 +213,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -226,7 +226,7 @@ jobs: edge_arm64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - tests name: Edge linux/arm64 runs-on: ubuntu-24.04-arm outputs: @@ -235,7 +235,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -248,7 +248,7 @@ jobs: edge_arm_v7: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - tests name: Edge linux/arm/v7 runs-on: ubuntu-24.04-arm outputs: @@ -257,7 +257,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Build and push id: build_push uses: ./.github/actions/build-push @@ -278,7 +278,7 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - + - name: Merge uses: ./.github/actions/merge with: diff --git a/.gitignore b/.gitignore index 8c9b840c2..219dc66fd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .idea .vscode .DS_Store +/node_modules/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..e69de29bb diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..8046bd73d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "plugins": ["prettier-plugin-gherkin"] +} diff --git a/Makefile b/Makefile index 334d1ad0b..9906a5084 100644 --- a/Makefile +++ b/Makefile @@ -183,12 +183,22 @@ tests-once: ## Run the tests once (prefer the "tests" command while developing) $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION)-tests \ gotest +.PHONY: tests-integration +tests-integration: ## Run integration tests + go test -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration + +.PHONY: lint +lint: ## Lint the code. + #golangci-lint run + npx prettier --check . + # go install mvdan.cc/gofumpt@latest # go install github.com/daixiang0/gci@latest .PHONY: fmt fmt: ## Format the code and "optimize" the dependencies gofumpt -l -w . gci write -s standard -s default -s "prefix(github.com/gotenberg/gotenberg/v8)" --skip-generated --skip-vendor --custom-order . + npx prettier --write . go mod tidy # go install golang.org/x/tools/cmd/godoc@latest diff --git a/README.md b/README.md index 7b29f5355..2ae1eb06e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ --- -**Gotenberg** provides a developer-friendly API to interact with powerful tools like Chromium and LibreOffice for converting +**Gotenberg** provides a developer-friendly API to interact with powerful tools like Chromium and LibreOffice for converting numerous document formats (HTML, Markdown, Word, Excel, etc.) into PDF files, and more! ## Quick Start diff --git a/SECURITY.md b/SECURITY.md index 43b1ab4f2..d624b8491 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,25 +2,25 @@ ## Supported Versions -Please ensure to keep your environment up-to-date and use only the latest version of Gotenberg. +Please ensure to keep your environment up-to-date and use only the latest version of Gotenberg. Security updates and patches will be applied only to the most recent version. ## Reporting a Vulnerability -Your help in identifying vulnerabilities in our project is much appreciated. +Your help in identifying vulnerabilities in our project is much appreciated. We take all reports regarding security seriously. -If you discover a security vulnerability, please refrain from publishing it publicly. -Instead, kindly send us the details via email to *neuhart [dot] julien [at] gmail [dot] com*. +If you discover a security vulnerability, please refrain from publishing it publicly. +Instead, kindly send us the details via email to _neuhart [dot] julien [at] gmail [dot] com_. -In the subject of your email, please indicate that it's a security vulnerability report for Gotenberg. +In the subject of your email, please indicate that it's a security vulnerability report for Gotenberg. In your message, please include: -* A detailed description of the vulnerability. -* The steps to reproduce the issue. -* Any potential impact of the vulnerability on the users or system. +- A detailed description of the vulnerability. +- The steps to reproduce the issue. +- Any potential impact of the vulnerability on the users or system. -Please remember that this process is done in a *'best-effort'* manner. +Please remember that this process is done in a _'best-effort'_ manner. This means we strive to respond and act as quickly as possible, but the speed may vary depending on the severity of the issue and our resources. @@ -28,14 +28,14 @@ Thank you in advance for helping to keep our project safe! ## Disclosure Policy -Once we have received your vulnerability report, we will work to validate and reproduce the issue. +Once we have received your vulnerability report, we will work to validate and reproduce the issue. If we can confirm the vulnerability, we will proceed to: -* Work on a fix and a release timeline. -* Notify you when the fix has been implemented and released. -* Credit you for discovering the vulnerability (unless you request anonymity). -* Please note that we will do our best to keep you informed about the progress towards resolving the issue. +- Work on a fix and a release timeline. +- Notify you when the fix has been implemented and released. +- Credit you for discovering the vulnerability (unless you request anonymity). +- Please note that we will do our best to keep you informed about the progress towards resolving the issue. ## Comments on this Policy -If you have suggestions on how this process could be improved, please submit a pull request. \ No newline at end of file +If you have suggestions on how this process could be improved, please submit a pull request. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..52398fe43 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,129 @@ +{ + "name": "gotenberg", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "3.5.2", + "prettier-plugin-gherkin": "^3.1.1" + } + }, + "node_modules/@cucumber/gherkin": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-27.0.0.tgz", + "integrity": "sha512-j5rCsjqzRiC3iVTier3sa0kzyNbkcAmF7xr7jKnyO7qDeK3Z8Ye1P3KSVpeQRMY+KCDJ3WbTDdyxH0FwfA/fIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cucumber/messages": ">=19.1.4 <=22" + } + }, + "node_modules/@cucumber/gherkin/node_modules/@cucumber/messages": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-22.0.0.tgz", + "integrity": "sha512-EuaUtYte9ilkxcKmfqGF9pJsHRUU0jwie5ukuZ/1NPTuHS1LxHPsGEODK17RPRbZHOFhqybNzG2rHAwThxEymg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/uuid": "9.0.1", + "class-transformer": "0.5.1", + "reflect-metadata": "0.1.13", + "uuid": "9.0.0" + } + }, + "node_modules/@cucumber/gherkin/node_modules/@types/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cucumber/gherkin/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@cucumber/messages": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-23.0.0.tgz", + "integrity": "sha512-2ZWKNnikNMBnle3P3cgy+fgzhABrY4oBbiWp9wcI8ANdT4LlYRN6jQ6j/9CQGTDGRfkyRtr/5VkYq7q24XCsuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/uuid": "9.0.6", + "class-transformer": "0.5.1", + "reflect-metadata": "0.1.13", + "uuid": "9.0.1" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", + "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", + "dev": true, + "license": "MIT" + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "dev": true, + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-gherkin": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-gherkin/-/prettier-plugin-gherkin-3.1.1.tgz", + "integrity": "sha512-cCfjqKMdR2a8jOf8yKg32iUfRq0Dfmx+K2qTMOD/ixJJq0Rp7QS8BaoABWC7CDGXbvOkVeWOvzhxWvYcEbjglw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cucumber/gherkin": "^27.0.0", + "@cucumber/messages": "^23.0.0", + "prettier": "^3.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..69805b724 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "prettier": "3.5.2", + "prettier-plugin-gherkin": "^3.1.1" + } +} From 820b914291464af6a630a1b48460d03eba43f3a6 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sun, 2 Mar 2025 18:04:21 +0100 Subject: [PATCH 072/254] feat(formatting): add prettier-plugin-sh --- .dockerignore | 2 +- .env | 6 +- .github/actions/build-push/build-push.sh | 26 +++---- .github/actions/clean/clean.sh | 90 ++++++++++++------------ .github/actions/merge/merge.sh | 55 +++++++-------- .prettierignore | 1 + .prettierrc | 2 +- Makefile | 8 ++- package-lock.json | 53 +++++++++++++- package.json | 3 +- 10 files changed, 151 insertions(+), 95 deletions(-) diff --git a/.dockerignore b/.dockerignore index 191381ee7..6b8710a71 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -.git \ No newline at end of file +.git diff --git a/.env b/.env index 1a590e556..6c1c2a5a6 100644 --- a/.env +++ b/.env @@ -5,9 +5,9 @@ GOTENBERG_VERSION=snapshot GOTENBERG_USER_GID=1001 GOTENBERG_USER_UID=1001 NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. -PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. -PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. -GOLANGCI_LINT_VERSION=v1.64.2 # See https://github.com/golangci/golangci-lint/releases. +PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. +PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. +GOLANGCI_LINT_VERSION=v1.64.2 # See https://github.com/golangci/golangci-lint/releases. GOTENBERG_VERSION=snapshot DOCKERFILE=build/Dockerfile DOCKERFILE_CLOUDRUN=build/Dockerfile.cloudrun diff --git a/.github/actions/build-push/build-push.sh b/.github/actions/build-push/build-push.sh index 2aba17564..c83bb0289 100755 --- a/.github/actions/build-push/build-push.sh +++ b/.github/actions/build-push/build-push.sh @@ -16,7 +16,7 @@ dry_run="" while [[ $# -gt 0 ]]; do case $1 in --version) - version="${2//v}" + version="${2//v/}" shift 2 ;; --platform) @@ -47,11 +47,11 @@ echo "Target platform: $platform" if [ -n "$alternate_repository" ]; then DOCKER_REPOSITORY=$alternate_repository echo "⚠️ Using $alternate_repository for DOCKER_REPOSITORY" - fi +fi - if [ "$dry_run" = "true" ]; then - echo "🚧 Dry run" - fi +if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" +fi # Build tags arrays. tags=() @@ -142,20 +142,20 @@ cmd="docker buildx build \ run_cmd "$cmd" if [ "$platform" != "linux/amd64" ]; then - echo "⚠️ Skip Cloud Run variant(s)" - echo "✅ Done!" - echo "tags=$(join "," "${tags[@]}")" >> "$GITHUB_OUTPUT" - echo "tags_cloud_run=$(join "," "${tags_cloud_run[@]}")" >> "$GITHUB_OUTPUT" - exit 0 + echo "⚠️ Skip Cloud Run variant(s)" + echo "✅ Done!" + echo "tags=$(join "," "${tags[@]}")" >> "$GITHUB_OUTPUT" + echo "tags_cloud_run=$(join "," "${tags_cloud_run[@]}")" >> "$GITHUB_OUTPUT" + exit 0 fi source_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-${arch[1]}" cmd="docker pull $source_tag_cloud_run" run_cmd "$cmd" - target_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version" - cmd="docker image tag $source_tag_cloud_run $target_tag_cloud_run" - run_cmd "$cmd" +target_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version" +cmd="docker image tag $source_tag_cloud_run $target_tag_cloud_run" +run_cmd "$cmd" cmd="docker build \ --build-arg DOCKER_REGISTRY=$DOCKER_REGISTRY \ diff --git a/.github/actions/clean/clean.sh b/.github/actions/clean/clean.sh index 4cfbc1dcd..b89a95363 100755 --- a/.github/actions/clean/clean.sh +++ b/.github/actions/clean/clean.sh @@ -19,7 +19,7 @@ while [[ $# -gt 0 ]]; do shift 2 ;; --snapshot-version) - snapshot_version="${2//v}" + snapshot_version="${2//v/}" shift 2 ;; --dry-run) @@ -47,9 +47,9 @@ for tag in "${tags_to_delete[@]}"; do echo "- $tag" done - if [ "$dry_run" = "true" ]; then - echo "🚧 Dry run" - fi +if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" +fi echo # Delete tags. @@ -61,28 +61,28 @@ if [ "$dry_run" = "true" ]; then echo "🚧 Dry run - would call $base_url to get a token" echo else - echo "🌐 Get token from $base_url" - - readarray -t lines < <( - curl -s -X POST \ - -H "Content-Type: application/json" \ - -d "{\"username\":\"$DOCKERHUB_USERNAME\", \"password\":\"$DOCKERHUB_TOKEN\"}" \ - -w "\n%{http_code}" \ - "$base_url/users/login" - ) - - http_code="${lines[-1]}" - unset 'lines[-1]' - json_body=$(printf "%s\n" "${lines[@]}") - - if [ "$http_code" -ne "200" ]; then - echo "❌ Wrong HTTP status - $http_code" - echo "$json_body" - exit 1 - fi + echo "🌐 Get token from $base_url" + + readarray -t lines < <( + curl -s -X POST \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$DOCKERHUB_USERNAME\", \"password\":\"$DOCKERHUB_TOKEN\"}" \ + -w "\n%{http_code}" \ + "$base_url/users/login" + ) + + http_code="${lines[-1]}" + unset 'lines[-1]' + json_body=$(printf "%s\n" "${lines[@]}") + + if [ "$http_code" -ne "200" ]; then + echo "❌ Wrong HTTP status - $http_code" + echo "$json_body" + exit 1 + fi - token=$(jq -r '.token' <<< "$json_body") - echo + token=$(jq -r '.token' <<< "$json_body") + echo fi if [ -z "$token" ]; then @@ -95,26 +95,26 @@ for tag in "${tags_to_delete[@]}"; do echo "🚧 Dry run - would call $base_url to delete tag $tag" echo else - echo "🌐 Delete tag $tag" - IFS=':' read -ra tag_parts <<< "$tag" - - readarray -t lines < <( - curl -s -X DELETE \ - -H "Authorization: Bearer $token" \ - -w "\n%{http_code}" \ - "$base_url/repositories/${tag_parts[0]}/tags/${tag_parts[1]}/" - ) - - http_code="${lines[-1]}" - unset 'lines[-1]' - - if [ "$http_code" -ne "200" ] && [ "$http_code" -ne "204" ]; then - echo "❌ Wrong HTTP status - $http_code" - printf '%s\n' "${lines[@]}" - exit 1 - fi - - echo + echo "🌐 Delete tag $tag" + IFS=':' read -ra tag_parts <<< "$tag" + + readarray -t lines < <( + curl -s -X DELETE \ + -H "Authorization: Bearer $token" \ + -w "\n%{http_code}" \ + "$base_url/repositories/${tag_parts[0]}/tags/${tag_parts[1]}/" + ) + + http_code="${lines[-1]}" + unset 'lines[-1]' + + if [ "$http_code" -ne "200" ] && [ "$http_code" -ne "204" ]; then + echo "❌ Wrong HTTP status - $http_code" + printf '%s\n' "${lines[@]}" + exit 1 + fi + + echo fi done diff --git a/.github/actions/merge/merge.sh b/.github/actions/merge/merge.sh index 50ef2fa2d..951bba226 100755 --- a/.github/actions/merge/merge.sh +++ b/.github/actions/merge/merge.sh @@ -46,23 +46,23 @@ if [ -n "$alternate_registry" ]; then echo "⚠️ Will also push to $alternate_registry registry" fi - if [ "$dry_run" = "true" ]; then - echo "🚧 Dry run" - fi +if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" +fi - echo +echo - # Build merge map. - declare -A merge_map +# Build merge map. +declare -A merge_map - for tag in "${tags_to_merge[@]}"; do - target_tag="${tag//-amd64/}" - target_tag="${target_tag//-arm64/}" - target_tag="${target_tag//-arm/}" - target_tag="${target_tag//-386/}" +for tag in "${tags_to_merge[@]}"; do + target_tag="${tag//-amd64/}" + target_tag="${target_tag//-arm64/}" + target_tag="${target_tag//-arm/}" + target_tag="${target_tag//-386/}" - merge_map["$target_tag"]+="$tag " - done + merge_map["$target_tag"]+="$tag " +done # Merge tags. run_cmd() { @@ -80,30 +80,29 @@ run_cmd() { fi } - for target in "${!merge_map[@]}"; do - IFS=' ' read -ra source_tags <<< "${merge_map[$target]}" +for target in "${!merge_map[@]}"; do + IFS=' ' read -ra source_tags <<< "${merge_map[$target]}" - cmd="docker buildx imagetools create \ + cmd="docker buildx imagetools create \ -t $target \ ${source_tags[*]} " - run_cmd "$cmd" + run_cmd "$cmd" - echo "➡️ $target pushed" - echo - if [ -n "$alternate_registry" ]; then - alternate_target="${target/$DOCKER_REGISTRY/$alternate_registry}" - cmd="docker buildx imagetools create \ + echo "➡️ $target pushed" + echo + if [ -n "$alternate_registry" ]; then + alternate_target="${target/$DOCKER_REGISTRY/$alternate_registry}" + cmd="docker buildx imagetools create \ -t $alternate_target \ $target " - run_cmd "$cmd" - - echo "➡️ $alternate_target pushed" - echo - fi - done + run_cmd "$cmd" + echo "➡️ $alternate_target pushed" + echo + fi +done echo "✅ Done!" exit 0 diff --git a/.prettierignore b/.prettierignore index e69de29bb..378eac25d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -0,0 +1 @@ +build diff --git a/.prettierrc b/.prettierrc index 8046bd73d..6d1814a3a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,3 @@ { - "plugins": ["prettier-plugin-gherkin"] + "plugins": ["prettier-plugin-gherkin", "prettier-plugin-sh"] } diff --git a/Makefile b/Makefile index 9906a5084..c3b3576f8 100644 --- a/Makefile +++ b/Makefile @@ -188,19 +188,23 @@ tests-integration: ## Run integration tests go test -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration .PHONY: lint -lint: ## Lint the code. +lint: ## Lint codebase. #golangci-lint run npx prettier --check . # go install mvdan.cc/gofumpt@latest # go install github.com/daixiang0/gci@latest .PHONY: fmt -fmt: ## Format the code and "optimize" the dependencies +fmt: ## Format codebase and "optimize" the dependencies gofumpt -l -w . gci write -s standard -s default -s "prefix(github.com/gotenberg/gotenberg/v8)" --skip-generated --skip-vendor --custom-order . npx prettier --write . go mod tidy +.PHONY: todo +todo: ## Find TODOs in codebase. + golangci-lint run --no-config --disable-all --enable godox + # go install golang.org/x/tools/cmd/godoc@latest .PHONY: godoc godoc: ## Run a webserver with Gotenberg godoc diff --git a/package-lock.json b/package-lock.json index 52398fe43..ce3522877 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "": { "devDependencies": { "prettier": "3.5.2", - "prettier-plugin-gherkin": "^3.1.1" + "prettier-plugin-gherkin": "^3.1.1", + "prettier-plugin-sh": "^0.15.0" } }, "node_modules/@cucumber/gherkin": { @@ -76,6 +77,13 @@ "dev": true, "license": "MIT" }, + "node_modules/mvdan-sh": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz", + "integrity": "sha512-kMbrH0EObaKmK3nVRKUIIya1dpASHIEusM13S4V1ViHFuxuNxCo+arxoa6j/dbV22YBGjl7UKJm9QQKJ2Crzhg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/prettier": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", @@ -104,6 +112,26 @@ "prettier": "^3.0.0" } }, + "node_modules/prettier-plugin-sh": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.15.0.tgz", + "integrity": "sha512-U0PikJr/yr2bzzARl43qI0mApBj0C1xdAfA04AZa6LnvIKawXHhuy2fFo6LNA7weRzGlAiNbaEFfKMFo0nZr/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mvdan-sh": "^0.10.1", + "sh-syntax": "^0.4.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -111,6 +139,29 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/sh-syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.4.2.tgz", + "integrity": "sha512-/l2UZ5fhGZLVZa16XQM9/Vq/hezGGbdHeVEA01uWjOL1+7Ek/gt6FquW0iKKws4a9AYPYvlz6RyVvjh3JxOteg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", diff --git a/package.json b/package.json index 69805b724..9d4beff8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "devDependencies": { "prettier": "3.5.2", - "prettier-plugin-gherkin": "^3.1.1" + "prettier-plugin-gherkin": "^3.1.1", + "prettier-plugin-sh": "^0.15.0" } } From 0febb60a3f1f718a96c58f3ff1455e2731aaadd3 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Mar 2025 11:18:20 +0100 Subject: [PATCH 073/254] test: add integration tests --- .../action.yml | 38 +- .../build.sh} | 18 +- .github/actions/build-test-push/push.sh | 70 + .github/actions/build-test-push/test.sh | 79 + .github/actions/merge/merge.sh | 1 - .github/workflows/continuous-delivery.yml | 20 +- .github/workflows/continuous-integration.yml | 162 +- .node-version | 1 + .prettierrc | 3 +- Makefile | 61 +- README.md | 2 - go.mod | 58 +- go.sum | 168 +- pkg/gotenberg/cmd_test.go | 306 -- pkg/gotenberg/context_test.go | 17 - pkg/gotenberg/fs_test.go | 146 - pkg/gotenberg/gc_test.go | 3 +- pkg/gotenberg/logging_test.go | 23 - pkg/gotenberg/mocks_test.go | 264 -- pkg/modules/api/api_test.go | 1033 ------- pkg/modules/api/context_test.go | 837 ------ pkg/modules/api/formdata_test.go | 24 +- pkg/modules/api/middlewares_test.go | 584 ---- pkg/modules/api/mocks_test.go | 193 -- .../modules/api/testdata/sample.txt | 0 pkg/modules/chromium/browser_test.go | 2484 ----------------- pkg/modules/chromium/chromium_test.go | 637 ----- pkg/modules/chromium/debug_test.go | 24 - pkg/modules/chromium/mocks_test.go | 50 - pkg/modules/chromium/routes_test.go | 1927 ------------- pkg/modules/exiftool/exiftool_test.go | 415 --- pkg/modules/libreoffice/api/api_test.go | 522 ---- .../libreoffice/api/libreoffice_test.go | 699 ----- pkg/modules/libreoffice/api/mocks_test.go | 94 - pkg/modules/libreoffice/libreoffice_test.go | 196 -- .../libreoffice/pdfengine/pdfengine_test.go | 210 -- pkg/modules/libreoffice/routes.go | 10 - pkg/modules/libreoffice/routes_test.go | 817 ------ pkg/modules/pdfcpu/pdfcpu_test.go | 329 --- pkg/modules/pdfengines/pdfengines_test.go | 417 --- pkg/modules/pdfengines/routes_test.go | 1813 ------------ pkg/modules/pdftk/pdftk_test.go | 313 --- pkg/modules/prometheus/prometheus_test.go | 360 --- pkg/modules/qpdf/qpdf_test.go | 425 --- pkg/modules/webhook/middleware_test.go | 582 ---- pkg/modules/webhook/webhook_test.go | 65 - test/Dockerfile | 50 - test/docker-entrypoint.sh | 59 - test/golint.sh | 5 - test/gotest.sh | 10 - test/gotodos.sh | 8 - test/integration/doc.go | 2 + .../features/chromium_convert_html.feature | 852 ++++++ .../chromium_convert_markdown.feature | 977 +++++++ .../features/chromium_convert_url.feature | 929 ++++++ test/integration/features/debug.feature | 147 + test/integration/features/health.feature | 107 + .../features/libreoffice_convert.feature | 614 ++++ .../features/pdfengines_convert.feature | 185 ++ .../features/pdfengines_flatten.feature | 112 + .../features/pdfengines_merge.feature | 324 +++ .../features/pdfengines_metadata.feature | 268 ++ .../features/pdfengines_split.feature | 551 ++++ .../features/prometheus_metrics.feature | 90 + test/integration/features/root.feature | 62 + test/integration/features/version.feature | 35 + test/integration/main_test.go | 50 + test/integration/scenario/compare.go | 57 + test/integration/scenario/containers.go | 136 + test/integration/scenario/doc.go | 2 + test/integration/scenario/http.go | 83 + test/integration/scenario/scenario.go | 911 ++++++ test/integration/scenario/server.go | 176 ++ .../feature-rich-html-remote/index.html | 64 + .../testdata/feature-rich-html/index.html | 69 + .../testdata/feature-rich-markdown/index.html | 70 + .../testdata/feature-rich-markdown/table.md | 7 + .../testdata/header-footer-html/footer.html | 13 + .../testdata/header-footer-html/header.html | 13 + .../testdata/page-1-html/index.html | 9 + .../testdata/page-1-markdown/index.html | 9 + .../testdata/page-1-markdown/page_1.md | 1 + test/integration/testdata/page_1.docx | Bin 0 -> 6408 bytes test/integration/testdata/page_1.pdf | Bin 0 -> 4560 bytes test/integration/testdata/page_2.docx | Bin 0 -> 6408 bytes test/integration/testdata/page_2.pdf | Bin 0 -> 4900 bytes .../testdata/pages-12-html/index.html | 25 + .../testdata/pages-12-markdown/index.html | 25 + .../testdata/pages-12-markdown/page_1.md | 1 + .../testdata/pages-12-markdown/page_10.md | 1 + .../testdata/pages-12-markdown/page_11.md | 1 + .../testdata/pages-12-markdown/page_12.md | 1 + .../testdata/pages-12-markdown/page_2.md | 1 + .../testdata/pages-12-markdown/page_3.md | 1 + .../testdata/pages-12-markdown/page_4.md | 1 + .../testdata/pages-12-markdown/page_5.md | 1 + .../testdata/pages-12-markdown/page_6.md | 1 + .../testdata/pages-12-markdown/page_7.md | 1 + .../testdata/pages-12-markdown/page_8.md | 1 + .../testdata/pages-12-markdown/page_9.md | 1 + .../testdata/pages-3-html/index.html | 16 + .../testdata/pages-3-markdown/index.html | 16 + .../testdata/pages-3-markdown/page_1.md | 1 + .../testdata/pages-3-markdown/page_2.md | 1 + .../testdata/pages-3-markdown/page_3.md | 1 + test/integration/testdata/pages_12.docx | Bin 0 -> 6539 bytes test/integration/testdata/pages_12.pdf | Bin 0 -> 89683 bytes test/integration/testdata/pages_3.docx | Bin 0 -> 6454 bytes test/integration/testdata/pages_3.pdf | Bin 0 -> 22548 bytes .../testdata/pem}/README.md | 0 .../api => integration/testdata/pem}/cert.pem | 0 .../api => integration/testdata/pem}/key.pem | 0 .../testdata/protected_page_1.docx | Bin 0 -> 12288 bytes test/integration/teststore/.gitkeep | 0 test/testdata/api/sample1.txt | 1 - test/testdata/api/sample2.pdf | Bin 208299 -> 0 bytes test/testdata/chromium/html/font.woff | Bin 12644 -> 0 bytes test/testdata/chromium/html/footer.html | 15 - test/testdata/chromium/html/header.html | 13 - test/testdata/chromium/html/img.gif | Bin 29685 -> 0 bytes test/testdata/chromium/html/index.html | 105 - test/testdata/chromium/html/style.css | 29 - .../testdata/chromium/markdown/defaultfont.md | 3 - test/testdata/chromium/markdown/font.woff | Bin 12644 -> 0 bytes test/testdata/chromium/markdown/footer.html | 15 - test/testdata/chromium/markdown/googlefont.md | 3 - test/testdata/chromium/markdown/header.html | 13 - test/testdata/chromium/markdown/img.gif | Bin 29685 -> 0 bytes test/testdata/chromium/markdown/index.html | 49 - test/testdata/chromium/markdown/localfont.md | 3 - test/testdata/chromium/markdown/style.css | 28 - test/testdata/chromium/markdown/table.md | 7 - test/testdata/libreoffice/document.docx | Bin 91571 -> 0 bytes test/testdata/libreoffice/protected.docx | Bin 12288 -> 0 bytes test/testdata/pdfengines/sample1.pdf | Bin 208299 -> 0 bytes test/testdata/pdfengines/sample2.pdf | Bin 208299 -> 0 bytes test/testdata/pdfengines/sample3.pdf | Bin 224175 -> 0 bytes test/testdata/pdfengines/sample4.pdf | Bin 8025 -> 0 bytes test/testdata/pdfengines/sample5.pdf | Bin 224175 -> 0 bytes 139 files changed, 7559 insertions(+), 16379 deletions(-) rename .github/actions/{build-push => build-test-push}/action.yml (56%) rename .github/actions/{build-push/build-push.sh => build-test-push/build.sh} (91%) create mode 100755 .github/actions/build-test-push/push.sh create mode 100755 .github/actions/build-test-push/test.sh create mode 100644 .node-version delete mode 100644 pkg/gotenberg/cmd_test.go delete mode 100644 pkg/gotenberg/fs_test.go delete mode 100644 pkg/gotenberg/logging_test.go delete mode 100644 pkg/gotenberg/mocks_test.go delete mode 100644 pkg/modules/api/api_test.go delete mode 100644 pkg/modules/api/context_test.go delete mode 100644 pkg/modules/api/middlewares_test.go delete mode 100644 pkg/modules/api/mocks_test.go rename test/testdata/libreoffice/document.txt => pkg/modules/api/testdata/sample.txt (100%) delete mode 100644 pkg/modules/chromium/browser_test.go delete mode 100644 pkg/modules/chromium/chromium_test.go delete mode 100644 pkg/modules/chromium/debug_test.go delete mode 100644 pkg/modules/chromium/mocks_test.go delete mode 100644 pkg/modules/chromium/routes_test.go delete mode 100644 pkg/modules/exiftool/exiftool_test.go delete mode 100644 pkg/modules/libreoffice/api/api_test.go delete mode 100644 pkg/modules/libreoffice/api/libreoffice_test.go delete mode 100644 pkg/modules/libreoffice/api/mocks_test.go delete mode 100644 pkg/modules/libreoffice/libreoffice_test.go delete mode 100644 pkg/modules/libreoffice/pdfengine/pdfengine_test.go delete mode 100644 pkg/modules/libreoffice/routes_test.go delete mode 100644 pkg/modules/pdfcpu/pdfcpu_test.go delete mode 100644 pkg/modules/pdfengines/pdfengines_test.go delete mode 100644 pkg/modules/pdfengines/routes_test.go delete mode 100644 pkg/modules/pdftk/pdftk_test.go delete mode 100644 pkg/modules/prometheus/prometheus_test.go delete mode 100644 pkg/modules/qpdf/qpdf_test.go delete mode 100644 pkg/modules/webhook/middleware_test.go delete mode 100644 pkg/modules/webhook/webhook_test.go delete mode 100644 test/Dockerfile delete mode 100755 test/docker-entrypoint.sh delete mode 100755 test/golint.sh delete mode 100755 test/gotest.sh delete mode 100755 test/gotodos.sh create mode 100644 test/integration/doc.go create mode 100644 test/integration/features/chromium_convert_html.feature create mode 100644 test/integration/features/chromium_convert_markdown.feature create mode 100644 test/integration/features/chromium_convert_url.feature create mode 100644 test/integration/features/debug.feature create mode 100644 test/integration/features/health.feature create mode 100644 test/integration/features/libreoffice_convert.feature create mode 100644 test/integration/features/pdfengines_convert.feature create mode 100644 test/integration/features/pdfengines_flatten.feature create mode 100644 test/integration/features/pdfengines_merge.feature create mode 100644 test/integration/features/pdfengines_metadata.feature create mode 100644 test/integration/features/pdfengines_split.feature create mode 100644 test/integration/features/prometheus_metrics.feature create mode 100644 test/integration/features/root.feature create mode 100644 test/integration/features/version.feature create mode 100644 test/integration/main_test.go create mode 100644 test/integration/scenario/compare.go create mode 100644 test/integration/scenario/containers.go create mode 100644 test/integration/scenario/doc.go create mode 100644 test/integration/scenario/http.go create mode 100644 test/integration/scenario/scenario.go create mode 100644 test/integration/scenario/server.go create mode 100644 test/integration/testdata/feature-rich-html-remote/index.html create mode 100644 test/integration/testdata/feature-rich-html/index.html create mode 100644 test/integration/testdata/feature-rich-markdown/index.html create mode 100644 test/integration/testdata/feature-rich-markdown/table.md create mode 100644 test/integration/testdata/header-footer-html/footer.html create mode 100644 test/integration/testdata/header-footer-html/header.html create mode 100644 test/integration/testdata/page-1-html/index.html create mode 100644 test/integration/testdata/page-1-markdown/index.html create mode 100644 test/integration/testdata/page-1-markdown/page_1.md create mode 100644 test/integration/testdata/page_1.docx create mode 100644 test/integration/testdata/page_1.pdf create mode 100644 test/integration/testdata/page_2.docx create mode 100644 test/integration/testdata/page_2.pdf create mode 100644 test/integration/testdata/pages-12-html/index.html create mode 100644 test/integration/testdata/pages-12-markdown/index.html create mode 100644 test/integration/testdata/pages-12-markdown/page_1.md create mode 100644 test/integration/testdata/pages-12-markdown/page_10.md create mode 100644 test/integration/testdata/pages-12-markdown/page_11.md create mode 100644 test/integration/testdata/pages-12-markdown/page_12.md create mode 100644 test/integration/testdata/pages-12-markdown/page_2.md create mode 100644 test/integration/testdata/pages-12-markdown/page_3.md create mode 100644 test/integration/testdata/pages-12-markdown/page_4.md create mode 100644 test/integration/testdata/pages-12-markdown/page_5.md create mode 100644 test/integration/testdata/pages-12-markdown/page_6.md create mode 100644 test/integration/testdata/pages-12-markdown/page_7.md create mode 100644 test/integration/testdata/pages-12-markdown/page_8.md create mode 100644 test/integration/testdata/pages-12-markdown/page_9.md create mode 100644 test/integration/testdata/pages-3-html/index.html create mode 100644 test/integration/testdata/pages-3-markdown/index.html create mode 100644 test/integration/testdata/pages-3-markdown/page_1.md create mode 100644 test/integration/testdata/pages-3-markdown/page_2.md create mode 100644 test/integration/testdata/pages-3-markdown/page_3.md create mode 100644 test/integration/testdata/pages_12.docx create mode 100644 test/integration/testdata/pages_12.pdf create mode 100644 test/integration/testdata/pages_3.docx create mode 100644 test/integration/testdata/pages_3.pdf rename test/{testdata/api => integration/testdata/pem}/README.md (100%) rename test/{testdata/api => integration/testdata/pem}/cert.pem (100%) rename test/{testdata/api => integration/testdata/pem}/key.pem (100%) create mode 100644 test/integration/testdata/protected_page_1.docx create mode 100644 test/integration/teststore/.gitkeep delete mode 100644 test/testdata/api/sample1.txt delete mode 100644 test/testdata/api/sample2.pdf delete mode 100644 test/testdata/chromium/html/font.woff delete mode 100644 test/testdata/chromium/html/footer.html delete mode 100644 test/testdata/chromium/html/header.html delete mode 100644 test/testdata/chromium/html/img.gif delete mode 100644 test/testdata/chromium/html/index.html delete mode 100644 test/testdata/chromium/html/style.css delete mode 100644 test/testdata/chromium/markdown/defaultfont.md delete mode 100644 test/testdata/chromium/markdown/font.woff delete mode 100644 test/testdata/chromium/markdown/footer.html delete mode 100644 test/testdata/chromium/markdown/googlefont.md delete mode 100644 test/testdata/chromium/markdown/header.html delete mode 100644 test/testdata/chromium/markdown/img.gif delete mode 100644 test/testdata/chromium/markdown/index.html delete mode 100644 test/testdata/chromium/markdown/localfont.md delete mode 100644 test/testdata/chromium/markdown/style.css delete mode 100644 test/testdata/chromium/markdown/table.md delete mode 100644 test/testdata/libreoffice/document.docx delete mode 100644 test/testdata/libreoffice/protected.docx delete mode 100644 test/testdata/pdfengines/sample1.pdf delete mode 100644 test/testdata/pdfengines/sample2.pdf delete mode 100644 test/testdata/pdfengines/sample3.pdf delete mode 100644 test/testdata/pdfengines/sample4.pdf delete mode 100644 test/testdata/pdfengines/sample5.pdf diff --git a/.github/actions/build-push/action.yml b/.github/actions/build-test-push/action.yml similarity index 56% rename from .github/actions/build-push/action.yml rename to .github/actions/build-test-push/action.yml index af94b0401..019dc911e 100644 --- a/.github/actions/build-push/action.yml +++ b/.github/actions/build-test-push/action.yml @@ -1,5 +1,5 @@ -name: Build and Push -description: Build and push Docker images +name: Build Test Push +description: Build, test and push Docker images for a given platform author: Julien Neuhart inputs: @@ -19,6 +19,9 @@ inputs: version: description: Gotenberg version required: true + skip_integrations_tests: + description: Define whether to skip integration testing + default: false alternate_repository: description: Alternate repository to push the tags to dry_run: @@ -27,10 +30,10 @@ inputs: outputs: tags: description: Comma separated list of tag - value: ${{ steps.build_push.outputs.tags }} + value: ${{ steps.build.outputs.tags }} tags_cloud_run: description: Comma separated list of Cloud Run tags (linux/amd64 only) - value: ${{ steps.build_push.outputs.tags_cloud_run }} + value: ${{ steps.build.outputs.tags_cloud_run }} runs: using: composite @@ -50,18 +53,35 @@ runs: username: ${{ inputs.docker_hub_username }} password: ${{ inputs.docker_hub_password }} - - name: Build and push ${{ inputs.platform }} - id: build_push + - name: Build ${{ inputs.platform }} + id: build shell: bash run: | - .github/actions/build-push/build-push.sh \ + .github/actions/build-test-push/build.sh \ --version "${{ inputs.version }}" \ --platform "${{ inputs.platform }}" \ --alternate-repository "${{ inputs.alternate_repository }}" \ --dry-run "${{ inputs.dry_run }}" + - name: Run integration tests + if: ${{ inputs.skip_integrations_tests != 'true' }} + shell: bash + run: | + .github/actions/build-test-push/test.sh \ + --version "${{ inputs.version }}" \ + --platform "${{ inputs.platform }}" \ + --alternate-repository "${{ inputs.alternate_repository }}" \ + --dry-run "${{ inputs.dry_run }}" + + - name: Push + shell: bash + run: | + .github/actions/build-test-push/push.sh \ + --tags "${{ steps.build.outputs.tags }}" \ + --dry-run "${{ inputs.dry_run }}" + - name: Outputs shell: bash run: | - echo "tags=${{ steps.build_push.outputs.tags }}" - echo "tags_cloud_run=${{ steps.build_push.outputs.tags_cloud_run }}" + echo "tags=${{ steps.build.outputs.tags }}" + echo "tags_cloud_run=${{ steps.build.outputs.tags_cloud_run }}" diff --git a/.github/actions/build-push/build-push.sh b/.github/actions/build-test-push/build.sh similarity index 91% rename from .github/actions/build-push/build-push.sh rename to .github/actions/build-test-push/build.sh index c83bb0289..410d3abde 100755 --- a/.github/actions/build-push/build-push.sh +++ b/.github/actions/build-test-push/build.sh @@ -92,7 +92,7 @@ fi tags_flags=() tags_cloud_run_flags=() -echo "Will push the following tags:" +echo "Will use the following tags:" for tag in "${tags[@]}"; do tags_flags+=("-t" "$tag") echo "- $tag" @@ -103,7 +103,7 @@ for tag in "${tags_cloud_run[@]}"; do done echo -# Build and push images. +# Build images. run_cmd() { local cmd="$1" @@ -126,6 +126,8 @@ join() { echo "$*" } +no_arch_tag="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version" + cmd="docker buildx build \ --build-arg GOLANG_VERSION=$GOLANG_VERSION \ --build-arg GOTENBERG_VERSION=$version \ @@ -134,9 +136,10 @@ cmd="docker buildx build \ --build-arg NOTO_COLOR_EMOJI_VERSION=$NOTO_COLOR_EMOJI_VERSION \ --build-arg PDFTK_VERSION=$PDFTK_VERSION \ --build-arg PDFCPU_VERSION=$PDFCPU_VERSION \ - --push \ --platform $platform \ + --load \ ${tags_flags[*]} \ + -t $no_arch_tag \ -f $DOCKERFILE $DOCKER_BUILD_CONTEXT " run_cmd "$cmd" @@ -149,19 +152,10 @@ if [ "$platform" != "linux/amd64" ]; then exit 0 fi -source_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-${arch[1]}" -cmd="docker pull $source_tag_cloud_run" -run_cmd "$cmd" - -target_tag_cloud_run="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version" -cmd="docker image tag $source_tag_cloud_run $target_tag_cloud_run" -run_cmd "$cmd" - cmd="docker build \ --build-arg DOCKER_REGISTRY=$DOCKER_REGISTRY \ --build-arg DOCKER_REPOSITORY=$DOCKER_REPOSITORY \ --build-arg GOTENBERG_VERSION=$version \ - --push \ ${tags_cloud_run_flags[*]} \ -f $DOCKERFILE_CLOUDRUN $DOCKER_BUILD_CONTEXT " diff --git a/.github/actions/build-test-push/push.sh b/.github/actions/build-test-push/push.sh new file mode 100755 index 000000000..599346f19 --- /dev/null +++ b/.github/actions/build-test-push/push.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Exit early. +# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin. +set -e + +# Source dot env file. +source .env + +# Arguments. +tags="" +dry_run="" + +while [[ $# -gt 0 ]]; do + case $1 in + --tags) + tags="$2" + shift 2 + ;; + --dry-run) + dry_run=$2 + shift 2 + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac +done + +echo "Push tag(s) 📦" +echo + +echo "Tag(s) to push:" +IFS=',' read -ra tags_to_push <<< "$tags" +for tag in "${tags_to_push[@]}"; do + echo "- $tag" +done + +if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" +fi +echo + +# Push tags. +run_cmd() { + local cmd="$1" + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run - would run the following command:" + echo "$cmd" + echo + else + echo "⚙️ Running command:" + echo "$cmd" + eval "$cmd" + echo + fi +} + +for tag in "${tags_to_push[@]}"; do + cmd="docker push $tag" + run_cmd "$cmd" + + echo "➡️ $tag pushed" + echo +done + +echo "✅ Done!" +exit 0 diff --git a/.github/actions/build-test-push/test.sh b/.github/actions/build-test-push/test.sh new file mode 100755 index 000000000..ee5eb1973 --- /dev/null +++ b/.github/actions/build-test-push/test.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# Exit early. +# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin. +set -e + +# Source dot env file. +source .env + +# Arguments. +version="" +platform="" +alternate_repository="" +dry_run="" + +while [[ $# -gt 0 ]]; do + case $1 in + --version) + version="${2//v/}" + shift 2 + ;; + --platform) + platform="$2" + shift 2 + ;; + --alternate-repository) + alternate_repository="$2" + shift 2 + ;; + --dry-run) + dry_run="$2" + shift 2 + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac +done + +echo "Integration testing 🧪" +echo + +echo "Gotenberg version: $version" +echo "Target platform: $platform" + +repository=$DOCKER_REPOSITORY + +if [ -n "$alternate_repository" ]; then + echo "⚠️ Using $alternate_repository for DOCKER_REPOSITORY" + repository=$alternate_repository +fi + +if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run" +fi +echo + +# Test image. +run_cmd() { + local cmd="$1" + + if [ "$dry_run" = "true" ]; then + echo "🚧 Dry run - would run the following command:" + echo "$cmd" + echo + else + echo "⚙️ Running command:" + echo "$cmd" + eval "$cmd" + echo + fi +} + +cmd="make test-integration DOCKER_REPOSITORY=$repository GOTENBERG_VERSION=$version PLATFORM=$platform" +run_cmd "$cmd" + +echo "✅ Done!" +exit 0 diff --git a/.github/actions/merge/merge.sh b/.github/actions/merge/merge.sh index 951bba226..ef4c0afed 100755 --- a/.github/actions/merge/merge.sh +++ b/.github/actions/merge/merge.sh @@ -49,7 +49,6 @@ fi if [ "$dry_run" = "true" ]; then echo "🚧 Dry run" fi - echo # Build merge map. diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index ab40aa6ea..1ada08b38 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -20,12 +20,13 @@ jobs: - name: Build and push id: build_push - uses: ./.github/actions/build-push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: ${{ github.event.release.tag_name }} + version: pr-${{ github.event.pull_request.number }} platform: linux/amd64 + skip_integrations_tests: true release_386: name: Release linux/386 @@ -39,12 +40,13 @@ jobs: - name: Build and push id: build_push - uses: ./.github/actions/build-push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: ${{ github.event.release.tag_name }} + version: pr-${{ github.event.pull_request.number }} platform: linux/386 + skip_integrations_tests: true release_arm64: name: Release linux/arm64 @@ -58,12 +60,13 @@ jobs: - name: Build and push id: build_push - uses: ./.github/actions/build-push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: ${{ github.event.release.tag_name }} + version: pr-${{ github.event.pull_request.number }} platform: linux/arm64 + skip_integrations_tests: true release_arm_v7: name: Release linux/arm/v7 @@ -77,12 +80,13 @@ jobs: - name: Build and push id: build_push - uses: ./.github/actions/build-push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: ${{ github.event.release.tag_name }} + version: pr-${{ github.event.pull_request.number }} platform: linux/arm/v7 + skip_integrations_tests: true merge_clean_release_tags: needs: diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 75980b6b6..ab8dc1c59 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,66 +17,74 @@ permissions: jobs: lint: - name: Lint + name: Lint Golang codebase runs-on: ubuntu-latest steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Setup Go uses: actions/setup-go@v5 with: - go-version: "1.23" - cache: false - - - name: Checkout source code - uses: actions/checkout@v4 + go-version-file: go.mod - name: Run linters uses: golangci/golangci-lint-action@v6 with: version: v1.64.2 - tests: - needs: - - lint - name: Tests + lint-prettier: + name: Lint non-Golang codebase runs-on: ubuntu-latest steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .node-version - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Install Dependencies + run: npm i + - name: Run linters + run: make lint-prettier + + test-unit: + needs: + - lint + - lint-prettier + name: Run unit tests + runs-on: ubuntu-latest + steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build testing environment - run: make build build-tests + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod - name: Run tests - run: make tests-once - - - name: Upload to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - verbose: true + run: make test-unit snapshot_amd64: if: github.event_name == 'pull_request' needs: - - tests + - test-unit name: Snapshot linux/amd64 runs-on: ubuntu-latest outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -87,19 +95,19 @@ jobs: snapshot_386: if: github.event_name == 'pull_request' needs: - - tests + - test-unit name: Snapshot linux/386 runs-on: ubuntu-latest outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -110,19 +118,19 @@ jobs: snapshot_arm64: if: github.event_name == 'pull_request' needs: - - tests + - test-unit name: Snapshot linux/arm64 runs-on: ubuntu-24.04-arm outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -133,19 +141,19 @@ jobs: snapshot_arm_v7: if: github.event_name == 'pull_request' needs: - - tests + - test-unit name: Snapshot linux/arm/v7 runs-on: ubuntu-24.04-arm outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -182,90 +190,94 @@ jobs: edge_amd64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - test-unit name: Edge linux/amd64 runs-on: ubuntu-latest outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: edge + version: pr-${{ github.event.pull_request.number }} platform: linux/amd64 + alternate_repository: snapshot edge_386: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - test-unit name: Edge linux/386 runs-on: ubuntu-latest outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: edge + version: pr-${{ github.event.pull_request.number }} platform: linux/386 + alternate_repository: snapshot edge_arm64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - test-unit name: Edge linux/arm64 runs-on: ubuntu-24.04-arm outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: edge + version: pr-${{ github.event.pull_request.number }} platform: linux/arm64 + alternate_repository: snapshot edge_arm_v7: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - tests + - test-unit name: Edge linux/arm/v7 runs-on: ubuntu-24.04-arm outputs: - tags: ${{ steps.build_push.outputs.tags }} - tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }} + tags: ${{ steps.build_test_push.outputs.tags }} + tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }} steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Build and push - id: build_push - uses: ./.github/actions/build-push + - name: Build, test and push + id: build_test_push + uses: ./.github/actions/build-test-push with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: edge + version: pr-${{ github.event.pull_request.number }} platform: linux/arm/v7 + alternate_repository: snapshot merge_clean_edge_tags: needs: diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..ca8f932a5 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +23.9.0 diff --git a/.prettierrc b/.prettierrc index 6d1814a3a..18106badc 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "plugins": ["prettier-plugin-gherkin", "prettier-plugin-sh"] + "plugins": ["prettier-plugin-gherkin", "prettier-plugin-sh"], + "escapeBackslashes": true } diff --git a/Makefile b/Makefile index c3b3576f8..e997b419f 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,7 @@ include .env .PHONY: help help: ## Show the help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.PHONY: it -it: build build-tests ## Initialize the development environment + @grep -hE '^[A-Za-z0-9_ \-]*?:.*##.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: build build: ## Build the Gotenberg's Docker image @@ -158,52 +155,42 @@ run: ## Start a Gotenberg container --webhook-client-timeout=$(WEBHOOK_CLIENT_TIMEOUT) \ --webhook-disable=$(WEBHOOK_DISABLE) -.PHONY: build-tests -build-tests: ## Build the tests' Docker image - docker build \ - --build-arg GOLANG_VERSION=$(GOLANG_VERSION) \ - --build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) \ - --build-arg DOCKER_REPOSITORY=$(DOCKER_REPOSITORY) \ - --build-arg GOTENBERG_VERSION=$(GOTENBERG_VERSION) \ - --build-arg GOLANGCI_LINT_VERSION=$(GOLANGCI_LINT_VERSION) \ - -t $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION)-tests \ - -f test/Dockerfile . - -.PHONY: tests -tests: ## Start the testing environment - docker run --rm -it \ - -v $(PWD):/tests \ - $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION)-tests \ - bash +.PHONY: test-unit +test-unit: ## Run unit tests + go test -race ./... -.PHONY: tests-once -tests-once: ## Run the tests once (prefer the "tests" command while developing) - docker run --rm \ - -v $(PWD):/tests \ - $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION)-tests \ - gotest +PLATFORM= -.PHONY: tests-integration -tests-integration: ## Run integration tests - go test -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration +.PHONY: test-integration +test-integration: ## Run integration tests + go test -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration -args \ + --gotenberg-docker-repository=$(DOCKER_REPOSITORY) \ + --gotenberg-version=$(GOTENBERG_VERSION) \ + --gotenberg-container-platform=$(PLATFORM) .PHONY: lint -lint: ## Lint codebase. - #golangci-lint run +lint: ## Lint Golang codebase + golangci-lint run + +.PHONY: lint-prettier +lint-prettier: ## Lint non-Golang codebase npx prettier --check . +.PHONY: lint-todo +lint-todo: ## Find TODOs in Golang codebase + golangci-lint run --no-config --disable-all --enable godox + # go install mvdan.cc/gofumpt@latest # go install github.com/daixiang0/gci@latest .PHONY: fmt -fmt: ## Format codebase and "optimize" the dependencies +fmt: ## Format Golang codebase and "optimize" the dependencies gofumpt -l -w . gci write -s standard -s default -s "prefix(github.com/gotenberg/gotenberg/v8)" --skip-generated --skip-vendor --custom-order . - npx prettier --write . go mod tidy -.PHONY: todo -todo: ## Find TODOs in codebase. - golangci-lint run --no-config --disable-all --enable godox +.PHONY: prettify +prettify: ## Format non-Golang codebase + npx prettier --write . # go install golang.org/x/tools/cmd/godoc@latest .PHONY: godoc diff --git a/README.md b/README.md index 2ae1eb06e..8c70fda2b 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,8 @@

Total downloads (gotenberg/gotenberg) Total downloads (thecodingmachine/gotenberg) -
Continuous Integration Go Reference - Code coverage

gotenberg%2Fgotenberg | Trendshift diff --git a/go.mod b/go.mod index 687ccd1e2..e9157c5e9 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250311215558-29dfcc2791de - github.com/chromedp/chromedp v0.13.1 + github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 + github.com/chromedp/chromedp v0.13.3 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 @@ -33,47 +33,97 @@ require ( ) require ( + github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 + github.com/docker/docker v28.0.2+incompatible + github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.0 github.com/shirou/gopsutil/v4 v4.25.2 + github.com/testcontainers/testcontainers-go v0.35.0 ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/STARRY-S/zip v0.2.2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.0 // indirect github.com/bodgit/windows v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect + github.com/cucumber/messages/go/v21 v21.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/ebitengine/purego v0.8.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-memdb v1.3.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nwaples/rardecode/v2 v2.1.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.63.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/procfs v0.16.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/time v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 237ed873c..3b96234c0 100644 --- a/go.sum +++ b/go.sum @@ -14,9 +14,17 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/STARRY-S/zip v0.2.2 h1:8QeCbIi1Z9U5MgoDARJR1ClbBo9RD46SmVy+dl0woCk= github.com/STARRY-S/zip v0.2.2/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/alexliesenfeld/health v0.8.0 h1:lCV0i+ZJPTbqP7LfKG7p3qZBl5VhelwUFCIVWl77fgk= @@ -35,24 +43,50 @@ github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250311215558-29dfcc2791de h1:tOKSCbB420VENW1Wz10EnSXr4jLnWoq25vnBW4ScmF0= -github.com/chromedp/cdproto v0.0.0-20250311215558-29dfcc2791de/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= -github.com/chromedp/chromedp v0.13.1 h1:FDh9CfaAt0w70gl69Hb69M/xgZrWuppH9AW22aGa+iU= -github.com/chromedp/chromedp v0.13.1/go.mod h1:O3nO4Lno7iLoVX+7GdqQkehhKG7DtLf/zFRyJo0AhXY= +github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 h1:AqW2bDQf67Zbq6Tpop/+yJSIknxhiQecO2B8jNYTAPs= +github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/chromedp v0.13.3 h1:c6nTn97XQBykzcXiGYL5LLebw3h3CEyrCihm4HquYh0= +github.com/chromedp/chromedp v0.13.3/go.mod h1:khsDP9OP20GrowpJfZ7N05iGCwcAYxk7qf9AZBzR3Qw= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= +github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= +github.com/cucumber/godog v0.15.0 h1:51AL8lBXF3f0cyA5CV4TnJFCTHpgiy+1x1Hb3TtZUmo= +github.com/cucumber/godog v0.15.0/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces= +github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= +github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= +github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8= +github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= @@ -62,10 +96,17 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -75,6 +116,12 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -107,6 +154,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -114,17 +163,31 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo= +github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -133,8 +196,13 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= @@ -143,8 +211,10 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844= -github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -153,14 +223,34 @@ github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q= github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew= github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= @@ -172,29 +262,46 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= +github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk= github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= +github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= @@ -210,6 +317,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -217,6 +326,26 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -229,6 +358,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= @@ -257,6 +387,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -271,6 +402,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -287,6 +420,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= @@ -303,10 +437,13 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -355,10 +492,13 @@ golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -385,6 +525,10 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -392,15 +536,21 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/gotenberg/cmd_test.go b/pkg/gotenberg/cmd_test.go deleted file mode 100644 index 1687387ca..000000000 --- a/pkg/gotenberg/cmd_test.go +++ /dev/null @@ -1,306 +0,0 @@ -package gotenberg - -import ( - "context" - "testing" - "time" - - "go.uber.org/zap" -) - -func TestCommand(t *testing.T) { - cmd := Command(zap.NewNop(), "foo") - if !cmd.process.SysProcAttr.Setpgid { - t.Error("expected cmd.process.SysProcAttr.Setpgid to be true") - } -} - -func TestCommandContext(t *testing.T) { - tests := []struct { - scenario string - ctx context.Context - expectCommandContextError bool - }{ - { - scenario: "nominal behavior", - ctx: context.Background(), - expectCommandContextError: false, - }, - { - scenario: "nil context", - ctx: nil, - expectCommandContextError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.scenario, func(t *testing.T) { - cmd, err := CommandContext(tc.ctx, zap.NewNop(), "foo") - - if err == nil && !cmd.process.SysProcAttr.Setpgid { - t.Fatal("expected cmd.process.SysProcAttr.Setpgid to be true") - } - - if !tc.expectCommandContextError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectCommandContextError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestCmd_Start(t *testing.T) { - tests := []struct { - scenario string - cmd *Cmd - expectStartError bool - }{ - { - scenario: "nominal behavior", - cmd: Command(zap.NewNop(), "echo", "Hello", "World"), - expectStartError: false, - }, - { - scenario: "start error", - cmd: Command(zap.NewNop(), "foo"), - expectStartError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.scenario, func(t *testing.T) { - err := tc.cmd.Start() - - if !tc.expectStartError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectStartError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestCmd_Wait(t *testing.T) { - tests := []struct { - scenario string - cmd *Cmd - expectWaitError bool - }{ - { - scenario: "nominal behavior", - cmd: func() *Cmd { - cmd := Command(zap.NewNop(), "echo", "Hello", "World") - err := cmd.Start() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return cmd - }(), - expectWaitError: false, - }, - { - scenario: "wait error", - cmd: Command(zap.NewNop(), "echo", "Hello", "World"), - expectWaitError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.scenario, func(t *testing.T) { - err := tc.cmd.Wait() - - if !tc.expectWaitError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectWaitError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestCmd_Exec(t *testing.T) { - tests := []struct { - scenario string - cmd *Cmd - timeout time.Duration - expectExecError bool - }{ - { - scenario: "nominal behavior", - cmd: func() *Cmd { - cmd, err := CommandContext(context.Background(), zap.NewNop(), "echo", "Hello", "World") - if err != nil { - t.Fatalf("expected no error from CommandContext(), but got: %v", err) - } - return cmd - }(), - expectExecError: false, - }, - { - scenario: "nil context", - cmd: Command(zap.NewNop(), "echo", "Hello", "World"), - expectExecError: true, - }, - { - scenario: "start error", - cmd: func() *Cmd { - cmd, err := CommandContext(context.Background(), zap.NewNop(), "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return cmd - }(), - expectExecError: true, - }, - { - scenario: "context done", - cmd: Command(zap.NewNop(), "sleep", "2"), - timeout: time.Duration(1) * time.Second, - expectExecError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.scenario, func(t *testing.T) { - if tc.timeout > 0 { - ctx, cancel := context.WithTimeout(context.TODO(), tc.timeout) - defer cancel() - - tc.cmd.ctx = ctx - } - - _, err := tc.cmd.Exec() - - if !tc.expectExecError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectExecError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestCmd_pipeOutput(t *testing.T) { - tests := []struct { - scenario string - cmd *Cmd - run bool - expectPipeOutputError bool - }{ - { - scenario: "nominal behavior", - cmd: Command(zap.NewExample(), "echo", "Hello", "World"), - run: true, - expectPipeOutputError: false, - }, - { - scenario: "no debug, no pipe", - cmd: Command(zap.NewNop(), "echo", "Hello", "World"), - run: false, - expectPipeOutputError: false, - }, - { - scenario: "stdout already piped", - cmd: func() *Cmd { - cmd := Command(zap.NewExample(), "echo", "Hello", "World") - _, err := cmd.process.StdoutPipe() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return cmd - }(), - run: false, - expectPipeOutputError: true, - }, - { - scenario: "stderr already piped", - cmd: func() *Cmd { - cmd := Command(zap.NewExample(), "echo", "Hello", "World") - _, err := cmd.process.StderrPipe() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return cmd - }(), - run: false, - expectPipeOutputError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.scenario, func(t *testing.T) { - err := tc.cmd.pipeOutput() - - if tc.run { - errStart := tc.cmd.process.Start() - if errStart != nil { - t.Fatalf("expected no error but got: %v", err) - } - } - - if !tc.expectPipeOutputError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectPipeOutputError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestCmd_Kill(t *testing.T) { - tests := []struct { - scenario string - cmd *Cmd - }{ - { - scenario: "nominal behavior", - cmd: func() *Cmd { - cmd := Command(zap.NewNop(), "sleep", "60") - err := cmd.process.Start() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return cmd - }(), - }, - { - scenario: "no process", - cmd: &Cmd{logger: zap.NewNop()}, - }, - { - scenario: "process already killed", - cmd: func() *Cmd { - cmd := Command(zap.NewNop(), "sleep", "60") - err := cmd.process.Start() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - err = cmd.Kill() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return cmd - }(), - }, - } - - for _, tc := range tests { - t.Run(tc.scenario, func(t *testing.T) { - err := tc.cmd.Kill() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} diff --git a/pkg/gotenberg/context_test.go b/pkg/gotenberg/context_test.go index 9b48cfd22..f8bc4d6f7 100644 --- a/pkg/gotenberg/context_test.go +++ b/pkg/gotenberg/context_test.go @@ -5,23 +5,6 @@ import ( "testing" ) -func TestNewContext(t *testing.T) { - if NewContext(ParsedFlags{}, nil) == nil { - t.Error("expected a non-nil value") - } -} - -func TestContext_ParsedFlags(t *testing.T) { - ctx := NewContext(ParsedFlags{}, nil) - - actual := ctx.ParsedFlags() - expect := ParsedFlags{} - - if actual != expect { - t.Errorf("expected %v but got %v", expect, actual) - } -} - func TestContext_Module(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/gotenberg/fs_test.go b/pkg/gotenberg/fs_test.go deleted file mode 100644 index ee57d1b9b..000000000 --- a/pkg/gotenberg/fs_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package gotenberg - -import ( - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/google/uuid" -) - -func TestOsMkdirAll_MkdirAll(t *testing.T) { - dirPath, err := NewFileSystem(new(OsMkdirAll)).MkdirAll() - if err != nil { - t.Fatalf("create working directory: %v", err) - } - - err = os.RemoveAll(dirPath) - if err != nil { - t.Fatalf("remove working directory: %v", err) - } -} - -func TestOsPathRename_Rename(t *testing.T) { - dirPath, err := NewFileSystem(new(OsMkdirAll)).MkdirAll() - if err != nil { - t.Fatalf("create working directory: %v", err) - } - - path := "/tests/test/testdata/api/sample1.txt" - copyPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString())) - - in, err := os.Open(path) - if err != nil { - t.Fatalf("open file: %v", err) - } - - defer func() { - err := in.Close() - if err != nil { - t.Fatalf("close file: %v", err) - } - }() - - out, err := os.Create(copyPath) - if err != nil { - t.Fatalf("create new file: %v", err) - } - - defer func() { - err := out.Close() - if err != nil { - t.Fatalf("close new file: %v", err) - } - }() - - _, err = io.Copy(out, in) - if err != nil { - t.Fatalf("copy file to new file: %v", err) - } - - rename := new(OsPathRename) - newPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString())) - - err = rename.Rename(copyPath, newPath) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - - err = os.RemoveAll(dirPath) - if err != nil { - t.Fatalf("remove working directory: %v", err) - } -} - -func TestFileSystem_WorkingDir(t *testing.T) { - fs := NewFileSystem(new(MkdirAllMock)) - dirName := fs.WorkingDir() - - if dirName == "" { - t.Error("expected directory name but got empty string") - } -} - -func TestFileSystem_WorkingDirPath(t *testing.T) { - fs := NewFileSystem(new(MkdirAllMock)) - expectedPath := fmt.Sprintf("%s/%s", os.TempDir(), fs.WorkingDir()) - - if fs.WorkingDirPath() != expectedPath { - t.Errorf("expected path '%s' but got '%s'", expectedPath, fs.WorkingDirPath()) - } -} - -func TestFileSystem_NewDirPath(t *testing.T) { - fs := NewFileSystem(new(MkdirAllMock)) - newDir := fs.NewDirPath() - expectedPrefix := fs.WorkingDirPath() - - if !strings.HasPrefix(newDir, expectedPrefix) { - t.Errorf("expected new directory to start with '%s' but got '%s'", expectedPrefix, newDir) - } -} - -func TestFileSystem_MkdirAll(t *testing.T) { - for _, tc := range []struct { - scenario string - mkdirAll MkdirAll - expectError bool - }{ - { - scenario: "error", - mkdirAll: &MkdirAllMock{ - MkdirAllMock: func(path string, perm os.FileMode) error { - return errors.New("foo") - }, - }, - expectError: true, - }, - { - scenario: "success", - mkdirAll: &MkdirAllMock{ - MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - fs := NewFileSystem(tc.mkdirAll) - - _, err := fs.MkdirAll() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} diff --git a/pkg/gotenberg/gc_test.go b/pkg/gotenberg/gc_test.go index c8a4c09ed..2124b0dda 100644 --- a/pkg/gotenberg/gc_test.go +++ b/pkg/gotenberg/gc_test.go @@ -3,6 +3,7 @@ package gotenberg import ( "fmt" "os" + "path" "testing" "time" @@ -51,7 +52,7 @@ func TestGarbageCollect(t *testing.T) { return path }(), - includeSubstr: []string{"foo", fmt.Sprintf("%s/a_directory/a_bar_file", os.TempDir())}, + includeSubstr: []string{"foo", path.Join(os.TempDir(), "/a_directory/a_bar_file")}, expectError: false, expectExists: []string{"a_baz_file"}, expectNotExists: []string{"a_foo_file", "a_bar_file"}, diff --git a/pkg/gotenberg/logging_test.go b/pkg/gotenberg/logging_test.go deleted file mode 100644 index cc8f203d1..000000000 --- a/pkg/gotenberg/logging_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package gotenberg - -import ( - "testing" - - "go.uber.org/zap" -) - -func TestLeveledLogger_Error(t *testing.T) { - NewLeveledLogger(zap.NewNop()).Error("foo") -} - -func TestLeveledLogger_Warn(t *testing.T) { - NewLeveledLogger(zap.NewNop()).Warn("foo") -} - -func TestLeveledLogger_Info(t *testing.T) { - NewLeveledLogger(zap.NewNop()).Info("foo") -} - -func TestLeveledLogger_Debug(t *testing.T) { - NewLeveledLogger(zap.NewNop()).Debug("foo") -} diff --git a/pkg/gotenberg/mocks_test.go b/pkg/gotenberg/mocks_test.go deleted file mode 100644 index d813499af..000000000 --- a/pkg/gotenberg/mocks_test.go +++ /dev/null @@ -1,264 +0,0 @@ -package gotenberg - -import ( - "context" - "os" - "testing" - - "go.uber.org/zap" -) - -func TestModuleMock(t *testing.T) { - mock := &ModuleMock{ - DescriptorMock: func() ModuleDescriptor { - return ModuleDescriptor{ID: "foo", New: func() Module { - return nil - }} - }, - } - - if mock.Descriptor().ID != "foo" { - t.Errorf("expected ID '%s' from ModuleMock.Descriptor, but got '%s'", "foo", mock.Descriptor().ID) - } -} - -func TestProvisionerMock(t *testing.T) { - mock := &ProvisionerMock{ - ProvisionMock: func(*Context) error { - return nil - }, - } - - err := mock.Provision(&Context{}) - if err != nil { - t.Errorf("expected no error from ProvisionerMock.Provision, but got: %v", err) - } -} - -func TestValidatorMock(t *testing.T) { - mock := &ValidatorMock{ - ValidateMock: func() error { - return nil - }, - } - - err := mock.Validate() - if err != nil { - t.Errorf("expected no error from ValidatorMock.Validate, but got: %v", err) - } -} - -func TestDebuggableMock(t *testing.T) { - mock := &DebuggableMock{ - DebugMock: func() map[string]interface{} { - return map[string]interface{}{ - "foo": "bar", - } - }, - } - - d := mock.Debug() - if d == nil { - t.Errorf("expected debug data, but got nil") - } -} - -func TestPDFEngineMock(t *testing.T) { - mock := &PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - SplitMock: func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) { - return nil, nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error { - return nil - }, - ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { - return nil, nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - } - - err := mock.Merge(context.Background(), zap.NewNop(), nil, "") - if err != nil { - t.Errorf("expected no error from PdfEngineMock.Merge, but got: %v", err) - } - - _, err = mock.Split(context.Background(), zap.NewNop(), SplitMode{}, "", "") - if err != nil { - t.Errorf("expected no error from PdfEngineMock.Split, but got: %v", err) - } - - err = mock.Flatten(context.Background(), zap.NewNop(), "") - if err != nil { - t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err) - } - - err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "") - if err != nil { - t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err) - } - - _, err = mock.ReadMetadata(context.Background(), zap.NewNop(), "") - if err != nil { - t.Errorf("expected no error from PdfEngineMock.ReadMetadata, but got: %v", err) - } - - err = mock.WriteMetadata(context.Background(), zap.NewNop(), map[string]interface{}{}, "") - if err != nil { - t.Errorf("expected no error from PdfEngineMock.WriteMetadata but got: %v", err) - } -} - -func TestPDFEngineProviderMock(t *testing.T) { - mock := &PdfEngineProviderMock{ - PdfEngineMock: func() (PdfEngine, error) { - return new(PdfEngineMock), nil - }, - } - - _, err := mock.PdfEngine() - if err != nil { - t.Errorf("expected no error from PdfEngineProviderMock.PdfEngine, but got: %v", err) - } -} - -func TestProcessMock(t *testing.T) { - mock := &ProcessMock{ - StartMock: func(logger *zap.Logger) error { - return nil - }, - StopMock: func(logger *zap.Logger) error { - return nil - }, - HealthyMock: func(logger *zap.Logger) bool { - return true - }, - } - - err := mock.Start(zap.NewNop()) - if err != nil { - t.Errorf("expected no error from ProcessMock.Start, but got: %v", err) - } - - err = mock.Stop(zap.NewNop()) - if err != nil { - t.Errorf("expected no error from ProcessMock.Stop, but got: %v", err) - } - - healthy := mock.Healthy(zap.NewNop()) - if !healthy { - t.Error("expected true from ProcessMock.Healthy, but got false") - } -} - -func TestProcessSupervisorMock(t *testing.T) { - mock := &ProcessSupervisorMock{ - LaunchMock: func() error { - return nil - }, - ShutdownMock: func() error { - return nil - }, - HealthyMock: func() bool { - return true - }, - RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error { - return nil - }, - ReqQueueSizeMock: func() int64 { - return 0 - }, - RestartsCountMock: func() int64 { - return 0 - }, - } - - err := mock.Launch() - if err != nil { - t.Errorf("expected no error from ProcessSupervisorMock.Launch, but got: %v", err) - } - - err = mock.Shutdown() - if err != nil { - t.Errorf("expected no error from ProcessSupervisorMock.Shutdown, but got: %v", err) - } - - healthy := mock.Healthy() - if !healthy { - t.Error("expected true from ProcessSupervisorMock.Healthy, but got false") - } - - err = mock.Run(context.TODO(), zap.NewNop(), nil) - if err != nil { - t.Errorf("expected no error from ProcessSupervisorMock.Run, but got: %v", err) - } - - size := mock.ReqQueueSize() - if size != 0 { - t.Errorf("expected 0 from ProcessSupervisorMock.ReqQueueSize, but got: %d", size) - } - - restarts := mock.RestartsCount() - if restarts != 0 { - t.Errorf("expected 0 from ProcessSupervisorMock.RestartsCount, but got: %d", restarts) - } -} - -func TestLoggerProviderMock(t *testing.T) { - mock := &LoggerProviderMock{ - LoggerMock: func(mod Module) (*zap.Logger, error) { - return nil, nil - }, - } - - _, err := mock.Logger(new(ModuleMock)) - if err != nil { - t.Errorf("expected no error from LoggerProviderMock.Logger, but got: %v", err) - } -} - -func TestMetricsProviderMock(t *testing.T) { - mock := &MetricsProviderMock{ - MetricsMock: func() ([]Metric, error) { - return nil, nil - }, - } - - _, err := mock.Metrics() - if err != nil { - t.Errorf("expected no error from MetricsProviderMock.Metrics, but got: %v", err) - } -} - -func TestMkdirAllMock(t *testing.T) { - mock := &MkdirAllMock{ - MkdirAllMock: func(dir string, perm os.FileMode) error { - return nil - }, - } - - err := mock.MkdirAll("/foo", 0o755) - if err != nil { - t.Errorf("expected no error from MkdirAllMock.MkdirAll, but got: %v", err) - } -} - -func TestPathRenameMock(t *testing.T) { - mock := &PathRenameMock{ - RenameMock: func(oldpath, newpath string) error { - return nil - }, - } - - err := mock.Rename("", "") - if err != nil { - t.Errorf("expected no error from PathRenameMock.Rename, but got: %v", err) - } -} diff --git a/pkg/modules/api/api_test.go b/pkg/modules/api/api_test.go deleted file mode 100644 index 465adce38..000000000 --- a/pkg/modules/api/api_test.go +++ /dev/null @@ -1,1033 +0,0 @@ -package api - -import ( - "bytes" - "context" - "errors" - "mime/multipart" - "net/http" - "net/http/httptest" - "os" - "reflect" - "testing" - "time" - - "github.com/alexliesenfeld/health" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestApi_Descriptor(t *testing.T) { - descriptor := new(Api).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(Api)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestApi_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - setEnv func() - expectPort int - expectMiddlewares []Middleware - expectError bool - }{ - { - scenario: "port from env: non-existing environment variable", - ctx: func() *gotenberg.Context { - fs := new(Api).Descriptor().FlagSet - err := fs.Parse([]string{"--api-port-from-env=FOO"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - nil, - ) - }(), - expectError: true, - }, - { - scenario: "port from env: invalid environment variable value", - ctx: func() *gotenberg.Context { - fs := new(Api).Descriptor().FlagSet - err := fs.Parse([]string{"--api-port-from-env=PORT"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - nil, - ) - }(), - setEnv: func() { - err := os.Setenv("PORT", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }, - expectError: true, - }, - { - scenario: "basic auth: non-existing GOTENBERG_API_BASIC_AUTH_USERNAME environment variable", - ctx: func() *gotenberg.Context { - fs := new(Api).Descriptor().FlagSet - err := fs.Parse([]string{"--api-enable-basic-auth=true"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - nil, - ) - }(), - expectError: true, - }, - { - scenario: "basic auth: non-existing GOTENBERG_API_BASIC_AUTH_PASSWORD environment variable", - ctx: func() *gotenberg.Context { - fs := new(Api).Descriptor().FlagSet - err := fs.Parse([]string{"--api-enable-basic-auth=true"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - nil, - ) - }(), - setEnv: func() { - err := os.Setenv("GOTENBERG_API_BASIC_AUTH_USERNAME", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }, - expectError: true, - }, - { - scenario: "no valid routers", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - RouterMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ValidateMock = func() error { - return errors.New("foo") - } - mod.RoutesMock = func() ([]Route, error) { - return nil, nil - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "cannot retrieve routes from router", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - RouterMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.RoutesMock = func() ([]Route, error) { - return nil, errors.New("foo") - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no valid middleware providers", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - MiddlewareProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ValidateMock = func() error { - return errors.New("foo") - } - mod.MiddlewaresMock = func() ([]Middleware, error) { - return nil, nil - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "cannot retrieve middlewares from middleware provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - MiddlewareProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.MiddlewaresMock = func() ([]Middleware, error) { - return nil, errors.New("foo") - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no valid health checkers", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - HealthCheckerMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ValidateMock = func() error { - return errors.New("foo") - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "cannot retrieve health checks from health checker", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - HealthCheckerMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ChecksMock = func() ([]health.CheckerOption, error) { - return nil, errors.New("foo") - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no logger provider", - ctx: func() *gotenberg.Context { - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{}, - ) - }(), - expectError: true, - }, - { - scenario: "no logger from logger provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return nil, errors.New("foo") - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "success", - ctx: func() *gotenberg.Context { - mod1 := &struct { - gotenberg.ModuleMock - RouterMock - }{} - mod1.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod1 }} - } - mod1.RoutesMock = func() ([]Route, error) { - return []Route{{}}, nil - } - - mod2 := &struct { - gotenberg.ModuleMock - MiddlewareProviderMock - }{} - mod2.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod2 }} - } - mod2.MiddlewaresMock = func() ([]Middleware, error) { - return []Middleware{ - { - Priority: VeryLowPriority, - }, - { - Priority: LowPriority, - }, - { - Priority: MediumPriority, - }, - { - Priority: HighPriority, - }, - { - Priority: VeryHighPriority, - }, - }, nil - } - - mod3 := &struct { - gotenberg.ModuleMock - HealthCheckerMock - }{} - mod3.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "baz", New: func() gotenberg.Module { return mod3 }} - } - mod3.ChecksMock = func() ([]health.CheckerOption, error) { - return []health.CheckerOption{health.WithDisabledAutostart()}, nil - } - mod3.ReadyMock = func() error { - return nil - } - - mod4 := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - }{} - mod4.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "qux", New: func() gotenberg.Module { return mod4 }} - } - mod4.LoggerMock = func(_ gotenberg.Module) (*zap.Logger, error) { - return zap.NewNop(), nil - } - - fs := new(Api).Descriptor().FlagSet - err := fs.Parse([]string{"--api-port-from-env=PORT", "--api-enable-basic-auth=true"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - []gotenberg.ModuleDescriptor{ - mod1.Descriptor(), - mod2.Descriptor(), - mod3.Descriptor(), - mod4.Descriptor(), - }, - ) - }(), - setEnv: func() { - err := os.Setenv("PORT", "1337") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - err = os.Setenv("GOTENBERG_API_BASIC_AUTH_USERNAME", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - err = os.Setenv("GOTENBERG_API_BASIC_AUTH_PASSWORD", "bar") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }, - expectPort: 1337, - expectMiddlewares: []Middleware{ - { - Priority: VeryHighPriority, - }, - { - Priority: HighPriority, - }, - { - Priority: MediumPriority, - }, - { - Priority: LowPriority, - }, - { - Priority: VeryLowPriority, - }, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - if tc.setEnv != nil { - tc.setEnv() - } - - mod := new(Api) - err := mod.Provision(tc.ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectPort != 0 && mod.port != tc.expectPort { - t.Errorf("expected port %d but got %d", tc.expectPort, mod.port) - } - - if !reflect.DeepEqual(mod.externalMiddlewares, tc.expectMiddlewares) { - t.Errorf("expected %+v, but got: %+v", tc.expectMiddlewares, mod.externalMiddlewares) - } - }) - } -} - -func TestApi_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - port int - bindIp string - tlsCertFile string - tlsKeyFile string - rootPath string - traceHeader string - routes []Route - middlewares []Middleware - expectError bool - }{ - { - scenario: "invalid port (< 1)", - port: 0, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid port (> 65535)", - port: 65536, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid IP", - port: 10, - bindIp: "foo", - rootPath: "/foo/", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid TLS files: only cert file provided", - port: 10, - bindIp: "127.0.0.1", - tlsCertFile: "cert.pem", - rootPath: "/foo/", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid TLS files: only key file provided", - port: 10, - bindIp: "127.0.0.1", - tlsKeyFile: "key.pem", - rootPath: "/foo/", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid root path: missing / prefix", - port: 10, - bindIp: "127.0.0.1", - rootPath: "foo/", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid root path: missing / suffix", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo", - traceHeader: "foo", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid trace header", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "", - routes: nil, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid route: empty path", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Path: "", - }, - }, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid route: missing / prefix in path", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Path: "foo", - }, - }, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid multipart route: no /forms prefix in path", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Path: "/foo", - IsMultipart: true, - }, - }, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid route: no method", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Path: "/foo", - Method: "", - }, - }, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid route: nil handler", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Method: http.MethodPost, - Path: "/foo", - Handler: nil, - }, - }, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid route: path already existing", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Method: http.MethodPost, - Path: "/foo", - Handler: func(_ echo.Context) error { return nil }, - }, - { - Method: http.MethodPost, - Path: "/foo", - Handler: func(_ echo.Context) error { return nil }, - }, - }, - middlewares: nil, - expectError: true, - }, - { - scenario: "invalid middleware: nil handler", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: nil, - middlewares: []Middleware{ - { - Priority: HighPriority, - Handler: nil, - }, - }, - expectError: true, - }, - { - scenario: "success", - port: 10, - bindIp: "127.0.0.1", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Method: http.MethodGet, - Path: "/foo", - Handler: func(_ echo.Context) error { return nil }, - }, - { - Method: http.MethodGet, - Path: "/forms/foo", - Handler: func(_ echo.Context) error { return nil }, - IsMultipart: true, - }, - }, - middlewares: []Middleware{ - { - Priority: HighPriority, - Handler: func() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - return next(c) - } - } - }(), - }, - }, - }, - { - scenario: "success with TLS", - port: 10, - tlsCertFile: "cert.pem", - tlsKeyFile: "key.pem", - rootPath: "/foo/", - traceHeader: "foo", - routes: []Route{ - { - Method: http.MethodGet, - Path: "/foo", - Handler: func(_ echo.Context) error { return nil }, - }, - { - Method: http.MethodGet, - Path: "/forms/foo", - Handler: func(_ echo.Context) error { return nil }, - IsMultipart: true, - }, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := Api{ - port: tc.port, - bindIp: tc.bindIp, - tlsCertFile: tc.tlsCertFile, - tlsKeyFile: tc.tlsKeyFile, - rootPath: tc.rootPath, - traceHeader: tc.traceHeader, - routes: tc.routes, - externalMiddlewares: tc.middlewares, - } - - err := mod.Validate() - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestApi_Start(t *testing.T) { - for _, tc := range []struct { - scenario string - readyFn []func() error - tlsCertFile string - tlsKeyFile string - expectError bool - }{ - { - scenario: "at least one module not ready", - readyFn: []func() error{ - func() error { return nil }, - func() error { return errors.New("not ready") }, - }, - expectError: true, - }, - { - scenario: "success", - readyFn: []func() error{ - func() error { return nil }, - func() error { return nil }, - }, - expectError: false, - }, - { - scenario: "success with TLS", - readyFn: []func() error{ - func() error { return nil }, - func() error { return nil }, - }, - tlsCertFile: "/tests/test/testdata/api/cert.pem", - tlsKeyFile: "/tests/test/testdata/api/key.pem", - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Api) - mod.port = 3000 - mod.tlsCertFile = tc.tlsCertFile - mod.tlsKeyFile = tc.tlsKeyFile - mod.startTimeout = time.Duration(30) * time.Second - mod.rootPath = "/" - mod.basicAuthUsername = "foo" - mod.basicAuthPassword = "bar" - mod.disableHealthCheckLogging = true - mod.enableDebugRoute = true - mod.routes = []Route{ - { - Method: http.MethodPost, - Path: "/forms/foo", - IsMultipart: true, - DisableLogging: true, - Handler: func(c echo.Context) error { - ctx := c.Get("context").(*Context) - ctx.outputPaths = []string{ - "/tests/test/testdata/api/sample1.txt", - } - - return nil - }, - }, - { - Method: http.MethodPost, - Path: "/forms/bar", - IsMultipart: true, - Handler: func(_ echo.Context) error { return errors.New("foo") }, - }, - } - mod.externalMiddlewares = []Middleware{ - { - Stack: PreRouterStack, - Handler: func() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - return next(c) - } - } - }(), - }, - { - Stack: MultipartStack, - Handler: func() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - return next(c) - } - } - }(), - }, - { - Stack: DefaultStack, - Handler: func() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - return next(c) - } - } - }(), - }, - { - Handler: func() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - return next(c) - } - } - }(), - }, - } - mod.readyFn = tc.readyFn - mod.fs = gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - mod.logger = zap.NewNop() - - err := mod.Start() - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectError { - return - } - - // root request. - recorder := httptest.NewRecorder() - rootRequest := httptest.NewRequest(http.MethodGet, "/", nil) - rootRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) - mod.srv.ServeHTTP(recorder, rootRequest) - if recorder.Code != http.StatusOK { - t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) - } - - // favicon request. - recorder = httptest.NewRecorder() - faviconRequest := httptest.NewRequest(http.MethodGet, "/favicon.ico", nil) - faviconRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) - mod.srv.ServeHTTP(recorder, faviconRequest) - if recorder.Code != http.StatusNoContent { - t.Errorf("expected %d status code but got %d", http.StatusNoContent, recorder.Code) - } - - // health requests. - recorder = httptest.NewRecorder() - healthGetRequest := httptest.NewRequest(http.MethodGet, "/health", nil) - mod.srv.ServeHTTP(recorder, healthGetRequest) - if recorder.Code != http.StatusOK { - t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) - } - - recorder = httptest.NewRecorder() - healthHeadRequest := httptest.NewRequest(http.MethodHead, "/health", nil) - mod.srv.ServeHTTP(recorder, healthHeadRequest) - if recorder.Code != http.StatusOK { - t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) - } - - // version request. - recorder = httptest.NewRecorder() - versionRequest := httptest.NewRequest(http.MethodGet, "/version", nil) - versionRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) - mod.srv.ServeHTTP(recorder, versionRequest) - if recorder.Code != http.StatusOK { - t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) - } - - // debug request. - recorder = httptest.NewRecorder() - debugRequest := httptest.NewRequest(http.MethodGet, "/debug", nil) - debugRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) - mod.srv.ServeHTTP(recorder, debugRequest) - if recorder.Code != http.StatusOK { - t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) - } - - // "multipart/form-data" request. - multipartRequest := func(url string) *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - - err := writer.WriteField("foo", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - part, err := writer.CreateFormFile("foo.txt", "foo.txt") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - _, err = part.Write([]byte("foo")) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - req := httptest.NewRequest(http.MethodPost, url, body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - req.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword) - - return req - } - - recorder = httptest.NewRecorder() - mod.srv.ServeHTTP(recorder, multipartRequest("/forms/foo")) - - if recorder.Code != http.StatusOK { - t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code) - } - - recorder = httptest.NewRecorder() - mod.srv.ServeHTTP(recorder, multipartRequest("/forms/bar")) - - if recorder.Code != http.StatusInternalServerError { - t.Errorf("expected %d status code but got %d", http.StatusInternalServerError, recorder.Code) - } - - err = mod.srv.Shutdown(context.TODO()) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - }) - } -} - -func TestApi_StartupMessage(t *testing.T) { - for _, tc := range []struct { - scenario string - port int - bindIp string - expectMessage string - }{ - { - scenario: "no custom IP", - port: 3000, - bindIp: "", - expectMessage: "server started on [::]:3000", - }, - { - scenario: "custom IP", - port: 3000, - bindIp: "127.0.0.1", - expectMessage: "server started on 127.0.0.1:3000", - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := Api{ - port: tc.port, - bindIp: tc.bindIp, - } - - actual := mod.StartupMessage() - if actual != tc.expectMessage { - t.Errorf("expected '%s' but got '%s'", tc.expectMessage, actual) - } - }) - } -} - -func TestApi_Stop(t *testing.T) { - mod := &Api{ - port: 3000, - routes: []Route{ - { - Method: http.MethodGet, - Path: "/foo", - Handler: func(_ echo.Context) error { return nil }, - }, - }, - logger: zap.NewNop(), - } - - err := mod.Start() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = mod.Stop(context.TODO()) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} diff --git a/pkg/modules/api/context_test.go b/pkg/modules/api/context_test.go deleted file mode 100644 index b69bc0a58..000000000 --- a/pkg/modules/api/context_test.go +++ /dev/null @@ -1,837 +0,0 @@ -package api - -import ( - "bytes" - "context" - "errors" - "mime/multipart" - "net/http" - "net/http/httptest" - "os" - "reflect" - "strings" - "testing" - "time" - - "github.com/dlclark/regexp2" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestNewContext(t *testing.T) { - defaultAllowList, err := regexp2.Compile("", 0) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - defaultDenyList, err := regexp2.Compile("", 0) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - defaultDownloadFromCfg := downloadFromConfig{ - allowList: defaultAllowList, - denyList: defaultDenyList, - maxRetry: 1, - disable: false, - } - - for _, tc := range []struct { - scenario string - request *http.Request - bodyLimit int64 - downloadFromCfg downloadFromConfig - downloadFromSrv *echo.Echo - expectContext *Context - expectError bool - expectHttpError bool - expectHttpStatus int - }{ - { - scenario: "http.ErrNotMultipart", - request: httptest.NewRequest(http.MethodPost, "/", nil), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusUnsupportedMediaType, - }, - { - scenario: "http.ErrMissingBoundary", - request: func() *http.Request { - req := httptest.NewRequest(http.MethodPost, "/", nil) - req.Header.Set(echo.HeaderContentType, echo.MIMEMultipartForm) - return req - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusUnsupportedMediaType, - }, - { - scenario: "malformed body", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("foo", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", nil) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "request entity too large: form values", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("key", "value") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - bodyLimit: 1, - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusRequestEntityTooLarge, - }, - { - scenario: "request entity too large: downloadFrom", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - bodyLimit: 45, // form values = 44 bytes. - downloadFromSrv: func() *echo.Echo { - srv := echo.New() - srv.HideBanner = true - srv.GET("/", func(c echo.Context) error { - c.Response().Header().Set(echo.HeaderContentDisposition, `attachment; filename="bar.txt"`) - c.Response().Header().Set(echo.HeaderContentType, "text/plain") - return c.String(http.StatusOK, http.StatusText(http.StatusOK)) - }) - return srv - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusRequestEntityTooLarge, - }, - { - scenario: "request entity too large: form files", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - part, err := writer.CreateFormFile("foo.txt", "foo.txt") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - _, err = part.Write([]byte("foo")) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - bodyLimit: 1, - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusRequestEntityTooLarge, - }, - { - scenario: "invalid downloadFrom form field: cannot unmarshal", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid downloadFrom form field: no URL", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid downloadFrom form field: filtered URL", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"https://foo.bar"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromCfg: func() downloadFromConfig { - denyList, err := regexp2.Compile("https://foo.bar", 0) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return downloadFromConfig{allowList: defaultAllowList, denyList: denyList, maxRetry: 1, disable: false} - }(), - expectError: true, - }, - { - scenario: "invalid downloadFrom form field: unreachable URL", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid downloadFrom form field: invalid status code", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromSrv: func() *echo.Echo { - srv := echo.New() - srv.HideBanner = true - srv.GET("/", func(c echo.Context) error { - return c.String(http.StatusNotFound, http.StatusText(http.StatusNotFound)) - }) - return srv - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid downloadFrom form field: no 'Content-Disposition' header", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromSrv: func() *echo.Echo { - srv := echo.New() - srv.HideBanner = true - srv.GET("/", func(c echo.Context) error { - return c.String(http.StatusOK, http.StatusText(http.StatusOK)) - }) - return srv - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid downloadFrom form field: malformed 'Content-Disposition' header", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromSrv: func() *echo.Echo { - srv := echo.New() - srv.HideBanner = true - srv.GET("/", func(c echo.Context) error { - c.Response().Header().Set(echo.HeaderContentDisposition, ";;") - return c.String(http.StatusOK, http.StatusText(http.StatusOK)) - }) - return srv - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid downloadFrom form field: no filename parameter in 'Content-Disposition' header", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromSrv: func() *echo.Echo { - srv := echo.New() - srv.HideBanner = true - srv.GET("/", func(c echo.Context) error { - c.Response().Header().Set(echo.HeaderContentDisposition, "inline;") - return c.String(http.StatusOK, http.StatusText(http.StatusOK)) - }) - return srv - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "success", - request: func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - err := writer.WriteField("foo", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - part, err := writer.CreateFormFile("foo.txt", "foo.txt") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - _, err = part.Write([]byte("foo")) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - err = writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/","extraHttpHeaders":{"X-Foo":"Bar"}}]`) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - return req - }(), - downloadFromSrv: func() *echo.Echo { - srv := echo.New() - srv.HideBanner = true - srv.GET("/", func(c echo.Context) error { - if c.Request().Header.Get("User-Agent") != "Gotenberg" { - t.Fatalf("expected 'Gotenberg' from header 'User-Agent', but got '%s'", c.Request().Header.Get("User-Agent")) - } - if c.Request().Header.Get("X-Foo") != "Bar" { - t.Fatalf("expected 'Bar' from header 'X-Foo', but got '%s'", c.Request().Header.Get("X-Foo")) - } - if c.Request().Header.Get("Gotenberg-Trace") != "123" { - t.Fatalf("expected '123' from header 'Gotenberg-Trace', but got '%s'", c.Request().Header.Get("Gotenberg-Trace")) - } - c.Response().Header().Set(echo.HeaderContentDisposition, `attachment; filename="bar.txt"`) - c.Response().Header().Set(echo.HeaderContentType, "text/plain") - return c.String(http.StatusOK, http.StatusText(http.StatusOK)) - }) - return srv - }(), - downloadFromCfg: defaultDownloadFromCfg, - expectContext: &Context{ - values: map[string][]string{ - "foo": {"foo"}, - "downloadFrom": { - `[{"url":"http://localhost:80/","extraHttpHeaders":{"X-Foo":"Bar"}}]`, - }, - }, - files: map[string]string{ - "foo.txt": "foo.txt", - "bar.txt": "bar.txt", // downloadFrom. - }, - }, - expectError: false, - expectHttpError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - if tc.downloadFromSrv != nil { - go func() { - err := tc.downloadFromSrv.Start(":80") - if !errors.Is(err, http.ErrServerClosed) { - t.Error(err) - return - } - }() - defer func() { - err := tc.downloadFromSrv.Shutdown(context.TODO()) - if err != nil { - t.Error(err) - } - }() - } - - handler := func(c echo.Context) error { - ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, tc.bodyLimit, tc.downloadFromCfg, "Gotenberg-Trace", "123") - defer cancel() - // Context already cancelled. - defer cancel() - - if err != nil { - return err - } - - if tc.expectContext != nil { - if !reflect.DeepEqual(tc.expectContext.values, ctx.values) { - t.Fatalf("expected context.values to be %v but got %v", tc.expectContext.values, ctx.values) - } - if len(tc.expectContext.files) != len(ctx.files) { - t.Fatalf("expected context.files to contain %d items but got %d", len(tc.expectContext.files), len(ctx.files)) - } - for key, value := range tc.expectContext.files { - if !strings.HasSuffix(ctx.files[key], value) { - t.Fatalf("expected context.files to contain '%s' but got '%s'", value, ctx.files[key]) - } - } - } - - return nil - } - - recorder := httptest.NewRecorder() - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(tc.request, recorder) - err := handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - }) - } -} - -func TestContext_Request(t *testing.T) { - request := httptest.NewRequest(http.MethodPost, "/", nil) - recorder := httptest.NewRecorder() - c := echo.New().NewContext(request, recorder) - - ctx := &Context{ - echoCtx: c, - } - - if !reflect.DeepEqual(ctx.Request(), c.Request()) { - t.Errorf("expected %v but got %v", ctx.Request(), c.Request()) - } -} - -func TestContext_FormData(t *testing.T) { - ctx := &Context{ - values: map[string][]string{ - "foo": {"foo"}, - }, - files: map[string]string{ - "foo.txt": "/foo.txt", - }, - } - - actual := ctx.FormData() - expect := &FormData{ - values: ctx.values, - files: ctx.files, - } - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("expected %+v but got %+v", expect, actual) - } -} - -func TestContext_CreateSubDirectory(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *Context - expectError bool - }{ - { - scenario: "failure", - ctx: &Context{mkdirAll: &gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return errors.New("cannot rename") - }}}, - expectError: true, - }, - { - scenario: "success", - ctx: &Context{mkdirAll: &gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.logger = zap.NewNop() - _, err := tc.ctx.CreateSubDirectory("foo") - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestContext_GeneratePath(t *testing.T) { - ctx := &Context{ - dirPath: "/foo", - } - - path := ctx.GeneratePath(".pdf") - if !strings.HasPrefix(path, ctx.dirPath) { - t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath) - } -} - -func TestContext_Rename(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *Context - expectError bool - }{ - { - scenario: "failure", - ctx: &Context{pathRename: &gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return errors.New("cannot rename") - }}}, - expectError: true, - }, - { - scenario: "success", - ctx: &Context{pathRename: &gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.logger = zap.NewNop() - err := tc.ctx.Rename("", "") - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestContext_AddOutputPaths(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *Context - path string - expectCount int - expectError bool - }{ - { - scenario: "ErrContextAlreadyClosed", - ctx: &Context{cancelled: true}, - expectCount: 0, - expectError: true, - }, - { - scenario: "ErrOutOfBoundsOutputPath", - ctx: &Context{dirPath: "/foo"}, - path: "/bar/foo.txt", - expectCount: 0, - expectError: true, - }, - { - scenario: "success", - ctx: &Context{dirPath: "/foo"}, - path: "/foo/foo.txt", - expectCount: 1, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - err := tc.ctx.AddOutputPaths(tc.path) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if len(tc.ctx.outputPaths) != tc.expectCount { - t.Errorf("expected %d output paths but got %d", tc.expectCount, len(tc.ctx.outputPaths)) - } - }) - } -} - -func TestContext_Log(t *testing.T) { - expect := zap.NewNop() - ctx := Context{logger: expect} - actual := ctx.Log() - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("expected %v but got %v", expect, actual) - } -} - -func TestContext_BuildOutputFile(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *Context - expectError bool - }{ - { - scenario: "ErrContextAlreadyClosed", - ctx: &Context{cancelled: true}, - expectError: true, - }, - { - scenario: "no output path", - ctx: &Context{}, - expectError: true, - }, - { - scenario: "success: one output path", - ctx: &Context{outputPaths: []string{"foo.txt"}}, - expectError: false, - }, - { - scenario: "cannot archive: invalid output paths", - ctx: &Context{outputPaths: []string{"foo.txt", "foo.pdf"}}, - expectError: true, - }, - { - scenario: "success: many output paths", - ctx: &Context{ - outputPaths: []string{ - "/tests/test/testdata/api/sample1.txt", - "/tests/test/testdata/api/sample1.txt", - }, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - dirPath, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected no erro but got: %v", err) - } - - defer func() { - err := os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - tc.ctx.dirPath = dirPath - tc.ctx.Context = context.Background() - tc.ctx.logger = zap.NewNop() - - _, err = tc.ctx.BuildOutputFile() - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestContext_OutputFilename(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *Context - outputPath string - expectOutputFilename string - }{ - { - scenario: "with Gotenberg-Output-Filename header", - ctx: func() *Context { - c := echo.New().NewContext(httptest.NewRequest(http.MethodGet, "/foo", nil), nil) - c.Request().Header.Set("Gotenberg-Output-Filename", "foo") - return &Context{echoCtx: c} - }(), - outputPath: "/foo/bar.txt", - expectOutputFilename: "foo.txt", - }, - { - scenario: "without custom filename", - ctx: func() *Context { - c := echo.New().NewContext(httptest.NewRequest(http.MethodGet, "/foo", nil), nil) - return &Context{echoCtx: c} - }(), - outputPath: "/foo/foo.txt", - expectOutputFilename: "foo.txt", - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - actual := tc.ctx.OutputFilename(tc.outputPath) - - if actual != tc.expectOutputFilename { - t.Errorf("expected '%s' but got '%s'", tc.expectOutputFilename, actual) - } - }) - } -} diff --git a/pkg/modules/api/formdata_test.go b/pkg/modules/api/formdata_test.go index 5ebd0b2f1..e3ce8f41c 100644 --- a/pkg/modules/api/formdata_test.go +++ b/pkg/modules/api/formdata_test.go @@ -1425,36 +1425,36 @@ func TestFormData_Content(t *testing.T) { scenario: "file does exist without file extension", form: &FormData{ files: map[string]string{ - "foo": "/tests/test/testdata/api/sample1.txt", + "foo": "testdata/sample.txt", }, }, filename: "foo", defaultValue: "", - expect: "foo", + expect: "This is a text from a text file.", expectError: false, }, { scenario: "file does exist with an uppercase file extension", form: &FormData{ files: map[string]string{ - "foo.TXT": "/tests/test/testdata/api/sample1.txt", + "foo.TXT": "testdata/sample.txt", }, }, filename: "foo.txt", defaultValue: "", - expect: "foo", + expect: "This is a text from a text file.", expectError: false, }, { scenario: "file does exist without a lowercase file extension", form: &FormData{ files: map[string]string{ - "foo.txt": "/tests/test/testdata/api/sample1.txt", + "foo.txt": "testdata/sample.txt", }, }, filename: "foo.txt", defaultValue: "", - expect: "foo", + expect: "This is a text from a text file.", expectError: false, }, } { @@ -1519,33 +1519,33 @@ func TestFormData_MandatoryContent(t *testing.T) { scenario: "mandatory file does exist without file extension", form: &FormData{ files: map[string]string{ - "foo": "/tests/test/testdata/api/sample1.txt", + "foo": "testdata/sample.txt", }, }, filename: "foo", - expect: "foo", + expect: "This is a text from a text file.", expectError: false, }, { scenario: "mandatory file does exist with an uppercase file extension", form: &FormData{ files: map[string]string{ - "foo.TXT": "/tests/test/testdata/api/sample1.txt", + "foo.TXT": "testdata/sample.txt", }, }, filename: "foo.txt", - expect: "foo", + expect: "This is a text from a text file.", expectError: false, }, { scenario: "mandatory file does exist without a lowercase file extension", form: &FormData{ files: map[string]string{ - "foo.txt": "/tests/test/testdata/api/sample1.txt", + "foo.txt": "testdata/sample.txt", }, }, filename: "foo.txt", - expect: "foo", + expect: "This is a text from a text file.", expectError: false, }, } { diff --git a/pkg/modules/api/middlewares_test.go b/pkg/modules/api/middlewares_test.go deleted file mode 100644 index 8bef12682..000000000 --- a/pkg/modules/api/middlewares_test.go +++ /dev/null @@ -1,584 +0,0 @@ -package api - -import ( - "bytes" - "context" - "errors" - "mime/multipart" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestParseError(t *testing.T) { - for i, tc := range []struct { - err error - expectStatus int - expectMessage string - }{ - { - err: echo.ErrInternalServerError, - expectStatus: http.StatusInternalServerError, - expectMessage: http.StatusText(http.StatusInternalServerError), - }, - { - err: gotenberg.ErrFiltered, - expectStatus: http.StatusForbidden, - expectMessage: http.StatusText(http.StatusForbidden), - }, - { - err: gotenberg.ErrMaximumQueueSizeExceeded, - expectStatus: http.StatusTooManyRequests, - expectMessage: http.StatusText(http.StatusTooManyRequests), - }, - { - err: gotenberg.ErrPdfSplitModeNotSupported, - expectStatus: http.StatusBadRequest, - expectMessage: "At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues", - }, - { - err: gotenberg.ErrPdfFormatNotSupported, - expectStatus: http.StatusBadRequest, - expectMessage: "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues", - }, - { - err: gotenberg.ErrPdfEngineMetadataValueNotSupported, - expectStatus: http.StatusBadRequest, - expectMessage: "At least one PDF engine cannot process the requested metadata, while others may have failed to convert due to different issues", - }, - { - err: WrapError( - errors.New("foo"), - NewSentinelHttpError(http.StatusBadRequest, "foo"), - ), - expectStatus: http.StatusBadRequest, - expectMessage: "foo", - }, - } { - actualStatus, actualMessage := ParseError(tc.err) - - if actualStatus != tc.expectStatus { - t.Errorf("test %d: expected HTTP status code %d but got %d", i, tc.expectStatus, actualStatus) - } - - if actualMessage != tc.expectMessage { - t.Errorf("test %d: expected message '%s' but got '%s'", i, tc.expectMessage, actualMessage) - } - } -} - -func TestHttpErrorHandler(t *testing.T) { - for i, tc := range []struct { - err error - expectStatus int - expectMessage string - }{ - { - err: echo.ErrInternalServerError, - expectStatus: http.StatusInternalServerError, - expectMessage: http.StatusText(http.StatusInternalServerError), - }, - { - err: context.DeadlineExceeded, - expectStatus: http.StatusServiceUnavailable, - expectMessage: http.StatusText(http.StatusServiceUnavailable), - }, - { - err: WrapError( - errors.New("foo"), - NewSentinelHttpError(http.StatusBadRequest, "foo"), - ), - expectStatus: http.StatusBadRequest, - expectMessage: "foo", - }, - } { - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/foo", nil) - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(request, recorder) - c.Set("logger", zap.NewNop()) - - handler := httpErrorHandler() - handler(tc.err, c) - - contentType := recorder.Header().Get(echo.HeaderContentType) - if contentType != echo.MIMETextPlainCharsetUTF8 { - t.Errorf("test %d: expected %s '%s' but got '%s'", i, echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8, contentType) - } - - // Note: we cannot test the trace header in the response here, as it is set in the trace middleware. - - if recorder.Code != tc.expectStatus { - t.Errorf("test %d: expected HTTP status code %d but got %d", i, tc.expectStatus, recorder.Code) - } - - if recorder.Body.String() != tc.expectMessage { - t.Errorf("test %d: expected message '%s' but got '%s'", i, tc.expectMessage, recorder.Body.String()) - } - } -} - -func TestLatencyMiddleware(t *testing.T) { - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/foo", nil) - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(request, recorder) - - err := latencyMiddleware()( - func(c echo.Context) error { - return nil - }, - )(c) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - startTime := c.Get("startTime").(time.Time) - now := time.Now() - - if now.Before(startTime) { - t.Errorf("expected start time %s to be < %s", startTime, now) - } -} - -func TestRootPathMiddleware(t *testing.T) { - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/foo", nil) - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(request, recorder) - - err := rootPathMiddleware("foo")( - func(c echo.Context) error { - return nil - }, - )(c) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - rootPath := c.Get("rootPath").(string) - - if rootPath != "foo" { - t.Errorf("expected '%s' but got '%s", "foo", rootPath) - } -} - -func TestTraceMiddleware(t *testing.T) { - for i, tc := range []struct { - trace string - }{ - { - trace: "foo", - }, - { - trace: "", - }, - } { - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/foo", nil) - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(request, recorder) - - if tc.trace != "" { - c.Request().Header.Set("Gotenberg-Trace", tc.trace) - } - - err := traceMiddleware("Gotenberg-Trace")( - func(c echo.Context) error { - return nil - }, - )(c) - if err != nil { - t.Fatalf("test %d: expected no error but got: %v", i, err) - } - - trace := c.Get("trace").(string) - - if trace == "" { - t.Errorf("test %d: expected non empty trace in context", i) - } - - if tc.trace != "" && trace != tc.trace { - t.Errorf("test %d: expected context trace '%s' but got '%s'", i, tc.trace, trace) - } - - if tc.trace == "" && trace == tc.trace { - t.Errorf("test %d: expected context trace different from '%s' but got '%s'", i, tc.trace, trace) - } - - responseTrace := recorder.Header().Get("Gotenberg-Trace") - - if tc.trace != "" && responseTrace != tc.trace { - t.Errorf("test %d: expected header trace '%s' but got '%s'", i, tc.trace, responseTrace) - } - - if tc.trace == "" && responseTrace == tc.trace { - t.Errorf("test %d: expected header trace different from '%s' but got '%s'", i, tc.trace, responseTrace) - } - } -} - -func TestBasicAuthMiddleware(t *testing.T) { - for _, tc := range []struct { - scenario string - request *http.Request - username string - password string - expectError bool - }{ - { - scenario: "invalid basic auth", - request: func() *http.Request { - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.SetBasicAuth("invalid", "invalid") - return req - }(), - username: "foo", - password: "bar", - expectError: true, - }, - { - scenario: "valid basic auth", - request: func() *http.Request { - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.SetBasicAuth("foo", "bar") - return req - }(), - username: "foo", - password: "bar", - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - recorder := httptest.NewRecorder() - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - c := srv.NewContext(tc.request, recorder) - err := basicAuthMiddleware(tc.username, tc.password)(func(c echo.Context) error { - return nil - })(c) - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestLoggerMiddleware(t *testing.T) { - for i, tc := range []struct { - request *http.Request - next echo.HandlerFunc - skipLogging bool - }{ - { - request: httptest.NewRequest(http.MethodGet, "/", nil), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return errors.New("foo") - } - }(), - }, - { - request: httptest.NewRequest(http.MethodGet, "/health", nil), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return nil - } - }(), - skipLogging: true, - }, - { - request: httptest.NewRequest(http.MethodGet, "/health", nil), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return nil - } - }(), - }, - } { - recorder := httptest.NewRecorder() - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(tc.request, recorder) - c.Set("startTime", time.Now()) - c.Set("trace", "foo") - c.Set("rootPath", "/") - - var disableLoggingForPaths []string - if tc.skipLogging { - disableLoggingForPaths = append(disableLoggingForPaths, tc.request.RequestURI) - } - - err := loggerMiddleware(zap.NewNop(), disableLoggingForPaths)(tc.next)(c) - if err != nil { - t.Errorf("test %d: expected no error but got: %v", i, err) - } - } -} - -func TestContextMiddleware(t *testing.T) { - buildMultipartFormDataRequest := func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - - err := writer.WriteField("foo", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - - return req - } - - for i, tc := range []struct { - request *http.Request - next echo.HandlerFunc - expectErr bool - expectStatus int - expectContentType string - expectFilename string - }{ - { - request: httptest.NewRequest(http.MethodGet, "/", nil), - expectErr: true, - }, - { - request: buildMultipartFormDataRequest(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return ErrAsyncProcess - } - }(), - expectStatus: http.StatusNoContent, - }, - { - request: buildMultipartFormDataRequest(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return ErrNoOutputFile - } - }(), - expectStatus: http.StatusOK, - }, - { - request: buildMultipartFormDataRequest(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return errors.New("foo") - } - }(), - expectErr: true, - }, - { - request: buildMultipartFormDataRequest(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return nil - } - }(), - expectErr: true, - }, - { - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Output-Filename", "foo") - - return req - }(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - ctx := c.Get("context").(*Context) - ctx.outputPaths = []string{ - "/tests/test/testdata/api/sample2.pdf", - } - - return nil - } - }(), - expectStatus: http.StatusOK, - expectContentType: "application/pdf", - expectFilename: "foo.pdf", - }, - { - request: buildMultipartFormDataRequest(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - ctx := c.Get("context").(*Context) - ctx.outputPaths = []string{ - "/tests/test/testdata/api/sample1.txt", - "/tests/test/testdata/api/sample2.pdf", - } - - return nil - } - }(), - expectStatus: http.StatusOK, - expectContentType: "application/zip", - }, - } { - recorder := httptest.NewRecorder() - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(tc.request, recorder) - c.Set("logger", zap.NewNop()) - c.Set("traceHeader", "Gotenberg-Trace") - c.Set("trace", "foo") - c.Set("startTime", time.Now()) - - err := contextMiddleware(gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c) - - if tc.expectErr && err == nil { - t.Errorf("test %d: expected error but got: %v", i, err) - } - - if !tc.expectErr && err != nil { - t.Errorf("test %d: expected no error but got: %v", i, err) - } - - if err != nil { - continue - } - - if recorder.Code != tc.expectStatus { - t.Errorf("test %d: expected HTTP status code %d but got %d", i, tc.expectStatus, recorder.Code) - } - - if tc.expectStatus == http.StatusNoContent { - continue - } - - contentType := recorder.Header().Get(echo.HeaderContentType) - if contentType != tc.expectContentType { - t.Errorf("test %d: expected %s '%s' but got '%s'", i, echo.HeaderContentType, tc.expectContentType, contentType) - } - - contentDisposition := recorder.Header().Get(echo.HeaderContentDisposition) - if !strings.Contains(contentDisposition, tc.expectFilename) { - t.Errorf("test %d: expected %s '%s' to contain '%s'", i, echo.HeaderContentDisposition, contentDisposition, tc.expectFilename) - } - } -} - -func TestHardTimeoutMiddleware(t *testing.T) { - for i, tc := range []struct { - next echo.HandlerFunc - timeout time.Duration - expectErr bool - expectHardTimeout bool - }{ - { - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return nil - } - }(), - timeout: time.Duration(100) * time.Millisecond, - }, - { - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - panic("foo") - } - }(), - timeout: time.Duration(100) * time.Millisecond, - expectErr: true, - expectHardTimeout: true, - }, - { - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return errors.New("foo") - } - }(), - timeout: time.Duration(100) * time.Millisecond, - expectErr: true, - }, - { - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - time.Sleep(time.Duration(200) * time.Millisecond) - - return nil - } - }(), - timeout: time.Duration(100) * time.Millisecond, - expectErr: true, - expectHardTimeout: true, - }, - } { - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/foo", nil) - - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(request, recorder) - c.Set("logger", zap.NewNop()) - - err := hardTimeoutMiddleware(tc.timeout)(tc.next)(c) - - if tc.expectErr && err == nil { - t.Errorf("test %d: expected error but got: %v", i, err) - } - - if !tc.expectErr && err != nil { - t.Errorf("test %d: expected no error but got: %v", i, err) - } - - var isHardTimeout bool - if err != nil { - isHardTimeout = strings.Contains(err.Error(), "hard timeout") - } - - if tc.expectHardTimeout && !isHardTimeout { - t.Errorf("test %d: expected hard timeout error but got: %v", i, err) - } - - if !tc.expectHardTimeout && isHardTimeout { - t.Errorf("test %d: expected no hard timeout error but got one: %v", i, err) - } - } -} diff --git a/pkg/modules/api/mocks_test.go b/pkg/modules/api/mocks_test.go deleted file mode 100644 index ecc2a19cf..000000000 --- a/pkg/modules/api/mocks_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package api - -import ( - "reflect" - "testing" - - "github.com/alexliesenfeld/health" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestContextMock_SetDirPath(t *testing.T) { - mock := &ContextMock{&Context{}} - mock.SetDirPath("/foo") - - actual := mock.dirPath - expect := "/foo" - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestContextMock_DirPath(t *testing.T) { - mock := &ContextMock{&Context{}} - mock.SetDirPath("/foo") - - actual := mock.DirPath() - expect := "/foo" - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestContextMock_SetValues(t *testing.T) { - mock := &ContextMock{&Context{}} - mock.SetValues(map[string][]string{ - "foo": {"foo"}, - }) - - actual := mock.values - expect := map[string][]string{ - "foo": {"foo"}, - } - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("expected %+v but got: %+v", expect, actual) - } -} - -func TestContextMock_SetFiles(t *testing.T) { - mock := &ContextMock{&Context{}} - mock.SetFiles(map[string]string{ - "foo": "/foo", - }) - - actual := mock.files - expect := map[string]string{ - "foo": "/foo", - } - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("expected %+v but got: %+v", expect, actual) - } -} - -func TestContextMock_SetCancelled(t *testing.T) { - mock := &ContextMock{&Context{}} - mock.SetCancelled(true) - - actual := mock.cancelled - - if !actual { - t.Errorf("expected %t but got %t", true, actual) - } -} - -func TestContextMock_OutputPaths(t *testing.T) { - mock := ContextMock{ - &Context{ - outputPaths: []string{"/foo"}, - }, - } - - actual := mock.OutputPaths() - expect := []string{"/foo"} - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("expected %+v but got: %+v", expect, actual) - } -} - -func TestContextMock_SetLogger(t *testing.T) { - mock := ContextMock{&Context{}} - - expect := zap.NewNop() - mock.SetLogger(expect) - - actual := mock.logger - - if actual != expect { - t.Errorf("expected %v but got %v", expect, actual) - } -} - -func TestContextMock_SetEchoContext(t *testing.T) { - mock := ContextMock{&Context{}} - - expect := echo.New().NewContext(nil, nil) - mock.SetEchoContext(expect) - - actual := mock.echoCtx - - if actual != expect { - t.Errorf("expected %v but got %v", expect, actual) - } -} - -func TestContextMock_SetMkdirAll(t *testing.T) { - mock := ContextMock{&Context{}} - - expect := new(gotenberg.OsMkdirAll) - mock.SetMkdirAll(expect) - - actual := mock.mkdirAll - - if actual != expect { - t.Errorf("expected %v but got %v", expect, actual) - } -} - -func TestContextMock_SetPathRename(t *testing.T) { - mock := ContextMock{&Context{}} - - expect := new(gotenberg.OsPathRename) - mock.SetPathRename(expect) - - actual := mock.pathRename - - if actual != expect { - t.Errorf("expected %v but got %v", expect, actual) - } -} - -func TestRouterMock(t *testing.T) { - mock := &RouterMock{ - RoutesMock: func() ([]Route, error) { - return nil, nil - }, - } - - _, err := mock.Routes() - if err != nil { - t.Errorf("expected no error from RouterMock.Routes, but got: %v", err) - } -} - -func TestMiddlewareProviderMock(t *testing.T) { - mock := &MiddlewareProviderMock{ - MiddlewaresMock: func() ([]Middleware, error) { - return nil, nil - }, - } - - _, err := mock.Middlewares() - if err != nil { - t.Errorf("expected no error from MiddlewareProviderMock.Middlewares, but got: %v", err) - } -} - -func TestHealthCheckerMock(t *testing.T) { - mock := &HealthCheckerMock{ - ChecksMock: func() ([]health.CheckerOption, error) { - return nil, nil - }, - ReadyMock: func() error { - return nil - }, - } - - _, err := mock.Checks() - if err != nil { - t.Errorf("expected no error from HealthCheckerMock.Checks, but got: %v", err) - } - - err = mock.Ready() - if err != nil { - t.Errorf("expected no error from HealthCheckerMock.Ready, but got: %v", err) - } -} diff --git a/test/testdata/libreoffice/document.txt b/pkg/modules/api/testdata/sample.txt similarity index 100% rename from test/testdata/libreoffice/document.txt rename to pkg/modules/api/testdata/sample.txt diff --git a/pkg/modules/chromium/browser_test.go b/pkg/modules/chromium/browser_test.go deleted file mode 100644 index a46364743..000000000 --- a/pkg/modules/chromium/browser_test.go +++ /dev/null @@ -1,2484 +0,0 @@ -package chromium - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - "testing" - "time" - - "github.com/dlclark/regexp2" - "github.com/google/uuid" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "go.uber.org/zap/zaptest/observer" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestChromiumBrowser_Start(t *testing.T) { - for _, tc := range []struct { - scenario string - browser browser - expectError bool - cleanup bool - }{ - { - scenario: "successful start", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - }, - ), - expectError: false, - cleanup: true, - }, - { - scenario: "all browser arguments", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - incognito: true, - allowInsecureLocalhost: true, - ignoreCertificateErrors: true, - disableWebSecurity: true, - allowFileAccessFromFiles: true, - hostResolverRules: "MAP forgery.docker.localhost traefik", - proxyServer: "1.2.3.4", - }, - ), - expectError: false, - cleanup: true, - }, - { - scenario: "browser already started", - browser: func() browser { - b := new(chromiumBrowser) - b.isStarted.Store(true) - return b - }(), - expectError: true, - cleanup: false, - }, - { - scenario: "browser start error", - browser: func() browser { - b := newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - }, - ).(*chromiumBrowser) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - b.initialCtx = ctx - - return b - }(), - expectError: true, - cleanup: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logger := zap.NewNop() - err := tc.browser.Start(logger) - - if tc.cleanup { - defer func(b browser, logger *zap.Logger) { - err = b.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.browser, logger) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromiumBrowser_Stop(t *testing.T) { - for _, tc := range []struct { - scenario string - browser browser - setup func(browser browser, logger *zap.Logger) error - expectError bool - }{ - { - scenario: "successful stop", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - }, - ), - setup: func(b browser, logger *zap.Logger) error { - return b.Start(logger) - }, - expectError: false, - }, - { - scenario: "browser already stopped", - browser: func() browser { - b := new(chromiumBrowser) - b.isStarted.Store(false) - return b - }(), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logger := zap.NewNop() - - if tc.setup != nil { - err := tc.setup(tc.browser, logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - } - - err := tc.browser.Stop(logger) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromiumBrowser_Healthy(t *testing.T) { - for _, tc := range []struct { - scenario string - browser browser - setup func(browser browser, logger *zap.Logger) error - expectHealthy bool - cleanup bool - }{ - { - scenario: "healthy browser", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - }, - ), - setup: func(b browser, logger *zap.Logger) error { - return b.Start(logger) - }, - expectHealthy: true, - cleanup: true, - }, - { - scenario: "browser not started", - browser: func() browser { - b := new(chromiumBrowser) - b.isStarted.Store(false) - return b - }(), - expectHealthy: false, - cleanup: false, - }, - { - scenario: "unhealthy browser", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - }, - ), - setup: func(b browser, logger *zap.Logger) error { - _ = b.Start(logger) - b.(*chromiumBrowser).cancelFunc() - - return nil - }, - expectHealthy: false, - cleanup: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logger := zap.NewNop() - - if tc.setup != nil { - err := tc.setup(tc.browser, logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - } - - if tc.cleanup { - defer func(b browser, logger *zap.Logger) { - err := b.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.browser, logger) - } - - healthy := tc.browser.Healthy(logger) - - if !tc.expectHealthy && healthy { - t.Fatal("expected unhealthy browser but got an healthy one") - } - - if tc.expectHealthy && !healthy { - t.Fatal("expected a healthy browser but got an unhealthy one") - } - }) - } -} - -func TestChromiumBrowser_pdf(t *testing.T) { - for _, tc := range []struct { - scenario string - browser browser - fs *gotenberg.FileSystem - options PdfOptions - url string - noDeadline bool - start bool - expectError bool - expectedError error - expectedLogEntries []string - }{ - { - scenario: "browser not started", - browser: func() browser { - b := new(chromiumBrowser) - b.isStarted.Store(false) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: false, - start: false, - expectError: true, - }, - { - scenario: "context has no deadline", - browser: func() browser { - b := new(chromiumBrowser) - b.isStarted.Store(true) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: true, - start: false, - expectError: true, - }, - { - scenario: "ErrFiltered: main URL does not match the allowed list", - browser: func() browser { - b := new(chromiumBrowser) - b.arguments = browserArguments{ - allowList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0), - denyList: regexp2.MustCompile("", 0), - } - b.isStarted.Store(true) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: false, - start: false, - expectError: true, - expectedError: gotenberg.ErrFiltered, - }, - { - scenario: "ErrFiltered: main URL does match the denied list", - browser: func() browser { - b := new(chromiumBrowser) - b.arguments = browserArguments{ - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("^file:///tmp.*", 0), - } - b.isStarted.Store(true) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: false, - start: false, - expectError: true, - expectedError: gotenberg.ErrFiltered, - }, - { - scenario: "a request does not match the allowed list", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("^file:///tmp.*", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "'file:///etc/passwd' does not match the expression from the allowed list", - }, - }, - { - scenario: "a request does match the denied list", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "'file:///etc/passwd' matches the expression from the denied list", - }, - }, - { - scenario: "do not skip networkIdle event", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Skip networkIdle event

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{SkipNetworkIdleEvent: false}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "event networkIdle fired", - }, - }, - { - scenario: "ErrInvalidHttpStatusCode", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidHttpStatusCode

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{FailOnHttpStatusCodes: []int64{299}}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidHttpStatusCode, - }, - { - scenario: "ErrInvalidResourceHttpStatusCode", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/style.css", fs.WorkingDirPath()), []byte("body{font-family: Arial, Helvetica, sans-serif;}"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidResourceHttpStatusCode

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{FailOnResourceHttpStatusCodes: []int64{200}}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidResourceHttpStatusCode, - }, - { - scenario: "ErrConsoleExceptions", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{FailOnConsoleExceptions: true}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrConsoleExceptions, - }, - { - scenario: "ErrLoadingFailed", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - url: "http://localhost:100", - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrLoadingFailed, - }, - { - scenario: "ErrResourceLoadingFailed", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrResourceLoadingFailed

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{FailOnResourceLoadingFailed: true}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrResourceLoadingFailed, - }, - { - scenario: "clear cache", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - clearCache: true, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cache

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "clear cache", - }, - }, - { - scenario: "clear cookies", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - clearCookies: true, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cookies

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "clear cookies", - }, - }, - { - scenario: "disable JavaScript", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - disableJavaScript: true, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "disable JavaScript", - "JavaScript disabled, skipping wait delay", - "JavaScript disabled, skipping wait expression", - }, - }, - { - scenario: "set cookies", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Set cookies

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{Cookies: []Cookie{{Name: "foo", Value: "bar", Domain: ".foo.bar"}}}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "set cookie", - }, - }, - { - scenario: "user agent override", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

User-Agent override

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{UserAgent: "foo"}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - fmt.Sprintf("user agent override: foo"), - }, - }, - { - scenario: "extra HTTP headers", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Extra HTTP headers

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{ExtraHttpHeaders: []ExtraHttpHeader{ - { - Name: "X-Foo", - Value: "foo", - }, - { - Name: "X-Bar", - Value: "bar", - Scope: regexp2.MustCompile(`.*index\.html.*`, 0), - }, - { - Name: "X-Baz", - Value: "baz", - Scope: regexp2.MustCompile(`.*another\.html.*`, 0), - }, - }}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "extra HTTP headers:", - "extra HTTP header 'X-Foo' will be set for request URL", - "extra HTTP header 'X-Bar' (scoped) will be set for request URL", - "extra HTTP header 'X-Baz' (scoped) will not be set for request URL", - "setting extra HTTP headers for request URL", - }, - }, - { - scenario: "ErrOmitBackgroundWithoutPrintBackground", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrOmitBackgroundWithoutPrintBackground

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{OmitBackground: true}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrOmitBackgroundWithoutPrintBackground, - }, - { - scenario: "hide default white background", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Hide default white background

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{OmitBackground: true}, - PrintBackground: true, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "hide default white background", - }, - }, - { - scenario: "ErrInvalidEmulatedMediaType", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEmulatedMediaType

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{EmulatedMediaType: "foo"}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidEmulatedMediaType, - }, - { - scenario: "emulate a media type", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Screen media type

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{EmulatedMediaType: "screen"}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "emulate media type 'screen'", - }, - }, - { - scenario: "wait delay: context done", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{WaitDelay: time.Duration(10) * time.Second}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedLogEntries: []string{ - "wait '10s' before print", - }, - }, - { - scenario: "wait delay", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{WaitDelay: time.Duration(1) * time.Millisecond}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "wait '1ms' before print", - }, - }, - { - scenario: "wait for expression: context done", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - html := ` - -` - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(html), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{WaitForExpression: "window.status === 'ready'"}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedLogEntries: []string{ - "wait until 'window.status === 'ready'' is true before print", - }, - }, - { - scenario: "ErrInvalidEvaluationExpression", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEvaluationExpression

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{WaitForExpression: "return undefined"}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedLogEntries: []string{ - "wait until 'return undefined' is true before print", - }, - }, - { - scenario: "wait for expression", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - html := ` - -` - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(html), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - Options: Options{WaitForExpression: "window.globalVar === 'ready'"}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "wait until 'window.globalVar === 'ready'' is true before print", - }, - }, - { - scenario: "single page", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Custom header and footer

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - SinglePage: true, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "single page PDF", - }, - }, - { - scenario: "custom header and footer", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Custom header and footer

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - HeaderTemplate: "

Header

", - FooterTemplate: "

Footer

", - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "with custom header and/or footer", - }, - }, - { - scenario: "ErrInvalidPrinterSettings", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidPrinterSettings

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - PaperWidth: 0, - PaperHeight: 0, - MarginTop: 1000000, - MarginBottom: 1000000, - MarginLeft: 1000000, - MarginRight: 1000000, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidPrinterSettings, - }, - { - scenario: "ErrPageRangesSyntaxError", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrPageRangesSyntaxError

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: PdfOptions{ - PageRanges: "foo", - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrPageRangesSyntaxError, - }, - { - scenario: "success (default options)", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: DefaultPdfOptions(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "cache not cleared", - "cookies not cleared", - "JavaScript not disabled", - "no cookies to set", - "no extra HTTP headers", - "navigate to", - "skipping network idle event", - "default white background not hidden", - "no emulated media type", - "no wait delay", - "no wait expression", - "no custom header nor footer", - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - core, recorded := observer.New(zapcore.DebugLevel) - logger := zap.New(core) - - defer func() { - err := os.RemoveAll(tc.fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }() - - if tc.start { - err := tc.browser.Start(logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - - defer func(b browser, logger *zap.Logger) { - err = b.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.browser, logger) - } - - var ( - ctx context.Context - cancel context.CancelFunc - ) - - if tc.noDeadline { - ctx = context.Background() - } else { - ctx, cancel = context.WithTimeout(context.Background(), time.Duration(5)*time.Second) - defer cancel() - } - - url := fmt.Sprintf("file://%s/index.html", tc.fs.WorkingDirPath()) - if tc.url != "" { - url = tc.url - } - - err := tc.browser.pdf( - ctx, - logger, - url, - fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()), - tc.options, - ) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - for _, entry := range tc.expectedLogEntries { - doExist := true - for _, log := range recorded.All() { - doExist = strings.Contains(log.Message, entry) - if doExist { - break - } - } - - if !doExist { - t.Errorf("expected '%s' to exist as log entry", entry) - } - } - }) - } -} - -func TestChromiumBrowser_screenshot(t *testing.T) { - for _, tc := range []struct { - scenario string - browser browser - fs *gotenberg.FileSystem - options ScreenshotOptions - url string - noDeadline bool - start bool - expectError bool - expectedError error - expectedLogEntries []string - }{ - { - scenario: "browser not started", - browser: func() browser { - b := new(chromiumBrowser) - b.isStarted.Store(false) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: false, - start: false, - expectError: true, - }, - { - scenario: "context has not deadline", - browser: func() browser { - b := new(chromiumBrowser) - b.arguments = browserArguments{ - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - } - b.isStarted.Store(true) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: true, - start: false, - expectError: true, - }, - { - scenario: "ErrFiltered: main URL does not match the allowed list", - browser: func() browser { - b := new(chromiumBrowser) - b.arguments = browserArguments{ - allowList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0), - denyList: regexp2.MustCompile("", 0), - } - b.isStarted.Store(true) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: false, - start: false, - expectError: true, - expectedError: gotenberg.ErrFiltered, - }, - { - scenario: "ErrFiltered: main URL does match the denied list", - browser: func() browser { - b := new(chromiumBrowser) - b.arguments = browserArguments{ - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("^file:///tmp.*", 0), - } - b.isStarted.Store(true) - return b - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - noDeadline: false, - start: false, - expectError: true, - expectedError: gotenberg.ErrFiltered, - }, - { - scenario: "a request does not match the allowed list", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("^file:///tmp.*", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "'file:///etc/passwd' does not match the expression from the allowed list", - }, - }, - { - scenario: "a request does match the denied list", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "'file:///etc/passwd' matches the expression from the denied list", - }, - }, - { - scenario: "do not skip networkIdle event", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Skip networkIdle event

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{SkipNetworkIdleEvent: false}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "event networkIdle fired", - }, - }, - { - scenario: "ErrInvalidHttpStatusCode", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidHttpStatusCode

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{FailOnHttpStatusCodes: []int64{299}}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidHttpStatusCode, - }, - { - scenario: "ErrInvalidResourceHttpStatusCode", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/style.css", fs.WorkingDirPath()), []byte("body{font-family: Arial, Helvetica, sans-serif;}"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidResourceHttpStatusCode

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{FailOnResourceHttpStatusCodes: []int64{299}}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidResourceHttpStatusCode, - }, - { - scenario: "ErrConsoleExceptions", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{FailOnConsoleExceptions: true}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrConsoleExceptions, - }, - { - scenario: "ErrLoadingFailed", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - url: "http://localhost:100", - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrLoadingFailed, - }, - { - scenario: "ErrResourceLoadingFailed", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrResourceLoadingFailed

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{FailOnResourceLoadingFailed: true}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrResourceLoadingFailed, - }, - { - scenario: "clear cache", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - clearCache: true, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cache

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "clear cache", - }, - }, - { - scenario: "clear cookies", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - clearCookies: true, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Clear cookies

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "clear cookies", - }, - }, - { - scenario: "disable JavaScript", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - disableJavaScript: true, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "disable JavaScript", - "JavaScript disabled, skipping wait delay", - "JavaScript disabled, skipping wait expression", - }, - }, - { - scenario: "set cookies", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Set cookies

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{Cookies: []Cookie{{Name: "fpp", Value: "bar", Domain: ".foo.bar"}}}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "set cookie", - }, - }, - { - scenario: "user agent override", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

User-Agent override

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{UserAgent: "foo"}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - fmt.Sprintf("user agent override: foo"), - }, - }, - { - scenario: "extra HTTP headers", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Extra HTTP headers

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{ExtraHttpHeaders: []ExtraHttpHeader{ - { - Name: "X-Foo", - Value: "foo", - }, - { - Name: "X-Bar", - Value: "bar", - Scope: regexp2.MustCompile(`.*index\.html.*`, 0), - }, - { - Name: "X-Baz", - Value: "baz", - Scope: regexp2.MustCompile(`.*another\.html.*`, 0), - }, - }}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "extra HTTP headers:", - "extra HTTP header 'X-Foo' will be set for request URL", - "extra HTTP header 'X-Bar' (scoped) will be set for request URL", - "extra HTTP header 'X-Baz' (scoped) will not be set for request URL", - "setting extra HTTP headers for request URL", - }, - }, - { - scenario: "ErrOmitBackgroundWithoutPrintBackground", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrOmitBackgroundWithoutPrintBackground

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{OmitBackground: true}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "hide default white background", - }, - }, - { - scenario: "ErrInvalidEmulatedMediaType", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEmulatedMediaType

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{EmulatedMediaType: "foo"}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedError: ErrInvalidEmulatedMediaType, - }, - { - scenario: "emulate a media type", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Screen media type

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{EmulatedMediaType: "screen"}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "emulate media type 'screen'", - }, - }, - { - scenario: "wait delay: context done", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{WaitDelay: time.Duration(10) * time.Second}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedLogEntries: []string{ - "wait '10s' before print", - }, - }, - { - scenario: "wait delay", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(""), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{WaitDelay: time.Duration(1) * time.Millisecond}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "wait '1ms' before print", - }, - }, - { - scenario: "wait for expression: context done", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - html := ` - -` - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(html), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{WaitForExpression: "window.status === 'ready'"}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedLogEntries: []string{ - "wait until 'window.status === 'ready'' is true before print", - }, - }, - { - scenario: "ErrInvalidEvaluationExpression", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

ErrInvalidEvaluationExpression

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{WaitForExpression: "return undefined"}, - }, - noDeadline: false, - start: true, - expectError: true, - expectedLogEntries: []string{ - "wait until 'return undefined' is true before print", - }, - }, - { - scenario: "wait for expression", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - html := ` - -` - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte(html), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: ScreenshotOptions{ - Options: Options{WaitForExpression: "window.globalVar === 'ready'"}, - }, - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "wait until 'window.globalVar === 'ready'' is true before print", - }, - }, - { - scenario: "success (default options)", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: DefaultScreenshotOptions(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "cache not cleared", - "cookies not cleared", - "JavaScript not disabled", - "no user agent override", - "no extra HTTP headers", - "navigate to", - "default white background not hidden", - "no emulated media type", - "no wait delay", - "no wait expression", - "set device metrics override", - }, - }, - { - scenario: "success (clip)", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Clip = true - return options - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "cache not cleared", - "cookies not cleared", - "JavaScript not disabled", - "no cookies to set", - "no user agent override", - "no extra HTTP headers", - "navigate to", - "default white background not hidden", - "no emulated media type", - "no wait delay", - "no wait expression", - "set device metrics override", - }, - }, - { - scenario: "success (jpeg)", - browser: newChromiumBrowser( - browserArguments{ - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - wsUrlReadTimeout: 5 * time.Second, - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("

Default options

"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Format = "jpeg" - return options - }(), - noDeadline: false, - start: true, - expectError: false, - expectedLogEntries: []string{ - "cache not cleared", - "cookies not cleared", - "JavaScript not disabled", - "no cookies to set", - "no user agent override", - "no extra HTTP headers", - "navigate to", - "skipping network idle event", - "default white background not hidden", - "no emulated media type", - "no wait delay", - "no wait expression", - "set device metrics override", - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - core, recorded := observer.New(zapcore.DebugLevel) - logger := zap.New(core) - - defer func() { - err := os.RemoveAll(tc.fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }() - - if tc.start { - err := tc.browser.Start(logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - - defer func(b browser, logger *zap.Logger) { - err = b.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.browser, logger) - } - - var ( - ctx context.Context - cancel context.CancelFunc - ) - - if tc.noDeadline { - ctx = context.Background() - } else { - ctx, cancel = context.WithTimeout(context.Background(), time.Duration(5)*time.Second) - defer cancel() - } - - url := fmt.Sprintf("file://%s/index.html", tc.fs.WorkingDirPath()) - if tc.url != "" { - url = tc.url - } - - err := tc.browser.screenshot( - ctx, - logger, - url, - fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()), - tc.options, - ) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - for _, entry := range tc.expectedLogEntries { - doExist := true - for _, log := range recorded.All() { - doExist = strings.Contains(log.Message, entry) - if doExist { - break - } - } - - if !doExist { - t.Errorf("expected '%s' to exist as log entry", entry) - } - } - }) - } -} diff --git a/pkg/modules/chromium/chromium_test.go b/pkg/modules/chromium/chromium_test.go deleted file mode 100644 index 720bc9433..000000000 --- a/pkg/modules/chromium/chromium_test.go +++ /dev/null @@ -1,637 +0,0 @@ -package chromium - -import ( - "context" - "errors" - "os" - "reflect" - "testing" - "time" - - "github.com/alexliesenfeld/health" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestDefaultOptions(t *testing.T) { - actual := DefaultOptions() - notExpect := Options{} - - if reflect.DeepEqual(actual, notExpect) { - t.Errorf("expected %v and got identical %v", actual, notExpect) - } -} - -func TestDefaultPdfOptions(t *testing.T) { - actual := DefaultPdfOptions() - notExpect := PdfOptions{} - - if reflect.DeepEqual(actual, notExpect) { - t.Errorf("expected %v and got identical %v", actual, notExpect) - } -} - -func TestDefaultScreenshotOptions(t *testing.T) { - actual := DefaultScreenshotOptions() - notExpect := ScreenshotOptions{} - - if reflect.DeepEqual(actual, notExpect) { - t.Errorf("expected %v and got identical %v", actual, notExpect) - } -} - -func TestChromium_Descriptor(t *testing.T) { - descriptor := new(Chromium).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(Chromium)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestChromium_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectError bool - }{ - { - scenario: "no logger provider", - ctx: func() *gotenberg.Context { - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Chromium).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{}, - ) - }(), - expectError: true, - }, - { - scenario: "no logger from logger provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return nil, errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Chromium).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no PDF engine provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return zap.NewNop(), nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Chromium).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no PDF engine from PDF engine provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - gotenberg.PdfEngineProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return zap.NewNop(), nil - } - mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) { - return nil, errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Chromium).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "provision success", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - gotenberg.PdfEngineProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return zap.NewNop(), nil - } - mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) { - return new(gotenberg.PdfEngineMock), nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Chromium).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - err := mod.Provision(tc.ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromium_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - binPath string - expectError bool - }{ - { - scenario: "empty bin path", - binPath: "", - expectError: true, - }, - { - scenario: "bin path does not exist", - binPath: "/foo", - expectError: true, - }, - { - scenario: "validate success", - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.args = browserArguments{ - binPath: tc.binPath, - } - err := mod.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromium_Start(t *testing.T) { - for _, tc := range []struct { - scenario string - autoStart bool - supervisor *gotenberg.ProcessSupervisorMock - expectError bool - }{ - { - scenario: "no auto-start", - autoStart: false, - expectError: false, - }, - { - scenario: "auto-start success", - autoStart: true, - supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error { - return nil - }}, - expectError: false, - }, - { - scenario: "auto-start failed", - autoStart: true, - supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error { - return errors.New("foo") - }}, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.autoStart = tc.autoStart - mod.supervisor = tc.supervisor - - err := mod.Start() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromium_StartupMessage(t *testing.T) { - mod := new(Chromium) - - mod.autoStart = true - autoStartMsg := mod.StartupMessage() - - mod.autoStart = false - noAutoStartMsg := mod.StartupMessage() - - if autoStartMsg == noAutoStartMsg { - t.Errorf("expected differrent startup messages based on auto start, but got '%s'", autoStartMsg) - } -} - -func TestChromium_Stop(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor *gotenberg.ProcessSupervisorMock - expectError bool - }{ - { - scenario: "stop success", - supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error { - return nil - }}, - expectError: false, - }, - { - scenario: "stop failed", - supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error { - return errors.New("foo") - }}, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.logger = zap.NewNop() - mod.supervisor = tc.supervisor - - ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second) - cancel() - - err := mod.Stop(ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromium_Debug(t *testing.T) { - for _, tc := range []struct { - scenario string - mod *Chromium - expect map[string]interface{} - doNotExpect map[string]interface{} - }{ - { - scenario: "cannot determine version", - mod: &Chromium{ - args: browserArguments{ - binPath: "foo", - }, - }, - expect: map[string]interface{}{ - "version": `exec: "foo": executable file not found in $PATH`, - }, - }, - { - scenario: "success", - mod: &Chromium{ - args: browserArguments{ - binPath: "echo", - }, - }, - doNotExpect: map[string]interface{}{ - "version": `exec: "echo": executable file not found in $PATH`, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - d := tc.mod.Debug() - - if tc.expect != nil { - if !reflect.DeepEqual(d, tc.expect) { - t.Errorf("expected '%v' but got '%v'", tc.expect, d) - } - } - - if tc.doNotExpect != nil { - if reflect.DeepEqual(d, tc.doNotExpect) { - t.Errorf("did not expect '%v'", d) - } - } - }) - } -} - -func TestChromium_Metrics(t *testing.T) { - mod := new(Chromium) - mod.supervisor = &gotenberg.ProcessSupervisorMock{ - ReqQueueSizeMock: func() int64 { - return 10 - }, - RestartsCountMock: func() int64 { - return 0 - }, - } - - metrics, err := mod.Metrics() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if len(metrics) != 2 { - t.Fatalf("expected %d metrics, but got %d", 2, len(metrics)) - } - - actual := metrics[0].Read() - if actual != float64(10) { - t.Errorf("expected %f for chromium_requests_queue_size, but got %f", float64(10), actual) - } - - actual = metrics[1].Read() - if actual != float64(0) { - t.Errorf("expected %f for chromium_restarts_count, but got %f", float64(0), actual) - } -} - -func TestChromium_Checks(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor gotenberg.ProcessSupervisor - expectAvailabilityStatus health.AvailabilityStatus - }{ - { - scenario: "healthy module", - supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool { - return true - }}, - expectAvailabilityStatus: health.StatusUp, - }, - { - scenario: "unhealthy module", - supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool { - return false - }}, - expectAvailabilityStatus: health.StatusDown, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.supervisor = tc.supervisor - - checks, err := mod.Checks() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - checker := health.NewChecker(checks...) - result := checker.Check(context.Background()) - - if result.Status != tc.expectAvailabilityStatus { - t.Errorf("expected '%s' as availability status, but got '%s'", tc.expectAvailabilityStatus, result.Status) - } - }) - } -} - -func TestChromium_Ready(t *testing.T) { - for _, tc := range []struct { - scenario string - autoStart bool - startTimeout time.Duration - browser browser - expectError bool - }{ - { - scenario: "no auto-start", - autoStart: false, - startTimeout: time.Duration(30) * time.Second, - browser: &browserMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool { - return false - }}}, - expectError: false, - }, - { - scenario: "auto-start: context done", - autoStart: true, - startTimeout: time.Duration(200) * time.Millisecond, - browser: &browserMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool { - return false - }}}, - expectError: true, - }, - { - scenario: "auto-start success", - autoStart: true, - startTimeout: time.Duration(30) * time.Second, - browser: &browserMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool { - return true - }}}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.autoStart = tc.autoStart - mod.args = browserArguments{wsUrlReadTimeout: tc.startTimeout} - mod.browser = tc.browser - - err := mod.Ready() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromium_Chromium(t *testing.T) { - mod := new(Chromium) - - _, err := mod.Chromium() - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestChromium_Routes(t *testing.T) { - for _, tc := range []struct { - scenario string - expectRoutes int - disableRoutes bool - }{ - { - scenario: "routes not disabled", - expectRoutes: 6, - disableRoutes: false, - }, - { - scenario: "routes disabled", - expectRoutes: 0, - disableRoutes: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.disableRoutes = tc.disableRoutes - - routes, err := mod.Routes() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectRoutes != len(routes) { - t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes)) - } - }) - } -} - -func TestChromium_Pdf(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor gotenberg.ProcessSupervisor - browser browser - expectError bool - }{ - { - scenario: "PDF task success", - browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - expectError: false, - }, - { - scenario: "PDF task error", - browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return errors.New("PDF task error") - }}, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error { - return task() - }} - mod.browser = tc.browser - - err := mod.Pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{}) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestChromium_Screenshot(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor gotenberg.ProcessSupervisor - browser browser - expectError bool - }{ - { - scenario: "Screenshot task success", - browser: &browserMock{screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }}, - expectError: false, - }, - { - scenario: "Screenshot task error", - browser: &browserMock{screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return errors.New("screenshot task error") - }}, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Chromium) - mod.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error { - return task() - }} - mod.browser = tc.browser - - err := mod.Screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{}) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} diff --git a/pkg/modules/chromium/debug_test.go b/pkg/modules/chromium/debug_test.go deleted file mode 100644 index 579f84db3..000000000 --- a/pkg/modules/chromium/debug_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package chromium - -import ( - "testing" - - "go.uber.org/zap" -) - -func TestDebugLogger_Write(t *testing.T) { - actual, err := (&debugLogger{logger: zap.NewNop()}).Write([]byte("foo")) - expected := len([]byte("foo")) - - if actual != expected { - t.Errorf("expected %d but got %d", expected, actual) - } - - if err != nil { - t.Errorf("expected not error but got: %v", err) - } -} - -func TestDebugLogger_Printf(t *testing.T) { - (&debugLogger{logger: zap.NewNop()}).Printf("%s", "foo") -} diff --git a/pkg/modules/chromium/mocks_test.go b/pkg/modules/chromium/mocks_test.go deleted file mode 100644 index 1f361a8aa..000000000 --- a/pkg/modules/chromium/mocks_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package chromium - -import ( - "context" - "testing" - - "go.uber.org/zap" -) - -func TestApiMock(t *testing.T) { - mock := &ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }, - ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }, - } - - err := mock.Pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{}) - if err != nil { - t.Errorf("expected no error from ApiMock.Pdf, but got: %v", err) - } - - err = mock.Screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{}) - if err != nil { - t.Errorf("expected no error from ApiMock.Screenshot, but got: %v", err) - } -} - -func TestBrowserMock(t *testing.T) { - mock := &browserMock{ - pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }, - screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }, - } - - err := mock.pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{}) - if err != nil { - t.Errorf("expected no error from browserMock.pdf, but got: %v", err) - } - - err = mock.screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{}) - if err != nil { - t.Errorf("expected no error from browserMock.screenshot, but got: %v", err) - } -} diff --git a/pkg/modules/chromium/routes_test.go b/pkg/modules/chromium/routes_test.go deleted file mode 100644 index 552e55ca9..000000000 --- a/pkg/modules/chromium/routes_test.go +++ /dev/null @@ -1,1927 +0,0 @@ -package chromium - -import ( - "context" - "errors" - "fmt" - "net/http" - "os" - "reflect" - "sort" - "testing" - - "github.com/dlclark/regexp2" - "github.com/google/uuid" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" - "github.com/gotenberg/gotenberg/v8/pkg/modules/api" -) - -func TestFormDataChromiumOptions(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - expectedOptions Options - compareWithoutDeepEqual bool - expectValidationError bool - }{ - { - scenario: "no custom form fields", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectedOptions: DefaultOptions(), - compareWithoutDeepEqual: false, - expectValidationError: false, - }, - { - scenario: "invalid failOnHttpStatusCodes form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "failOnHttpStatusCodes": { - "foo", - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.FailOnHttpStatusCodes = nil - return options - }(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "valid failOnHttpStatusCodes form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "failOnHttpStatusCodes": { - `[399,499,599]`, - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.FailOnHttpStatusCodes = []int64{399, 499, 599} - return options - }(), - compareWithoutDeepEqual: false, - expectValidationError: false, - }, - { - scenario: "invalid failOnResourceHttpStatusCodes form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "failOnResourceHttpStatusCodes": { - "foo", - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.FailOnResourceHttpStatusCodes = nil - return options - }(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "valid failOnResourceHttpStatusCodes form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "failOnResourceHttpStatusCodes": { - `[399,499,599]`, - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.FailOnResourceHttpStatusCodes = []int64{399, 499, 599} - return options - }(), - compareWithoutDeepEqual: false, - expectValidationError: false, - }, - { - scenario: "invalid cookies form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "cookies": { - "foo", - }, - }) - return ctx - }(), - expectedOptions: DefaultOptions(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "invalid cookies form field (missing required values)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "cookies": { - "[{}]", - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - // No validation in this method, so it still instantiates - // an empty item. - options.Cookies = []Cookie{{}} - return options - }(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "valid cookies form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "cookies": { - `[{"name":"foo","value":"bar","domain":".foo.bar"}]`, - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.Cookies = []Cookie{{ - Name: "foo", - Value: "bar", - Domain: ".foo.bar", - }} - return options - }(), - compareWithoutDeepEqual: false, - expectValidationError: false, - }, - { - scenario: "invalid extraHttpHeaders form field: cannot unmarshall", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "extraHttpHeaders": { - "foo", - }, - }) - return ctx - }(), - expectedOptions: DefaultOptions(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "invalid extraHttpHeaders form field: invalid scope", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "extraHttpHeaders": { - `{"foo":"bar;scope;;"}`, - }, - }) - return ctx - }(), - expectedOptions: DefaultOptions(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "invalid extraHttpHeaders form field: invalid scope regex pattern", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "extraHttpHeaders": { - `{"foo":"bar;scope=*."}`, - }, - }) - return ctx - }(), - expectedOptions: DefaultOptions(), - compareWithoutDeepEqual: false, - expectValidationError: true, - }, - { - scenario: "valid extraHttpHeaders form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "extraHttpHeaders": { - `{"foo":"bar","baz":"qux;scope=https?:\\/\\/([a-zA-Z0-9-]+\\.)*qux\\.com\\/.*"}`, - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.ExtraHttpHeaders = []ExtraHttpHeader{ - { - Name: "foo", - Value: "bar", - }, - { - Name: "baz", - Value: "qux", - Scope: regexp2.MustCompile(`https?:\/\/([a-zA-Z0-9-]+\.)*qux\.com\/.*`, 0), - }, - } - return options - }(), - compareWithoutDeepEqual: true, - expectValidationError: false, - }, - { - scenario: "invalid emulatedMediaType form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "emulatedMediaType": { - "foo", - }, - }) - return ctx - }(), - expectedOptions: DefaultOptions(), - expectValidationError: true, - }, - { - scenario: "valid emulatedMediaType form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "emulatedMediaType": { - "screen", - }, - }) - return ctx - }(), - expectedOptions: func() Options { - options := DefaultOptions() - options.EmulatedMediaType = "screen" - return options - }(), - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form, actual := FormDataChromiumOptions(tc.ctx.Context) - - if tc.compareWithoutDeepEqual { - if len(tc.expectedOptions.ExtraHttpHeaders) != len(actual.ExtraHttpHeaders) { - t.Fatalf("expected %d extra HTTP headers, but got %d", len(tc.expectedOptions.ExtraHttpHeaders), len(actual.ExtraHttpHeaders)) - } - - sort.Slice(tc.expectedOptions.ExtraHttpHeaders, func(i, j int) bool { - return tc.expectedOptions.ExtraHttpHeaders[i].Name < tc.expectedOptions.ExtraHttpHeaders[j].Name - }) - sort.Slice(actual.ExtraHttpHeaders, func(i, j int) bool { - return actual.ExtraHttpHeaders[i].Name < actual.ExtraHttpHeaders[j].Name - }) - - for i := range tc.expectedOptions.ExtraHttpHeaders { - if tc.expectedOptions.ExtraHttpHeaders[i].Name != actual.ExtraHttpHeaders[i].Name { - t.Fatalf("expected '%s' extra HTTP header, but got '%s'", tc.expectedOptions.ExtraHttpHeaders[i].Name, tc.expectedOptions.ExtraHttpHeaders[i].Name) - } - - if tc.expectedOptions.ExtraHttpHeaders[i].Value != actual.ExtraHttpHeaders[i].Value { - t.Fatalf("expected '%s' as value for extra HTTP header '%s', but got '%s'", tc.expectedOptions.ExtraHttpHeaders[i].Value, tc.expectedOptions.ExtraHttpHeaders[i].Name, actual.ExtraHttpHeaders[i].Value) - } - - var expectedScope string - if tc.expectedOptions.ExtraHttpHeaders[i].Scope != nil { - expectedScope = tc.expectedOptions.ExtraHttpHeaders[i].Scope.String() - } - var actualScope string - if actual.ExtraHttpHeaders[i].Scope != nil { - actualScope = actual.ExtraHttpHeaders[i].Scope.String() - } - - if expectedScope != actualScope { - t.Fatalf("expected '%s' as scope for extra HTTP header '%s', but got '%s'", expectedScope, tc.expectedOptions.ExtraHttpHeaders[i].Name, actualScope) - } - } - } else { - if !reflect.DeepEqual(actual, tc.expectedOptions) { - t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual) - } - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestFormDataChromiumPdfOptions(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - expectedOptions PdfOptions - expectValidationError bool - }{ - { - scenario: "no custom form fields", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectedOptions: DefaultPdfOptions(), - expectValidationError: false, - }, - { - scenario: "custom form fields (Options & PdfOptions)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "landscape": { - "true", - }, - "emulatedMediaType": { - "screen", - }, - }) - return ctx - }(), - expectedOptions: func() PdfOptions { - options := DefaultPdfOptions() - options.Landscape = true - options.EmulatedMediaType = "screen" - return options - }(), - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form, actual := FormDataChromiumPdfOptions(tc.ctx.Context) - - if !reflect.DeepEqual(actual, tc.expectedOptions) { - t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestFormDataChromiumScreenshotOptions(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - expectedOptions ScreenshotOptions - expectValidationError bool - }{ - { - scenario: "no custom form fields", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectedOptions: DefaultScreenshotOptions(), - expectValidationError: false, - }, - { - scenario: "invalid format form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "format": { - "gif", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Format = "" - return options - }(), - expectValidationError: true, - }, - { - scenario: "valid png format form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "format": { - "png", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Format = "png" - return options - }(), - expectValidationError: false, - }, - { - scenario: "valid jpeg format form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "format": { - "jpeg", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Format = "jpeg" - return options - }(), - expectValidationError: false, - }, - { - scenario: "valid webp format form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "format": { - "webp", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Format = "webp" - return options - }(), - expectValidationError: false, - }, - { - scenario: "invalid quality form field (not an integer)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "quality": { - "foo", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Quality = 0 - return options - }(), - expectValidationError: true, - }, - { - scenario: "invalid quality form field (< 0)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "quality": { - "-1", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Quality = 0 - return options - }(), - expectValidationError: true, - }, - { - scenario: "invalid quality form field (> 100)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "quality": { - "101", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Quality = 0 - return options - }(), - expectValidationError: true, - }, - { - scenario: "valid quality form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "quality": { - "50", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Quality = 50 - return options - }(), - expectValidationError: false, - }, - { - scenario: "custom form fields (Options & ScreenshotOptions)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "width": { - "1280", - }, - "height": { - "800", - }, - "clip": { - "true", - }, - "optimizeForSpeed": { - "true", - }, - "emulatedMediaType": { - "screen", - }, - }) - return ctx - }(), - expectedOptions: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.Width = 1280 - options.Height = 800 - options.Clip = true - options.OptimizeForSpeed = true - options.EmulatedMediaType = "screen" - return options - }(), - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form, actual := FormDataChromiumScreenshotOptions(tc.ctx.Context) - - if !reflect.DeepEqual(actual, tc.expectedOptions) { - t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestConvertUrlRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing mandatory url form field", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "empty url form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "url": { - "", - }, - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "url": { - "foo", - }, - }) - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return errors.New("foo") - }}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "url": { - "foo", - }, - }) - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := convertUrlRoute(tc.api, nil).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestScreenshotUrlRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing mandatory url form field", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "empty url form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "url": { - "", - }, - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "url": { - "foo", - }, - }) - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return errors.New("foo") - }}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "url": { - "foo", - }, - }) - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := screenshotUrlRoute(tc.api).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestConvertHtmlRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing mandatory index.html form file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "index.html": "/index.html", - }) - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return errors.New("foo") - }}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "index.html": "/index.html", - }) - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := convertHtmlRoute(tc.api, nil).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestScreenshotHtmlRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing mandatory index.html form file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "index.html": "/index.html", - }) - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return errors.New("foo") - }}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "index.html": "/index.html", - }) - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := screenshotHtmlRoute(tc.api).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestConvertMarkdownRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing mandatory index.html form file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "missing mandatory markdown form files", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "index.html": "/index.html", - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "markdown file requested in index.html not found", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "wrong_name.md": fmt.Sprintf("%s/wrong_name.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "non-existing markdown file", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "markdown.md": fmt.Sprintf("%s/markdown.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "markdown.md": fmt.Sprintf("%s/markdown.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/markdown.md", dirPath), []byte("# Hello World!"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return errors.New("foo") - }}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "markdown.md": fmt.Sprintf("%s/markdown.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/markdown.md", dirPath), []byte("# Hello World!"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - if tc.ctx.DirPath() != "" { - defer func() { - err := os.RemoveAll(tc.ctx.DirPath()) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - } - - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := convertMarkdownRoute(tc.api, nil).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestScreenshotMarkdownRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing mandatory index.html form file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "missing mandatory markdown form files", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "index.html": "/index.html", - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "markdown file requested in index.html not found", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "wrong_name.md": fmt.Sprintf("%s/wrong_name.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "non-existing markdown file", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "markdown.md": fmt.Sprintf("%s/markdown.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "markdown.md": fmt.Sprintf("%s/markdown.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/markdown.md", dirPath), []byte("# Hello World!"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return errors.New("foo") - }}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetDirPath(dirPath) - ctx.SetFiles(map[string]string{ - "index.html": fmt.Sprintf("%s/index.html", dirPath), - "markdown.md": fmt.Sprintf("%s/markdown.md", dirPath), - }) - - err := os.MkdirAll(dirPath, 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/index.html", dirPath), []byte("
{{ toHTML \"markdown.md\" }}
"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/markdown.md", dirPath), []byte("# Hello World!"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - if tc.ctx.DirPath() != "" { - defer func() { - err := os.RemoveAll(tc.ctx.DirPath()) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - } - - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := screenshotMarkdownRoute(tc.api).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestConvertUrl(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - engine gotenberg.PdfEngine - options PdfOptions - splitMode gotenberg.SplitMode - pdfFormats gotenberg.PdfFormats - metadata map[string]interface{} - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "ErrOmitBackgroundWithoutPrintBackground", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrOmitBackgroundWithoutPrintBackground - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidEvaluationExpression (without waitForExpression form field)", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrInvalidEvaluationExpression - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidEvaluationExpression (with waitForExpression form field)", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrInvalidEvaluationExpression - }}, - options: func() PdfOptions { - options := DefaultPdfOptions() - options.WaitForExpression = "foo" - - return options - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidPrinterSettings", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrInvalidPrinterSettings - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrPageRangesSyntaxError", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrPageRangesSyntaxError - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidHttpStatusCode", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrInvalidHttpStatusCode - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidResourceHttpStatusCode", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrInvalidResourceHttpStatusCode - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrConsoleExceptions", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrConsoleExceptions - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrLoadingFailed", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrLoadingFailed - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrResourceLoadingFailed", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return ErrResourceLoadingFailed - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return errors.New("foo") - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "PDF engine split error", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return nil, errors.New("foo") - }}, - options: DefaultPdfOptions(), - splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success with split mode", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }}, - options: DefaultPdfOptions(), - splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "PDF engine convert error", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }}, - options: DefaultPdfOptions(), - pdfFormats: gotenberg.PdfFormats{PdfA: "foo"}, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success with PDF formats", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }}, - options: DefaultPdfOptions(), - pdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "success with split mode and PDF formats", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - options: DefaultPdfOptions(), - splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, - pdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b}, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "PDF engine write metadata error", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") - }}, - options: DefaultPdfOptions(), - metadata: map[string]interface{}{ - "Creator": "foo", - "Producer": "bar", - }, - expectError: true, - expectHttpError: false, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetCancelled(true) - return ctx - }(), - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - options: DefaultPdfOptions(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { - return nil - }}, - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - }, - options: DefaultPdfOptions(), - pdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b}, - metadata: map[string]interface{}{ - "Creator": "foo", - "Producer": "bar", - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - err := convertUrl(tc.ctx.Context, tc.api, tc.engine, "", tc.options, tc.splitMode, tc.pdfFormats, tc.metadata) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestScreenshotUrl(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - api Api - options ScreenshotOptions - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "ErrInvalidEvaluationExpression (without waitForExpression form field)", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrInvalidEvaluationExpression - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidEvaluationExpression (with waitForExpression form field)", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrInvalidEvaluationExpression - }}, - options: func() ScreenshotOptions { - options := DefaultScreenshotOptions() - options.WaitForExpression = "foo" - - return options - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidHttpStatusCode", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrInvalidHttpStatusCode - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrInvalidResourceHttpStatusCode", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrInvalidResourceHttpStatusCode - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrConsoleExceptions", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrConsoleExceptions - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrLoadingFailed", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrLoadingFailed - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrResourceLoadingFailed", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return ErrResourceLoadingFailed - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusConflict, - expectOutputPathsCount: 0, - }, - { - scenario: "error from Chromium", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return errors.New("foo") - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetCancelled(true) - return ctx - }(), - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }}, - options: DefaultScreenshotOptions(), - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: &api.ContextMock{Context: new(api.Context)}, - api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { - return nil - }}, - options: DefaultScreenshotOptions(), - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - err := screenshotUrl(tc.ctx.Context, tc.api, "", tc.options) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} diff --git a/pkg/modules/exiftool/exiftool_test.go b/pkg/modules/exiftool/exiftool_test.go deleted file mode 100644 index 87576b697..000000000 --- a/pkg/modules/exiftool/exiftool_test.go +++ /dev/null @@ -1,415 +0,0 @@ -package exiftool - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "reflect" - "testing" - - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestExifTool_Descriptor(t *testing.T) { - descriptor := new(ExifTool).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(ExifTool)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestExifTool_Provision(t *testing.T) { - engine := new(ExifTool) - ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil) - - err := engine.Provision(ctx) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestExifTool_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - binPath string - expectError bool - }{ - { - scenario: "empty bin path", - binPath: "", - expectError: true, - }, - { - scenario: "bin path does not exist", - binPath: "/foo", - expectError: true, - }, - { - scenario: "validate success", - binPath: os.Getenv("EXIFTOOL_BIN_PATH"), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(ExifTool) - engine.binPath = tc.binPath - err := engine.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestExifTool_Debug(t *testing.T) { - for _, tc := range []struct { - scenario string - engine *ExifTool - expect map[string]interface{} - doNotExpect map[string]interface{} - }{ - { - scenario: "cannot determine version", - engine: &ExifTool{ - binPath: "foo", - }, - expect: map[string]interface{}{ - "version": `exec: "foo": executable file not found in $PATH`, - }, - }, - { - scenario: "success", - engine: &ExifTool{ - binPath: "echo", - }, - doNotExpect: map[string]interface{}{ - "version": `exec: "echo": executable file not found in $PATH`, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - d := tc.engine.Debug() - - if tc.expect != nil { - if !reflect.DeepEqual(d, tc.expect) { - t.Errorf("expected '%v' but got '%v'", tc.expect, d) - } - } - - if tc.doNotExpect != nil { - if reflect.DeepEqual(d, tc.doNotExpect) { - t.Errorf("did not expect '%v'", d) - } - } - }) - } -} - -func TestExiftool_Merge(t *testing.T) { - engine := new(ExifTool) - err := engine.Merge(context.Background(), zap.NewNop(), nil, "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestExiftool_Split(t *testing.T) { - engine := new(ExifTool) - _, err := engine.Split(context.Background(), zap.NewNop(), gotenberg.SplitMode{}, "", "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestExiftool_Flatten(t *testing.T) { - engine := new(ExifTool) - err := engine.Flatten(context.Background(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestExiftool_Convert(t *testing.T) { - engine := new(ExifTool) - err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestExiftool_ReadMetadata(t *testing.T) { - for _, tc := range []struct { - scenario string - inputPath string - expectMetadata map[string]interface{} - expectError bool - }{ - { - scenario: "invalid input path", - inputPath: "foo", - expectMetadata: nil, - expectError: true, - }, - { - scenario: "success", - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - expectMetadata: map[string]interface{}{ - "FileName": "sample1.pdf", - "FileTypeExtension": "pdf", - "MIMEType": "application/pdf", - "PDFVersion": 1.4, - "PageCount": float64(3), - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(ExifTool) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - metadata, err := engine.ReadMetadata(context.Background(), zap.NewNop(), tc.inputPath) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectMetadata != nil && err == nil { - for k, v := range tc.expectMetadata { - if v2, ok := metadata[k]; !ok || v != v2 { - t.Errorf("expected entry %s with value %v to exists", k, v) - } - } - } - }) - } -} - -func TestExiftool_WriteMetadata(t *testing.T) { - for _, tc := range []struct { - scenario string - createCopy bool - inputPath string - metadata map[string]interface{} - expectMetadata map[string]interface{} - expectError bool - expectedError error - }{ - { - scenario: "invalid input path", - createCopy: false, - inputPath: "foo", - expectError: true, - }, - { - scenario: "gotenberg.ErrPdfEngineMetadataValueNotSupported (not string array)", - createCopy: true, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - metadata: map[string]interface{}{ - "Unsupported": []interface{}{ - "foo", - 1, - }, - }, - expectError: true, - expectedError: gotenberg.ErrPdfEngineMetadataValueNotSupported, - }, - { - scenario: "gotenberg.ErrPdfEngineMetadataValueNotSupported (default)", - createCopy: true, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - metadata: map[string]interface{}{ - "Unsupported": map[string]interface{}{}, - }, - expectError: true, - expectedError: gotenberg.ErrPdfEngineMetadataValueNotSupported, - }, - { - scenario: "success (interface array to string array)", - createCopy: true, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - metadata: map[string]interface{}{ - "Keywords": []interface{}{ - "first", - "second", - }, - }, - expectMetadata: map[string]interface{}{ - "Keywords": []interface{}{ - "first", - "second", - }, - }, - expectError: false, - }, - { - scenario: "success", - createCopy: true, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - metadata: map[string]interface{}{ - "Author": "Julien Neuhart", - "Copyright": "Julien Neuhart", - "CreationDate": "2006-09-18T16:27:50-04:00", - "Creator": "Gotenberg", - "Keywords": []string{ - "first", - "second", - }, - "Marked": true, - "ModDate": "2006-09-18T16:27:50-04:00", - "PDFVersion": 1.7, - "Producer": "Gotenberg", - "Subject": "Sample", - "Title": "Sample", - "Trapped": "Unknown", - // Those are not valid PDF metadata. - "int": 1, - "int64": int64(2), - "float32": float32(2.2), - "float64": 3.3, - }, - expectMetadata: map[string]interface{}{ - "Author": "Julien Neuhart", - "Copyright": "Julien Neuhart", - "CreationDate": "2006:09:18 16:27:50-04:00", - "Creator": "Gotenberg", - "Keywords": []interface{}{ - "first", - "second", - }, - "Marked": true, - "ModDate": "2006:09:18 16:27:50-04:00", - "PDFVersion": 1.7, - "Producer": "Gotenberg", - "Subject": "Sample", - "Title": "Sample", - "Trapped": "Unknown", - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(ExifTool) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var destinationPath string - if tc.createCopy { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error no but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - destinationPath = fmt.Sprintf("%s/copy_temp.pdf", outputDir) - source, err := os.Open(tc.inputPath) - if err != nil { - t.Fatalf("open source file: %v", err) - } - - defer func(source *os.File) { - err := source.Close() - if err != nil { - t.Fatalf("close file: %v", err) - } - }(source) - - destination, err := os.Create(destinationPath) - if err != nil { - t.Fatalf("create destination file: %v", err) - } - - defer func(destination *os.File) { - err := destination.Close() - if err != nil { - t.Fatalf("close file: %v", err) - } - }(destination) - - _, err = io.Copy(destination, source) - if err != nil { - t.Fatalf("copy source into destination: %v", err) - } - } else { - destinationPath = tc.inputPath - } - - err = engine.WriteMetadata(context.Background(), zap.NewNop(), tc.metadata, destinationPath) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - if tc.expectError { - return - } - - metadata, err := engine.ReadMetadata(context.Background(), zap.NewNop(), destinationPath) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectMetadata != nil && err == nil { - for k, v := range tc.expectMetadata { - v2, ok := metadata[k] - if !ok { - t.Errorf("expected entry %s with value %v to exists, but got none", k, v) - continue - } - - switch v2.(type) { - case []interface{}: - for i, entry := range v.([]interface{}) { - if entry != v2.([]interface{})[i] { - t.Errorf("expected entry %s to contain value %v, but got %v", k, entry, v2.([]interface{})[i]) - } - } - default: - if v != v2 { - t.Errorf("expected entry %s with value %v to exists, but got %v", k, v, v2) - } - } - } - } - }) - } -} diff --git a/pkg/modules/libreoffice/api/api_test.go b/pkg/modules/libreoffice/api/api_test.go deleted file mode 100644 index 7df16d9ca..000000000 --- a/pkg/modules/libreoffice/api/api_test.go +++ /dev/null @@ -1,522 +0,0 @@ -package api - -import ( - "context" - "errors" - "os" - "reflect" - "testing" - "time" - - "github.com/alexliesenfeld/health" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestDefaultOptions(t *testing.T) { - actual := DefaultOptions() - notExpect := Options{} - - if reflect.DeepEqual(actual, notExpect) { - t.Errorf("expected %v and got identical %v", actual, notExpect) - } -} - -func TestApi_Descriptor(t *testing.T) { - descriptor := new(Api).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(Api)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestApi_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectError bool - }{ - { - scenario: "no logger provider", - ctx: func() *gotenberg.Context { - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{}, - ) - }(), - expectError: true, - }, - { - scenario: "no logger from logger provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return nil, errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "provision success", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.LoggerProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) { - return zap.NewNop(), nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Api).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - err := a.Provision(tc.ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestApi_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - binPath string - unoBinPath string - expectError bool - }{ - { - scenario: "empty LibreOffice bin path", - binPath: "", - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - expectError: true, - }, - { - scenario: "LibreOffice bin path does not exist", - binPath: "/foo", - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - expectError: true, - }, - { - scenario: "empty uno bin path", - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - unoBinPath: "", - expectError: true, - }, - { - scenario: "uno bin path does not exist", - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - unoBinPath: "/foo", - expectError: true, - }, - { - scenario: "validate success", - binPath: os.Getenv("CHROMIUM_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - a.args = libreOfficeArguments{ - binPath: tc.binPath, - unoBinPath: tc.unoBinPath, - } - err := a.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestApi_Start(t *testing.T) { - for _, tc := range []struct { - scenario string - autoStart bool - supervisor *gotenberg.ProcessSupervisorMock - expectError bool - }{ - { - scenario: "no auto-start", - autoStart: false, - expectError: false, - }, - { - scenario: "auto-start success", - autoStart: true, - supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error { - return nil - }}, - expectError: false, - }, - { - scenario: "auto-start failed", - autoStart: true, - supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error { - return errors.New("foo") - }}, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - a.autoStart = tc.autoStart - a.supervisor = tc.supervisor - - err := a.Start() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestApi_StartupMessage(t *testing.T) { - a := new(Api) - - a.autoStart = true - autoStartMsg := a.StartupMessage() - - a.autoStart = false - noAutoStartMsg := a.StartupMessage() - - if autoStartMsg == noAutoStartMsg { - t.Errorf("expected differrent startup messages based on auto start, but got '%s'", autoStartMsg) - } -} - -func TestApi_Stop(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor *gotenberg.ProcessSupervisorMock - expectError bool - }{ - { - scenario: "stop success", - supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error { - return nil - }}, - expectError: false, - }, - { - scenario: "stop failed", - supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error { - return errors.New("foo") - }}, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - a.logger = zap.NewNop() - a.supervisor = tc.supervisor - - ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second) - cancel() - - err := a.Stop(ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPdfTk_Debug(t *testing.T) { - for _, tc := range []struct { - scenario string - a *Api - expect map[string]interface{} - doNotExpect map[string]interface{} - }{ - { - scenario: "cannot determine version", - a: &Api{ - args: libreOfficeArguments{ - binPath: "foo", - }, - }, - expect: map[string]interface{}{ - "version": `exec: "foo": executable file not found in $PATH`, - }, - }, - { - scenario: "success", - a: &Api{ - args: libreOfficeArguments{ - binPath: "echo", - }, - }, - doNotExpect: map[string]interface{}{ - "version": `exec: "echo": executable file not found in $PATH`, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - d := tc.a.Debug() - - if tc.expect != nil { - if !reflect.DeepEqual(d, tc.expect) { - t.Errorf("expected '%v' but got '%v'", tc.expect, d) - } - } - - if tc.doNotExpect != nil { - if reflect.DeepEqual(d, tc.doNotExpect) { - t.Errorf("did not expect '%v'", d) - } - } - }) - } -} - -func TestApi_Metrics(t *testing.T) { - a := new(Api) - a.supervisor = &gotenberg.ProcessSupervisorMock{ - ReqQueueSizeMock: func() int64 { - return 10 - }, - RestartsCountMock: func() int64 { - return 0 - }, - } - - metrics, err := a.Metrics() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if len(metrics) != 2 { - t.Fatalf("expected %d metrics, but got %d", 2, len(metrics)) - } - - actual := metrics[0].Read() - if actual != float64(10) { - t.Errorf("expected %f for libreoffice_requests_queue_size, but got %f", float64(10), actual) - } - - actual = metrics[1].Read() - if actual != float64(0) { - t.Errorf("expected %f for libreoffice_restarts_count, but got %f", float64(0), actual) - } -} - -func TestApi_Checks(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor gotenberg.ProcessSupervisor - expectAvailabilityStatus health.AvailabilityStatus - }{ - { - scenario: "healthy module", - supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool { - return true - }}, - expectAvailabilityStatus: health.StatusUp, - }, - { - scenario: "unhealthy module", - supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool { - return false - }}, - expectAvailabilityStatus: health.StatusDown, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - a.supervisor = tc.supervisor - - checks, err := a.Checks() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - checker := health.NewChecker(checks...) - result := checker.Check(context.Background()) - - if result.Status != tc.expectAvailabilityStatus { - t.Errorf("expected '%s' as availability status, but got '%s'", tc.expectAvailabilityStatus, result.Status) - } - }) - } -} - -func TestChromium_Ready(t *testing.T) { - for _, tc := range []struct { - scenario string - autoStart bool - startTimeout time.Duration - libreOffice libreOffice - expectError bool - }{ - { - scenario: "no auto-start", - autoStart: false, - startTimeout: time.Duration(30) * time.Second, - libreOffice: &libreOfficeMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool { - return false - }}}, - expectError: false, - }, - { - scenario: "auto-start: context done", - autoStart: true, - startTimeout: time.Duration(200) * time.Millisecond, - libreOffice: &libreOfficeMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool { - return false - }}}, - expectError: true, - }, - { - scenario: "auto-start success", - autoStart: true, - startTimeout: time.Duration(30) * time.Second, - libreOffice: &libreOfficeMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool { - return true - }}}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - a.autoStart = tc.autoStart - a.args = libreOfficeArguments{startTimeout: tc.startTimeout} - a.libreOffice = tc.libreOffice - - err := a.Ready() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestApi_LibreOffice(t *testing.T) { - a := new(Api) - - _, err := a.LibreOffice() - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestApi_Pdf(t *testing.T) { - for _, tc := range []struct { - scenario string - supervisor gotenberg.ProcessSupervisor - libreOffice libreOffice - expectError bool - }{ - { - scenario: "PDF task success", - libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return nil - }}, - expectError: false, - }, - { - scenario: "PDF task error", - libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return errors.New("PDF task error") - }}, - expectError: true, - }, - { - scenario: "ErrCoreDumped", - libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return ErrCoreDumped - }}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - a := new(Api) - a.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error { - return task() - }} - a.libreOffice = tc.libreOffice - - err := a.Pdf(context.Background(), zap.NewNop(), "", "", Options{}) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestApi_Extensions(t *testing.T) { - a := new(Api) - extensions := a.Extensions() - - actual := len(extensions) - expect := 130 - - if actual != expect { - t.Errorf("expected %d extensions, but got %d", expect, actual) - } -} diff --git a/pkg/modules/libreoffice/api/libreoffice_test.go b/pkg/modules/libreoffice/api/libreoffice_test.go deleted file mode 100644 index 348d6c18b..000000000 --- a/pkg/modules/libreoffice/api/libreoffice_test.go +++ /dev/null @@ -1,699 +0,0 @@ -package api - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "testing" - "time" - - "github.com/google/uuid" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestLibreOfficeProcess_Start(t *testing.T) { - for _, tc := range []struct { - scenario string - libreOffice libreOffice - expectError bool - cleanup bool - }{ - { - scenario: "successful start", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - expectError: false, - cleanup: true, - }, - { - scenario: "LibreOffice already started", - libreOffice: func() libreOffice { - p := new(libreOfficeProcess) - p.isStarted.Store(true) - return p - }(), - expectError: true, - cleanup: false, - }, - { - scenario: "non-exit code 81 on first start", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: "foo", - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - expectError: true, - cleanup: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logger := zap.NewNop() - err := tc.libreOffice.Start(logger) - - if tc.cleanup { - defer func(p libreOffice, logger *zap.Logger) { - err = p.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.libreOffice, logger) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestLibreOfficeProcess_Stop(t *testing.T) { - for _, tc := range []struct { - scenario string - libreOffice libreOffice - setup func(libreOffice libreOffice, logger *zap.Logger) error - expectError bool - }{ - { - scenario: "successful stop", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - setup: func(p libreOffice, logger *zap.Logger) error { - return p.Start(logger) - }, - expectError: false, - }, - { - scenario: "LibreOffice already stopped", - libreOffice: func() libreOffice { - p := new(libreOfficeProcess) - p.isStarted.Store(false) - return p - }(), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logger := zap.NewNop() - - if tc.setup != nil { - err := tc.setup(tc.libreOffice, logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - } - - err := tc.libreOffice.Stop(logger) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestLibreOfficeProcess_Healthy(t *testing.T) { - for _, tc := range []struct { - scenario string - libreOffice libreOffice - setup func(libreOffice libreOffice, logger *zap.Logger) error - expectHealthy bool - cleanup bool - }{ - { - scenario: "healthy LibreOffice", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - setup: func(p libreOffice, logger *zap.Logger) error { - return p.Start(logger) - }, - expectHealthy: true, - cleanup: true, - }, - { - scenario: "LibreOffice not started", - libreOffice: func() libreOffice { - p := new(libreOfficeProcess) - p.isStarted.Store(false) - return p - }(), - expectHealthy: false, - cleanup: false, - }, - { - scenario: "unhealthy LibreOffice", - libreOffice: func() libreOffice { - p := new(libreOfficeProcess) - p.isStarted.Store(true) - p.socketPort = 12345 - return p - }(), - expectHealthy: false, - cleanup: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - logger := zap.NewNop() - - if tc.setup != nil { - err := tc.setup(tc.libreOffice, logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - } - - if tc.cleanup { - defer func(p libreOffice, logger *zap.Logger) { - err := p.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.libreOffice, logger) - } - - healthy := tc.libreOffice.Healthy(logger) - - if !tc.expectHealthy && healthy { - t.Fatal("expected unhealthy LibreOffice but got an healthy one") - } - - if tc.expectHealthy && !healthy { - t.Fatal("expected a healthy LibreOffice but got an unhealthy one") - } - }) - } -} - -func TestLibreOfficeProcess_pdf(t *testing.T) { - for _, tc := range []struct { - scenario string - libreOffice libreOffice - fs *gotenberg.FileSystem - options Options - cancelledCtx bool - start bool - expectError bool - expectedError error - }{ - { - scenario: "LibreOffice not started", - libreOffice: func() libreOffice { - p := new(libreOfficeProcess) - p.isStarted.Store(false) - return p - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - cancelledCtx: false, - start: false, - expectError: true, - }, - { - scenario: "ErrInvalidPdfFormats", - libreOffice: func() libreOffice { - p := new(libreOfficeProcess) - p.socketPort = 12345 - p.isStarted.Store(true) - return p - }(), - fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), - options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: "foo"}}, - cancelledCtx: false, - start: false, - expectError: true, - expectedError: ErrInvalidPdfFormats, - }, - { - scenario: "ErrUnoException", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - options: Options{PageRanges: "foo"}, - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Context done"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - cancelledCtx: false, - start: true, - expectError: true, - expectedError: ErrUnoException, - }, - { - scenario: "ErrRuntimeException", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - options: Options{Password: "foo"}, - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - in, err := os.Open("/tests/test/testdata/libreoffice/protected.docx") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - defer func() { - err := in.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - - out, err := os.Create(fmt.Sprintf("%s/protected.docx", fs.WorkingDirPath())) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - defer func() { - err := out.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - - _, err = io.Copy(out, in) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - cancelledCtx: false, - start: true, - expectError: true, - expectedError: ErrRuntimeException, - }, - { - scenario: "context done", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Context done"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - cancelledCtx: true, - start: true, - expectError: true, - }, - { - scenario: "success (default options)", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - cancelledCtx: false, - start: true, - expectError: false, - }, - { - scenario: "success (not default options)", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: Options{ - Password: "", // Ok, the only exception in this list. - Landscape: true, - PageRanges: "1", - ExportFormFields: false, - AllowDuplicateFieldNames: true, - ExportBookmarks: false, - ExportBookmarksToPdfDestination: true, - ExportPlaceholders: true, - ExportNotes: true, - ExportNotesPages: true, - ExportOnlyNotesPages: true, - ExportNotesInMargin: true, - ConvertOooTargetToPdfTarget: true, - ExportLinksRelativeFsys: true, - ExportHiddenSlides: true, - SkipEmptyPages: true, - AddOriginalDocumentAsStream: true, - SinglePageSheets: true, - LosslessImageCompression: true, - Quality: 100, - ReduceImageResolution: true, - MaxImageResolution: 600, - }, - cancelledCtx: false, - start: true, - expectError: false, - }, - { - scenario: "success (PDF/A-1b)", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b}}, - cancelledCtx: false, - start: true, - expectError: false, - }, - { - scenario: "success (PDF/A-2b)", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA2b}}, - cancelledCtx: false, - start: true, - expectError: false, - }, - { - scenario: "success (PDF/A-3b)", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA3b}}, - cancelledCtx: false, - start: true, - expectError: false, - }, - { - scenario: "success (PDF/UA)", - libreOffice: newLibreOfficeProcess( - libreOfficeArguments{ - binPath: os.Getenv("LIBREOFFICE_BIN_PATH"), - unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"), - startTimeout: 5 * time.Second, - }, - ), - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - options: Options{PdfFormats: gotenberg.PdfFormats{PdfUa: true}}, - cancelledCtx: false, - start: true, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - // Force the debug level. - logger := zap.NewExample() - - defer func() { - err := os.RemoveAll(tc.fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }() - - if tc.start { - err := tc.libreOffice.Start(logger) - if err != nil { - t.Fatalf("setup error: %v", err) - } - - defer func(p libreOffice, logger *zap.Logger) { - err = p.Stop(logger) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }(tc.libreOffice, logger) - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5)*time.Second) - defer cancel() - - if tc.cancelledCtx { - cancel() - } - - err := tc.libreOffice.pdf( - ctx, - logger, - fmt.Sprintf("%s/document.txt", tc.fs.WorkingDirPath()), - fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()), - tc.options, - ) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - }) - } -} - -func TestNonBasicLatinCharactersGuard(t *testing.T) { - for _, tc := range []struct { - scenario string - fs *gotenberg.FileSystem - filename string - expectSameInputPath bool - expectError bool - }{ - { - scenario: "basic latin characters", - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Basic latin characters"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - filename: "document.txt", - expectSameInputPath: true, - expectError: false, - }, - { - scenario: "non-basic latin characters", - fs: func() *gotenberg.FileSystem { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - - err := os.MkdirAll(fs.WorkingDirPath(), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - err = os.WriteFile(fmt.Sprintf("%s/éèßàùä.txt", fs.WorkingDirPath()), []byte("Non-basic latin characters"), 0o755) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return fs - }(), - filename: "éèßàùä.txt", - expectSameInputPath: false, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - defer func() { - err := os.RemoveAll(tc.fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up, but got: %v", err) - } - }() - - inputPath := fmt.Sprintf("%s/%s", tc.fs.WorkingDirPath(), tc.filename) - newInputPath, err := nonBasicLatinCharactersGuard( - zap.NewNop(), - inputPath, - ) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectSameInputPath && newInputPath != inputPath { - t.Fatalf("expected same input path, but got '%s'", newInputPath) - } - - if !tc.expectSameInputPath && newInputPath == inputPath { - t.Fatalf("expected different input path, but got same '%s'", newInputPath) - } - }) - } -} diff --git a/pkg/modules/libreoffice/api/mocks_test.go b/pkg/modules/libreoffice/api/mocks_test.go deleted file mode 100644 index f2a03530f..000000000 --- a/pkg/modules/libreoffice/api/mocks_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package api - -import ( - "context" - "testing" - - "go.uber.org/zap" -) - -func TestApiMock(t *testing.T) { - mock := &ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return nil - }, - ExtensionsMock: func() []string { - return nil - }, - } - - err := mock.Pdf(context.Background(), zap.NewNop(), "", "", Options{}) - if err != nil { - t.Errorf("expected no error from ApiMock.Pdf, but got: %v", err) - } - - ext := mock.Extensions() - if ext != nil { - t.Errorf("expected nil result from ApiMock.Extensions, but got: %v", ext) - } -} - -func TestProviderMock(t *testing.T) { - mock := &ProviderMock{ - LibreOfficeMock: func() (Uno, error) { - return nil, nil - }, - } - - _, err := mock.LibreOffice() - if err != nil { - t.Errorf("expected no error from ProviderMock.LibreOffice, but got: %v", err) - } -} - -func TestLibreOfficeMock(t *testing.T) { - for _, tc := range []struct { - scenario string - mock *libreOfficeMock - expectError bool - }{ - { - scenario: "success", - mock: &libreOfficeMock{ - pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return nil - }, - }, - expectError: false, - }, - { - scenario: "ErrCoreDumped (first call)", - mock: &libreOfficeMock{ - pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return ErrCoreDumped - }, - }, - expectError: true, - }, - { - scenario: "ErrCoreDumped (second call)", - mock: func() *libreOfficeMock { - m := &libreOfficeMock{ - pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error { - return ErrCoreDumped - }, - } - m.pdf(context.Background(), zap.NewNop(), "", "", Options{}) - return m - }(), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - err := tc.mock.pdf(context.Background(), zap.NewNop(), "", "", Options{}) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error from libreOfficeMock.pdf but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error from libreOfficeMock.pdf but got none") - } - }) - } -} diff --git a/pkg/modules/libreoffice/libreoffice_test.go b/pkg/modules/libreoffice/libreoffice_test.go deleted file mode 100644 index 4d756a079..000000000 --- a/pkg/modules/libreoffice/libreoffice_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package libreoffice - -import ( - "errors" - "reflect" - "testing" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" - libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api" -) - -func TestLibreOffice_Descriptor(t *testing.T) { - descriptor := new(LibreOffice).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(LibreOffice)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestLibreOffice_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectError bool - }{ - { - scenario: "no LibreOffice API provider", - ctx: func() *gotenberg.Context { - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOffice).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{}, - ) - }(), - expectError: true, - }, - { - scenario: "no LibreOffice API from LibreOffice API provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - libreofficeapi.ProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) { - return nil, errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOffice).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no PDF engine provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - libreofficeapi.ProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) { - return new(libreofficeapi.ApiMock), nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOffice).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "no PDF engine from PDF engine provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - libreofficeapi.ProviderMock - gotenberg.PdfEngineProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) { - return new(libreofficeapi.ApiMock), nil - } - mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) { - return nil, errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOffice).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "provision success", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - libreofficeapi.ProviderMock - gotenberg.PdfEngineProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }} - } - mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) { - return new(libreofficeapi.ApiMock), nil - } - mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) { - return new(gotenberg.PdfEngineMock), nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOffice).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(LibreOffice) - err := mod.Provision(tc.ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestLibreOffice_Routes(t *testing.T) { - for _, tc := range []struct { - scenario string - expectRoutes int - disableRoutes bool - }{ - { - scenario: "routes not disabled", - expectRoutes: 1, - disableRoutes: false, - }, - { - scenario: "routes disabled", - expectRoutes: 0, - disableRoutes: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(LibreOffice) - mod.disableRoutes = tc.disableRoutes - - routes, err := mod.Routes() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectRoutes != len(routes) { - t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes)) - } - }) - } -} diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine_test.go b/pkg/modules/libreoffice/pdfengine/pdfengine_test.go deleted file mode 100644 index d7034c2e3..000000000 --- a/pkg/modules/libreoffice/pdfengine/pdfengine_test.go +++ /dev/null @@ -1,210 +0,0 @@ -package pdfengine - -import ( - "context" - "errors" - "reflect" - "testing" - - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" - "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api" -) - -func TestLibreOfficePdfEngine_Descriptor(t *testing.T) { - descriptor := new(LibreOfficePdfEngine).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(LibreOfficePdfEngine)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestLibreOfficePdfEngine_Provider(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectError bool - }{ - { - scenario: "no LibreOffice API provider", - ctx: gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOfficePdfEngine).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{}, - ), - expectError: true, - }, - { - scenario: "no API from LibreOffice API provider", - ctx: func() *gotenberg.Context { - provider := &struct { - gotenberg.ModuleMock - api.ProviderMock - }{} - provider.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { - return provider - }} - } - provider.LibreOfficeMock = func() (api.Uno, error) { - return nil, errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOfficePdfEngine).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - provider.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "provision success", - ctx: func() *gotenberg.Context { - provider := &struct { - gotenberg.ModuleMock - api.ProviderMock - }{} - provider.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { - return provider - }} - } - provider.LibreOfficeMock = func() (api.Uno, error) { - return new(api.ApiMock), nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(LibreOfficePdfEngine).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - provider.Descriptor(), - }, - ) - }(), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(LibreOfficePdfEngine) - err := engine.Provision(tc.ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestLibreOfficePdfEngine_Merge(t *testing.T) { - engine := new(LibreOfficePdfEngine) - err := engine.Merge(context.Background(), zap.NewNop(), nil, "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_Split(t *testing.T) { - engine := new(LibreOfficePdfEngine) - _, err := engine.Split(context.Background(), zap.NewNop(), gotenberg.SplitMode{}, "", "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_Flatten(t *testing.T) { - engine := new(LibreOfficePdfEngine) - err := engine.Flatten(context.Background(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_Convert(t *testing.T) { - for _, tc := range []struct { - scenario string - api api.Uno - expectError bool - expectedError error - }{ - { - scenario: "convert success", - api: &api.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error { - return nil - }, - }, - expectError: false, - }, - { - scenario: "ErrInvalidPdfFormats", - api: &api.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error { - return api.ErrInvalidPdfFormats - }, - }, - expectError: true, - expectedError: gotenberg.ErrPdfFormatNotSupported, - }, - { - scenario: "convert fail", - api: &api.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error { - return errors.New("foo") - }, - }, - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := &LibreOfficePdfEngine{unoApi: tc.api} - err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - }) - } -} - -func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) { - engine := new(LibreOfficePdfEngine) - _, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) { - engine := new(LibreOfficePdfEngine) - err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index 47a2846fe..113b5d79e 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -1,7 +1,6 @@ package libreoffice import ( - "encoding/json" "errors" "fmt" "net/http" @@ -129,15 +128,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap }). Bool("nativePdfFormats", &nativePdfFormats, true). Bool("merge", &merge, false). - Custom("metadata", func(value string) error { - if len(value) > 0 { - err := json.Unmarshal([]byte(value), &metadata) - if err != nil { - return fmt.Errorf("unmarshal metadata: %w", err) - } - } - return nil - }). Bool("flatten", &flatten, false). Validate() if err != nil { diff --git a/pkg/modules/libreoffice/routes_test.go b/pkg/modules/libreoffice/routes_test.go deleted file mode 100644 index c033591de..000000000 --- a/pkg/modules/libreoffice/routes_test.go +++ /dev/null @@ -1,817 +0,0 @@ -package libreoffice - -import ( - "context" - "errors" - "fmt" - "net/http" - "os" - "path/filepath" - "slices" - "testing" - - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" - "github.com/gotenberg/gotenberg/v8/pkg/modules/api" - libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api" -) - -func TestConvertRoute(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - libreOffice libreofficeapi.Uno - engine gotenberg.PdfEngine - expectOptions libreofficeapi.Options - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid quality form field (not an integer)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "quality": { - "foo", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid quality form field (< 1)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "quality": { - "0", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid quality form field (> 100)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "quality": { - "101", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid maxImageResolution form field (not an integer)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "maxImageResolution": { - "foo", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid maxImageResolution form field (not in range)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "maxImageResolution": { - "1", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "foo", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string { - return []string{".docx"} - }}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrPdfFormatNotSupported (nativePdfFormats)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return libreofficeapi.ErrInvalidPdfFormats - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrUnoException", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "nativePageRanges": { - "foo", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return libreofficeapi.ErrUnoException - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrRuntimeException", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "password": { - "invalid", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return libreofficeapi.ErrRuntimeException - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from LibreOffice", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return errors.New("foo") - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "PDF engine merge error", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - }) - ctx.SetValues(map[string][]string{ - "merge": { - "true", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "PDF engine split error", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return nil, errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "PDF engine convert error", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "nativePdfFormats": { - "false", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "PDF engine write metadata error", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "PDF engine flatten error", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "flatten": { - "true", - }, - }) - ctx.SetCancelled(true) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetCancelled(true) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success (single file)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - }) - ctx.SetValues(map[string][]string{ - "quality": { - "100", - }, - "maxImageResolution": { - "1200", - }, - "merge": { - "true", - }, - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - "nativePdfFormats": { - "false", - }, - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - "flatten": { - "true", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "success (many files)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - "document2.doc": "/document2.doc", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - "nativePdfFormats": { - "false", - }, - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - "flatten": { - "true", - }, - }) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx", ".doc"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 3, - expectOutputPaths: []string{"/document.docx.pdf", "/document2.docx.pdf", "/document2.doc.pdf"}, - }, - { - scenario: "success with native PDF/A & PDF/UA", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "success with split (many files)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] - filenameNoExt := filepath.Base(inputPathNoExt) - return []string{ - fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, 0, - ), - fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, 1, - ), - }, nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 4, - expectOutputPaths: []string{"/document_docx/document.docx_0.pdf", "/document_docx/document.docx_1.pdf", "/document2_docx/document2.docx_0.pdf", "/document2_docx/document2.docx_1.pdf"}, - }, - { - scenario: "success with merge and split", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - }) - ctx.SetValues(map[string][]string{ - "merge": { - "true", - }, - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] - filenameNoExt := filepath.Base(inputPathNoExt) - return []string{ - fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, 0, - ), - fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, 1, - ), - }, nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 2, - }, - { - scenario: "success with split and native PDF/A & PDF/UA (many files)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - "document2.docx": "/document2.docx", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - libreOffice: &libreofficeapi.ApiMock{ - PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error { - return nil - }, - ExtensionsMock: func() []string { - return []string{".docx"} - }, - }, - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] - filenameNoExt := filepath.Base(inputPathNoExt) - return []string{ - fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, 0, - ), - fmt.Sprintf( - "%s/%s_%d.pdf", - outputDirPath, filenameNoExt, 1, - ), - }, nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 4, - expectOutputPaths: []string{"/document_docx/document.docx_0.pdf", "/document_docx/document.docx_1.pdf", "/document2_docx/document2.docx_0.pdf", "/document2_docx/document2.docx_1.pdf"}, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := convertRoute(tc.libreOffice, tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - - for _, path := range tc.expectOutputPaths { - if !slices.Contains(tc.ctx.OutputPaths(), path) { - t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) - } - } - }) - } -} diff --git a/pkg/modules/pdfcpu/pdfcpu_test.go b/pkg/modules/pdfcpu/pdfcpu_test.go deleted file mode 100644 index b1253cfd6..000000000 --- a/pkg/modules/pdfcpu/pdfcpu_test.go +++ /dev/null @@ -1,329 +0,0 @@ -package pdfcpu - -import ( - "context" - "errors" - "os" - "reflect" - "testing" - - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestPdfCpu_Descriptor(t *testing.T) { - descriptor := new(PdfCpu).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(PdfCpu)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestPdfCpu_Provision(t *testing.T) { - engine := new(PdfCpu) - ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil) - - err := engine.Provision(ctx) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestPdfCpu_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - binPath string - expectError bool - }{ - { - scenario: "empty bin path", - binPath: "", - expectError: true, - }, - { - scenario: "bin path does not exist", - binPath: "/foo", - expectError: true, - }, - { - scenario: "validate success", - binPath: os.Getenv("PDFTK_BIN_PATH"), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(PdfCpu) - engine.binPath = tc.binPath - err := engine.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPdfCpu_Debug(t *testing.T) { - for _, tc := range []struct { - scenario string - engine *PdfCpu - expect map[string]interface{} - doNotExpect map[string]interface{} - }{ - { - scenario: "cannot determine version (command error)", - engine: &PdfCpu{ - binPath: "foo", - }, - expect: map[string]interface{}{ - "version": `exec: "foo": executable file not found in $PATH`, - }, - }, - { - scenario: "cannot determine version (no pdfcpu)", - engine: &PdfCpu{ - binPath: "echo", - }, - expect: map[string]interface{}{ - "version": "Unable to determine pdfcpu version", - }, - }, - { - scenario: "success", - engine: &PdfCpu{ - binPath: "pdfcpu", - }, - doNotExpect: map[string]interface{}{ - "version": "Unable to determine pdfcpu version", - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - d := tc.engine.Debug() - - if tc.expect != nil { - if !reflect.DeepEqual(d, tc.expect) { - t.Errorf("expected '%v' but got '%v'", tc.expect, d) - } - } - - if tc.doNotExpect != nil { - if reflect.DeepEqual(d, tc.doNotExpect) { - t.Errorf("did not expect '%v'", d) - } - } - }) - } -} - -func TestPdfCpu_Merge(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - inputPaths []string - expectError bool - }{ - { - scenario: "invalid context", - ctx: nil, - expectError: true, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - inputPaths: []string{ - "foo", - }, - expectError: true, - }, - { - scenario: "single file success", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - }, - expectError: false, - }, - { - scenario: "many files success", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - "/tests/test/testdata/pdfengines/sample2.pdf", - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(PdfCpu) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - err = engine.Merge(tc.ctx, zap.NewNop(), tc.inputPaths, outputDir+"/foo.pdf") - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPdfCpu_Split(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - mode gotenberg.SplitMode - inputPath string - expectError bool - expectedError error - expectOutputPathsCount int - }{ - { - scenario: "ErrPdfSplitModeNotSupported", - expectError: true, - expectedError: gotenberg.ErrPdfSplitModeNotSupported, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid context", - ctx: nil, - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - expectError: true, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - inputPath: "", - expectError: true, - expectOutputPathsCount: 0, - }, - { - scenario: "success (intervals)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - inputPath: "/tests/test/testdata/pdfengines/sample4.pdf", - expectError: false, - expectOutputPathsCount: 20, - }, - { - scenario: "success (pages)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"}, - inputPath: "/tests/test/testdata/pdfengines/sample4.pdf", - expectError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "success (pages & unify)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "/tests/test/testdata/pdfengines/sample4.pdf", - expectError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(PdfCpu) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - if tc.expectOutputPathsCount != len(outputPaths) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths)) - } - }) - } -} - -func TestPdfCpu_Flatten(t *testing.T) { - mod := new(PdfCpu) - err := mod.Flatten(context.TODO(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestPdfCpu_Convert(t *testing.T) { - mod := new(PdfCpu) - err := mod.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) { - engine := new(PdfCpu) - _, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) { - engine := new(PdfCpu) - err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} diff --git a/pkg/modules/pdfengines/pdfengines_test.go b/pkg/modules/pdfengines/pdfengines_test.go deleted file mode 100644 index b0beed3ea..000000000 --- a/pkg/modules/pdfengines/pdfengines_test.go +++ /dev/null @@ -1,417 +0,0 @@ -package pdfengines - -import ( - "errors" - "fmt" - "reflect" - "strings" - "testing" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestPdfEngines_Descriptor(t *testing.T) { - descriptor := new(PdfEngines).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(PdfEngines)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestPdfEngines_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectedMergePdfEngines []string - expectedSplitPdfEngines []string - expectedFlattenPdfEngines []string - expectedConvertPdfEngines []string - expectedReadMetadataPdfEngines []string - expectedWriteMetadataPdfEngines []string - expectError bool - }{ - { - scenario: "no selection from user", - ctx: func() *gotenberg.Context { - provider := &struct { - gotenberg.ModuleMock - }{} - provider.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { - return provider - }} - } - - engine := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.PdfEngineMock - }{} - engine.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return engine }} - } - engine.ValidateMock = func() error { - return nil - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(PdfEngines).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - provider.Descriptor(), - engine.Descriptor(), - }, - ) - }(), - expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"}, - expectedSplitPdfEngines: []string{"pdfcpu", "qpdf", "pdftk"}, - expectedFlattenPdfEngines: []string{"qpdf"}, - expectedConvertPdfEngines: []string{"libreoffice-pdfengine"}, - expectedReadMetadataPdfEngines: []string{"exiftool"}, - expectedWriteMetadataPdfEngines: []string{"exiftool"}, - expectError: false, - }, - { - scenario: "selection from user", - ctx: func() *gotenberg.Context { - provider := &struct { - gotenberg.ModuleMock - }{} - provider.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { - return provider - }} - } - engine1 := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.PdfEngineMock - }{} - engine1.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "a", New: func() gotenberg.Module { return engine1 }} - } - engine1.ValidateMock = func() error { - return nil - } - - engine2 := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.PdfEngineMock - }{} - engine2.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "b", New: func() gotenberg.Module { return engine2 }} - } - engine2.ValidateMock = func() error { - return nil - } - - fs := new(PdfEngines).Descriptor().FlagSet - err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-flatten-engines=c", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - []gotenberg.ModuleDescriptor{ - provider.Descriptor(), - engine1.Descriptor(), - engine2.Descriptor(), - }, - ) - }(), - - expectedMergePdfEngines: []string{"b"}, - expectedSplitPdfEngines: []string{"a"}, - expectedFlattenPdfEngines: []string{"c"}, - expectedConvertPdfEngines: []string{"b"}, - expectedReadMetadataPdfEngines: []string{"a"}, - expectedWriteMetadataPdfEngines: []string{"a"}, - expectError: false, - }, - { - scenario: "no valid PDF engine", - ctx: func() *gotenberg.Context { - provider := &struct { - gotenberg.ModuleMock - }{} - provider.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { - return provider - }} - } - engine := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.PdfEngineMock - }{} - engine.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return engine }} - } - engine.ValidateMock = func() error { - return errors.New("foo") - } - - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(PdfEngines).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - provider.Descriptor(), - engine.Descriptor(), - }, - ) - }(), - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(PdfEngines) - err := mod.Provision(tc.ctx) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if len(tc.expectedMergePdfEngines) != len(mod.mergeNames) { - t.Fatalf("expected %d merge names but got %d", len(tc.expectedMergePdfEngines), len(mod.mergeNames)) - } - - if len(tc.expectedFlattenPdfEngines) != len(mod.flattenNames) { - t.Fatalf("expected %d flatten names but got %d", len(tc.expectedFlattenPdfEngines), len(mod.flattenNames)) - } - - if len(tc.expectedConvertPdfEngines) != len(mod.convertNames) { - t.Fatalf("expected %d convert names but got %d", len(tc.expectedConvertPdfEngines), len(mod.convertNames)) - } - - if len(tc.expectedReadMetadataPdfEngines) != len(mod.readMetadataNames) { - t.Fatalf("expected %d read metadata names but got %d", len(tc.expectedReadMetadataPdfEngines), len(mod.readMetadataNames)) - } - - if len(tc.expectedWriteMetadataPdfEngines) != len(mod.writeMetadataNames) { - t.Fatalf("expected %d write metadata names but got %d", len(tc.expectedWriteMetadataPdfEngines), len(mod.writeMetadataNames)) - } - - for index, name := range mod.mergeNames { - if name != tc.expectedMergePdfEngines[index] { - t.Fatalf("expected merge name at index %d to be %s, but got: %s", index, name, tc.expectedMergePdfEngines[index]) - } - } - - for index, name := range mod.splitNames { - if name != tc.expectedSplitPdfEngines[index] { - t.Fatalf("expected split name at index %d to be %s, but got: %s", index, name, tc.expectedSplitPdfEngines[index]) - } - } - - for index, name := range mod.convertNames { - if name != tc.expectedConvertPdfEngines[index] { - t.Fatalf("expected convert name at index %d to be %s, but got: %s", index, name, tc.expectedConvertPdfEngines[index]) - } - } - - for index, name := range mod.readMetadataNames { - if name != tc.expectedReadMetadataPdfEngines[index] { - t.Fatalf("expected read metadata name at index %d to be %s, but got: %s", index, name, tc.expectedReadMetadataPdfEngines[index]) - } - } - - for index, name := range mod.writeMetadataNames { - if name != tc.expectedWriteMetadataPdfEngines[index] { - t.Fatalf("expected write metadat name at index %d to be %s, but got: %s", index, name, tc.expectedWriteMetadataPdfEngines[index]) - } - } - }) - } -} - -func TestPdfEngines_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - names []string - engines []gotenberg.PdfEngine - expectError bool - }{ - { - scenario: "existing PDF engine", - names: []string{"foo"}, - engines: func() []gotenberg.PdfEngine { - engine := &struct { - gotenberg.ModuleMock - gotenberg.PdfEngineMock - }{} - engine.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return engine }} - } - - return []gotenberg.PdfEngine{ - engine, - } - }(), - expectError: false, - }, - { - scenario: "non-existing bar PDF engine", - names: []string{"foo", "bar", "baz"}, - engines: func() []gotenberg.PdfEngine { - engine1 := &struct { - gotenberg.ModuleMock - gotenberg.PdfEngineMock - }{} - engine1.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return engine1 }} - } - - engine2 := &struct { - gotenberg.ModuleMock - gotenberg.PdfEngineMock - }{} - engine2.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "baz", New: func() gotenberg.Module { return engine2 }} - } - - return []gotenberg.PdfEngine{ - engine1, - engine2, - } - }(), - expectError: true, - }, - { - scenario: "no PDF engine", - expectError: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := PdfEngines{ - mergeNames: tc.names, - convertNames: tc.names, - readMetadataNames: tc.names, - writeMetadataNames: tc.names, - engines: tc.engines, - } - - err := mod.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPdfEngines_SystemMessages(t *testing.T) { - mod := new(PdfEngines) - mod.mergeNames = []string{"foo", "bar"} - mod.splitNames = []string{"foo", "bar"} - mod.convertNames = []string{"foo", "bar"} - mod.readMetadataNames = []string{"foo", "bar"} - mod.writeMetadataNames = []string{"foo", "bar"} - - expectedMessages := 6 - messages := mod.SystemMessages() - if len(messages) != expectedMessages { - t.Errorf("expected %d message(s), but got %d", expectedMessages, len(messages)) - } - - expect := []string{ - fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")), - fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")), - fmt.Sprintf("flatten engines - %s", strings.Join(mod.flattenNames[:], " ")), - fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")), - fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")), - fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")), - } - - for i, message := range messages { - if message != expect[i] { - t.Errorf("expected message at index %d to be %s, but got %s", i, message, expect[i]) - } - } -} - -func TestPdfEngines_PdfEngine(t *testing.T) { - mod := PdfEngines{ - mergeNames: []string{"foo", "bar"}, - splitNames: []string{"foo", "bar"}, - convertNames: []string{"foo", "bar"}, - readMetadataNames: []string{"foo", "bar"}, - writeMetadataNames: []string{"foo", "bar"}, - engines: func() []gotenberg.PdfEngine { - engine1 := &struct { - gotenberg.ModuleMock - gotenberg.PdfEngineMock - }{} - engine1.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return engine1 }} - } - - engine2 := &struct { - gotenberg.ModuleMock - gotenberg.PdfEngineMock - }{} - engine2.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return engine2 }} - } - - return []gotenberg.PdfEngine{ - engine1, - engine2, - } - }(), - } - - _, err := mod.PdfEngine() - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestPdfEngines_Routes(t *testing.T) { - for _, tc := range []struct { - scenario string - expectRoutes int - disableRoutes bool - }{ - { - scenario: "routes not disabled", - expectRoutes: 6, - disableRoutes: false, - }, - { - scenario: "routes disabled", - expectRoutes: 0, - disableRoutes: true, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(PdfEngines) - mod.disableRoutes = tc.disableRoutes - - routes, err := mod.Routes() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectRoutes != len(routes) { - t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes)) - } - }) - } -} diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go deleted file mode 100644 index c50d470e5..000000000 --- a/pkg/modules/pdfengines/routes_test.go +++ /dev/null @@ -1,1813 +0,0 @@ -package pdfengines - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "os" - "reflect" - "slices" - "strings" - "testing" - - "github.com/google/uuid" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" - "github.com/gotenberg/gotenberg/v8/pkg/modules/api" -) - -func TestFormDataPdfSplitMode(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - mandatory bool - expectedSplitMode gotenberg.SplitMode - expectValidationError bool - }{ - { - scenario: "no custom form fields", - ctx: &api.ContextMock{Context: new(api.Context)}, - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{}, - expectValidationError: false, - }, - { - scenario: "no custom form fields (mandatory)", - ctx: &api.ContextMock{Context: new(api.Context)}, - mandatory: true, - expectedSplitMode: gotenberg.SplitMode{}, - expectValidationError: true, - }, - { - scenario: "invalid splitMode", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "foo", - }, - }) - return ctx - }(), - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{}, - expectValidationError: true, - }, - { - scenario: "invalid splitSpan (intervals)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "intervals", - }, - "splitSpan": { - "1-2", - }, - }) - return ctx - }(), - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals}, - expectValidationError: true, - }, - { - scenario: "splitSpan inferior to 1 (intervals)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "intervals", - }, - "splitSpan": { - "-1", - }, - }) - return ctx - }(), - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals}, - expectValidationError: true, - }, - { - scenario: "invalid splitUnify (intervals)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "intervals", - }, - "splitSpan": { - "1", - }, - "splitUnify": { - "true", - }, - }) - return ctx - }(), - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1", Unify: true}, - expectValidationError: true, - }, - { - scenario: "valid form fields (intervals)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "intervals", - }, - "splitSpan": { - "1", - }, - }) - return ctx - }(), - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - expectValidationError: false, - }, - { - scenario: "valid form fields (pages)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "pages", - }, - "splitSpan": { - "1-2", - }, - "splitUnify": { - "true", - }, - }) - return ctx - }(), - mandatory: false, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - expectValidationError: false, - }, - { - scenario: "valid form fields (mandatory)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "splitMode": { - "intervals", - }, - "splitSpan": { - "1", - }, - }) - return ctx - }(), - mandatory: true, - expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form := tc.ctx.Context.FormData() - actual := FormDataPdfSplitMode(form, tc.mandatory) - - if !reflect.DeepEqual(actual, tc.expectedSplitMode) { - t.Fatalf("expected %+v but got: %+v", tc.expectedSplitMode, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestFormDataPdfFormats(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - expectedPdfFormats gotenberg.PdfFormats - expectValidationError bool - }{ - { - scenario: "no custom form fields", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectedPdfFormats: gotenberg.PdfFormats{}, - expectValidationError: false, - }, - { - scenario: "pdfa and pdfua form fields", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "pdfa": { - "foo", - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - expectedPdfFormats: gotenberg.PdfFormats{PdfA: "foo", PdfUa: true}, - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form := tc.ctx.Context.FormData() - actual := FormDataPdfFormats(form) - - if !reflect.DeepEqual(actual, tc.expectedPdfFormats) { - t.Fatalf("expected %+v but got: %+v", tc.expectedPdfFormats, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestFormDataPdfMetadata(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - mandatory bool - expectedMetadata map[string]interface{} - expectValidationError bool - }{ - { - scenario: "no metadata form field", - ctx: &api.ContextMock{Context: new(api.Context)}, - mandatory: false, - expectedMetadata: nil, - expectValidationError: false, - }, - { - scenario: "no metadata form field (mandatory)", - ctx: &api.ContextMock{Context: new(api.Context)}, - mandatory: true, - expectedMetadata: nil, - expectValidationError: true, - }, - { - scenario: "invalid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "metadata": { - "foo", - }, - }) - return ctx - }(), - mandatory: false, - expectedMetadata: nil, - expectValidationError: true, - }, - { - scenario: "valid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"foo\":\"bar\"}", - }, - }) - return ctx - }(), - mandatory: false, - expectedMetadata: map[string]interface{}{ - "foo": "bar", - }, - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form := tc.ctx.Context.FormData() - actual := FormDataPdfMetadata(form, tc.mandatory) - - if !reflect.DeepEqual(actual, tc.expectedMetadata) { - t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestMergeStub(t *testing.T) { - for _, tc := range []struct { - scenario string - engine gotenberg.PdfEngine - inputPaths []string - expectError bool - }{ - { - scenario: "no input path (nil)", - inputPaths: nil, - expectError: true, - }, - { - scenario: "no input path (empty)", - inputPaths: make([]string, 0), - expectError: true, - }, - { - scenario: "only one input path", - inputPaths: []string{"my.pdf"}, - expectError: false, - }, - { - scenario: "merge error", - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return errors.New("foo") - }, - }, - inputPaths: []string{"my.pdf", "my2.pdf"}, - expectError: true, - }, - { - scenario: "merge success", - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - }, - inputPaths: []string{"my.pdf", "my2.pdf"}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - _, err := MergeStub(new(api.Context), tc.engine, tc.inputPaths) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestSplitPdfStub(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - mode gotenberg.SplitMode - expectError bool - }{ - { - scenario: "no split mode", - mode: gotenberg.SplitMode{}, - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: false, - }, - { - scenario: "cannot create subdirectory", - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return errors.New("cannot create subdirectory") - }}) - return ctx - }(), - expectError: true, - }, - { - scenario: "split error", - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return nil, errors.New("foo") - }, - }, - expectError: true, - }, - { - scenario: "rename error", - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return errors.New("cannot rename") - }}) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - }, - expectError: true, - }, - { - scenario: "success (intervals)", - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"}, - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - }, - expectError: false, - }, - { - scenario: "success (pages)", - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString()) - tc.ctx.SetDirPath(dirPath) - tc.ctx.SetLogger(zap.NewNop()) - - _, err := SplitPdfStub(tc.ctx.Context, tc.engine, tc.mode, []string{"my.pdf", "my2.pdf"}) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestFlattenStub(t *testing.T) { - for _, tc := range []struct { - scenario string - engine gotenberg.PdfEngine - expectError bool - }{ - { - scenario: "flatten error", - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - }, - { - scenario: "flatten success", - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - err := FlattenStub(new(api.Context), tc.engine, []string{"my.pdf", "my2.pdf"}) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestConvertStub(t *testing.T) { - for _, tc := range []struct { - scenario string - engine gotenberg.PdfEngine - pdfFormats gotenberg.PdfFormats - expectError bool - }{ - { - scenario: "no PDF formats", - pdfFormats: gotenberg.PdfFormats{}, - expectError: false, - }, - { - scenario: "convert error", - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }, - }, - pdfFormats: gotenberg.PdfFormats{ - PdfA: gotenberg.PdfA3b, - PdfUa: true, - }, - expectError: true, - }, - { - scenario: "convert success", - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - pdfFormats: gotenberg.PdfFormats{ - PdfA: gotenberg.PdfA3b, - PdfUa: true, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - _, err := ConvertStub(new(api.Context), tc.engine, tc.pdfFormats, []string{"my.pdf", "my2.pdf"}) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestWriteMetadataStub(t *testing.T) { - for _, tc := range []struct { - scenario string - engine gotenberg.PdfEngine - metadata map[string]interface{} - expectError bool - }{ - { - scenario: "no metadata (nil)", - metadata: nil, - expectError: false, - }, - { - scenario: "no metadata (empty)", - metadata: make(map[string]interface{}, 0), - expectError: false, - }, - { - scenario: "write metadata error", - engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") - }, - }, - metadata: map[string]interface{}{"foo": "bar"}, - expectError: true, - }, - { - scenario: "write metadata success", - engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - }, - metadata: map[string]interface{}{"foo": "bar"}, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - err := WriteMetadataStub(new(api.Context), tc.engine, tc.metadata, []string{"my.pdf", "my2.pdf"}) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }) - } -} - -func TestMergeHandler(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "foo", - }, - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (merge)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (convert)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (write metadata)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (flatten)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "flatten": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetCancelled(true) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - "flatten": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := mergeRoute(tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - }) - } -} - -func TestSplitHandler(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "no split mode", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (split)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return nil, errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (convert)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (write metadata)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine (flatten)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - "flatten": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - }) - ctx.SetCancelled(true) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{inputPath}, nil - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "splitMode": { - gotenberg.SplitModeIntervals, - }, - "splitSpan": { - "1", - }, - "pdfua": { - "true", - }, - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - "flatten": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) { - return []string{"file_split_1.pdf", "file_split_2.pdf"}, nil - }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 2, - expectOutputPaths: []string{"/file/file_0.pdf", "/file/file_1.pdf"}, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error { - return nil - }}) - tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := splitRoute(tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - - for _, path := range tc.expectOutputPaths { - if !slices.Contains(tc.ctx.OutputPaths(), path) { - t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) - } - } - }) - } -} - -func TestFlattenHandler(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetCancelled(true) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success with single file", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "success (many files)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 2, - expectOutputPaths: []string{"/file.pdf", "/file2.pdf"}, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := flattenRoute(tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - - for _, path := range tc.expectOutputPaths { - if !slices.Contains(tc.ctx.OutputPaths(), path) { - t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) - } - } - }) - } -} - -func TestConvertHandler(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "no PDF formats", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - }) - ctx.SetCancelled(true) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success with PDF/A & PDF/UA form fields (single file)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "cannot rename many files", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - }) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return errors.New("cannot rename") - }}) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success with PDF/A & PDF/UA form fields (many files)", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - }) - ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error { - return nil - }}) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 2, - expectOutputPaths: []string{"/file.pdf", "/file2.pdf"}, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := convertRoute(tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - - for _, path := range tc.expectOutputPaths { - if !slices.Contains(tc.ctx.OutputPaths(), path) { - t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) - } - } - }) - } -} - -func TestReadMetadataHandler(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - expectError bool - expectedError error - expectHttpError bool - expectHttpStatus int - expectedJson string - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "error from PDF engine", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { - return nil, errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { - return map[string]interface{}{ - "foo": "bar", - "bar": "foo", - }, nil - }, - }, - expectError: true, - expectedError: api.ErrNoOutputFile, - expectHttpError: false, - expectedJson: `{"file.pdf":{"bar":"foo","foo":"bar"}}`, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - req := httptest.NewRequest(http.MethodPost, "/forms/pdfengines/metadata/read", nil) - rec := httptest.NewRecorder() - c := echo.New().NewContext(req, rec) - c.Set("context", tc.ctx.Context) - - err := readMetadataRoute(tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectedJson != "" && tc.expectedJson != strings.TrimSpace(rec.Body.String()) { - t.Errorf("expected '%s' as HTTP response but got '%s'", tc.expectedJson, strings.TrimSpace(rec.Body.String())) - } - }) - } -} - -func TestWriteMetadataHandler(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - engine gotenberg.PdfEngine - expectError bool - expectHttpError bool - expectHttpStatus int - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "missing at least one mandatory file", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "no metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "foo", - }, - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "no metadata", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "document.docx": "/document.docx", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "{}", - }, - }) - return ctx - }(), - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - expectOutputPathsCount: 0, - }, - { - scenario: "error from PDF engine", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "cannot add output paths", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - ctx.SetCancelled(true) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - }) - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - c := echo.New().NewContext(nil, nil) - c.Set("context", tc.ctx.Context) - - err := writeMetadataRoute(tc.engine).Handler(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - - if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths())) - } - - for _, path := range tc.expectOutputPaths { - if !slices.Contains(tc.ctx.OutputPaths(), path) { - t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths()) - } - } - }) - } -} diff --git a/pkg/modules/pdftk/pdftk_test.go b/pkg/modules/pdftk/pdftk_test.go deleted file mode 100644 index 0a49aeda4..000000000 --- a/pkg/modules/pdftk/pdftk_test.go +++ /dev/null @@ -1,313 +0,0 @@ -package pdftk - -import ( - "context" - "errors" - "os" - "reflect" - "testing" - - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestPdfTk_Descriptor(t *testing.T) { - descriptor := new(PdfTk).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(PdfTk)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestPdfTk_Provision(t *testing.T) { - engine := new(PdfTk) - ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil) - - err := engine.Provision(ctx) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestPdfTk_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - binPath string - expectError bool - }{ - { - scenario: "empty bin path", - binPath: "", - expectError: true, - }, - { - scenario: "bin path does not exist", - binPath: "/foo", - expectError: true, - }, - { - scenario: "validate success", - binPath: os.Getenv("PDFTK_BIN_PATH"), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(PdfTk) - engine.binPath = tc.binPath - err := engine.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPdfTk_Debug(t *testing.T) { - for _, tc := range []struct { - scenario string - engine *PdfTk - expect map[string]interface{} - doNotExpect map[string]interface{} - }{ - { - scenario: "cannot determine version", - engine: &PdfTk{ - binPath: "foo", - }, - expect: map[string]interface{}{ - "version": `exec: "foo": executable file not found in $PATH`, - }, - }, - { - scenario: "success", - engine: &PdfTk{ - binPath: "echo", - }, - doNotExpect: map[string]interface{}{ - "version": `exec: "echo": executable file not found in $PATH`, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - d := tc.engine.Debug() - - if tc.expect != nil { - if !reflect.DeepEqual(d, tc.expect) { - t.Errorf("expected '%v' but got '%v'", tc.expect, d) - } - } - - if tc.doNotExpect != nil { - if reflect.DeepEqual(d, tc.doNotExpect) { - t.Errorf("did not expect '%v'", d) - } - } - }) - } -} - -func TestPdfTk_Merge(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - inputPaths []string - expectError bool - }{ - { - scenario: "invalid context", - ctx: nil, - expectError: true, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - inputPaths: []string{ - "foo", - }, - expectError: true, - }, - { - scenario: "single file success", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - }, - expectError: false, - }, - { - scenario: "many files success", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - "/tests/test/testdata/pdfengines/sample2.pdf", - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(PdfTk) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - err = engine.Merge(tc.ctx, zap.NewNop(), tc.inputPaths, outputDir+"/foo.pdf") - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPdfCpu_Split(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - mode gotenberg.SplitMode - inputPath string - expectError bool - expectedError error - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "ErrPdfSplitModeNotSupported", - expectError: true, - expectedError: gotenberg.ErrPdfSplitModeNotSupported, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrPdfSplitModeNotSupported (no unify with pages)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1", Unify: false}, - expectError: true, - expectedError: gotenberg.ErrPdfSplitModeNotSupported, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid context", - ctx: nil, - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - expectError: true, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "", - expectError: true, - expectOutputPathsCount: 0, - }, - { - scenario: "success (pages & unify)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - expectError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(PdfTk) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - if tc.expectOutputPathsCount != len(outputPaths) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths)) - } - }) - } -} - -func TestPdfTk_Flatten(t *testing.T) { - engine := new(PdfTk) - err := engine.Flatten(context.TODO(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestPdfTk_Convert(t *testing.T) { - engine := new(PdfTk) - err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) { - engine := new(PdfTk) - _, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) { - engine := new(PdfTk) - err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} diff --git a/pkg/modules/prometheus/prometheus_test.go b/pkg/modules/prometheus/prometheus_test.go deleted file mode 100644 index 02816b0f7..000000000 --- a/pkg/modules/prometheus/prometheus_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package prometheus - -import ( - "errors" - "reflect" - "testing" - "time" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestPrometheus_Descriptor(t *testing.T) { - descriptor := new(Prometheus).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(Prometheus)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestPrometheus_Provision(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectMetrics []gotenberg.Metric - expectError bool - }{ - { - scenario: "disable collect", - ctx: func() *gotenberg.Context { - fs := new(Prometheus).Descriptor().FlagSet - err := fs.Parse([]string{"--prometheus-disable-collect=true"}) - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: fs, - }, - nil, - ) - }(), - expectError: false, - }, - { - scenario: "invalid metrics provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.MetricsProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ValidateMock = func() error { - return errors.New("foo") - } - mod.MetricsMock = func() ([]gotenberg.Metric, error) { - return nil, nil - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Prometheus).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "invalid metrics from metrics provider", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.MetricsProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ValidateMock = func() error { - return nil - } - mod.MetricsMock = func() ([]gotenberg.Metric, error) { - return nil, errors.New("foo") - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Prometheus).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectError: true, - }, - { - scenario: "provision success", - ctx: func() *gotenberg.Context { - mod := &struct { - gotenberg.ModuleMock - gotenberg.ValidatorMock - gotenberg.MetricsProviderMock - }{} - mod.DescriptorMock = func() gotenberg.ModuleDescriptor { - return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }} - } - mod.ValidateMock = func() error { - return nil - } - mod.MetricsMock = func() ([]gotenberg.Metric, error) { - return []gotenberg.Metric{ - { - Name: "foo", - Description: "Bar.", - }, - }, nil - } - return gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Prometheus).Descriptor().FlagSet, - }, - []gotenberg.ModuleDescriptor{ - mod.Descriptor(), - }, - ) - }(), - expectMetrics: []gotenberg.Metric{ - { - Name: "foo", - Description: "Bar.", - }, - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := new(Prometheus) - err := mod.Provision(tc.ctx) - - if !reflect.DeepEqual(mod.metrics, tc.expectMetrics) { - t.Fatalf("expected metrics %+v, but got: %+v", tc.expectMetrics, mod.metrics) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPrometheus_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - namespace string - metrics []gotenberg.Metric - disableCollect bool - expectError bool - }{ - { - scenario: "collect disabled", - namespace: "foo", - disableCollect: true, - expectError: false, - }, - { - scenario: "empty namespace", - namespace: "", - disableCollect: false, - expectError: true, - }, - { - scenario: "empty metric name", - namespace: "foo", - metrics: []gotenberg.Metric{ - { - Name: "", - }, - }, - disableCollect: false, - expectError: true, - }, - { - scenario: "nil read metric method", - namespace: "foo", - metrics: []gotenberg.Metric{ - { - Name: "foo", - Read: nil, - }, - }, - disableCollect: false, - expectError: true, - }, - { - scenario: "already registered metric", - namespace: "foo", - metrics: []gotenberg.Metric{ - { - Name: "foo", - Read: func() float64 { - return 0 - }, - }, - { - Name: "foo", - Read: func() float64 { - return 0 - }, - }, - }, - disableCollect: false, - expectError: true, - }, - { - scenario: "validate success", - namespace: "foo", - metrics: []gotenberg.Metric{ - { - Name: "foo", - Read: func() float64 { - return 0 - }, - }, - { - Name: "bar", - Read: func() float64 { - return 0 - }, - }, - }, - disableCollect: false, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := &Prometheus{ - namespace: tc.namespace, - metrics: tc.metrics, - disableCollect: tc.disableCollect, - } - err := mod.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestPrometheus_Start(t *testing.T) { - for _, tc := range []struct { - scenario string - metrics []gotenberg.Metric - disableCollect bool - }{ - { - scenario: "collect disabled", - disableCollect: true, - }, - { - scenario: "start success", - metrics: []gotenberg.Metric{ - { - Name: "foo", - Read: func() float64 { - return 0 - }, - }, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := &Prometheus{ - namespace: "foo", - interval: time.Duration(1) * time.Second, - metrics: tc.metrics, - disableCollect: tc.disableCollect, - registry: prometheus.NewRegistry(), - } - - err := mod.Start() - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - }) - } -} - -func TestPrometheus_StartupMessage(t *testing.T) { - mod := new(Prometheus) - - mod.disableCollect = true - disableCollectMsg := mod.StartupMessage() - - mod.disableCollect = false - noDisableCollectMsg := mod.StartupMessage() - - if disableCollectMsg == noDisableCollectMsg { - t.Errorf("expected differrent startup messages if collect is disabled or not, but got '%s'", disableCollectMsg) - } -} - -func TestPrometheus_Stop(t *testing.T) { - err := new(Prometheus).Stop(nil) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestPrometheus_Routes(t *testing.T) { - for _, tc := range []struct { - scenario string - disableCollect bool - expectRoutes int - }{ - { - scenario: "collect disabled", - disableCollect: true, - expectRoutes: 0, - }, - { - scenario: "routes not disabled", - disableCollect: false, - expectRoutes: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - mod := &Prometheus{ - disableCollect: tc.disableCollect, - registry: prometheus.NewRegistry(), - } - - routes, err := mod.Routes() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectRoutes != len(routes) { - t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes)) - } - }) - } -} diff --git a/pkg/modules/qpdf/qpdf_test.go b/pkg/modules/qpdf/qpdf_test.go deleted file mode 100644 index 212a2cc4c..000000000 --- a/pkg/modules/qpdf/qpdf_test.go +++ /dev/null @@ -1,425 +0,0 @@ -package qpdf - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "reflect" - "testing" - - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestQPdf_Descriptor(t *testing.T) { - descriptor := new(QPdf).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(QPdf)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestQPdf_Provision(t *testing.T) { - engine := new(QPdf) - ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil) - - err := engine.Provision(ctx) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestQPdf_Validate(t *testing.T) { - for _, tc := range []struct { - scenario string - binPath string - expectError bool - }{ - { - scenario: "empty bin path", - binPath: "", - expectError: true, - }, - { - scenario: "bin path does not exist", - binPath: "/foo", - expectError: true, - }, - { - scenario: "validate success", - binPath: os.Getenv("QPDF_BIN_PATH"), - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(QPdf) - engine.binPath = tc.binPath - err := engine.Validate() - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestQPdf_Debug(t *testing.T) { - for _, tc := range []struct { - scenario string - engine *QPdf - expect map[string]interface{} - doNotExpect map[string]interface{} - }{ - { - scenario: "cannot determine version", - engine: &QPdf{ - binPath: "foo", - }, - expect: map[string]interface{}{ - "version": `exec: "foo": executable file not found in $PATH`, - }, - }, - { - scenario: "success", - engine: &QPdf{ - binPath: "echo", - }, - doNotExpect: map[string]interface{}{ - "version": `exec: "echo": executable file not found in $PATH`, - }, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - d := tc.engine.Debug() - - if tc.expect != nil { - if !reflect.DeepEqual(d, tc.expect) { - t.Errorf("expected '%v' but got '%v'", tc.expect, d) - } - } - - if tc.doNotExpect != nil { - if reflect.DeepEqual(d, tc.doNotExpect) { - t.Errorf("did not expect '%v'", d) - } - } - }) - } -} - -func TestQPdf_Merge(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - inputPaths []string - expectError bool - }{ - { - scenario: "invalid context", - ctx: nil, - expectError: true, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - inputPaths: []string{ - "foo", - }, - expectError: true, - }, - { - scenario: "single file success", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - }, - expectError: false, - }, - { - scenario: "many files success", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - "/tests/test/testdata/pdfengines/sample2.pdf", - }, - expectError: false, - }, - { - scenario: "success even with warnings", - ctx: context.TODO(), - inputPaths: []string{ - "/tests/test/testdata/pdfengines/sample1.pdf", - "/tests/test/testdata/pdfengines/sample5.pdf", - }, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(QPdf) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - err = engine.Merge(tc.ctx, zap.NewNop(), tc.inputPaths, outputDir+"/foo.pdf") - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestQPdf_Split(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - mode gotenberg.SplitMode - inputPath string - expectError bool - expectedError error - expectOutputPathsCount int - expectOutputPaths []string - }{ - { - scenario: "ErrPdfSplitModeNotSupported", - expectError: true, - expectedError: gotenberg.ErrPdfSplitModeNotSupported, - expectOutputPathsCount: 0, - }, - { - scenario: "ErrPdfSplitModeNotSupported (no unify with pages)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1", Unify: false}, - expectError: true, - expectedError: gotenberg.ErrPdfSplitModeNotSupported, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid context", - ctx: nil, - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - expectError: true, - expectOutputPathsCount: 0, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "", - expectError: true, - expectOutputPathsCount: 0, - }, - { - scenario: "success (pages & unify)", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "/tests/test/testdata/pdfengines/sample1.pdf", - expectError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "success even with warnings", - ctx: context.TODO(), - mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true}, - inputPath: "/tests/test/testdata/pdfengines/sample5.pdf", - expectError: false, - expectOutputPathsCount: 1, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(QPdf) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - - if tc.expectedError != nil && !errors.Is(err, tc.expectedError) { - t.Fatalf("expected error %v but got: %v", tc.expectedError, err) - } - - if tc.expectOutputPathsCount != len(outputPaths) { - t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths)) - } - }) - } -} - -func TestQPdf_Flatten(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx context.Context - inputPath string - createCopy bool - expectError bool - }{ - { - scenario: "invalid context", - ctx: nil, - expectError: true, - }, - { - scenario: "invalid input path", - ctx: context.TODO(), - inputPath: "foo.pdf", - expectError: true, - }, - { - scenario: "success", - ctx: context.TODO(), - inputPath: "/tests/test/testdata/pdfengines/sample3.pdf", - createCopy: true, - expectError: false, - }, - { - scenario: "success even with warnings", - ctx: context.TODO(), - inputPath: "/tests/test/testdata/pdfengines/sample5.pdf", - createCopy: true, - expectError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - engine := new(QPdf) - err := engine.Provision(nil) - if err != nil { - t.Fatalf("expected error but got: %v", err) - } - - var destinationPath string - if tc.createCopy { - fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) - outputDir, err := fs.MkdirAll() - if err != nil { - t.Fatalf("expected error no but got: %v", err) - } - - defer func() { - err = os.RemoveAll(fs.WorkingDirPath()) - if err != nil { - t.Fatalf("expected no error while cleaning up but got: %v", err) - } - }() - - destinationPath = fmt.Sprintf("%s/copy_temp.pdf", outputDir) - source, err := os.Open(tc.inputPath) - if err != nil { - t.Fatalf("open source file: %v", err) - } - - defer func(source *os.File) { - err := source.Close() - if err != nil { - t.Fatalf("close file: %v", err) - } - }(source) - - destination, err := os.Create(destinationPath) - if err != nil { - t.Fatalf("create destination file: %v", err) - } - - defer func(destination *os.File) { - err := destination.Close() - if err != nil { - t.Fatalf("close file: %v", err) - } - }(destination) - - _, err = io.Copy(destination, source) - if err != nil { - t.Fatalf("copy source into destination: %v", err) - } - } else { - destinationPath = tc.inputPath - } - - err = engine.Flatten(tc.ctx, zap.NewNop(), destinationPath) - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectError && err == nil { - t.Fatal("expected error but got none") - } - }) - } -} - -func TestQPdf_Convert(t *testing.T) { - engine := new(QPdf) - err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) { - engine := new(QPdf) - _, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} - -func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) { - engine := new(QPdf) - err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "") - - if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) { - t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err) - } -} diff --git a/pkg/modules/webhook/middleware_test.go b/pkg/modules/webhook/middleware_test.go deleted file mode 100644 index 8940f4069..000000000 --- a/pkg/modules/webhook/middleware_test.go +++ /dev/null @@ -1,582 +0,0 @@ -package webhook - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "math/rand" - "mime/multipart" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/dlclark/regexp2" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - - "github.com/gotenberg/gotenberg/v8/pkg/modules/api" -) - -func TestWebhookMiddlewareGuards(t *testing.T) { - buildMultipartFormDataRequest := func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - - err := writer.WriteField("foo", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - - return req - } - - buildWebhookModule := func() *Webhook { - return &Webhook{ - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - errorAllowList: regexp2.MustCompile("", 0), - errorDenyList: regexp2.MustCompile("", 0), - maxRetry: 0, - retryMinWait: 0, - retryMaxWait: 0, - disable: false, - } - } - - for _, tc := range []struct { - scenario string - request *http.Request - mod *Webhook - next echo.HandlerFunc - noDeadline bool - expectError bool - expectHttpError bool - expectHttpStatus int - }{ - { - scenario: "no webhook URL, skip middleware", - request: buildMultipartFormDataRequest(), - mod: buildWebhookModule(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return nil - } - }(), - noDeadline: false, - expectError: false, - expectHttpError: false, - }, - { - scenario: "no webhook error URL", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "context has no deadline", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - return req - }(), - mod: buildWebhookModule(), - noDeadline: true, - expectError: true, - }, - { - scenario: "webhook URL is not allowed", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - return req - }(), - mod: func() *Webhook { - mod := buildWebhookModule() - mod.allowList = regexp2.MustCompile("bar", 0) - return mod - }(), - noDeadline: false, - expectError: true, - }, - { - scenario: "webhook URL is denied", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - return req - }(), - mod: func() *Webhook { - mod := buildWebhookModule() - mod.denyList = regexp2.MustCompile("foo", 0) - return mod - }(), - noDeadline: false, - expectError: true, - }, - { - scenario: "webhook error URL is not allowed", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - return req - }(), - mod: func() *Webhook { - mod := buildWebhookModule() - mod.errorAllowList = regexp2.MustCompile("foo", 0) - return mod - }(), - noDeadline: false, - expectError: true, - }, - { - scenario: "webhook error URL is denied", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - return req - }(), - mod: func() *Webhook { - mod := buildWebhookModule() - mod.errorDenyList = regexp2.MustCompile("bar", 0) - return mod - }(), - noDeadline: false, - expectError: true, - }, - { - scenario: "invalid webhook method (GET)", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Method", http.MethodGet) - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid webhook error method (GET)", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "valid webhook method (POST) but invalid webhook error method (GET)", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Method", http.MethodPost) - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "valid webhook method (PATH) but invalid webhook error method (GET)", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Method", http.MethodPatch) - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "valid webhook method (PUT) but invalid webhook error method (GET)", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Method", http.MethodPut) - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - { - scenario: "invalid webhook extra HTTP headers", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Webhook-Url", "foo") - req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") - req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", "foo") - return req - }(), - mod: buildWebhookModule(), - noDeadline: false, - expectError: true, - expectHttpError: true, - expectHttpStatus: http.StatusBadRequest, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(tc.request, httptest.NewRecorder()) - - if tc.noDeadline { - ctx := &api.ContextMock{Context: &api.Context{Context: context.Background()}} - ctx.SetEchoContext(c) - c.Set("context", ctx.Context) - c.Set("cancel", func() context.CancelFunc { - return nil - }()) - } else { - timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second) - ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}} - ctx.SetEchoContext(c) - c.Set("context", ctx.Context) - c.Set("cancel", cancel) - } - - err := webhookMiddleware(tc.mod).Handler(tc.next)(c) - - if tc.expectError && err == nil { - t.Fatal("expected error but got none", err) - } - - if !tc.expectError && err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - var httpErr api.HttpError - isHttpError := errors.As(err, &httpErr) - - if tc.expectHttpError && !isHttpError { - t.Errorf("expected an HTTP error but got: %v", err) - } - - if !tc.expectHttpError && isHttpError { - t.Errorf("expected no HTTP error but got one: %v", httpErr) - } - - if err != nil && tc.expectHttpError && isHttpError { - status, _ := httpErr.HttpError() - if status != tc.expectHttpStatus { - t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status) - } - } - }) - } -} - -func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) { - buildMultipartFormDataRequest := func() *http.Request { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - defer func() { - err := writer.Close() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - }() - - err := writer.WriteField("foo", "foo") - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - req := httptest.NewRequest(http.MethodPost, "/", body) - req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) - - return req - } - - buildWebhookModule := func() *Webhook { - return &Webhook{ - allowList: regexp2.MustCompile("", 0), - denyList: regexp2.MustCompile("", 0), - errorAllowList: regexp2.MustCompile("", 0), - errorDenyList: regexp2.MustCompile("", 0), - maxRetry: 0, - retryMinWait: 0, - retryMaxWait: 0, - clientTimeout: time.Duration(30) * time.Second, - disable: false, - } - } - - for _, tc := range []struct { - scenario string - request *http.Request - mod *Webhook - next echo.HandlerFunc - expectWebhookContentType string - expectWebhookMethod string - expectWebhookExtraHttpHeaders map[string]string - expectWebhookFilename string - expectWebhookErrorStatus int - expectWebhookErrorMessage string - returnedError *echo.HTTPError - }{ - { - scenario: "next handler return an error", - request: buildMultipartFormDataRequest(), - mod: buildWebhookModule(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return errors.New("foo") - } - }(), - expectWebhookContentType: echo.MIMEApplicationJSON, - expectWebhookMethod: http.MethodPost, - expectWebhookErrorStatus: http.StatusInternalServerError, - expectWebhookErrorMessage: http.StatusText(http.StatusInternalServerError), - }, - { - scenario: "next handler return an HTTP error", - request: buildMultipartFormDataRequest(), - mod: buildWebhookModule(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - return api.NewSentinelHttpError(http.StatusBadRequest, http.StatusText(http.StatusBadRequest)) - } - }(), - expectWebhookContentType: echo.MIMEApplicationJSON, - expectWebhookMethod: http.MethodPost, - expectWebhookErrorStatus: http.StatusBadRequest, - expectWebhookErrorMessage: http.StatusText(http.StatusBadRequest), - }, - { - scenario: "success", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Output-Filename", "foo") - req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", `{ "foo": "bar" }`) - return req - }(), - mod: buildWebhookModule(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - ctx := c.Get("context").(*api.Context) - return ctx.AddOutputPaths("/tests/test/testdata/api/sample2.pdf") - } - }(), - expectWebhookContentType: "application/pdf", - expectWebhookMethod: http.MethodPost, - expectWebhookFilename: "foo", - expectWebhookExtraHttpHeaders: map[string]string{"foo": "bar"}, - }, - { - scenario: "success (return an error)", - request: func() *http.Request { - req := buildMultipartFormDataRequest() - req.Header.Set("Gotenberg-Output-Filename", "foo") - return req - }(), - mod: buildWebhookModule(), - next: func() echo.HandlerFunc { - return func(c echo.Context) error { - ctx := c.Get("context").(*api.Context) - return ctx.AddOutputPaths("/tests/test/testdata/api/sample1.pdf") - } - }(), - returnedError: echo.ErrInternalServerError, - expectWebhookContentType: echo.MIMEApplicationJSON, - expectWebhookMethod: http.MethodPost, - expectWebhookErrorStatus: http.StatusInternalServerError, - expectWebhookErrorMessage: http.StatusText(http.StatusInternalServerError), - expectWebhookFilename: "foo", - }, - } { - func() { - srv := echo.New() - srv.HideBanner = true - srv.HidePort = true - - c := srv.NewContext(tc.request, httptest.NewRecorder()) - c.Set("logger", zap.NewNop()) - c.Set("traceHeader", "Gotenberg-Trace") - c.Set("trace", "foo") - c.Set("startTime", time.Now()) - - timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second) - ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}} - ctx.SetLogger(zap.NewNop()) - ctx.SetEchoContext(c) - - c.Set("context", ctx.Context) - c.Set("cancel", cancel) - - webhook := echo.New() - webhook.HideBanner = true - webhook.HidePort = true - webhookPort := rand.Intn(65535-1025+1) + 1025 - - c.Request().Header.Set("Gotenberg-Webhook-Url", fmt.Sprintf("http://localhost:%d/", webhookPort)) - c.Request().Header.Set("Gotenberg-Webhook-Error-Url", fmt.Sprintf("http://localhost:%d/", webhookPort)) - - errChan := make(chan error, 1) - - webhook.POST( - "/", - func() echo.HandlerFunc { - return func(c echo.Context) error { - contentType := c.Request().Header.Get(echo.HeaderContentType) - if contentType != tc.expectWebhookContentType { - t.Errorf("expected '%s' '%s' but got '%s'", echo.HeaderContentType, tc.expectWebhookContentType, contentType) - } - - trace := c.Request().Header.Get("Gotenberg-Trace") - if trace != "foo" { - t.Errorf("expected '%s' '%s' but got '%s'", "Gotenberg-Trace", "foo", trace) - } - - method := c.Request().Method - if method != tc.expectWebhookMethod { - t.Errorf("expected HTTP method '%s' but got '%s'", tc.expectWebhookMethod, method) - } - - for key, expect := range tc.expectWebhookExtraHttpHeaders { - actual := c.Request().Header.Get(key) - - if actual != expect { - t.Errorf("expected '%s' '%s' but got '%s'", key, expect, actual) - } - } - - if contentType == echo.MIMEApplicationJSON { - body, err := io.ReadAll(c.Request().Body) - if err != nil { - errChan <- err - return nil - } - - result := struct { - Status int `json:"status"` - Message string `json:"message"` - }{} - - err = json.Unmarshal(body, &result) - if err != nil { - errChan <- err - return nil - } - - if result.Status != tc.expectWebhookErrorStatus { - t.Errorf("expected status %d from JSON but got %d", tc.expectWebhookErrorStatus, result.Status) - } - - if result.Message != tc.expectWebhookErrorMessage { - t.Errorf("expected message '%s' from JSON but got '%s'", tc.expectWebhookErrorMessage, result.Message) - } - - errChan <- nil - return nil - } - - contentLength := c.Request().Header.Get(echo.HeaderContentLength) - if contentLength == "" { - t.Errorf("expected non empty '%s'", echo.HeaderContentLength) - } - - contentDisposition := c.Request().Header.Get(echo.HeaderContentDisposition) - if !strings.Contains(contentDisposition, tc.expectWebhookFilename) { - t.Errorf("expected '%s' '%s' to contain '%s'", echo.HeaderContentDisposition, contentDisposition, tc.expectWebhookFilename) - } - - body, err := io.ReadAll(c.Request().Body) - if err != nil { - errChan <- err - return nil - } - - if body == nil || len(body) == 0 { - t.Error("expected non nil body") - } - - errChan <- nil - - if tc.returnedError != nil { - return tc.returnedError - } - - return nil - } - }(), - ) - - go func() { - err := webhook.Start(fmt.Sprintf(":%d", webhookPort)) - if !errors.Is(err, http.ErrServerClosed) { - t.Errorf("expected no error but got: %v", err) - } - }() - - defer func() { - err := webhook.Shutdown(context.TODO()) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - }() - - err := webhookMiddleware(tc.mod).Handler(tc.next)(c) - if err != nil && !errors.Is(err, api.ErrAsyncProcess) { - t.Errorf("expected no error but got: %v", err) - } - - err = <-errChan - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - }() - } -} diff --git a/pkg/modules/webhook/webhook_test.go b/pkg/modules/webhook/webhook_test.go deleted file mode 100644 index 03928046e..000000000 --- a/pkg/modules/webhook/webhook_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package webhook - -import ( - "reflect" - "testing" - - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" -) - -func TestWebhook_Descriptor(t *testing.T) { - descriptor := new(Webhook).Descriptor() - - actual := reflect.TypeOf(descriptor.New()) - expect := reflect.TypeOf(new(Webhook)) - - if actual != expect { - t.Errorf("expected '%s' but got '%s'", expect, actual) - } -} - -func TestWebhook_Provision(t *testing.T) { - mod := new(Webhook) - ctx := gotenberg.NewContext( - gotenberg.ParsedFlags{ - FlagSet: new(Webhook).Descriptor().FlagSet, - }, - nil, - ) - - err := mod.Provision(ctx) - if err != nil { - t.Errorf("expected no error but got: %v", err) - } -} - -func TestWebhook_Middlewares(t *testing.T) { - for _, tc := range []struct { - scenario string - disable bool - expectMiddlewares int - }{ - { - scenario: "webhook disabled", - disable: true, - expectMiddlewares: 0, - }, - { - scenario: "webhook enabled", - disable: false, - expectMiddlewares: 1, - }, - } { - mod := new(Webhook) - mod.disable = tc.disable - - middlewares, err := mod.Middlewares() - if err != nil { - t.Fatalf("expected no error but got: %v", err) - } - - if tc.expectMiddlewares != len(middlewares) { - t.Errorf("expected %d middlewares but got %d", tc.expectMiddlewares, len(middlewares)) - } - } -} diff --git a/test/Dockerfile b/test/Dockerfile deleted file mode 100644 index a0dad3ff4..000000000 --- a/test/Dockerfile +++ /dev/null @@ -1,50 +0,0 @@ -ARG GOLANG_VERSION -ARG DOCKER_REGISTRY -ARG DOCKER_REPOSITORY -ARG GOTENBERG_VERSION -ARG GOLANGCI_LINT_VERSION - -FROM golang:$GOLANG_VERSION-bookworm AS golang - -# We're extending the Gotenberg's Docker image because our code relies on external -# dependencies like Google Chrome, LibreOffice, etc. -FROM $DOCKER_REGISTRY/$DOCKER_REPOSITORY:$GOTENBERG_VERSION - -USER root - -ENV GOPATH=/go -ENV PATH=$GOPATH/bin:/usr/local/go/bin:$PATH -ENV CGO_ENABLED=1 - -COPY --from=golang /usr/local/go /usr/local/go - -RUN apt-get update -qq &&\ - apt-get upgrade -yqq &&\ - apt-get install -y -qq --no-install-recommends \ - sudo \ - # gcc for cgo. - g++ \ - gcc \ - libc6-dev \ - make \ - pkg-config &&\ - rm -rf /var/lib/apt/lists/* &&\ - mkdir -p "$GOPATH/src" "$GOPATH/bin" &&\ - chmod -R 777 "$GOPATH" &&\ - adduser gotenberg sudo &&\ - echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers &&\ - # We cannot use $PATH in the next command (print $PATH instead of the environment variable value). - sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/go/bin:/usr/local/go/bin#g' /etc/sudoers &&\ - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin $GOLANGCI_LINT_VERSION &&\ - go install mvdan.cc/gofumpt@latest &&\ - go install github.com/daixiang0/gci@latest - -COPY ./test/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh -COPY ./test/golint.sh /usr/bin/golint -COPY ./test/gotest.sh /usr/bin/gotest -COPY ./test/gotodos.sh /usr/bin/gotodos - -# Pristine working directory. -WORKDIR /tests - -ENTRYPOINT [ "/usr/bin/tini", "--", "docker-entrypoint.sh" ] \ No newline at end of file diff --git a/test/docker-entrypoint.sh b/test/docker-entrypoint.sh deleted file mode 100755 index 4862f516d..000000000 --- a/test/docker-entrypoint.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -# This entrypoint allows us to set the UID and GID of the host user so that -# our testing environment does not override files permissions from the host. -# Credits: https://github.com/thecodingmachine/docker-images-php. - -set +e -mkdir -p testing_file_system_rights.foo -chmod 700 testing_file_system_rights.foo -su gotenberg -c "touch testing_file_system_rights.foo/foo > /dev/null 2>&1" -HAS_CONSISTENT_RIGHTS=$? - -if [[ "$HAS_CONSISTENT_RIGHTS" != "0" ]]; then - # If not specified, the DOCKER_USER is the owner of the current working directory (heuristic!). - DOCKER_USER=$(stat -c '%u' "$(pwd)") -else - # macOs or Windows. - # Note: in most cases, we don't care about the rights (they are not respected). - FILE_OWNER=$(stat -c '%u' "testing_file_system_rights.foo/foo") - if [[ "$FILE_OWNER" == "0" ]]; then - # If root, we are likely on a Windows host. - # All files will belong to root, but it does not matter as everybody can write/delete - # those (0777 access rights). - DOCKER_USER=gotenberg - else - # In case of a NFS mount (common on macOS), the created files will belong to the NFS user. - DOCKER_USER=$FILE_OWNER - fi -fi - -rm -rf testing_file_system_rights.foo -set -e -unset HAS_CONSISTENT_RIGHTS - -# Note: DOCKER_USER is either a username (if the user exists in the container), -# otherwise a user ID (a user from the host). - -# DOCKER_USER is an ID. -if [[ "$DOCKER_USER" =~ ^[0-9]+$ ]] ; then - # Let's change the gotenberg user's ID in order to match this free ID. - usermod -u "$DOCKER_USER" -G sudo gotenberg - DOCKER_USER=gotenberg -fi - -DOCKER_USER_ID=$(id -ur $DOCKER_USER) - -# Fix access rights to stdout and stderr. -set +e -chown $DOCKER_USER /proc/self/fd/{1,2} -set -e - -# Install modules. -set -x -go mod download -go mod tidy -set +x - -# Run the command with the correct user. -exec "sudo" "-E" "-H" "-u" "#$DOCKER_USER_ID" "$@" diff --git a/test/golint.sh b/test/golint.sh deleted file mode 100755 index 107c7018c..000000000 --- a/test/golint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -set -x - -golangci-lint run \ No newline at end of file diff --git a/test/gotest.sh b/test/gotest.sh deleted file mode 100755 index 99bb64332..000000000 --- a/test/gotest.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -x - -go test -race -covermode=atomic -coverprofile=/tests/coverage.txt ./... -RESULT=$? - -go tool cover -html=coverage.txt -o /tests/coverage.html - -exit $RESULT \ No newline at end of file diff --git a/test/gotodos.sh b/test/gotodos.sh deleted file mode 100755 index c6201369c..000000000 --- a/test/gotodos.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -x - -golangci-lint run \ - --no-config \ - --disable-all \ - --enable godox \ No newline at end of file diff --git a/test/integration/doc.go b/test/integration/doc.go new file mode 100644 index 000000000..d52de84a7 --- /dev/null +++ b/test/integration/doc.go @@ -0,0 +1,2 @@ +// Package integration contains everything related to integration testing. +package integration diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature new file mode 100644 index 000000000..4f615c74b --- /dev/null +++ b/test/integration/features/chromium_convert_html.feature @@ -0,0 +1,852 @@ +Feature: /forms/chromium/convert/html + + Scenario: POST /forms/chromium/convert/html (Default) + 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 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/chromium/convert/html (Single Page) + 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/pages-12-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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 12 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + Page 12 + """ + 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/pages-12-html/index.html | file | + | singlePage | 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 + 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 + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + # page-break-after: always; tells the browser's print engine to force a page break after each element, + # even when calculating a large enough paper height, Chromium's PDF rendering will still honor those page break + # directives. + """ + Page 12 + """ + + Scenario: POST /forms/chromium/convert/html (Landscape) + 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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should NOT be set to landscape orientation + 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 | + | landscape | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should be set to landscape orientation + + Scenario: POST /forms/chromium/convert/html (Native Page Ranges) + 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/pages-12-html/index.html | file | + | nativePageRanges | 2-3 | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/html (Header & Footer) + 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/pages-12-html/index.html | file | + | files | testdata/header-footer-html/header.html | file | + | files | testdata/header-footer-html/footer.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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 12 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Pages 12 + """ + Then the "foo.pdf" PDF should have the following content at page 1: + """ + 1 of 12 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + Pages 12 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + 12 of 12 + """ + + Scenario: POST /forms/chromium/convert/html (Wait Delay) + 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/feature-rich-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 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 NOT have the following content at page 1: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + 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/feature-rich-html/index.html | file | + | waitDelay | 2.5s | 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 + 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: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + + Scenario: POST /forms/chromium/convert/html (Wait For Expression) + 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/feature-rich-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 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 NOT have the following content at page 1: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + 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/feature-rich-html/index.html | file | + | waitForExpression | window.globalVar === 'ready' | 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 + 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: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + + Scenario: POST /forms/chromium/convert/html (Emulated Media Type) + 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/feature-rich-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 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: + """ + Emulated media type is 'print'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'screen'. + """ + 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/feature-rich-html/index.html | file | + | emulatedMediaType | print | 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 + 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: + """ + Emulated media type is 'print'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'screen'. + """ + 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/feature-rich-html/index.html | file | + | emulatedMediaType | screen | 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 + 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: + """ + Emulated media type is 'screen'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'print'. + """ + + Scenario: POST /forms/chromium/convert/html (Default Allow / Deny Lists) + 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/feature-rich-html/index.html | file | + 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 Gotenberg container should log the following entries: + | 'file:///etc/passwd' matches the expression from the denied list | + + Scenario: POST /forms/chromium/convert/html (Main URL does NOT match allowed list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | ^file:(?!//\\/tmp/).* | + 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/feature-rich-html/index.html | file | + Then the response status code should be 403 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Forbidden + """ + + Scenario: POST /forms/chromium/convert/html (Main URL does match denied list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | | + | CHROMIUM_DENY_LIST | ^file:///tmp.* | + 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/feature-rich-html/index.html | file | + Then the response status code should be 403 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Forbidden + """ + + Scenario: POST /forms/chromium/convert/html (Request does not match the allowed list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | ^file:///tmp.* | + 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/feature-rich-html/index.html | file | + 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 Gotenberg container should log the following entries: + | 'file:///etc/passwd' does not match the expression from the allowed list | + + Scenario: POST /forms/chromium/convert/html (JavaScript Enabled) + 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/feature-rich-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 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: + """ + JavaScript is enabled. + """ + + Scenario: POST /forms/chromium/convert/html (JavaScript Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_DISABLE_JAVASCRIPT | true | + 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/feature-rich-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 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 NOT have the following content at page 1: + """ + JavaScript is enabled. + """ + + Scenario: POST /forms/chromium/convert/html (Fail On Resource HTTP Status Codes) + 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/feature-rich-html/index.html | file | + | failOnResourceHttpStatusCodes | [499,599] | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid HTTP status code from resources: + https://httpstat.us/400 - 400: Bad Request + """ + + Scenario: POST /forms/chromium/convert/html (Fail On Resource Loading Failed) + 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/feature-rich-html/index.html | file | + | failOnResourceLoadingFailed | true | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Chromium failed to load resources + """ + Then the response body should contain string: + """ + resource Stylesheet: net::ERR_CONNECTION_REFUSED + """ + Then the response body should contain string: + """ + resource Stylesheet: net::ERR_FILE_NOT_FOUND + """ + + Scenario: POST /forms/chromium/convert/html (Fail On Console Exceptions) + 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/feature-rich-html/index.html | file | + | failOnConsoleExceptions | true | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Chromium console exceptions + """ + Then the response body should contain string: + """ + exception "Uncaught" (61:12): Error: Exception 1 + """ + Then the response body should contain string: + """ + exception "Uncaught" (65:12): Error: Exception 2 + """ + + Scenario: POST /forms/chromium/convert/html (Bad Request) + 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): + | singlePage | foo | field | + | paperWidth | foo | field | + | paperHeight | foo | field | + | marginTop | foo | field | + | marginBottom | foo | field | + | marginLeft | foo | field | + | marginRight | foo | field | + | preferCssPageSize | foo | field | + | generateDocumentOutline | foo | field | + | printBackground | foo | field | + | omitBackground | foo | field | + | landscape | foo | field | + | scale | foo | field | + | waitDelay | foo | field | + | emulatedMediaType | foo | field | + | failOnHttpStatusCodes | foo | field | + | failOnResourceHttpStatusCodes | foo | field | + | failOnResourceLoadingFailed | foo | field | + | failOnConsoleExceptions | foo | field | + | skipNetworkIdleEvent | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form file 'index.html' is required + """ + 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 | + | omitBackground | true | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + omitBackground requires printBackground set to true + """ + 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 | + | paperWidth | 0 | field | + | paperHeight | 0 | field | + | marginTop | 1000000 | field | + | marginBottom | 1000000 | field | + | marginLeft | 1000000 | field | + | marginRight | 1000000 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Chromium does not handle the provided settings; please check for aberrant form values + """ + 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 | + | nativePageRanges | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Chromium does not handle the page ranges 'foo' (nativePageRanges) + """ + 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 | + | waitForExpression | undefined | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + The expression 'undefined' (waitForExpression) returned an exception or undefined + """ + 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 | + | cookies | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'cookies' is invalid (got 'foo', resulting to unmarshal cookies: invalid character 'o' in literal false (expecting 'a')) + """ + 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 | + | cookies | [{"name":"yummy_cookie","value":"choco"}] | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'cookies' is invalid (got '[{"name":"yummy_cookie","value":"choco"}]', resulting to cookie 0 must have its name, value and domain set) + """ + 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 | + | extraHttpHeaders | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got 'foo', resulting to unmarshal extraHttpHeaders: invalid character 'o' in literal false (expecting 'a')) + """ + 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 | + | extraHttpHeaders | {"foo":"bar;scope;;"} | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got '{"foo":"bar;scope;;"}', resulting to invalid scope '' for header 'foo') + """ + 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 | + | extraHttpHeaders | {"foo":"bar;scope=*."} | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got '{"foo":"bar;scope=*."}', resulting to invalid scope regex pattern for header 'foo': error parsing regexp: missing argument to repetition operator in `*.`) + """ + 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 | + | splitMode | foo | field | + | splitSpan | 2 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitMode' is invalid (got 'foo', resulting to wrong value, expected either 'intervals' or 'pages') + """ + 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 | + | splitMode | intervals | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitSpan' is invalid (got 'foo', resulting to strconv.Atoi: parsing "foo": invalid syntax) + """ + 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 | + | splitMode | pages | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues + """ + 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 | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues + """ + 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 | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + 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 | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/chromium/convert/html (Split Intervals) + 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/pages-3-html/index.html | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 2 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/html (Split Pages) + 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/pages-3-html/index.html | file | + | splitMode | pages | field | + | splitSpan | 2- | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/html (Split Pages & Unify) + 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/pages-3-html/index.html | file | + | splitMode | pages | field | + | splitSpan | 2- | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/html (Split Many PDFs - Lot of Pages) + 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/pages-12-html/index.html | file | + | splitMode | intervals | field | + | splitSpan | 1 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 12 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + | *_2.pdf | + | *_3.pdf | + | *_4.pdf | + | *_5.pdf | + | *_6.pdf | + | *_7.pdf | + | *_8.pdf | + | *_9.pdf | + | *_10.pdf | + | *_11.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_11.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_11.pdf" PDF should have the following content at page 1: + """ + Page 12 + """ + + Scenario: POST /forms/chromium/convert/html (PDF/A-1b & PDF/UA-1) + 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 | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/chromium/convert/html (Split & PDF/A-1b & PDF/UA-1) + 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/pages-3-html/index.html | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 2 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/chromium/convert/html (Metadata) + 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 | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/chromium/convert/html (Flatten) + 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 | + | flatten | true | field | + 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 + + Scenario: POST /forms/chromium/convert/html (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + 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 | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | flatten | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 7 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be flatten + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/chromium/convert/html (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_DISABLE_ROUTES | true | + 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 | + Then the response status code should be 404 + + Scenario: POST /forms/chromium/convert/html (Gotenberg Trace) + 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-Trace | forms_chromium_convert_html | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_chromium_convert_html" + Then the Gotenberg container should log the following entries: + | "trace":"forms_chromium_convert_html" | + + Scenario: POST /forms/chromium/convert/html (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page-1-html/index.html","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + Then the response status code should be 200 + Then the file request header "X-Foo" should be "bar" + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/chromium/convert/html (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + 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 | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then there should be the following file(s) in the webhook request: + | 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/chromium/convert/html (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + 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 | + Then the response status code should be 401 + + Scenario: POST /foo/forms/chromium/convert/html (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/chromium/convert/html" endpoint with the following form data and header(s): + | 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" diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature new file mode 100644 index 000000000..286ea9338 --- /dev/null +++ b/test/integration/features/chromium_convert_markdown.feature @@ -0,0 +1,977 @@ +Feature: /forms/chromium/convert/markdown + + Scenario: POST /forms/chromium/convert/markdown (Default) + 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 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/chromium/convert/markdown (Single Page) + 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/pages-12-markdown/index.html | file | + | files | testdata/pages-12-markdown/page_1.md | file | + | files | testdata/pages-12-markdown/page_2.md | file | + | files | testdata/pages-12-markdown/page_3.md | file | + | files | testdata/pages-12-markdown/page_4.md | file | + | files | testdata/pages-12-markdown/page_5.md | file | + | files | testdata/pages-12-markdown/page_6.md | file | + | files | testdata/pages-12-markdown/page_7.md | file | + | files | testdata/pages-12-markdown/page_8.md | file | + | files | testdata/pages-12-markdown/page_9.md | file | + | files | testdata/pages-12-markdown/page_10.md | file | + | files | testdata/pages-12-markdown/page_11.md | file | + | files | testdata/pages-12-markdown/page_12.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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 12 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + Page 12 + """ + 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/pages-12-markdown/index.html | file | + | files | testdata/pages-12-markdown/page_1.md | file | + | files | testdata/pages-12-markdown/page_2.md | file | + | files | testdata/pages-12-markdown/page_3.md | file | + | files | testdata/pages-12-markdown/page_4.md | file | + | files | testdata/pages-12-markdown/page_5.md | file | + | files | testdata/pages-12-markdown/page_6.md | file | + | files | testdata/pages-12-markdown/page_7.md | file | + | files | testdata/pages-12-markdown/page_8.md | file | + | files | testdata/pages-12-markdown/page_9.md | file | + | files | testdata/pages-12-markdown/page_10.md | file | + | files | testdata/pages-12-markdown/page_11.md | file | + | files | testdata/pages-12-markdown/page_12.md | file | + | singlePage | 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 + 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 + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + # page-break-after: always; tells the browser's print engine to force a page break after each element, + # even when calculating a large enough paper height, Chromium's PDF rendering will still honor those page break + # directives. + """ + Page 12 + """ + + Scenario: POST /forms/chromium/convert/markdown (Landscape) + 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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should NOT be set to landscape orientation + 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 | + | landscape | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should be set to landscape orientation + + Scenario: POST /forms/chromium/convert/markdown (Native Page Ranges) + 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/pages-12-markdown/index.html | file | + | files | testdata/pages-12-markdown/page_1.md | file | + | files | testdata/pages-12-markdown/page_2.md | file | + | files | testdata/pages-12-markdown/page_3.md | file | + | files | testdata/pages-12-markdown/page_4.md | file | + | files | testdata/pages-12-markdown/page_5.md | file | + | files | testdata/pages-12-markdown/page_6.md | file | + | files | testdata/pages-12-markdown/page_7.md | file | + | files | testdata/pages-12-markdown/page_8.md | file | + | files | testdata/pages-12-markdown/page_9.md | file | + | files | testdata/pages-12-markdown/page_10.md | file | + | files | testdata/pages-12-markdown/page_11.md | file | + | files | testdata/pages-12-markdown/page_12.md | file | + | nativePageRanges | 2-3 | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/markdown (Header & Footer) + 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/pages-12-markdown/index.html | file | + | files | testdata/pages-12-markdown/page_1.md | file | + | files | testdata/pages-12-markdown/page_2.md | file | + | files | testdata/pages-12-markdown/page_3.md | file | + | files | testdata/pages-12-markdown/page_4.md | file | + | files | testdata/pages-12-markdown/page_5.md | file | + | files | testdata/pages-12-markdown/page_6.md | file | + | files | testdata/pages-12-markdown/page_7.md | file | + | files | testdata/pages-12-markdown/page_8.md | file | + | files | testdata/pages-12-markdown/page_9.md | file | + | files | testdata/pages-12-markdown/page_10.md | file | + | files | testdata/pages-12-markdown/page_11.md | file | + | files | testdata/pages-12-markdown/page_12.md | file | + | files | testdata/header-footer-html/header.html | file | + | files | testdata/header-footer-html/footer.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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 12 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Pages 12 + """ + Then the "foo.pdf" PDF should have the following content at page 1: + """ + 1 of 12 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + Pages 12 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + 12 of 12 + """ + + Scenario: POST /forms/chromium/convert/markdown (Wait Delay) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.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 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 NOT have the following content at page 1: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | waitDelay | 2.5s | 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 + 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: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + + Scenario: POST /forms/chromium/convert/markdown (Wait For Expression) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.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 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 NOT have the following content at page 1: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | waitForExpression | window.globalVar === 'ready' | 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 + 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: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + + Scenario: POST /forms/chromium/convert/markdown (Emulated Media Type) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.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 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: + """ + Emulated media type is 'print'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'screen'. + """ + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | emulatedMediaType | print | 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 + 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: + """ + Emulated media type is 'print'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'screen'. + """ + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | emulatedMediaType | screen | 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 + 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: + """ + Emulated media type is 'screen'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'print'. + """ + + Scenario: POST /forms/chromium/convert/markdown (Default Allow / Deny Lists) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + 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 Gotenberg container should log the following entries: + | 'file:///etc/passwd' matches the expression from the denied list | + + Scenario: POST /forms/chromium/convert/markdown (Main URL does NOT match allowed list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | ^file:(?!//\\/tmp/).* | + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + Then the response status code should be 403 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Forbidden + """ + + Scenario: POST /forms/chromium/convert/markdown (Main URL does match denied list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | | + | CHROMIUM_DENY_LIST | ^file:///tmp.* | + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + Then the response status code should be 403 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Forbidden + """ + + Scenario: POST /forms/chromium/convert/markdown (Request does not match the allowed list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | ^file:///tmp.* | + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + 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 Gotenberg container should log the following entries: + | 'file:///etc/passwd' does not match the expression from the allowed list | + + Scenario: POST /forms/chromium/convert/markdown (JavaScript Enabled) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.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 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: + """ + JavaScript is enabled. + """ + + Scenario: POST /forms/chromium/convert/markdown (JavaScript Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_DISABLE_JAVASCRIPT | true | + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.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 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 NOT have the following content at page 1: + """ + JavaScript is enabled. + """ + + Scenario: POST /forms/chromium/convert/markdown (Fail On Resource HTTP Status Codes) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | failOnResourceHttpStatusCodes | [499,599] | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid HTTP status code from resources: + https://httpstat.us/400 - 400: Bad Request + """ + + Scenario: POST /forms/chromium/convert/markdown (Fail On Resource Loading Failed) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | failOnResourceLoadingFailed | true | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Chromium failed to load resources + """ + Then the response body should contain string: + """ + resource Stylesheet: net::ERR_CONNECTION_REFUSED + """ + Then the response body should contain string: + """ + resource Stylesheet: net::ERR_FILE_NOT_FOUND + """ + + Scenario: POST /forms/chromium/convert/markdown (Fail On Console Exceptions) + 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/feature-rich-markdown/index.html | file | + | files | testdata/feature-rich-markdown/table.md | file | + | failOnConsoleExceptions | true | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Chromium console exceptions + """ + Then the response body should contain string: + """ + exception "Uncaught" (93:12): Error: Exception 1 + """ + Then the response body should contain string: + """ + exception "Uncaught" (97:12): Error: Exception 2 + """ + + Scenario: POST /forms/chromium/convert/markdown (Bad Request) + 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Markdown file(s) not found: 'page_2.md'; 'page_3.md' + """ + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/markdown" endpoint with the following form data and header(s): + | singlePage | foo | field | + | paperWidth | foo | field | + | paperHeight | foo | field | + | marginTop | foo | field | + | marginBottom | foo | field | + | marginLeft | foo | field | + | marginRight | foo | field | + | preferCssPageSize | foo | field | + | generateDocumentOutline | foo | field | + | printBackground | foo | field | + | omitBackground | foo | field | + | landscape | foo | field | + | scale | foo | field | + | waitDelay | foo | field | + | emulatedMediaType | foo | field | + | failOnHttpStatusCodes | foo | field | + | failOnResourceHttpStatusCodes | foo | field | + | failOnResourceLoadingFailed | foo | field | + | failOnConsoleExceptions | foo | field | + | skipNetworkIdleEvent | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form file 'index.html' is required; no form file found for extensions: [.md] + """ + 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 | + | omitBackground | true | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + omitBackground requires printBackground set to true + """ + 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 | + | paperWidth | 0 | field | + | paperHeight | 0 | field | + | marginTop | 1000000 | field | + | marginBottom | 1000000 | field | + | marginLeft | 1000000 | field | + | marginRight | 1000000 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Chromium does not handle the provided settings; please check for aberrant form values + """ + 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 | + | nativePageRanges | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Chromium does not handle the page ranges 'foo' (nativePageRanges) + """ + 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 | + | waitForExpression | undefined | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + The expression 'undefined' (waitForExpression) returned an exception or undefined + """ + 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 | + | cookies | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'cookies' is invalid (got 'foo', resulting to unmarshal cookies: invalid character 'o' in literal false (expecting 'a')) + """ + 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 | + | cookies | [{"name":"yummy_cookie","value":"choco"}] | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'cookies' is invalid (got '[{"name":"yummy_cookie","value":"choco"}]', resulting to cookie 0 must have its name, value and domain set) + """ + 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 | + | extraHttpHeaders | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got 'foo', resulting to unmarshal extraHttpHeaders: invalid character 'o' in literal false (expecting 'a')) + """ + 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 | + | extraHttpHeaders | {"foo":"bar;scope;;"} | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got '{"foo":"bar;scope;;"}', resulting to invalid scope '' for header 'foo') + """ + 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 | + | extraHttpHeaders | {"foo":"bar;scope=*."} | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got '{"foo":"bar;scope=*."}', resulting to invalid scope regex pattern for header 'foo': error parsing regexp: missing argument to repetition operator in `*.`) + """ + 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 | + | splitMode | foo | field | + | splitSpan | 2 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitMode' is invalid (got 'foo', resulting to wrong value, expected either 'intervals' or 'pages') + """ + 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 | + | splitMode | intervals | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitSpan' is invalid (got 'foo', resulting to strconv.Atoi: parsing "foo": invalid syntax) + """ + 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 | + | splitMode | pages | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues + """ + 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 | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues + """ + 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 | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + 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 | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/chromium/convert/markdown (Split Intervals) + 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + | files | testdata/pages-3-markdown/page_2.md | file | + | files | testdata/pages-3-markdown/page_3.md | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 2 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/markdown (Split Pages) + 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + | files | testdata/pages-3-markdown/page_2.md | file | + | files | testdata/pages-3-markdown/page_3.md | file | + | splitMode | pages | field | + | splitSpan | 2- | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/markdown (Split Pages & Unify) + 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + | files | testdata/pages-3-markdown/page_2.md | file | + | files | testdata/pages-3-markdown/page_3.md | file | + | splitMode | pages | field | + | splitSpan | 2- | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/markdown (Split Many PDFs - Lot of Pages) + 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/pages-12-markdown/index.html | file | + | files | testdata/pages-12-markdown/page_1.md | file | + | files | testdata/pages-12-markdown/page_2.md | file | + | files | testdata/pages-12-markdown/page_3.md | file | + | files | testdata/pages-12-markdown/page_4.md | file | + | files | testdata/pages-12-markdown/page_5.md | file | + | files | testdata/pages-12-markdown/page_6.md | file | + | files | testdata/pages-12-markdown/page_7.md | file | + | files | testdata/pages-12-markdown/page_8.md | file | + | files | testdata/pages-12-markdown/page_9.md | file | + | files | testdata/pages-12-markdown/page_10.md | file | + | files | testdata/pages-12-markdown/page_11.md | file | + | files | testdata/pages-12-markdown/page_12.md | file | + | splitMode | intervals | field | + | splitSpan | 1 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 12 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + | *_2.pdf | + | *_3.pdf | + | *_4.pdf | + | *_5.pdf | + | *_6.pdf | + | *_7.pdf | + | *_8.pdf | + | *_9.pdf | + | *_10.pdf | + | *_11.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_11.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_11.pdf" PDF should have the following content at page 1: + """ + Page 12 + """ + + Scenario: POST /forms/chromium/convert/markdown (PDF/A-1b & PDF/UA-1) + 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 | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/chromium/convert/markdown (Split & PDF/A-1b & PDF/UA-1) + 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + | files | testdata/pages-3-markdown/page_2.md | file | + | files | testdata/pages-3-markdown/page_3.md | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 2 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/chromium/convert/markdown (Metadata) + 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 | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/chromium/convert/markdown (Flatten) + 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 | + | flatten | true | field | + 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 + + Scenario: POST /forms/chromium/convert/markdown (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + 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 | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | flatten | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 7 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be flatten + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/chromium/convert/markdown (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_DISABLE_ROUTES | true | + 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 | + Then the response status code should be 404 + + Scenario: POST /forms/chromium/convert/markdown (Gotenberg Trace) + 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-Trace | forms_chromium_convert_html | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_chromium_convert_html" + Then the Gotenberg container should log the following entries: + | "trace":"forms_chromium_convert_html" | + + Scenario: POST /forms/chromium/convert/markdown (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/markdown" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page-1-markdown/index.html","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + | files | testdata/page-1-markdown/page_1.md | file | + Then the response status code should be 200 + Then the file request header "X-Foo" should be "bar" + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/chromium/convert/markdown (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + 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 | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then there should be the following file(s) in the webhook request: + | 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/chromium/convert/markdown (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + 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 | + Then the response status code should be 401 + + Scenario: POST /foo/forms/chromium/convert/markdown (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/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 | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature new file mode 100644 index 000000000..b48c2f4ae --- /dev/null +++ b/test/integration/features/chromium_convert_url.feature @@ -0,0 +1,929 @@ +Feature: /forms/chromium/convert/url + + Scenario: POST /forms/chromium/convert/url (Default) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | 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 + 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/chromium/convert/url (Single Page) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-12-html/index.html | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 12 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + Page 12 + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-12-html/index.html | field | + | singlePage | 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 + 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 + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + # page-break-after: always; tells the browser's print engine to force a page break after each element, + # even when calculating a large enough paper height, Chromium's PDF rendering will still honor those page break + # directives. + """ + Page 12 + """ + + Scenario: POST /forms/chromium/convert/url (Landscape) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should NOT be set to landscape orientation + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | landscape | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should be set to landscape orientation + + Scenario: POST /forms/chromium/convert/url (Native Page Ranges) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-12-html/index.html | field | + | nativePageRanges | 2-3 | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/url (Header & Footer) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-12-html/index.html | field | + | files | testdata/header-footer-html/header.html | file | + | files | testdata/header-footer-html/footer.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 there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 12 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Pages 12 + """ + Then the "foo.pdf" PDF should have the following content at page 1: + """ + 1 of 12 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + Pages 12 + """ + Then the "foo.pdf" PDF should have the following content at page 12: + """ + 12 of 12 + """ + + Scenario: POST /forms/chromium/convert/url (Custom HTTP Headers) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | userAgent | Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) | field | + | extraHttpHeaders | {"X-Header":"foo","X-Scoped-Header-1":"bar;scope=https?:\\/\\/([a-zA-Z0-9-]+\\\\.)*domain\\\\.com\\/.*","X-Scoped-Header-2":"baz;scope=https?:\\/\\/([a-zA-Z0-9-]+\\\\.)*docker\\\\.internal:(\\\\d+)\\/.*"} | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the server request header "User-Agent" should be "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)" + Then the server request header "X-Header" should be "foo" + Then the server request header "X-Scoped-Header-1" should be "" + Then the server request header "X-Scoped-Header-2" should be "baz" + + Scenario: POST /forms/chromium/convert/url (Cookies) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | cookies | [{"name":"cookie_1","value":"foo","domain":"host.docker.internal:%d"},{"name":"cookie_2","value":"bar","domain":"domain.com"}] | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the server request cookie "cookie_1" should be "foo" + Then the server request cookie "cookie_2" should be "" + + Scenario: POST /forms/chromium/convert/url (Wait Delay) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | 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 + 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 NOT have the following content at page 1: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | waitDelay | 2.5s | 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 + 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: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + + Scenario: POST /forms/chromium/convert/url (Wait For Expression) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | 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 + 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 NOT have the following content at page 1: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | waitForExpression | window.globalVar === 'ready' | 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 + 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: + """ + Wait delay > 2 seconds or expression window globalVar === 'ready' returns true. + """ + + Scenario: POST /forms/chromium/convert/url (Emulated Media Type) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | 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 + 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: + """ + Emulated media type is 'print'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'screen'. + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | emulatedMediaType | print | 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 + 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: + """ + Emulated media type is 'print'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'screen'. + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | emulatedMediaType | screen | 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 + 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: + """ + Emulated media type is 'screen'. + """ + Then the "foo.pdf" PDF should NOT have the following content at page 1: + """ + Emulated media type is 'print'. + """ + + Scenario: POST /forms/chromium/convert/url (Default Allow / Deny Lists) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + 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 Gotenberg container should NOT log the following entries: + # Modern browsers block file URIs from being loaded into iframes when the parent page is served over HTTP/HTTPS. + | 'file:///etc/passwd' matches the expression from the denied list | + + Scenario: POST /forms/chromium/convert/url (Main URL does NOT match allowed list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | ^file:(?!//\\/tmp/).* | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + Then the response status code should be 403 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Forbidden + """ + + Scenario: POST /forms/chromium/convert/url (Main URL does match denied list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | | + | CHROMIUM_DENY_LIST | ^http.* | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + Then the response status code should be 403 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Forbidden + """ + + Scenario: POST /forms/chromium/convert/url (Request does not match the allowed list) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_ALLOW_LIST | ^.* | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + 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 Gotenberg container should NOT log the following entries: + # Modern browsers block file URIs from being loaded into iframes when the parent page is served over HTTP/HTTPS. + | 'file:///etc/passwd' does not match the expression from the allowed list | + + Scenario: POST /forms/chromium/convert/url (JavaScript Enabled) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | 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 + 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: + """ + JavaScript is enabled. + """ + + Scenario: POST /forms/chromium/convert/url (JavaScript Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_DISABLE_JAVASCRIPT | true | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | 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 + 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 NOT have the following content at page 1: + """ + JavaScript is enabled. + """ + + Scenario: POST /forms/chromium/convert/url (Fail On Resource HTTP Status Codes) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | failOnResourceHttpStatusCodes | [499,599] | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Invalid HTTP status code from resources: + """ + Then the response body should contain string: + """ + /favicon.ico - 404: Not Found + """ + + Scenario: POST /forms/chromium/convert/url (Fail On Resource Loading Failed) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | failOnResourceLoadingFailed | true | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Chromium failed to load resources + """ + Then the response body should contain string: + """ + resource Stylesheet: net::ERR_CONNECTION_REFUSED + """ + + Scenario: POST /forms/chromium/convert/url (Fail On Console Exceptions) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/feature-rich-html-remote/index.html | field | + | failOnConsoleExceptions | true | field | + Then the response status code should be 409 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should contain string: + """ + Chromium console exceptions + """ + Then the response body should contain string: + """ + exception "Uncaught" (56:12): Error: Exception 1 + """ + Then the response body should contain string: + """ + exception "Uncaught" (60:12): Error: Exception 2 + """ + + Scenario: POST /forms/chromium/convert/url (Bad Request) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | singlePage | foo | field | + | paperWidth | foo | field | + | paperHeight | foo | field | + | marginTop | foo | field | + | marginBottom | foo | field | + | marginLeft | foo | field | + | marginRight | foo | field | + | preferCssPageSize | foo | field | + | generateDocumentOutline | foo | field | + | printBackground | foo | field | + | omitBackground | foo | field | + | landscape | foo | field | + | scale | foo | field | + | waitDelay | foo | field | + | emulatedMediaType | foo | field | + | failOnHttpStatusCodes | foo | field | + | failOnResourceHttpStatusCodes | foo | field | + | failOnResourceLoadingFailed | foo | field | + | failOnConsoleExceptions | foo | field | + | skipNetworkIdleEvent | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'url' is required + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | omitBackground | true | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + omitBackground requires printBackground set to true + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | paperWidth | 0 | field | + | paperHeight | 0 | field | + | marginTop | 1000000 | field | + | marginBottom | 1000000 | field | + | marginLeft | 1000000 | field | + | marginRight | 1000000 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Chromium does not handle the provided settings; please check for aberrant form values + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | nativePageRanges | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Chromium does not handle the page ranges 'foo' (nativePageRanges) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | waitForExpression | undefined | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + The expression 'undefined' (waitForExpression) returned an exception or undefined + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | cookies | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'cookies' is invalid (got 'foo', resulting to unmarshal cookies: invalid character 'o' in literal false (expecting 'a')) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | cookies | [{"name":"yummy_cookie","value":"choco"}] | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'cookies' is invalid (got '[{"name":"yummy_cookie","value":"choco"}]', resulting to cookie 0 must have its name, value and domain set) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | extraHttpHeaders | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got 'foo', resulting to unmarshal extraHttpHeaders: invalid character 'o' in literal false (expecting 'a')) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | extraHttpHeaders | {"foo":"bar;scope;;"} | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got '{"foo":"bar;scope;;"}', resulting to invalid scope '' for header 'foo') + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | extraHttpHeaders | {"foo":"bar;scope=*."} | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'extraHttpHeaders' is invalid (got '{"foo":"bar;scope=*."}', resulting to invalid scope regex pattern for header 'foo': error parsing regexp: missing argument to repetition operator in `*.`) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | splitMode | foo | field | + | splitSpan | 2 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitMode' is invalid (got 'foo', resulting to wrong value, expected either 'intervals' or 'pages') + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | splitMode | intervals | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitSpan' is invalid (got 'foo', resulting to strconv.Atoi: parsing "foo": invalid syntax) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | splitMode | pages | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/chromium/convert/url (Split Intervals) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-3-html/index.html | field | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 2 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/url (Split Pages) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-3-html/index.html | field | + | splitMode | pages | field | + | splitSpan | 2- | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/url (Split Pages & Unify) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-3-html/index.html | field | + | splitMode | pages | field | + | splitSpan | 2- | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/chromium/convert/url (Split Many PDFs - Lot of Pages) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-12-html/index.html | field | + | splitMode | intervals | field | + | splitSpan | 1 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 12 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + | *_2.pdf | + | *_3.pdf | + | *_4.pdf | + | *_5.pdf | + | *_6.pdf | + | *_7.pdf | + | *_8.pdf | + | *_9.pdf | + | *_10.pdf | + | *_11.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_11.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_11.pdf" PDF should have the following content at page 1: + """ + Page 12 + """ + + Scenario: POST /forms/chromium/convert/url (PDF/A-1b & PDF/UA-1) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/chromium/convert/url (Split & PDF/A-1b & PDF/UA-1) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-3-html/index.html | field | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 2 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/chromium/convert/url (Metadata) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/chromium/convert/url (Flatten) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | flatten | true | field | + 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 + + Scenario: POST /forms/chromium/convert/url (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | flatten | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 7 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be flatten + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/chromium/convert/url (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | CHROMIUM_DISABLE_ROUTES | true | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + Then the response status code should be 404 + + Scenario: POST /forms/chromium/convert/url (Gotenberg Trace) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | Gotenberg-Trace | forms_chromium_convert_url | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_chromium_convert_url" + Then the Gotenberg container should log the following entries: + | "trace":"forms_chromium_convert_url" | + + Scenario: POST /forms/chromium/convert/url (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + | Gotenberg-Output-Filename | foo | header | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then there should be the following file(s) in the webhook request: + | 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/chromium/convert/url (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + Then the response status code should be 401 + + Scenario: POST /foo/forms/chromium/convert/url (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + Given I have a static server + When I make a "POST" request to Gotenberg at the "/foo/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" diff --git a/test/integration/features/debug.feature b/test/integration/features/debug.feature new file mode 100644 index 000000000..a68e5fe73 --- /dev/null +++ b/test/integration/features/debug.feature @@ -0,0 +1,147 @@ +Feature: /debug + + Scenario: GET /debug (Disabled) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/debug" endpoint + Then the response status code should be 404 + + Scenario: GET /debug (Enabled) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + When I make a "GET" request to Gotenberg at the "/debug" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "version": "{version}", + "architecture": "ignore", + "modules": [ + "api", + "chromium", + "exiftool", + "libreoffice", + "libreoffice-api", + "libreoffice-pdfengine", + "logging", + "pdfcpu", + "pdfengines", + "pdftk", + "prometheus", + "qpdf", + "webhook" + ], + "modules_additional_data": { + "chromium": { + "version": "ignore" + }, + "exiftool": { + "version": "ignore" + }, + "libreoffice-api": { + "version": "ignore" + }, + "pdfcpu": { + "version": "ignore" + }, + "pdftk": { + "version": "ignore" + }, + "qpdf": { + "version": "ignore" + } + }, + "flags": { + "api-bind-ip": "", + "api-body-limit": "", + "api-disable-download-from": "false", + "api-disable-health-check-logging": "false", + "api-download-from-allow-list": "", + "api-download-from-deny-list": "", + "api-download-from-max-retry": "4", + "api-enable-basic-auth": "false", + "api-enable-debug-route": "true", + "api-port": "3000", + "api-port-from-env": "", + "api-root-path": "/", + "api-start-timeout": "30s", + "api-timeout": "30s", + "api-tls-cert-file": "", + "api-tls-key-file": "", + "api-trace-header": "Gotenberg-Trace", + "chromium-allow-file-access-from-files": "false", + "chromium-allow-insecure-localhost": "false", + "chromium-allow-list": "", + "chromium-auto-start": "false", + "chromium-clear-cache": "false", + "chromium-clear-cookies": "false", + "chromium-deny-list": "^file:(?!//\\/tmp/).*", + "chromium-disable-javascript": "false", + "chromium-disable-routes": "false", + "chromium-disable-web-security": "false", + "chromium-host-resolver-rules": "", + "chromium-ignore-certificate-errors": "false", + "chromium-incognito": "false", + "chromium-max-queue-size": "0", + "chromium-proxy-server": "", + "chromium-restart-after": "10", + "chromium-start-timeout": "20s", + "gotenberg-graceful-shutdown-duration": "30s", + "libreoffice-auto-start": "false", + "libreoffice-disable-routes": "false", + "libreoffice-max-queue-size": "0", + "libreoffice-restart-after": "10", + "libreoffice-start-timeout": "20s", + "log-fields-prefix": "", + "log-format": "auto", + "log-level": "info", + "pdfengines-convert-engines": "[libreoffice-pdfengine]", + "pdfengines-disable-routes": "false", + "pdfengines-engines": "[]", + "pdfengines-flatten-engines": "[qpdf]", + "pdfengines-merge-engines": "[qpdf,pdfcpu,pdftk]", + "pdfengines-read-metadata-engines": "[exiftool]", + "pdfengines-split-engines": "[pdfcpu,qpdf,pdftk]", + "pdfengines-write-metadata-engines": "[exiftool]", + "prometheus-collect-interval": "1s", + "prometheus-disable-collect": "false", + "prometheus-disable-route-logging": "false", + "prometheus-namespace": "gotenberg", + "webhook-allow-list": "", + "webhook-client-timeout": "30s", + "webhook-deny-list": "", + "webhook-disable": "false", + "webhook-error-allow-list": "", + "webhook-error-deny-list": "", + "webhook-max-retry": "4", + "webhook-retry-max-wait": "30s", + "webhook-retry-min-wait": "1s" + } + } + """ + + Scenario: GET /debug (Gotenberg Trace) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + When I make a "GET" request to Gotenberg at the "/debug" endpoint with the following header(s): + | Gotenberg-Trace | debug | + Then the response status code should be 200 + Then the response header "Gotenberg-Trace" should be "debug" + Then the Gotenberg container should log the following entries: + | "trace":"debug" | + + Scenario: GET /debug (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "GET" request to Gotenberg at the "/debug" endpoint + Then the response status code should be 401 + + Scenario: GET /foo/debug (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "GET" request to Gotenberg at the "/foo/debug" endpoint + Then the response status code should be 200 diff --git a/test/integration/features/health.feature b/test/integration/features/health.feature new file mode 100644 index 000000000..887808f84 --- /dev/null +++ b/test/integration/features/health.feature @@ -0,0 +1,107 @@ +# TODO: +# 1. Check if down for each module. +# 2. Restarting modules do not make health check fail. + +Feature: /health + + Scenario: GET /health + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/health" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json; charset=utf-8" + Then the response body should match JSON: + """ + { + "status": "up", + "details": { + "chromium": { + "status": "up", + "timestamp": "ignore" + }, + "libreoffice": { + "status": "up", + "timestamp": "ignore" + } + } + } + """ + Then the Gotenberg container should log the following entries: + | "path":"/health" | + + Scenario: GET /health (No Logging) + Given I have a Gotenberg container with the following environment variable(s): + | API_DISABLE_HEALTH_CHECK_LOGGING | true | + When I make a "GET" request to Gotenberg at the "/health" endpoint + Then the response status code should be 200 + Then the Gotenberg container should NOT log the following entries: + | "path":"/health" | + + Scenario: GET /health (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/health" endpoint with the following header(s): + | Gotenberg-Trace | get_health | + Then the response status code should be 200 + Then the response header "Gotenberg-Trace" should be "get_health" + Then the Gotenberg container should log the following entries: + | "trace":"get_health" | + + Scenario: GET /health (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "GET" request to Gotenberg at the "/health" endpoint + Then the response status code should be 200 + + Scenario: GET /foo/health (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ROOT_PATH | /foo/ | + When I make a "GET" request to Gotenberg at the "/foo/health" endpoint + Then the response status code should be 200 + + Scenario: HEAD /health + Given I have a default Gotenberg container + When I make a "HEAD" request to Gotenberg at the "/health" endpoint + Then the response status code should be 200 + Then the response body should match string: + """ + + """ + Then the Gotenberg container should log the following entries: + | "path":"/health" | + + Scenario: HEAD /health (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "HEAD" request to Gotenberg at the "/health" endpoint with the following header(s): + | Gotenberg-Trace | head_health | + Then the response status code should be 200 + Then the response header "Gotenberg-Trace" should be "head_health" + Then the Gotenberg container should log the following entries: + | "trace":"head_health" | + + Scenario: HEAD /health (No Logging) + Given I have a Gotenberg container with the following environment variable(s): + | API_DISABLE_HEALTH_CHECK_LOGGING | true | + When I make a "HEAD" request to Gotenberg at the "/health" endpoint + Then the response status code should be 200 + Then the Gotenberg container should NOT log the following entries: + | "path":"/health" | + + Scenario: HEAD /health (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "HEAD" request to Gotenberg at the "/health" endpoint + Then the response status code should be 200 + + Scenario: HEAD /foo/health (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ROOT_PATH | /foo/ | + When I make a "HEAD" request to Gotenberg at the "/foo/health" endpoint + Then the response status code should be 200 + + +# TODO: +# 1. Check if down for each module. +# 2. Restarting modules do not make health check fail. \ No newline at end of file diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature new file mode 100644 index 000000000..91a4fcb97 --- /dev/null +++ b/test/integration/features/libreoffice_convert.feature @@ -0,0 +1,614 @@ +Feature: /forms/libreoffice/convert + + Scenario: POST /forms/libreoffice/convert (Single Document) + 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/page_1.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 (Many Documents) + 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/page_1.docx | file | + | files | testdata/page_2.docx | file | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | page_1.docx.pdf | + | page_2.docx.pdf | + Then the "page_1.docx.pdf" PDF should have 1 page(s) + Then the "page_2.docx.pdf" PDF should have 1 page(s) + Then the "page_1.docx.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "page_2.docx.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + + 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): + | files | testdata/protected_page_1.docx | file | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + LibreOffice failed to process a document: a password may be required, or, if one has been given, it is invalid. In any case, the exact cause is uncertain. + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/protected_page_1.docx | file | + | password | foo | field | + 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 + + Scenario: POST /forms/libreoffice/convert (Landscape) + 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/page_1.docx | file | + | Gotenberg-Output-Filename | foo | header | + 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 NOT be set to landscape orientation + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | landscape | true | field | + | Gotenberg-Output-Filename | foo | header | + 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 be set to landscape orientation + + Scenario: POST /forms/libreoffice/convert (Native Page Ranges - Single Document) + 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/pages_3.docx | file | + | nativePageRanges | 2-3 | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/libreoffice/convert (Native Page Ranges - Many Documents) + 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/pages_3.docx | file | + | files | testdata/pages_12.docx | file | + | nativePageRanges | 2-3 | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | pages_3.docx.pdf | + | pages_12.docx.pdf | + Then the "pages_3.docx.pdf" PDF should have 2 page(s) + Then the "pages_12.docx.pdf" PDF should have 2 page(s) + Then the "pages_3.docx.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3.docx.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + Then the "pages_12.docx.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_12.docx.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/libreoffice/convert (Bad Request) + 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): + | landscape | foo | field | + | exportFormFields | foo | field | + | allowDuplicateFieldNames | foo | field | + | exportBookmarks | foo | field | + | exportBookmarksToPdfDestination | foo | field | + | exportPlaceholders | foo | field | + | exportNotes | foo | field | + | exportNotesPages | foo | field | + | exportOnlyNotesPages | foo | field | + | exportNotesInMargin | foo | field | + | convertOooTargetToPdfTarget | foo | field | + | exportLinksRelativeFsys | foo | field | + | exportHiddenSlides | foo | field | + | skipEmptyPages | foo | field | + | addOriginalDocumentAsStream | foo | field | + | singlePageSheets | foo | field | + | losslessImageCompression | foo | field | + | quality | -1 | field | + | reduceImageResolution | foo | field | + | maxImageResolution | 10 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: no form file found for extensions: [.123 .602 .abw .bib .bmp .cdr .cgm .cmx .csv .cwk .dbf .dif .doc .docm .docx .dot .dotm .dotx .dxf .emf .eps .epub .fodg .fodp .fods .fodt .fopd .gif .htm .html .hwp .jpeg .jpg .key .ltx .lwp .mcw .met .mml .mw .numbers .odd .odg .odm .odp .ods .odt .otg .oth .otp .ots .ott .pages .pbm .pcd .pct .pcx .pdb .pdf .pgm .png .pot .potm .potx .ppm .pps .ppt .pptm .pptx .psd .psw .pub .pwp .pxl .ras .rtf .sda .sdc .sdd .sdp .sdw .sgl .slk .smf .stc .std .sti .stw .svg .svm .swf .sxc .sxd .sxg .sxi .sxm .sxw .tga .tif .tiff .txt .uof .uop .uos .uot .vdx .vor .vsd .vsdm .vsdx .wb2 .wk1 .wks .wmf .wpd .wpg .wps .xbm .xhtml .xls .xlsb .xlsm .xlsx .xlt .xltm .xltx .xlw .xml .xpm .zabw]; form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportFormFields' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'allowDuplicateFieldNames' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportBookmarks' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportBookmarksToPdfDestination' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportPlaceholders' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportNotes' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportNotesPages' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportOnlyNotesPages' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportNotesInMargin' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'convertOooTargetToPdfTarget' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportLinksRelativeFsys' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'exportHiddenSlides' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'skipEmptyPages' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'addOriginalDocumentAsStream' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'singlePageSheets' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'losslessImageCompression' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'quality' is invalid (got '-1', resulting to value is inferior to 1); form field 'reduceImageResolution' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'maxImageResolution' is invalid (got '10', resulting to value is not 75, 150, 300, 600 or 1200) + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | nativePageRanges | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + LibreOffice failed to process a document: possible causes include malformed page ranges 'foo' (nativePageRanges), or, if a password has been provided, it may not be required. In any case, the exact cause is uncertain. + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | password | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + LibreOffice failed to process a document: possible causes include malformed page ranges '' (nativePageRanges), or, if a password has been provided, it may not be required. In any case, the exact cause is uncertain. + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/protected_page_1.docx | file | + | password | bar | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + LibreOffice failed to process a document: a password may be required, or, if one has been given, it is invalid. In any case, the exact cause is uncertain. + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | files | testdata/page_2.docx | file | + | merge | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'merge' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/pages_3.docx | file | + | splitMode | foo | field | + | splitSpan | 2 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitMode' is invalid (got 'foo', resulting to wrong value, expected either 'intervals' or 'pages') + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/pages_3.docx | file | + | splitMode | intervals | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitSpan' is invalid (got 'foo', resulting to strconv.Atoi: parsing "foo": invalid syntax) + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/pages_3.docx | file | + | splitMode | pages | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/pages_3.docx | file | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + A PDF format in '{PdfA:foo PdfUa:false}' is not supported + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/libreoffice/convert (Merge) + 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/page_1.docx | file | + | files | testdata/page_2.docx | file | + | merge | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + + Scenario: POST /forms/libreoffice/convert (Merge & Split) + 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/page_1.docx | file | + | files | testdata/page_2.docx | file | + | merge | true | field | + | splitMode | intervals | field | + | splitSpan | 1 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | *_0.pdf | + | *_1.pdf | + Then the "*_0.pdf" PDF should have 1 page(s) + Then the "*_1.pdf" PDF should have 1 page(s) + Then the "*_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "*_1.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + + Scenario: POST /forms/libreoffice/convert (Split Intervals) + 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/pages_3.docx | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3.docx_0.pdf | + | pages_3.docx_1.pdf | + Then the "pages_3.docx_0.pdf" PDF should have 2 page(s) + Then the "pages_3.docx_1.pdf" PDF should have 1 page(s) + Then the "pages_3.docx_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3.docx_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3.docx_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/libreoffice/convert (Split Pages) + 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/pages_3.docx | file | + | splitMode | pages | field | + | splitSpan | 2- | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3.docx_0.pdf | + | pages_3.docx_1.pdf | + Then the "pages_3.docx_0.pdf" PDF should have 1 page(s) + Then the "pages_3.docx_1.pdf" PDF should have 1 page(s) + Then the "pages_3.docx_0.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3.docx_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/libreoffice/convert (Split Pages & Unify) + 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/pages_3.docx | file | + | splitMode | pages | field | + | splitSpan | 2- | field | + | splitUnify | true | field | + 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: + | pages_3.docx.pdf | + Then the "pages_3.docx.pdf" PDF should have 2 page(s) + Then the "pages_3.docx.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3.docx.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/libreoffice/convert (Split Many PDFs - Lot of Pages) + 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/pages_12.docx | file | + | files | testdata/pages_3.docx | file | + | splitMode | intervals | field | + | splitSpan | 1 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 15 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3.docx_0.pdf | + | pages_3.docx_1.pdf | + | pages_3.docx_2.pdf | + | pages_12.docx_0.pdf | + | pages_12.docx_1.pdf | + | pages_12.docx_2.pdf | + | pages_12.docx_3.pdf | + | pages_12.docx_4.pdf | + | pages_12.docx_5.pdf | + | pages_12.docx_6.pdf | + | pages_12.docx_7.pdf | + | pages_12.docx_8.pdf | + | pages_12.docx_9.pdf | + | pages_12.docx_10.pdf | + | pages_12.docx_11.pdf | + Then the "pages_3.docx_0.pdf" PDF should have 1 page(s) + Then the "pages_3.docx_2.pdf" PDF should have 1 page(s) + Then the "pages_12.docx_0.pdf" PDF should have 1 page(s) + Then the "pages_12.docx_11.pdf" PDF should have 1 page(s) + Then the "pages_3.docx_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3.docx_2.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the "pages_12.docx_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_12.docx_11.pdf" PDF should have the following content at page 1: + """ + Page 12 + """ + + Scenario: POST /forms/libreoffice/convert (PDF/A-1b & PDF/UA-1) + 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/page_1.docx | file | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/libreoffice/convert (Split & PDF/A-1b & PDF/UA-1) + 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/pages_3.docx | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3.docx_0.pdf | + | pages_3.docx_1.pdf | + Then the "pages_3.docx_0.pdf" PDF should have 2 page(s) + Then the "pages_3.docx_1.pdf" PDF should have 1 page(s) + Then the "pages_3.docx_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3.docx_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3.docx_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/libreoffice/convert (Metadata) + 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/page_1.docx | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/libreoffice/convert (Flatten) + 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/page_1.docx | file | + | flatten | true | field | + 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 + + Scenario: POST /forms/libreoffice/convert (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + 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/page_1.docx | file | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | flatten | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 7 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be flatten + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/libreoffice/convert (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | LIBREOFFICE_DISABLE_ROUTES | true | + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + Then the response status code should be 404 + + Scenario: POST /forms/libreoffice/convert (Gotenberg Trace) + 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/page_1.docx | file | + | Gotenberg-Trace | forms_libreoffice_convert | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_libreoffice_convert" + Then the Gotenberg container should log the following entries: + | "trace":"forms_libreoffice_convert" | + + Scenario: POST /forms/libreoffice/convert (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.docx","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + Then the response status code should be 200 + Then the file request header "X-Foo" should be "bar" + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/libreoffice/convert (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + | Gotenberg-Output-Filename | foo | header | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then there should be the following file(s) in the webhook request: + | 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 (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + Then the response status code should be 401 + + Scenario: POST /foo/forms/libreoffice/convert (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/libreoffice/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.docx | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" diff --git a/test/integration/features/pdfengines_convert.feature b/test/integration/features/pdfengines_convert.feature new file mode 100644 index 000000000..867dfcf0c --- /dev/null +++ b/test/integration/features/pdfengines_convert.feature @@ -0,0 +1,185 @@ +# TODO: +# 1. PDF/UA-2. + +Feature: /forms/pdfengines/convert + + Scenario: POST /forms/pdfengines/convert (Single PDF/A-1b) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | field | + 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Single PDF/A-2b) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-2b | field | + 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 valid "PDF/A-2b" with a tolerance of 1 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Single PDF/A-3b) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-3b | field | + 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 valid "PDF/A-3b" with a tolerance of 1 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Single PDF/UA-1) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfua | true | field | + 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 valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Single PDF/A-1b & PDF/UA-1) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Many PDFs) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | pdfa | PDF/A-1b | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Bad Request) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: either 'pdfa' or 'pdfua' form fields must be provided + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | pdfa | PDF/A-1b | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: no form file found for extensions: [.pdf] + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + + Scenario: POST /forms/pdfengines/convert (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | field | + | Gotenberg-Trace | forms_pdfengines_convert | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_convert" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_convert" | + + Scenario: POST /forms/pdfengines/convert (Output Filename - Single PDF) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | 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 the following file(s) in the response: + | foo.pdf | + + Scenario: POST /forms/pdfengines/convert (Output Filename - Many PDFs) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | pdfa | PDF/A-1b | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be the following file(s) in the response: + | foo.zip | + | page_1.pdf | + | page_2.pdf | + + Scenario: POST /forms/pdfengines/convert (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + | pdfa | PDF/A-1b | field | + Then the file request header "X-Foo" should be "bar" + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/pdfengines/convert (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | field | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then the webhook request PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + + Scenario: POST /forms/pdfengines/convert (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | field | + Then the response status code should be 401 + + Scenario: POST /foo/forms/pdfengines/convert (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/convert" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | pdfa | PDF/A-1b | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" diff --git a/test/integration/features/pdfengines_flatten.feature b/test/integration/features/pdfengines_flatten.feature new file mode 100644 index 000000000..521a93ae3 --- /dev/null +++ b/test/integration/features/pdfengines_flatten.feature @@ -0,0 +1,112 @@ +Feature: /forms/pdfengines/flatten + + Scenario: POST /forms/pdfengines/flatten (Single PDF) + 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/page_1.pdf | file | + 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 + + Scenario: POST /forms/pdfengines/flatten (Many PDFs) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then the response PDF(s) should be flatten + + Scenario: POST /forms/pdfengines/flatten (Bad Request) + 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): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: no form file found for extensions: [.pdf] + """ + + Scenario: POST /forms/pdfengines/flatten (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/flatten (Gotenberg Trace) + 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/page_1.pdf | file | + | Gotenberg-Trace | forms_pdfengines_flatten | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_flatten" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_flatten" | + + Scenario: POST /forms/pdfengines/flatten (Output Filename - Single PDF) + 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/page_1.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 the following file(s) in the response: + | foo.pdf | + + Scenario: POST /forms/pdfengines/flatten (Output Filename - Many PDFs) + 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/page_1.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/zip" + Then there should be the following file(s) in the response: + | foo.zip | + | page_1.pdf | + | page_2.pdf | + + Scenario: POST /forms/pdfengines/flatten (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + Then the file request header "X-Foo" should be "bar" + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/pdfengines/flatten (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then the response PDF(s) should be flatten + + Scenario: POST /forms/pdfengines/flatten (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 401 + + Scenario: POST /foo/forms/pdfengines/flatten (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" diff --git a/test/integration/features/pdfengines_merge.feature b/test/integration/features/pdfengines_merge.feature new file mode 100644 index 000000000..248a81ad1 --- /dev/null +++ b/test/integration/features/pdfengines_merge.feature @@ -0,0 +1,324 @@ +Feature: /forms/pdfengines/merge + + Scenario: POST /forms/pdfengines/merge (default - QPDF) + 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/page_1.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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + + Scenario: POST /forms/pdfengines/merge (pdfcpu) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_MERGE_ENGINES | pdfcpu | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + + Scenario: POST /forms/pdfengines/merge (PDFtk) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_MERGE_ENGINES | pdftk | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + + Scenario: POST /forms/pdfengines/merge (Bad Request) + 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): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: no form file found for extensions: [.pdf] + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | pdfa | PDF/A-1b | field | + | pdfua | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/pdfengines/merge (Metadata) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/merge (Flatten) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | flatten | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the response PDF(s) should be flatten + + Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | flatten | 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 + Then there should be the following file(s) in the response: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 7 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be flatten + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/merge (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/merge (Gotenberg Trace) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | Gotenberg-Trace | forms_pdfengines_merge | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_merge" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_merge" | + + Scenario: POST /forms/pdfengines/merge (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf"},{"url":"http://host.docker.internal:%d/static/testdata/page_2.pdf"}] | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/pdfengines/merge (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | Gotenberg-Output-Filename | foo | header | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + Then there should be the following file(s) in the webhook request: + | foo.pdf | + Then the "foo.pdf" PDF should have 2 page(s) + Then the "foo.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + + Scenario: POST /forms/pdfengines/merge (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + Then the response status code should be 401 + + Scenario: POST /foo/forms/pdfengines/merge (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" diff --git a/test/integration/features/pdfengines_metadata.feature b/test/integration/features/pdfengines_metadata.feature new file mode 100644 index 000000000..1cbb0ee11 --- /dev/null +++ b/test/integration/features/pdfengines_metadata.feature @@ -0,0 +1,268 @@ +Feature: /forms/pdfengines/{write|read} + + Scenario: POST /forms/pdfengines/metadata/{write|read} (Single PDF) + 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/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/metadata/{write|read} (Many PDFs) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/page_1.pdf | file | + | files | teststore/page_2.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "page_1.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + }, + "page_2.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/metadata/write (Bad Request) + 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): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is required; no form file found for extensions: [.pdf] + """ + 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/page_1.pdf | file | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/pdfengines/metadata/read (Bad Request) + 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): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: no form file found for extensions: [.pdf] + """ + + Scenario: POST /forms/pdfengines/metadata/write (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + 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/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/metadata/read (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + 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/page_1.pdf | file | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/metadata/write (Gotenberg Trace) + 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/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | Gotenberg-Trace | forms_pdfengines_metadata_write | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_metadata_write" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_metadata_write" | + + Scenario: POST /forms/pdfengines/metadata/read (Gotenberg Trace) + 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/page_1.pdf | file | + | Gotenberg-Trace | forms_pdfengines_metadata_read | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_metadata_read" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_metadata_read" | + + Scenario: POST /forms/pdfengines/metadata/write (Output Filename - Single PDF) + 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/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | 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 the following file(s) in the response: + | foo.pdf | + + Scenario: POST /forms/pdfengines/metadata/write (Output Filename - Many PDFs) + 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/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be the following file(s) in the response: + | foo.zip | + | page_1.pdf | + | page_2.pdf | + + Scenario: POST /forms/pdfengines/metadata/write (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/write" endpoint with the following form data and header(s): + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + | Gotenberg-Output-Filename | foo | header | + Then the file request header "X-Foo" should be "bar" + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + + Scenario: POST /forms/pdfengines/metadata/read (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + Then the file request header "X-Foo" should be "bar" + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + + Scenario: POST /forms/pdfengines/metadata/write (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + 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/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/metadata/write (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + 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/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + Then the response status code should be 401 + + Scenario: POST /forms/pdfengines/metadata/read (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + 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/page_1.pdf | file | + Then the response status code should be 401 + + Scenario: POST /foo/forms/pdfengines/metadata/{write|read} (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/metadata/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" diff --git a/test/integration/features/pdfengines_split.feature b/test/integration/features/pdfengines_split.feature new file mode 100644 index 000000000..d9813bfb9 --- /dev/null +++ b/test/integration/features/pdfengines_split.feature @@ -0,0 +1,551 @@ +Feature: /forms/pdfengines/split + + Scenario: POST /forms/pdfengines/split (Intervals - Default - pdfcpu) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 2 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/pdfengines/split (Pages - Default - pdfcpu) + 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/pages_3.pdf | file | + | splitMode | pages | field | + | splitSpan | 2- | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 1 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/pdfengines/split (Pages & Unify - Default - pdfcpu) + 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/pages_3.pdf | file | + | splitMode | pages | field | + | splitSpan | 2- | field | + | splitUnify | true | field | + 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: + | pages_3.pdf | + Then the "pages_3.pdf" PDF should have 2 page(s) + Then the "pages_3.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/pdfengines/split (Pages & Unify - QPDF) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_SPLIT_ENGINES | qpdf | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | pages | field | + | splitSpan | 2-z | field | + | splitUnify | true | field | + 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: + | pages_3.pdf | + Then the "pages_3.pdf" PDF should have 2 page(s) + Then the "pages_3.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/pdfengines/split (Pages & Unify - PDFtk) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_SPLIT_ENGINES | pdftk | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | pages | field | + | splitSpan | 2-end | field | + | splitUnify | true | field | + 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: + | pages_3.pdf | + Then the "pages_3.pdf" PDF should have 2 page(s) + Then the "pages_3.pdf" PDF should have the following content at page 1: + """ + Page 2 + """ + Then the "pages_3.pdf" PDF should have the following content at page 2: + """ + Page 3 + """ + + Scenario: POST /forms/pdfengines/split (Many PDFs - Lot of Pages) + 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/pages_12.pdf | file | + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 1 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 15 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + | pages_3_2.pdf | + | pages_12_0.pdf | + | pages_12_1.pdf | + | pages_12_2.pdf | + | pages_12_3.pdf | + | pages_12_4.pdf | + | pages_12_5.pdf | + | pages_12_6.pdf | + | pages_12_7.pdf | + | pages_12_8.pdf | + | pages_12_9.pdf | + | pages_12_10.pdf | + | pages_12_11.pdf | + Then the "pages_3_0.pdf" PDF should have 1 page(s) + Then the "pages_3_2.pdf" PDF should have 1 page(s) + Then the "pages_12_0.pdf" PDF should have 1 page(s) + Then the "pages_12_11.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_2.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the "pages_12_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_12_11.pdf" PDF should have the following content at page 1: + """ + Page 12 + """ + + Scenario: POST /forms/pdfengines/split (Bad Request) + 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): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitMode' is required; form field 'splitSpan' is required; no form file found for extensions: [.pdf] + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | foo | field | + | splitSpan | 2 | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitMode' is invalid (got 'foo', resulting to wrong value, expected either 'intervals' or 'pages') + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'splitSpan' is invalid (got 'foo', resulting to strconv.Atoi: parsing "foo": invalid syntax) + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | pages | field | + | splitSpan | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfua | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'pdfua' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | metadata | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/pdfengines/split (PDF/A-1b & PDF/UA-1) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 2 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + + Scenario: POST /forms/pdfengines/split (Metadata) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 2 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/pages_3_0.pdf | file | + | files | teststore/pages_3_1.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "pages_3_0.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + }, + "pages_3_1.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/split (Flatten) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | flatten | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 2 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be flatten + + Scenario: POST /forms/pdfengines/split (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | flatten | true | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 2 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 7 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be flatten + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): + | files | teststore/pages_3_0.pdf | file | + | files | teststore/pages_3_1.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "pages_3_0.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + }, + "pages_3_1.pdf": { + "Author": "Julien Neuhart", + "Copyright": "Julien Neuhart", + "CreateDate": "2006:09:18 16:27:50-04:00", + "Creator": "Gotenberg", + "Keywords": ["first", "second"], + "Marked": true, + "ModDate": "2006:09:18 16:27:50-04:00", + "PDFVersion": 1.7, + "Producer": "Gotenberg", + "Subject": "Sample", + "Title": "Sample", + "Trapped": "Unknown" + } + } + """ + + Scenario: POST /forms/pdfengines/split (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/split (Gotenberg Trace) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | Gotenberg-Trace | forms_pdfengines_split | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_split" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_split" | + + Scenario: POST /forms/pdfengines/split (Output Filename - Single PDF) + 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/pages_3.pdf | file | + | splitMode | pages | field | + | splitSpan | 2- | 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 the following file(s) in the response: + | foo.pdf | + + Scenario: POST /forms/pdfengines/split (Output Filename - Many PDFs) + 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/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be the following file(s) in the response: + | foo.zip | + | pages_3_0.pdf | + | pages_3_1.pdf | + + Scenario: POST /forms/pdfengines/split (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/pages_3.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the file request header "X-Foo" should be "bar" + Then the response header "Content-Type" should be "application/zip" + + Scenario: POST /forms/pdfengines/split (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the webhook request + Then there should be the following file(s) in the webhook request: + | pages_3_0.pdf | + | pages_3_1.pdf | + Then the "pages_3_0.pdf" PDF should have 2 page(s) + Then the "pages_3_1.pdf" PDF should have 1 page(s) + Then the "pages_3_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "pages_3_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "pages_3_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + + Scenario: POST /forms/pdfengines/split (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 401 + + Scenario: POST /foo/forms/pdfengines/split (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/split" endpoint with the following form data and header(s): + | files | testdata/pages_3.pdf | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" diff --git a/test/integration/features/prometheus_metrics.feature b/test/integration/features/prometheus_metrics.feature new file mode 100644 index 000000000..e42c3d785 --- /dev/null +++ b/test/integration/features/prometheus_metrics.feature @@ -0,0 +1,90 @@ +# TODO: +# 1. Count restarts. +# 2. Count queue size. + +Feature: /prometheus/metrics + + Scenario: GET /prometheus/metrics (Enabled) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/prometheus/metrics" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "text/plain; version=0.0.4; charset=utf-8; escaping=underscores" + Then the response body should match string: + """ + # HELP gotenberg_chromium_requests_queue_size Current number of Chromium conversion requests waiting to be treated. + # TYPE gotenberg_chromium_requests_queue_size gauge + gotenberg_chromium_requests_queue_size 0 + # HELP gotenberg_chromium_restarts_count Current number of Chromium restarts. + # TYPE gotenberg_chromium_restarts_count gauge + gotenberg_chromium_restarts_count 0 + # HELP gotenberg_libreoffice_requests_queue_size Current number of LibreOffice conversion requests waiting to be treated. + # TYPE gotenberg_libreoffice_requests_queue_size gauge + gotenberg_libreoffice_requests_queue_size 0 + # HELP gotenberg_libreoffice_restarts_count Current number of LibreOffice restarts. + # TYPE gotenberg_libreoffice_restarts_count gauge + gotenberg_libreoffice_restarts_count 0 + + """ + Then the Gotenberg container should log the following entries: + | "path":"/prometheus/metrics" | + + Scenario: GET /prometheus/metrics (Custom Namespace) + Given I have a Gotenberg container with the following environment variable(s): + | PROMETHEUS_NAMESPACE | foo | + When I make a "GET" request to Gotenberg at the "/prometheus/metrics" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "text/plain; version=0.0.4; charset=utf-8; escaping=underscores" + Then the response body should match string: + """ + # HELP foo_chromium_requests_queue_size Current number of Chromium conversion requests waiting to be treated. + # TYPE foo_chromium_requests_queue_size gauge + foo_chromium_requests_queue_size 0 + # HELP foo_chromium_restarts_count Current number of Chromium restarts. + # TYPE foo_chromium_restarts_count gauge + foo_chromium_restarts_count 0 + # HELP foo_libreoffice_requests_queue_size Current number of LibreOffice conversion requests waiting to be treated. + # TYPE foo_libreoffice_requests_queue_size gauge + foo_libreoffice_requests_queue_size 0 + # HELP foo_libreoffice_restarts_count Current number of LibreOffice restarts. + # TYPE foo_libreoffice_restarts_count gauge + foo_libreoffice_restarts_count 0 + + """ + + Scenario: GET /prometheus/metrics (Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PROMETHEUS_DISABLE_COLLECT | true | + When I make a "GET" request to Gotenberg at the "/prometheus/metrics" endpoint + Then the response status code should be 404 + + Scenario: GET /prometheus/metrics (No Logging) + Given I have a Gotenberg container with the following environment variable(s): + | PROMETHEUS_DISABLE_ROUTE_LOGGING | true | + When I make a "GET" request to Gotenberg at the "/prometheus/metrics" endpoint + Then the response status code should be 200 + Then the Gotenberg container should NOT log the following entries: + | "path":"/prometheus/metrics" | + + Scenario: GET /prometheus/metrics (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/prometheus/metrics" endpoint with the following header(s): + | Gotenberg-Trace | prometheus_metrics | + Then the response status code should be 200 + Then the response header "Gotenberg-Trace" should be "prometheus_metrics" + Then the Gotenberg container should log the following entries: + | "trace":"prometheus_metrics" | + + Scenario: GET /prometheus/metrics (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "GET" request to Gotenberg at the "/prometheus/metrics" endpoint + Then the response status code should be 401 + + Scenario: GET /foo/prometheus/metrics (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "GET" request to Gotenberg at the "/foo/prometheus/metrics" endpoint + Then the response status code should be 200 diff --git a/test/integration/features/root.feature b/test/integration/features/root.feature new file mode 100644 index 000000000..9ebf0e171 --- /dev/null +++ b/test/integration/features/root.feature @@ -0,0 +1,62 @@ +Feature: / + + Scenario: GET / + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "text/html; charset=UTF-8" + Then the response body should match string: + """ + Hey, Gotenberg has no UI, it's an API. Head to the documentation to learn how to interact with it 🚀 + """ + + Scenario: GET / (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/" endpoint with the following header(s): + | Gotenberg-Trace | root | + Then the response status code should be 200 + Then the response header "Gotenberg-Trace" should be "root" + Then the Gotenberg container should log the following entries: + | "trace":"root" | + + Scenario: GET / (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "GET" request to Gotenberg at the "/" endpoint + Then the response status code should be 401 + + Scenario: GET /foo/ (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ROOT_PATH | /foo/ | + When I make a "GET" request to Gotenberg at the "/foo/" endpoint + Then the response status code should be 200 + + Scenario: GET /favicon.ico + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/favicon.ico" endpoint + Then the response status code should be 204 + + Scenario: GET /favicon.ico (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/favicon.ico" endpoint with the following header(s): + | Gotenberg-Trace | favicon | + Then the response status code should be 204 + Then the response header "Gotenberg-Trace" should be "favicon" + Then the Gotenberg container should log the following entries: + | "trace":"favicon" | + + Scenario: GET /favicon.ico (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "GET" request to Gotenberg at the "/favicon.ico" endpoint + Then the response status code should be 401 + + Scenario: GET /foo/favicon.ico (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ROOT_PATH | /foo/ | + When I make a "GET" request to Gotenberg at the "/foo/favicon.ico" endpoint + Then the response status code should be 204 diff --git a/test/integration/features/version.feature b/test/integration/features/version.feature new file mode 100644 index 000000000..b9d9ae93f --- /dev/null +++ b/test/integration/features/version.feature @@ -0,0 +1,35 @@ +Feature: /version + + Scenario: GET /version + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/version" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + {version} + """ + + Scenario: GET /version (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "GET" request to Gotenberg at the "/version" endpoint with the following header(s): + | Gotenberg-Trace | version | + Then the response status code should be 200 + Then the response header "Gotenberg-Trace" should be "version" + Then the Gotenberg container should log the following entries: + | "trace":"version" | + + Scenario: GET /version (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "GET" request to Gotenberg at the "/version" endpoint + Then the response status code should be 401 + + Scenario: GET /foo/version (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "GET" request to Gotenberg at the "/foo/version" endpoint + Then the response status code should be 200 diff --git a/test/integration/main_test.go b/test/integration/main_test.go new file mode 100644 index 000000000..f77432814 --- /dev/null +++ b/test/integration/main_test.go @@ -0,0 +1,50 @@ +//go:build integration + +package integration + +import ( + "os" + "runtime" + "testing" + + "github.com/cucumber/godog" + "github.com/cucumber/godog/colors" + flag "github.com/spf13/pflag" + + "github.com/gotenberg/gotenberg/v8/test/integration/scenario" +) + +var opts = godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + Output: colors.Colored(os.Stdout), + Concurrency: runtime.NumCPU(), +} + +func TestMain(m *testing.M) { + repository := flag.String("gotenberg-docker-repository", "", "") + version := flag.String("gotenberg-version", "", "") + platform := flag.String("gotenberg-container-platform", "", "") + flag.Parse() + + if *platform == "" { + switch runtime.GOARCH { + case "arm64": + *platform = "linux/arm64" + default: + *platform = "linux/amd64" + } + } + + scenario.GotenbergDockerRepository = *repository + scenario.GotenbergVersion = *version + scenario.GotenbergContainerPlatform = *platform + + code := godog.TestSuite{ + Name: "integration", + ScenarioInitializer: scenario.InitializeScenario, + Options: &opts, + }.Run() + + os.Exit(code) +} diff --git a/test/integration/scenario/compare.go b/test/integration/scenario/compare.go new file mode 100644 index 000000000..5f61337f0 --- /dev/null +++ b/test/integration/scenario/compare.go @@ -0,0 +1,57 @@ +package scenario + +import ( + "fmt" + "reflect" +) + +func compareJson(expected, actual interface{}) error { + // Handle maps (JSON objects). + expectedMap, ok := expected.(map[string]interface{}) + if ok { + actualMap, ok := actual.(map[string]interface{}) + if !ok { + return fmt.Errorf("expected an object, but actual is: %T", actual) + } + // For each key in expected, compare if the expected value is not + // "ignore". + for key, expVal := range expectedMap { + if str, isStr := expVal.(string); isStr && str == "ignore" { + continue // Skip. + } + actVal, exists := actualMap[key] + if !exists { + return fmt.Errorf("missing expected key %q", key) + } + if err := compareJson(expVal, actVal); err != nil { + return fmt.Errorf("key %q: %w", key, err) + } + } + return nil + } + + // Handle slices (JSON arrays). + expectedSlice, ok := expected.([]interface{}) + if ok { + actualSlice, ok := actual.([]interface{}) + if !ok { + return fmt.Errorf("expected an array, but actual is: %T", actual) + } + if len(expectedSlice) != len(actualSlice) { + return fmt.Errorf("expected array length to be: %d, but actual is: %d", len(expectedSlice), len(actualSlice)) + } + for i := range expectedSlice { + if err := compareJson(expectedSlice[i], actualSlice[i]); err != nil { + return fmt.Errorf("at index %d: %w", i, err) + } + } + return nil + } + + // For other types, compare directly. + if !reflect.DeepEqual(expected, actual) { + return fmt.Errorf("expected %v (%T) but got %v (%T)", expected, expected, actual, actual) + } + + return nil +} diff --git a/test/integration/scenario/containers.go b/test/integration/scenario/containers.go new file mode 100644 index 000000000..1d752ca10 --- /dev/null +++ b/test/integration/scenario/containers.go @@ -0,0 +1,136 @@ +package scenario + +import ( + "context" + "fmt" + "io" + "path/filepath" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" +) + +var ( + GotenbergDockerRepository string + GotenbergVersion string + GotenbergContainerPlatform string +) + +type noopLogger struct{} + +func (n *noopLogger) Printf(format string, v ...interface{}) { + // NOOP +} + +func startGotenbergContainer(ctx context.Context, env map[string]string) (*testcontainers.DockerNetwork, testcontainers.Container, error) { + ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + + n, err := network.New(ctx) + if err != nil { + return nil, nil, fmt.Errorf("create Gotenberg container network: %w", err) + } + + heathPath := "/health" + if env["API_ROOT_PATH"] != "" { + heathPath = fmt.Sprintf("%shealth", env["API_ROOT_PATH"]) + } + + req := testcontainers.ContainerRequest{ + Image: fmt.Sprintf("gotenberg/%s:%s", GotenbergDockerRepository, GotenbergVersion), + ImagePlatform: GotenbergContainerPlatform, + ExposedPorts: []string{"3000/tcp"}, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.ExtraHosts = []string{"host.docker.internal:host-gateway"} + }, + Networks: []string{n.Name}, + WaitingFor: wait.ForHTTP(heathPath), + Env: env, + } + + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + Logger: &noopLogger{}, + }) + if err != nil { + err = fmt.Errorf("start new Gotenberg container: %w", err) + } + + return n, c, err +} + +func execCommandInIntegrationToolsContainer(ctx context.Context, cmd []string, path string) (string, error) { + ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + + req := testcontainers.ContainerRequest{ + Image: "gotenberg/integration-tools:latest", + Files: []testcontainers.ContainerFile{ + { + HostFilePath: path, + ContainerFilePath: filepath.Base(path), + FileMode: 0o700, + }, + }, + Cmd: []string{"tail", "-f", "/dev/null"}, // Keeps container running indefinitely. + } + + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + Logger: &noopLogger{}, + }) + if err != nil { + return "", fmt.Errorf("start new Integration Tools container: %w", err) + } + defer func(c testcontainers.Container, ctx context.Context) { + err := c.Terminate(ctx) + if err != nil { + fmt.Printf("terminate container: %v\n", err) + } + }(c, ctx) + + _, output, err := c.Exec(ctx, cmd) + if err != nil { + return "", fmt.Errorf("exec %q: %w", cmd, err) + } + + b, err := io.ReadAll(output) + if err != nil { + return "", fmt.Errorf("read output: %w", err) + } + + return string(b), nil +} + +func containerHttpEndpoint(ctx context.Context, container testcontainers.Container, port nat.Port) (string, error) { + ip, err := container.Host(ctx) + if err != nil { + return "", fmt.Errorf("get container IP: %w", err) + } + mapped, err := container.MappedPort(ctx, port) + if err != nil { + return "", fmt.Errorf("get container port: %w", err) + } + return fmt.Sprintf("http://%s:%s", ip, mapped.Port()), nil +} + +func containerLogEntries(ctx context.Context, container testcontainers.Container) (string, error) { + logReader, err := container.Logs(ctx) + if err != nil { + return "", fmt.Errorf("get container log entries: %w", err) + } + defer logReader.Close() + + logsBytes, err := io.ReadAll(logReader) + if err != nil { + return "", fmt.Errorf("read container log entries: %w", err) + } + + return string(logsBytes), nil +} diff --git a/test/integration/scenario/doc.go b/test/integration/scenario/doc.go new file mode 100644 index 000000000..a874ef8f0 --- /dev/null +++ b/test/integration/scenario/doc.go @@ -0,0 +1,2 @@ +// Package scenario gathers all steps used in the features. +package scenario diff --git a/test/integration/scenario/http.go b/test/integration/scenario/http.go new file mode 100644 index 000000000..7d88d02b9 --- /dev/null +++ b/test/integration/scenario/http.go @@ -0,0 +1,83 @@ +package scenario + +import ( + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" +) + +func doRequest(method, url string, headers map[string]string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, fmt.Errorf("create a request: %w", err) + } + + for header, value := range headers { + req.Header.Set(header, value) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("send a request: %w", err) + } + + return resp, nil +} + +func doFormDataRequest(method, url string, fields map[string]string, files map[string][]string, headers map[string]string) (*http.Response, error) { + var b bytes.Buffer + writer := multipart.NewWriter(&b) + + for name, value := range fields { + err := writer.WriteField(name, value) + if err != nil { + return nil, fmt.Errorf("write field %q: %w", name, err) + } + } + + for name, paths := range files { + for _, path := range paths { + part, err := writer.CreateFormFile(name, filepath.Base(path)) + if err != nil { + return nil, fmt.Errorf("create form file %q: %w", filepath.Base(path), err) + } + + reader, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("open file %q: %w", path, err) + } + defer reader.Close() + + _, err = io.Copy(part, reader) + if err != nil { + return nil, fmt.Errorf("copy file %q: %w", path, err) + } + } + } + + err := writer.Close() + if err != nil { + return nil, fmt.Errorf("close writer: %w", err) + } + + req, err := http.NewRequest(method, url, &b) + if err != nil { + return nil, fmt.Errorf("create a request: %w", err) + } + + for header, value := range headers { + req.Header.Set(header, value) + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("send a request: %w", err) + } + + return resp, nil +} diff --git a/test/integration/scenario/scenario.go b/test/integration/scenario/scenario.go new file mode 100644 index 000000000..b76e739d4 --- /dev/null +++ b/test/integration/scenario/scenario.go @@ -0,0 +1,911 @@ +package scenario + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + "github.com/cucumber/godog" + "github.com/google/uuid" + "github.com/mholt/archives" + "github.com/testcontainers/testcontainers-go" +) + +type scenario struct { + resp *httptest.ResponseRecorder + workdir string + gotenbergContainer testcontainers.Container + gotenbergContainerNetwork *testcontainers.DockerNetwork + server *server + hostPort int +} + +func (s *scenario) reset(ctx context.Context) error { + s.resp = httptest.NewRecorder() + + err := os.RemoveAll(s.workdir) + if err != nil { + return fmt.Errorf("remove workdir %q: %w", s.workdir, err) + } + s.workdir = "" + + if s.server == nil { + return nil + } + + err = s.server.stop(ctx) + if err != nil { + return fmt.Errorf("stop server: %w", err) + } + + return nil +} + +func (s *scenario) iHaveADefaultGotenbergContainer(ctx context.Context) error { + n, c, err := startGotenbergContainer(ctx, nil) + if err != nil { + return fmt.Errorf("create Gotenberg container: %s", err) + } + s.gotenbergContainerNetwork = n + s.gotenbergContainer = c + return nil +} + +func (s *scenario) iHaveAGotenbergContainerWithTheFollowingEnvironmentVariables(ctx context.Context, envTable *godog.Table) error { + env := make(map[string]string) + for _, row := range envTable.Rows { + env[row.Cells[0].Value] = row.Cells[1].Value + } + n, c, err := startGotenbergContainer(ctx, env) + if err != nil { + return fmt.Errorf("create Gotenberg container: %s", err) + } + s.gotenbergContainerNetwork = n + s.gotenbergContainer = c + return nil +} + +func (s *scenario) iHaveAServer(ctx context.Context) error { + srv, err := newServer(ctx, s.workdir) + if err != nil { + return fmt.Errorf("create server: %s", err) + } + s.server = srv + port, err := s.server.start(ctx) + if err != nil { + return fmt.Errorf("start server: %s", err) + } + s.hostPort = port + return nil +} + +func (s *scenario) iMakeARequestToGotenberg(ctx context.Context, method, endpoint string) error { + return s.iMakeARequestToGotenbergWithTheFollowingHeaders(ctx, method, endpoint, nil) +} + +func (s *scenario) iMakeARequestToGotenbergWithTheFollowingHeaders(ctx context.Context, method, endpoint string, headersTable *godog.Table) error { + if s.gotenbergContainer == nil { + return errors.New("no Gotenberg container") + } + + base, err := containerHttpEndpoint(ctx, s.gotenbergContainer, "3000") + if err != nil { + return fmt.Errorf("get container HTTP endpoint: %w", err) + } + + headers := make(map[string]string) + if headersTable != nil { + for _, row := range headersTable.Rows { + headers[row.Cells[0].Value] = row.Cells[1].Value + } + } + + resp, err := doRequest(method, fmt.Sprintf("%s%s", base, endpoint), headers, nil) + if err != nil { + return fmt.Errorf("do request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read response body: %w", err) + } + + s.resp = httptest.NewRecorder() + s.resp.Code = resp.StatusCode + for key, values := range resp.Header { + for _, v := range values { + s.resp.Header().Add(key, v) + } + } + _, err = s.resp.Body.Write(body) + if err != nil { + return fmt.Errorf("write response body: %w", err) + } + + return nil +} + +func (s *scenario) iMakeARequestToGotenbergWithTheFollowingFormDataAndHeaders(ctx context.Context, method, endpoint string, dataTable *godog.Table) error { + if s.gotenbergContainer == nil { + return errors.New("no Gotenberg container") + } + + fields := make(map[string]string) + files := make(map[string][]string) + headers := make(map[string]string) + + for _, row := range dataTable.Rows { + name := row.Cells[0].Value + value := row.Cells[1].Value + kind := row.Cells[2].Value + + switch kind { + case "field": + if name == "downloadFrom" || name == "url" || name == "cookies" { + fields[name] = strings.ReplaceAll(value, "%d", fmt.Sprintf("%d", s.hostPort)) + continue + } + fields[name] = value + case "file": + if strings.Contains(value, "teststore") { + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + _, err := os.Stat(dirPath) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", dirPath) + } + value = strings.ReplaceAll(value, "teststore", dirPath) + } else { + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("get current directory: %w", err) + } + value = fmt.Sprintf("%s/%s", wd, value) + } + files[name] = append(files[name], value) + case "header": + if name == "Gotenberg-Webhook-Url" || name == "Gotenberg-Webhook-Error-Url" { + headers[name] = fmt.Sprintf(value, s.hostPort) + continue + } + headers[name] = value + default: + return fmt.Errorf("unexpected %q %q", kind, value) + } + } + + base, err := containerHttpEndpoint(ctx, s.gotenbergContainer, "3000") + if err != nil { + return fmt.Errorf("get container HTTP endpoint: %w", err) + } + + resp, err := doFormDataRequest(method, fmt.Sprintf("%s%s", base, endpoint), fields, files, headers) + if err != nil { + return fmt.Errorf("do request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read response body: %w", err) + } + + s.resp = httptest.NewRecorder() + s.resp.Code = resp.StatusCode + for key, values := range resp.Header { + for _, v := range values { + s.resp.Header().Add(key, v) + } + } + _, err = s.resp.Body.Write(body) + if err != nil { + return fmt.Errorf("write response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil + } + + cd := resp.Header.Get("Content-Disposition") + if cd == "" { + return nil + } + + _, params, err := mime.ParseMediaType(cd) + if err != nil { + return fmt.Errorf("parse Content-Disposition header: %w", err) + } + + filename, ok := params["filename"] + if !ok { + return errors.New("no filename in Content-Disposition header") + } + + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + err = os.MkdirAll(dirPath, 0o755) + if err != nil { + return fmt.Errorf("create working directory: %w", err) + } + + fpath := fmt.Sprintf("%s/%s", dirPath, filename) + file, err := os.Create(fpath) + if err != nil { + return fmt.Errorf("create file %q: %w", fpath, err) + } + defer file.Close() + + _, err = file.Write(body) + if err != nil { + return fmt.Errorf("write file %q: %w", fpath, err) + } + + if resp.Header.Get("Content-Type") == "application/zip" { + var format archives.Zip + err = format.Extract(ctx, file, func(ctx context.Context, f archives.FileInfo) error { + source, err := f.Open() + if err != nil { + return fmt.Errorf("open file %q: %w", f.Name(), err) + } + defer source.Close() + + targetPath := fmt.Sprintf("%s/%s", dirPath, f.Name()) + target, err := os.Create(targetPath) + if err != nil { + return fmt.Errorf("create file %q: %w", targetPath, err) + } + defer target.Close() + + _, err = io.Copy(target, source) + if err != nil { + return fmt.Errorf("copy file %q: %w", targetPath, err) + } + + return nil + }) + if err != nil { + return err + } + } + + return nil +} + +func (s *scenario) iWaitForTheAsynchronousRequestToWebhook(ctx context.Context) error { + if s.server == nil { + return errors.New("server not initialized") + } + if s.server.req != nil { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-s.server.errChan: + return err + } +} + +func (s *scenario) theGotenbergContainerShouldLogTheFollowingEntries(ctx context.Context, should string, entriesTable *godog.Table) error { + if s.gotenbergContainer == nil { + return errors.New("no Gotenberg container") + } + + expected := make([]string, len(entriesTable.Rows)) + for i, row := range entriesTable.Rows { + expected[i] = row.Cells[0].Value + } + + invert := should == "should NOT" + check := func() error { + logs, err := containerLogEntries(ctx, s.gotenbergContainer) + if err != nil { + return fmt.Errorf("get log entries: %w", err) + } + + for _, entry := range expected { + if !invert && !strings.Contains(logs, entry) { + return fmt.Errorf("expected log entry %q not found in %q", expected, logs) + } + + if invert && strings.Contains(logs, entry) { + return fmt.Errorf("log entry %q NOT expected", expected) + } + } + + return nil + } + + var err error + for i := 0; i < 3; i++ { + err = check() + if err != nil && !invert { + // We have to retry as not all logs may have been produced. + time.Sleep(500 * time.Millisecond) + continue + } + break + } + return err +} + +func (s *scenario) theResponseStatusCodeShouldBe(expected int) error { + if expected != s.resp.Code { + return fmt.Errorf("expected response status code to be: %d, but actual is: %d %q", expected, s.resp.Code, s.resp.Body.String()) + } + return nil +} + +func (s *scenario) theHeaderValueShouldBe(kind, name string, expected string) error { + var actual string + if kind == "response" { + actual = s.resp.Header().Get(name) + } else if s.server == nil { + return errors.New("server not initialized") + } else if s.server.req == nil { + return errors.New("no webhook request found") + } else { + actual = s.server.req.Header.Get(name) + } + + if expected != actual { + return fmt.Errorf("expected %s header %q to be: %q, but actual is: %q", kind, name, expected, actual) + } + return nil +} + +func (s *scenario) theCookieValueShouldBe(kind, name, expected string) error { + var cookies []*http.Cookie + if kind == "response" { + cookies = s.resp.Result().Cookies() + } else if s.server == nil { + return errors.New("server not initialized") + } else if s.server.req == nil { + return errors.New("no webhook request found") + } else { + cookies = s.server.req.Cookies() + } + + var actual *http.Cookie + for _, cookie := range cookies { + if cookie.Name == name { + actual = cookie + break + } + } + + if actual == nil { + if expected != "" { + return fmt.Errorf("expected %s cookie %q not found", kind, name) + } + return nil + } + + if expected != actual.Value { + return fmt.Errorf("expected %s cookie %q to be: %q, but actual is: %q", kind, name, expected, actual.Value) + } + + return nil +} + +func (s *scenario) theBodyShouldMatchString(kind string, expectedDoc *godog.DocString) error { + var actual string + if kind == "response" { + actual = s.resp.Body.String() + } else if s.server == nil { + return errors.New("server not initialized") + } else if s.server.req == nil { + return errors.New("no webhook request found") + } else { + body, err := io.ReadAll(s.server.req.Body) + if err != nil { + return fmt.Errorf("read request body: %w", err) + } + actual = string(body) + } + + expected := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion) + + if actual != expected { + return fmt.Errorf("expected %q body to be: %q, but actual is: %q", kind, expected, actual) + } + return nil +} + +func (s *scenario) theBodyShouldContainString(kind string, expectedDoc *godog.DocString) error { + var actual string + if kind == "response" { + actual = s.resp.Body.String() + } else if s.server == nil { + return errors.New("server not initialized") + } else if s.server.req == nil { + return errors.New("no webhook request found") + } else { + body, err := io.ReadAll(s.server.req.Body) + if err != nil { + return fmt.Errorf("read request body: %w", err) + } + actual = string(body) + } + + expected := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion) + + if !strings.Contains(actual, expected) { + return fmt.Errorf("expected %q body to contain: %q, but actual is: %q", kind, expected, actual) + } + return nil +} + +func (s *scenario) theBodyShouldMatchJSON(kind string, expectedDoc *godog.DocString) error { + var body []byte + if kind == "response" { + body = s.resp.Body.Bytes() + } else if s.server == nil { + return errors.New("server not initialized") + } else if s.server.req == nil { + return errors.New("no webhook request found") + } else { + b, err := io.ReadAll(s.server.req.Body) + if err != nil { + return fmt.Errorf("read request body: %w", err) + } + body = b + } + + var expected, actual interface{} + + content := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion) + err := json.Unmarshal([]byte(content), &expected) + if err != nil { + return fmt.Errorf("unmarshal expected JSON: %w", err) + } + + err = json.Unmarshal(body, &actual) + if err != nil { + return fmt.Errorf("unmarshal actual JSON: %w", err) + } + + err = compareJson(expected, actual) + if err != nil { + return fmt.Errorf("expected matching JSON: %w", err) + } + + return nil +} + +func (s *scenario) thereShouldBePdfs(expected int, kind string) error { + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + + _, err := os.Stat(dirPath) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", dirPath) + } + + var paths []string + err = filepath.Walk(dirPath, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if strings.EqualFold(filepath.Ext(info.Name()), ".pdf") { + paths = append(paths, path) + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + + if len(paths) != expected { + return fmt.Errorf("expected %d PDF(s), but actual is %d", expected, len(paths)) + } + + return nil +} + +func (s *scenario) thereShouldBeTheFollowingFiles(kind string, filesTable *godog.Table) error { + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + + _, err := os.Stat(dirPath) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", dirPath) + } + + var filenames []string + err = filepath.Walk(dirPath, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if !info.IsDir() { + filenames = append(filenames, info.Name()) + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + + for _, row := range filesTable.Rows { + found := false + expected := row.Cells[0].Value + for _, filename := range filenames { + if strings.HasPrefix(expected, "*_") && strings.Contains(filename, strings.ReplaceAll(expected, "*_", "")) { + found = true + } + if strings.EqualFold(expected, filename) { + found = true + break + } + } + if !found { + return fmt.Errorf("expected file %q not found among %q", expected, filenames) + } + } + + return nil +} + +func (s *scenario) thePdfsShouldBeValidWithAToleranceOf(ctx context.Context, kind, validate string, tolerance int) error { + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + + _, err := os.Stat(dirPath) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", dirPath) + } + + var paths []string + err = filepath.Walk(dirPath, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if strings.EqualFold(filepath.Ext(info.Name()), ".pdf") { + paths = append(paths, path) + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + + var flavor string + switch validate { + case "PDF/A-1b": + flavor = "1b" + case "PDF/A-2b": + flavor = "2b" + case "PDF/A-3b": + flavor = "3b" + case "PDF/UA-1": + flavor = "ua1" + case "PDF/UA-2": + flavor = "ua2" + default: + return fmt.Errorf("unknown %q", validate) + } + + for _, path := range paths { + cmd := []string{ + "verapdf", + "-f", + flavor, + filepath.Base(path), + } + + output, err := execCommandInIntegrationToolsContainer(ctx, cmd, path) + if err != nil { + return fmt.Errorf("exec %q: %w", cmd, err) + } + + re := regexp.MustCompile(`failedRules="(\d+)"`) + matches := re.FindStringSubmatch(output) + + if len(matches) < 2 { + return errors.New("expected failed rules") + } + + failedRules, err := strconv.Atoi(matches[1]) + if err != nil { + return fmt.Errorf("convert failed rules value %q to integer: %w", matches[1], err) + } + + if tolerance < failedRules { + return fmt.Errorf("expected failed rules to be inferior or equal to: %d, but actual is %d", tolerance, failedRules) + } + } + + return nil +} + +func (s *scenario) thePdfShouldHavePages(ctx context.Context, name string, pages int) error { + var path string + if !strings.HasPrefix(name, "*_") { + path = fmt.Sprintf("%s/%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace"), name) + + _, err := os.Stat(path) + if os.IsNotExist(err) { + return fmt.Errorf("PDF %q does not exist", path) + } + } else { + substr := strings.ReplaceAll(name, "*_", "") + err := filepath.Walk(fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")), func(currentPath string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if strings.Contains(info.Name(), substr) { + path = currentPath + return filepath.SkipDir + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + } + + cmd := []string{ + "pdfinfo", + filepath.Base(path), + } + + output, err := execCommandInIntegrationToolsContainer(ctx, cmd, path) + if err != nil { + return fmt.Errorf("exec %q: %w", cmd, err) + } + + output = strings.ReplaceAll(output, " ", "") + re := regexp.MustCompile(`Pages:(\d+)`) + matches := re.FindStringSubmatch(output) + + if len(matches) < 2 { + return errors.New("expected pages") + } + + actual, err := strconv.Atoi(matches[1]) + if err != nil { + return fmt.Errorf("convert pages value %q to integer: %w", matches[1], err) + } + + if actual != pages { + return fmt.Errorf("expected %d pages, but actual is %d", pages, actual) + } + + return nil +} + +func (s *scenario) thePdfShouldBeSetToLandscapeOrientation(ctx context.Context, name string, kind string) error { + var path string + if !strings.HasPrefix(name, "*_") { + path = fmt.Sprintf("%s/%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace"), name) + + _, err := os.Stat(path) + if os.IsNotExist(err) { + return fmt.Errorf("PDF %q does not exist", path) + } + } else { + substr := strings.ReplaceAll(name, "*_", "") + err := filepath.Walk(fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")), func(currentPath string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if strings.Contains(info.Name(), substr) { + path = currentPath + return filepath.SkipDir + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + } + + cmd := []string{ + "pdfinfo", + filepath.Base(path), + } + + output, err := execCommandInIntegrationToolsContainer(ctx, cmd, path) + if err != nil { + return fmt.Errorf("exec %q: %w", cmd, err) + } + + output = strings.ReplaceAll(output, " ", "") + re := regexp.MustCompile(`Pagesize:(\d+)x(\d+).*`) + matches := re.FindStringSubmatch(output) + + if len(matches) < 3 { + return errors.New("expected page size") + } + + invert := kind == "should NOT" + + width, err := strconv.Atoi(matches[1]) + if err != nil { + return fmt.Errorf("convert width value %q to integer: %w", matches[1], err) + } + + height, err := strconv.Atoi(matches[2]) + if err != nil { + return fmt.Errorf("convert height value %q to integer: %w", matches[2], err) + } + + if invert && height < width { + return fmt.Errorf("expected height %d to be greater than width %d", height, width) + } + + if !invert && width < height { + return fmt.Errorf("expected width %d to be greater than height %d", width, height) + } + + return nil +} + +func (s *scenario) thePdfShouldHaveTheFollowingContentAtPage(ctx context.Context, name, kind string, page int, expected *godog.DocString) error { + var path string + if !strings.HasPrefix(name, "*_") { + path = fmt.Sprintf("%s/%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace"), name) + + _, err := os.Stat(path) + if os.IsNotExist(err) { + return fmt.Errorf("PDF %q does not exist", path) + } + } else { + substr := strings.ReplaceAll(name, "*_", "") + err := filepath.Walk(fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")), func(currentPath string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if strings.Contains(info.Name(), substr) { + path = currentPath + return filepath.SkipDir + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + } + + cmd := []string{ + "pdftotext", + "-f", + fmt.Sprintf("%d", page), + "-l", + fmt.Sprintf("%d", page), + filepath.Base(path), + "-", + } + + output, err := execCommandInIntegrationToolsContainer(ctx, cmd, path) + if err != nil { + return fmt.Errorf("exec %q: %w", cmd, err) + } + + invert := kind == "should NOT" + + if !invert && !strings.Contains(output, expected.Content) { + return fmt.Errorf("expected %q not found in %q", expected.Content, output) + } + + if invert && strings.Contains(output, expected.Content) { + return fmt.Errorf("%q found in %q", expected.Content, output) + } + + return nil +} + +func (s *scenario) thePdfsShouldBeFlatten(ctx context.Context, kind, should string) error { + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + + _, err := os.Stat(dirPath) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", dirPath) + } + + var paths []string + err = filepath.Walk(dirPath, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr + } + if strings.EqualFold(filepath.Ext(info.Name()), ".pdf") { + paths = append(paths, path) + } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", s.workdir, err) + } + + for _, path := range paths { + cmd := []string{ + "verapdf", + "-off", + "--extract", + "annotations", + filepath.Base(path), + } + + output, err := execCommandInIntegrationToolsContainer(ctx, cmd, path) + if err != nil { + return fmt.Errorf("exec %q: %w", cmd, err) + } + + invert := should == "should NOT" + if invert && strings.Contains(output, "") { + return fmt.Errorf("PDF %q is flatten", path) + } + + if !invert && !strings.Contains(output, "") { + return fmt.Errorf("PDF %q is not flatten", path) + } + } + + return nil +} + +func InitializeScenario(ctx *godog.ScenarioContext) { + s := &scenario{} + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + wd, err := os.Getwd() + if err != nil { + return ctx, fmt.Errorf("get current directory: %w", err) + } + s.workdir = fmt.Sprintf("%s/teststore/%s", wd, uuid.NewString()) + err = os.MkdirAll(s.workdir, 0o755) + if err != nil { + return ctx, fmt.Errorf("create working directory: %w", err) + } + return ctx, nil + }) + ctx.Given(`^I have a default Gotenberg container$`, s.iHaveADefaultGotenbergContainer) + ctx.Given(`^I have a Gotenberg container with the following environment variable\(s\):$`, s.iHaveAGotenbergContainerWithTheFollowingEnvironmentVariables) + ctx.Given(`^I have a (webhook|static) server$`, s.iHaveAServer) + ctx.When(`^I make a "(GET|HEAD)" request to Gotenberg at the "([^"]*)" endpoint$`, s.iMakeARequestToGotenberg) + ctx.When(`^I make a "(GET|HEAD)" request to Gotenberg at the "([^"]*)" endpoint with the following header\(s\):$`, s.iMakeARequestToGotenbergWithTheFollowingHeaders) + ctx.When(`^I make a "(POST)" request to Gotenberg at the "([^"]*)" endpoint with the following form data and header\(s\):$`, s.iMakeARequestToGotenbergWithTheFollowingFormDataAndHeaders) + ctx.When(`^I wait for the asynchronous request to the webhook$`, s.iWaitForTheAsynchronousRequestToWebhook) + ctx.Then(`^the Gotenberg container (should|should NOT) log the following entries:$`, s.theGotenbergContainerShouldLogTheFollowingEntries) + ctx.Then(`^the response status code should be (\d+)$`, s.theResponseStatusCodeShouldBe) + ctx.Then(`^the (response|webhook request|file request|server request) header "([^"]*)" should be "([^"]*)"$`, s.theHeaderValueShouldBe) + ctx.Then(`^the (response|webhook request|file request|server request) cookie "([^"]*)" should be "([^"]*)"$`, s.theCookieValueShouldBe) + ctx.Then(`^the (response|webhook request) body should match string:$`, s.theBodyShouldMatchString) + ctx.Then(`^the (response|webhook request) body should contain string:$`, s.theBodyShouldContainString) + ctx.Then(`^the (response|webhook request) body should match JSON:$`, s.theBodyShouldMatchJSON) + ctx.Then(`^there should be (\d+) PDF\(s\) in the (response|webhook request)$`, s.thereShouldBePdfs) + ctx.Then(`^there should be the following file\(s\) in the (response|webhook request):$`, s.thereShouldBeTheFollowingFiles) + ctx.Then(`^the (response|webhook request) PDF\(s\) should be valid "([^"]*)" with a tolerance of (\d+) failed rule\(s\)$`, s.thePdfsShouldBeValidWithAToleranceOf) + ctx.Then(`^the (response|webhook request) PDF\(s\) (should|should NOT) be flatten$`, s.thePdfsShouldBeFlatten) + ctx.Then(`^the "([^"]*)" PDF should have (\d+) page\(s\)$`, s.thePdfShouldHavePages) + ctx.Then(`^the "([^"]*)" PDF (should|should NOT) be set to landscape orientation$`, s.thePdfShouldBeSetToLandscapeOrientation) + ctx.Then(`^the "([^"]*)" PDF (should|should NOT) have the following content at page (\d+):$`, s.thePdfShouldHaveTheFollowingContentAtPage) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + if s.gotenbergContainer != nil { + errTerminate := s.gotenbergContainer.Terminate(ctx, testcontainers.StopTimeout(0)) + if errTerminate != nil { + return ctx, fmt.Errorf("terminate Gotenberg container: %w", errTerminate) + } + } + if s.gotenbergContainerNetwork != nil { + errRemove := s.gotenbergContainerNetwork.Remove(ctx) + if errRemove != nil { + return ctx, fmt.Errorf("remove Gotenberg container network: %w", errRemove) + } + } + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + errReset := s.reset(ctx) + if errReset != nil { + return ctx, fmt.Errorf("reset scenario: %w", errReset) + } + return ctx, nil + }) +} diff --git a/test/integration/scenario/server.go b/test/integration/scenario/server.go new file mode 100644 index 000000000..ddf2f0a14 --- /dev/null +++ b/test/integration/scenario/server.go @@ -0,0 +1,176 @@ +package scenario + +import ( + "context" + "errors" + "fmt" + "io" + "mime" + "net" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/cucumber/godog" + "github.com/labstack/echo/v4" + "github.com/mholt/archives" +) + +type server struct { + srv *echo.Echo + req *http.Request + errChan chan error +} + +func newServer(ctx context.Context, workdir string) (*server, error) { + srv := echo.New() + srv.HideBanner = true + srv.HidePort = true + s := &server{ + srv: srv, + errChan: make(chan error, 1), + } + + wd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("get current directory: %w", err) + } + + webhookErr := func(err error) error { + s.errChan <- err + return err + } + + webhookHandler := func(c echo.Context) error { + s.req = c.Request() + + body, err := io.ReadAll(s.req.Body) + if err != nil { + return webhookErr(fmt.Errorf("read request body: %w", err)) + } + + cd := s.req.Header.Get("Content-Disposition") + if cd == "" { + return webhookErr(fmt.Errorf("no Content-Disposition header")) + } + + _, params, err := mime.ParseMediaType(cd) + if err != nil { + return webhookErr(fmt.Errorf("parse Content-Disposition header: %w", err)) + } + + filename, ok := params["filename"] + if !ok { + return webhookErr(errors.New("no filename in Content-Disposition header")) + } + + dirPath := fmt.Sprintf("%s/%s", workdir, s.req.Header.Get("Gotenberg-Trace")) + err = os.MkdirAll(dirPath, 0o755) + if err != nil { + return webhookErr(fmt.Errorf("create working directory: %w", err)) + } + + fpath := fmt.Sprintf("%s/%s", dirPath, filename) + file, err := os.Create(fpath) + if err != nil { + return webhookErr(fmt.Errorf("create file %q: %w", fpath, err)) + } + defer file.Close() + + _, err = file.Write(body) + if err != nil { + return webhookErr(fmt.Errorf("write file %q: %w", fpath, err)) + } + + if s.req.Header.Get("Content-Type") == "application/zip" { + var format archives.Zip + err = format.Extract(ctx, file, func(ctx context.Context, f archives.FileInfo) error { + source, err := f.Open() + if err != nil { + return fmt.Errorf("open file %q: %w", f.Name(), err) + } + defer source.Close() + + targetPath := fmt.Sprintf("%s/%s", dirPath, f.Name()) + target, err := os.Create(targetPath) + if err != nil { + return fmt.Errorf("create file %q: %w", targetPath, err) + } + defer target.Close() + + _, err = io.Copy(target, source) + if err != nil { + return fmt.Errorf("copy file %q: %w", targetPath, err) + } + + return nil + }) + if err != nil { + return webhookErr(err) + } + } + + return webhookErr(c.String(http.StatusOK, http.StatusText(http.StatusOK))) + } + webhookErrorHandler := func(c echo.Context) error { + s.req = c.Request() + return webhookErr(c.String(http.StatusOK, http.StatusText(http.StatusOK))) + } + + srv.POST("/webhook", webhookHandler) + srv.PATCH("/webhook", webhookHandler) + srv.PUT("/webhook", webhookHandler) + srv.POST("/webhook/error", webhookErrorHandler) + srv.PATCH("/webhook/error", webhookErrorHandler) + srv.PUT("/webhook/error", webhookErrorHandler) + srv.GET("/static/:path", func(c echo.Context) error { + s.req = c.Request() + path := c.Param("path") + if strings.Contains(path, "teststore") { + return c.Attachment(fmt.Sprintf("%s/%s/%s", workdir, s.req.Header.Get("Gotenberg-Trace"), filepath.Base(path)), filepath.Base(path)) + } + return c.Attachment(fmt.Sprintf("%s/%s", wd, path), filepath.Base(path)) + }) + srv.GET("/html/:path", func(c echo.Context) error { + s.req = c.Request() + path := fmt.Sprintf("%s/%s", wd, c.Param("path")) + f, err := os.Open(path) + if err != nil { + return c.String(http.StatusInternalServerError, fmt.Sprintf("open file %q: %s", path, err)) + } + defer f.Close() + b, err := io.ReadAll(f) + if err != nil { + return c.String(http.StatusInternalServerError, fmt.Sprintf("read file %q: %s", path, err)) + } + return c.HTML(http.StatusOK, string(b)) + }) + + return s, nil +} + +func (s *server) start(ctx context.Context) (int, error) { + // #nosec + ln, err := net.Listen("tcp", "0.0.0.0:0") + if err != nil { + return 0, fmt.Errorf("create listener: %w", err) + } + + port := ln.Addr().(*net.TCPAddr).Port + + go func() { + s.srv.Listener = ln + err = s.srv.Start("") + if err != nil && !errors.Is(err, http.ErrServerClosed) { + godog.Log(ctx, err.Error()) + } + }() + + return port, nil +} + +func (s *server) stop(ctx context.Context) error { + close(s.errChan) + return s.srv.Shutdown(ctx) +} diff --git a/test/integration/testdata/feature-rich-html-remote/index.html b/test/integration/testdata/feature-rich-html-remote/index.html new file mode 100644 index 000000000..edfb30779 --- /dev/null +++ b/test/integration/testdata/feature-rich-html-remote/index.html @@ -0,0 +1,64 @@ + + + + + + + Feature Rich HTML + + + + +

Emulated media type is 'print'.

+

Emulated media type is 'screen'.

+ + + + + + + + + + + + + + diff --git a/test/integration/testdata/feature-rich-html/index.html b/test/integration/testdata/feature-rich-html/index.html new file mode 100644 index 000000000..f679383e0 --- /dev/null +++ b/test/integration/testdata/feature-rich-html/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + + Feature Rich HTML + + + + +

Emulated media type is 'print'.

+

Emulated media type is 'screen'.

+ + + + + + + + + + + + + + + diff --git a/test/integration/testdata/feature-rich-markdown/index.html b/test/integration/testdata/feature-rich-markdown/index.html new file mode 100644 index 000000000..cc3fec011 --- /dev/null +++ b/test/integration/testdata/feature-rich-markdown/index.html @@ -0,0 +1,70 @@ + + + + + + + + + + + Feature Rich HTML + + + + {{ toHTML "table.md" }} + +

Emulated media type is 'print'.

+

Emulated media type is 'screen'.

+ + + + + + + + + + + + + + + diff --git a/test/integration/testdata/feature-rich-markdown/table.md b/test/integration/testdata/feature-rich-markdown/table.md new file mode 100644 index 000000000..bf2ab2f79 --- /dev/null +++ b/test/integration/testdata/feature-rich-markdown/table.md @@ -0,0 +1,7 @@ +## This paragraph displays a table from a markdown file + +| Tables | Are | Cool | +| -------- | :-----------: | ----: | +| col 1 is | left-aligned | $1600 | +| col 2 is | centered | $12 | +| col 3 is | right-aligned | $1 | diff --git a/test/integration/testdata/header-footer-html/footer.html b/test/integration/testdata/header-footer-html/footer.html new file mode 100644 index 000000000..41185251e --- /dev/null +++ b/test/integration/testdata/header-footer-html/footer.html @@ -0,0 +1,13 @@ + + + + + +

of

+ + diff --git a/test/integration/testdata/header-footer-html/header.html b/test/integration/testdata/header-footer-html/header.html new file mode 100644 index 000000000..f9501f916 --- /dev/null +++ b/test/integration/testdata/header-footer-html/header.html @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/test/integration/testdata/page-1-html/index.html b/test/integration/testdata/page-1-html/index.html new file mode 100644 index 000000000..9fd123da8 --- /dev/null +++ b/test/integration/testdata/page-1-html/index.html @@ -0,0 +1,9 @@ + + + + Page 1 + + +

Page 1

+ + diff --git a/test/integration/testdata/page-1-markdown/index.html b/test/integration/testdata/page-1-markdown/index.html new file mode 100644 index 000000000..d2b7442b9 --- /dev/null +++ b/test/integration/testdata/page-1-markdown/index.html @@ -0,0 +1,9 @@ + + + + Page 1 + + + {{ toHTML "page_1.md" }} + + diff --git a/test/integration/testdata/page-1-markdown/page_1.md b/test/integration/testdata/page-1-markdown/page_1.md new file mode 100644 index 000000000..960800143 --- /dev/null +++ b/test/integration/testdata/page-1-markdown/page_1.md @@ -0,0 +1 @@ +# Page 1 diff --git a/test/integration/testdata/page_1.docx b/test/integration/testdata/page_1.docx new file mode 100644 index 0000000000000000000000000000000000000000..1868509c5fe654e5dc4edb66f210459c88aa3f25 GIT binary patch literal 6408 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 + + + Pages 12 + + + +

Page 1

+

Page 2

+

Page 3

+

Page 4

+

Page 5

+

Page 6

+

Page 7

+

Page 8

+

Page 9

+

Page 10

+

Page 11

+

Page 12

+ + diff --git a/test/integration/testdata/pages-12-markdown/index.html b/test/integration/testdata/pages-12-markdown/index.html new file mode 100644 index 000000000..79b9c6d27 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/index.html @@ -0,0 +1,25 @@ + + + + Pages 12 + + + +
{{ toHTML "page_1.md" }}
+
{{ toHTML "page_2.md" }}
+
{{ toHTML "page_3.md" }}
+
{{ toHTML "page_4.md" }}
+
{{ toHTML "page_5.md" }}
+
{{ toHTML "page_6.md" }}
+
{{ toHTML "page_7.md" }}
+
{{ toHTML "page_8.md" }}
+
{{ toHTML "page_9.md" }}
+
{{ toHTML "page_10.md" }}
+
{{ toHTML "page_11.md" }}
+
{{ toHTML "page_12.md" }}
+ + diff --git a/test/integration/testdata/pages-12-markdown/page_1.md b/test/integration/testdata/pages-12-markdown/page_1.md new file mode 100644 index 000000000..960800143 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_1.md @@ -0,0 +1 @@ +# Page 1 diff --git a/test/integration/testdata/pages-12-markdown/page_10.md b/test/integration/testdata/pages-12-markdown/page_10.md new file mode 100644 index 000000000..50459fd66 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_10.md @@ -0,0 +1 @@ +# Page 10 diff --git a/test/integration/testdata/pages-12-markdown/page_11.md b/test/integration/testdata/pages-12-markdown/page_11.md new file mode 100644 index 000000000..fad63c84f --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_11.md @@ -0,0 +1 @@ +# Page 11 diff --git a/test/integration/testdata/pages-12-markdown/page_12.md b/test/integration/testdata/pages-12-markdown/page_12.md new file mode 100644 index 000000000..cfe1ccf87 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_12.md @@ -0,0 +1 @@ +# Page 12 diff --git a/test/integration/testdata/pages-12-markdown/page_2.md b/test/integration/testdata/pages-12-markdown/page_2.md new file mode 100644 index 000000000..f310be332 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_2.md @@ -0,0 +1 @@ +# Page 2 diff --git a/test/integration/testdata/pages-12-markdown/page_3.md b/test/integration/testdata/pages-12-markdown/page_3.md new file mode 100644 index 000000000..294d95c1b --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_3.md @@ -0,0 +1 @@ +# Page 3 diff --git a/test/integration/testdata/pages-12-markdown/page_4.md b/test/integration/testdata/pages-12-markdown/page_4.md new file mode 100644 index 000000000..7caf305d2 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_4.md @@ -0,0 +1 @@ +# Page 4 diff --git a/test/integration/testdata/pages-12-markdown/page_5.md b/test/integration/testdata/pages-12-markdown/page_5.md new file mode 100644 index 000000000..eb51736e5 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_5.md @@ -0,0 +1 @@ +# Page 5 diff --git a/test/integration/testdata/pages-12-markdown/page_6.md b/test/integration/testdata/pages-12-markdown/page_6.md new file mode 100644 index 000000000..74deae7e1 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_6.md @@ -0,0 +1 @@ +# Page 6 diff --git a/test/integration/testdata/pages-12-markdown/page_7.md b/test/integration/testdata/pages-12-markdown/page_7.md new file mode 100644 index 000000000..8bc5eeaf0 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_7.md @@ -0,0 +1 @@ +# Page 7 diff --git a/test/integration/testdata/pages-12-markdown/page_8.md b/test/integration/testdata/pages-12-markdown/page_8.md new file mode 100644 index 000000000..32ea6ab3c --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_8.md @@ -0,0 +1 @@ +# Page 8 diff --git a/test/integration/testdata/pages-12-markdown/page_9.md b/test/integration/testdata/pages-12-markdown/page_9.md new file mode 100644 index 000000000..6288d6f81 --- /dev/null +++ b/test/integration/testdata/pages-12-markdown/page_9.md @@ -0,0 +1 @@ +# Page 9 diff --git a/test/integration/testdata/pages-3-html/index.html b/test/integration/testdata/pages-3-html/index.html new file mode 100644 index 000000000..ab0e1d832 --- /dev/null +++ b/test/integration/testdata/pages-3-html/index.html @@ -0,0 +1,16 @@ + + + + Pages 3 + + + +

Page 1

+

Page 2

+

Page 3

+ + diff --git a/test/integration/testdata/pages-3-markdown/index.html b/test/integration/testdata/pages-3-markdown/index.html new file mode 100644 index 000000000..46ba2fe47 --- /dev/null +++ b/test/integration/testdata/pages-3-markdown/index.html @@ -0,0 +1,16 @@ + + + + Pages 3 + + + +
{{ toHTML "page_1.md" }}
+
{{ toHTML "page_2.md" }}
+
{{ toHTML "page_3.md" }}
+ + diff --git a/test/integration/testdata/pages-3-markdown/page_1.md b/test/integration/testdata/pages-3-markdown/page_1.md new file mode 100644 index 000000000..960800143 --- /dev/null +++ b/test/integration/testdata/pages-3-markdown/page_1.md @@ -0,0 +1 @@ +# Page 1 diff --git a/test/integration/testdata/pages-3-markdown/page_2.md b/test/integration/testdata/pages-3-markdown/page_2.md new file mode 100644 index 000000000..f310be332 --- /dev/null +++ b/test/integration/testdata/pages-3-markdown/page_2.md @@ -0,0 +1 @@ +# Page 2 diff --git a/test/integration/testdata/pages-3-markdown/page_3.md b/test/integration/testdata/pages-3-markdown/page_3.md new file mode 100644 index 000000000..294d95c1b --- /dev/null +++ b/test/integration/testdata/pages-3-markdown/page_3.md @@ -0,0 +1 @@ +# Page 3 diff --git a/test/integration/testdata/pages_12.docx b/test/integration/testdata/pages_12.docx new file mode 100644 index 0000000000000000000000000000000000000000..6fa6d1272cae78b6174b480d97c95cf462b31f9b GIT binary patch literal 6539 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 EA8~!?A^-pY literal 0 HcmV?d00001 diff --git a/test/integration/testdata/pages_3.docx b/test/integration/testdata/pages_3.docx new file mode 100644 index 0000000000000000000000000000000000000000..792f530be917475ca0e366a963535ec3d38b573d GIT binary patch literal 6454 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 literal 0 HcmV?d00001 diff --git a/test/integration/testdata/pages_3.pdf b/test/integration/testdata/pages_3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2f5c4c030c8c1b14813e3d70c536f2b2f78da569 GIT binary patch literal 22548 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%0ACYL9{(&E+-Ie8;bPpF2ThpvZugc>7Fl2efp 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 E2DjGkHvj+t literal 0 HcmV?d00001 diff --git a/test/testdata/api/README.md b/test/integration/testdata/pem/README.md similarity index 100% rename from test/testdata/api/README.md rename to test/integration/testdata/pem/README.md diff --git a/test/testdata/api/cert.pem b/test/integration/testdata/pem/cert.pem similarity index 100% rename from test/testdata/api/cert.pem rename to test/integration/testdata/pem/cert.pem diff --git a/test/testdata/api/key.pem b/test/integration/testdata/pem/key.pem similarity index 100% rename from test/testdata/api/key.pem rename to test/integration/testdata/pem/key.pem diff --git a/test/integration/testdata/protected_page_1.docx b/test/integration/testdata/protected_page_1.docx new file mode 100644 index 0000000000000000000000000000000000000000..a7b0deb29acfe174e08f7a9b68fd9c866325f000 GIT binary patch literal 12288 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�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n 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%0A6FCOd%gjUpnyH_`i+2)^I0D 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

- of -

- - \ No newline at end of file diff --git a/test/testdata/chromium/html/header.html b/test/testdata/chromium/html/header.html deleted file mode 100644 index 213ec4fc7..000000000 --- a/test/testdata/chromium/html/header.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/test/testdata/chromium/html/img.gif b/test/testdata/chromium/html/img.gif deleted file mode 100644 index 6b066b53edc654a8a3037f2ed5787607a67fea4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29685 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

p00D}zTu$KmEJRkzASh)(@K7-9wp~2 zCKTmxDwPT@{Azt2`QKzBBdEN4bomJVrPZkmDW_d0p`8Na{{jsB{Q>#6;N;H~=X&=@ z0Q*qKM1iH9VsYIMo&JxSqk<&z~#-!R?{MY z8UqnffPkk@{u;#h?L<-oprS*Jyze_A9Aa=qhr$m)EFD|@!R<3&i-deATh(#MZN8t| z;&|MoKf3M=aa6PrjQApJ0Eo6eCE-E-9N-WS_^QBryDxDG3M=HoR-b$8~wybg3jY zuqKj73Bl?M``_uG73C&ipvI}}c5<)?4;SvUl(KhBs>S2b=nMR&L{fx4ZLH=P*=o7V zlwy}X$x~kF%PlUKWfRyFnb+LZ8SzC$CpHGz1uDO*YDG(~sqBMjD^MpjF zxWz|T;c0fv*v4C7LBATEmB~TVCcHMLO?Qmflh1(lX$yH?-Y<2})HM2+?Q1H-jwTdT zhf2+>~iPowzl1h)R*F?*x2q+Lgtc(M)4i=MGTKwE^YdrhPwWE*cvW4|EC?K6d@^cLZ z&ybQV6Ly^gkUNSK6f(l9DHC{{sRYj-#d??ncCamF^9yOo7r9(;9A!ae%;Bv zrgUXA9Qco_EsH10j5z@F_2zkABgVMyL~(Nbiz>OkYzpSJT6DZt|1fe_TEW?d*Y`t| z7Cd4K!_b7djx7#qxOJ*X*GyY{1)yXN9!rcZZwFv}x&3{^uo_cVR~CZ{tmAkAr5;cS zAW{2vka=kN9ES=QYTB-FSDwY>dH_A{ginxwGRX^-@A_SVS+G?-HyrbDrhVVwzw@|lR(1GV!eHqCEQI@)~PCPWVuVrH}euJ<0 zB9EJX9%ZE)r(1xtmOt<4-dnLbR2eyg_UTBx90j_yY9(XtrZphX>3KYM8cILzxcnhhtIH`l&jstG&J!q96U}q&L$#z|c$`c@T@A)S4x(!q zcuK4^%Z`icd~#u$2M)_xpozFIM2FoJm&STIDa}A}@2~tWB?Odo$tdpz3NSucxX9Ji zCoP%b132)y&pj0ju0U_%J*^+Wv`W`)>n_Sz&8u%$0l0ClK-(;Ks`cRd6fsBu?44%6 z7L~5QFvZmh%SU|;C7GUcERk zcP7zZ<-dHbQMw38s9X_J>kp1f+0^$C+hyv&A;p8yu7ye#(P z_ZoT^KSL<&yzk+q@HSbW9W`<#mgC8oaXsC|m|?WtE=}xt=gx=Oz!U}3g2%z#l|hrk zSo0eqb2fnuQG59}8pWEqH;>2% zRuEd!GX;`nG8JX^BqV@o)JWRIMSF)~vvoG>pRnCrUEH&aYwWV5y&CnGGx_|Fl{3zK zv%h>T`k9jJ*K*9DpXqFkH*at>)bPK_W?B!u15hRDwcQxbUybjRZ35smnVPssAXJh> zwLMldyLiutqrXb~2J^M(9Tf-syF(M?d|MGVmoy_QC^%dU0-$e}u{^fx{hOg~GOpJU zzH9qacH4MGTHh|wHi#P$0TMTiHdFX0pI_&pd^Md$%51Mn4&V080WrSH!fr_REthxa zI^Y=?JWkoz|FrsND756$!#dXhx8a&8*%mHJM}^=2N{^pM3UwKFurV&jB9(jY^K!~F z;OL?}{?sR^;kzW&eh&q3KAt`&& zg>RkrRtcqI4c%gN#%v_+LCOb&vJ6Z7!{(ds)59mjJRoMYp2?>h49cf&#x-P_X$QMg zAJsOoj}9)Q7)ooCu~2cLBN0*wh&u_-eS1L#b>m$!#nIdLJQJG|EU*22nz*1&{!)JC zTTpsPpIRkBSIEz>x}$dzp^vT7_SMaFsOls%b#%2G!cToUPrtVbQ{8X6PA-RP^KT6$ zTMqTfbZI+u=G|*A4`TKHENi|`dGv#$)}^$@v<0^og`N)MLrnrxLkmhK5mS!nBhhjU z7bB?vW?GU0(X%UOztVUb zV=Xdr-$54R-QZIq6M(vbia{@lz;JkxcuHky(*T21DE6Ugz6>Tgg{H|s0c&|rtF?zn zcYXp4%))u9eTy+ptbSy#CB3d4fQjZ;Aq^#y7r1lwE9zl7TB@azE4+#&)XnPTF)Xcb z3+AjUHgJ@+J;$-wR~s=5fMJS#9lJ1|(D+cavv0nS1F!$k=o-mXETTAko(o3-@pKq~ z_<;xm4i_~2YZc%sN=r}{8qRGbzcFT=UvVLeLDWonLq<4PB|n%%c^njMBbE1*#a@#k z*`;*pJ=u=k(-cZgHwt#t0sDuRRt(M0uHo`JF3Fm45Hp(+?n$iA3%CcXN%lzw12EM4 zKgT1$vXsy~7+jH827JOwoq=awui|G=I69(xGaaY^#)RZXLpY^|w$UW-F-g%jQn6tw zNYljk%f{yhsqF>=VLU&`V)0&SQ{}C8g`|8Bs96P-rO}63Kz@Q(`0PT37gO(W{ZC!VRLvtrn#m8lgW7FYpfei(x2m^Bo8r4xbqU^6ic~O zo1p0EeA{`h<@wr9kIcxd>0^~C{p8=AXK|#5;!WT4IJm?cey|OK>x!N-vJuW%3f06E z4do@AW+o-o&LEZE#!GV&wNypnl3~01GuyigLmFx3Q`z>UlfUq2wnib~c_i zS{>ToS~xRGWV&0jb^yE#Sd$Nhd9MWdOhl}kzyqw%n!>7*+PQ9#wVfSUozm&D)5Q@T z$}j2N5(`#0lVK(fE#pdgd1C;%=Jx%zx;>+tNeS36!ZX;nn(ssQ0$Qq!l1xlAuX7o_ z3JWuE$#-qy7CV^CNT4B9lgN;GN>-ROU91^O`pHXSrRr|Usl!FM*Ojq(@>#JN zktMfc3RAhOkIQ_y${7hw#OjgJ`}9#SM`b|9NoCZSRgTVq#1?iWGlAwxZ&YlPW}PeOd^pc@SXcyOg_g*ToaH3qdqDD9B)W!3CoWk$n|55z6)o4FfT zb(_)L#nG@rzomzP>h&wMAdQid5``(V`OKm6U+rA_#-Dcg{UNF!8(!aI~6`8-CQdI=9 zLOqU|>tw)Hu{TO6%asy^ZBsx}Uu7*)|4)^5&hPxkwk4q}Fus~0_$S%diMD0CX47-( zHb)H7F99JiHnRi)Dy=#@emit){0HTgP_!dY{~cs_a}+UOiNE0ig>#;za{Qh?aw!R! ze4E~;DfFjZ_@uVnR9Ws3Rwjk+L=BYQndZ+JT1l35rywL{vCxcu?>h%x#Wii~^PM#t zn*U-ZPKgq^pL;2~q8iO}-}@Rpm;|1U#WW>=?=LKi_pIBMAO)W*k7jat)99}XKod=j zvtRlxcMUdGUdIjJ+)HIhp0G-Jh49jllf}}SJR#G=56eqYSrqXs@(lmwVu>Vdyu)qw ziadK+-eiofTL3axbma&`d-yel`Q`Apy&>e${{a>1ff)F*%yWXN!Fl0K4TvA8K(>c| zxdX;N%Ajcs3`Wdek!|Q?sK1alMTNR#CNt*S4G-&uoUz#H-^Y*Nif+VFABRuMwq$c{ z_`r;04P#UMWTOyY24^Oat@PwYk7fmDtlo<`lxmfcv;^#$=V{&+n9P)&>d)-QSeS;i zRP}qY2&TX3N>li5LkV4RfDB<$xe!&0oy2Efqd0p%c0Yr8!Ts`|Q}UaNs!WmupD?Sg z*RfvNlmPVVcI9Ci4iXYpO8@PbS?>0C-yb`DR!FY2#&tC1RkgN2@~Ve6D}3P2#z4*K zzxuo#Ii@49x8!tr+d_)6Sg61AqZs}HE~S+Hti+aE+dqpSRhPk)`4|pHpMJkzGZu+_}u(>_D|k_ z2FNp;g}TGRGsg6F?3u^=ulxuB8n0yg#2c1jRy7{#Bp-8vwE6Ca$? zd!wqzz;MRT1CBGek8PIq!j}kslohUkWpZ%~ufQsaO-)+O<$h>6Mk6y*x$UnJjmS7fRT}!L@^R)$jIGhfhmu*ulN?0T;KB4&)N3qwZyP2N#MkYJ_%7@|6>9zf&|Y-( z_UU}M-S_EEksay!vW#|!D$IyGrrKA#?f4fei}NsGK#xz(3M0p06t1)4rxB+!8OUGU z0!_*8)_b3GplT8nb`(aPnGljD|e_Cfyu9Od1tIbSwuE1&_1$L4J}NwLvf=X$i~Ox-M8nVM^7^LQl$s{J{r z<(qrxab%z>w)dR|YL|`B)`TbvbL}qEUjj2kN)h`zXo1PvJ#Gc&?yLWLvzVW=UKnt^ zY$uZ1orfm#IzB?rWnod1lSliAvZ~y#y}4hmygZCF`;txmu3BeEU%Y<8H!6>ky!K9;q8b(jbD;|*yYM<;X(_f_43%aCjjzqb>8H?$`aB@Q+N z(qJxPy#7kJJfJ_-SmZ1hYB6zpT4ua{xha8Tb;VwY_F*KpxWi;Q

-%qte=8FS)zy{Z??EX)jmlqiI{9V1867- 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 I2?Naj58Q}%!2kdN diff --git a/test/testdata/chromium/html/index.html b/test/testdata/chromium/html/index.html deleted file mode 100644 index a19f166e1..000000000 --- a/test/testdata/chromium/html/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - Gutenberg - - - -

- -
-

This paragraph uses the default font

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- -

This paragraph uses a Google font

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- -

This paragraph uses a local font

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
- -
-

This image is loaded from a URL

- -
- -
-

This paragraph appears if wait delay > 2 seconds or if expression window.globalVar === 'ready' returns true

- - -

This paragraph appears if the emulated media type is 'print'

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- -

This paragraph appears if the emulated media type is 'screen'

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
- -
-

This paragraph appears if JavaScript is NOT disabled

- -
- -
-

/etc/passwd

- - -

\\localhost/etc/passwd

- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/test/testdata/chromium/html/style.css b/test/testdata/chromium/html/style.css deleted file mode 100644 index a32c9a714..000000000 --- a/test/testdata/chromium/html/style.css +++ /dev/null @@ -1,29 +0,0 @@ -body { - font-family: Arial, Helvetica, sans-serif; -} - -.center { - text-align: center; -} - -.google-font { - font-family: 'Montserrat', sans-serif; -} - -@font-face { - font-family: 'Local'; - src: url('font.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -.local-font { - font-family: 'Local' -} - -@media print { - .page-break-after { - page-break-after: always; - } -} - diff --git a/test/testdata/chromium/markdown/defaultfont.md b/test/testdata/chromium/markdown/defaultfont.md deleted file mode 100644 index 14c830357..000000000 --- a/test/testdata/chromium/markdown/defaultfont.md +++ /dev/null @@ -1,3 +0,0 @@ -## This paragraph uses the default font and has been generated from a markdown file - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/test/testdata/chromium/markdown/font.woff b/test/testdata/chromium/markdown/font.woff deleted file mode 100644 index ebd62d592e7ff4cfed3020332d996b90ff24dc8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12644 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 O(-;7vnZf`p2LC^-w-1v5 diff --git a/test/testdata/chromium/markdown/footer.html b/test/testdata/chromium/markdown/footer.html deleted file mode 100644 index c355f721b..000000000 --- a/test/testdata/chromium/markdown/footer.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - -

- of -

- - \ No newline at end of file diff --git a/test/testdata/chromium/markdown/googlefont.md b/test/testdata/chromium/markdown/googlefont.md deleted file mode 100644 index d8dd4f23c..000000000 --- a/test/testdata/chromium/markdown/googlefont.md +++ /dev/null @@ -1,3 +0,0 @@ -## This paragraph uses a Google font and has been generated from a markdown file - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/test/testdata/chromium/markdown/header.html b/test/testdata/chromium/markdown/header.html deleted file mode 100644 index 213ec4fc7..000000000 --- a/test/testdata/chromium/markdown/header.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/test/testdata/chromium/markdown/img.gif b/test/testdata/chromium/markdown/img.gif deleted file mode 100644 index 6b066b53edc654a8a3037f2ed5787607a67fea4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29685 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

p00D}zTu$KmEJRkzASh)(@K7-9wp~2 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

-%qte=8FS)zy{Z??EX)jmlqiI{9V1867- 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 I2?Naj58Q}%!2kdN diff --git a/test/testdata/chromium/markdown/index.html b/test/testdata/chromium/markdown/index.html deleted file mode 100644 index faa522dbc..000000000 --- a/test/testdata/chromium/markdown/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - Gutenberg - - -

- -
- {{ toHTML "defaultfont.md" }} - -
- {{ toHTML "googlefont.md" }} -
- -
- {{ toHTML "localfont.md" }} -
-
- -
- {{ toHTML "table.md" }} - -

HTML from previous table

- -
- - \ No newline at end of file diff --git a/test/testdata/chromium/markdown/localfont.md b/test/testdata/chromium/markdown/localfont.md deleted file mode 100644 index c0aac6f33..000000000 --- a/test/testdata/chromium/markdown/localfont.md +++ /dev/null @@ -1,3 +0,0 @@ -## This paragraph uses a local font and has been generated from a markdown file - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/test/testdata/chromium/markdown/style.css b/test/testdata/chromium/markdown/style.css deleted file mode 100644 index e937998f4..000000000 --- a/test/testdata/chromium/markdown/style.css +++ /dev/null @@ -1,28 +0,0 @@ -body { - font-family: Arial, Helvetica, sans-serif; -} - -.center { - text-align: center; -} - -.google-font { - font-family: 'Montserrat', sans-serif; -} - -@font-face { - font-family: 'Local'; - src: url('font.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -.local-font { - font-family: 'Local' -} - -@media print { - .page-break-after { - page-break-after: always; - } -} \ No newline at end of file diff --git a/test/testdata/chromium/markdown/table.md b/test/testdata/chromium/markdown/table.md deleted file mode 100644 index 057f69b3d..000000000 --- a/test/testdata/chromium/markdown/table.md +++ /dev/null @@ -1,7 +0,0 @@ -## This paragraph displays a table from a markdown file - -| Tables | Are | Cool | -|----------|:-------------:|------:| -| col 1 is | left-aligned | $1600 | -| col 2 is | centered | $12 | -| col 3 is | right-aligned | $1 | \ No newline at end of file diff --git a/test/testdata/libreoffice/document.docx b/test/testdata/libreoffice/document.docx deleted file mode 100644 index e745b3e5ec71e96fa1c3a306a63fff8f0237dae5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91571 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 diff --git a/test/testdata/libreoffice/protected.docx b/test/testdata/libreoffice/protected.docx deleted file mode 100644 index 840d00d5632f950382b3403b8104d31174f62714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 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 diff --git a/test/testdata/pdfengines/sample1.pdf b/test/testdata/pdfengines/sample1.pdf deleted file mode 100644 index 0a0b284fabbf8819a1b88185dea5fba64f7e3e34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208299 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�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n 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%0A6FCOd%gjUpnyH_`i+2)^I0D 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 GQU4DG17s5b diff --git a/test/testdata/pdfengines/sample2.pdf b/test/testdata/pdfengines/sample2.pdf deleted file mode 100644 index 0a0b284fabbf8819a1b88185dea5fba64f7e3e34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208299 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�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n zhyM5g00aZh0cO+&8BVXyAdkrWC=aXrJ@G7wA5e?A(Y}06EndJkFJnt^NIS>knq)In zA$w&J?-PbDkDLM2fSaB@a7+$~co>34NO&Awl;Dk4bgc)m5oR=-q<~74?CcNM9E_=u zw0MK^A@}_@WSF{K5QMp~_mSYd2@NtT_|f^|7zIK_L1JT_c<-00N-9*uqLg=aCLHT& zSFnY`0zO>M;!J~7DMPJTy~ppJ`H8@iG=n~8Iti190(E=+etBG=)~w$U--hL(?Nrzlw+9!Zci&)c*hj`Ho#$IjI-+D&?!zBtdk7DRVn`6-{0!8tDY+ zW~gxhq#UM6p@BdM5I^B^=qO?(3|1y&Hq(*N!$<%i42GAlo-h;v2m+DS>0$Emuo(mW zT`%yw0RuueeV;ek!TX9U{0O7@;x%Y6h;|*fK=_N!x4xaBlfl7AgZc9!|hS$mqHx1J`Q{)toDUaHc}M zM!rfPC8_#{^Zklq`wfV^4wD_7@`9aYM*^Q4sAqi#QX0{KESu&k1ownZr1%H=2;en%$6Dwyh4(mf5%-w6C|2wI-P1!P#4q#@^<$`Pl9h*^qeMVEUcYEPK@5h z@ZqPS8$nglnF;xL?E#S}L}d7Hyc(p;VV%B1;haB%1~XZ{QJ!&Bc$x?o{{X)5m6Npq zBP|ZFAi>oJUDX^kEYeU|w>at?1qLfSkpBRDwIOK=uCGF^yajC%3iLi=;zUuPk-rCH zc#Rb@1`u*IJtNntLXnz$az3GZ2!2QWxMReD0fl~5Tk<>QDy~x&PtzWB@f3r=eJ|ed zEW!;;uZ&Dq{LrYy5{t4KP}TxeZxqMqJOb}PEIgBiJa8bl53>G0JQFGN9_1YhPcIuJ z+XfEr8lU&@I{+6)iTNiR#Y4DN1hn;sDMEs-8c@7=+Hf?UJ;njX7HV5kr z7v<_5I=AL4tLW0N&3Hy5`jP-qdlx1Ft1LzOf#|^^Qe#b*`Zyx;`fL1!`B3)ph_<&k zu@#LdR9xXESz%olRsg;U@Pu1#i9)J}ZvC9UHURhqX=P79(3^5d<_5gvL9la1A<>MA zgrlUNXUU#2sD_1hIH`?8esw> zEi6(%oxDK>0@R3`SyGr%@iiwch!@#t>_M!Pr=?C;)E0cJyxs4E*Y*%dxb(BCdc$}~ zh%B((>?mH~uL1#Ns}>V38Y*IHCTR;j$*e;A`|xD(wjv^(DIUh@)}B9URD_n%ugKmb zzYn}5?VaJmd86|AxXXrX#L2sX8mWRfot$ApMpN@|@ZkzJ4L@$Da#M*m5N?eIS43?& z9J(w8gd`LI(C}UgS4hGDDTow{!0d9bNHMGxpAPGJ&CwV;MB4ZP{TcG$T?Cq^w`d+-hnCce))ey6cp<&> z-wJSfy=srk`VORf*a*1z{(kDK03j?7dJ|vTaSh*!ssly@fp46MKM0BR{f2y=rAsb= za5V-5p5gT49P!X5(Jom z(6glMPxPGfntCt~(ii^#E(c1tv}}>=ugY+?y$c$NWXbz^I3b476QS`#c)_icNG$4q zv+ILgXY#>*3cMM}!L~ofe}MSo4G=~QB$S4_oMgYGH$;91&*zjPgYRYOWV$~ZoLUi= z2|!#0YM~u3RiC~^gT(aabjd6Qe=pYt{745_UrkrFRD|c3`NG3zYoBi&tG(g_1HMK4 z@Frh73(`_AY+L*mfWnW^f9`QeHCbjYx@>p8NQ|KftM;ze87J;8n{d*SE^ zgGchmr|gJ4Vve;UjZae!o1K8soJmi>lp2_+@^R^IawPjBq+EA;V^vE8xN5XvJ65#W|1c(p$Kr@zO3zK2c zW4{{w@Gguz!=V8W+hOMx{Xr7dgI%pU>W)2jKhPi_2l@V(004#Y{{SClIY_9^QsGv( z829fQYA{{&sS`{(ZQ%nj6g~p$ll{0o5SaeW%5aKUDwnjK7}!bDauq~m8X!ys2`5R> zsM83b^hl2I^)c&&0i?~H3!uq|x9eI48i7aT zmgL(`$i$loCimi;dkaT_(SAlBUoQ>v8E6lE4DYL#tz@==JnerxL%;)Ca`m$Jg2TH*&3>hWsg|Djv9GmYk?GjCw&<4DMkBWD#})LA1;OVPD+{= zNs;dG1a-nRhL{#QGp@%qcV`$92?hN3l4*ehppqknx4hWDJs@LJgoj_tmO%w0;4d>1 zi4c1WXKnTHBepD+?W=xVbU4XD3hK*9;|IPxXM(ZB`Z2|vO~@T@hM4_vpYJgL0K{=> zqzfcDIy~Q#gwv`*gGy*Lb)s^8AVBlAE&zQ6Lp1N4oR0 ztY0%p5AYuN+D0?+wb}2M;n7!EP+>reF$I=s#wIP%(&FI@iVo^u`!FyP)qn?R?ck9ftZq@MV@5<d~m_LuV7$hP_KsVsO9JX**#O=*#j6M0r_=SWq ziw)_0H-HwZkO)RO$-CEc#nC?^2vfLq-`^E80XQE{&^AzO!GX47$~__@CkkrCEykae z@Es}S_g4f_5 z!{?Qbh$J7AVZSejf@64**nVH$zZ^O{#R_VAK=|435`}|{L8A|2VVj_V2*zT72_Dyf zUNx$~rUmy``1;~=!{Ejn>7l>lg?mRX(Ek7r>ze94*e3Zc_rleM;!>*?9V(v=3H_sC z0zhgbK`TxWOt5yMQ5u07kyBxriICH=PlXaAWL8`gNEJ9!NDl8FLIa8l(b=Fay71fj zVSHg?1F@OZcqrB&SFF(b0fk>LAB7_LT3_413{4ae0ax|uFA>4OHiJQAUHB^D%irNb zl#v(U?el|07q^_*Cs_g;I1aV0eT+;x>ow!RsjS7YB$i0|dGZG27ZdVb^S?LKKBIsxdlzS6&4JKoTIk)gbeJrLSjX^*N*gGn(J-UAgPQb4GpSijO3Ihih z;@ei|CN~wJWdSsy*Xx*IW<(t*SK=HHvp2Z?hX7Q+^(LzM5_4G|w*U^y319$m#DIzs zS4Q&&=5x>29U2HOlPULj=Tl&-Cr!eIjpatbrF6{wm!%619gz$YMGf$ezID$6L>l`& zY)$N@@D%{7=Nd1hTUpylCsJ35Uau4!g9;DF612xvf+$h`bCJR0o9Z}0p@$zvf&}tQB-d>90kL>}>VJ?#tSIrE5 zcsMm+x_jDaZwZg~HSEoMwt6IH}>Fk}HFTx(MMJO|FWJ2+ac{ zN_CRVMC>L~rSDqw(j z(7t2(ytXeLiK`ds!d?*VQqjZcDi^;(6z_NZ*=;APf zNkzTE6C3k)j{3SD^i7{h!PCnDJL-ka+s@=As5~mP%>x?MXZqSOhrI#P=p>w z(&a`|u0bdnoyVOF`M_H_l*4hNjB(T61_1q{6H>ChB4c=DO>47<Ku!er29e&^A zgI1TsK&Z$DHFV?nL?!OYwu<21oGmy)0UZvDUtsrmYnrAj@6T81J~%&Rsw+l5hvGtW zDhQemm$g&f$K!~|>IS`!YOQ(^+e(B4evM@)csG~Oc2z(fxWDM!IJa34Bu>UQFz_}%1|0)eDM zARVk3c=U6j5Z3&NpI>}h*R(28-c(&D7yPgSny<6?4jh%>4=;eI`~lz#yCP6?@KOxz z_;?vl5CMQ6UUBiuZ$n?V`Mf>V=m(-lq3|_)a9-J>2JOxGeYt$?8t z7v#Od0nY*$%C#OGIeuo9sw?Vf7K;UV#Tx`}TEzJIVFL0l1(g=h5L)pe1p59cA+#gC zJS!O&aEWflkIb)plFs1>yE;%l?iA+FIjj~4!6=d%_lZyQQeOj6*V4XoU(Cm=y}h^l zVE~k6tUbdIb>D#EfU5(w=J_;B9hCIs-Z}!3QuO_s!iBvb$&vmzWq|-dK0l(Oxzrvl zq!65xP+7#AYJ^qTyn6iLAM(rwPs0&^Jnh>Ur0hrdM;@LYP!myxW*b5Xi8F<^sFz$E z0SMM(S;>Ua#DDH2N2EmM)xER=CuFbZdDOwPYCrf>l$m8zR0l!y{d>i73~e^ws29*b z99(J63&on@e6h{|P@Sv@GF@Pm^g7O_0PmIvK`+_Bh`P-KN`8Np_;7{Y68^<{eD5ZR zky0ZWS$QF$!>n7Bpa!va3`Zmg{ zHb@md!92daK=WcIf`T148UnofI(rTRpeqt5yjhbFC*ZKGuczJSG>eeNkA}?KMbVWj z3OKS2pebt?va#AMgSdKvqTrS!&kS~wmhl9RPR2{~__ISj) zz5ayNf~oLn&nf=^wxyMX6HISnly1#=pQmkNBjhXY%is4?KUEE@ir~x#Se&t>QySev zogV<@bc|aXz|P(VPyy~fz|NlM5>Emp*Rrf*GsokMD~O5`15c8EIalGx8uTof{>K)k z!mBTq1^h1tv-SW#?BcXzWhhgxmEp_y5Rl!T7dj4g!(Mi=jOi(qdh3%}52$=X;PkBc z*0041CCKE6cmOc_Qt^*f`X+Xw1 zU1`@kk3-nJZcxl8e84UT{SThm?l7j?AUWnhf%fVIVQv+a7RmQUMls)s* zKBzCq{Bc-HfBdJlezSzbZ$zx51(93AjfCnjYS{pUKEI)m_CSUtY1Z704=9$LLhvy` zDSW5o;=Gb^4zvgH#K6j?^$+0lCo>kTu;Xb zF=@(URN6a#GlX=zDeydYKZYOv1jmdjhgBeCK=|u`wp9i|5+V3-pft^mghTJV07^oC z_pbMtc16M3y)PY9P#CljoB}p&-CqyCa;|5O5uyqCaOh zsGkD?GvJ4#b_>l{T#n$g^CLLfkdj=jSj!Ba^oi9FK-X%Zb;knH;sr>4rT#;dqdc98 zjVESyxp>#QakPQ+oAvs%g!_R6gEH2bTFwIlgd1Rocf%+{INFd@zMayIJfDEE?A~LZ zi$6v@r6HHZ(4H-*186tu2b~n#kNlg$9~vhJ2eni7H_OQ5+@NH=jC|yC@A1_Wl&NIj z4lJ*w+Ez2C8dXm@-H*RFTs264{%{CVy?{s1{{SJt%7gT0AGkO*c_Bm-^V$6PtW=wU zPsKmm${`0`1a+&=ILuJ0XP>(M`HwIqcOq3M%;r^*mazTcxd(m$lLw`nT{!*}SW!BZ zh1;Vz#-1tAMgRv`NZtrzIAXzH%>4j){UXDcDZ&zuES$}&Dd(+>5)*;?BQ3@B7*R-FwgB%p{Y{WHOUUmfZ+R za@pw`rbgjQ&cAX}%_c^QZmQ(`}IZF%}` zacJiTp%>8PGUfH2s6?^k)3wo`kF^6ynjkQb2?yd)S;-yklTt~rLqkd6iLeS!I^IvAD}jAE2-POy}e zd-d3P#J{r6pt3v*Wda~1Zg{5}=T(cbVifaj zoSr-m!c95IH;yA^>}%DHf8C7*K0O4JE^I(<_Z9g6DwslKY(f@QUgp;U*-lkOZ%ZWb zwn~;@xgQ@4fg3;U3W@@#l~zHky*i$wWX~!|*heDarOf-Gi-W7gZqi5XG3c&BH8shu zK3N|zR)i0z{NpHOks{PgrO#hc(JW_FF#J&s%;@snVzr^U&$olSpeedLQw_L02lLRV z;$G{h=dR*QzZ+&V3C(so=WB|PMhuhRIu=3QZYogc@ZXGojuz2s3^-GH1}Z3gs6fb$ zD9G(3ku5ERS6Z#cJN|xmU>rbk{lnLNKMdA{lN6>-nZai-u(Z2S7E3uEhzeIsbKd4c zhnffSSS#+3%7aq))Mcg>hG(u{UCwH2P_7Rli9r>G)kCm7l*>+a2mh2)Y6l60_V@2% zRkJ~PE#cTO5`r#2Va{J4gXT0mY)+AiH;Dc7&lnW16H`210a`thLn8br@*n#H)<1NS z@fo0B_XEHWU_{y%w?7v%QaGxbkceRIuq6z5L6upH8)#2Y+!Mq4$l)aR#BKQO55dVD z&00O)+z$^6uoPRFYYPD#;^HV+BInSnbGx~#ja;}%0A6FCOd%gjUpnyH_`i+2)^I0D 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 GQU4DG17s5b diff --git a/test/testdata/pdfengines/sample3.pdf b/test/testdata/pdfengines/sample3.pdf deleted file mode 100644 index d079b89242260e3967ad2e2a43d6940e58e9fdc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224175 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�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n 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%0A6FCOd%gjUpnyH_`i+2)^I0D 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 diff --git a/test/testdata/pdfengines/sample4.pdf b/test/testdata/pdfengines/sample4.pdf deleted file mode 100644 index 5fc57f1403a293b2f5145fe3e39d172f1a796422..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8025 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�*vR@PW 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%0AF<}dU#dI*5D`$OCWsZ_|vQTQ7n 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%0A6FCOd%gjUpnyH_`i+2)^I0D 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 From 2131de77b0942146aced51065409242f5c96090d Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Mar 2025 13:51:54 +0100 Subject: [PATCH 074/254] ci(test): no concurrency --- .github/actions/build-test-push/test.sh | 2 +- Makefile | 4 +++- test/integration/main_test.go | 20 ++++++++++++-------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/actions/build-test-push/test.sh b/.github/actions/build-test-push/test.sh index ee5eb1973..cc44776bc 100755 --- a/.github/actions/build-test-push/test.sh +++ b/.github/actions/build-test-push/test.sh @@ -72,7 +72,7 @@ run_cmd() { fi } -cmd="make test-integration DOCKER_REPOSITORY=$repository GOTENBERG_VERSION=$version PLATFORM=$platform" +cmd="make test-integration DOCKER_REPOSITORY=$repository GOTENBERG_VERSION=$version PLATFORM=$platform NO_CONCURRENCY=true" run_cmd "$cmd" echo "✅ Done!" diff --git a/Makefile b/Makefile index e997b419f..948141fd6 100644 --- a/Makefile +++ b/Makefile @@ -160,13 +160,15 @@ test-unit: ## Run unit tests go test -race ./... PLATFORM= +NO_CONCURRENCY=false .PHONY: test-integration test-integration: ## Run integration tests go test -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration -args \ --gotenberg-docker-repository=$(DOCKER_REPOSITORY) \ --gotenberg-version=$(GOTENBERG_VERSION) \ - --gotenberg-container-platform=$(PLATFORM) + --gotenberg-container-platform=$(PLATFORM) \ + --no-concurrency=$(NO_CONCURRENCY) .PHONY: lint lint: ## Lint Golang codebase diff --git a/test/integration/main_test.go b/test/integration/main_test.go index f77432814..6e4036c06 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -14,17 +14,11 @@ import ( "github.com/gotenberg/gotenberg/v8/test/integration/scenario" ) -var opts = godog.Options{ - Format: "pretty", - Paths: []string{"features"}, - Output: colors.Colored(os.Stdout), - Concurrency: runtime.NumCPU(), -} - func TestMain(m *testing.M) { repository := flag.String("gotenberg-docker-repository", "", "") version := flag.String("gotenberg-version", "", "") platform := flag.String("gotenberg-container-platform", "", "") + noConcurrency := flag.Bool("no-concurrency", false, "") flag.Parse() if *platform == "" { @@ -40,10 +34,20 @@ func TestMain(m *testing.M) { scenario.GotenbergVersion = *version scenario.GotenbergContainerPlatform = *platform + concurrency := runtime.NumCPU() + if *noConcurrency { + concurrency = 0 + } + code := godog.TestSuite{ Name: "integration", ScenarioInitializer: scenario.InitializeScenario, - Options: &opts, + Options: &godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + Output: colors.Colored(os.Stdout), + Concurrency: concurrency, + }, }.Run() os.Exit(code) From 33f9f1ddf4773b22d3229cfca2130df370531a33 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Mar 2025 14:14:00 +0100 Subject: [PATCH 075/254] docs(test): add TODOs [ci skip] --- test/integration/features/chromium_convert_html.feature | 3 +++ test/integration/features/chromium_convert_markdown.feature | 3 +++ test/integration/features/chromium_convert_url.feature | 3 +++ 3 files changed, 9 insertions(+) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index 4f615c74b..934ba6ff4 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -1,3 +1,6 @@ +# TODO: +# 1. JavaScript disabled on some feature. + Feature: /forms/chromium/convert/html Scenario: POST /forms/chromium/convert/html (Default) diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index 286ea9338..584403ada 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -1,3 +1,6 @@ +# TODO: +# 1. JavaScript disabled on some feature. + Feature: /forms/chromium/convert/markdown Scenario: POST /forms/chromium/convert/markdown (Default) diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index b48c2f4ae..9c1052471 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -1,3 +1,6 @@ +# TODO: +# 1. JavaScript disabled on some feature. + Feature: /forms/chromium/convert/url Scenario: POST /forms/chromium/convert/url (Default) From cd6add14a00319412ba8794d88ea077aa77cdca4 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Mar 2025 14:15:44 +0100 Subject: [PATCH 076/254] ci(build-test-push): add missing Cloud Run tags --- .github/actions/build-test-push/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-test-push/action.yml b/.github/actions/build-test-push/action.yml index 019dc911e..78a017d60 100644 --- a/.github/actions/build-test-push/action.yml +++ b/.github/actions/build-test-push/action.yml @@ -77,7 +77,7 @@ runs: shell: bash run: | .github/actions/build-test-push/push.sh \ - --tags "${{ steps.build.outputs.tags }}" \ + --tags "${{ steps.build.outputs.tags }},${{ steps.build.outputs.tags_cloud_run }}" \ --dry-run "${{ inputs.dry_run }}" - name: Outputs From 67a65c66b64597c433b857e045eefd6e9fe588ad Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 21 Mar 2025 16:52:57 +0100 Subject: [PATCH 077/254] ci(edge): fix wrong repository and tag --- .github/workflows/continuous-integration.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ab8dc1c59..f22d22e3b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -206,9 +206,8 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: edge platform: linux/amd64 - alternate_repository: snapshot edge_386: if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -229,9 +228,8 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: edge platform: linux/386 - alternate_repository: snapshot edge_arm64: if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -252,9 +250,8 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: edge platform: linux/arm64 - alternate_repository: snapshot edge_arm_v7: if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -275,9 +272,8 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: edge platform: linux/arm/v7 - alternate_repository: snapshot merge_clean_edge_tags: needs: From f112cb748f9338ac961cb4c33da3d8982d9aa247 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 22 Mar 2025 18:02:00 +0100 Subject: [PATCH 078/254] ci(tests): add go test timeout --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 948141fd6..ae6747e9b 100644 --- a/Makefile +++ b/Makefile @@ -164,7 +164,7 @@ NO_CONCURRENCY=false .PHONY: test-integration test-integration: ## Run integration tests - go test -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration -args \ + go test -timeout 20m -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration -args \ --gotenberg-docker-repository=$(DOCKER_REPOSITORY) \ --gotenberg-version=$(GOTENBERG_VERSION) \ --gotenberg-container-platform=$(PLATFORM) \ From 218052d5dde35b11ae781d5a1f0e4179fd67fe0c Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 22 Mar 2025 18:05:47 +0100 Subject: [PATCH 079/254] chore(tests): fix typo [skip ci] --- test/integration/scenario/containers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/scenario/containers.go b/test/integration/scenario/containers.go index 1d752ca10..23f5f3c11 100644 --- a/test/integration/scenario/containers.go +++ b/test/integration/scenario/containers.go @@ -35,9 +35,9 @@ func startGotenbergContainer(ctx context.Context, env map[string]string) (*testc return nil, nil, fmt.Errorf("create Gotenberg container network: %w", err) } - heathPath := "/health" + healthPath := "/health" if env["API_ROOT_PATH"] != "" { - heathPath = fmt.Sprintf("%shealth", env["API_ROOT_PATH"]) + healthPath = fmt.Sprintf("%shealth", env["API_ROOT_PATH"]) } req := testcontainers.ContainerRequest{ @@ -48,7 +48,7 @@ func startGotenbergContainer(ctx context.Context, env map[string]string) (*testc hostConfig.ExtraHosts = []string{"host.docker.internal:host-gateway"} }, Networks: []string{n.Name}, - WaitingFor: wait.ForHTTP(heathPath), + WaitingFor: wait.ForHTTP(healthPath), Env: env, } From a9754d6a1c994801a44a2ccad9d7bd0c15088569 Mon Sep 17 00:00:00 2001 From: Hector Zarate Date: Wed, 26 Mar 2025 10:28:58 +0100 Subject: [PATCH 080/254] fix(logging): correctly map GCP structured log keys (#1159) * fix: correctly map GCP structured log keys * Update pkg/modules/logging/logging.go Co-authored-by: Julien Neuhart --------- Co-authored-by: Julien Neuhart --- pkg/modules/logging/logging.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/modules/logging/logging.go b/pkg/modules/logging/logging.go index 36fc1ae28..e25260388 100644 --- a/pkg/modules/logging/logging.go +++ b/pkg/modules/logging/logging.go @@ -198,6 +198,12 @@ func newLogEncoder(format string, gcpSeverity bool) (zapcore.Encoder, error) { if gcpSeverity { encCfg.EncodeLevel = gcpSeverityEncoder + // Those only make sense in JSON. + encCfg.TimeKey = "time" + encCfg.LevelKey = "severity" + encCfg.MessageKey = "message" + encCfg.EncodeTime = zapcore.ISO8601TimeEncoder + encCfg.EncodeDuration = zapcore.MillisDurationEncoder } } From 6c727b7e51144ad3a4cd91ea865a5459f19621e8 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 26 Mar 2025 11:37:48 +0100 Subject: [PATCH 081/254] refactor(logging): use log-enable-gcp-fields --- Makefile | 6 +-- pkg/modules/logging/logging.go | 69 ++++++++++++++++------------ pkg/modules/pdfengines/pdfengines.go | 1 + 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index ae6747e9b..fc2719570 100644 --- a/Makefile +++ b/Makefile @@ -60,8 +60,7 @@ LIBREOFFICE_DISABLE_ROUTES=false LOG_LEVEL=info LOG_FORMAT=auto LOG_FIELDS_PREFIX= -LOG_ENABLE_GCP_SEVERITY=false -PDFENGINES_ENGINES= +LOG_ENABLE_GCP_FIELDS=false PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk PDFENGINES_FLATTEN_ENGINES=qpdf @@ -132,8 +131,7 @@ run: ## Start a Gotenberg container --log-level=$(LOG_LEVEL) \ --log-format=$(LOG_FORMAT) \ --log-fields-prefix=$(LOG_FIELDS_PREFIX) \ - --log-enable-gcp-severity=$(LOG_ENABLE_GCP_SEVERITY) \ - --pdfengines-engines=$(PDFENGINES_ENGINES) \ + --log-enable-gcp-fields=$(LOG_ENABLE_GCP_FIELDS) \ --pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \ --pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \ --pdfengines-flatten-engines=$(PDFENGINES_FLATTEN_ENGINES) \ diff --git a/pkg/modules/logging/logging.go b/pkg/modules/logging/logging.go index e25260388..0c4fd7579 100644 --- a/pkg/modules/logging/logging.go +++ b/pkg/modules/logging/logging.go @@ -34,10 +34,10 @@ const ( // Logging is a module which implements the [gotenberg.LoggerProvider] // interface. type Logging struct { - level string - format string - fieldsPrefix string - enableGcpSeverity bool + level string + format string + fieldsPrefix string + enableGcpFields bool } // Descriptor returns a [Logging]'s module descriptor. @@ -49,7 +49,14 @@ func (log *Logging) Descriptor() gotenberg.ModuleDescriptor { fs.String("log-level", infoLoggingLevel, fmt.Sprintf("Choose the level of logging detail. Options include %s, %s, %s, or %s", errorLoggingLevel, warnLoggingLevel, infoLoggingLevel, debugLoggingLevel)) fs.String("log-format", autoLoggingFormat, fmt.Sprintf("Specify the format of logging. Options include %s, %s, or %s", autoLoggingFormat, jsonLoggingFormat, textLoggingFormat)) fs.String("log-fields-prefix", "", "Prepend a specified prefix to each field in the logs") + fs.Bool("log-enable-gcp-fields", false, "Enable GCP fields - namely: time, message, severity") + + // Deprecated flags. fs.Bool("log-enable-gcp-severity", false, "Enable Google Cloud Platform severity mapping") + err := fs.MarkDeprecated("log-enable-gcp-severity", "use log-enable-gcp-fields instead") + if err != nil { + panic(err) + } return fs }(), @@ -64,7 +71,7 @@ func (log *Logging) Provision(ctx *gotenberg.Context) error { log.level = flags.MustString("log-level") log.format = flags.MustString("log-format") log.fieldsPrefix = flags.MustString("log-fields-prefix") - log.enableGcpSeverity = flags.MustBool("log-enable-gcp-severity") + log.enableGcpFields = flags.MustDeprecatedBool("log-enable-gcp-severity", "log-enable-gcp-fields") return nil } @@ -104,7 +111,7 @@ func (log *Logging) Logger(mod gotenberg.Module) (*zap.Logger, error) { return nil, fmt.Errorf("get log level: %w", err) } - encoder, err := newLogEncoder(log.format, log.enableGcpSeverity) + encoder, err := newLogEncoder(log.format, log.enableGcpFields) if err != nil { return nil, fmt.Errorf("get log encoder: %w", err) } @@ -169,42 +176,44 @@ func newLogLevel(level string) (zapcore.Level, error) { return lvl, nil } -func newLogEncoder(format string, gcpSeverity bool) (zapcore.Encoder, error) { +func newLogEncoder(format string, gcpFields bool) (zapcore.Encoder, error) { isTerminal := term.IsTerminal(int(os.Stdout.Fd())) encCfg := zap.NewProductionEncoderConfig() + // Normalize the log format based on the output device. + if format == autoLoggingFormat { + if isTerminal { + format = textLoggingFormat + } else { + format = jsonLoggingFormat + } + } + + // Use a human-readable time format if running in a terminal. if isTerminal { - // If interactive terminal, make output more human-readable by default. - // Credits: https://github.com/caddyserver/caddy/blob/v2.1.1/logging.go#L671. encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString(ts.Local().Format("2006/01/02 15:04:05.000")) } + } - if format == autoLoggingFormat { - format = textLoggingFormat - } - - if format == textLoggingFormat && gcpSeverity { + // Configure level encoding based on format and GCP settings. + if format == textLoggingFormat && isTerminal { + if gcpFields { encCfg.EncodeLevel = gcpSeverityColorEncoder - } else if format == textLoggingFormat { + } else { encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder - } else if gcpSeverity { - encCfg.EncodeLevel = gcpSeverityEncoder - } - } else { - if format == autoLoggingFormat { - format = jsonLoggingFormat } + } - if gcpSeverity { - encCfg.EncodeLevel = gcpSeverityEncoder - // Those only make sense in JSON. - encCfg.TimeKey = "time" - encCfg.LevelKey = "severity" - encCfg.MessageKey = "message" - encCfg.EncodeTime = zapcore.ISO8601TimeEncoder - encCfg.EncodeDuration = zapcore.MillisDurationEncoder - } + // For non-text (JSON) or when GCP fields are requested outside a terminal text output, + // adjust the configuration to use GCP-specific field names and encoders. + if gcpFields && format != textLoggingFormat { + encCfg.EncodeLevel = gcpSeverityEncoder + encCfg.TimeKey = "time" + encCfg.LevelKey = "severity" + encCfg.MessageKey = "message" + encCfg.EncodeTime = zapcore.ISO8601TimeEncoder + encCfg.EncodeDuration = zapcore.MillisDurationEncoder } switch format { diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index f029f0af6..aae87f0d5 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -51,6 +51,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor { fs.StringSlice("pdfengines-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature - empty means all") fs.Bool("pdfengines-disable-routes", false, "Disable the routes") + // Deprecated flags. fs.StringSlice("pdfengines-engines", make([]string, 0), "Set the default PDF engines and their default order - all by default") err := fs.MarkDeprecated("pdfengines-engines", "use other flags for a more granular selection of PDF engines per method") if err != nil { From e7c77b7bf364b06e0f178a2f8dd77cf26e49d726 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 26 Mar 2025 11:38:56 +0100 Subject: [PATCH 082/254] ci(continous-delivery): fix wrong version --- .github/workflows/continuous-delivery.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 1ada08b38..1bb094e6a 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -24,7 +24,7 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: ${{ github.event.release.tag_name }} platform: linux/amd64 skip_integrations_tests: true @@ -44,7 +44,7 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: ${{ github.event.release.tag_name }} platform: linux/386 skip_integrations_tests: true @@ -64,7 +64,7 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: ${{ github.event.release.tag_name }} platform: linux/arm64 skip_integrations_tests: true @@ -84,7 +84,7 @@ jobs: with: docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }} - version: pr-${{ github.event.pull_request.number }} + version: ${{ github.event.release.tag_name }} platform: linux/arm/v7 skip_integrations_tests: true From 0b7c51a323999b3411b4fd2b3011c47130916e1e Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 26 Mar 2025 20:08:18 +0100 Subject: [PATCH 083/254] chore(deps): update golangci-lint to v2 --- .env | 1 - .github/workflows/continuous-integration.yml | 2 +- .golangci.yml | 65 +++++++++++--------- Makefile | 6 +- pkg/modules/api/mocks.go | 6 +- 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/.env b/.env index 6c1c2a5a6..7922de9fd 100644 --- a/.env +++ b/.env @@ -7,7 +7,6 @@ GOTENBERG_USER_UID=1001 NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. -GOLANGCI_LINT_VERSION=v1.64.2 # See https://github.com/golangci/golangci-lint/releases. GOTENBERG_VERSION=snapshot DOCKERFILE=build/Dockerfile DOCKERFILE_CLOUDRUN=build/Dockerfile.cloudrun diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f22d22e3b..4f527b85f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -31,7 +31,7 @@ jobs: - name: Run linters uses: golangci/golangci-lint-action@v6 with: - version: v1.64.2 + version: v2.0.2 lint-prettier: name: Lint non-Golang codebase diff --git a/.golangci.yml b/.golangci.yml index aad2389a5..1108eb25d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,56 +1,65 @@ -linters-settings: - gci: - sections: - - standard - - default - - prefix(github.com/gotenberg/gotenberg/v8) - skip-generated: true - custom-order: true - +version: "2" +run: + issues-exit-code: 1 + tests: false linters: - disable-all: true + default: none enable: - asasalint - asciicheck - bidichk - bodyclose + - copyloopvar - decorder - dogsled - dupl - dupword - durationcheck - - copyloopvar - errcheck - errname - exhaustive - - gci - - gofmt - - goimports - - gofumpt - gosec - - gosimple - govet - - ineffassign - importas + - ineffassign - misspell - prealloc - promlinter - #- sloglint - staticcheck - testableexamples - tparallel - - typecheck - unconvert - unused - usetesting - wastedassign - whitespace - -run: - timeout: 5m - issues-exit-code: 1 - tests: false - -output: - print-issued-lines: true - print-linter-name: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(github.com/gotenberg/gotenberg/v8) + custom-order: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index fc2719570..8b2aa4024 100644 --- a/Makefile +++ b/Makefile @@ -180,13 +180,9 @@ lint-prettier: ## Lint non-Golang codebase lint-todo: ## Find TODOs in Golang codebase golangci-lint run --no-config --disable-all --enable godox -# go install mvdan.cc/gofumpt@latest -# go install github.com/daixiang0/gci@latest .PHONY: fmt fmt: ## Format Golang codebase and "optimize" the dependencies - gofumpt -l -w . - gci write -s standard -s default -s "prefix(github.com/gotenberg/gotenberg/v8)" --skip-generated --skip-vendor --custom-order . - go mod tidy + golangci-lint fmt .PHONY: prettify prettify: ## Format non-Golang codebase diff --git a/pkg/modules/api/mocks.go b/pkg/modules/api/mocks.go index 6d6c2a5f7..3cdb8307e 100644 --- a/pkg/modules/api/mocks.go +++ b/pkg/modules/api/mocks.go @@ -83,7 +83,7 @@ func (ctx *ContextMock) SetLogger(logger *zap.Logger) { // ctx := &api.ContextMock{Context: &api.Context{}} // ctx.setEchoContext(c) func (ctx *ContextMock) SetEchoContext(c echo.Context) { - ctx.Context.echoCtx = c + ctx.echoCtx = c } // SetMkdirAll sets the [gotenberg.MkdirAll]. @@ -91,7 +91,7 @@ func (ctx *ContextMock) SetEchoContext(c echo.Context) { // ctx := &api.ContextMock{Context: &api.Context{}} // ctx.SetMkdirAll(mkdirAll) func (ctx *ContextMock) SetMkdirAll(mkdirAll gotenberg.MkdirAll) { - ctx.Context.mkdirAll = mkdirAll + ctx.mkdirAll = mkdirAll } // SetPathRename sets the [gotenberg.PathRename]. @@ -99,7 +99,7 @@ func (ctx *ContextMock) SetMkdirAll(mkdirAll gotenberg.MkdirAll) { // ctx := &api.ContextMock{Context: &api.Context{}} // ctx.setPathRename(rename) func (ctx *ContextMock) SetPathRename(rename gotenberg.PathRename) { - ctx.Context.pathRename = rename + ctx.pathRename = rename } // RouterMock is a mock for the [Router] interface. From c737579b7f45c26fabed2be2af29f9da9dedd5a5 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 26 Mar 2025 20:11:16 +0100 Subject: [PATCH 084/254] ci(lint): fix golangci-lint action version --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 4f527b85f..a35d0f970 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -29,7 +29,7 @@ jobs: go-version-file: go.mod - name: Run linters - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: version: v2.0.2 From 368c4fa1a7534a541d8854fd87add4d9b955823b Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 28 Mar 2025 12:17:18 +0100 Subject: [PATCH 085/254] chore(logging): better description for --log-enable-gcp-fields flag [skip ci] --- pkg/modules/logging/logging.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/modules/logging/logging.go b/pkg/modules/logging/logging.go index 0c4fd7579..26600122e 100644 --- a/pkg/modules/logging/logging.go +++ b/pkg/modules/logging/logging.go @@ -49,7 +49,7 @@ func (log *Logging) Descriptor() gotenberg.ModuleDescriptor { fs.String("log-level", infoLoggingLevel, fmt.Sprintf("Choose the level of logging detail. Options include %s, %s, %s, or %s", errorLoggingLevel, warnLoggingLevel, infoLoggingLevel, debugLoggingLevel)) fs.String("log-format", autoLoggingFormat, fmt.Sprintf("Specify the format of logging. Options include %s, %s, or %s", autoLoggingFormat, jsonLoggingFormat, textLoggingFormat)) fs.String("log-fields-prefix", "", "Prepend a specified prefix to each field in the logs") - fs.Bool("log-enable-gcp-fields", false, "Enable GCP fields - namely: time, message, severity") + fs.Bool("log-enable-gcp-fields", false, "Enable Google Cloud Platform fields - namely: time, message, severity") // Deprecated flags. fs.Bool("log-enable-gcp-severity", false, "Enable Google Cloud Platform severity mapping") From 674f1de1833a9037471ad9d63eb5740bce873da4 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 28 Mar 2025 12:20:27 +0100 Subject: [PATCH 086/254] chore(deps): update Go dependencies --- Makefile | 1 + go.mod | 16 +++++++--------- go.sum | 30 ++++++++++++------------------ 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index 8b2aa4024..91bf0e306 100644 --- a/Makefile +++ b/Makefile @@ -183,6 +183,7 @@ lint-todo: ## Find TODOs in Golang codebase .PHONY: fmt fmt: ## Format Golang codebase and "optimize" the dependencies golangci-lint fmt + go mod tidy .PHONY: prettify prettify: ## Format non-Golang codebase diff --git a/go.mod b/go.mod index e9157c5e9..ae04d13ee 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.0 + golang.org/x/net v0.38.0 golang.org/x/sync v0.12.0 golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 @@ -35,16 +35,15 @@ require ( require ( github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.0.2+incompatible + github.com/docker/docker v28.0.4+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.0 github.com/shirou/gopsutil/v4 v4.25.2 - github.com/testcontainers/testcontainers-go v0.35.0 + github.com/testcontainers/testcontainers-go v0.36.0 ) require ( dario.cat/mergo v1.0.1 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/STARRY-S/zip v0.2.2 // indirect @@ -103,10 +102,8 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.16.0 // indirect - github.com/shirou/gopsutil/v3 v3.24.5 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sorairolake/lzip-go v0.3.5 // indirect + github.com/sorairolake/lzip-go v0.3.7 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect @@ -117,13 +114,14 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/protobuf v1.36.5 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3b96234c0..fa8f288d1 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8= -github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= +github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -94,8 +94,8 @@ github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -270,18 +270,12 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= -github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk= github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= -github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= +github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= +github.com/sorairolake/lzip-go v0.3.7/go.mod h1:THOHr0FlNVCw2eOIEE9shFJAG1QxQg/pf2XUPAmNIqg= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -300,8 +294,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= -github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= +github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00= +github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= @@ -407,8 +401,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -538,8 +532,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From 45f04ae8d442b44f8fe5e7fe7d533ca4598e6de2 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 2 Apr 2025 14:07:15 +0200 Subject: [PATCH 087/254] fix(chromium): printBackground now works as expected and PDF are not tagged anymore - fixes #1154 and fixes #1058 --- go.mod | 6 +++--- go.sum | 12 ++++++------ pkg/modules/chromium/browser.go | 4 ++-- pkg/modules/chromium/tasks.go | 26 ++++++++++++++------------ 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index ae04d13ee..884a613e1 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 - github.com/chromedp/chromedp v0.13.3 + github.com/chromedp/cdproto v0.0.0-20250401205909-91afd104e2b8 + github.com/chromedp/chromedp v0.13.5 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 @@ -38,7 +38,7 @@ require ( github.com/docker/docker v28.0.4+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.0 - github.com/shirou/gopsutil/v4 v4.25.2 + github.com/shirou/gopsutil/v4 v4.25.3 github.com/testcontainers/testcontainers-go v0.36.0 ) diff --git a/go.sum b/go.sum index fa8f288d1..8269a80eb 100644 --- a/go.sum +++ b/go.sum @@ -48,10 +48,10 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 h1:AqW2bDQf67Zbq6Tpop/+yJSIknxhiQecO2B8jNYTAPs= -github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= -github.com/chromedp/chromedp v0.13.3 h1:c6nTn97XQBykzcXiGYL5LLebw3h3CEyrCihm4HquYh0= -github.com/chromedp/chromedp v0.13.3/go.mod h1:khsDP9OP20GrowpJfZ7N05iGCwcAYxk7qf9AZBzR3Qw= +github.com/chromedp/cdproto v0.0.0-20250401205909-91afd104e2b8 h1:k5Q26sl2euPK4oyhfIC+84SYKrr+JCRtjYFlDS71s1c= +github.com/chromedp/cdproto v0.0.0-20250401205909-91afd104e2b8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/chromedp v0.13.5 h1:FrgR5oAJsKtKOQMeusSiF67Rvnw/cFV3yRC690GfbTM= +github.com/chromedp/chromedp v0.13.5/go.mod h1:pzEMe7rBROFW34SWhxqUr1AI7bNdkZ4WCgdEQfD39n0= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -270,8 +270,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk= -github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA= +github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= +github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index d8a2d724f..24b55827d 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -271,7 +271,7 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outp userAgentOverride(logger, options.UserAgent), navigateActionFunc(logger, url, options.SkipNetworkIdleEvent), hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground), - forceExactColorsActionFunc(), + forceExactColorsActionFunc(logger, options.PrintBackground), emulateMediaTypeActionFunc(logger, options.EmulatedMediaType), waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression), @@ -294,7 +294,7 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, ur userAgentOverride(logger, options.UserAgent), navigateActionFunc(logger, url, options.SkipNetworkIdleEvent), hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true), - forceExactColorsActionFunc(), + forceExactColorsActionFunc(logger, true), emulateMediaTypeActionFunc(logger, options.EmulatedMediaType), waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression), diff --git a/pkg/modules/chromium/tasks.go b/pkg/modules/chromium/tasks.go index 5f19a2884..1e2eb7175 100644 --- a/pkg/modules/chromium/tasks.go +++ b/pkg/modules/chromium/tasks.go @@ -49,8 +49,6 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti WithPageRanges(pageRanges). WithPreferCSSPageSize(options.PreferCssPageSize). WithGenerateDocumentOutline(options.GenerateDocumentOutline). - // Does not seem to work. - // See https://github.com/gotenberg/gotenberg/issues/831. WithGenerateTaggedPDF(false) hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate || @@ -392,26 +390,30 @@ func hideDefaultWhiteBackgroundActionFunc(logger *zap.Logger, omitBackground, pr } } -func forceExactColorsActionFunc() chromedp.ActionFunc { +func forceExactColorsActionFunc(logger *zap.Logger, printBackground bool) chromedp.ActionFunc { return func(ctx context.Context) error { - // See: - // https://github.com/gotenberg/gotenberg/issues/354 - // https://github.com/puppeteer/puppeteer/issues/2685 - // https://github.com/chromedp/chromedp/issues/520 - script := ` -(() => { - const css = 'html { -webkit-print-color-adjust: exact !important; }'; + css := "html { -webkit-print-color-adjust: exact !important; }" + if !printBackground { + // The -webkit-print-color-adjust: exact CSS property forces the + // print of the background, whatever the printToPDF args. + // See https://github.com/gotenberg/gotenberg/issues/1154. + additionalCss := "html, body { background: none !important; }" + logger.Debug(fmt.Sprintf("inject %s as printBackground is %t", additionalCss, printBackground)) + css += additionalCss + } + script := fmt.Sprintf(` +(() => { + const css = '%s'; const style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); })(); -` +`, css) evaluate := chromedp.Evaluate(script, nil) err := evaluate.Do(ctx) - if err == nil { return nil } From bf4607d3684bfba654df38565743217a7f0dce65 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 2 Apr 2025 15:04:52 +0200 Subject: [PATCH 088/254] chore(.gitignore): remove coverage entries [skip ci] --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 219dc66fd..6ae82b8ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -/coverage.html -/coverage.txt .idea .vscode .DS_Store From 10588efe259dd7492d92588bf21e0fdd7f82cb85 Mon Sep 17 00:00:00 2001 From: stephentgrammer <36522622+stephentgrammer@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:04:14 -0700 Subject: [PATCH 089/254] fix(webhook): content-disposition header (#1164) * fix(webhook): pass content-disposition header through and fix spelling of attachment on default * code review Co-authored-by: Julien Neuhart * formatting --------- Co-authored-by: Julien Neuhart --- pkg/modules/webhook/middleware.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/modules/webhook/middleware.go b/pkg/modules/webhook/middleware.go index 63150cce9..aa6ac1258 100644 --- a/pkg/modules/webhook/middleware.go +++ b/pkg/modules/webhook/middleware.go @@ -239,10 +239,16 @@ func webhookMiddleware(w *Webhook) api.Middleware { } headers := map[string]string{ - echo.HeaderContentDisposition: fmt.Sprintf("attachement; filename=%q", ctx.OutputFilename(outputPath)), - echo.HeaderContentType: http.DetectContentType(fileHeader), - echo.HeaderContentLength: strconv.FormatInt(fileStat.Size(), 10), - traceHeader: trace, + echo.HeaderContentType: http.DetectContentType(fileHeader), + echo.HeaderContentLength: strconv.FormatInt(fileStat.Size(), 10), + traceHeader: trace, + } + + // Allow for custom Content-Disposition header. + // See https://github.com/gotenberg/gotenberg/issues/1165. + _, ok := extraHttpHeaders[echo.HeaderContentDisposition] + if !ok { + headers[echo.HeaderContentDisposition] = fmt.Sprintf("attachment; filename=%q", ctx.OutputFilename(outputPath)) } // Send the output file to the webhook. From 7fe0e05050cba8960c2ff5fdd136e8f854020b3a Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 8 Apr 2025 18:51:51 +0200 Subject: [PATCH 090/254] test(webhook): add integration testing for the webhook feature - testing the custom Content-Disposition header from #1165 --- test/integration/features/webhook.feature | 33 +++++++++++++++++++++++ test/integration/scenario/server.go | 12 ++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 test/integration/features/webhook.feature diff --git a/test/integration/features/webhook.feature b/test/integration/features/webhook.feature new file mode 100644 index 000000000..e77c1f81a --- /dev/null +++ b/test/integration/features/webhook.feature @@ -0,0 +1,33 @@ +# TODO: +# 1. Other HTTP Methods +# 2. Errors + +Feature: Webhook + + Scenario: Default + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the webhook request + + Scenario: Extra HTTP Headers + Given I have a default Gotenberg container + Given I have a webhook server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + | Gotenberg-Webhook-Extra-Http-Headers | {"X-Foo":"bar","Content-Disposition":"inline"} | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/pdf" + Then the webhook request header "X-Foo" should be "bar" + # https://github.com/gotenberg/gotenberg/issues/1165 + Then the webhook request header "Content-Disposition" should be "inline" + Then there should be 1 PDF(s) in the webhook request diff --git a/test/integration/scenario/server.go b/test/integration/scenario/server.go index ddf2f0a14..d49121133 100644 --- a/test/integration/scenario/server.go +++ b/test/integration/scenario/server.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/cucumber/godog" + "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/mholt/archives" ) @@ -62,7 +63,16 @@ func newServer(ctx context.Context, workdir string) (*server, error) { filename, ok := params["filename"] if !ok { - return webhookErr(errors.New("no filename in Content-Disposition header")) + filename = uuid.NewString() + contentType := s.req.Header.Get("Content-Type") + switch contentType { + case "application/zip": + filename = fmt.Sprintf("%s.zip", filename) + case "application/pdf": + filename = fmt.Sprintf("%s.pdf", filename) + default: + return webhookErr(errors.New("no filename in Content-Disposition header")) + } } dirPath := fmt.Sprintf("%s/%s", workdir, s.req.Header.Get("Gotenberg-Trace")) From 1bfe86738f6aebce55d3d6ff92f2bfbbd1337ab3 Mon Sep 17 00:00:00 2001 From: Hector Zarate Date: Thu, 10 Apr 2025 13:30:24 +0200 Subject: [PATCH 091/254] feat(debug): only build debug data when flag is true (#1167) * only build debug info when log level is debug * introduce new flag 'gotenberg-build-debug-data' * add gotenberg-build-debug-data flag to Makefile * add new integration test for gotenberg-build-debug-data flag * update GET /debug (Enabled) test with gotenberg-build-debug-data flag * reorder GOTENBERG_BUILD_DEBUG_DATA in Makefile * reorder Build Debug Data Disabled test scenario --------- Co-authored-by: Hector Zarate --- Makefile | 2 ++ cmd/gotenberg.go | 7 +++++-- test/integration/features/debug.feature | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 91bf0e306..c094892d6 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ build: ## Build the Gotenberg's Docker image -f $(DOCKERFILE) $(DOCKER_BUILD_CONTEXT) GOTENBERG_GRACEFUL_SHUTDOWN_DURATION=30s +GOTENBERG_BUILD_DEBUG_DATA=true API_PORT=3000 API_PORT_FROM_ENV= API_BIND_IP= @@ -91,6 +92,7 @@ run: ## Start a Gotenberg container $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION) \ gotenberg \ --gotenberg-graceful-shutdown-duration=$(GOTENBERG_GRACEFUL_SHUTDOWN_DURATION) \ + --gotenberg-build-debug-data=$(GOTENBERG_BUILD_DEBUG_DATA) \ --api-port=$(API_PORT) \ --api-port-from-env=$(API_PORT_FROM_ENV) \ --api-bind-ip=$(API_BIND_IP) \ diff --git a/cmd/gotenberg.go b/cmd/gotenberg.go index b687974fe..5e456a5f8 100644 --- a/cmd/gotenberg.go +++ b/cmd/gotenberg.go @@ -41,6 +41,7 @@ func Run() { // Create the root FlagSet and adds the modules flags to it. fs := flag.NewFlagSet("gotenberg", flag.ExitOnError) fs.Duration("gotenberg-graceful-shutdown-duration", time.Duration(30)*time.Second, "Set the graceful shutdown duration") + fs.Bool("gotenberg-build-debug-data", true, "Set if build data is needed") descriptors := gotenberg.GetModuleDescriptors() var modsInfo string @@ -137,8 +138,10 @@ func Run() { }(l.(gotenberg.SystemLogger)) } - // Build the debug data. - gotenberg.BuildDebug(ctx) + if parsedFlags.MustBool("gotenberg-build-debug-data") { + // Build the debug data. + gotenberg.BuildDebug(ctx) + } quit := make(chan os.Signal, 1) diff --git a/test/integration/features/debug.feature b/test/integration/features/debug.feature index a68e5fe73..78a9cdba3 100644 --- a/test/integration/features/debug.feature +++ b/test/integration/features/debug.feature @@ -5,6 +5,23 @@ Feature: /debug When I make a "GET" request to Gotenberg at the "/debug" endpoint Then the response status code should be 404 + Scenario: GET /debug (Build Debug Data Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | GOTENBERG_BUILD_DEBUG_DATA | false | + When I make a "GET" request to Gotenberg at the "/debug" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "version": "", + "architecture": "", + "modules": null, + "modules_additional_data": null, + "flags": null + } + """ + Scenario: GET /debug (Enabled) Given I have a Gotenberg container with the following environment variable(s): | API_ENABLE_DEBUG_ROUTE | true | @@ -86,6 +103,7 @@ Feature: /debug "chromium-proxy-server": "", "chromium-restart-after": "10", "chromium-start-timeout": "20s", + "gotenberg-build-debug-data": "true", "gotenberg-graceful-shutdown-duration": "30s", "libreoffice-auto-start": "false", "libreoffice-disable-routes": "false", From 02c4343dfdef5f2dbfc9688e638d89957fa3cdc0 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 10 Apr 2025 16:28:15 +0200 Subject: [PATCH 092/254] fix(test): missing env var for debug --- test/integration/features/debug.feature | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/test/integration/features/debug.feature b/test/integration/features/debug.feature index 78a9cdba3..8bc0de345 100644 --- a/test/integration/features/debug.feature +++ b/test/integration/features/debug.feature @@ -5,23 +5,6 @@ Feature: /debug When I make a "GET" request to Gotenberg at the "/debug" endpoint Then the response status code should be 404 - Scenario: GET /debug (Build Debug Data Disabled) - Given I have a Gotenberg container with the following environment variable(s): - | GOTENBERG_BUILD_DEBUG_DATA | false | - When I make a "GET" request to Gotenberg at the "/debug" endpoint - Then the response status code should be 200 - Then the response header "Content-Type" should be "application/json" - Then the response body should match JSON: - """ - { - "version": "", - "architecture": "", - "modules": null, - "modules_additional_data": null, - "flags": null - } - """ - Scenario: GET /debug (Enabled) Given I have a Gotenberg container with the following environment variable(s): | API_ENABLE_DEBUG_ROUTE | true | @@ -138,6 +121,24 @@ Feature: /debug } """ + Scenario: GET /debug (No Debug Data) + Given I have a Gotenberg container with the following environment variable(s): + | GOTENBERG_BUILD_DEBUG_DATA | false | + | API_ENABLE_DEBUG_ROUTE | true | + When I make a "GET" request to Gotenberg at the "/debug" endpoint + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "version": "", + "architecture": "", + "modules": null, + "modules_additional_data": null, + "flags": null + } + """ + Scenario: GET /debug (Gotenberg Trace) Given I have a Gotenberg container with the following environment variable(s): | API_ENABLE_DEBUG_ROUTE | true | From ab8144477357eb4b2bbd5e53eb2dcefa6f7ce1a9 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 12 Apr 2025 17:24:23 +0200 Subject: [PATCH 093/254] fix(Makefile): add double-quote to new flag --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c094892d6..52b8847d6 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ run: ## Start a Gotenberg container $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION) \ gotenberg \ --gotenberg-graceful-shutdown-duration=$(GOTENBERG_GRACEFUL_SHUTDOWN_DURATION) \ - --gotenberg-build-debug-data=$(GOTENBERG_BUILD_DEBUG_DATA) \ + --gotenberg-build-debug-data="$(GOTENBERG_BUILD_DEBUG_DATA)" \ --api-port=$(API_PORT) \ --api-port-from-env=$(API_PORT_FROM_ENV) \ --api-bind-ip=$(API_BIND_IP) \ From a7dfa21cbf87b7d007dcde285e8a4771b81c393d Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 12 Apr 2025 17:25:39 +0200 Subject: [PATCH 094/254] fix(pdfengines): correct merge order - fixes #1168 --- pkg/gotenberg/sort.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/gotenberg/sort.go b/pkg/gotenberg/sort.go index 0a64ec2fe..21922f521 100644 --- a/pkg/gotenberg/sort.go +++ b/pkg/gotenberg/sort.go @@ -1,6 +1,7 @@ package gotenberg import ( + "path/filepath" "regexp" "sort" "strconv" @@ -54,6 +55,9 @@ func (s AlphanumericSort) Less(i, j int) bool { // If that fails, it then attempts a trailing numeric pattern. // If no number is found, it returns -1 and the original string. func extractNumber(str string) (int, string) { + // See https://github.com/gotenberg/gotenberg/issues/1168. + str = filepath.Base(str) + // Check for a numeric prefix. if matches := prefixRegexp.FindStringSubmatch(str); len(matches) > 2 { if num, err := strconv.Atoi(matches[1]); err == nil { From 7ca1d118a1e167fea8f4cfe1cf51d696b190bfd3 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 12 Apr 2025 17:37:15 +0200 Subject: [PATCH 095/254] chore(deps): update Go dependencies --- go.mod | 29 +++++++++++++++-------------- go.sum | 58 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 884a613e1..8b4f356a6 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250401205909-91afd104e2b8 - github.com/chromedp/chromedp v0.13.5 + github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b + github.com/chromedp/chromedp v0.13.6 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 @@ -18,18 +18,18 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/microcosm-cc/bluemonday v1.0.27 github.com/pierrec/lz4/v4 v4.1.22 // indirect - github.com/prometheus/client_golang v1.21.1 + github.com/prometheus/client_golang v1.22.0 github.com/russross/blackfriday/v2 v2.1.0 github.com/spf13/pflag v1.0.6 github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.38.0 - golang.org/x/sync v0.12.0 - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 - golang.org/x/text v0.23.0 + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 + golang.org/x/sync v0.13.0 + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 + golang.org/x/text v0.24.0 ) require ( @@ -37,7 +37,7 @@ require ( github.com/dlclark/regexp2 v1.11.5 github.com/docker/docker v28.0.4+incompatible github.com/docker/go-connections v0.5.0 - github.com/mholt/archives v0.1.0 + github.com/mholt/archives v0.1.1 github.com/shirou/gopsutil/v4 v4.25.3 github.com/testcontainers/testcontainers-go v0.36.0 ) @@ -46,7 +46,7 @@ require ( dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/STARRY-S/zip v0.2.2 // indirect + github.com/STARRY-S/zip v0.2.3 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bodgit/plumbing v1.3.0 // indirect @@ -83,12 +83,13 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect - github.com/magiconair/properties v1.8.9 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect + github.com/minio/minlz v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect - github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -99,7 +100,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.16.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index 8269a80eb..c9476602b 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/STARRY-S/zip v0.2.2 h1:8QeCbIi1Z9U5MgoDARJR1ClbBo9RD46SmVy+dl0woCk= -github.com/STARRY-S/zip v0.2.2/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= +github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= +github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/alexliesenfeld/health v0.8.0 h1:lCV0i+ZJPTbqP7LfKG7p3qZBl5VhelwUFCIVWl77fgk= github.com/alexliesenfeld/health v0.8.0/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -48,10 +48,10 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250401205909-91afd104e2b8 h1:k5Q26sl2euPK4oyhfIC+84SYKrr+JCRtjYFlDS71s1c= -github.com/chromedp/cdproto v0.0.0-20250401205909-91afd104e2b8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= -github.com/chromedp/chromedp v0.13.5 h1:FrgR5oAJsKtKOQMeusSiF67Rvnw/cFV3yRC690GfbTM= -github.com/chromedp/chromedp v0.13.5/go.mod h1:pzEMe7rBROFW34SWhxqUr1AI7bNdkZ4WCgdEQfD39n0= +github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b h1:jJmiCljLNTaq/O1ju9Bzz2MPpFlmiTn0F7LwCoeDZVw= +github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk= +github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -213,24 +213,26 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q= -github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I= +github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY= +github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= +github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= @@ -255,11 +257,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= @@ -354,8 +356,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -401,8 +403,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -416,8 +418,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -442,13 +444,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -456,8 +458,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= From 9701f88ae398dbf8d24036f6576f6098e7473151 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 12 Apr 2025 19:40:16 +0200 Subject: [PATCH 096/254] fix(pdfcpu): use custom sort to retrieve the splitted PDFs --- pkg/modules/pdfcpu/pdfcpu.go | 2 +- pkg/modules/pdfcpu/sort.go | 68 +++++++++++++++++++++++++++++++++ pkg/modules/pdfcpu/sort_test.go | 29 ++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 pkg/modules/pdfcpu/sort.go create mode 100644 pkg/modules/pdfcpu/sort_test.go diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index 609e955d1..50b7acbe3 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -146,7 +146,7 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenb return nil, fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err) } - sort.Sort(gotenberg.AlphanumericSort(outputPaths)) + sort.Sort(digitSuffixSort(outputPaths)) return outputPaths, nil } diff --git a/pkg/modules/pdfcpu/sort.go b/pkg/modules/pdfcpu/sort.go new file mode 100644 index 000000000..8ee83487d --- /dev/null +++ b/pkg/modules/pdfcpu/sort.go @@ -0,0 +1,68 @@ +package pdfcpu + +import ( + "path/filepath" + "regexp" + "sort" + "strconv" +) + +type digitSuffixSort []string + +func (s digitSuffixSort) Len() int { + return len(s) +} + +func (s digitSuffixSort) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s digitSuffixSort) Less(i, j int) bool { + numI, restI := extractNumber(s[i]) + numJ, restJ := extractNumber(s[j]) + + // If both strings contain a number, compare them numerically. + if numI != -1 && numJ != -1 { + if numI != numJ { + return numI < numJ + } + // If the numbers are equal, compare the "rest" strings. + return restI < restJ + } + + // If one contains a number and the other doesn't, the one with the number + // comes first. + if numI != -1 { + return true + } + if numJ != -1 { + return false + } + + // Neither has a number; fall back to lexicographical order. + return s[i] < s[j] +} + +func extractNumber(str string) (int, string) { + str = filepath.Base(str) + + // Check for a number immediately before an extension. + if matches := extensionSuffixRegexp.FindStringSubmatch(str); len(matches) > 3 { + if num, err := strconv.Atoi(matches[2]); err == nil { + // Remove the numeric block but keep the extension. + return num, matches[1] + matches[3] + } + } + + // No numeric portion found. + return -1, str +} + +// Regular expressions used by extractNumber. +var ( + // Matches a numeric block immediately before a file extension. + extensionSuffixRegexp = regexp.MustCompile(`^(.*?)(\d+)(\.[^.]+)$`) +) + +// Interface guard. +var _ sort.Interface = (*digitSuffixSort)(nil) diff --git a/pkg/modules/pdfcpu/sort_test.go b/pkg/modules/pdfcpu/sort_test.go new file mode 100644 index 000000000..e7d94a789 --- /dev/null +++ b/pkg/modules/pdfcpu/sort_test.go @@ -0,0 +1,29 @@ +package pdfcpu + +import ( + "reflect" + "sort" + "testing" +) + +func TestDigitSuffixSort(t *testing.T) { + for _, tc := range []struct { + scenario string + values []string + expectedSort []string + }{ + { + scenario: "UUIDs with digit suffixes", + values: []string{"2521a33d-1fb4-4279-80fe-8a945285b8f4_12.pdf", "2521a33d-1fb4-4279-80fe-8a945285b8f4_1.pdf", "2521a33d-1fb4-4279-80fe-8a945285b8f4_10.pdf", "2521a33d-1fb4-4279-80fe-8a945285b8f4_3.pdf"}, + expectedSort: []string{"2521a33d-1fb4-4279-80fe-8a945285b8f4_1.pdf", "2521a33d-1fb4-4279-80fe-8a945285b8f4_3.pdf", "2521a33d-1fb4-4279-80fe-8a945285b8f4_10.pdf", "2521a33d-1fb4-4279-80fe-8a945285b8f4_12.pdf"}, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + sort.Sort(digitSuffixSort(tc.values)) + + if !reflect.DeepEqual(tc.values, tc.expectedSort) { + t.Fatalf("expected %+v but got: %+v", tc.expectedSort, tc.values) + } + }) + } +} From 3cbf772acf273ad6123e42edb1c72800937e55a6 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 17 Apr 2025 11:15:52 +0200 Subject: [PATCH 097/254] docs(godoc): fix a bunch of typos --- README.md | 2 +- SECURITY.md | 2 +- pkg/gotenberg/cmd.go | 4 +-- pkg/gotenberg/context.go | 4 +-- pkg/gotenberg/fs.go | 2 +- pkg/gotenberg/gc.go | 2 +- pkg/gotenberg/gc_test.go | 24 +++++++------- pkg/gotenberg/logging.go | 10 +++--- pkg/gotenberg/pdfengine.go | 4 +-- pkg/modules/api/api.go | 18 +++++----- pkg/modules/api/doc.go | 2 +- pkg/modules/api/formdata.go | 18 +++++----- pkg/modules/api/middlewares.go | 8 ++--- pkg/modules/api/mocks.go | 4 +-- pkg/modules/chromium/browser.go | 6 ++-- pkg/modules/chromium/chromium.go | 28 ++++++++-------- pkg/modules/chromium/events.go | 8 ++--- pkg/modules/chromium/routes.go | 10 +++--- pkg/modules/chromium/stream.go | 2 +- pkg/modules/exiftool/exiftool.go | 6 ++-- pkg/modules/libreoffice/api/api.go | 33 ++++++++++--------- pkg/modules/libreoffice/api/libreoffice.go | 8 ++--- pkg/modules/libreoffice/libreoffice.go | 2 +- .../libreoffice/pdfengine/pdfengine.go | 2 +- pkg/modules/libreoffice/routes.go | 2 +- pkg/modules/logging/logging.go | 2 +- pkg/modules/pdfengines/multi.go | 2 +- pkg/modules/pdfengines/pdfengines.go | 2 +- pkg/modules/pdftk/pdftk.go | 2 +- pkg/modules/prometheus/prometheus.go | 4 +-- pkg/modules/qpdf/qpdf.go | 2 +- pkg/modules/webhook/client.go | 14 ++++---- pkg/modules/webhook/middleware.go | 4 +-- pkg/modules/webhook/webhook.go | 2 +- 34 files changed, 123 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 8c70fda2b..ff4f69a1f 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,4 @@ Head to the [documentation](https://gotenberg.dev/docs/getting-started/introduct

-Sponsorships help maintaining and improving Gotenberg - [become a sponsor](https://github.com/sponsors/gulien) ❤️ +Sponsorships help maintain and improve Gotenberg - [become a sponsor](https://github.com/sponsors/gulien) ❤️ diff --git a/SECURITY.md b/SECURITY.md index d624b8491..f281db277 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -Please ensure to keep your environment up-to-date and use only the latest version of Gotenberg. +Please ensure to keep your environment up to date and use only the latest version of Gotenberg. Security updates and patches will be applied only to the most recent version. ## Reporting a Vulnerability diff --git a/pkg/gotenberg/cmd.go b/pkg/gotenberg/cmd.go index afbb0f713..83dd3ba22 100644 --- a/pkg/gotenberg/cmd.go +++ b/pkg/gotenberg/cmd.go @@ -74,7 +74,7 @@ func (cmd *Cmd) Start() error { } // Wait waits for the command to complete. It should be called when using the -// Start method, so that the command does not leak zombies. +// Start method so that the command does not leak zombies. func (cmd *Cmd) Wait() error { err := cmd.process.Wait() if err != nil { @@ -84,7 +84,7 @@ func (cmd *Cmd) Wait() error { return nil } -// Exec executes the command and wait for its completion or until the context +// Exec executes the command and waits for its completion or until the context // is done. In any case, it kills the unix process and all its children. func (cmd *Cmd) Exec() (int, error) { if cmd.ctx == nil { diff --git a/pkg/gotenberg/context.go b/pkg/gotenberg/context.go index 48ab7f5d0..507f30d4e 100644 --- a/pkg/gotenberg/context.go +++ b/pkg/gotenberg/context.go @@ -5,7 +5,7 @@ import ( "reflect" ) -// Context is a struct which helps to initialize modules. When provisioning, a +// Context is a struct that helps to initialize modules. When provisioning, a // module may use the context to get other modules that it needs internally. type Context struct { flags ParsedFlags @@ -58,7 +58,7 @@ func (ctx *Context) Module(kind interface{}) (interface{}, error) { return mods[0], nil } -// Modules returns the list of modules which satisfies the requested interface. +// Modules return the list of modules which satisfies the requested interface. // // func (m *YourModule) Provision(ctx *gotenberg.Context) error { // mods, _ := ctx.Modules(new(ModuleInterface)) diff --git a/pkg/gotenberg/fs.go b/pkg/gotenberg/fs.go index ec58c8299..5ddc997f9 100644 --- a/pkg/gotenberg/fs.go +++ b/pkg/gotenberg/fs.go @@ -23,7 +23,7 @@ func (o *OsMkdirAll) MkdirAll(path string, perm os.FileMode) error { return os.M // PathRename defines the method signature for renaming files. Implement this // interface if you don't want to rely on [os.Rename], notably for testing -// purpose. +// purposes. type PathRename interface { // Rename uses the same signature as [os.Rename]. Rename(oldpath, newpath string) error diff --git a/pkg/gotenberg/gc.go b/pkg/gotenberg/gc.go index 2f53cc385..169dd508e 100644 --- a/pkg/gotenberg/gc.go +++ b/pkg/gotenberg/gc.go @@ -11,7 +11,7 @@ import ( ) // GarbageCollect scans the root path and deletes files or directories with -// names containing specific substrings and before a given experiation time. +// names containing specific substrings and before a given expiration time. func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string, expirationTime time.Time) error { logger = logger.Named("gc") diff --git a/pkg/gotenberg/gc_test.go b/pkg/gotenberg/gc_test.go index 2124b0dda..833a0a6f6 100644 --- a/pkg/gotenberg/gc_test.go +++ b/pkg/gotenberg/gc_test.go @@ -28,29 +28,29 @@ func TestGarbageCollect(t *testing.T) { { scenario: "remove include substrings", rootPath: func() string { - path := fmt.Sprintf("%s/a_directory", os.TempDir()) + p := fmt.Sprintf("%s/a_directory", os.TempDir()) - err := os.MkdirAll(path, 0o755) + err := os.MkdirAll(p, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } - err = os.WriteFile(fmt.Sprintf("%s/a_foo_file", path), []byte{1}, 0o755) + err = os.WriteFile(fmt.Sprintf("%s/a_foo_file", p), []byte{1}, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } - err = os.WriteFile(fmt.Sprintf("%s/a_bar_file", path), []byte{1}, 0o755) + err = os.WriteFile(fmt.Sprintf("%s/a_bar_file", p), []byte{1}, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } - err = os.WriteFile(fmt.Sprintf("%s/a_baz_file", path), []byte{1}, 0o755) + err = os.WriteFile(fmt.Sprintf("%s/a_baz_file", p), []byte{1}, 0o755) if err != nil { t.Fatalf("expected no error but got: %v", err) } - return path + return p }(), includeSubstr: []string{"foo", path.Join(os.TempDir(), "/a_directory/a_bar_file")}, expectError: false, @@ -81,18 +81,18 @@ func TestGarbageCollect(t *testing.T) { } for _, name := range tc.expectNotExists { - path := fmt.Sprintf("%s/%s", tc.rootPath, name) - _, err = os.Stat(path) + p := fmt.Sprintf("%s/%s", tc.rootPath, name) + _, err = os.Stat(p) if !os.IsNotExist(err) { - t.Errorf("expected '%s' not to exist but it does: %v", path, err) + t.Errorf("expected '%s' not to exist but it does: %v", p, err) } } for _, name := range tc.expectExists { - path := fmt.Sprintf("%s/%s", tc.rootPath, name) - _, err = os.Stat(path) + p := fmt.Sprintf("%s/%s", tc.rootPath, name) + _, err = os.Stat(p) if os.IsNotExist(err) { - t.Errorf("expected '%s' to exist but it does not: %v", path, err) + t.Errorf("expected '%s' to exist but it does not: %v", p, err) } } }) diff --git a/pkg/gotenberg/logging.go b/pkg/gotenberg/logging.go index 1fb116453..ea0bdf425 100644 --- a/pkg/gotenberg/logging.go +++ b/pkg/gotenberg/logging.go @@ -18,7 +18,7 @@ type LoggerProvider interface { Logger(mod Module) (*zap.Logger, error) } -// LeveledLogger is wrapper around a [zap.Logger] so that it may be used by a +// LeveledLogger is a wrapper around a [zap.Logger] so that it may be used by a // [retryablehttp.Client]. type LeveledLogger struct { logger *zap.Logger @@ -31,22 +31,22 @@ func NewLeveledLogger(logger *zap.Logger) *LeveledLogger { } } -// Error logs a message at error level using the wrapped zap.Logger. +// Error logs a message at the error level using the wrapped zap.Logger. func (leveled LeveledLogger) Error(msg string, keysAndValues ...interface{}) { leveled.logger.Error(fmt.Sprintf("%s: %+v", msg, keysAndValues)) } -// Warn logs a message at warning level using the wrapped zap.Logger. +// Warn logs a message at the warning level using the wrapped zap.Logger. func (leveled LeveledLogger) Warn(msg string, keysAndValues ...interface{}) { leveled.logger.Warn(fmt.Sprintf("%s: %+v", msg, keysAndValues)) } -// Info logs a message at info level using the wrapped zap.Logger. +// Info logs a message at the info level using the wrapped zap.Logger. func (leveled LeveledLogger) Info(msg string, keysAndValues ...interface{}) { leveled.logger.Info(fmt.Sprintf("%s: %+v", msg, keysAndValues)) } -// Debug logs a message at debug level using the wrapped zap.Logger. +// Debug logs a message at the debug level using the wrapped zap.Logger. func (leveled LeveledLogger) Debug(msg string, keysAndValues ...interface{}) { leveled.logger.Debug(fmt.Sprintf("%s: %+v", msg, keysAndValues)) } diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index 02c55a418..bb9d43ab4 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -13,7 +13,7 @@ var ( ErrPdfEngineMethodNotSupported = errors.New("method not supported") // ErrPdfSplitModeNotSupported is returned when the Split method of the - // PdfEngine interface does not sumport a requested PDF split mode. + // PdfEngine interface does not support a requested PDF split mode. ErrPdfSplitModeNotSupported = errors.New("split mode not supported") // ErrPdfFormatNotSupported is returned when the Convert method of the @@ -86,7 +86,7 @@ type PdfFormats struct { } // PdfEngine provides an interface for operations on PDFs. Implementations -// can utilize various tools like PDFtk, or implement functionality directly in +// can use various tools like PDFtk, or implement functionality directly in // Go. // //nolint:dupl diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index b31d0ebd4..7662fe384 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -26,7 +26,7 @@ func init() { gotenberg.MustRegisterModule(new(Api)) } -// Api is a module which provides an HTTP server. Other modules may add routes, +// Api is a module that provides an HTTP server. Other modules may add routes, // middlewares or health checks. type Api struct { port int @@ -60,7 +60,7 @@ type downloadFromConfig struct { disable bool } -// Router is a module interface which adds routes to the [Api]. +// Router is a module interface that adds routes to the [Api]. type Router interface { Routes() ([]Route, error) } @@ -83,17 +83,17 @@ type Route struct { // Optional. DisableLogging bool - // Handler is the function which handles the request. + // Handler is the function that handles the request. // Required. Handler echo.HandlerFunc } -// MiddlewareProvider is a module interface which adds middlewares to the [Api]. +// MiddlewareProvider is a module interface that adds middlewares to the [Api]. type MiddlewareProvider interface { Middlewares() ([]Middleware, error) } -// MiddlewareStack is a type which helps to determine in which stack the +// MiddlewareStack is a type that helps to determine in which stack the // middlewares provided by the [MiddlewareProvider] modules should be located. type MiddlewareStack uint32 @@ -103,7 +103,7 @@ const ( MultipartStack ) -// MiddlewarePriority is a type which helps to determine the execution order of +// MiddlewarePriority is a type that helps to determine the execution order of // middlewares provided by the [MiddlewareProvider] modules in a stack. type MiddlewarePriority uint32 @@ -115,7 +115,7 @@ const ( VeryHighPriority ) -// Middleware is a middleware which can be added to the [Api]'s middlewares +// Middleware is a middleware that can be added to the [Api]'s middlewares // chain. // // middleware := Middleware{ @@ -157,7 +157,7 @@ type Middleware struct { Handler echo.MiddlewareFunc } -// HealthChecker is a module interface which allows adding health checks to the +// HealthChecker is a module interface that allows adding health checks to the // API. // // See https://github.com/alexliesenfeld/health for more details. @@ -431,7 +431,7 @@ func (a *Api) Start() error { } } - // Check if the user wish to add logging entries related to the health + // Check if the user wishes to add logging entries related to the health // check route. if a.disableHealthCheckLogging { disableLoggingForPaths = append(disableLoggingForPaths, "health") diff --git a/pkg/modules/api/doc.go b/pkg/modules/api/doc.go index 3c255a454..8b9c119e3 100644 --- a/pkg/modules/api/doc.go +++ b/pkg/modules/api/doc.go @@ -1,3 +1,3 @@ -// Package api provides a module which is an HTTP server. Other modules may +// Package api provides a module, which is an HTTP server. Other modules may // add multipart/form-data routes, middlewares, and health checks. package api diff --git a/pkg/modules/api/formdata.go b/pkg/modules/api/formdata.go index 5483415df..fd6b66484 100644 --- a/pkg/modules/api/formdata.go +++ b/pkg/modules/api/formdata.go @@ -27,7 +27,7 @@ type FormData struct { } // Validate returns nil or an error related to the [FormData] values, with a -// [SentinelHttpError] (status code 400, errors' details as message) wrapped +// [SentinelHttpError] (status code 400, errors' details as a message) wrapped // inside. // // var foo string @@ -96,7 +96,7 @@ func (form *FormData) Int(key string, target *int, defaultValue int) *FormData { } // MandatoryInt binds a form field to an int variable. It populates an -// error if the value is not int, is empty, or the "key" does not exist. +// error if the value is not int, or is empty, or the "key" does not exist. // // var foo int // @@ -116,7 +116,7 @@ func (form *FormData) Float64(key string, target *float64, defaultValue float64) } // MandatoryFloat64 binds a form field to a float64 variable. It populates -// an error if the is not float64, is empty, or the "key" does not exist. +// an error if the value is not float64, is empty, or the "key" does not exist. // // var foo float64 // @@ -136,8 +136,8 @@ func (form *FormData) Duration(key string, target *time.Duration, defaultValue t } // MandatoryDuration binds a form field to a time.Duration variable. It -// populates an error if the value is not time.Duration, is empty, or the "key" -// does not exist. +// populates an error if the value is not time.Duration, or is empty, or the +// "key" does not exist. // // var foo time.Duration // @@ -146,7 +146,7 @@ func (form *FormData) MandatoryDuration(key string, target *time.Duration) *Form return form.mustMandatoryField(key, target) } -// Inches binds a form field to a float64 variable. It populates an error +// Inches bind a form field to a float64 variable. It populates an error // if the value cannot be computed back to inches. // // var foo float64 @@ -303,7 +303,7 @@ func (form *FormData) Path(filename string, target *string) *FormData { return form.path(filename, target) } -// MandatoryPath binds the absolute path ofa form data file to a string +// MandatoryPath binds the absolute path of a form data file to a string // variable. It populates an error if the file does not exist. // // var path string @@ -348,7 +348,7 @@ func (form *FormData) MandatoryContent(filename string, target *string) *FormDat return form.readFile(path, filename, target) } -// Paths binds the absolute paths of form data files, according to a list of +// Paths bind the absolute paths of form data files, according to a list of // file extensions, to a string slice variable. // // var paths []string @@ -379,7 +379,7 @@ func (form *FormData) MandatoryPaths(extensions []string, target *[]string) *For return form } -// paths binds the absolute paths of form data files, according to a list of +// paths bind the absolute paths of form data files, according to a list of // file extensions, to a string slice variable. func (form *FormData) paths(extensions []string, target *[]string) *FormData { for filename, path := range form.files { diff --git a/pkg/modules/api/middlewares.go b/pkg/modules/api/middlewares.go index f93545103..d1cb3c540 100644 --- a/pkg/modules/api/middlewares.go +++ b/pkg/modules/api/middlewares.go @@ -86,7 +86,7 @@ func httpErrorHandler() echo.HTTPErrorHandler { } // latencyMiddleware sets the start time in the [echo.Context] under -// "startTime". Its value will be used later to calculate a request latency. +// "startTime". Its value will be used later to calculate request latency. // // startTime := c.Get("startTime").(time.Time) func latencyMiddleware() echo.MiddlewareFunc { @@ -109,7 +109,7 @@ func latencyMiddleware() echo.MiddlewareFunc { // rootPath := c.Get("rootPath").(string) // healthURI := fmt.Sprintf("%s/health", rootPath) // -// // Skip the middleware if health check URI. +// // Skip the middleware if it's the health check URI. // if c.Request().RequestURI == healthURI { // // Call the next middleware in the chain. // return next(c) @@ -241,7 +241,7 @@ func basicAuthMiddleware(username, password string) echo.MiddlewareFunc { }) } -// contextMiddleware, a middleware for "multipart/form-data" requests, sets the +// contextMiddleware, middleware for "multipart/form-data" requests, sets the // [Context] and related context.CancelFunc in the [echo.Context] under // "context" and "cancel". If the process is synchronous, it also handles the // result of a "multipart/form-data" request. @@ -256,7 +256,7 @@ func contextMiddleware(fs *gotenberg.FileSystem, timeout time.Duration, bodyLimi trace := c.Get("trace").(string) // We create a context with a timeout so that underlying processes are - // able to stop early and handle correctly a timeout scenario. + // able to stop early and correctly handle a timeout scenario. ctx, cancel, err := newContext(c, logger, fs, timeout, bodyLimit, downloadFromCfg, traceHeader, trace) if err != nil { cancel() diff --git a/pkg/modules/api/mocks.go b/pkg/modules/api/mocks.go index 3cdb8307e..7de02d643 100644 --- a/pkg/modules/api/mocks.go +++ b/pkg/modules/api/mocks.go @@ -54,7 +54,7 @@ func (ctx *ContextMock) SetFiles(files map[string]string) { ctx.files = files } -// SetCancelled sets if the context is cancelled or not. +// SetCancelled sets if the context is canceled or not. // // ctx := &api.ContextMock{Context: &api.Context{}} // ctx.SetCancelled(true) @@ -120,7 +120,7 @@ func (provider *MiddlewareProviderMock) Middlewares() ([]Middleware, error) { return provider.MiddlewaresMock() } -// HealthCheckerMock is mock for the [HealthChecker] interface. +// HealthCheckerMock is a mock for the [HealthChecker] interface. type HealthCheckerMock struct { ChecksMock func() ([]health.CheckerOption, error) ReadyMock func() error diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index 24b55827d..ed838d8b7 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -211,7 +211,7 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error { logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath)) } - // Also remove Chromium specific files in the temporary directory. + // Also, remove Chromium-specific files in the temporary directory. err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{".org.chromium.Chromium", ".com.google.Chrome"}, expirationTime) if err != nil { logger.Error(err.Error()) @@ -314,7 +314,7 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string return errors.New("context has no deadline") } - // We validate the "main" URL against our allow / deny lists. + // We validate the "main" URL against our allowed / deny lists. err := gotenberg.FilterDeadline(b.arguments.allowList, b.arguments.denyList, url, deadline) if err != nil { return fmt.Errorf("filter URL: %w", err) @@ -329,7 +329,7 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string taskCtx, taskCancel := chromedp.NewContext(timeoutCtx) defer taskCancel() - // We validate all others requests against our allow / deny lists. + // We validate all other requests against our allowed / deny lists. // If a request does not pass the validation, we make it fail. It also set // the extra HTTP headers, if any. // See https://github.com/gotenberg/gotenberg/issues/1011. diff --git a/pkg/modules/chromium/chromium.go b/pkg/modules/chromium/chromium.go index b356f7d94..5b2290acc 100644 --- a/pkg/modules/chromium/chromium.go +++ b/pkg/modules/chromium/chromium.go @@ -26,7 +26,7 @@ func init() { var ( // ErrInvalidEmulatedMediaType happens if the emulated media type is not - // "screen" nor "print". Empty value are allowed though. + // "screen" nor "print". Empty value is allowed, though. ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type") // ErrInvalidEvaluationExpression happens if an evaluation expression @@ -38,11 +38,11 @@ var ( ErrRpccMessageTooLarge = errors.New("rpcc message too large") // ErrInvalidHttpStatusCode happens when the status code from the main page - // matches with one of the entry in [Options.FailOnHttpStatusCodes]. + // matches with one of the entries in [Options.FailOnHttpStatusCodes]. ErrInvalidHttpStatusCode = errors.New("invalid HTTP status code") // ErrInvalidResourceHttpStatusCode happens when the status code from one - // or more resources matches with one of the entry in + // or more resources matches with one of the entries in // [Options.FailOnResourceHttpStatusCodes]. ErrInvalidResourceHttpStatusCode = errors.New("invalid resource HTTP status code") @@ -68,11 +68,11 @@ var ( ErrInvalidPrinterSettings = errors.New("invalid printer settings") // ErrPageRangesSyntaxError happens if the PdfOptions have an invalid page - // ranges. + // range. ErrPageRangesSyntaxError = errors.New("page ranges syntax error") ) -// Chromium is a module which provides both an [Api] and routes for converting +// Chromium is a module that provides both an [Api] and routes for converting // HTML document to PDF. type Chromium struct { autoStart bool @@ -128,15 +128,15 @@ type Options struct { UserAgent string // ExtraHttpHeaders are extra HTTP headers to send by Chromium while - // loading he HTML document. + // loading the HTML document. ExtraHttpHeaders []ExtraHttpHeader // EmulatedMediaType is the media type to emulate, either "screen" or // "print". EmulatedMediaType string - // OmitBackground hides default white background and allows generating PDFs - // with transparency. + // OmitBackground hides the default white background and allows generating + // PDFs with transparency. OmitBackground bool } @@ -197,9 +197,9 @@ type PdfOptions struct { // Page ranges to print, e.g., '1-5, 8, 11-13'. Empty means all pages. PageRanges string - // HeaderTemplate is the HTML template of the header. It should be valid - // HTML markup with following classes used to inject printing values into - // them: + // HeaderTemplate is the HTML template of the header. It should be a valid + // HTML markup with the following classes used to inject printing values + // into them: // - date: formatted print date // - title: document title // - url: document location @@ -338,7 +338,7 @@ type Api interface { Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error } -// Provider is a module interface which exposes a method for creating an [Api] +// Provider is a module interface that exposes a method for creating an [Api] // for other modules. // // func (m *YourModule) Provision(ctx *gotenberg.Context) error { @@ -473,8 +473,8 @@ func (mod *Chromium) StartupMessage() string { // Stop stops the current browser instance. func (mod *Chromium) Stop(ctx context.Context) error { - // Block until the context is done so that other module may gracefully stop - // before we do a shutdown. + // Block until the context is done so that another module may gracefully + // stop before we do a shutdown. mod.logger.Debug("wait for the end of grace duration") <-ctx.Done() diff --git a/pkg/modules/chromium/events.go b/pkg/modules/chromium/events.go index ec02ef08d..747161560 100644 --- a/pkg/modules/chromium/events.go +++ b/pkg/modules/chromium/events.go @@ -72,10 +72,10 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, option var extraHttpHeadersToSet []ExtraHttpHeader if len(options.extraHttpHeaders) > 0 { - // The user want to set extra HTTP headers. + // The user wants to set extra HTTP headers. // First, we have to check if at least one header has to be - // set for current request. + // set for the current request. for _, header := range options.extraHttpHeaders { if header.Scope == nil { // Non-scoped header. @@ -147,8 +147,8 @@ type eventResponseReceivedOptions struct { invalidResourceHttpStatusCodeMu *sync.RWMutex } -// listenForEventResponseReceived listens for an invalid HTTP status code that -// is returned by the main page or by one or more resources. +// listenForEventResponseReceived listens for an invalid HTTP status code +// returned by the main page or by one or more resources. // See: // https://github.com/gotenberg/gotenberg/issues/613. // https://github.com/gotenberg/gotenberg/issues/1021. diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index 2c5791719..d176dc926 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -25,7 +25,7 @@ import ( ) // FormDataChromiumOptions creates [Options] from the form data. Fallback to -// default value if the considered key is not present. +// the default value if the considered key is not present. func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) { defaultOptions := DefaultOptions() @@ -194,7 +194,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) { } // FormDataChromiumPdfOptions creates [PdfOptions] from the form data. Fallback to -// default value if the considered key is not present. +// the default value if the considered key is not present. func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) { form, options := FormDataChromiumOptions(ctx) defaultPdfOptions := DefaultPdfOptions() @@ -249,7 +249,7 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) { } // FormDataChromiumScreenshotOptions creates [ScreenshotOptions] from the form -// data. Fallback to default value if the considered key is not present. +// data. Fallback to the default value if the considered key is not present. func FormDataChromiumScreenshotOptions(ctx *api.Context) (*api.FormData, ScreenshotOptions) { form, options := FormDataChromiumOptions(ctx) defaultScreenshotOptions := DefaultScreenshotOptions() @@ -483,7 +483,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { } // screenshotMarkdownRoute returns an [api.Route] which can take a screenshot -// from markdown files. +// from Markdown files. func screenshotMarkdownRoute(chromium Api) api.Route { return api.Route{ Method: http.MethodPost, @@ -522,7 +522,7 @@ func screenshotMarkdownRoute(chromium Api) api.Route { } func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string) (string, error) { - // We have to convert each markdown file referenced in the HTML + // We have to convert each Markdown file referenced in the HTML // file to... HTML. Thanks to the "html/template" package, we are // able to provide the "toHTML" function which the user may call // directly inside the HTML file. diff --git a/pkg/modules/chromium/stream.go b/pkg/modules/chromium/stream.go index 70d205764..23092a40f 100644 --- a/pkg/modules/chromium/stream.go +++ b/pkg/modules/chromium/stream.go @@ -24,7 +24,7 @@ type streamReader struct { // Read a chunk of the stream. func (reader *streamReader) Read(p []byte) (n int, err error) { if reader.r != nil { - // Continue reading from buffer. + // Continue reading from the buffer. return reader.read(p) } diff --git a/pkg/modules/exiftool/exiftool.go b/pkg/modules/exiftool/exiftool.go index f1e07cdf1..6127035f6 100644 --- a/pkg/modules/exiftool/exiftool.go +++ b/pkg/modules/exiftool/exiftool.go @@ -142,15 +142,15 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *zap.Logger, m fileMetadata[0].SetStrings(key, val) case []interface{}: // See https://github.com/gotenberg/gotenberg/issues/1048. - strings := make([]string, len(val)) + strs := make([]string, len(val)) for i, entry := range val { if str, ok := entry.(string); ok { - strings[i] = str + strs[i] = str continue } return fmt.Errorf("write PDF metadata with ExifTool: %s %+v %s %w", key, val, reflect.TypeOf(val), gotenberg.ErrPdfEngineMetadataValueNotSupported) } - fileMetadata[0].SetStrings(key, strings) + fileMetadata[0].SetStrings(key, strs) case bool: fileMetadata[0].SetString(key, fmt.Sprintf("%t", val)) case int: diff --git a/pkg/modules/libreoffice/api/api.go b/pkg/modules/libreoffice/api/api.go index 2ca56ed91..2e0529b46 100644 --- a/pkg/modules/libreoffice/api/api.go +++ b/pkg/modules/libreoffice/api/api.go @@ -24,23 +24,23 @@ func init() { } var ( - // ErrInvalidPdfFormats happens if the PDF formats option cannot be handled - // by LibreOffice. + // ErrInvalidPdfFormats happens if LibreOffice cannot handle the PDF + // formats option. ErrInvalidPdfFormats = errors.New("invalid PDF formats") - // ErrUnoException happens when unoconverter returns an exit code 5. + // ErrUnoException happens when unoconverter returns exit code 5. ErrUnoException = errors.New("uno exception") - // ErrRuntimeException happens when unoconverter returns an exit code 6. + // ErrRuntimeException happens when unoconverter returns exit code 6. ErrRuntimeException = errors.New("uno exception") - // ErrCoreDumped happens randomly; sometime a conversion will work as + // ErrCoreDumped happens randomly; sometimes a conversion will work as // expected, and some other time the same conversion will fail. // See https://github.com/gotenberg/gotenberg/issues/639. ErrCoreDumped = errors.New("core dumped") ) -// Api is a module which provides a [Uno] to interact with LibreOffice. +// Api is a module that provides a [Uno] to interact with LibreOffice. type Api struct { autoStart bool args libreOfficeArguments @@ -56,10 +56,10 @@ type Options struct { // Password specifies the password for opening the source file. Password string - // Landscape allows to change the orientation of the resulting PDF. + // Landscape allows changing the orientation of the resulting PDF. Landscape bool - // PageRanges allows to select the pages to convert. + // PageRanges allows selecting the pages to convert. PageRanges string // UpdateIndexes specifies whether to update the indexes before conversion, @@ -83,7 +83,7 @@ type Options struct { // Named Destination. ExportBookmarksToPdfDestination bool - // ExportPlaceholders exports the placeholders fields visual markings only. + // ExportPlaceholders exports the placeholder fields visual markings only. // The exported placeholder is ineffective. ExportPlaceholders bool @@ -94,15 +94,16 @@ type Options struct { // Notes pages are available in Impress documents only. ExportNotesPages bool - // ExportOnlyNotesPages specifies, if the property ExportNotesPages is set - // to true, if only notes pages are exported to PDF. + // ExportOnlyNotesPages specifies if the property ExportNotesPages is set + // to true if only notes pages are exported to PDF. ExportOnlyNotesPages bool - // ExportNotesInMargin specifies if notes in margin are exported to PDF. + // ExportNotesInMargin specifies if notes in the margin are exported to + // PDF. ExportNotesInMargin bool // ConvertOooTargetToPdfTarget specifies that the target documents with - // .od[tpgs] extension, will have that extension changed to .pdf when the + // .od[tpgs] extension will have that extension changed to .pdf when the // link is exported to PDF. The source document remains untouched. ConvertOooTargetToPdfTarget bool @@ -190,7 +191,7 @@ type Uno interface { Extensions() []string } -// Provider is a module interface which exposes a method for creating a +// Provider is a module interface that exposes a method for creating a // [Uno] for other modules. // // func (m *YourModule) Provision(ctx *gotenberg.Context) error { @@ -300,8 +301,8 @@ func (a *Api) StartupMessage() string { // Stop stops the current browser instance. func (a *Api) Stop(ctx context.Context) error { - // Block until the context is done so that other module may gracefully stop - // before we do a shutdown. + // Block until the context is done so that another module may gracefully + // stop before we do a shutdown. a.logger.Debug("wait for the end of grace duration") <-ctx.Done() diff --git a/pkg/modules/libreoffice/api/libreoffice.go b/pkg/modules/libreoffice/api/libreoffice.go index f196e3272..b9dfe6079 100644 --- a/pkg/modules/libreoffice/api/libreoffice.go +++ b/pkg/modules/libreoffice/api/libreoffice.go @@ -200,7 +200,7 @@ func (p *libreOfficeProcess) Stop(logger *zap.Logger) error { logger.Debug(fmt.Sprintf("'%s' LibreOffice's user profile directory removed", userProfileDirPath)) } - // Also remove LibreOffice specific files in the temporary directory. + // Also, remove LibreOffice specific files in the temporary directory. err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{"OSL_PIPE", ".tmp"}, expirationTime) if err != nil { logger.Error(err.Error()) @@ -353,10 +353,10 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP } // LibreOffice's errors are not explicit. - // For instance, an exit code 5 may be explained by a malformed page - // ranges, but also by a not required password. + // For instance, exit code 5 may be explained by a malformed page range + // but also by a not required password. - // We may want to retry in case of a core dumped event. + // We may want to retry in case of a core-dumped event. // See https://github.com/gotenberg/gotenberg/issues/639. if strings.Contains(err.Error(), "core dumped") { return ErrCoreDumped diff --git a/pkg/modules/libreoffice/libreoffice.go b/pkg/modules/libreoffice/libreoffice.go index 6dd04b82e..fe6573394 100644 --- a/pkg/modules/libreoffice/libreoffice.go +++ b/pkg/modules/libreoffice/libreoffice.go @@ -14,7 +14,7 @@ func init() { gotenberg.MustRegisterModule(new(LibreOffice)) } -// LibreOffice is a module which provides a route for converting documents to +// LibreOffice is a module that provides a route for converting documents to // PDF with LibreOffice. type LibreOffice struct { api libeofficeapi.Uno diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine.go b/pkg/modules/libreoffice/pdfengine/pdfengine.go index 72ae31122..94dfc4f1f 100644 --- a/pkg/modules/libreoffice/pdfengine/pdfengine.go +++ b/pkg/modules/libreoffice/pdfengine/pdfengine.go @@ -58,7 +58,7 @@ func (engine *LibreOfficePdfEngine) Split(ctx context.Context, logger *zap.Logge // Flatten is not available in this implementation. func (engine *LibreOfficePdfEngine) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { - return fmt.Errorf("Flatten PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) + return fmt.Errorf("flatten PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) } // Convert converts the given PDF to a specific PDF format. Currently, only the diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index 113b5d79e..ceaf282ec 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -164,7 +164,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } if nativePdfFormats && splitMode == zeroValuedSplitMode { - // Only apply natively given PDF formats if we're not + // Only natively apply given PDF formats if we're not // splitting the PDF later. options.PdfFormats = pdfFormats } diff --git a/pkg/modules/logging/logging.go b/pkg/modules/logging/logging.go index 26600122e..91f591b14 100644 --- a/pkg/modules/logging/logging.go +++ b/pkg/modules/logging/logging.go @@ -31,7 +31,7 @@ const ( textLoggingFormat = "text" ) -// Logging is a module which implements the [gotenberg.LoggerProvider] +// Logging is a module that implements the [gotenberg.LoggerProvider] // interface. type Logging struct { level string diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index 4995ccfd3..4347a0dbf 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -124,7 +124,7 @@ func (multi *multiPdfEngines) Flatten(ctx context.Context, logger *zap.Logger, i return fmt.Errorf("flatten PDF with multi PDF engines: %w", err) } -// Convert converts the given PDF to a specific PDF format. thanks to its +// Convert converts the given PDF to a specific PDF format, thanks to its // children. If the context is done, it stops and returns an error. func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { var err error diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index aae87f0d5..c73ea85d9 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -92,7 +92,7 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { defaultNames[i] = engine.(gotenberg.Module).Descriptor().ID } - // Example in case of deprecated module name. + // Example in the case of deprecated module name. //for i, name := range defaultNames { // if name == "unoconv-pdfengine" || name == "uno-pdfengine" { // logger.Warn(fmt.Sprintf("%s is deprecated; prefer libreoffice-pdfengine instead", name)) diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index d43951739..b2831dcc2 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -33,7 +33,7 @@ func (engine *PdfTk) Descriptor() gotenberg.ModuleDescriptor { } } -// Provision sets the modules properties. +// Provision sets the module properties. func (engine *PdfTk) Provision(ctx *gotenberg.Context) error { binPath, ok := os.LookupEnv("PDFTK_BIN_PATH") if !ok { diff --git a/pkg/modules/prometheus/prometheus.go b/pkg/modules/prometheus/prometheus.go index 6f54d047e..d899e6b42 100644 --- a/pkg/modules/prometheus/prometheus.go +++ b/pkg/modules/prometheus/prometheus.go @@ -20,7 +20,7 @@ func init() { gotenberg.MustRegisterModule(new(Prometheus)) } -// Prometheus is a module which collects metrics and exposes them via an HTTP +// Prometheus is a module that collects metrics and exposes them via an HTTP // route. type Prometheus struct { namespace string @@ -49,7 +49,7 @@ func (mod *Prometheus) Descriptor() gotenberg.ModuleDescriptor { } } -// Provision sets the modules properties. +// Provision sets the module properties. func (mod *Prometheus) Provision(ctx *gotenberg.Context) error { flags := ctx.ParsedFlags() mod.namespace = flags.MustString("prometheus-namespace") diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 45123bbb0..7a65991b5 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -34,7 +34,7 @@ func (engine *QPdf) Descriptor() gotenberg.ModuleDescriptor { } } -// Provision sets the modules properties. +// Provision sets the module properties. func (engine *QPdf) Provision(ctx *gotenberg.Context) error { binPath, ok := os.LookupEnv("QPDF_BIN_PATH") if !ok { diff --git a/pkg/modules/webhook/client.go b/pkg/modules/webhook/client.go index 9ce23b190..39d91bd6b 100644 --- a/pkg/modules/webhook/client.go +++ b/pkg/modules/webhook/client.go @@ -26,14 +26,14 @@ type client struct { } // send call the webhook either to send the success response or the error response. -func (c client) send(body io.Reader, headers map[string]string, erroed bool) error { +func (c client) send(body io.Reader, headers map[string]string, errored bool) error { url := c.url - if erroed { + if errored { url = c.errorUrl } method := c.method - if erroed { + if errored { method = c.errorMethod } @@ -54,9 +54,9 @@ func (c client) send(body io.Reader, headers map[string]string, erroed bool) err contentLength, ok := headers[echo.HeaderContentLength] if ok { // Golang "http" package should automatically calculate the size of the - // body. But, when using a buffered file reader, it does not work. - // Worse, the "Content-Length" header is also removed. Therefore, in - // order to keep this valuable information, we have to trust the caller + // body. But when using a buffered file reader, it does not work. + // Worse, the "Content-Length" header is also removed. Therefore, + // to keep this valuable information, we have to trust the caller // by reading the value of the "Content-Length" entry and set it as the // content length of the request. It's kinda suboptimal, but hey, at // least it works. @@ -100,7 +100,7 @@ func (c client) send(body io.Reader, headers map[string]string, erroed bool) err fields[3] = zap.String("latency_human", finishTime.Sub(c.startTime).String()) fields[4] = zap.Int64("bytes_out", req.ContentLength) - if erroed { + if errored { c.logger.Warn("request to webhook with error details handled", fields...) return nil diff --git a/pkg/modules/webhook/middleware.go b/pkg/modules/webhook/middleware.go index aa6ac1258..ab8363d79 100644 --- a/pkg/modules/webhook/middleware.go +++ b/pkg/modules/webhook/middleware.go @@ -113,7 +113,7 @@ func webhookMiddleware(w *Webhook) api.Middleware { } } - // Retrieve values from echo.Context before it get recycled. + // Retrieve values from echo.Context before it gets recycled. // See https://github.com/gotenberg/gotenberg/issues/1000. startTime := c.Get("startTime").(time.Time) traceHeader := c.Get("traceHeader").(string) @@ -189,7 +189,7 @@ func webhookMiddleware(w *Webhook) api.Middleware { return } - // No error, let's get build the output file. + // No error, let's get to build the output file. outputPath, err := ctx.BuildOutputFile() if err != nil { ctx.Log().Error(fmt.Sprintf("build output file: %s", err)) diff --git a/pkg/modules/webhook/webhook.go b/pkg/modules/webhook/webhook.go index 866122096..54661a20a 100644 --- a/pkg/modules/webhook/webhook.go +++ b/pkg/modules/webhook/webhook.go @@ -14,7 +14,7 @@ func init() { gotenberg.MustRegisterModule(new(Webhook)) } -// Webhook is a module which provides a middleware for uploading output files +// Webhook is a module that provides a middleware for uploading output files // to any destinations in an asynchronous fashion. type Webhook struct { allowList *regexp2.Regexp From a731bf034e53ef868f80b949cae2e6bbc61bc597 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 17 Apr 2025 11:27:47 +0200 Subject: [PATCH 098/254] chore(deps): update Go dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8b4f356a6..1913e5ea2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexliesenfeld/health v0.8.0 github.com/andybalholm/brotli v1.1.1 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b + github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a github.com/chromedp/chromedp v0.13.6 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/go.sum b/go.sum index c9476602b..c9179e7b6 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b h1:jJmiCljLNTaq/O1ju9Bzz2MPpFlmiTn0F7LwCoeDZVw= -github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a h1:TVjyMfX22UgV/jP0A4UwrrCmI9zfvp6ZS5UBZMy3HDw= +github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk= github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= From fabe323e3422996f6f93539726532028baf53239 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 17 Apr 2025 11:28:17 +0200 Subject: [PATCH 099/254] fix(chromium): add --no-zygote and --disable-dev-shm-usage flags - fixes #1177 --- pkg/modules/chromium/browser.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index ed838d8b7..b0f394667 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -93,6 +93,9 @@ func (b *chromiumBrowser) Start(logger *zap.Logger) error { chromedp.UserDataDir(b.userProfileDirPath), // See https://github.com/gotenberg/gotenberg/issues/831. chromedp.Flag("disable-pdf-tagging", true), + // See https://github.com/gotenberg/gotenberg/issues/1177. + chromedp.Flag("no-zygote", true), + chromedp.Flag("disable-dev-shm-usage", true), ) if b.arguments.incognito { From 088b3274f26c9863fe06c1299e2b431702acbe2e Mon Sep 17 00:00:00 2001 From: Hector Zarate Date: Sat, 26 Apr 2025 13:07:51 +0200 Subject: [PATCH 100/254] feat(dep): add dependabot for mod and docker files (#1179) * add dependabot for mod and docker files * lint dependabot file --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6fddca0d6..03ae5d6b5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,11 @@ updates: directory: "/" schedule: interval: "weekly" + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "docker" + directory: "/build" + schedule: + interval: "weekly" From e5363205a717a5b7d2b61a3c6f13062a96484436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 11:08:54 +0000 Subject: [PATCH 101/254] chore(deps): bump github.com/testcontainers/testcontainers-go Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.36.0 to 0.37.0. - [Release notes](https://github.com/testcontainers/testcontainers-go/releases) - [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.36.0...v0.37.0) --- updated-dependencies: - dependency-name: github.com/testcontainers/testcontainers-go dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1913e5ea2..1a7ca74f7 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.1 github.com/shirou/gopsutil/v4 v4.25.3 - github.com/testcontainers/testcontainers-go v0.36.0 + github.com/testcontainers/testcontainers-go v0.37.0 ) require ( diff --git a/go.sum b/go.sum index c9179e7b6..b581aa022 100644 --- a/go.sum +++ b/go.sum @@ -296,8 +296,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00= -github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= From 9a38f1621917c6e0ba15735a66f4086991e82da2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 11:08:46 +0000 Subject: [PATCH 102/254] chore(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.0.4+incompatible to 28.1.1+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v28.0.4...v28.1.1) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.1.1+incompatible dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 +++- go.sum | 16 ++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1a7ca74f7..61ee06d7b 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( require ( github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.0.4+incompatible + github.com/docker/docker v28.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.1 github.com/shirou/gopsutil/v4 v4.25.3 @@ -87,7 +87,9 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/minio/minlz v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect diff --git a/go.sum b/go.sum index b581aa022..c6c75ffa4 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -81,8 +81,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= -github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -227,8 +227,12 @@ github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= @@ -545,8 +549,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c78ef8771d87a7b2f79e81d8115e9318ac0c8a13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 03:00:21 +0000 Subject: [PATCH 103/254] chore(deps): bump github.com/shirou/gopsutil/v4 from 4.25.3 to 4.25.4 Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.25.3 to 4.25.4. - [Release notes](https://github.com/shirou/gopsutil/releases) - [Commits](https://github.com/shirou/gopsutil/compare/v4.25.3...v4.25.4) --- updated-dependencies: - dependency-name: github.com/shirou/gopsutil/v4 dependency-version: 4.25.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 61ee06d7b..dc5969498 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/docker/docker v28.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.1 - github.com/shirou/gopsutil/v4 v4.25.3 + github.com/shirou/gopsutil/v4 v4.25.4 github.com/testcontainers/testcontainers-go v0.37.0 ) diff --git a/go.sum b/go.sum index c6c75ffa4..910a8671c 100644 --- a/go.sum +++ b/go.sum @@ -276,8 +276,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= -github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= +github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw= +github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= From 3d652bc8292ed2975da446c3d6bcc12be1df780a Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 5 May 2025 15:48:52 +0200 Subject: [PATCH 104/254] ci(dependabot): add npm --- .github/dependabot.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 03ae5d6b5..544562557 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,15 +1,18 @@ version: 2 updates: - # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + - package-ecosystem: "docker" + directory: "/build" + schedule: + interval: "weekly" - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" - - package-ecosystem: "docker" - directory: "/build" + - package-ecosystem: "npm" + directory: "/" schedule: interval: "weekly" From b63f4c53d20fe00cc7fa9c0cd7ed937615784b90 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 5 May 2025 15:49:24 +0200 Subject: [PATCH 105/254] ci(lint): update golangci-lint version --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index a35d0f970..291cd6d40 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -31,7 +31,7 @@ jobs: - name: Run linters uses: golangci/golangci-lint-action@v7 with: - version: v2.0.2 + version: v2.1.6 lint-prettier: name: Lint non-Golang codebase From 5c43eefe4635218c4f6f4e5a87ea8af43df845dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 13:50:23 +0000 Subject: [PATCH 106/254] chore(deps): bump golangci/golangci-lint-action from 7 to 8 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7 to 8. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v7...v8) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 291cd6d40..abccd254b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -29,7 +29,7 @@ jobs: go-version-file: go.mod - name: Run linters - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@v8 with: version: v2.1.6 From 05f9ac3745e2afedcf55083e43f2d825892ed417 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 14:26:40 +0000 Subject: [PATCH 107/254] chore(deps-dev): bump prettier-plugin-gherkin from 3.1.1 to 3.1.2 Bumps [prettier-plugin-gherkin](https://github.com/mapado/prettier-plugin-gherkin) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/mapado/prettier-plugin-gherkin/releases) - [Changelog](https://github.com/mapado/prettier-plugin-gherkin/blob/main/CHANGELOG.md) - [Commits](https://github.com/mapado/prettier-plugin-gherkin/compare/v3.1.1...v3.1.2) --- updated-dependencies: - dependency-name: prettier-plugin-gherkin dependency-version: 3.1.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce3522877..e99f79998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "devDependencies": { "prettier": "3.5.2", - "prettier-plugin-gherkin": "^3.1.1", + "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.15.0" } }, @@ -101,9 +101,9 @@ } }, "node_modules/prettier-plugin-gherkin": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-gherkin/-/prettier-plugin-gherkin-3.1.1.tgz", - "integrity": "sha512-cCfjqKMdR2a8jOf8yKg32iUfRq0Dfmx+K2qTMOD/ixJJq0Rp7QS8BaoABWC7CDGXbvOkVeWOvzhxWvYcEbjglw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-gherkin/-/prettier-plugin-gherkin-3.1.2.tgz", + "integrity": "sha512-LgehRNKCAqZsoE/yblmqXYfMpEK2mhVWQKNj/TFzeLR+0rhFGHqq3RlYcYhPgzbr0n6w2Bv+0Gm1yxNdzkdacw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 9d4beff8b..9498322de 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "devDependencies": { "prettier": "3.5.2", - "prettier-plugin-gherkin": "^3.1.1", + "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.15.0" } } From 910af7a44cd8fcab76719890448a3f884fabf10e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 14:09:52 +0000 Subject: [PATCH 108/254] chore(deps): bump golang.org/x/sync from 0.13.0 to 0.14.0 Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.13.0 to 0.14.0. - [Commits](https://github.com/golang/sync/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-version: 0.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dc5969498..6e0bb03c9 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.39.0 - golang.org/x/sync v0.13.0 + golang.org/x/sync v0.14.0 golang.org/x/sys v0.32.0 // indirect golang.org/x/term v0.31.0 golang.org/x/text v0.24.0 diff --git a/go.sum b/go.sum index 910a8671c..7c713e119 100644 --- a/go.sum +++ b/go.sum @@ -422,8 +422,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From a7c02438e46157cbb590fe10889569dfb7b8bd39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 17:22:44 +0000 Subject: [PATCH 109/254] chore(deps-dev): bump prettier from 3.5.2 to 3.5.3 Bumps [prettier](https://github.com/prettier/prettier) from 3.5.2 to 3.5.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.2...3.5.3) --- updated-dependencies: - dependency-name: prettier dependency-version: 3.5.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e99f79998..a368c0879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "prettier": "3.5.2", + "prettier": "3.5.3", "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.15.0" } @@ -85,9 +85,9 @@ "license": "BSD-3-Clause" }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 9498322de..24b67311a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "prettier": "3.5.2", + "prettier": "3.5.3", "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.15.0" } From 64add9a5421945855992f136f07d75e6db63d34d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 17:22:43 +0000 Subject: [PATCH 110/254] chore(deps-dev): bump prettier-plugin-sh from 0.15.0 to 0.17.2 Bumps [prettier-plugin-sh](https://github.com/un-ts/prettier) from 0.15.0 to 0.17.2. - [Release notes](https://github.com/un-ts/prettier/releases) - [Changelog](https://github.com/un-ts/prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/un-ts/prettier/compare/prettier-plugin-sh@0.15.0...prettier-plugin-sh@0.17.2) --- updated-dependencies: - dependency-name: prettier-plugin-sh dependency-version: 0.17.2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 39 +++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index a368c0879..f11a39bc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "prettier": "3.5.3", "prettier-plugin-gherkin": "^3.1.2", - "prettier-plugin-sh": "^0.15.0" + "prettier-plugin-sh": "^0.17.2" } }, "node_modules/@cucumber/gherkin": { @@ -63,6 +63,16 @@ "uuid": "9.0.1" } }, + "node_modules/@reteps/dockerfmt": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@reteps/dockerfmt/-/dockerfmt-0.3.6.tgz", + "integrity": "sha512-Tb5wIMvBf/nLejTQ61krK644/CEMB/cpiaIFXqGApfGqO3GwcR3qnI0DbmkFVCl2OyEp8LnLX3EkucoL0+tbFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^v12.20.0 || ^14.13.0 || >=16.0.0" + } + }, "node_modules/@types/uuid": { "version": "9.0.6", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", @@ -77,13 +87,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mvdan-sh": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz", - "integrity": "sha512-kMbrH0EObaKmK3nVRKUIIya1dpASHIEusM13S4V1ViHFuxuNxCo+arxoa6j/dbV22YBGjl7UKJm9QQKJ2Crzhg==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/prettier": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", @@ -113,14 +116,14 @@ } }, "node_modules/prettier-plugin-sh": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.15.0.tgz", - "integrity": "sha512-U0PikJr/yr2bzzARl43qI0mApBj0C1xdAfA04AZa6LnvIKawXHhuy2fFo6LNA7weRzGlAiNbaEFfKMFo0nZr/A==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.17.2.tgz", + "integrity": "sha512-7+dEo/IYbhrUj4qP+1QXj41/5Hv9ZkxBuEatI1jywrcAlVF1aGhdYJF4Sn+M67nkA16iRL53W4FSRe1bitTdmQ==", "dev": true, "license": "MIT", "dependencies": { - "mvdan-sh": "^0.10.1", - "sh-syntax": "^0.4.2" + "@reteps/dockerfmt": "^0.3.2", + "sh-syntax": "^0.5.6" }, "engines": { "node": ">=16.0.0" @@ -140,19 +143,19 @@ "license": "Apache-2.0" }, "node_modules/sh-syntax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.4.2.tgz", - "integrity": "sha512-/l2UZ5fhGZLVZa16XQM9/Vq/hezGGbdHeVEA01uWjOL1+7Ek/gt6FquW0iKKws4a9AYPYvlz6RyVvjh3JxOteg==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.5.7.tgz", + "integrity": "sha512-74m9dt91konrF5+m0kASugzi37VxKsnTJQ6yvdDZu3IijG5/vIZpImP6FadsJLWNt2X2YD0VaTwW5W7Ox7mFVg==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "engines": { "node": ">=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/sh-syntax" } }, "node_modules/tslib": { diff --git a/package.json b/package.json index 24b67311a..d12a72383 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "devDependencies": { "prettier": "3.5.3", "prettier-plugin-gherkin": "^3.1.2", - "prettier-plugin-sh": "^0.15.0" + "prettier-plugin-sh": "^0.17.2" } } From 71f1f3dd223978fe24f5217d1ade0ad3e58d45d8 Mon Sep 17 00:00:00 2001 From: teng Date: Tue, 6 May 2025 16:26:46 +0800 Subject: [PATCH 111/254] feat(chromium): change sort of wait delay action and wait expression action (#1186) eg: scroll screen to load all lazy image. --- pkg/modules/chromium/browser.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index b0f394667..6bf63ec67 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -276,8 +276,8 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outp hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground), forceExactColorsActionFunc(logger, options.PrintBackground), emulateMediaTypeActionFunc(logger, options.EmulatedMediaType), - waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression), + waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), // PDF specific. printToPdfActionFunc(logger, outputPath, options), }) @@ -299,8 +299,8 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, ur hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true), forceExactColorsActionFunc(logger, true), emulateMediaTypeActionFunc(logger, options.EmulatedMediaType), - waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression), + waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), // Screenshot specific. setDeviceMetricsOverride(logger, options.Width, options.Height), captureScreenshotActionFunc(logger, outputPath, options), From ca1d11006c2975b56cd7051b18033c4d4413160d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:56:00 +0200 Subject: [PATCH 112/254] chore(deps-dev): bump prettier-plugin-sh from 0.17.2 to 0.17.4 (#1205) Bumps [prettier-plugin-sh](https://github.com/un-ts/prettier) from 0.17.2 to 0.17.4. - [Release notes](https://github.com/un-ts/prettier/releases) - [Changelog](https://github.com/un-ts/prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/un-ts/prettier/compare/prettier-plugin-sh@0.17.2...prettier-plugin-sh@0.17.4) --- updated-dependencies: - dependency-name: prettier-plugin-sh dependency-version: 0.17.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f11a39bc7..f7a7f6998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "prettier": "3.5.3", "prettier-plugin-gherkin": "^3.1.2", - "prettier-plugin-sh": "^0.17.2" + "prettier-plugin-sh": "^0.17.4" } }, "node_modules/@cucumber/gherkin": { @@ -116,13 +116,13 @@ } }, "node_modules/prettier-plugin-sh": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.17.2.tgz", - "integrity": "sha512-7+dEo/IYbhrUj4qP+1QXj41/5Hv9ZkxBuEatI1jywrcAlVF1aGhdYJF4Sn+M67nkA16iRL53W4FSRe1bitTdmQ==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.17.4.tgz", + "integrity": "sha512-aAVKXZ7GTEMZdZsIPSwMwddwPvt2ibMbRGd4OJAP0G7QoeYZV+mPNg2Oln3R53sZ4PVjeAA7Xzi/PuI0QlHHfQ==", "dev": true, "license": "MIT", "dependencies": { - "@reteps/dockerfmt": "^0.3.2", + "@reteps/dockerfmt": "^0.3.5", "sh-syntax": "^0.5.6" }, "engines": { diff --git a/package.json b/package.json index d12a72383..d254bc8f8 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "devDependencies": { "prettier": "3.5.3", "prettier-plugin-gherkin": "^3.1.2", - "prettier-plugin-sh": "^0.17.2" + "prettier-plugin-sh": "^0.17.4" } } From 1ed33a74009d3f9d239e2c20771a7dd2941e2973 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:56:09 +0200 Subject: [PATCH 113/254] chore(deps): bump golang.org/x/net from 0.39.0 to 0.40.0 (#1204) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.39.0 to 0.40.0. - [Commits](https://github.com/golang/net/compare/v0.39.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 6e0bb03c9..08b2822b7 100644 --- a/go.mod +++ b/go.mod @@ -24,12 +24,12 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/net v0.39.0 + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 - golang.org/x/sys v0.32.0 // indirect - golang.org/x/term v0.31.0 - golang.org/x/text v0.24.0 + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 + golang.org/x/text v0.25.0 ) require ( diff --git a/go.sum b/go.sum index 7c713e119..1c9df313a 100644 --- a/go.sum +++ b/go.sum @@ -360,8 +360,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -407,8 +407,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -448,13 +448,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -462,8 +462,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= From 4b30567c399f136def61937ed1e09ab141d3d33b Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 12 May 2025 09:35:10 +0200 Subject: [PATCH 114/254] feat(chromium): close page after conversion --- pkg/modules/chromium/browser.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/modules/chromium/browser.go b/pkg/modules/chromium/browser.go index 6bf63ec67..fad281520 100644 --- a/pkg/modules/chromium/browser.go +++ b/pkg/modules/chromium/browser.go @@ -12,6 +12,7 @@ import ( "github.com/chromedp/cdproto/fetch" "github.com/chromedp/cdproto/network" + "github.com/chromedp/cdproto/page" "github.com/chromedp/cdproto/runtime" "github.com/chromedp/chromedp" "github.com/dlclark/regexp2" @@ -280,6 +281,8 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outp waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay), // PDF specific. printToPdfActionFunc(logger, outputPath, options), + // Teardown. + page.Close(), }) } @@ -304,6 +307,8 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, ur // Screenshot specific. setDeviceMetricsOverride(logger, options.Width, options.Height), captureScreenshotActionFunc(logger, outputPath, options), + // Teardown. + page.Close(), }) } From 45b47a10714c07937a6b72cdce2e217ac52425f6 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 13 May 2025 09:43:16 +0200 Subject: [PATCH 115/254] feat(Dockerfile): use upx-ucl to reduce Go binaries size --- build/Dockerfile | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index f8dff9a65..6a49c87ce 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -8,7 +8,6 @@ ARG GOLANG_VERSION # ---------------------------------------------- # Note: this stage is required as pdfcpu does not release an armhf variant by # default. - FROM golang:$GOLANG_VERSION AS pdfcpu-binary-stage ARG PDFCPU_VERSION @@ -49,7 +48,25 @@ RUN go mod download &&\ COPY cmd ./cmd COPY pkg ./pkg -RUN go build -o gotenberg -ldflags "-X 'github.com/gotenberg/gotenberg/v8/cmd.Version=$GOTENBERG_VERSION'" cmd/gotenberg/main.go +RUN go build -o gotenberg -ldflags "-s -w -X 'github.com/gotenberg/gotenberg/v8/cmd.Version=$GOTENBERG_VERSION'" cmd/gotenberg/main.go + +# ---------------------------------------------- +# Compress Golang binaries stage +# ---------------------------------------------- +FROM debian:12-slim AS compress-go-binaries-stage + +RUN \ + echo "deb http://deb.debian.org/debian bookworm-backports main" >> /etc/apt/sources.list &&\ + apt-get update -qq &&\ + apt-get upgrade -yqq &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends -t bookworm-backports upx-ucl + +COPY --from=pdfcpu-binary-stage /home/pdfcpu /home/pdfcpu +COPY --from=gotenberg-binary-stage /home/gotenberg /home/gotenberg + +RUN \ + upx-ucl --best /home/pdfcpu &&\ + upx-ucl --best /home/gotenberg # ---------------------------------------------- # Final stage @@ -206,11 +223,9 @@ RUN \ # https://github.com/arachnys/athenapdf/commit/ba25a8d80a25d08d58865519c4cd8756dc9a336d. COPY build/fonts.conf /etc/fonts/conf.d/100-gotenberg.conf -# Copy the pdfcpu binary from the pdfcpu-binary-stage. -COPY --from=pdfcpu-binary-stage /home/pdfcpu /usr/bin/ - -# Copy the Gotenberg binary from the gotenberg-binary-stage. -COPY --from=gotenberg-binary-stage /home/gotenberg /usr/bin/ +# Copy the Golang binaries. +COPY --from=compress-go-binaries-stage /home/pdfcpu /usr/bin/ +COPY --from=compress-go-binaries-stage /home/gotenberg /usr/bin/ # Environment variables required by modules or else. ENV CHROMIUM_BIN_PATH=/usr/bin/chromium From 79425af848873dc9efc91f88b60132481b9ba7de Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 13 May 2025 15:32:18 +0200 Subject: [PATCH 116/254] feat(Dockerfile): use jlink to switch to a lightweight JRE --- build/Dockerfile | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 6a49c87ce..88dcd64a4 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -68,10 +68,42 @@ RUN \ upx-ucl --best /home/pdfcpu &&\ upx-ucl --best /home/gotenberg +# ---------------------------------------------- +# Custom JRE stage +# Credits: https://github.com/jodconverter/docker-image-jodconverter-runtime +# ---------------------------------------------- +FROM debian:12-slim AS custom-jre-stage + +RUN \ + apt-get update -qq &&\ + apt-get upgrade -yqq &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends default-jdk-headless binutils + +# Note: jdeps helps finding which modules a JAR requires. +# Currently only for PDFtk, as we don't rely on LibreOffice UNO Java SDK. +ENV JAVA_MODULES=java.base,java.desktop,java.naming,java.sql + +RUN jlink \ + --add-modules $JAVA_MODULES \ + --strip-debug \ + --no-man-pages \ + --no-header-files \ + --compress=2 \ + --output /custom-jre + +# ---------------------------------------------- +# Base image stage +# ---------------------------------------------- +FROM debian:12-slim AS base-image-stage + +COPY --from=custom-jre-stage /custom-jre /opt/java + +ENV PATH="/opt/java/bin:${PATH}" + # ---------------------------------------------- # Final stage # ---------------------------------------------- -FROM debian:12-slim +FROM base-image-stage ARG GOTENBERG_VERSION ARG GOTENBERG_USER_GID @@ -99,7 +131,7 @@ RUN \ # Note: tini is a helper for reaping zombie processes. apt-get update -qq &&\ apt-get upgrade -yqq &&\ - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends curl gnupg tini python3 default-jre-headless &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends curl gnupg tini python3 &&\ # Cleanup. # Note: the Debian image does automatically a clean after each install thanks to a hook. # Therefore, there is no need for apt-get clean. From be3145aae97b5c4df0930b7a6e4e50d2d55c3b9d Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 13 May 2025 16:09:51 +0200 Subject: [PATCH 117/254] 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 +++++ .../testdata/Special_Chars_\303\237.docx" | Bin 0 -> 6408 bytes 4 files changed, 26 insertions(+), 67 deletions(-) create mode 100644 "test/integration/testdata/Special_Chars_\303\237.docx" diff --git a/build/Dockerfile b/build/Dockerfile index 88dcd64a4..699405466 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 b9dfe6079..9ee9e3531 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 91a4fcb97..8a96a936f 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_\303\237.docx" "b/test/integration/testdata/Special_Chars_\303\237.docx" new file mode 100644 index 0000000000000000000000000000000000000000..1868509c5fe654e5dc4edb66f210459c88aa3f25 GIT binary patch literal 6408 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 q!tYG&_a%NUj)(j9mr35Q`2XBKRYl-^XaNA^` Date: Wed, 14 May 2025 09:37:42 +0200 Subject: [PATCH 118/254] ci: make it work for forks too --- .github/actions/build-test-push/action.yml | 4 +++- .github/workflows/continuous-integration.yml | 19 ++++++++++++++++++- .github/workflows/pull-request-cleanup.yml | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/actions/build-test-push/action.yml b/.github/actions/build-test-push/action.yml index 78a017d60..767bb76bd 100644 --- a/.github/actions/build-test-push/action.yml +++ b/.github/actions/build-test-push/action.yml @@ -48,6 +48,7 @@ runs: uses: actions/checkout@v4 - name: Log in to Docker Hub + if: inputs.docker_hub_username != '' uses: docker/login-action@v3 with: username: ${{ inputs.docker_hub_username }} @@ -64,7 +65,7 @@ runs: --dry-run "${{ inputs.dry_run }}" - name: Run integration tests - if: ${{ inputs.skip_integrations_tests != 'true' }} + if: inputs.skip_integrations_tests != 'true' shell: bash run: | .github/actions/build-test-push/test.sh \ @@ -74,6 +75,7 @@ runs: --dry-run "${{ inputs.dry_run }}" - name: Push + if: inputs.docker_hub_username != '' shell: bash run: | .github/actions/build-test-push/push.sh \ diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index abccd254b..11e515fa1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -161,12 +161,29 @@ jobs: platform: linux/arm/v7 alternate_repository: snapshot - merge_clean_snapshot_tags: + merge_clean_snapshot_guard: needs: - snapshot_amd64 - snapshot_386 - snapshot_arm64 - snapshot_arm_v7 + name: Secrets access check + runs-on: ubuntu-latest + outputs: + continue: ${{ steps.check.outputs.continue }} + steps: + - name: Check + id: check + run: | + if [ -n "${{ secrets.DOCKERHUB_USERNAME }}" ]; then + echo "continue=true" >> "$GITHUB_OUTPUT" + else + echo "continue=false" >> "$GITHUB_OUTPUT" + fi + + merge_clean_snapshot_tags: + if: needs.merge_clean_snapshot_guard.outputs.continue == 'true' + needs: merge_clean_snapshot_guard name: Merge and clean snapshot tags runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pull-request-cleanup.yml b/.github/workflows/pull-request-cleanup.yml index dad5e1c33..4f3a9de67 100644 --- a/.github/workflows/pull-request-cleanup.yml +++ b/.github/workflows/pull-request-cleanup.yml @@ -11,6 +11,7 @@ jobs: cleanup: name: Cleanup Docker images runs-on: ubuntu-latest + continue-on-error: true steps: - name: Check out code uses: actions/checkout@v4 From 66317197b6e15ca9eba84f145455a5868958486f Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 14 May 2025 15:27:14 +0200 Subject: [PATCH 119/254] fix: graceful shutdown with asynchronous processes - fixes #1022 --- cmd/gotenberg.go | 5 +++- pkg/gotenberg/shutdown.go | 8 ++++++ pkg/modules/api/api.go | 43 ++++++++++++++++++++++++++++++- pkg/modules/webhook/middleware.go | 3 +++ pkg/modules/webhook/webhook.go | 15 ++++++++--- 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 pkg/gotenberg/shutdown.go diff --git a/cmd/gotenberg.go b/cmd/gotenberg.go index 5e456a5f8..8e5580646 100644 --- a/cmd/gotenberg.go +++ b/cmd/gotenberg.go @@ -2,6 +2,7 @@ package gotenbergcmd import ( "context" + "errors" "fmt" "os" "os/signal" @@ -173,7 +174,9 @@ func Run() { id := app.(gotenberg.Module).Descriptor().ID err = app.Stop(gracefulShutdownCtx) - if err != nil { + if errors.Is(err, gotenberg.ErrCancelGracefulShutdownContext) { + cancel() + } else if err != nil { return fmt.Errorf("stopping %s: %w", id, err) } diff --git a/pkg/gotenberg/shutdown.go b/pkg/gotenberg/shutdown.go new file mode 100644 index 000000000..053ed5d15 --- /dev/null +++ b/pkg/gotenberg/shutdown.go @@ -0,0 +1,8 @@ +package gotenberg + +import "errors" + +// ErrCancelGracefulShutdownContext tells that a module wants to abort a +// graceful shutdown and stops Gotenberg right away as there are no more +// ongoing processes. +var ErrCancelGracefulShutdownContext = errors.New("cancel graceful shutdown's context") diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index 7662fe384..72a831ced 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -48,6 +48,7 @@ type Api struct { externalMiddlewares []Middleware healthChecks []health.CheckerOption readyFn []func() error + asyncCounters []AsynchronousCounter fs *gotenberg.FileSystem logger *zap.Logger srv *echo.Echo @@ -166,6 +167,14 @@ type HealthChecker interface { Ready() error } +// AsynchronousCounter is a module interface that returns the number of active +// asynchronous requests. +// +// See https://github.com/gotenberg/gotenberg/issues/1022. +type AsynchronousCounter interface { + AsyncCount() int64 +} + // Descriptor returns an [Api]'s module descriptor. func (a *Api) Descriptor() gotenberg.ModuleDescriptor { return gotenberg.ModuleDescriptor{ @@ -307,6 +316,17 @@ func (a *Api) Provision(ctx *gotenberg.Context) error { a.readyFn = append(a.readyFn, healthChecker.Ready) } + // Get asynchronous counters. + mods, err = ctx.Modules(new(AsynchronousCounter)) + if err != nil { + return fmt.Errorf("get asynchronous counters: %w", err) + } + + a.asyncCounters = make([]AsynchronousCounter, len(mods)) + for i, asyncCounter := range mods { + a.asyncCounters[i] = asyncCounter.(AsynchronousCounter) + } + // Logger. loggerProvider, err := ctx.Module(new(gotenberg.LoggerProvider)) if err != nil { @@ -597,7 +617,28 @@ func (a *Api) StartupMessage() string { // Stop stops the HTTP server. func (a *Api) Stop(ctx context.Context) error { - return a.srv.Shutdown(ctx) + for { + count := int64(0) + for _, asyncCounter := range a.asyncCounters { + count += asyncCounter.AsyncCount() + } + select { + case <-ctx.Done(): + return a.srv.Shutdown(ctx) + default: + a.logger.Debug(fmt.Sprintf("%d asynchronous requests", count)) + if count > 0 { + time.Sleep(1 * time.Second) + continue + } + a.logger.Debug("no more asynchronous requests, continue with shutdown") + err := a.srv.Shutdown(ctx) + if err != nil { + return fmt.Errorf("shutdown: %w", err) + } + return gotenberg.ErrCancelGracefulShutdownContext + } + } } // Interface guards. diff --git a/pkg/modules/webhook/middleware.go b/pkg/modules/webhook/middleware.go index ab8363d79..ec2904f2e 100644 --- a/pkg/modules/webhook/middleware.go +++ b/pkg/modules/webhook/middleware.go @@ -173,10 +173,13 @@ func webhookMiddleware(w *Webhook) api.Middleware { } } + w.asyncCount.Add(1) + // As a webhook URL has been given, we handle the request in a // goroutine and return immediately. go func() { defer cancel() + defer w.asyncCount.Add(-1) // Call the next middleware in the chain. err := next(c) diff --git a/pkg/modules/webhook/webhook.go b/pkg/modules/webhook/webhook.go index 54661a20a..0ff6f3f74 100644 --- a/pkg/modules/webhook/webhook.go +++ b/pkg/modules/webhook/webhook.go @@ -1,6 +1,7 @@ package webhook import ( + "sync/atomic" "time" "github.com/dlclark/regexp2" @@ -25,6 +26,7 @@ type Webhook struct { retryMinWait time.Duration retryMaxWait time.Duration clientTimeout time.Duration + asyncCount atomic.Int64 disable bool } @@ -62,6 +64,7 @@ func (w *Webhook) Provision(ctx *gotenberg.Context) error { w.retryMaxWait = flags.MustDuration("webhook-retry-max-wait") w.clientTimeout = flags.MustDuration("webhook-client-timeout") w.disable = flags.MustBool("webhook-disable") + w.asyncCount.Store(0) return nil } @@ -77,9 +80,15 @@ func (w *Webhook) Middlewares() ([]api.Middleware, error) { }, nil } +// AsyncCount returns the number of asynchronous requests. +func (w *Webhook) AsyncCount() int64 { + return w.asyncCount.Load() +} + // Interface guards. var ( - _ gotenberg.Module = (*Webhook)(nil) - _ gotenberg.Provisioner = (*Webhook)(nil) - _ api.MiddlewareProvider = (*Webhook)(nil) + _ gotenberg.Module = (*Webhook)(nil) + _ gotenberg.Provisioner = (*Webhook)(nil) + _ api.MiddlewareProvider = (*Webhook)(nil) + _ api.AsynchronousCounter = (*Webhook)(nil) ) From 9aaab97d6acc254739a6c6b774988a8ef1990797 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 15 May 2025 09:22:52 +0200 Subject: [PATCH 120/254] fix(pdfengines): read metadata better webhook error - fixes #1148 --- pkg/modules/pdfengines/routes.go | 5 +++++ pkg/modules/webhook/middleware.go | 17 +++++++++++------ .../features/pdfengines_metadata.feature | 18 ++++++++++++++++++ test/integration/scenario/scenario.go | 18 +++--------------- test/integration/scenario/server.go | 14 +++++++++++--- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index ac800a237..a31614244 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -500,6 +500,11 @@ func readMetadataRoute(engine gotenberg.PdfEngine) api.Route { err = c.JSON(http.StatusOK, res) if err != nil { + if strings.Contains(err.Error(), "request method or response status code does not allow body") { + // High probability that the user is using the webhook + // feature. It does not make sense for this route. + return api.ErrNoOutputFile + } return fmt.Errorf("return JSON response: %w", err) } diff --git a/pkg/modules/webhook/middleware.go b/pkg/modules/webhook/middleware.go index ec2904f2e..4360d3442 100644 --- a/pkg/modules/webhook/middleware.go +++ b/pkg/modules/webhook/middleware.go @@ -184,11 +184,21 @@ func webhookMiddleware(w *Webhook) api.Middleware { // Call the next middleware in the chain. err := next(c) if err != nil { + if errors.Is(err, api.ErrNoOutputFile) { + errNoOutputFile := fmt.Errorf("%w - the webhook middleware cannot handle the result of this route", err) + handleAsyncError(api.WrapError( + errNoOutputFile, + api.NewSentinelHttpError( + http.StatusBadRequest, + "The webhook middleware can only work with multipart/form-data routes that results in output files", + ), + )) + return + } // The process failed for whatever reason. Let's send the // details to the webhook. ctx.Log().Error(err.Error()) handleAsyncError(err) - return } @@ -197,7 +207,6 @@ func webhookMiddleware(w *Webhook) api.Middleware { if err != nil { ctx.Log().Error(fmt.Sprintf("build output file: %s", err)) handleAsyncError(err) - return } @@ -205,7 +214,6 @@ func webhookMiddleware(w *Webhook) api.Middleware { if err != nil { ctx.Log().Error(fmt.Sprintf("open output file: %s", err)) handleAsyncError(err) - return } @@ -221,7 +229,6 @@ func webhookMiddleware(w *Webhook) api.Middleware { if err != nil { ctx.Log().Error(fmt.Sprintf("read header of output file: %s", err)) handleAsyncError(err) - return } @@ -229,7 +236,6 @@ func webhookMiddleware(w *Webhook) api.Middleware { if err != nil { ctx.Log().Error(fmt.Sprintf("get stat from output file: %s", err)) handleAsyncError(err) - return } @@ -237,7 +243,6 @@ func webhookMiddleware(w *Webhook) api.Middleware { if err != nil { ctx.Log().Error(fmt.Sprintf("reset output file reader: %s", err)) handleAsyncError(err) - return } diff --git a/test/integration/features/pdfengines_metadata.feature b/test/integration/features/pdfengines_metadata.feature index 1cbb0ee11..19dd0b8f0 100644 --- a/test/integration/features/pdfengines_metadata.feature +++ b/test/integration/features/pdfengines_metadata.feature @@ -233,6 +233,24 @@ Feature: /forms/pdfengines/{write|read} } """ + Scenario: POST /forms/pdfengines/metadata/read (Webhook) + Given I have a default Gotenberg container + Given I have a webhook server + 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/page_1.pdf | file | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + Then the response status code should be 204 + When I wait for the asynchronous request to the webhook + Then the webhook request header "Content-Type" should be "application/json" + Then the webhook request body should match JSON: + """ + { + "status": 400, + "message": "the webhook middleware can only work with multipart/form-data routes that results in output files" + } + """ + Scenario: POST /forms/pdfengines/metadata/write (Basic Auth) Given I have a Gotenberg container with the following environment variable(s): | API_ENABLE_BASIC_AUTH | true | diff --git a/test/integration/scenario/scenario.go b/test/integration/scenario/scenario.go index b76e739d4..a8f31a937 100644 --- a/test/integration/scenario/scenario.go +++ b/test/integration/scenario/scenario.go @@ -407,11 +407,7 @@ func (s *scenario) theBodyShouldMatchString(kind string, expectedDoc *godog.DocS } else if s.server.req == nil { return errors.New("no webhook request found") } else { - body, err := io.ReadAll(s.server.req.Body) - if err != nil { - return fmt.Errorf("read request body: %w", err) - } - actual = string(body) + actual = string(s.server.bodyCopy) } expected := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion) @@ -431,11 +427,7 @@ func (s *scenario) theBodyShouldContainString(kind string, expectedDoc *godog.Do } else if s.server.req == nil { return errors.New("no webhook request found") } else { - body, err := io.ReadAll(s.server.req.Body) - if err != nil { - return fmt.Errorf("read request body: %w", err) - } - actual = string(body) + actual = string(s.server.bodyCopy) } expected := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion) @@ -455,11 +447,7 @@ func (s *scenario) theBodyShouldMatchJSON(kind string, expectedDoc *godog.DocStr } else if s.server.req == nil { return errors.New("no webhook request found") } else { - b, err := io.ReadAll(s.server.req.Body) - if err != nil { - return fmt.Errorf("read request body: %w", err) - } - body = b + body = s.server.bodyCopy } var expected, actual interface{} diff --git a/test/integration/scenario/server.go b/test/integration/scenario/server.go index d49121133..861d70fb8 100644 --- a/test/integration/scenario/server.go +++ b/test/integration/scenario/server.go @@ -19,9 +19,10 @@ import ( ) type server struct { - srv *echo.Echo - req *http.Request - errChan chan error + srv *echo.Echo + req *http.Request + bodyCopy []byte + errChan chan error } func newServer(ctx context.Context, workdir string) (*server, error) { @@ -51,6 +52,8 @@ func newServer(ctx context.Context, workdir string) (*server, error) { return webhookErr(fmt.Errorf("read request body: %w", err)) } + s.bodyCopy = body + cd := s.req.Header.Get("Content-Disposition") if cd == "" { return webhookErr(fmt.Errorf("no Content-Disposition header")) @@ -125,6 +128,11 @@ func newServer(ctx context.Context, workdir string) (*server, error) { } webhookErrorHandler := func(c echo.Context) error { s.req = c.Request() + body, err := io.ReadAll(s.req.Body) + if err != nil { + return webhookErr(fmt.Errorf("read request body: %w", err)) + } + s.bodyCopy = body return webhookErr(c.String(http.StatusOK, http.StatusText(http.StatusOK))) } From 790b69a32e34711f426763834114e90c169861f6 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 15 May 2025 10:16:57 +0200 Subject: [PATCH 121/254] test(integration): fix typo --- test/integration/features/pdfengines_metadata.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/features/pdfengines_metadata.feature b/test/integration/features/pdfengines_metadata.feature index 19dd0b8f0..9c75588d3 100644 --- a/test/integration/features/pdfengines_metadata.feature +++ b/test/integration/features/pdfengines_metadata.feature @@ -247,7 +247,7 @@ Feature: /forms/pdfengines/{write|read} """ { "status": 400, - "message": "the webhook middleware can only work with multipart/form-data routes that results in output files" + "message": "The webhook middleware can only work with multipart/form-data routes that results in output files" } """ From e662f5ce0b8fa4251c2b5c22b2b5fb7bd9545cbf Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 15 May 2025 10:38:22 +0200 Subject: [PATCH 122/254] fix(libreoffice): correct values for CreateDate, MetadataDate, and ModifyDate metadata entries - fixes #1193 --- pkg/modules/libreoffice/routes.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index ceaf282ec..e83a87fcd 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -6,6 +6,7 @@ import ( "net/http" "slices" "strconv" + "time" "github.com/labstack/echo/v4" @@ -251,6 +252,20 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } + // See https://github.com/gotenberg/gotenberg/issues/1193. + if len(metadata) == 0 { + metadata = make(map[string]interface{}) + } + if _, ok := metadata["CreateDate"]; !ok { + metadata["CreateDate"] = time.Now().Format("2006-01-02T15:04:05-07:00") + } + if _, ok := metadata["MetadataDate"]; !ok { + metadata["MetadataDate"] = time.Now().Format("2006-01-02T15:04:05-07:00") + } + if _, ok := metadata["ModifyDate"]; !ok { + metadata["ModifyDate"] = time.Now().Format("2006-01-02T15:04:05-07:00") + } + err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) if err != nil { return fmt.Errorf("write metadata: %w", err) From de1b887d2951cd9bf908cd531c873c0e2eac608e Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 15 May 2025 14:23:31 +0200 Subject: [PATCH 123/254] test(integration): fix rules failed toleration --- test/integration/features/libreoffice_convert.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature index 8a96a936f..daa13e410 100644 --- a/test/integration/features/libreoffice_convert.feature +++ b/test/integration/features/libreoffice_convert.feature @@ -447,7 +447,7 @@ Feature: /forms/libreoffice/convert 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 valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 2 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) Scenario: POST /forms/libreoffice/convert (Split & PDF/A-1b & PDF/UA-1) @@ -478,7 +478,7 @@ Feature: /forms/libreoffice/convert """ Page 3 """ - Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 2 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) Scenario: POST /forms/libreoffice/convert (Metadata) From 5aa1cd7f401b66db3dc18c114e9e78c245cf11b9 Mon Sep 17 00:00:00 2001 From: Hector Zarate Date: Sun, 18 May 2025 14:08:11 +0200 Subject: [PATCH 124/254] feat(Dockerfile): pin versions of dependencies (#1190) * Pin versions of dependencies * no need to override default values in github build action * unpin curl,gnupg,tiny,python3,default-jre-headless * Revert "unpin curl,gnupg,tiny,python3,default-jre-headless" This reverts commit 95b0250700657ab28740b8030b658b6b6b680187. * remove pin from Libreoffice * Update build/Dockerfile Co-authored-by: Julien Neuhart --------- Co-authored-by: Julien Neuhart --- .env | 9 +- .github/actions/build-test-push/build.sh | 6 -- Makefile | 7 -- build/Dockerfile | 117 ++++++++++++----------- 4 files changed, 61 insertions(+), 78 deletions(-) diff --git a/.env b/.env index 7922de9fd..7179b1f08 100644 --- a/.env +++ b/.env @@ -1,13 +1,6 @@ -GOLANG_VERSION=1.24 +GOTENBERG_VERSION=snapshot DOCKER_REGISTRY=gotenberg DOCKER_REPOSITORY=gotenberg -GOTENBERG_VERSION=snapshot -GOTENBERG_USER_GID=1001 -GOTENBERG_USER_UID=1001 -NOTO_COLOR_EMOJI_VERSION=v2.047 # See https://github.com/googlefonts/noto-emoji/releases. -PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. -PDFCPU_VERSION=v0.8.1 # See https://github.com/pdfcpu/pdfcpu/releases. -GOTENBERG_VERSION=snapshot DOCKERFILE=build/Dockerfile DOCKERFILE_CLOUDRUN=build/Dockerfile.cloudrun DOCKER_BUILD_CONTEXT='.' diff --git a/.github/actions/build-test-push/build.sh b/.github/actions/build-test-push/build.sh index 410d3abde..170ba5744 100755 --- a/.github/actions/build-test-push/build.sh +++ b/.github/actions/build-test-push/build.sh @@ -129,13 +129,7 @@ join() { no_arch_tag="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version" cmd="docker buildx build \ - --build-arg GOLANG_VERSION=$GOLANG_VERSION \ --build-arg GOTENBERG_VERSION=$version \ - --build-arg GOTENBERG_USER_GID=$GOTENBERG_USER_GID \ - --build-arg GOTENBERG_USER_UID=$GOTENBERG_USER_UID \ - --build-arg NOTO_COLOR_EMOJI_VERSION=$NOTO_COLOR_EMOJI_VERSION \ - --build-arg PDFTK_VERSION=$PDFTK_VERSION \ - --build-arg PDFCPU_VERSION=$PDFCPU_VERSION \ --platform $platform \ --load \ ${tags_flags[*]} \ diff --git a/Makefile b/Makefile index 52b8847d6..c8af55939 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,6 @@ help: ## Show the help .PHONY: build build: ## Build the Gotenberg's Docker image docker build \ - --build-arg GOLANG_VERSION=$(GOLANG_VERSION) \ - --build-arg GOTENBERG_VERSION=$(GOTENBERG_VERSION) \ - --build-arg GOTENBERG_USER_GID=$(GOTENBERG_USER_GID) \ - --build-arg GOTENBERG_USER_UID=$(GOTENBERG_USER_UID) \ - --build-arg NOTO_COLOR_EMOJI_VERSION=$(NOTO_COLOR_EMOJI_VERSION) \ - --build-arg PDFTK_VERSION=$(PDFTK_VERSION) \ - --build-arg PDFCPU_VERSION=$(PDFCPU_VERSION) \ -t $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION) \ -f $(DOCKERFILE) $(DOCKER_BUILD_CONTEXT) diff --git a/build/Dockerfile b/build/Dockerfile index 699405466..96499bf3f 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,7 +1,7 @@ # ARG instructions do not create additional layers. Instead, next layers will # concatenate them. Also, we have to repeat ARG instructions in each build # stage that uses them. -ARG GOLANG_VERSION +ARG GOLANG_VERSION=1.24 # ---------------------------------------------- # pdfcpu binary build stage @@ -10,7 +10,8 @@ ARG GOLANG_VERSION # default. FROM golang:$GOLANG_VERSION AS pdfcpu-binary-stage -ARG PDFCPU_VERSION +# See https://github.com/pdfcpu/pdfcpu/releases. +ARG PDFCPU_VERSION=v0.8.1 ENV CGO_ENABLED=0 # Define the working directory outside of $GOPATH (we're using go modules). @@ -32,7 +33,7 @@ RUN go build -o pdfcpu -ldflags "-s -w -X 'main.version=$PDFCPU_VERSION' -X 'git # ---------------------------------------------- FROM golang:$GOLANG_VERSION AS gotenberg-binary-stage -ARG GOTENBERG_VERSION +ARG GOTENBERG_VERSION=snapshot ENV CGO_ENABLED=0 # Define the working directory outside of $GOPATH (we're using go modules). @@ -105,18 +106,20 @@ ENV PATH="/opt/java/bin:${PATH}" # ---------------------------------------------- FROM base-image-stage -ARG GOTENBERG_VERSION -ARG GOTENBERG_USER_GID -ARG GOTENBERG_USER_UID -ARG NOTO_COLOR_EMOJI_VERSION -ARG PDFTK_VERSION +ARG GOTENBERG_VERSION=snapshot +ARG GOTENBERG_USER_GID=1001 +ARG GOTENBERG_USER_UID=1001 +# See https://github.com/googlefonts/noto-emoji/releases. +ARG NOTO_COLOR_EMOJI_VERSION=v2.047 +# See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. +ARG PDFTK_VERSION=v3.3.3 LABEL org.opencontainers.image.title="Gotenberg" \ - org.opencontainers.image.description="A containerized API for seamless PDF conversion." \ - org.opencontainers.image.version="$GOTENBERG_VERSION" \ - org.opencontainers.image.authors="Julien Neuhart " \ - org.opencontainers.image.documentation="https://gotenberg.dev" \ - org.opencontainers.image.source="https://github.com/gotenberg/gotenberg" + org.opencontainers.image.description="A containerized API for seamless PDF conversion." \ + org.opencontainers.image.version="$GOTENBERG_VERSION" \ + org.opencontainers.image.authors="Julien Neuhart " \ + org.opencontainers.image.documentation="https://gotenberg.dev" \ + org.opencontainers.image.source="https://github.com/gotenberg/gotenberg" RUN \ # Create a non-root user. @@ -131,7 +134,7 @@ RUN \ # Note: tini is a helper for reaping zombie processes. apt-get update -qq &&\ apt-get upgrade -yqq &&\ - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends curl gnupg tini python3 &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends curl gnupg=2.2.40-1.1 tini=0.19.0-1 python3=3.11.2-1+b1 &&\ # Cleanup. # Note: the Debian image does automatically a clean after each install thanks to a hook. # Therefore, there is no need for apt-get clean. @@ -148,40 +151,40 @@ RUN \ apt-get upgrade -yqq &&\ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \ ./ttf-mscorefonts-installer_3.8.1_all.deb \ - culmus \ - fonts-beng \ - fonts-hosny-amiri \ - fonts-lklug-sinhala \ - fonts-lohit-guru \ - fonts-lohit-knda \ - fonts-samyak-gujr \ - fonts-samyak-mlym \ - fonts-samyak-taml \ - fonts-sarai \ - fonts-sil-abyssinica \ - fonts-sil-padauk \ - fonts-telu \ - fonts-thai-tlwg \ + culmus=0.133-1 \ + fonts-beng=2:1.3 \ + fonts-hosny-amiri=0.113-1 \ + fonts-lklug-sinhala=0.6-4 \ + fonts-lohit-guru=2.91.2-3 \ + fonts-lohit-knda=2.5.4-3 \ + fonts-samyak-gujr=1.2.2-6 \ + fonts-samyak-mlym=1.2.2-6 \ + fonts-samyak-taml=1.2.2-6 \ + fonts-sarai=1.0-3 \ + fonts-sil-abyssinica=2.100-3 \ + fonts-sil-padauk=5.000-3 \ + fonts-telu=2:1.3 \ + fonts-thai-tlwg=1:0.7.3-1 \ ttf-wqy-zenhei \ - fonts-arphic-ukai \ - fonts-arphic-uming \ - fonts-ipafont-mincho \ - fonts-ipafont-gothic \ - fonts-unfonts-core \ + fonts-arphic-ukai=0.2.20080216.2-5 \ + fonts-arphic-uming=0.2.20080216.2-11 \ + fonts-ipafont-mincho=00303-23 \ + fonts-ipafont-gothic=00303-23 \ + fonts-unfonts-core=1:1.0.2-080608-18 \ # LibreOffice recommends. - fonts-crosextra-caladea \ - fonts-crosextra-carlito \ - fonts-dejavu \ - fonts-dejavu-extra \ - fonts-liberation \ - fonts-liberation2 \ - fonts-linuxlibertine \ - fonts-noto-cjk \ - fonts-noto-core \ - fonts-noto-mono \ - fonts-noto-ui-core \ - fonts-sil-gentium \ - fonts-sil-gentium-basic &&\ + fonts-crosextra-caladea=20200211-1 \ + fonts-crosextra-carlito=20220224-1 \ + fonts-dejavu=2.37-6 \ + fonts-dejavu-extra=2.37-6 \ + fonts-liberation=1:1.07.4-11 \ + fonts-liberation2=2.1.5-1 \ + fonts-linuxlibertine=5.3.0-6 \ + fonts-noto-cjk=1:20220127+repack1-1 \ + fonts-noto-core=20201225-1 \ + fonts-noto-mono=20201225-1 \ + fonts-noto-ui-core=20201225-1 \ + fonts-sil-gentium=20081126:1.03-4 \ + fonts-sil-gentium-basic=1.102-1.1 &&\ rm -f ./ttf-mscorefonts-installer_3.8.1_all.deb &&\ # Add Color and Black-and-White Noto emoji font. # Credits: @@ -198,16 +201,16 @@ RUN \ /bin/bash -c \ 'set -e &&\ if [[ "$(dpkg --print-architecture)" == "amd64" ]]; then \ - curl https://dl.google.com/linux/linux_signing_key.pub | apt-key add - &&\ - echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | tee /etc/apt/sources.list.d/google-chrome.list &&\ - apt-get update -qq &&\ - apt-get upgrade -yqq &&\ - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends --allow-unauthenticated google-chrome-stable &&\ - mv /usr/bin/google-chrome-stable /usr/bin/chromium; \ + curl https://dl.google.com/linux/linux_signing_key.pub | apt-key add - &&\ + echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | tee /etc/apt/sources.list.d/google-chrome.list &&\ + apt-get update -qq &&\ + apt-get upgrade -yqq &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends --allow-unauthenticated google-chrome-stable &&\ + mv /usr/bin/google-chrome-stable /usr/bin/chromium; \ else \ - apt-get update -qq &&\ - apt-get upgrade -yqq &&\ - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends chromium; \ + apt-get update -qq &&\ + apt-get upgrade -yqq &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends chromium; \ fi' &&\ # Verify installation. chromium --version &&\ @@ -242,11 +245,11 @@ RUN \ # See https://github.com/gotenberg/gotenberg/pull/273. curl -o /usr/bin/pdftk-all.jar "https://gitlab.com/api/v4/projects/5024297/packages/generic/pdftk-java/$PDFTK_VERSION/pdftk-all.jar" &&\ chmod a+x /usr/bin/pdftk-all.jar &&\ - echo '#!/bin/bash\n\nexec java -jar /usr/bin/pdftk-all.jar "$@"' > /usr/bin/pdftk && \ + printf '#!/bin/bash\n\nexec java -jar /usr/bin/pdftk-all.jar "$@"' > /usr/bin/pdftk && \ chmod +x /usr/bin/pdftk &&\ apt-get update -qq &&\ apt-get upgrade -yqq &&\ - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends qpdf exiftool &&\ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends qpdf=11.3.0-1+deb12u1 exiftool &&\ # See https://github.com/nextcloud/docker/issues/380. mkdir -p /usr/share/man/man1 &&\ # Verify installations. From e1870429b9a98ae83639fbf574d93959f856e7ce Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sun, 18 May 2025 14:15:12 +0200 Subject: [PATCH 125/254] Revert "test(integration): fix rules failed toleration" This reverts commit de1b887d2951cd9bf908cd531c873c0e2eac608e. --- test/integration/features/libreoffice_convert.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature index daa13e410..8a96a936f 100644 --- a/test/integration/features/libreoffice_convert.feature +++ b/test/integration/features/libreoffice_convert.feature @@ -447,7 +447,7 @@ Feature: /forms/libreoffice/convert 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 valid "PDF/A-1b" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) Scenario: POST /forms/libreoffice/convert (Split & PDF/A-1b & PDF/UA-1) @@ -478,7 +478,7 @@ Feature: /forms/libreoffice/convert """ Page 3 """ - Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 2 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) Scenario: POST /forms/libreoffice/convert (Metadata) From f13a6c0a3b9be1d808715dacc3231c42f8857ac9 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sun, 18 May 2025 14:15:22 +0200 Subject: [PATCH 126/254] Revert "fix(libreoffice): correct values for CreateDate, MetadataDate, and ModifyDate metadata entries - fixes #1193" This reverts commit e662f5ce0b8fa4251c2b5c22b2b5fb7bd9545cbf. --- pkg/modules/libreoffice/routes.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index e83a87fcd..ceaf282ec 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -6,7 +6,6 @@ import ( "net/http" "slices" "strconv" - "time" "github.com/labstack/echo/v4" @@ -252,20 +251,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } - // See https://github.com/gotenberg/gotenberg/issues/1193. - if len(metadata) == 0 { - metadata = make(map[string]interface{}) - } - if _, ok := metadata["CreateDate"]; !ok { - metadata["CreateDate"] = time.Now().Format("2006-01-02T15:04:05-07:00") - } - if _, ok := metadata["MetadataDate"]; !ok { - metadata["MetadataDate"] = time.Now().Format("2006-01-02T15:04:05-07:00") - } - if _, ok := metadata["ModifyDate"]; !ok { - metadata["ModifyDate"] = time.Now().Format("2006-01-02T15:04:05-07:00") - } - err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) if err != nil { return fmt.Errorf("write metadata: %w", err) From 67ebe17fcbcd40980a3803cdf1b193d02f09111e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 02:26:33 +0000 Subject: [PATCH 127/254] chore(deps): bump github.com/mholt/archives from 0.1.1 to 0.1.2 Bumps [github.com/mholt/archives](https://github.com/mholt/archives) from 0.1.1 to 0.1.2. - [Release notes](https://github.com/mholt/archives/releases) - [Commits](https://github.com/mholt/archives/compare/v0.1.1...v0.1.2) --- updated-dependencies: - dependency-name: github.com/mholt/archives dependency-version: 0.1.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 08b2822b7..38bd711f2 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.0 require ( github.com/alexliesenfeld/health v0.8.0 - github.com/andybalholm/brotli v1.1.1 // indirect + github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect github.com/barasher/go-exiftool v1.10.0 github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a github.com/chromedp/chromedp v0.13.6 @@ -37,7 +37,7 @@ require ( github.com/dlclark/regexp2 v1.11.5 github.com/docker/docker v28.1.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/mholt/archives v0.1.1 + github.com/mholt/archives v0.1.2 github.com/shirou/gopsutil/v4 v4.25.4 github.com/testcontainers/testcontainers-go v0.37.0 ) diff --git a/go.sum b/go.sum index 1c9df313a..6865c6e12 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/alexliesenfeld/health v0.8.0 h1:lCV0i+ZJPTbqP7LfKG7p3qZBl5VhelwUFCIVWl77fgk= github.com/alexliesenfeld/health v0.8.0/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/barasher/go-exiftool v1.10.0 h1:f5JY5jc42M7tzR6tbL9508S2IXdIcG9QyieEXNMpIhs= @@ -219,8 +219,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY= -github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I= +github.com/mholt/archives v0.1.2 h1:UBSe5NfYKHI1sy+S5dJsEsG9jsKKk8NJA4HCC+xTI4A= +github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprUOwZWDs= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= From ce155fb43e6a516cd19162556e397ec9e13c0fa5 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 20 May 2025 11:49:10 +0200 Subject: [PATCH 128/254] feat(chromium): add generateTaggedPdf to form fields - closes #1210 --- pkg/modules/chromium/chromium.go | 5 +++++ pkg/modules/chromium/routes.go | 5 ++++- pkg/modules/chromium/tasks.go | 3 ++- test/integration/features/chromium_convert_html.feature | 3 ++- test/integration/features/chromium_convert_markdown.feature | 3 ++- test/integration/features/chromium_convert_url.feature | 3 ++- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/modules/chromium/chromium.go b/pkg/modules/chromium/chromium.go index 5b2290acc..2780e9f65 100644 --- a/pkg/modules/chromium/chromium.go +++ b/pkg/modules/chromium/chromium.go @@ -220,6 +220,10 @@ type PdfOptions struct { // GenerateDocumentOutline defines whether the document outline should be // embedded into the PDF. GenerateDocumentOutline bool + + // GenerateTaggedPdf defines whether to generate tagged (accessible) + // PDF. + GenerateTaggedPdf bool } // DefaultPdfOptions returns the default values for PdfOptions. @@ -241,6 +245,7 @@ func DefaultPdfOptions() PdfOptions { FooterTemplate: "", PreferCssPageSize: false, GenerateDocumentOutline: false, + GenerateTaggedPdf: false, } } diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index d176dc926..633c90a73 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -207,6 +207,7 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) { headerTemplate, footerTemplate string preferCssPageSize bool generateDocumentOutline bool + generateTaggedPdf bool ) form. @@ -224,7 +225,8 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) { Content("header.html", &headerTemplate, defaultPdfOptions.HeaderTemplate). Content("footer.html", &footerTemplate, defaultPdfOptions.FooterTemplate). Bool("preferCssPageSize", &preferCssPageSize, defaultPdfOptions.PreferCssPageSize). - Bool("generateDocumentOutline", &generateDocumentOutline, defaultPdfOptions.GenerateDocumentOutline) + Bool("generateDocumentOutline", &generateDocumentOutline, defaultPdfOptions.GenerateDocumentOutline). + Bool("generateTaggedPdf", &generateTaggedPdf, defaultPdfOptions.GenerateTaggedPdf) pdfOptions := PdfOptions{ Options: options, @@ -243,6 +245,7 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) { FooterTemplate: footerTemplate, PreferCssPageSize: preferCssPageSize, GenerateDocumentOutline: generateDocumentOutline, + GenerateTaggedPdf: generateTaggedPdf, } return form, pdfOptions diff --git a/pkg/modules/chromium/tasks.go b/pkg/modules/chromium/tasks.go index 1e2eb7175..c6c46ed45 100644 --- a/pkg/modules/chromium/tasks.go +++ b/pkg/modules/chromium/tasks.go @@ -49,7 +49,8 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti WithPageRanges(pageRanges). WithPreferCSSPageSize(options.PreferCssPageSize). WithGenerateDocumentOutline(options.GenerateDocumentOutline). - WithGenerateTaggedPDF(false) + // See https://github.com/gotenberg/gotenberg/issues/1210. + WithGenerateTaggedPDF(options.GenerateTaggedPdf) hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate || options.FooterTemplate != DefaultPdfOptions().FooterTemplate diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index 934ba6ff4..d136b75f7 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -393,6 +393,7 @@ Feature: /forms/chromium/convert/html | marginRight | foo | field | | preferCssPageSize | foo | field | | generateDocumentOutline | foo | field | + | generateTaggedPdf | foo | field | | printBackground | foo | field | | omitBackground | foo | field | | landscape | foo | field | @@ -408,7 +409,7 @@ Feature: /forms/chromium/convert/html Then the response header "Content-Type" should be "text/plain; charset=UTF-8" Then the response body should match string: """ - Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form file 'index.html' is required + Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateTaggedPdf' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form file 'index.html' is required """ 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 | diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index 584403ada..b6efe4a4b 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -469,6 +469,7 @@ Feature: /forms/chromium/convert/markdown | marginRight | foo | field | | preferCssPageSize | foo | field | | generateDocumentOutline | foo | field | + | generateTaggedPdf | foo | field | | printBackground | foo | field | | omitBackground | foo | field | | landscape | foo | field | @@ -484,7 +485,7 @@ Feature: /forms/chromium/convert/markdown Then the response header "Content-Type" should be "text/plain; charset=UTF-8" Then the response body should match string: """ - Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form file 'index.html' is required; no form file found for extensions: [.md] + Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateTaggedPdf' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form file 'index.html' is required; no form file found for extensions: [.md] """ 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 | diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index 9c1052471..7847d9d0e 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -451,6 +451,7 @@ Feature: /forms/chromium/convert/url | marginRight | foo | field | | preferCssPageSize | foo | field | | generateDocumentOutline | foo | field | + | generateTaggedPdf | foo | field | | printBackground | foo | field | | omitBackground | foo | field | | landscape | foo | field | @@ -466,7 +467,7 @@ Feature: /forms/chromium/convert/url Then the response header "Content-Type" should be "text/plain; charset=UTF-8" Then the response body should match string: """ - Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'url' is required + Invalid form data: form field 'skipNetworkIdleEvent' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceHttpStatusCodes' is invalid (got 'foo', resulting to unmarshal failOnResourceHttpStatusCodes: invalid character 'o' in literal false (expecting 'a')); form field 'failOnResourceLoadingFailed' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'failOnConsoleExceptions' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'waitDelay' is invalid (got 'foo', resulting to time: invalid duration "foo"); form field 'emulatedMediaType' is invalid (got 'foo', resulting to wrong value, expected either 'screen', 'print' or empty); form field 'omitBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'landscape' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'printBackground' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'scale' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'singlePage' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'paperWidth' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'paperHeight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginTop' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginBottom' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginLeft' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'marginRight' is invalid (got 'foo', resulting to strconv.ParseFloat: parsing "foo": invalid syntax); form field 'preferCssPageSize' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateDocumentOutline' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'generateTaggedPdf' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax); form field 'url' is required """ Given I have a static server When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): From 1d8630810b516d5b99bdd9c18d20189b25acfe25 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 20 May 2025 11:50:18 +0200 Subject: [PATCH 129/254] fix(godoc): typo --- pkg/modules/chromium/chromium.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/modules/chromium/chromium.go b/pkg/modules/chromium/chromium.go index 2780e9f65..22432633a 100644 --- a/pkg/modules/chromium/chromium.go +++ b/pkg/modules/chromium/chromium.go @@ -73,7 +73,7 @@ var ( ) // Chromium is a module that provides both an [Api] and routes for converting -// HTML document to PDF. +// an HTML document to PDF. type Chromium struct { autoStart bool disableRoutes bool From f75912033e72b62e175700609740776ce5327b2c Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 20 May 2025 14:14:05 +0200 Subject: [PATCH 130/254] feat(chromium): split filenames can be controlled with the Gotenberg-Output-Filename header - closes #1130 --- pkg/modules/api/context.go | 7 ++ pkg/modules/chromium/routes.go | 3 + .../features/chromium_convert_html.feature | 64 +++++++++++++++++ .../chromium_convert_markdown.feature | 70 +++++++++++++++++++ .../features/chromium_convert_url.feature | 66 +++++++++++++++++ 5 files changed, 210 insertions(+) diff --git a/pkg/modules/api/context.go b/pkg/modules/api/context.go index 37c3a3707..576c3176d 100644 --- a/pkg/modules/api/context.go +++ b/pkg/modules/api/context.go @@ -409,6 +409,13 @@ func (ctx *Context) GeneratePath(extension string) string { return fmt.Sprintf("%s/%s%s", ctx.dirPath, uuid.New().String(), extension) } +// GeneratePathFromFilename generates a path within the context's working +// directory, using the given filename (with extension). It does not create +// a file. +func (ctx *Context) GeneratePathFromFilename(filename string) string { + return fmt.Sprintf("%s/%s", ctx.dirPath, filename) +} + // CreateSubDirectory creates a subdirectory within the context's working // directory. func (ctx *Context) CreateSubDirectory(dirName string) (string, error) { diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index 633c90a73..ee9c2ca1c 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -601,6 +601,9 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string) func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]interface{}) error { outputPath := ctx.GeneratePath(".pdf") + // See https://github.com/gotenberg/gotenberg/issues/1130. + filename := ctx.OutputFilename(outputPath) + outputPath = ctx.GeneratePathFromFilename(filename) err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options) err = handleChromiumError(err, options.Options) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index d136b75f7..ab93e3480 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -582,6 +582,36 @@ Feature: /forms/chromium/convert/html Page 3 """ + # See https://github.com/gotenberg/gotenberg/issues/1130. + Scenario: POST /forms/chromium/convert/html (Split Output 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/pages-3-html/index.html | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | foo_0.pdf | + | foo_1.pdf | + Then the "foo_0.pdf" PDF should have 2 page(s) + Then the "foo_1.pdf" PDF should have 1 page(s) + Then the "foo_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "foo_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Scenario: POST /forms/chromium/convert/html (Split Pages) 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): @@ -704,6 +734,40 @@ Feature: /forms/chromium/convert/html Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + # See https://github.com/gotenberg/gotenberg/issues/1130. + Scenario: POST /forms/chromium/convert/html (Split & PDF/A-1b & PDF/UA-1 & Output 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/pages-3-html/index.html | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | foo_0.pdf | + | foo_1.pdf | + Then the "foo_0.pdf" PDF should have 2 page(s) + Then the "foo_1.pdf" PDF should have 1 page(s) + Then the "foo_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "foo_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Scenario: POST /forms/chromium/convert/html (Metadata) 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): diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index b6efe4a4b..b8d84c28c 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -676,6 +676,39 @@ Feature: /forms/chromium/convert/markdown Page 3 """ + # See https://github.com/gotenberg/gotenberg/issues/1130. + Scenario: POST /forms/chromium/convert/markdown (Split Output 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + | files | testdata/pages-3-markdown/page_2.md | file | + | files | testdata/pages-3-markdown/page_3.md | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | foo_0.pdf | + | foo_1.pdf | + Then the "foo_0.pdf" PDF should have 2 page(s) + Then the "foo_1.pdf" PDF should have 1 page(s) + Then the "foo_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "foo_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Scenario: POST /forms/chromium/convert/markdown (Split Pages) 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): @@ -820,6 +853,43 @@ Feature: /forms/chromium/convert/markdown Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + # See https://github.com/gotenberg/gotenberg/issues/1130. + Scenario: POST /forms/chromium/convert/markdown (Split & PDF/A-1b & PDF/UA-1 & Output 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/pages-3-markdown/index.html | file | + | files | testdata/pages-3-markdown/page_1.md | file | + | files | testdata/pages-3-markdown/page_2.md | file | + | files | testdata/pages-3-markdown/page_3.md | file | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | foo_0.pdf | + | foo_1.pdf | + Then the "foo_0.pdf" PDF should have 2 page(s) + Then the "foo_1.pdf" PDF should have 1 page(s) + Then the "foo_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "foo_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Scenario: POST /forms/chromium/convert/markdown (Metadata) 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): diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index 7847d9d0e..4c16dc259 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -656,6 +656,37 @@ Feature: /forms/chromium/convert/url Page 3 """ + # See https://github.com/gotenberg/gotenberg/issues/1130. + Scenario: POST /forms/chromium/convert/url (Split Output Filename) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-3-html/index.html | field | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | foo_0.pdf | + | foo_1.pdf | + Then the "foo_0.pdf" PDF should have 2 page(s) + Then the "foo_1.pdf" PDF should have 1 page(s) + Then the "foo_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "foo_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Scenario: POST /forms/chromium/convert/url (Split Pages) Given I have a default Gotenberg container Given I have a static server @@ -783,6 +814,41 @@ Feature: /forms/chromium/convert/url Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + # See https://github.com/gotenberg/gotenberg/issues/1130. + Scenario: POST /forms/chromium/convert/url (Split & PDF/A-1b & PDF/UA-1 & Output Filename) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + | url | http://host.docker.internal:%d/html/testdata/pages-3-html/index.html | field | + | splitMode | intervals | field | + | splitSpan | 2 | field | + | pdfa | PDF/A-1b | field | + | pdfua | true | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + Then there should be the following file(s) in the response: + | foo.zip | + | foo_0.pdf | + | foo_1.pdf | + Then the "foo_0.pdf" PDF should have 2 page(s) + Then the "foo_1.pdf" PDF should have 1 page(s) + Then the "foo_0.pdf" PDF should have the following content at page 1: + """ + Page 1 + """ + Then the "foo_0.pdf" PDF should have the following content at page 2: + """ + Page 2 + """ + Then the "foo_1.pdf" PDF should have the following content at page 1: + """ + Page 3 + """ + Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s) + Scenario: POST /forms/chromium/convert/url (Metadata) Given I have a default Gotenberg container Given I have a static server From 61c8217ed5ac762134eb7dbd62d2cbda5ba55b65 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 20 May 2025 15:07:05 +0200 Subject: [PATCH 131/254] feat(Dockerfile): support for arbitrary user ids - closes #1049 --- build/Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build/Dockerfile b/build/Dockerfile index 96499bf3f..11dd1aa57 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -259,6 +259,15 @@ RUN \ # Cleanup. rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Support for arbitrary user ids (OpenShift). +# See: +# https://github.com/gotenberg/gotenberg/issues/1049. +# https://docs.redhat.com/en/documentation/openshift_container_platform/4.15/html/images/creating-images#use-uid_create-images. +RUN \ + usermod -aG root gotenberg &&\ + chgrp -R 0 /home/gotenberg &&\ + chmod -R g=u /home/gotenberg + # Improve fonts subpixel hinting and smoothing. # Credits: # https://github.com/arachnys/athenapdf/issues/69. From 8875f4dfc41fbda84d3ae932404f88f52951fb5f Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 20 May 2025 15:43:36 +0200 Subject: [PATCH 132/254] ci: fix needs entries in merge_clean_snapshot_tags step --- .github/workflows/continuous-integration.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 11e515fa1..b13dec5d6 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -183,7 +183,12 @@ jobs: merge_clean_snapshot_tags: if: needs.merge_clean_snapshot_guard.outputs.continue == 'true' - needs: merge_clean_snapshot_guard + needs: + - merge_clean_snapshot_guard + - snapshot_amd64 + - snapshot_386 + - snapshot_arm64 + - snapshot_arm_v7 name: Merge and clean snapshot tags runs-on: ubuntu-latest steps: From a50e60b2a00fc4af8c97bae3499c8f986bb003b9 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 22 May 2025 09:41:07 +0200 Subject: [PATCH 133/254] fix(Dockerfile): remove fonts-dejavu-extra as the package is already packaged in fonts-dejavu --- build/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 11dd1aa57..d2a3610ca 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -175,7 +175,6 @@ RUN \ fonts-crosextra-caladea=20200211-1 \ fonts-crosextra-carlito=20220224-1 \ fonts-dejavu=2.37-6 \ - fonts-dejavu-extra=2.37-6 \ fonts-liberation=1:1.07.4-11 \ fonts-liberation2=2.1.5-1 \ fonts-linuxlibertine=5.3.0-6 \ From 863498949740053b7df7a9c907759f1aa8327971 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 22 May 2025 09:41:41 +0200 Subject: [PATCH 134/254] fix(Dockerfile): typo --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index d2a3610ca..51291a1c5 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -258,7 +258,7 @@ RUN \ # Cleanup. rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Support for arbitrary user ids (OpenShift). +# Support for arbitrary user IDs (OpenShift). # See: # https://github.com/gotenberg/gotenberg/issues/1049. # https://docs.redhat.com/en/documentation/openshift_container_platform/4.15/html/images/creating-images#use-uid_create-images. From 4aff4982b082b5904a0e158b3da3d419cdb66616 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 02:24:02 +0000 Subject: [PATCH 135/254] chore(deps): bump github.com/labstack/echo/v4 from 4.13.3 to 4.13.4 Bumps [github.com/labstack/echo/v4](https://github.com/labstack/echo) from 4.13.3 to 4.13.4. - [Release notes](https://github.com/labstack/echo/releases) - [Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md) - [Commits](https://github.com/labstack/echo/compare/v4.13.3...v4.13.4) --- updated-dependencies: - dependency-name: github.com/labstack/echo/v4 dependency-version: 4.13.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 38bd711f2..76a22eb4b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/labstack/echo/v4 v4.13.3 + github.com/labstack/echo/v4 v4.13.4 github.com/labstack/gommon v0.4.2 github.com/mattn/go-isatty v0.0.20 // indirect github.com/microcosm-cc/bluemonday v1.0.27 diff --git a/go.sum b/go.sum index 6865c6e12..195759b28 100644 --- a/go.sum +++ b/go.sum @@ -205,8 +205,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= +github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= From 6f714277f73458bf09ae37fab781172f46a02645 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 28 May 2025 15:25:04 +0200 Subject: [PATCH 136/254] docs(README): remove Zolsec from sponsors --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ff4f69a1f..f0e9f7592 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,6 @@ Head to the [documentation](https://gotenberg.dev/docs/getting-started/introduct TheCodingMachine Logo - - Zolsec Logo - pdfme Logo From a635fd8a7b1bf9493e0dd923afcf9da8909e2be9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 02:54:07 +0000 Subject: [PATCH 137/254] chore(deps): bump github.com/shirou/gopsutil/v4 from 4.25.4 to 4.25.5 Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.25.4 to 4.25.5. - [Release notes](https://github.com/shirou/gopsutil/releases) - [Commits](https://github.com/shirou/gopsutil/compare/v4.25.4...v4.25.5) --- updated-dependencies: - dependency-name: github.com/shirou/gopsutil/v4 dependency-version: 4.25.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 76a22eb4b..f43f5d149 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/docker/docker v28.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.2 - github.com/shirou/gopsutil/v4 v4.25.4 + github.com/shirou/gopsutil/v4 v4.25.5 github.com/testcontainers/testcontainers-go v0.37.0 ) @@ -64,7 +64,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect - github.com/ebitengine/purego v0.8.2 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/go.sum b/go.sum index 195759b28..9c712dc3f 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= -github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -276,8 +276,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw= -github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= +github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= +github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= From 9226b78b0c33c11bf1747d149cf9d3ce474939eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 05:08:27 +0000 Subject: [PATCH 138/254] chore(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.1.1+incompatible to 28.2.2+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v28.1.1...v28.2.2) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.2.2+incompatible dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 5 +++-- go.sum | 17 +++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index f43f5d149..598c5d810 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( require ( github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.1.1+incompatible + github.com/docker/docker v28.2.2+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.2 github.com/shirou/gopsutil/v4 v4.25.5 @@ -55,6 +55,8 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect @@ -124,7 +126,6 @@ require ( go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9c712dc3f..499848f23 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,10 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -81,8 +85,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= -github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -525,10 +529,11 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -536,8 +541,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From cf2d413a2853fe957f7ffe0ffaf3062d6b3c1f66 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 2 Jun 2025 11:27:25 +0200 Subject: [PATCH 139/254] test(integration): comment scenario that is not happening anymore on amd architectures --- .../features/chromium_convert_html.feature | 30 +++++++++-------- .../chromium_convert_markdown.feature | 32 ++++++++++--------- .../features/chromium_convert_url.feature | 32 ++++++++++--------- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index ab93e3480..822f33618 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -420,20 +420,22 @@ Feature: /forms/chromium/convert/html """ omitBackground requires printBackground set to true """ - 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 | - | paperWidth | 0 | field | - | paperHeight | 0 | field | - | marginTop | 1000000 | field | - | marginBottom | 1000000 | field | - | marginLeft | 1000000 | field | - | marginRight | 1000000 | field | - Then the response status code should be 400 - Then the response header "Content-Type" should be "text/plain; charset=UTF-8" - Then the response body should match string: - """ - Chromium does not handle the provided settings; please check for aberrant form values - """ + # Does not seems to happen on amd architectures anymore since Chromium 137. + # See: https://github.com/gotenberg/gotenberg/actions/runs/15384321883/job/43280184372. +# 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 | +# | paperWidth | 0 | field | +# | paperHeight | 0 | field | +# | marginTop | 1000000 | field | +# | marginBottom | 1000000 | field | +# | marginLeft | 1000000 | field | +# | marginRight | 1000000 | field | +# Then the response status code should be 400 +# Then the response header "Content-Type" should be "text/plain; charset=UTF-8" +# Then the response body should match string: +# """ +# Chromium does not handle the provided settings; please check for aberrant form values +# """ 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 | | nativePageRanges | foo | field | diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index b8d84c28c..338be06e6 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -497,21 +497,23 @@ Feature: /forms/chromium/convert/markdown """ omitBackground requires printBackground set to true """ - 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 | - | paperWidth | 0 | field | - | paperHeight | 0 | field | - | marginTop | 1000000 | field | - | marginBottom | 1000000 | field | - | marginLeft | 1000000 | field | - | marginRight | 1000000 | field | - Then the response status code should be 400 - Then the response header "Content-Type" should be "text/plain; charset=UTF-8" - Then the response body should match string: - """ - Chromium does not handle the provided settings; please check for aberrant form values - """ + # Does not seems to happen on amd architectures anymore since Chromium 137. + # See: https://github.com/gotenberg/gotenberg/actions/runs/15384321883/job/43280184372. +# 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 | +# | paperWidth | 0 | field | +# | paperHeight | 0 | field | +# | marginTop | 1000000 | field | +# | marginBottom | 1000000 | field | +# | marginLeft | 1000000 | field | +# | marginRight | 1000000 | field | +# Then the response status code should be 400 +# Then the response header "Content-Type" should be "text/plain; charset=UTF-8" +# Then the response body should match string: +# """ +# Chromium does not handle the provided settings; please check for aberrant form values +# """ 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 | diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index 4c16dc259..2e13e2c0c 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -479,21 +479,23 @@ Feature: /forms/chromium/convert/url """ omitBackground requires printBackground set to true """ - Given I have a static server - When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): - | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | - | paperWidth | 0 | field | - | paperHeight | 0 | field | - | marginTop | 1000000 | field | - | marginBottom | 1000000 | field | - | marginLeft | 1000000 | field | - | marginRight | 1000000 | field | - Then the response status code should be 400 - Then the response header "Content-Type" should be "text/plain; charset=UTF-8" - Then the response body should match string: - """ - Chromium does not handle the provided settings; please check for aberrant form values - """ + # Does not seems to happen on amd architectures anymore since Chromium 137. + # See: https://github.com/gotenberg/gotenberg/actions/runs/15384321883/job/43280184372. +# Given I have a static server +# When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): +# | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | +# | paperWidth | 0 | field | +# | paperHeight | 0 | field | +# | marginTop | 1000000 | field | +# | marginBottom | 1000000 | field | +# | marginLeft | 1000000 | field | +# | marginRight | 1000000 | field | +# Then the response status code should be 400 +# Then the response header "Content-Type" should be "text/plain; charset=UTF-8" +# Then the response body should match string: +# """ +# Chromium does not handle the provided settings; please check for aberrant form values +# """ Given I have a static server When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | From b0a84bcbf75bad5dbad749642472466104f4e3e9 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 2 Jun 2025 11:45:44 +0200 Subject: [PATCH 140/254] chore(integration): prettify --- .../features/chromium_convert_html.feature | 28 ++++++++--------- .../chromium_convert_markdown.feature | 30 +++++++++---------- .../features/chromium_convert_url.feature | 30 +++++++++---------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index 822f33618..2255fb289 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -422,20 +422,20 @@ Feature: /forms/chromium/convert/html """ # Does not seems to happen on amd architectures anymore since Chromium 137. # See: https://github.com/gotenberg/gotenberg/actions/runs/15384321883/job/43280184372. -# 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 | -# | paperWidth | 0 | field | -# | paperHeight | 0 | field | -# | marginTop | 1000000 | field | -# | marginBottom | 1000000 | field | -# | marginLeft | 1000000 | field | -# | marginRight | 1000000 | field | -# Then the response status code should be 400 -# Then the response header "Content-Type" should be "text/plain; charset=UTF-8" -# Then the response body should match string: -# """ -# Chromium does not handle the provided settings; please check for aberrant form values -# """ + # 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 | + # | paperWidth | 0 | field | + # | paperHeight | 0 | field | + # | marginTop | 1000000 | field | + # | marginBottom | 1000000 | field | + # | marginLeft | 1000000 | field | + # | marginRight | 1000000 | field | + # Then the response status code should be 400 + # Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + # Then the response body should match string: + # """ + # Chromium does not handle the provided settings; please check for aberrant form values + # """ 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 | | nativePageRanges | foo | field | diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index 338be06e6..113c3ff39 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -499,21 +499,21 @@ Feature: /forms/chromium/convert/markdown """ # Does not seems to happen on amd architectures anymore since Chromium 137. # See: https://github.com/gotenberg/gotenberg/actions/runs/15384321883/job/43280184372. -# 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 | -# | paperWidth | 0 | field | -# | paperHeight | 0 | field | -# | marginTop | 1000000 | field | -# | marginBottom | 1000000 | field | -# | marginLeft | 1000000 | field | -# | marginRight | 1000000 | field | -# Then the response status code should be 400 -# Then the response header "Content-Type" should be "text/plain; charset=UTF-8" -# Then the response body should match string: -# """ -# Chromium does not handle the provided settings; please check for aberrant form values -# """ + # 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 | + # | paperWidth | 0 | field | + # | paperHeight | 0 | field | + # | marginTop | 1000000 | field | + # | marginBottom | 1000000 | field | + # | marginLeft | 1000000 | field | + # | marginRight | 1000000 | field | + # Then the response status code should be 400 + # Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + # Then the response body should match string: + # """ + # Chromium does not handle the provided settings; please check for aberrant form values + # """ 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 | diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index 2e13e2c0c..f25840ea8 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -481,21 +481,21 @@ Feature: /forms/chromium/convert/url """ # Does not seems to happen on amd architectures anymore since Chromium 137. # See: https://github.com/gotenberg/gotenberg/actions/runs/15384321883/job/43280184372. -# Given I have a static server -# When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): -# | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | -# | paperWidth | 0 | field | -# | paperHeight | 0 | field | -# | marginTop | 1000000 | field | -# | marginBottom | 1000000 | field | -# | marginLeft | 1000000 | field | -# | marginRight | 1000000 | field | -# Then the response status code should be 400 -# Then the response header "Content-Type" should be "text/plain; charset=UTF-8" -# Then the response body should match string: -# """ -# Chromium does not handle the provided settings; please check for aberrant form values -# """ + # Given I have a static server + # When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): + # | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | + # | paperWidth | 0 | field | + # | paperHeight | 0 | field | + # | marginTop | 1000000 | field | + # | marginBottom | 1000000 | field | + # | marginLeft | 1000000 | field | + # | marginRight | 1000000 | field | + # Then the response status code should be 400 + # Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + # Then the response body should match string: + # """ + # Chromium does not handle the provided settings; please check for aberrant form values + # """ Given I have a static server When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/url" endpoint with the following form data and header(s): | url | http://host.docker.internal:%d/html/testdata/page-1-html/index.html | field | From aa58615650a9bf276eaf3a7045232317e62f1af2 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 6 Jun 2025 15:14:06 +0000 Subject: [PATCH 141/254] fix(api): outpout filename as path - fixes #1227 --- pkg/modules/api/api.go | 1 + pkg/modules/api/context.go | 2 +- pkg/modules/api/middlewares.go | 20 +++++++++++ .../features/output_filename.feature | 33 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/integration/features/output_filename.feature diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index 72a831ced..3863bcac7 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -462,6 +462,7 @@ func (a *Api) Start() error { latencyMiddleware(), rootPathMiddleware(a.rootPath), traceMiddleware(a.traceHeader), + outputFilenameMiddleware(), loggerMiddleware(a.logger, disableLoggingForPaths), ) diff --git a/pkg/modules/api/context.go b/pkg/modules/api/context.go index 576c3176d..61c6dcb4a 100644 --- a/pkg/modules/api/context.go +++ b/pkg/modules/api/context.go @@ -513,7 +513,7 @@ func (ctx *Context) BuildOutputFile() (string, error) { // OutputFilename returns the filename based on the given output path or the // "Gotenberg-Output-Filename" header's value. func (ctx *Context) OutputFilename(outputPath string) string { - filename := ctx.echoCtx.Request().Header.Get("Gotenberg-Output-Filename") + filename := ctx.echoCtx.Get("outputFilename").(string) if filename == "" { return filepath.Base(outputPath) diff --git a/pkg/modules/api/middlewares.go b/pkg/modules/api/middlewares.go index d1cb3c540..176d6f4da 100644 --- a/pkg/modules/api/middlewares.go +++ b/pkg/modules/api/middlewares.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "path/filepath" "strings" "time" @@ -150,6 +151,25 @@ func traceMiddleware(header string) echo.MiddlewareFunc { } } +// outputFilenameMiddleware sets the output filename in the [echo.Context] +// under "outputFilename". +// +// outputFilename := c.Get("outputFilename").(string) +func outputFilenameMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + filename := c.Request().Header.Get("Gotenberg-Output-Filename") + // See https://github.com/gotenberg/gotenberg/issues/1227. + if filename != "" { + filename = filepath.Base(filename) + } + c.Set("outputFilename", filename) + // Call the next middleware in the chain. + return next(c) + } + } +} + // loggerMiddleware sets the logger in the [echo.Context] under "logger" and // logs a synchronous request result. // diff --git a/test/integration/features/output_filename.feature b/test/integration/features/output_filename.feature new file mode 100644 index 000000000..f4f8b44c8 --- /dev/null +++ b/test/integration/features/output_filename.feature @@ -0,0 +1,33 @@ +Feature: Output Filename + + Scenario: Default (Single Output File) + 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/page_1.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 the following file(s) in the response: + | foo.pdf | + + Scenario: Default (Many Output Files) + 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/page_1.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/zip" + Then there should be the following file(s) in the response: + | foo.zip | + + # See https://github.com/gotenberg/gotenberg/issues/1227. + Scenario: Path As 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/page_1.pdf | file | + | Gotenberg-Output-Filename | /tmp/foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then there should be the following file(s) in the response: + | foo.pdf | From f0fc704efc785df888e85d762301f6d69120388d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:22:48 +0000 Subject: [PATCH 142/254] chore(deps): bump golang.org/x/sync from 0.14.0 to 0.15.0 Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.14.0 to 0.15.0. - [Commits](https://github.com/golang/sync/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-version: 0.15.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 598c5d810..8c3641dcc 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.40.0 - golang.org/x/sync v0.14.0 + golang.org/x/sync v0.15.0 golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 golang.org/x/text v0.25.0 diff --git a/go.sum b/go.sum index 499848f23..cd34b17c8 100644 --- a/go.sum +++ b/go.sum @@ -426,8 +426,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From fcf41187faec8c5c3e77833d3e82dcb0738b0b9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:22:32 +0000 Subject: [PATCH 143/254] chore(deps): bump golang.org/x/text from 0.25.0 to 0.26.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.25.0 to 0.26.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8c3641dcc..7669cd574 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( golang.org/x/sync v0.15.0 golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 - golang.org/x/text v0.25.0 + golang.org/x/text v0.26.0 ) require ( diff --git a/go.sum b/go.sum index cd34b17c8..67859e634 100644 --- a/go.sum +++ b/go.sum @@ -466,8 +466,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= From ffeb4703fd85666fa5cbd970eb97ce1721d95dfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:08:05 +0000 Subject: [PATCH 144/254] chore(deps): bump golang.org/x/net from 0.40.0 to 0.41.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0. - [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7669cd574..6b950d86c 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/net v0.40.0 + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 diff --git a/go.sum b/go.sum index 67859e634..d11b26a8f 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -411,8 +411,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From e945329d9183259c1820a06fcd6969971d548af9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:39:57 +0000 Subject: [PATCH 145/254] chore(deps-dev): bump prettier from 3.5.3 to 3.6.0 Bumps [prettier](https://github.com/prettier/prettier) from 3.5.3 to 3.6.0. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.3...3.6.0) --- updated-dependencies: - dependency-name: prettier dependency-version: 3.6.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7a7f6998..89b211932 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "prettier": "3.5.3", + "prettier": "3.6.0", "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.17.4" } @@ -88,9 +88,9 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.0.tgz", + "integrity": "sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index d254bc8f8..0d063c7a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "prettier": "3.5.3", + "prettier": "3.6.0", "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.17.4" } From 2ba70141ccc8610ac4fef431c83d212da81f3751 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:36:17 +0000 Subject: [PATCH 146/254] chore(deps): bump github.com/chromedp/chromedp from 0.13.6 to 0.13.7 Bumps [github.com/chromedp/chromedp](https://github.com/chromedp/chromedp) from 0.13.6 to 0.13.7. - [Release notes](https://github.com/chromedp/chromedp/releases) - [Commits](https://github.com/chromedp/chromedp/compare/v0.13.6...v0.13.7) --- updated-dependencies: - dependency-name: github.com/chromedp/chromedp dependency-version: 0.13.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6b950d86c..de98ce096 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect github.com/barasher/go-exiftool v1.10.0 github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a - github.com/chromedp/chromedp v0.13.6 + github.com/chromedp/chromedp v0.13.7 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 diff --git a/go.sum b/go.sum index d11b26a8f..43b994c55 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a h1:TVjyMfX22UgV/jP0A4UwrrCmI9zfvp6ZS5UBZMy3HDw= github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= -github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk= -github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= +github.com/chromedp/chromedp v0.13.7 h1:vt+mslxscyvUr58eC+6DLSeeo74jpV/HI2nWetjv/W4= +github.com/chromedp/chromedp v0.13.7/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= From 16f740b318a8bc4c7cf7beae9b8bd383fca37d10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:35:54 +0000 Subject: [PATCH 147/254] chore(deps): bump github.com/hashicorp/go-retryablehttp Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.7 to 0.7.8. - [Changelog](https://github.com/hashicorp/go-retryablehttp/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.7...v0.7.8) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-retryablehttp dependency-version: 0.7.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index de98ce096..2310bb04f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/chromedp/chromedp v0.13.7 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/hashicorp/go-retryablehttp v0.7.8 github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/labstack/echo/v4 v4.13.4 diff --git a/go.sum b/go.sum index 43b994c55..8d6978644 100644 --- a/go.sum +++ b/go.sum @@ -175,8 +175,8 @@ github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+ github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= From 57264da21b5a090d6cb650483861e0bf31ebccde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:33:37 +0000 Subject: [PATCH 148/254] chore(deps): bump github.com/alexliesenfeld/health from 0.8.0 to 0.8.1 Bumps [github.com/alexliesenfeld/health](https://github.com/alexliesenfeld/health) from 0.8.0 to 0.8.1. - [Release notes](https://github.com/alexliesenfeld/health/releases) - [Changelog](https://github.com/alexliesenfeld/health/blob/main/CHANGELOG.md) - [Commits](https://github.com/alexliesenfeld/health/compare/v0.8.0...v0.8.1) --- updated-dependencies: - dependency-name: github.com/alexliesenfeld/health dependency-version: 0.8.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2310bb04f..c2ce94c1c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gotenberg/gotenberg/v8 go 1.24.0 require ( - github.com/alexliesenfeld/health v0.8.0 + github.com/alexliesenfeld/health v0.8.1 github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect github.com/barasher/go-exiftool v1.10.0 github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a diff --git a/go.sum b/go.sum index 8d6978644..31b1c90d9 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= -github.com/alexliesenfeld/health v0.8.0 h1:lCV0i+ZJPTbqP7LfKG7p3qZBl5VhelwUFCIVWl77fgk= -github.com/alexliesenfeld/health v0.8.0/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= +github.com/alexliesenfeld/health v0.8.1 h1:wdE3vt+cbJotiR8DGDBZPKHDFoJbAoWEfQTcqrmedUg= +github.com/alexliesenfeld/health v0.8.1/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= From 51b084117100f010dfd0f2c9ebcc7bf08f273e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 03:05:24 +0000 Subject: [PATCH 149/254] chore(deps): bump github.com/mholt/archives from 0.1.2 to 0.1.3 Bumps [github.com/mholt/archives](https://github.com/mholt/archives) from 0.1.2 to 0.1.3. - [Release notes](https://github.com/mholt/archives/releases) - [Commits](https://github.com/mholt/archives/compare/v0.1.2...v0.1.3) --- updated-dependencies: - dependency-name: github.com/mholt/archives dependency-version: 0.1.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c2ce94c1c..5aaabda23 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/dlclark/regexp2 v1.11.5 github.com/docker/docker v28.2.2+incompatible github.com/docker/go-connections v0.5.0 - github.com/mholt/archives v0.1.2 + github.com/mholt/archives v0.1.3 github.com/shirou/gopsutil/v4 v4.25.5 github.com/testcontainers/testcontainers-go v0.37.0 ) @@ -87,6 +87,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect @@ -110,7 +111,6 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/sorairolake/lzip-go v0.3.7 // indirect github.com/stretchr/testify v1.10.0 // indirect - github.com/therootcompany/xz v1.0.1 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index 31b1c90d9..8680dc072 100644 --- a/go.sum +++ b/go.sum @@ -223,10 +223,12 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mholt/archives v0.1.2 h1:UBSe5NfYKHI1sy+S5dJsEsG9jsKKk8NJA4HCC+xTI4A= -github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprUOwZWDs= +github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458= +github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= +github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -306,8 +308,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= -github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= -github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= From ac5dfb7ff0e3c2c06d89e9c60e2fedcd7eb0bb94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 03:07:16 +0000 Subject: [PATCH 150/254] chore(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.2.2+incompatible to 28.3.0+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v28.2.2...v28.3.0) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.3.0+incompatible dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5aaabda23..79ed9ed6f 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( require ( github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.2.2+incompatible + github.com/docker/docker v28.3.0+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.3 github.com/shirou/gopsutil/v4 v4.25.5 diff --git a/go.sum b/go.sum index 8680dc072..5a011fd5d 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= -github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.0+incompatible h1:ffS62aKWupCWdvcee7nBU9fhnmknOqDPaJAMtfK0ImQ= +github.com/docker/docker v28.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= From ef745dc378de44046a6de9b3785089f3248a4b84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 03:16:35 +0000 Subject: [PATCH 151/254] chore(deps-dev): bump prettier from 3.6.0 to 3.6.2 Bumps [prettier](https://github.com/prettier/prettier) from 3.6.0 to 3.6.2. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.6.0...3.6.2) --- updated-dependencies: - dependency-name: prettier dependency-version: 3.6.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89b211932..ba842f720 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "prettier": "3.6.0", + "prettier": "3.6.2", "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.17.4" } @@ -88,11 +88,10 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.0.tgz", - "integrity": "sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, diff --git a/package.json b/package.json index 0d063c7a1..173f412e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "prettier": "3.6.0", + "prettier": "3.6.2", "prettier-plugin-gherkin": "^3.1.2", "prettier-plugin-sh": "^0.17.4" } From e79d7e6fb560922f03ecc6a0b104d7d2632a5180 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 03:20:25 +0000 Subject: [PATCH 152/254] chore(deps-dev): bump prettier-plugin-sh from 0.17.4 to 0.18.0 Bumps [prettier-plugin-sh](https://github.com/un-ts/prettier) from 0.17.4 to 0.18.0. - [Release notes](https://github.com/un-ts/prettier/releases) - [Changelog](https://github.com/un-ts/prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/un-ts/prettier/compare/prettier-plugin-sh@0.17.4...prettier-plugin-sh@0.18.0) --- updated-dependencies: - dependency-name: prettier-plugin-sh dependency-version: 0.18.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 25 +++++++++++-------------- package.json | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba842f720..760fb03a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "prettier": "3.6.2", "prettier-plugin-gherkin": "^3.1.2", - "prettier-plugin-sh": "^0.17.4" + "prettier-plugin-sh": "^0.18.0" } }, "node_modules/@cucumber/gherkin": { @@ -115,14 +115,13 @@ } }, "node_modules/prettier-plugin-sh": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.17.4.tgz", - "integrity": "sha512-aAVKXZ7GTEMZdZsIPSwMwddwPvt2ibMbRGd4OJAP0G7QoeYZV+mPNg2Oln3R53sZ4PVjeAA7Xzi/PuI0QlHHfQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.18.0.tgz", + "integrity": "sha512-cW1XL27FOJQ/qGHOW6IHwdCiNWQsAgK+feA8V6+xUTaH0cD3Mh+tFAtBvEEWvuY6hTDzRV943Fzeii+qMOh7nQ==", "dev": true, - "license": "MIT", "dependencies": { - "@reteps/dockerfmt": "^0.3.5", - "sh-syntax": "^0.5.6" + "@reteps/dockerfmt": "^0.3.6", + "sh-syntax": "^0.5.8" }, "engines": { "node": ">=16.0.0" @@ -131,7 +130,7 @@ "url": "https://opencollective.com/unts" }, "peerDependencies": { - "prettier": "^3.0.3" + "prettier": "^3.6.0" } }, "node_modules/reflect-metadata": { @@ -142,11 +141,10 @@ "license": "Apache-2.0" }, "node_modules/sh-syntax": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.5.7.tgz", - "integrity": "sha512-74m9dt91konrF5+m0kASugzi37VxKsnTJQ6yvdDZu3IijG5/vIZpImP6FadsJLWNt2X2YD0VaTwW5W7Ox7mFVg==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.5.8.tgz", + "integrity": "sha512-JfVoxf4FxQI5qpsPbkHhZo+n6N9YMJobyl4oGEUBb/31oQYlgTjkXQD8PBiafS2UbWoxrTO0Z5PJUBXEPAG1Zw==", "dev": true, - "license": "MIT", "dependencies": { "tslib": "^2.8.1" }, @@ -161,8 +159,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" + "dev": true }, "node_modules/uuid": { "version": "9.0.1", diff --git a/package.json b/package.json index 173f412e7..777d2441b 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "devDependencies": { "prettier": "3.6.2", "prettier-plugin-gherkin": "^3.1.2", - "prettier-plugin-sh": "^0.17.4" + "prettier-plugin-sh": "^0.18.0" } } From 00a85b0d90f98c328e3a9cfdee838170f2bb6946 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 02:59:43 +0000 Subject: [PATCH 153/254] chore(deps): bump github.com/shirou/gopsutil/v4 from 4.25.5 to 4.25.6 --- updated-dependencies: - dependency-name: github.com/shirou/gopsutil/v4 dependency-version: 4.25.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 79ed9ed6f..95c165c10 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/docker/docker v28.3.0+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.3 - github.com/shirou/gopsutil/v4 v4.25.5 + github.com/shirou/gopsutil/v4 v4.25.6 github.com/testcontainers/testcontainers-go v0.37.0 ) diff --git a/go.sum b/go.sum index 5a011fd5d..8097fb163 100644 --- a/go.sum +++ b/go.sum @@ -282,8 +282,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= -github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= From f73b72753fcad8815d6c0b50a4f12634fc91ad34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 02:58:33 +0000 Subject: [PATCH 154/254] chore(deps): bump github.com/docker/docker --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.3.1+incompatible dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 95c165c10..611af0975 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( require ( github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.3.0+incompatible + github.com/docker/docker v28.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.3 github.com/shirou/gopsutil/v4 v4.25.6 diff --git a/go.sum b/go.sum index 8097fb163..f88d423f0 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.3.0+incompatible h1:ffS62aKWupCWdvcee7nBU9fhnmknOqDPaJAMtfK0ImQ= -github.com/docker/docker v28.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY= +github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= From 0e250a99edfd7761c8b280f28969558c31127493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 02:59:58 +0000 Subject: [PATCH 155/254] chore(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.41.0 to 0.42.0. - [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 611af0975..4a6f7e246 100644 --- a/go.mod +++ b/go.mod @@ -24,12 +24,12 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 - golang.org/x/sync v0.15.0 - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 - golang.org/x/text v0.26.0 + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.42.0 + golang.org/x/sync v0.16.0 + golang.org/x/sys v0.34.0 // indirect + golang.org/x/term v0.33.0 + golang.org/x/text v0.27.0 ) require ( diff --git a/go.sum b/go.sum index f88d423f0..4b319d86a 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -411,8 +411,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -426,8 +426,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -452,13 +452,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -466,8 +466,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= From 59a091e7dc10eada55c7e93047eddaafb35d31fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 02:55:54 +0000 Subject: [PATCH 156/254] chore(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.3.1+incompatible to 28.3.2+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v28.3.1...v28.3.2) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.3.2+incompatible dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a6f7e246..e3051fc84 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( require ( github.com/cucumber/godog v0.15.0 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.3.1+incompatible + github.com/docker/docker v28.3.2+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.3 github.com/shirou/gopsutil/v4 v4.25.6 diff --git a/go.sum b/go.sum index 4b319d86a..5539bd482 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY= -github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA= +github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= From 3a71032d62691147b73adea6b919d01306c099ce Mon Sep 17 00:00:00 2001 From: Andrea <1174320+roy20021@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:30:41 +0200 Subject: [PATCH 157/254] tests(integration): use same targeted platform for integration-tools image (#1275) * Use same targeted platform integration-tools image @ integration tests * run make fmt --- test/integration/scenario/containers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/scenario/containers.go b/test/integration/scenario/containers.go index 23f5f3c11..c4dc1a559 100644 --- a/test/integration/scenario/containers.go +++ b/test/integration/scenario/containers.go @@ -69,7 +69,8 @@ func execCommandInIntegrationToolsContainer(ctx context.Context, cmd []string, p defer cancel() req := testcontainers.ContainerRequest{ - Image: "gotenberg/integration-tools:latest", + Image: "gotenberg/integration-tools:latest", + ImagePlatform: GotenbergContainerPlatform, Files: []testcontainers.ContainerFile{ { HostFilePath: path, From 588e27e6d115fc3e7022479d462d5c237689eae8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 02:31:37 +0000 Subject: [PATCH 158/254] chore(deps): bump github.com/cucumber/godog from 0.15.0 to 0.15.1 Bumps [github.com/cucumber/godog](https://github.com/cucumber/godog) from 0.15.0 to 0.15.1. - [Release notes](https://github.com/cucumber/godog/releases) - [Changelog](https://github.com/cucumber/godog/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/godog/compare/v0.15.0...v0.15.1) --- updated-dependencies: - dependency-name: github.com/cucumber/godog dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e3051fc84..b216c3f5b 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/prometheus/client_golang v1.22.0 github.com/russross/blackfriday/v2 v2.1.0 - github.com/spf13/pflag v1.0.6 + github.com/spf13/pflag v1.0.7 github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 @@ -33,7 +33,7 @@ require ( ) require ( - github.com/cucumber/godog v0.15.0 + github.com/cucumber/godog v0.15.1 github.com/dlclark/regexp2 v1.11.5 github.com/docker/docker v28.3.2+incompatible github.com/docker/go-connections v0.5.0 diff --git a/go.sum b/go.sum index 5539bd482..18185dfa7 100644 --- a/go.sum +++ b/go.sum @@ -73,8 +73,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= -github.com/cucumber/godog v0.15.0 h1:51AL8lBXF3f0cyA5CV4TnJFCTHpgiy+1x1Hb3TtZUmo= -github.com/cucumber/godog v0.15.0/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces= +github.com/cucumber/godog v0.15.1 h1:rb/6oHDdvVZKS66hrhpjFQFHjthFSrQBCOI1LwshNTI= +github.com/cucumber/godog v0.15.1/go.mod h1:qju+SQDewOljHuq9NSM66s0xEhogx0q30flfxL4WUk8= github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= @@ -290,8 +290,8 @@ github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRK github.com/sorairolake/lzip-go v0.3.7/go.mod h1:THOHr0FlNVCw2eOIEE9shFJAG1QxQg/pf2XUPAmNIqg= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From 911fdd3067a84d74cbd548c9033f4f762e0bc9ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 02:29:43 +0000 Subject: [PATCH 159/254] chore(deps): bump github.com/testcontainers/testcontainers-go --- updated-dependencies: - dependency-name: github.com/testcontainers/testcontainers-go dependency-version: 0.38.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 5 ++--- go.sum | 11 +++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b216c3f5b..71f07c15d 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.3 github.com/shirou/gopsutil/v4 v4.25.6 - github.com/testcontainers/testcontainers-go v0.37.0 + github.com/testcontainers/testcontainers-go v0.38.0 ) require ( @@ -92,7 +92,6 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect @@ -125,7 +124,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/time v0.11.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 18185dfa7..f4e3005ee 100644 --- a/go.sum +++ b/go.sum @@ -306,8 +306,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= -github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw= +github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= @@ -528,12 +528,11 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From 339afe614c8f94eb3b87620bc26ed02cae8905fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:05:39 +0000 Subject: [PATCH 160/254] chore(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.3.2+incompatible to 28.3.3+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v28.3.2...v28.3.3) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.3.3+incompatible dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71f07c15d..303d1df95 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( require ( github.com/cucumber/godog v0.15.1 github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.3.2+incompatible + github.com/docker/docker v28.3.3+incompatible github.com/docker/go-connections v0.5.0 github.com/mholt/archives v0.1.3 github.com/shirou/gopsutil/v4 v4.25.6 diff --git a/go.sum b/go.sum index f4e3005ee..d4be80277 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA= -github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= From 6ff5109b00b00561bb8325edb512ff5df1cf8abd Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sun, 3 Aug 2025 18:02:35 +0200 Subject: [PATCH 161/254] fix(integration): non-working scenarios because of a remote service down --- build/Dockerfile | 2 +- go.mod | 77 ++++++------- go.sum | 107 +++++++++--------- pkg/modules/chromium/tasks.go | 2 +- .../features/chromium_convert_html.feature | 2 +- .../chromium_convert_markdown.feature | 2 +- .../testdata/feature-rich-html/index.html | 2 +- .../testdata/feature-rich-markdown/index.html | 2 +- 8 files changed, 95 insertions(+), 101 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 51291a1c5..5a6fc8367 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,7 +1,7 @@ # ARG instructions do not create additional layers. Instead, next layers will # concatenate them. Also, we have to repeat ARG instructions in each build # stage that uses them. -ARG GOLANG_VERSION=1.24 +ARG GOLANG_VERSION=1.24.5 # ---------------------------------------------- # pdfcpu binary build stage diff --git a/go.mod b/go.mod index 303d1df95..780a03065 100644 --- a/go.mod +++ b/go.mod @@ -1,56 +1,45 @@ module github.com/gotenberg/gotenberg/v8 -go 1.24.0 +go 1.24.5 require ( github.com/alexliesenfeld/health v0.8.1 - github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect github.com/barasher/go-exiftool v1.10.0 - github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a - github.com/chromedp/chromedp v0.13.7 + github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 + github.com/chromedp/chromedp v0.14.0 + github.com/cucumber/godog v0.15.1 + github.com/dlclark/regexp2 v1.11.5 + github.com/docker/docker v28.3.3+incompatible + github.com/docker/go-connections v0.5.0 github.com/google/uuid v1.6.0 - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 - github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/pgzip v1.2.6 // indirect github.com/labstack/echo/v4 v4.13.4 github.com/labstack/gommon v0.4.2 - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mholt/archives v0.1.3 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/pierrec/lz4/v4 v4.1.22 // indirect - github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_golang v1.23.0 github.com/russross/blackfriday/v2 v2.1.0 + github.com/shirou/gopsutil/v4 v4.25.7 github.com/spf13/pflag v1.0.7 - github.com/ulikunitz/xz v0.5.12 // indirect + github.com/testcontainers/testcontainers-go v0.38.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.40.0 // indirect golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 - golang.org/x/sys v0.34.0 // indirect golang.org/x/term v0.33.0 golang.org/x/text v0.27.0 ) require ( - github.com/cucumber/godog v0.15.1 - github.com/dlclark/regexp2 v1.11.5 - github.com/docker/docker v28.3.3+incompatible - github.com/docker/go-connections v0.5.0 - github.com/mholt/archives v0.1.3 - github.com/shirou/gopsutil/v4 v4.25.6 - github.com/testcontainers/testcontainers-go v0.38.0 -) - -require ( - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/STARRY-S/zip v0.2.3 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bodgit/plumbing v1.3.0 // indirect - github.com/bodgit/sevenzip v1.6.0 // indirect + github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -68,8 +57,8 @@ require ( github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/ebitengine/purego v0.8.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect @@ -78,17 +67,19 @@ require ( github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gorilla/css v1.0.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect - github.com/minio/minlz v1.0.0 // indirect + github.com/minio/minlz v1.0.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect @@ -101,30 +92,36 @@ require ( github.com/nwaples/rardecode/v2 v2.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.63.0 // indirect - github.com/prometheus/procfs v0.16.0 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sorairolake/lzip-go v0.3.7 // indirect + github.com/spf13/afero v1.14.0 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/time v0.11.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/time v0.12.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d4be80277..2e546fad4 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= @@ -29,8 +29,8 @@ github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/alexliesenfeld/health v0.8.1 h1:wdE3vt+cbJotiR8DGDBZPKHDFoJbAoWEfQTcqrmedUg= github.com/alexliesenfeld/health v0.8.1/go.mod h1:TfNP0f+9WQVWMQRzvMUjlws4ceXKEL3WR+6Hp95HUFc= -github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= -github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/barasher/go-exiftool v1.10.0 h1:f5JY5jc42M7tzR6tbL9508S2IXdIcG9QyieEXNMpIhs= @@ -39,8 +39,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= -github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A= -github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= +github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= +github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -48,10 +48,10 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a h1:TVjyMfX22UgV/jP0A4UwrrCmI9zfvp6ZS5UBZMy3HDw= -github.com/chromedp/cdproto v0.0.0-20250416210000-d7e4d624041a/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= -github.com/chromedp/chromedp v0.13.7 h1:vt+mslxscyvUr58eC+6DLSeeo74jpV/HI2nWetjv/W4= -github.com/chromedp/chromedp v0.13.7/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= +github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E= +github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chromedp/chromedp v0.14.0 h1:/xE5m6wEBwivhalHwlCOyYfBcAJNwg4nLw96QiCfYr0= +github.com/chromedp/chromedp v0.14.0/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -104,11 +104,11 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= -github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs= +github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -158,11 +158,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -173,8 +170,6 @@ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo= github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -229,8 +224,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= -github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= -github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= +github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= @@ -267,27 +262,29 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= -github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= -github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= github.com/sorairolake/lzip-go v0.3.7/go.mod h1:THOHr0FlNVCw2eOIEE9shFJAG1QxQg/pf2XUPAmNIqg= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= @@ -332,24 +329,24 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -470,8 +467,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -528,11 +525,11 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8= +google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -540,8 +537,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/modules/chromium/tasks.go b/pkg/modules/chromium/tasks.go index c6c46ed45..13e004323 100644 --- a/pkg/modules/chromium/tasks.go +++ b/pkg/modules/chromium/tasks.go @@ -330,7 +330,7 @@ func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent boo return func(ctx context.Context) error { logger.Debug(fmt.Sprintf("navigate to '%s'", url)) - _, _, _, err := page.Navigate(url).Do(ctx) + _, _, _, _, err := page.Navigate(url).Do(ctx) if err != nil { return fmt.Errorf("navigate to '%s': %w", url, err) } diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index 2255fb289..fea39b942 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -338,7 +338,7 @@ Feature: /forms/chromium/convert/html Then the response body should match string: """ Invalid HTTP status code from resources: - https://httpstat.us/400 - 400: Bad Request + https://gethttpstatus.com/400 - 400: Bad Request """ Scenario: POST /forms/chromium/convert/html (Fail On Resource Loading Failed) diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index 113c3ff39..1bf0f12ec 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -403,7 +403,7 @@ Feature: /forms/chromium/convert/markdown Then the response body should match string: """ Invalid HTTP status code from resources: - https://httpstat.us/400 - 400: Bad Request + https://gethttpstatus.com/400 - 400: Bad Request """ Scenario: POST /forms/chromium/convert/markdown (Fail On Resource Loading Failed) diff --git a/test/integration/testdata/feature-rich-html/index.html b/test/integration/testdata/feature-rich-html/index.html index f679383e0..e4c49b7dc 100644 --- a/test/integration/testdata/feature-rich-html/index.html +++ b/test/integration/testdata/feature-rich-html/index.html @@ -11,7 +11,7 @@ href="http://localhost:100/style.css" /> - + Feature Rich HTML

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 GQU4DG17s5b diff --git a/test/testdata/chromium/html/font.woff b/test/testdata/chromium/html/font.woff deleted file mode 100644 index ebd62d592e7ff4cfed3020332d996b90ff24dc8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12644 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 O(-;7vnZf`p2LC^-w-1v5 diff --git a/test/testdata/chromium/html/footer.html b/test/testdata/chromium/html/footer.html deleted file mode 100644 index c355f721b..000000000 --- a/test/testdata/chromium/html/footer.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - -