Compare commits

...

16 Commits

Author SHA1 Message Date
Julien Neuhart
a780373cfc chore(go): update to 1.22 2024-03-15 16:23:27 +01:00
Julien Neuhart
5ad90ccdb8 fix: file rename now correctly works for zip archive 2024-03-15 16:12:18 +01:00
Julien Neuhart
ef97d81ca8 fix(libreoffice): wrong number of file extensions 2024-03-15 11:37:34 +01:00
Julien Neuhart
c9f7b642c5 fix(api): wrong expected message in tests 2024-03-15 11:25:55 +01:00
Julien Neuhart
770962be3f chore(deps): update golangci-lint 2024-03-15 11:22:32 +01:00
Julien Neuhart
d6c857f4a8 chore(libreoffice): disable PDF tagging by default - for consistency with Chromium 2024-03-15 11:11:51 +01:00
Julien Neuhart
7b7ffb427d fix(chromium): disable PDF tagging 2024-03-15 10:56:35 +01:00
Julien Neuhart
b80318d1b6 chore(deps): update Go dependencies 2024-03-15 09:50:19 +01:00
dependabot[bot]
3997d69e49 chore(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0
Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-14 07:10:17 +01:00
Julien Neuhart
4af41b33c0 chore: update Go dependencies 2024-03-04 11:33:10 +01:00
Julien Neuhart
0f8bbead66 fix(api): do not catch gotenberg.ErrPdfEngineMethodNotSupported as it may hides other errors 2024-03-04 11:17:36 +01:00
Julien Neuhart
804fcff5b6 fix(chromium): remove tagged PDF by default 2024-03-04 10:28:54 +01:00
Julien Neuhart
d18e6920fc chore(libreoffice): order ext list alphabetically 2024-03-04 10:09:42 +01:00
eMerzh
c95174ff11 feat(libreoffice): add macOS *.key support 2024-02-26 16:47:52 +01:00
Julien Neuhart
c70329c22b chore(api): improve gotenberg.ErrPdfFormatNotSupported HTTP message 2024-02-24 14:55:48 +01:00
Julien Neuhart
34f69263a2 feat(libreoffice): explicity set tagged PDF as default 2024-02-24 13:45:01 +01:00
23 changed files with 400 additions and 169 deletions

View File

@@ -17,14 +17,14 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.22'
cache: false cache: false
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Run linters - name: Run linters
uses: golangci/golangci-lint-action@v4 uses: golangci/golangci-lint-action@v4
with: with:
version: v1.55.2 version: v1.56.2
tests: tests:
needs: needs:

View File

@@ -5,14 +5,14 @@ help: ## Show the help
.PHONY: it .PHONY: it
it: build build-tests ## Initialize the development environment it: build build-tests ## Initialize the development environment
GOLANG_VERSION=1.21 GOLANG_VERSION=1.22
DOCKER_REPOSITORY=gotenberg DOCKER_REPOSITORY=gotenberg
GOTENBERG_VERSION=snapshot GOTENBERG_VERSION=snapshot
GOTENBERG_USER_GID=1001 GOTENBERG_USER_GID=1001
GOTENBERG_USER_UID=1001 GOTENBERG_USER_UID=1001
NOTO_COLOR_EMOJI_VERSION=v2.042 # See https://github.com/googlefonts/noto-emoji/releases. NOTO_COLOR_EMOJI_VERSION=v2.042 # See https://github.com/googlefonts/noto-emoji/releases.
PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package. PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package.
GOLANGCI_LINT_VERSION=v1.55.2 # See https://github.com/golangci/golangci-lint/releases. GOLANGCI_LINT_VERSION=v1.56.2 # See https://github.com/golangci/golangci-lint/releases.
.PHONY: build .PHONY: build
build: ## Build the Gotenberg's Docker image build: ## Build the Gotenberg's Docker image

24
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/gotenberg/gotenberg/v8 module github.com/gotenberg/gotenberg/v8
go 1.21 go 1.22
require ( require (
github.com/alexliesenfeld/health v0.8.0 github.com/alexliesenfeld/health v0.8.0
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e github.com/chromedp/cdproto v0.0.0-20240312231614-1e5096e63154
github.com/chromedp/chromedp v0.9.5 github.com/chromedp/chromedp v0.9.5
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@@ -19,24 +19,24 @@ require (
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/nwaples/rardecode v1.1.3 // indirect github.com/nwaples/rardecode v1.1.3 // indirect
github.com/pdfcpu/pdfcpu v0.6.0 github.com/pdfcpu/pdfcpu v0.7.0
github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.19.0
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/ulikunitz/xz v0.5.11 // indirect github.com/ulikunitz/xz v0.5.11 // indirect
go.uber.org/multierr v1.11.0 go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.19.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/image v0.15.0 // indirect golang.org/x/image v0.15.0 // indirect
golang.org/x/net v0.21.0 golang.org/x/net v0.22.0
golang.org/x/sync v0.6.0 golang.org/x/sync v0.6.0
golang.org/x/sys v0.17.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.17.0 golang.org/x/term v0.18.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
) )
require github.com/dlclark/regexp2 v1.10.0 require github.com/dlclark/regexp2 v1.11.0
require ( require (
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
@@ -56,12 +56,12 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/common v0.50.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.13.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
google.golang.org/protobuf v1.32.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

44
go.sum
View File

@@ -10,16 +10,16 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e h1:kXEolCWQZzuEFcuaTzfqXToX+e29OcvK87BcBiBBJ1c= github.com/chromedp/cdproto v0.0.0-20240312231614-1e5096e63154 h1:jeAmkzyOAQBPRmZMhX+i/CJv0VViLkHk1nF0qx8s0Mk=
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/cdproto v0.0.0-20240312231614-1e5096e63154/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg= github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y= github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@@ -87,8 +87,8 @@ github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9l
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pdfcpu/pdfcpu v0.6.0 h1:z4kARP5bcWa39TTYMcN/kjBnm7MvhTWjXgeYmkdAGMI= github.com/pdfcpu/pdfcpu v0.7.0 h1:cd7/z7hAyyDuzdciKfNZyQ3TYreJza2DsuPdIHYURcA=
github.com/pdfcpu/pdfcpu v0.6.0/go.mod h1:kmpD0rk8YnZj0l3qSeGBlAB+XszHUgNv//ORH/E7EYo= github.com/pdfcpu/pdfcpu v0.7.0/go.mod h1:kmpD0rk8YnZj0l3qSeGBlAB+XszHUgNv//ORH/E7EYo=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
@@ -96,14 +96,14 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -134,26 +134,26 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -51,3 +51,11 @@ func (fs *FileSystem) MkdirAll() (string, error) {
return path, nil return path, nil
} }
// PathRename defines the method signature for renaming files. Implement this
// interface if you don't want to rely on [os.Rename], notably for testing
// purpose.
type PathRename interface {
// Rename uses the same signature as [os.Rename].
Rename(oldpath, newpath string) error
}

View File

@@ -127,6 +127,15 @@ func (provider *MetricsProviderMock) Metrics() ([]Metric, error) {
return provider.MetricsMock() return provider.MetricsMock()
} }
// PathRenameMock is a mock for the [PathRename] interface.
type PathRenameMock struct {
RenameMock func(oldpath, newpath string) error
}
func (rename *PathRenameMock) Rename(oldpath, newpath string) error {
return rename.RenameMock(oldpath, newpath)
}
// Interface guards. // Interface guards.
var ( var (
_ Module = (*ModuleMock)(nil) _ Module = (*ModuleMock)(nil)

View File

@@ -188,3 +188,16 @@ func TestMetricsProviderMock(t *testing.T) {
t.Errorf("expected no error from MetricsProviderMock.Metrics, but got: %v", err) t.Errorf("expected no error from MetricsProviderMock.Metrics, but got: %v", err)
} }
} }
func TestPathRenameMock(t *testing.T) {
mock := &PathRenameMock{
RenameMock: func(oldpath, newpath string) error {
return nil
},
}
err := mock.Rename("", "")
if err != nil {
t.Errorf("expected no error from PathRenameMock.Rename, but got: %v", err)
}
}

View File

@@ -34,18 +34,24 @@ var (
// Context is the request context for a "multipart/form-data" requests. // Context is the request context for a "multipart/form-data" requests.
type Context struct { type Context struct {
dirPath string dirPath string
values map[string][]string values map[string][]string
files map[string]string files map[string]string
outputPaths []string outputPaths []string
cancelled bool
cancelled bool logger *zap.Logger
logger *zap.Logger echoCtx echo.Context
echoCtx echo.Context pathRename gotenberg.PathRename
context.Context context.Context
} }
type osPathRename struct{}
func (o *osPathRename) Rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
// newContext returns a [Context] by parsing a "multipart/form-data" request. // newContext returns a [Context] by parsing a "multipart/form-data" request.
func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSystem, timeout time.Duration) (*Context, context.CancelFunc, error) { func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSystem, timeout time.Duration) (*Context, context.CancelFunc, error) {
processCtx, processCancel := context.WithTimeout(context.Background(), timeout) processCtx, processCancel := context.WithTimeout(context.Background(), timeout)
@@ -55,6 +61,7 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
cancelled: false, cancelled: false,
logger: logger, logger: logger,
echoCtx: echoCtx, echoCtx: echoCtx,
pathRename: new(osPathRename),
Context: processCtx, Context: processCtx,
} }
@@ -191,14 +198,19 @@ func (ctx *Context) FormData() *FormData {
} }
// GeneratePath generates a path within the context's working directory. // GeneratePath generates a path within the context's working directory.
// It either generates a new UUID-based filename or uses the provided filename. // It generates a new UUID-based filename. It does not create a file.
// It does not create a file. func (ctx *Context) GeneratePath(extension string) string {
func (ctx *Context) GeneratePath(filename, extension string) string { return fmt.Sprintf("%s/%s%s", ctx.dirPath, uuid.New().String(), extension)
if filename == "" { }
// Generate a new UUID-based filename
filename = uuid.New().String() // Rename is just a wrapper around [os.Rename], as we need to mock this
// behavior in our tests.
func (ctx *Context) Rename(oldpath, newpath string) error {
err := ctx.pathRename.Rename(oldpath, newpath)
if err != nil {
return fmt.Errorf("rename path: %w", err)
} }
return fmt.Sprintf("%s/%s%s", ctx.dirPath, filename, extension) return nil
} }
// AddOutputPaths adds the given paths. Those paths will be used later to build // AddOutputPaths adds the given paths. Those paths will be used later to build
@@ -250,7 +262,7 @@ func (ctx *Context) BuildOutputFile() (string, error) {
ImplicitTopLevelFolder: false, ImplicitTopLevelFolder: false,
} }
archivePath := ctx.GeneratePath("", ".zip") archivePath := ctx.GeneratePath(".zip")
err := z.Archive(ctx.outputPaths, archivePath) err := z.Archive(ctx.outputPaths, archivePath)
if err != nil { if err != nil {
@@ -273,3 +285,8 @@ func (ctx *Context) OutputFilename(outputPath string) string {
return fmt.Sprintf("%s%s", filename, filepath.Ext(outputPath)) return fmt.Sprintf("%s%s", filename, filepath.Ext(outputPath))
} }
// Interface guard.
var (
_ gotenberg.PathRename = (*osPathRename)(nil)
)

View File

@@ -3,21 +3,77 @@ package api
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/google/uuid"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
) )
func TestOsPathRename_Rename(t *testing.T) {
dirPath, err := gotenberg.NewFileSystem().MkdirAll()
if err != nil {
t.Fatalf("create working directory: %v", err)
}
path := "/tests/test/testdata/api/sample1.txt"
copyPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString()))
in, err := os.Open(path)
if err != nil {
t.Fatalf("open file: %v", err)
}
defer func() {
err := in.Close()
if err != nil {
t.Fatalf("close file: %v", err)
}
}()
out, err := os.Create(copyPath)
if err != nil {
t.Fatalf("create new file: %v", err)
}
defer func() {
err := out.Close()
if err != nil {
t.Fatalf("close new file: %v", err)
}
}()
_, err = io.Copy(out, in)
if err != nil {
t.Fatalf("copy file to new file: %v", err)
}
rename := new(osPathRename)
newPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString()))
err = rename.Rename(copyPath, newPath)
if err != nil {
t.Errorf("expected no error but got: %v", err)
}
err = os.RemoveAll(dirPath)
if err != nil {
t.Fatalf("remove working directory: %v", err)
}
}
func TestNewContext(t *testing.T) { func TestNewContext(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
scenario string scenario string
@@ -190,14 +246,44 @@ func TestContext_GeneratePath(t *testing.T) {
dirPath: "/foo", dirPath: "/foo",
} }
path := ctx.GeneratePath("", ".pdf") path := ctx.GeneratePath(".pdf")
if !strings.HasPrefix(path, ctx.dirPath) { if !strings.HasPrefix(path, ctx.dirPath) {
t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath) t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath)
} }
}
path = ctx.GeneratePath("foo.txt", ".pdf") func TestContext_Rename(t *testing.T) {
if !strings.Contains(path, "foo.txt.pdf") { for _, tc := range []struct {
t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath) scenario string
ctx *Context
expectError bool
}{
{
scenario: "failure",
ctx: &Context{pathRename: &gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return errors.New("cannot rename")
}}},
expectError: true,
},
{
scenario: "success",
ctx: &Context{pathRename: &gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return nil
}}},
expectError: false,
},
} {
t.Run(tc.scenario, func(t *testing.T) {
err := tc.ctx.Rename("", "")
if tc.expectError && err == nil {
t.Fatal("expected error but got none", err)
}
if !tc.expectError && err != nil {
t.Fatalf("expected no error but got: %v", err)
}
})
} }
} }

