From ba944f9dc2b86a3dfd3015546d6bc9f58b33d5d7 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 28 May 2025 15:21:41 +0200 Subject: [PATCH] chore: slight improvement of the encrypt feature to be more in line with others features --- pkg/gotenberg/pdfengine.go | 2 +- pkg/modules/libreoffice/routes.go | 10 +- pkg/modules/pdfcpu/pdfcpu.go | 5 +- pkg/modules/pdfengines/multi.go | 24 ++- pkg/modules/pdfengines/pdfengines.go | 2 +- pkg/modules/pdfengines/routes.go | 8 +- pkg/modules/pdftk/pdftk.go | 11 +- pkg/modules/qpdf/qpdf.go | 2 - .../features/chromium_convert_html.feature | 94 ++------- .../chromium_convert_markdown.feature | 102 +++------ .../features/chromium_convert_url.feature | 101 +++------ .../features/libreoffice_convert.feature | 110 ++-------- .../features/pdfengines_convert.feature | 75 ------- .../features/pdfengines_encrypt.feature | 193 ++++++++++-------- .../features/pdfengines_flatten.feature | 1 + .../features/pdfengines_merge.feature | 102 +++------ .../features/pdfengines_metadata.feature | 58 ------ .../features/pdfengines_split.feature | 92 +++------ test/integration/scenario/scenario.go | 123 ++++------- 19 files changed, 318 insertions(+), 797 deletions(-) diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index a27910ec..604cc185 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -104,7 +104,7 @@ type PdfEngine interface { // 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 + // 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 diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index 748f6a72..7acaea71 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -264,6 +264,11 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } + err = pdfengines.EncryptPdfStub(ctx, engine, userPassword, ownerPassword, outputPaths) + if err != nil { + return fmt.Errorf("encrypt PDFs: %w", err) + } + if len(outputPaths) > 1 && splitMode == zeroValuedSplitMode { // If .zip archive, document.docx -> document.docx.pdf. for i, inputPath := range inputPaths { @@ -278,11 +283,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } - err = pdfengines.EncryptPdfStub(ctx, engine, userPassword, ownerPassword, outputPaths) - if err != nil { - return fmt.Errorf("encrypt PDFs: %w", err) - } - err = ctx.AddOutputPaths(outputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index d681f952..97e25f58 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -177,17 +177,16 @@ func (engine *PdfCpu) Encrypt(ctx context.Context, logger *zap.Logger, inputPath return errors.New("user password cannot be empty") } - // If owner password is not provided, use the user password as owner password if ownerPassword == "" { ownerPassword = userPassword } var args []string args = append(args, "encrypt") - args = append(args, "-mode", "aes") // Use AES encryption + args = append(args, "-mode", "aes") args = append(args, "-upw", userPassword) args = append(args, "-opw", ownerPassword) - args = append(args, "-perm", "all") // Grant all permissions with owner password + args = append(args, "-perm", "all") args = append(args, inputPath, inputPath) cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index 640c776c..78a5277a 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -41,8 +41,8 @@ func newMultiPdfEngines( } } -// Merge tries to merge the given PDFs into a unique PDF thanks to its -// children. If the context is done, it stops and returns an error. +// Merge combines multiple PDF files into a single document using the first +// available engine that supports PDF merging. func (multi *multiPdfEngines) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { var err error errChan := make(chan error, 1) @@ -71,8 +71,8 @@ type splitResult struct { 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. +// Split divides the PDF into separate pages using the first available engine +// that supports PDF splitting. 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. @@ -102,8 +102,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, thanks to -// its children. If the context is done, it stops and returns an error +// Flatten merges existing annotation appearances with page content using the +// first available engine that supports flattening. func (multi *multiPdfEngines) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error { var err error errChan := make(chan error, 1) @@ -127,8 +127,8 @@ 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 -// children. If the context is done, it stops and returns an error. +// Convert transforms the given PDF to a specific PDF format using the first +// available engine that supports PDF conversion. func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { var err error errChan := make(chan error, 1) @@ -157,6 +157,8 @@ type readMetadataResult struct { err error } +// ReadMetadata extracts metadata from a PDF file using the first available +// engine that supports metadata reading. func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { var err error var mu sync.Mutex // to safely append errors. @@ -186,6 +188,8 @@ func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logg return nil, fmt.Errorf("read PDF metadata with multi PDF engines: %w", err) } +// WriteMetadata embeds metadata into a PDF file using the first available +// engine that supports metadata writing. func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { var err error errChan := make(chan error, 1) @@ -209,8 +213,8 @@ func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Log return fmt.Errorf("write PDF metadata with multi PDF engines: %w", err) } -// Encrypt adds password protection to a PDF file using the first available engine -// that supports password protection. +// Encrypt adds password protection to a PDF file using the first available +// engine that supports password protection. func (multi *multiPdfEngines) Encrypt(ctx context.Context, logger *zap.Logger, inputPath, userPassword, ownerPassword string) error { var err error errChan := make(chan error, 1) diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index 48f1050a..8e3bc175 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -210,7 +210,7 @@ func (mod *PdfEngines) SystemMessages() []string { 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[:], " ")), - fmt.Sprintf("password protection engines - %s", strings.Join(mod.encryptNames[:], " ")), + fmt.Sprintf("encrypt engines - %s", strings.Join(mod.encryptNames[:], " ")), } } diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index 262a18af..ce9135cb 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -254,7 +254,6 @@ func WriteMetadataStub(ctx *api.Context, engine gotenberg.PdfEngine, metadata ma return nil } -// EncryptPdfStub adds password protection to PDF files. // FormDataPdfEncrypt extracts encryption parameters from form data. func FormDataPdfEncrypt(form *api.FormData) (userPassword, ownerPassword string) { form.String("userPassword", &userPassword, "") @@ -262,6 +261,7 @@ func FormDataPdfEncrypt(form *api.FormData) (userPassword, ownerPassword string) return userPassword, ownerPassword } +// EncryptPdfStub adds password protection to PDF files. func EncryptPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, userPassword, ownerPassword string, inputPaths []string) error { if userPassword == "" { return nil @@ -459,7 +459,6 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route { form := ctx.FormData() pdfFormats := FormDataPdfFormats(form) - userPassword, ownerPassword := FormDataPdfEncrypt(form) var inputPaths []string err := form. @@ -496,11 +495,6 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route { } } - err = EncryptPdfStub(ctx, engine, userPassword, ownerPassword, outputPaths) - if err != nil { - return fmt.Errorf("encrypt PDFs: %w", err) - } - err = ctx.AddOutputPaths(outputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index 755d172b..c5b76076 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -151,7 +151,6 @@ func (engine *PdfTk) Encrypt(ctx context.Context, logger *zap.Logger, inputPath, return errors.New("user password cannot be empty") } - // If owner password is not provided, use the user password as owner password if ownerPassword == "" { ownerPassword = userPassword } @@ -160,14 +159,8 @@ func (engine *PdfTk) Encrypt(ctx context.Context, logger *zap.Logger, inputPath, args = append(args, inputPath) args = append(args, "output", inputPath) args = append(args, "encrypt_128bit") - - if userPassword != "" { - args = append(args, "user_pw", userPassword) - } - - if ownerPassword != "" { - args = append(args, "owner_pw", ownerPassword) - } + args = append(args, "user_pw", userPassword) + args = append(args, "owner_pw", ownerPassword) cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...) if err != nil { diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 8d2cd1f8..bba05c6b 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -178,12 +178,10 @@ func (engine *QPdf) Encrypt(ctx context.Context, logger *zap.Logger, inputPath, return errors.New("user password cannot be empty") } - // If owner password is not provided, use the user password as owner password if ownerPassword == "" { ownerPassword = userPassword } - // QPDF command to encrypt a PDF var args []string args = append(args, inputPath) args = append(args, engine.globalArgs...) diff --git a/test/integration/features/chromium_convert_html.feature b/test/integration/features/chromium_convert_html.feature index c34db024..10c84903 100644 --- a/test/integration/features/chromium_convert_html.feature +++ b/test/integration/features/chromium_convert_html.feature @@ -815,6 +815,28 @@ Feature: /forms/chromium/convert/html Then there should be 1 PDF(s) in the response Then the response PDF(s) should be flatten + Scenario: POST /forms/chromium/convert/html (Encrypt - user password only) + 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 | + | userPassword | 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 + Then the response PDF(s) should be encrypted + + Scenario: POST /forms/chromium/convert/html (Encrypt - both user and owner passwords) + 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 | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + # FIXME: once decrypt is done, add encrypt and check after the content of the PDF. 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): @@ -920,75 +942,3 @@ Feature: /forms/chromium/convert/html | files | testdata/page-1-html/index.html | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" - - Scenario: POST /forms/chromium/convert/html with encryption (user password only) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/html with encryption (user and owner passwords) - 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 | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/html with encryption and PDF/A conversion - 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 | - | userPassword | test123 | field | - | pdfa | PDF/A-1a | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/html with encryption and page splitting - 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 | - | userPassword | test123 | field | - | splitMode | intervals | field | - | splitSpan | 5 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/chromium/convert/html without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted - Then the "unencrypted.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/chromium_convert_markdown.feature b/test/integration/features/chromium_convert_markdown.feature index f080b1b4..349d52ac 100644 --- a/test/integration/features/chromium_convert_markdown.feature +++ b/test/integration/features/chromium_convert_markdown.feature @@ -939,6 +939,30 @@ Feature: /forms/chromium/convert/markdown Then there should be 1 PDF(s) in the response Then the response PDF(s) should be flatten + Scenario: POST /forms/chromium/convert/markdown (Encrypt - user password only) + 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 | + | userPassword | 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 + Then the response PDF(s) should be encrypted + + Scenario: POST /forms/chromium/convert/markdown (Encrypt - both user and owner passwords) + 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 | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + # FIXME: once decrypt is done, add encrypt and check after the content of the PDF. 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): @@ -1051,81 +1075,3 @@ Feature: /forms/chromium/convert/markdown | files | testdata/page-1-markdown/page_1.md | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" - - Scenario: POST /forms/chromium/convert/markdown with encryption (user password only) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/markdown with encryption (user and owner passwords) - 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 | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/markdown with encryption and PDF/A conversion - 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 | - | userPassword | test123 | field | - | pdfa | PDF/A-1a | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/markdown with encryption and page splitting - 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 | - | userPassword | test123 | field | - | splitMode | intervals | field | - | splitSpan | 5 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/chromium/convert/markdown without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted - Then the "unencrypted.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/chromium_convert_url.feature b/test/integration/features/chromium_convert_url.feature index db8f4773..3d5f772d 100644 --- a/test/integration/features/chromium_convert_url.feature +++ b/test/integration/features/chromium_convert_url.feature @@ -898,6 +898,30 @@ Feature: /forms/chromium/convert/url Then there should be 1 PDF(s) in the response Then the response PDF(s) should be flatten + Scenario: POST /forms/chromium/convert/url (Encrypt - user password only) + 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 | + | userPassword | 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 + Then the response PDF(s) should be encrypted + + Scenario: POST /forms/chromium/convert/url (Encrypt - both user and owner passwords) + 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 | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + # FIXME: once decrypt is done, add encrypt and check after the content of the PDF. 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 @@ -999,80 +1023,3 @@ Feature: /forms/chromium/convert/url | 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" - - Scenario: POST /forms/chromium/convert/url with encryption (user password only) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/url with encryption (user and owner passwords) - 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 | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/url with encryption and PDF/A conversion - 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 | - | userPassword | test123 | field | - | pdfa | PDF/A-1a | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/chromium/convert/url with encryption and page splitting - 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 | - | userPassword | test123 | field | - | splitMode | intervals | field | - | splitSpan | 5 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/chromium/convert/url without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted - Then the "unencrypted.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/libreoffice_convert.feature b/test/integration/features/libreoffice_convert.feature index 15ebb29f..0001369c 100644 --- a/test/integration/features/libreoffice_convert.feature +++ b/test/integration/features/libreoffice_convert.feature @@ -526,6 +526,28 @@ Feature: /forms/libreoffice/convert Then there should be 1 PDF(s) in the response Then the response PDF(s) should be flatten + Scenario: POST /forms/libreoffice/convert (Encrypt - user password only) + 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 | + | userPassword | 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 + Then the response PDF(s) should be encrypted + + Scenario: POST /forms/libreoffice/convert (Encrypt - both user and owner passwords) + 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 | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + # FIXME: once decrypt is done, add encrypt and check after the content of the PDF. 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): @@ -533,7 +555,6 @@ Feature: /forms/libreoffice/convert | 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" @@ -631,90 +652,3 @@ Feature: /forms/libreoffice/convert | files | testdata/page_1.docx | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" - - Scenario: POST /forms/libreoffice/convert with encryption (user password only) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/libreoffice/convert with encryption (user and owner passwords) - 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 | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/libreoffice/convert with encryption and PDF/A conversion - 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 | - | userPassword | test123 | field | - | pdfa | PDF/A-1a | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/libreoffice/convert with encryption (multiple files) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the response PDF(s) should be encrypted - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/libreoffice/convert with encryption and flattening - 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 | - | userPassword | test123 | field | - | flatten | true | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 1 page(s) - - Scenario: POST /forms/libreoffice/convert without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted - Then the "unencrypted.pdf" PDF should have 1 page(s) diff --git a/test/integration/features/pdfengines_convert.feature b/test/integration/features/pdfengines_convert.feature index b0a77dfb..867dfcf0 100644 --- a/test/integration/features/pdfengines_convert.feature +++ b/test/integration/features/pdfengines_convert.feature @@ -183,78 +183,3 @@ Feature: /forms/pdfengines/convert | pdfa | PDF/A-1b | field | Then the response status code should be 200 Then the response header "Content-Type" should be "application/pdf" - - Scenario: POST /forms/pdfengines/convert with encryption (user password only) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) - - Scenario: POST /forms/pdfengines/convert with encryption (user and owner passwords) - 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 | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) - - Scenario: POST /forms/pdfengines/convert with encryption (multiple files) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the response PDF(s) should be encrypted - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/pdfengines/convert with encryption and PDF/UA - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - - Scenario: POST /forms/pdfengines/convert without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted - Then the "unencrypted.pdf" PDF should be valid "PDF/A-1b" with a tolerance of 1 failed rule(s) diff --git a/test/integration/features/pdfengines_encrypt.feature b/test/integration/features/pdfengines_encrypt.feature index 28901690..bd81cb17 100644 --- a/test/integration/features/pdfengines_encrypt.feature +++ b/test/integration/features/pdfengines_encrypt.feature @@ -1,129 +1,152 @@ Feature: /forms/pdfengines/encrypt - Scenario: POST /forms/pdfengines/encrypt (default - QPDF) + Scenario: POST /forms/pdfengines/encrypt (default - QPDF - user password only) Given I have a default Gotenberg container When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | protected | header | + | files | testdata/page_1.pdf | file | + | userPassword | 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 - Then there should be the following file(s) in the response: - | protected.pdf | - Then the "protected.pdf" PDF should be encrypted + Then the response PDF(s) should be encrypted - Scenario: POST /forms/pdfengines/encrypt with user and owner passwords (QPDF) + Scenario: POST /forms/pdfengines/encrypt (default - QPDF - both user and owner passwords) Given I have a default Gotenberg container When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | protected | header | + | files | testdata/page_1.pdf | file | + | userPassword | foo | field | + | ownerPassword | bar | 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: - | protected.pdf | - Then the "protected.pdf" PDF should be encrypted + Then the response PDF(s) should be encrypted - Scenario: POST /forms/pdfengines/encrypt (PDFtk) + Scenario: POST /forms/pdfengines/encrypt (PDFtk - user password only) Given I have a Gotenberg container with the following environment variable(s): | PDFENGINES_PASSWORD_ENGINES | pdftk | When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | protected | header | + | files | testdata/page_1.pdf | file | + | userPassword | 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 - Then there should be the following file(s) in the response: - | protected.pdf | - Then the "protected.pdf" PDF should be encrypted + Then the response PDF(s) should be encrypted - Scenario: POST /forms/pdfengines/encrypt (pdfcpu) + Scenario: POST /forms/pdfengines/encrypt (PDFtk - both user and owner passwords) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_PASSWORD_ENGINES | pdftk | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + Scenario: POST /forms/pdfengines/encrypt (pdfcpu - user password only) Given I have a Gotenberg container with the following environment variable(s): | PDFENGINES_PASSWORD_ENGINES | pdfcpu | When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | protected | header | + | files | testdata/page_1.pdf | file | + | userPassword | 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 - Then there should be the following file(s) in the response: - | protected.pdf | - Then the "protected.pdf" PDF should be encrypted + Then the response PDF(s) should be encrypted - Scenario: POST /forms/pdfengines/encrypt with multiple files + Scenario: POST /forms/pdfengines/encrypt (pdfcpu - both user and owner passwords) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_PASSWORD_ENGINES | pdfcpu | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + Scenario: POST /forms/pdfengines/encrypt (Many PDFs) Given I have a default Gotenberg container When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | files | testdata/page_2.pdf | file | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | protected | header | + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | userPassword | foo | field | 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: - | protected.zip | - Then the "protected.zip" archive should contain 2 file(s) - Then the "protected.zip" archive should contain encrypted PDF file(s) + Then there should be 2 PDF(s) in the response + Then the response PDF(s) should be encrypted - Scenario: POST /forms/pdfengines/encrypt without required userPassword field + Scenario: POST /forms/pdfengines/encrypt (Bad Request) Given I have a default Gotenberg container When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | Gotenberg-Output-Filename | protected | header | + | files | testdata/page_1.pdf | file | Then the response status code should be 400 - Then the response body should contain "userPassword" - - Scenario: POST /forms/pdfengines/encrypt with password engines that don't support password protection - Given I have a Gotenberg container with the following environment variable(s): - | PDFENGINES_PASSWORD_ENGINES | exiftool | - When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.pdf | file | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | protected | header | - Then the response status code should be 500 - Then the response body should contain "password protection not supported" - - Scenario: POST /forms/pdfengines/encrypt (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/encrypt" 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | protected | 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: - | protected.pdf | - Then the "protected.pdf" PDF should be encrypted - - Scenario: POST /forms/pdfengines/encrypt (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/encrypt" endpoint with the following form data and header(s): - | files | testdata/page_1.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 1 page(s) - Then the "foo.pdf" PDF should have the following content at page 1: + Then the response body should match string: """ - Page 1 + Invalid form data: form field 'userPassword' is required """ Scenario: POST /forms/pdfengines/encrypt (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/encrypt" endpoint with the following form data and header(s): - | files | testdata/pages_3.pdf | file | + | files | testdata/page_1.pdf | file | Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/encrypt (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/encrypt" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | userPassword | foo | field | + | Gotenberg-Trace | forms_pdfengines_encrypt | 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_encrypt" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_encrypt" | + + Scenario: POST /forms/pdfengines/encrypt (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/encrypt" 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 | + | userPassword | 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 + Then the response PDF(s) should be encrypted + + Scenario: POST /forms/pdfengines/encrypt (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/encrypt" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | userPassword | foo | 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 response PDF(s) should be encrypted + + Scenario: POST /forms/pdfengines/encrypt (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/encrypt" 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/encrypt (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/encrypt" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | userPassword | foo | 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 index 521a93ae..96804dbd 100644 --- a/test/integration/features/pdfengines_flatten.feature +++ b/test/integration/features/pdfengines_flatten.feature @@ -79,6 +79,7 @@ Feature: /forms/pdfengines/flatten 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" + Then the response PDF(s) should be flatten Scenario: POST /forms/pdfengines/flatten (Webhook) Given I have a default Gotenberg container diff --git a/test/integration/features/pdfengines_merge.feature b/test/integration/features/pdfengines_merge.feature index b6fdae99..57cf2890 100644 --- a/test/integration/features/pdfengines_merge.feature +++ b/test/integration/features/pdfengines_merge.feature @@ -199,6 +199,30 @@ Feature: /forms/pdfengines/merge """ Then the response PDF(s) should be flatten + Scenario: POST /forms/pdfengines/merge (Encrypt - user password only) + 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 | + | userPassword | 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 + Then the response PDF(s) should be encrypted + + Scenario: POST /forms/pdfengines/merge (Encrypt - both user and owner passwords) + 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 | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + # FIXME: once decrypt is done, add encrypt and check after the content of the PDF. 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): @@ -322,81 +346,3 @@ 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" - - Scenario: POST /forms/pdfengines/merge with encryption (user password only) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 2 page(s) - - Scenario: POST /forms/pdfengines/merge with encryption (user and owner passwords) - 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 | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 2 page(s) - - Scenario: POST /forms/pdfengines/merge with encryption and PDF/A conversion - 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 | - | userPassword | test123 | field | - | pdfa | PDF/A-1a | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 2 page(s) - - Scenario: POST /forms/pdfengines/merge with encryption and flattening - 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 | - | userPassword | test123 | field | - | flatten | true | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - Then the "encrypted.pdf" PDF should have 2 page(s) - - Scenario: POST /forms/pdfengines/merge without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted - Then the "unencrypted.pdf" PDF should have 2 page(s) diff --git a/test/integration/features/pdfengines_metadata.feature b/test/integration/features/pdfengines_metadata.feature index 3cd0b291..9c75588d 100644 --- a/test/integration/features/pdfengines_metadata.feature +++ b/test/integration/features/pdfengines_metadata.feature @@ -284,61 +284,3 @@ Feature: /forms/pdfengines/{write|read} | files | teststore/foo.pdf | file | Then the response status code should be 200 Then the response header "Content-Type" should be "application/json" - - Scenario: POST /forms/pdfengines/metadata/write with encryption (user password only) - 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 | {"Title":"Encrypted Sample","Author":"Test Author","Subject":"Test Subject"} | field | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - - Scenario: POST /forms/pdfengines/metadata/write with encryption (user and owner passwords) - 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 | {"Title":"Encrypted Sample","Author":"Test Author","Subject":"Test Subject"} | field | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.pdf | - Then the "encrypted.pdf" PDF should be encrypted - - Scenario: POST /forms/pdfengines/metadata/write with encryption (multiple files) - 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 | {"Title":"Encrypted Sample","Author":"Test Author","Subject":"Test Subject"} | field | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the response PDF(s) should be encrypted - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/pdfengines/metadata/write without encryption (empty password) - 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 | {"Title":"Unencrypted Sample","Author":"Test Author","Subject":"Test Subject"} | field | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.pdf | - Then the "unencrypted.pdf" PDF should NOT be encrypted diff --git a/test/integration/features/pdfengines_split.feature b/test/integration/features/pdfengines_split.feature index d336239b..2cbbae5c 100644 --- a/test/integration/features/pdfengines_split.feature +++ b/test/integration/features/pdfengines_split.feature @@ -365,6 +365,32 @@ Feature: /forms/pdfengines/split """ Then the response PDF(s) should be flatten + Scenario: POST /forms/pdfengines/split (Encrypt - user password only) + 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 | + | userPassword | foo | 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 encrypted + + Scenario: POST /forms/pdfengines/split (Encrypt - both user and owner passwords) + 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 | + | userPassword | foo | field | + | ownerPassword | bar | 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 encrypted + + # FIXME: once decrypt is done, add encrypt and check after the content of the PDFs. 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): @@ -549,69 +575,3 @@ 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" - - Scenario: POST /forms/pdfengines/split with encryption (intervals) - 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 | - | userPassword | test123 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the response PDF(s) should be encrypted - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/pdfengines/split with encryption (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_3.pdf | file | - | splitMode | pages | field | - | splitSpan | 2- | field | - | userPassword | user123 | field | - | ownerPassword | owner456 | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the response PDF(s) should be encrypted - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/pdfengines/split with encryption and PDF/A conversion - 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 | - | userPassword | test123 | field | - | pdfa | PDF/A-1a | field | - | Gotenberg-Output-Filename | encrypted | 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: - | encrypted.zip | - Then the response PDF(s) should be encrypted - Then the "encrypted.zip" archive should contain encrypted PDF file(s) - - Scenario: POST /forms/pdfengines/split without encryption (empty password) - 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 | - | userPassword | | field | - | Gotenberg-Output-Filename | unencrypted | 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: - | unencrypted.zip | - Then the "unencrypted.zip" archive should contain 2 file(s) - Then the "unencrypted.zip" archive should NOT contain encrypted PDF file(s) diff --git a/test/integration/scenario/scenario.go b/test/integration/scenario/scenario.go index 8a666675..4852e389 100644 --- a/test/integration/scenario/scenario.go +++ b/test/integration/scenario/scenario.go @@ -580,6 +580,7 @@ func (s *scenario) thePdfsShouldBeValidWithAToleranceOf(ctx context.Context, kin return fmt.Errorf("unknown %q", validate) } + re := regexp.MustCompile(`failedRules="(\d+)"`) for _, path := range paths { cmd := []string{ "verapdf", @@ -593,9 +594,7 @@ func (s *scenario) thePdfsShouldBeValidWithAToleranceOf(ctx context.Context, kin 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") } @@ -812,6 +811,8 @@ func (s *scenario) thePdfsShouldBeFlatten(ctx context.Context, kind, should stri return fmt.Errorf("walk %q: %w", s.workdir, err) } + invert := should == "should NOT" + for _, path := range paths { cmd := []string{ "verapdf", @@ -826,7 +827,6 @@ func (s *scenario) thePdfsShouldBeFlatten(ctx context.Context, kind, should stri 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) } @@ -839,93 +839,53 @@ func (s *scenario) thePdfsShouldBeFlatten(ctx context.Context, kind, should stri return nil } -// thePdfShouldBeEncrypted checks if a PDF file is encrypted or not encrypted based on condition. -func (s *scenario) thePdfShouldBeEncrypted(ctx context.Context, filename, should string) error { - filePath := fmt.Sprintf("%s/%s", s.workdir, filename) +func (s *scenario) thePdfsShouldBeEncrypted(ctx context.Context, kind string, should string) error { + dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) - cmd := []string{ - "qpdf", - "--check", - filepath.Base(filePath), + _, err := os.Stat(dirPath) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", dirPath) } - output, err := execCommandInIntegrationToolsContainer(ctx, cmd, filePath) - - invert := should == "should NOT" - isEncrypted := err != nil && (strings.Contains(output, "password") || strings.Contains(output, "encrypted")) - - if invert && isEncrypted { - return fmt.Errorf("expected PDF %s to not be encrypted, but it is encrypted", filename) - } - - if !invert && !isEncrypted { - return fmt.Errorf("expected PDF %s to be encrypted, but it is not", filename) - } - - return nil -} - -// theArchiveShouldContainEncryptedPdfFiles checks if a zip archive contains encrypted PDF files based on condition. -func (s *scenario) theArchiveShouldContainEncryptedPdfFiles(ctx context.Context, archiveFilename, should string) error { - archivePath := fmt.Sprintf("%s/%s", s.workdir, archiveFilename) - - // First, extract the archive inside the container - extractCmd := []string{ - "sh", - "-c", - fmt.Sprintf("mkdir -p /tmp/extract && unzip -o %s -d /tmp/extract", filepath.Base(archivePath)), - } - - _, err := execCommandInIntegrationToolsContainer(ctx, extractCmd, archivePath) - if err != nil { - return fmt.Errorf("extract archive in container: %w", err) - } - - // List PDF files in the extracted directory - listCmd := []string{ - "sh", - "-c", - "find /tmp/extract -name '*.pdf' -type f", - } - - output, err := execCommandInIntegrationToolsContainer(ctx, listCmd, archivePath) - if err != nil { - return fmt.Errorf("list PDFs in container: %w", err) - } - - // No PDFs found - if output == "" { - return fmt.Errorf("no PDF files found in archive %s", archiveFilename) - } - - // Check each PDF for password protection - pdfPaths := strings.Split(strings.TrimSpace(output), "\n") - foundProtectedPdf := false - - for _, pdfPath := range pdfPaths { - checkCmd := []string{ - "qpdf", - "--check", - pdfPath, + var paths []string + err = filepath.Walk(dirPath, func(path string, info os.FileInfo, pathErr error) error { + if pathErr != nil { + return pathErr } - - checkOutput, err := execCommandInIntegrationToolsContainer(ctx, checkCmd, archivePath) - - // If we get a password error, we found a protected PDF - if err != nil && (strings.Contains(checkOutput, "password") || strings.Contains(checkOutput, "encrypted")) { - foundProtectedPdf = true - break + if strings.EqualFold(filepath.Ext(info.Name()), ".pdf") { + paths = append(paths, path) } + return nil + }) + if err != nil { + return fmt.Errorf("walk %q: %w", dirPath, err) } invert := should == "should NOT" + re := regexp.MustCompile(`CommandLineError:Incorrectpassword`) - if invert && foundProtectedPdf { - return fmt.Errorf("found encrypted PDF files in archive %s, but expected none", archiveFilename) - } + for _, path := range paths { + cmd := []string{ + "pdfinfo", + filepath.Base(path), + } - if !invert && !foundProtectedPdf { - return fmt.Errorf("no encrypted PDF files found in archive %s", archiveFilename) + output, err := execCommandInIntegrationToolsContainer(ctx, cmd, path) + if err != nil { + return fmt.Errorf("exec %q: %w", cmd, err) + } + + output = strings.ReplaceAll(output, " ", "") + output = strings.ReplaceAll(output, "\n", "") + matches := re.FindStringSubmatch(output) + isEncrypted := len(matches) >= 1 && matches[0] == "CommandLineError:Incorrectpassword" + + if invert && isEncrypted { + return fmt.Errorf("PDF %q is encrypted", path) + } + if !invert && !isEncrypted { + return fmt.Errorf("PDF %q is not encrypted: %q", path, output) + } } return nil @@ -963,11 +923,10 @@ func InitializeScenario(ctx *godog.ScenarioContext) { 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 (response|webhook request) PDF\(s\) (should|should NOT) be encrypted`, s.thePdfsShouldBeEncrypted) 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.Then(`^the "([^"]*)" PDF (should|should NOT) be encrypted$`, s.thePdfShouldBeEncrypted) - ctx.Then(`^the "([^"]*)" archive (should|should NOT) contain encrypted PDF file\(s\)$`, s.theArchiveShouldContainEncryptedPdfFiles) 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))