From 21e300fcec270820f65ae5e3964e7ca15fb3ced6 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 18 Mar 2026 15:24:31 +0100 Subject: [PATCH] fix(pdfengines): better workflow when applying PDF/A or PDF/UA compliance --- .agents/DEVELOPER.md | 15 +++ Makefile | 4 + pkg/modules/chromium/routes.go | 31 +++-- pkg/modules/libreoffice/routes.go | 60 +++++---- pkg/modules/pdfengines/routes.go | 116 +++++++++++++----- .../features/chromium_convert_html.feature | 11 +- .../chromium_convert_markdown.feature | 11 +- .../features/chromium_convert_url.feature | 11 +- .../features/libreoffice_convert.feature | 11 +- .../features/pdfengines_merge.feature | 44 +++++-- .../features/pdfengines_split.feature | 50 ++++++-- 11 files changed, 252 insertions(+), 112 deletions(-) diff --git a/.agents/DEVELOPER.md b/.agents/DEVELOPER.md index d195af9b..bb5a328c 100644 --- a/.agents/DEVELOPER.md +++ b/.agents/DEVELOPER.md @@ -39,6 +39,21 @@ Commits must follow the [Conventional Commits](https://www.conventionalcommits.o Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. The scope should match the module or area of the change (e.g., `chromium`, `pdfengines`, `api`). +## Adding PDF Engine Features + +When adding a new PDF engine capability (e.g., bookmarks, watermark, stamp, embed), you must update the Makefile to include the corresponding engine list variable and flag. Every `--pdfengines-*-engines` flag registered in `pkg/modules/pdfengines/pdfengines.go` must have a matching entry in the Makefile: + +1. **Add a variable** in the Makefile's variable block (around line 60-70): + ```makefile + PDFENGINES__ENGINES= + ``` +2. **Add the flag** in the Makefile's command args block (around line 140-155): + ```makefile + --pdfengines--engines=$(PDFENGINES__ENGINES) \ + ``` + +The default value should match what is defined in `pdfengines.go`'s `fs.StringSlice(...)` call for that flag. + ## Coding Patterns - **Error handling:** Always wrap errors with context using `fmt.Errorf("description: %w", err)`. Never swallow errors silently. diff --git a/Makefile b/Makefile index eca99aa5..0e018f77 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,8 @@ PDFENGINES_READ_METADATA_ENGINES=exiftool PDFENGINES_WRITE_METADATA_ENGINES=exiftool PDFENGINES_READ_BOOKMARKS_ENGINES=pdfcpu PDFENGINES_WRITE_BOOKMARKS_ENGINES=pdfcpu +PDFENGINES_WATERMARK_ENGINES=pdfcpu,pdftk +PDFENGINES_STAMP_ENGINES=pdfcpu,pdftk PDFENGINES_ENCRYPT_ENGINES=qpdf,pdfcpu,pdftk PDFENGINES_EMBED_ENGINES=pdfcpu PROMETHEUS_NAMESPACE=gotenberg @@ -146,6 +148,8 @@ run: ## Start a Gotenberg container --pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \ --pdfengines-read-bookmarks-engines=$(PDFENGINES_READ_BOOKMARKS_ENGINES) \ --pdfengines-write-bookmarks-engines=$(PDFENGINES_WRITE_BOOKMARKS_ENGINES) \ + --pdfengines-watermark-engines=$(PDFENGINES_WATERMARK_ENGINES) \ + --pdfengines-stamp-engines=$(PDFENGINES_STAMP_ENGINES) \ --pdfengines-encrypt-engines=$(PDFENGINES_ENCRYPT_ENGINES) \ --pdfengines-embed-engines=$(PDFENGINES_EMBED_ENGINES) \ --prometheus-namespace=$(PROMETHEUS_NAMESPACE) \ diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index d7e07df3..a0756642 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -781,24 +781,36 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url return fmt.Errorf("convert to PDF: %w", err) } + err = pdfengines.ValidatePdfFormatsCompat(pdfFormats, userPassword, embedPaths) + if err != nil { + return err + } + outputPaths, err := pdfengines.SplitPdfStub(ctx, engine, mode, []string{outputPath}) if err != nil { return fmt.Errorf("split PDF: %w", err) } + err = pdfengines.WatermarkStub(ctx, engine, watermark, outputPaths) + if err != nil { + return fmt.Errorf("watermark PDFs: %w", err) + } + + err = pdfengines.StampStub(ctx, engine, stamp, outputPaths) + if err != nil { + return fmt.Errorf("stamp PDFs: %w", err) + } + convertOutputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths) if err != nil { return fmt.Errorf("convert PDF(s): %w", err) } - err = pdfengines.WatermarkStub(ctx, engine, watermark, convertOutputPaths) + // Metadata, embeds are written after Convert, as LibreOffice + // strips them during PDF/A conversion. + err = pdfengines.WriteMetadataStub(ctx, engine, metadata, convertOutputPaths) if err != nil { - return fmt.Errorf("watermark PDFs: %w", err) - } - - err = pdfengines.StampStub(ctx, engine, stamp, convertOutputPaths) - if err != nil { - return fmt.Errorf("stamp PDFs: %w", err) + return fmt.Errorf("write metadata: %w", err) } err = pdfengines.EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths) @@ -806,11 +818,6 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url return fmt.Errorf("embed files into PDFs: %w", err) } - err = pdfengines.WriteMetadataStub(ctx, engine, metadata, convertOutputPaths) - if err != nil { - return fmt.Errorf("write metadata: %w", err) - } - err = pdfengines.EncryptPdfStub(ctx, engine, userPassword, ownerPassword, convertOutputPaths) if err != nil { return fmt.Errorf("encrypt PDFs: %w", err) diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index 60506cb3..2f85b82e 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -195,6 +195,14 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap stamp.Expression = stampFiles[0] } + err = pdfengines.ValidatePdfFormatsCompat(pdfFormats, userPassword, embedPaths) + if err != nil { + return err + } + + hasPostProcessing := watermark.Source != "" || stamp.Source != "" || + len(embedPaths) > 0 || len(metadata) > 0 || flatten + outputPaths := make([]string, len(inputPaths)) for i, inputPath := range inputPaths { outputPaths[i] = ctx.GeneratePath(".pdf") @@ -230,9 +238,10 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap NativeTiledWatermarkText: nativeTiledWatermarkText, } - if nativePdfFormats && splitMode == zeroValuedSplitMode { + if nativePdfFormats && splitMode == zeroValuedSplitMode && !hasPostProcessing { // Only natively apply given PDF formats if we're not - // splitting the PDF later. + // splitting the PDF later and no post-processing features + // are enabled (as they would degrade compliance). options.PdfFormats = pdfFormats } @@ -298,7 +307,27 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } - if !nativePdfFormats || (nativePdfFormats && splitMode != zeroValuedSplitMode) { + err = pdfengines.WatermarkStub(ctx, engine, watermark, outputPaths) + if err != nil { + return fmt.Errorf("watermark PDFs: %w", err) + } + + err = pdfengines.StampStub(ctx, engine, stamp, outputPaths) + if err != nil { + return fmt.Errorf("stamp PDFs: %w", err) + } + + if flatten { + err = pdfengines.FlattenStub(ctx, engine, outputPaths) + if err != nil { + return fmt.Errorf("flatten PDFs: %w", err) + } + } + + needsConvertStub := !nativePdfFormats || + (nativePdfFormats && splitMode != zeroValuedSplitMode) || + (nativePdfFormats && hasPostProcessing) + if needsConvertStub { convertOutputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths) if err != nil { return fmt.Errorf("convert PDFs: %w", err) @@ -318,31 +347,16 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } - err = pdfengines.WatermarkStub(ctx, engine, watermark, outputPaths) - if err != nil { - return fmt.Errorf("watermark PDFs: %w", err) - } - - err = pdfengines.StampStub(ctx, engine, stamp, outputPaths) - if err != nil { - return fmt.Errorf("stamp PDFs: %w", err) - } - - err = pdfengines.EmbedFilesStub(ctx, engine, embedPaths, outputPaths) - if err != nil { - return fmt.Errorf("embed files into PDFs: %w", err) - } - + // Metadata, embeds are written after Convert, as LibreOffice + // strips them during PDF/A conversion. err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) if err != nil { 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) - } + err = pdfengines.EmbedFilesStub(ctx, engine, embedPaths, outputPaths) + if err != nil { + return fmt.Errorf("embed files into PDFs: %w", err) } err = pdfengines.EncryptPdfStub(ctx, engine, userPassword, ownerPassword, outputPaths) diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index fd67faf4..9470094b 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -166,6 +166,41 @@ func FormDataPdfBookmarks(form *api.FormData, mandatory bool) any { return bookmarks } +// ValidatePdfFormatsCompat checks for incompatible combinations of PDF formats +// with other features and returns an appropriate error if found. +func ValidatePdfFormatsCompat(pdfFormats gotenberg.PdfFormats, userPassword string, embedPaths []string) error { + zeroValued := gotenberg.PdfFormats{} + if pdfFormats == zeroValued { + return nil + } + + // PDF/A forbids encryption per the standard. + if pdfFormats.PdfA != "" && userPassword != "" { + return api.WrapError( + errors.New("PDF/A format is incompatible with encryption"), + api.NewSentinelHttpError( + http.StatusBadRequest, + "Invalid form data: PDF/A format is incompatible with encryption", + ), + ) + } + + // Only PDF/A-3 variants allow embedded file attachments. + if pdfFormats.PdfA != "" && len(embedPaths) > 0 { + if pdfFormats.PdfA != gotenberg.PdfA3a && pdfFormats.PdfA != gotenberg.PdfA3b && pdfFormats.PdfA != gotenberg.PdfA3u { + return api.WrapError( + fmt.Errorf("PDF format '%s' does not support embedded files", pdfFormats.PdfA), + api.NewSentinelHttpError( + http.StatusBadRequest, + fmt.Sprintf("Invalid form data: PDF format '%s' does not support embedded files; only PDF/A-3 variants allow attachments", pdfFormats.PdfA), + ), + ) + } + } + + return nil +} + // MergeStub merges given PDFs. If only one input PDF, it does nothing and // returns the corresponding input path. func MergeStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string) (string, error) { @@ -546,16 +581,18 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { stamp.Expression = stampFiles[0] } + err = ValidatePdfFormatsCompat(pdfFormats, userPassword, embedPaths) + if err != nil { + return err + } + outputPath := ctx.GeneratePath(".pdf") err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath) if err != nil { return fmt.Errorf("merge PDFs: %w", err) } - outputPaths, err := ConvertStub(ctx, engine, pdfFormats, []string{outputPath}) - if err != nil { - return fmt.Errorf("convert PDF: %w", err) - } + outputPaths := []string{outputPath} err = WatermarkStub(ctx, engine, watermark, outputPaths) if err != nil { @@ -567,11 +604,21 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("stamp PDFs: %w", err) } - err = EmbedFilesStub(ctx, engine, embedPaths, outputPaths) - if err != nil { - return fmt.Errorf("embed files into PDFs: %w", err) + if flatten { + err = FlattenStub(ctx, engine, outputPaths) + if err != nil { + return fmt.Errorf("flatten PDFs: %w", err) + } } + outputPaths, err = ConvertStub(ctx, engine, pdfFormats, outputPaths) + if err != nil { + return fmt.Errorf("convert PDF: %w", err) + } + + // Bookmarks, metadata, and embeds are written after Convert, + // as LibreOffice strips them during PDF/A conversion. + var finalBookmarks []gotenberg.Bookmark if b, ok := bookmarks.([]gotenberg.Bookmark); ok { finalBookmarks = b @@ -620,11 +667,9 @@ 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 = EmbedFilesStub(ctx, engine, embedPaths, outputPaths) + if err != nil { + return fmt.Errorf("embed files into PDFs: %w", err) } err = EncryptPdfStub(ctx, engine, userPassword, ownerPassword, outputPaths) @@ -679,41 +724,48 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route { stamp.Expression = stampFiles[0] } + err = ValidatePdfFormatsCompat(pdfFormats, userPassword, embedPaths) + if err != nil { + return err + } + outputPaths, err := SplitPdfStub(ctx, engine, mode, inputPaths) if err != nil { return fmt.Errorf("split PDFs: %w", err) } + err = WatermarkStub(ctx, engine, watermark, outputPaths) + if err != nil { + return fmt.Errorf("watermark PDFs: %w", err) + } + + err = StampStub(ctx, engine, stamp, outputPaths) + if err != nil { + return fmt.Errorf("stamp PDFs: %w", err) + } + + if flatten { + err = FlattenStub(ctx, engine, outputPaths) + if err != nil { + return fmt.Errorf("flatten PDFs: %w", err) + } + } + convertOutputPaths, err := ConvertStub(ctx, engine, pdfFormats, outputPaths) if err != nil { return fmt.Errorf("convert PDFs: %w", err) } - err = WatermarkStub(ctx, engine, watermark, convertOutputPaths) - if err != nil { - return fmt.Errorf("watermark PDFs: %w", err) - } - - err = StampStub(ctx, engine, stamp, convertOutputPaths) - if err != nil { - return fmt.Errorf("stamp PDFs: %w", err) - } - - err = EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths) - if err != nil { - return fmt.Errorf("embed files into PDFs: %w", err) - } - + // Metadata, embeds are written after Convert, as LibreOffice + // strips them during PDF/A conversion. err = WriteMetadataStub(ctx, engine, metadata, convertOutputPaths) if err != nil { return fmt.Errorf("write metadata: %w", err) } - if flatten { - err = FlattenStub(ctx, engine, convertOutputPaths) - if err != nil { - return fmt.Errorf("flatten PDFs: %w", err) - } + err = EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths) + if err != nil { + return fmt.Errorf("embed files into PDFs: %w", err) } err = EncryptPdfStub(ctx, engine, userPassword, ownerPassword, convertOutputPaths) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index 262ef4c7..5ded17f2 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -1033,11 +1033,11 @@ Feature: /forms/chromium/convert/html @stamp @flatten @embed - Scenario: POST /forms/chromium/convert/html (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds) + Scenario: POST /forms/chromium/convert/html (PDF/A-3b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds) 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 | + | pdfa | PDF/A-3b | 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 | @@ -1049,8 +1049,8 @@ Feature: /forms/chromium/convert/html 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 11 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 valid "PDF/A-3b" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 3 failed rule(s) Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded @@ -1064,11 +1064,8 @@ Feature: /forms/chromium/convert/html "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", diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index 8afa1c79..3c405ef0 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -1012,12 +1012,12 @@ Feature: /forms/chromium/convert/markdown @metadata @flatten @embed - Scenario: POST /forms/chromium/convert/markdown (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds) + Scenario: POST /forms/chromium/convert/markdown (PDF/A-3b & PDF/UA-1 & Metadata & Flatten & Embeds) 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 | + | pdfa | PDF/A-3b | 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 | @@ -1029,8 +1029,8 @@ Feature: /forms/chromium/convert/markdown 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 9 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 valid "PDF/A-3b" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 3 failed rule(s) Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded @@ -1044,11 +1044,8 @@ Feature: /forms/chromium/convert/markdown "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", diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index 7907d108..89e0d23d 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -1107,12 +1107,12 @@ Feature: /forms/chromium/convert/url @metadata @flatten @embed - Scenario: POST /forms/chromium/convert/url (PDF/A-1b & PDF/UA-1 & Metadata & Flatten) + Scenario: POST /forms/chromium/convert/url (PDF/A-3b & PDF/UA-1 & Metadata & Flatten & Embeds) 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 | + | pdfa | PDF/A-3b | 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 | @@ -1124,8 +1124,8 @@ Feature: /forms/chromium/convert/url 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 9 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 valid "PDF/A-3b" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 3 failed rule(s) Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded @@ -1139,11 +1139,8 @@ Feature: /forms/chromium/convert/url "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", diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature index 0271d6fd..86693fee 100644 --- a/test/integration/features/libreoffice_convert.feature +++ b/test/integration/features/libreoffice_convert.feature @@ -642,11 +642,11 @@ Feature: /forms/libreoffice/convert @stamp @flatten @embed - Scenario: POST /forms/libreoffice/convert (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds) + Scenario: POST /forms/libreoffice/convert (PDF/A-3b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds) 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 | + | pdfa | PDF/A-3b | 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 | | watermarkSource | text | field | @@ -662,8 +662,8 @@ Feature: /forms/libreoffice/convert 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 12 failed rule(s) - Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-3b" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 3 failed rule(s) Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded @@ -677,11 +677,8 @@ Feature: /forms/libreoffice/convert "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", diff --git a/test/integration/features/pdfengines_merge.feature b/test/integration/features/pdfengines_merge.feature index ed744903..7a1eac8e 100644 --- a/test/integration/features/pdfengines_merge.feature +++ b/test/integration/features/pdfengines_merge.feature @@ -446,12 +446,12 @@ Feature: /forms/pdfengines/merge @flatten @embed @bookmarks - Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds & Bookmarks) + Scenario: POST /forms/pdfengines/merge (PDF/A-3b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds & Bookmarks) 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 | + | pdfa | PDF/A-3b | 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 | | watermarkSource | text | field | @@ -477,8 +477,8 @@ Feature: /forms/pdfengines/merge """ Page 2 """ - Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 12 failed rule(s) - Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-3b" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 3 failed rule(s) Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded @@ -507,11 +507,8 @@ Feature: /forms/pdfengines/merge "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", @@ -595,3 +592,36 @@ Feature: /forms/pdfengines/merge | files | testdata/page_2.pdf | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" + + @convert + @encrypt + Scenario: POST /forms/pdfengines/merge (PDF/A + Encrypt => 400) + 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 | + | userPassword | secret | field | + Then the response status code should be 400 + + @convert + @embed + Scenario: POST /forms/pdfengines/merge (PDF/A-1b + Embeds => 400) + 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 | + | embeds | testdata/embed_1.xml | file | + Then the response status code should be 400 + + @convert + @embed + Scenario: POST /forms/pdfengines/merge (PDF/A-3b + Embeds => 200) + 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-3b | field | + | embeds | testdata/embed_1.xml | file | + Then the response status code should be 200 diff --git a/test/integration/features/pdfengines_split.feature b/test/integration/features/pdfengines_split.feature index 05914c0d..125dce01 100644 --- a/test/integration/features/pdfengines_split.feature +++ b/test/integration/features/pdfengines_split.feature @@ -524,13 +524,13 @@ Feature: /forms/pdfengines/split @stamp @flatten @embed - Scenario: POST /forms/pdfengines/split (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds) + Scenario: POST /forms/pdfengines/split (PDF/A-3b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds) 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 | + | pdfa | PDF/A-3b | 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 | | watermarkSource | text | field | @@ -560,8 +560,8 @@ Feature: /forms/pdfengines/split """ Page 3 """ - Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 12 failed rule(s) - Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/A-3b" with a tolerance of 5 failed rule(s) + Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 3 failed rule(s) Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded @@ -576,11 +576,8 @@ Feature: /forms/pdfengines/split "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", @@ -590,11 +587,8 @@ Feature: /forms/pdfengines/split "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", @@ -720,3 +714,39 @@ Feature: /forms/pdfengines/split | splitSpan | 2 | field | Then the response status code should be 200 Then the response header "Content-Type" should be "application/zip" + + @convert + @encrypt + Scenario: POST /forms/pdfengines/split (PDF/A + Encrypt => 400) + 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 | + | userPassword | secret | field | + Then the response status code should be 400 + + @convert + @embed + Scenario: POST /forms/pdfengines/split (PDF/A-1b + Embeds => 400) + 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 | + | embeds | testdata/embed_1.xml | file | + Then the response status code should be 400 + + @convert + @embed + Scenario: POST /forms/pdfengines/split (PDF/A-3b + Embeds => 200) + 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-3b | field | + | embeds | testdata/embed_1.xml | file | + Then the response status code should be 200