View File

@@ -40,12 +40,8 @@ func ParseError(err error) (int, string) {
return http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests) return http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)
} }
if errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
return http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)
}
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) { if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
return http.StatusBadRequest, "A least one PDF engine does not handle one of the requested PDF format, while other have failed to convert for other reasons" return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues"
} }
var httpErr HttpError var httpErr HttpError

View File

@@ -38,15 +38,10 @@ func TestParseError(t *testing.T) {
expectStatus: http.StatusTooManyRequests, expectStatus: http.StatusTooManyRequests,
expectMessage: http.StatusText(http.StatusTooManyRequests), expectMessage: http.StatusText(http.StatusTooManyRequests),
}, },
{
err: gotenberg.ErrPdfEngineMethodNotSupported,
expectStatus: http.StatusNotImplemented,
expectMessage: http.StatusText(http.StatusNotImplemented),
},
{ {
err: gotenberg.ErrPdfFormatNotSupported, err: gotenberg.ErrPdfFormatNotSupported,
expectStatus: http.StatusBadRequest, expectStatus: http.StatusBadRequest,
expectMessage: "A least one PDF engine does not handle one of the requested PDF format, while other have failed to convert for other reasons", expectMessage: "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues",
}, },
{ {
err: WrapError( err: WrapError(

View File

@@ -4,6 +4,8 @@ import (
"github.com/alexliesenfeld/health" "github.com/alexliesenfeld/health"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
) )
// ContextMock is a helper for tests. // ContextMock is a helper for tests.
@@ -76,7 +78,7 @@ func (ctx *ContextMock) SetLogger(logger *zap.Logger) {
ctx.logger = logger ctx.logger = logger
} }
// SetEchoContext sets the echo.Context. // SetEchoContext sets the [echo.Context].
// //
// ctx := &api.ContextMock{Context: &api.Context{}} // ctx := &api.ContextMock{Context: &api.Context{}}
// ctx.setEchoContext(c) // ctx.setEchoContext(c)
@@ -84,6 +86,14 @@ func (ctx *ContextMock) SetEchoContext(c echo.Context) {
ctx.Context.echoCtx = c ctx.Context.echoCtx = c
} }
// SetPathRename sets the [gotenberg.PathRename].
//
// ctx := &api.ContextMock{Context: &api.Context{}}
// ctx.setPathRename(rename)
func (ctx *ContextMock) SetPathRename(rename gotenberg.PathRename) {
ctx.Context.pathRename = rename
}
// RouterMock is a mock for the [Router] interface. // RouterMock is a mock for the [Router] interface.
type RouterMock struct { type RouterMock struct {
RoutesMock func() ([]Route, error) RoutesMock func() ([]Route, error)

View File

@@ -117,6 +117,19 @@ func TestContextMock_SetEchoContext(t *testing.T) {
} }
} }
func TestContextMock_SetPathRename(t *testing.T) {
mock := ContextMock{&Context{}}
expect := new(osPathRename)
mock.SetPathRename(expect)
actual := mock.pathRename
if actual != expect {
t.Errorf("expected %v but got %v", expect, actual)
}
}
func TestRouterMock(t *testing.T) { func TestRouterMock(t *testing.T) {
mock := &RouterMock{ mock := &RouterMock{
RoutesMock: func() ([]Route, error) { RoutesMock: func() ([]Route, error) {

View File

@@ -90,6 +90,8 @@ func (b *chromiumBrowser) Start(logger *zap.Logger) error {
// https://github.com/puppeteer/puppeteer/issues/2410 // https://github.com/puppeteer/puppeteer/issues/2410
chromedp.Flag("font-render-hinting", "none"), chromedp.Flag("font-render-hinting", "none"),
chromedp.UserDataDir(b.userProfileDirPath), chromedp.UserDataDir(b.userProfileDirPath),
// See https://github.com/gotenberg/gotenberg/issues/831.
chromedp.Flag("disable-pdf-tagging", true),
) )
if b.arguments.incognito { if b.arguments.incognito {
@@ -164,7 +166,7 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
go func() { go func() {
// FIXME: Chromium seems to recreate the user profile directory // FIXME: Chromium seems to recreate the user profile directory
// right after its deletion if we do not wait a certain amount // right after its deletion if we do not wait a certain amount
// of time before re-deleting it. // of time before deleting it.
<-time.After(10 * time.Second) <-time.After(10 * time.Second)
err := os.RemoveAll(userProfileDirPath) err := os.RemoveAll(userProfileDirPath)

View File

@@ -496,7 +496,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
) )
} }
inputPath = ctx.GeneratePath("", ".html") inputPath = ctx.GeneratePath(".html")
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600) err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
if err != nil { if err != nil {
@@ -507,7 +507,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
} }
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, pdfFormats gotenberg.PdfFormats, options PdfOptions) error { func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, pdfFormats gotenberg.PdfFormats, options PdfOptions) error {
outputPath := ctx.GeneratePath("", ".pdf") outputPath := ctx.GeneratePath(".pdf")
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options) err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
err = handleChromiumError(err, options.Options) err = handleChromiumError(err, options.Options)
@@ -551,7 +551,7 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
zeroValued := gotenberg.PdfFormats{} zeroValued := gotenberg.PdfFormats{}
if pdfFormats != zeroValued { if pdfFormats != zeroValued {
convertInputPath := outputPath convertInputPath := outputPath
convertOutputPath := ctx.GeneratePath("", ".pdf") convertOutputPath := ctx.GeneratePath(".pdf")
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath) err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
if err != nil { if err != nil {
@@ -572,7 +572,7 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
func screenshotUrl(ctx *api.Context, chromium Api, url string, options ScreenshotOptions) error { func screenshotUrl(ctx *api.Context, chromium Api, url string, options ScreenshotOptions) error {
ext := fmt.Sprintf(".%s", options.Format) ext := fmt.Sprintf(".%s", options.Format)
outputPath := ctx.GeneratePath("", ext) outputPath := ctx.GeneratePath(ext)
err := chromium.Screenshot(ctx, ctx.Log(), url, outputPath, options) err := chromium.Screenshot(ctx, ctx.Log(), url, outputPath, options)
err = handleChromiumError(err, options.Options) err = handleChromiumError(err, options.Options)

View File

@@ -47,7 +47,9 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
WithMarginRight(options.MarginRight). WithMarginRight(options.MarginRight).
WithPageRanges(pageRanges). WithPageRanges(pageRanges).
WithPreferCSSPageSize(options.PreferCssPageSize). WithPreferCSSPageSize(options.PreferCssPageSize).
WithGenerateTaggedPDF(true) // Does not seem to work.
// See https://github.com/gotenberg/gotenberg/issues/831.
WithGenerateTaggedPDF(false)
hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate || hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate ||
options.FooterTemplate != DefaultPdfOptions().FooterTemplate options.FooterTemplate != DefaultPdfOptions().FooterTemplate

View File

@@ -267,85 +267,86 @@ func (a *Api) Pdf(ctx context.Context, logger *zap.Logger, inputPath, outputPath
func (a *Api) Extensions() []string { func (a *Api) Extensions() []string {
return []string{ return []string{
".bib", ".bib",
".doc",
".xml",
".docx",
".fodt",
".html",
".ltx",
".txt",
".odt",
".ott",
".pdb",
".pdf",
".psw",
".rtf",
".sdw",
".stw",
".sxw",
".uot",
".vor",
".wps",
".epub",
".png",
".bmp", ".bmp",
".emf",
".eps",
".fodg",
".gif",
".jpg",
".jpeg",
".met",
".odd",
".otg",
".pbm",
".pct",
".pgm",
".ppm",
".ras",
".std",
".svg",
".svm",
".swf",
".sxd",
".sxw",
".tif",
".tiff",
".xhtml",
".xpm",
".odp",
".fodp",
".pages",
".potm",
".pot",
".pptx",
".pps",
".ppt",
".pwp",
".sda",
".sdd",
".sti",
".sxi",
".uop",
".wmf",
".csv", ".csv",
".dbf", ".dbf",
".dif", ".dif",
".doc",
".docx",
".dotx",
".emf",
".eps",
".epub",
".fodg",
".fodp",
".fods", ".fods",
".fodt",
".gif",
".html",
".jpeg",
".jpg",
".key",
".ltx",
".met",
".odd",
".odg",
".odp",
".ods", ".ods",
".odt",
".otg",
".ots", ".ots",
".ott",
".pages",
".pbm",
".pct",
".pdb",
".pdf",
".pgm",
".png",
".pot",
".potm",
".ppm",
".pps",
".ppt",
".pptx",
".psw",
".pwp",
".pxl", ".pxl",
".ras",
".rtf",
".sda",
".sdc", ".sdc",
".sdd",
".sdw",
".slk", ".slk",
".stc", ".stc",
".std",
".sti",
".stw",
".svg",
".svm",
".swf",
".sxc", ".sxc",
".sxd",
".sxi",
".sxw",
".sxw",
".tif",
".tiff",
".txt",
".uop",
".uos", ".uos",
".uot",
".vor",
".wmf",
".wps",
".xhtml",
".xls", ".xls",
".xlt",
".xlsx", ".xlsx",
".odg", ".xlt",
".dotx",
".xltx", ".xltx",
".xml",
".xpm",
} }
} }

View File

@@ -450,7 +450,7 @@ func TestApi_Extensions(t *testing.T) {
extensions := a.Extensions() extensions := a.Extensions()
actual := len(extensions) actual := len(extensions)
expect := 80 expect := 81
if actual != expect { if actual != expect {
t.Errorf("expected %d extensions, but got %d", expect, actual) t.Errorf("expected %d extensions, but got %d", expect, actual)

View File

@@ -288,8 +288,14 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP
if options.PdfFormats.PdfUa { if options.PdfFormats.PdfUa {
args = append( args = append(
args, args,
"--export", "EnableTextAccessForAccessibilityTools=true",
"--export", "UseTaggedPDF=true", "--export", "UseTaggedPDF=true",
"--export", "EnableTextAccessForAccessibilityTools=true",
)
} else {
args = append(
args,
"--export", "UseTaggedPDF=false",
"--export", "EnableTextAccessForAccessibilityTools=false",
) )
} }

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"path/filepath"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@@ -55,8 +54,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
// Alright, let's convert each document to PDF. // Alright, let's convert each document to PDF.
outputPaths := make([]string, len(inputPaths)) outputPaths := make([]string, len(inputPaths))
for i, inputPath := range inputPaths { for i, inputPath := range inputPaths {
// document.docx -> document.docx.pdf. outputPaths[i] = ctx.GeneratePath(".pdf")
outputPaths[i] = ctx.GeneratePath(filepath.Base(inputPath), ".pdf")
options := libreofficeapi.Options{ options := libreofficeapi.Options{
Landscape: landscape, Landscape: landscape,
PageRanges: nativePageRanges, PageRanges: nativePageRanges,
@@ -91,9 +89,8 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
// So far so good, let's check if we have to merge the PDFs. Quick // So far so good, let's check if we have to merge the PDFs. Quick
// win: if there is only one PDF, skip this step. // win: if there is only one PDF, skip this step.
if len(outputPaths) > 1 && merge { if len(outputPaths) > 1 && merge {
outputPath := ctx.GeneratePath("", ".pdf") outputPath := ctx.GeneratePath(".pdf")
err = engine.Merge(ctx, ctx.Log(), outputPaths, outputPath) err = engine.Merge(ctx, ctx.Log(), outputPaths, outputPath)
if err != nil { if err != nil {
@@ -105,7 +102,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
zeroValued := gotenberg.PdfFormats{} zeroValued := gotenberg.PdfFormats{}
if !nativePdfFormats && pdfFormats != zeroValued { if !nativePdfFormats && pdfFormats != zeroValued {
convertInputPath := outputPath convertInputPath := outputPath
convertOutputPath := ctx.GeneratePath("", ".pdf") convertOutputPath := ctx.GeneratePath(".pdf")
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath) err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
if err != nil { if err != nil {
@@ -117,8 +114,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
} }
// Last but not least, add the output path to the context so that // Last but not least, add the output path to the context so that
// the Uno is able to send it as a response to the client. // the API is able to send it as a response to the client.
err = ctx.AddOutputPaths(outputPath) err = ctx.AddOutputPaths(outputPath)
if err != nil { if err != nil {
return fmt.Errorf("add output path: %w", err) return fmt.Errorf("add output path: %w", err)
@@ -135,23 +131,34 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
for i, outputPath := range outputPaths { for i, outputPath := range outputPaths {
convertInputPath := outputPath convertInputPath := outputPath
// document.docx -> document.docx.pdf. convertOutputPaths[i] = ctx.GeneratePath(".pdf")
convertOutputPaths[i] = ctx.GeneratePath(filepath.Base(inputPaths[i]), ".pdf")
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPaths[i]) err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPaths[i])
if err != nil { if err != nil {
return fmt.Errorf("convert PDF: %w", err) return fmt.Errorf("convert PDF: %w", err)
} }
} }
// Important: the output paths are now the converted files. // Important: the output paths are now the converted files.
outputPaths = convertOutputPaths outputPaths = convertOutputPaths
} }
// Last but not least, add the output paths to the context so that if len(outputPaths) > 1 {
// the Uno is able to send them as a response to the client. // If .zip archive, document.docx -> document.docx.pdf.
for i, inputPath := range inputPaths {
outputPath := fmt.Sprintf("%s.pdf", inputPath)
err = ctx.Rename(outputPaths[i], outputPath)
if err != nil {
return fmt.Errorf("rename output path: %w", err)
}
outputPaths[i] = outputPath
}
}
// Last but not least, add the output paths to the context so that
// the API is able to send them as a response to the client.
err = ctx.AddOutputPaths(outputPaths...) err = ctx.AddOutputPaths(outputPaths...)
if err != nil { if err != nil {
return fmt.Errorf("add output paths: %w", err) return fmt.Errorf("add output paths: %w", err)

View File

@@ -185,7 +185,32 @@ func TestConvertRoute(t *testing.T) {
expectError: false, expectError: false,
expectHttpError: false, expectHttpError: false,
expectOutputPathsCount: 1, expectOutputPathsCount: 1,
expectOutputPaths: []string{"/document.docx.pdf"}, },
{
scenario: "cannot rename many files",
ctx: func() *api.ContextMock {
ctx := &api.ContextMock{Context: new(api.Context)}
ctx.SetFiles(map[string]string{
"document.docx": "/document.docx",
"document2.docx": "/document2.docx",
"document2.doc": "/document2.doc",
})
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return errors.New("cannot rename")
}})
return ctx
}(),
libreOffice: &libreofficeapi.ApiMock{
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
return nil
},
ExtensionsMock: func() []string {
return []string{".docx", ".doc"}
},
},
expectError: true,
expectHttpError: false,
expectOutputPathsCount: 0,
}, },
{ {
scenario: "success (many files)", scenario: "success (many files)",
@@ -196,6 +221,9 @@ func TestConvertRoute(t *testing.T) {
"document2.docx": "/document2.docx", "document2.docx": "/document2.docx",
"document2.doc": "/document2.doc", "document2.doc": "/document2.doc",
}) })
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return nil
}})
return ctx return ctx
}(), }(),
libreOffice: &libreofficeapi.ApiMock{ libreOffice: &libreofficeapi.ApiMock{
@@ -230,6 +258,9 @@ func TestConvertRoute(t *testing.T) {
"false", "false",
}, },
}) })
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return nil
}})
return ctx return ctx
}(), }(),
libreOffice: &libreofficeapi.ApiMock{ libreOffice: &libreofficeapi.ApiMock{
@@ -266,6 +297,9 @@ func TestConvertRoute(t *testing.T) {
"true", "true",
}, },
}) })
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return nil
}})
return ctx return ctx
}(), }(),
libreOffice: &libreofficeapi.ApiMock{ libreOffice: &libreofficeapi.ApiMock{

View File

@@ -4,8 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"path/filepath"
"strings"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@@ -44,8 +42,7 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
} }
// Alright, let's merge the PDFs. // Alright, let's merge the PDFs.
outputPath := ctx.GeneratePath(".pdf")
outputPath := ctx.GeneratePath("", ".pdf")
err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath) err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath)
if err != nil { if err != nil {
@@ -58,7 +55,7 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
zeroValued := gotenberg.PdfFormats{} zeroValued := gotenberg.PdfFormats{}
if pdfFormats != zeroValued { if pdfFormats != zeroValued {
convertInputPath := outputPath convertInputPath := outputPath
convertOutputPath := ctx.GeneratePath("", ".pdf") convertOutputPath := ctx.GeneratePath(".pdf")
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath) err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
if err != nil { if err != nil {
@@ -71,7 +68,6 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
// Last but not least, add the output path to the context so that // Last but not least, add the output path to the context so that
// the API is able to send it as a response to the client. // the API is able to send it as a response to the client.
err = ctx.AddOutputPaths(outputPath) err = ctx.AddOutputPaths(outputPath)
if err != nil { if err != nil {
return fmt.Errorf("add output path: %w", err) return fmt.Errorf("add output path: %w", err)
@@ -124,26 +120,29 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route {
) )
} }
// Alright, let's convert the PDFs.s // Alright, let's convert the PDFs.
outputPaths := make([]string, len(inputPaths)) outputPaths := make([]string, len(inputPaths))
for i, inputPath := range inputPaths { for i, inputPath := range inputPaths {
if len(outputPaths) > 1 { outputPaths[i] = ctx.GeneratePath(".pdf")
// If .zip archive, keep the original filenames.
outputPaths[i] = ctx.GeneratePath(strings.TrimSuffix(filepath.Base(inputPath), filepath.Ext(inputPath)), ".pdf")
} else {
outputPaths[i] = ctx.GeneratePath("", ".pdf")
}
err = engine.Convert(ctx, ctx.Log(), pdfFormats, inputPath, outputPaths[i]) err = engine.Convert(ctx, ctx.Log(), pdfFormats, inputPath, outputPaths[i])
if err != nil { if err != nil {
return fmt.Errorf("convert PDF: %w", err) return fmt.Errorf("convert PDF: %w", err)
} }
if len(outputPaths) > 1 {
// If .zip archive, keep the original filename.
err = ctx.Rename(outputPaths[i], inputPath)
if err != nil {
return fmt.Errorf("rename output path: %w", err)
}
outputPaths[i] = inputPath
}
} }
// Last but not least, add the output paths to the context so that // Last but not least, add the output paths to the context so that
// the API is able to send them as a response to the client. // the API is able to send them as a response to the client.
err = ctx.AddOutputPaths(outputPaths...) err = ctx.AddOutputPaths(outputPaths...)
if err != nil { if err != nil {
return fmt.Errorf("add output paths: %w", err) return fmt.Errorf("add output paths: %w", err)

View File

@@ -294,6 +294,36 @@ func TestConvertHandler(t *testing.T) {
expectHttpError: false, expectHttpError: false,
expectOutputPathsCount: 1, expectOutputPathsCount: 1,
}, },
{
scenario: "cannot rename many files",
ctx: func() *api.ContextMock {
ctx := &api.ContextMock{Context: new(api.Context)}
ctx.SetFiles(map[string]string{
"file.pdf": "/file.pdf",
"file2.pdf": "/file2.pdf",
})
ctx.SetValues(map[string][]string{
"pdfa": {
gotenberg.PdfA1b,
},
"pdfua": {
"true",
},
})
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return errors.New("cannot rename")
}})
return ctx
}(),
engine: &gotenberg.PdfEngineMock{
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
return nil
},
},
expectError: true,
expectHttpError: false,
expectOutputPathsCount: 0,
},
{ {
scenario: "success with PDF/A & PDF/UA form fields (many files)", scenario: "success with PDF/A & PDF/UA form fields (many files)",
ctx: func() *api.ContextMock { ctx: func() *api.ContextMock {
@@ -310,6 +340,9 @@ func TestConvertHandler(t *testing.T) {
"true", "true",
}, },
}) })
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
return nil
}})
return ctx return ctx
}(), }(),
engine: &gotenberg.PdfEngineMock{ engine: &gotenberg.PdfEngineMock{