fix(pdgengines): encrypt behavior accross different engines

This commit is contained in:
Julien Neuhart
2025-09-01 20:26:34 +02:00
parent ba944f9dc2
commit 0ad6623e24
8 changed files with 165 additions and 19 deletions

View File

@@ -62,6 +62,7 @@ PDFENGINES_FLATTEN_ENGINES=qpdf
PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine
PDFENGINES_READ_METADATA_ENGINES=exiftool
PDFENGINES_WRITE_METADATA_ENGINES=exiftool
PDFENGINES_ENCRYPT_ENGINES=qpdf,pdfcpu,pdftk
PDFENGINES_DISABLE_ROUTES=false
PROMETHEUS_NAMESPACE=gotenberg
PROMETHEUS_COLLECT_INTERVAL=1s
@@ -136,6 +137,7 @@ run: ## Start a Gotenberg container
--pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \
--pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \
--pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \
--pdfengines-encrypt-engines=$(PDFENGINES_ENCRYPT_ENGINES) \
--pdfengines-disable-routes=$(PDFENGINES_DISABLE_ROUTES) \
--prometheus-namespace=$(PROMETHEUS_NAMESPACE) \
--prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \

View File

@@ -27,6 +27,10 @@ var (
// ErrPdfEncryptionNotSupported is returned when encryption
// is not supported by the PDF engine.
ErrPdfEncryptionNotSupported = errors.New("encryption not supported")
// ErrPdfEngineEncryptionPasswordsNotSupported is returned when provided
// passwords are not supported by the PDF engine.
ErrPdfEngineEncryptionPasswordsNotSupported = errors.New("passwords not supported")
)
const (

View File

@@ -270,6 +270,16 @@ func EncryptPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, userPassword,
for _, inputPath := range inputPaths {
err := engine.Encrypt(ctx, ctx.Log(), inputPath, userPassword, ownerPassword)
if err != nil {
if errors.Is(err, gotenberg.ErrPdfEngineEncryptionPasswordsNotSupported) {
return api.WrapError(
err,
api.NewSentinelHttpError(
http.StatusBadRequest,
"Invalid form data: both 'userPassword' and 'ownerPassword' form fields must be provided and different",
),
)
}
return fmt.Errorf("encrypt PDF '%s': %w", inputPath, err)
}
}

View File

@@ -151,13 +151,16 @@ func (engine *PdfTk) Encrypt(ctx context.Context, logger *zap.Logger, inputPath,
return errors.New("user password cannot be empty")
}
if ownerPassword == "" {
ownerPassword = userPassword
if ownerPassword == userPassword || ownerPassword == "" {
return gotenberg.ErrPdfEngineEncryptionPasswordsNotSupported
}
// Create a temp output file in the same directory.
tmpPath := inputPath + ".tmp"
var args []string
args = append(args, inputPath)
args = append(args, "output", inputPath)
args = append(args, "output", tmpPath)
args = append(args, "encrypt_128bit")
args = append(args, "user_pw", userPassword)
args = append(args, "owner_pw", ownerPassword)
@@ -172,6 +175,11 @@ func (engine *PdfTk) Encrypt(ctx context.Context, logger *zap.Logger, inputPath,
return fmt.Errorf("encrypt PDF with PDFtk: %w", err)
}
err = os.Rename(tmpPath, inputPath)
if err != nil {
return fmt.Errorf("rename temporary output file with input file: %w", err)
}
return nil
}

View File

@@ -185,8 +185,8 @@ func (engine *QPdf) Encrypt(ctx context.Context, logger *zap.Logger, inputPath,
var args []string
args = append(args, inputPath)
args = append(args, engine.globalArgs...)
args = append(args, "--encrypt", userPassword, ownerPassword, "256", "--use-aes=y", "--")
args = append(args, inputPath)
args = append(args, "--replace-input")
args = append(args, "--encrypt", userPassword, ownerPassword, "256", "--")
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
if err != nil {

View File

@@ -1,6 +1,6 @@
Feature: /forms/pdfengines/encrypt
Scenario: POST /forms/pdfengines/encrypt (default - QPDF - user password only)
Scenario: POST /forms/pdfengines/encrypt (default - 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 |
@@ -10,7 +10,31 @@ Feature: /forms/pdfengines/encrypt
Then there should be 1 PDF(s) in the response
Then the response PDF(s) should be encrypted
Scenario: POST /forms/pdfengines/encrypt (default - QPDF - both user and owner passwords)
Scenario: POST /forms/pdfengines/encrypt (default - 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 | 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 (QPDF - user password only)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_ENCRYPT_ENGINES | qpdf |
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 |
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 (QPDF - both user and owner passwords)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_ENCRYPT_ENGINES | qpdf |
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 |
@@ -23,18 +47,19 @@ Feature: /forms/pdfengines/encrypt
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 |
| PDFENGINES_ENCRYPT_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 |
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
Then the response status code should be 400
Then the response body should match string:
"""
Invalid form data: both 'userPassword' and 'ownerPassword' form fields must be provided and different
"""
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 |
| PDFENGINES_ENCRYPT_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 |
@@ -46,7 +71,7 @@ Feature: /forms/pdfengines/encrypt
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 |
| PDFENGINES_ENCRYPT_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 |
@@ -57,7 +82,7 @@ Feature: /forms/pdfengines/encrypt
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 |
| PDFENGINES_ENCRYPT_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 |

View File

@@ -1,6 +1,6 @@
Feature: /forms/pdfengines/merge
Scenario: POST /forms/pdfengines/merge (default - QPDF)
Scenario: POST /forms/pdfengines/merge (default)
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 |
@@ -21,6 +21,28 @@ Feature: /forms/pdfengines/merge
Page 2
"""
Scenario: POST /forms/pdfengines/merge (QPDF)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_MERGE_ENGINES | qpdf |
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s):
| files | testdata/page_1.pdf | file |
| files | testdata/page_2.pdf | file |
| Gotenberg-Output-Filename | foo | header |
Then the response status code should be 200
Then the response header "Content-Type" should be "application/pdf"
Then there should be 1 PDF(s) in the response
Then there should be the following file(s) in the response:
| foo.pdf |
Then the "foo.pdf" PDF should have 2 page(s)
Then the "foo.pdf" PDF should have the following content at page 1:
"""
Page 1
"""
Then the "foo.pdf" PDF should have the following content at page 2:
"""
Page 2
"""
Scenario: POST /forms/pdfengines/merge (pdfcpu)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_MERGE_ENGINES | pdfcpu |

View File

@@ -1,6 +1,6 @@
Feature: /forms/pdfengines/split
Scenario: POST /forms/pdfengines/split (Intervals - Default - pdfcpu)
Scenario: POST /forms/pdfengines/split (Intervals - Default)
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 |
@@ -27,7 +27,7 @@ Feature: /forms/pdfengines/split
Page 3
"""
Scenario: POST /forms/pdfengines/split (Pages - Default - pdfcpu)
Scenario: POST /forms/pdfengines/split (Pages - Default)
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 |
@@ -50,7 +50,7 @@ Feature: /forms/pdfengines/split
Page 3
"""
Scenario: POST /forms/pdfengines/split (Pages & Unify - Default - pdfcpu)
Scenario: POST /forms/pdfengines/split (Pages & Unify - Default)
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 |
@@ -72,6 +72,81 @@ Feature: /forms/pdfengines/split
Page 3
"""
Scenario: POST /forms/pdfengines/split (Intervals - pdfcpu)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_SPLIT_ENGINES | pdfcpu |
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s):
| files | testdata/pages_3.pdf | file |
| splitMode | intervals | field |
| splitSpan | 2 | field |
Then the response status code should be 200
Then the response header "Content-Type" should be "application/zip"
Then there should be 2 PDF(s) in the response
Then there should be the following file(s) in the response:
| pages_3_0.pdf |
| pages_3_1.pdf |
Then the "pages_3_0.pdf" PDF should have 2 page(s)
Then the "pages_3_1.pdf" PDF should have 1 page(s)
Then the "pages_3_0.pdf" PDF should have the following content at page 1:
"""
Page 1
"""
Then the "pages_3_0.pdf" PDF should have the following content at page 2:
"""
Page 2
"""
Then the "pages_3_1.pdf" PDF should have the following content at page 1:
"""
Page 3
"""
Scenario: POST /forms/pdfengines/split (Pages - pdfcpu)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_SPLIT_ENGINES | pdfcpu |
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s):
| files | testdata/pages_3.pdf | file |
| splitMode | pages | field |
| splitSpan | 2- | field |
Then the response status code should be 200
Then the response header "Content-Type" should be "application/zip"
Then there should be 2 PDF(s) in the response
Then there should be the following file(s) in the response:
| pages_3_0.pdf |
| pages_3_1.pdf |
Then the "pages_3_0.pdf" PDF should have 1 page(s)
Then the "pages_3_1.pdf" PDF should have 1 page(s)
Then the "pages_3_0.pdf" PDF should have the following content at page 1:
"""
Page 2
"""
Then the "pages_3_1.pdf" PDF should have the following content at page 1:
"""
Page 3
"""
Scenario: POST /forms/pdfengines/split (Pages & Unify - pdfcpu)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_SPLIT_ENGINES | pdfcpu |
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s):
| files | testdata/pages_3.pdf | file |
| splitMode | pages | field |
| splitSpan | 2- | field |
| splitUnify | true | field |
Then the response status code should be 200
Then the response header "Content-Type" should be "application/pdf"
Then there should be 1 PDF(s) in the response
Then there should be the following file(s) in the response:
| pages_3.pdf |
Then the "pages_3.pdf" PDF should have 2 page(s)
Then the "pages_3.pdf" PDF should have the following content at page 1:
"""
Page 2
"""
Then the "pages_3.pdf" PDF should have the following content at page 2:
"""
Page 3
"""
Scenario: POST /forms/pdfengines/split (Pages & Unify - QPDF)
Given I have a Gotenberg container with the following environment variable(s):
| PDFENGINES_SPLIT_ENGINES | qpdf |