mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 19:32:15 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1f69ee533 | ||
|
|
d90e431b5b | ||
|
|
45a9eca0ce | ||
|
|
af202f4871 | ||
|
|
7b32fa35f3 | ||
|
|
0c40b230dc | ||
|
|
2baba67bfc | ||
|
|
b1e69a9059 | ||
|
|
f87362ec1b | ||
|
|
84dc57373a | ||
|
|
b3f4a02658 | ||
|
|
50304dbefe | ||
|
|
bf01af0bf3 | ||
|
|
0e3fe3463b | ||
|
|
3956763825 | ||
|
|
d6fe895f6e |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
github: [gulien]
|
||||||
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
# Number of days of inactivity before an issue becomes stale
|
||||||
daysUntilStale: 15
|
daysUntilStale: 7
|
||||||
# Number of days of inactivity before a stale issue is closed
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilClose: 7
|
daysUntilClose: 3
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- bug
|
- bug
|
||||||
|
|||||||
@@ -32,10 +32,15 @@ Head to the [documentation](https://gotenberg.dev/docs/getting-started/introduct
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://thecodingmachine.com">
|
<a href="https://thecodingmachine.com">
|
||||||
<img src="https://user-images.githubusercontent.com/8983173/130324668-9d6e7b35-53a3-49c7-a574-38190d2bd6b0.png" alt="TheCodingMachine Logo" width="429" height="210" />
|
<img src="https://user-images.githubusercontent.com/8983173/130324668-9d6e7b35-53a3-49c7-a574-38190d2bd6b0.png" alt="TheCodingMachine Logo" width="333" height="163" />
|
||||||
|
</a>
|
||||||
|
<a href="https://zolsec.com?utm_source=gotenberg_github&utm_medium=website" target="_blank">
|
||||||
|
<img src="https://github.com/gotenberg/gotenberg/assets/8983173/707ccc97-a79b-4dcb-8fc8-6827366e5be3" alt="Zolsec Logo" width="333" height="163" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
Sponsorships help maintaining and improving Gotenberg - [become a sponsor](https://github.com/sponsors/gulien) ❤️
|
||||||
|
|
||||||
## Badges
|
## Badges
|
||||||
|
|
||||||
[](https://hub.docker.com/r/gotenberg/gotenberg)
|
[](https://hub.docker.com/r/gotenberg/gotenberg)
|
||||||
|
|||||||
19
go.mod
19
go.mod
@@ -6,7 +6,7 @@ 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/barasher/go-exiftool v1.10.0
|
github.com/barasher/go-exiftool v1.10.0
|
||||||
github.com/chromedp/cdproto v0.0.0-20240602235142-49d0e97b7881
|
github.com/chromedp/cdproto v0.0.0-20240709201219-e202069cc16b
|
||||||
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
|
||||||
@@ -18,7 +18,7 @@ require (
|
|||||||
github.com/labstack/gommon v0.4.2
|
github.com/labstack/gommon v0.4.2
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
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.27
|
||||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||||
github.com/pdfcpu/pdfcpu v0.8.0
|
github.com/pdfcpu/pdfcpu v0.8.0
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
@@ -28,16 +28,16 @@ require (
|
|||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.12 // 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.24.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/image v0.17.0 // indirect
|
golang.org/x/image v0.18.0 // indirect
|
||||||
golang.org/x/net v0.26.0
|
golang.org/x/net v0.27.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.7.0
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/term v0.21.0
|
golang.org/x/term v0.22.0
|
||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.16.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/dlclark/regexp2 v1.11.0
|
require github.com/dlclark/regexp2 v1.11.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
@@ -56,9 +56,10 @@ require (
|
|||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.54.0 // indirect
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // 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
|
||||||
|
|||||||
42
go.sum
42
go.sum
@@ -12,8 +12,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.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-20240602235142-49d0e97b7881 h1:RAUqkPvbEDGPgCYVc4GefBqAorWJAjKpVHgsRZyJmGE=
|
github.com/chromedp/cdproto v0.0.0-20240709201219-e202069cc16b h1:U1h0qXjQvrOWOjagZmtDkxg/A4QKkWJyGWoQ3sXt6Vg=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240602235142-49d0e97b7881/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20240709201219-e202069cc16b/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=
|
||||||
@@ -21,8 +21,8 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 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.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.2 h1:/u628IuisSTwri5/UKloiIsH8+qF2Pu7xEQX+yIKg68=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.2/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=
|
||||||
@@ -88,8 +88,10 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
|||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
||||||
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
@@ -108,8 +110,8 @@ github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQ
|
|||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
|
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||||
github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
|
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
@@ -125,8 +127,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||||
@@ -143,21 +145,21 @@ 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.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
|
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||||
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
|||||||
@@ -187,10 +187,13 @@ func (s *processSupervisor) Run(ctx context.Context, logger *zap.Logger, task fu
|
|||||||
logger.Debug("process lock acquired")
|
logger.Debug("process lock acquired")
|
||||||
s.reqQueueSize.Add(-1)
|
s.reqQueueSize.Add(-1)
|
||||||
s.reqCounter.Add(1)
|
s.reqCounter.Add(1)
|
||||||
|
releaseMutexChan := true
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
logger.Debug("process lock released")
|
if releaseMutexChan {
|
||||||
<-s.mutexChan
|
logger.Debug("process lock released")
|
||||||
|
<-s.mutexChan
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !s.firstStart.Load() {
|
if !s.firstStart.Load() {
|
||||||
@@ -212,18 +215,26 @@ func (s *processSupervisor) Run(ctx context.Context, logger *zap.Logger, task fu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := s.runWithDeadline(ctx, task)
|
||||||
|
|
||||||
if s.maxReqLimit > 0 && s.reqCounter.Load() >= s.maxReqLimit {
|
if s.maxReqLimit > 0 && s.reqCounter.Load() >= s.maxReqLimit {
|
||||||
s.logger.Debug("max request limit reached, restarting...")
|
s.logger.Debug("max request limit reached, restarting eagerly...")
|
||||||
err := s.runWithDeadline(ctx, func() error {
|
releaseMutexChan = false
|
||||||
return s.restart()
|
|
||||||
})
|
go func() {
|
||||||
if err != nil {
|
err := s.runWithDeadline(context.Background(), func() error {
|
||||||
return fmt.Errorf("process restart before task: %w", err)
|
return s.restart()
|
||||||
}
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(fmt.Sprintf("process restart after task: %v", err))
|
||||||
|
}
|
||||||
|
logger.Debug("process lock released")
|
||||||
|
<-s.mutexChan
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: no error wrapping because it leaks on Chromium console exceptions output.
|
// Note: no error wrapping because it leaks on Chromium console exceptions output.
|
||||||
return s.runWithDeadline(ctx, task)
|
return err
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Debug("failed to acquire process lock before deadline")
|
logger.Debug("failed to acquire process lock before deadline")
|
||||||
s.reqQueueSize.Add(-1)
|
s.reqQueueSize.Add(-1)
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ func TestProcessSupervisor_Run(t *testing.T) {
|
|||||||
expectedStopCalls: 1,
|
expectedStopCalls: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "cannot restart after reaching max request limit",
|
scenario: "auto-restart after reaching max request limit",
|
||||||
startError: errors.New("start error"),
|
startError: errors.New("start error"),
|
||||||
initiallyStarted: true,
|
initiallyStarted: true,
|
||||||
isRestarting: false,
|
isRestarting: false,
|
||||||
@@ -451,6 +451,10 @@ func TestProcessSupervisor_Run(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Making sure restarts are finished.
|
||||||
|
ps.mutexChan <- struct{}{}
|
||||||
|
<-ps.mutexChan
|
||||||
|
|
||||||
if startCalls.Load() != tc.expectedStartCalls {
|
if startCalls.Load() != tc.expectedStartCalls {
|
||||||
t.Errorf("expected %d process.Start calls, got %d", tc.expectedStartCalls, startCalls.Load())
|
t.Errorf("expected %d process.Start calls, got %d", tc.expectedStartCalls, startCalls.Load())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -411,13 +411,6 @@ func (a *Api) Start() error {
|
|||||||
loggerMiddleware(a.logger, disableLoggingForPaths),
|
loggerMiddleware(a.logger, disableLoggingForPaths),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Basic auth?
|
|
||||||
if a.basicAuthUsername != "" {
|
|
||||||
a.srv.Pre(
|
|
||||||
basicAuthMiddleware(a.basicAuthUsername, a.basicAuthPassword),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the modules' middlewares in their respective stacks.
|
// Add the modules' middlewares in their respective stacks.
|
||||||
var externalMultipartMiddlewares []Middleware
|
var externalMultipartMiddlewares []Middleware
|
||||||
for _, externalMiddleware := range a.externalMiddlewares {
|
for _, externalMiddleware := range a.externalMiddlewares {
|
||||||
@@ -437,6 +430,11 @@ func (a *Api) Start() error {
|
|||||||
for _, route := range a.routes {
|
for _, route := range a.routes {
|
||||||
var middlewares []echo.MiddlewareFunc
|
var middlewares []echo.MiddlewareFunc
|
||||||
|
|
||||||
|
// Basic auth?
|
||||||
|
if a.basicAuthUsername != "" {
|
||||||
|
middlewares = append(middlewares, basicAuthMiddleware(a.basicAuthUsername, a.basicAuthPassword))
|
||||||
|
}
|
||||||
|
|
||||||
if route.IsMultipart {
|
if route.IsMultipart {
|
||||||
middlewares = append(middlewares, contextMiddleware(a.fs, a.timeout))
|
middlewares = append(middlewares, contextMiddleware(a.fs, a.timeout))
|
||||||
|
|
||||||
|
|||||||
@@ -842,7 +842,6 @@ func TestApi_Start(t *testing.T) {
|
|||||||
// health request.
|
// health request.
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
healthRequest := httptest.NewRequest(http.MethodGet, "/health", nil)
|
healthRequest := httptest.NewRequest(http.MethodGet, "/health", nil)
|
||||||
healthRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword)
|
|
||||||
|
|
||||||
mod.srv.ServeHTTP(recorder, healthRequest)
|
mod.srv.ServeHTTP(recorder, healthRequest)
|
||||||
if recorder.Code != http.StatusOK {
|
if recorder.Code != http.StatusOK {
|
||||||
@@ -851,7 +850,6 @@ func TestApi_Start(t *testing.T) {
|
|||||||
|
|
||||||
// version request.
|
// version request.
|
||||||
versionRequest := httptest.NewRequest(http.MethodGet, "/version", nil)
|
versionRequest := httptest.NewRequest(http.MethodGet, "/version", nil)
|
||||||
versionRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword)
|
|
||||||
|
|
||||||
mod.srv.ServeHTTP(recorder, versionRequest)
|
mod.srv.ServeHTTP(recorder, versionRequest)
|
||||||
if recorder.Code != http.StatusOK {
|
if recorder.Code != http.StatusOK {
|
||||||
|
|||||||
@@ -314,6 +314,14 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
|
listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
connectionRefused error
|
||||||
|
connectionRefusedMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/913.
|
||||||
|
listenForEventLoadingFailedOnConnectionRefused(taskCtx, logger, &connectionRefused, &connectionRefusedMu)
|
||||||
|
|
||||||
err = chromedp.Run(taskCtx, tasks...)
|
err = chromedp.Run(taskCtx, tasks...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
@@ -349,6 +357,14 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
return fmt.Errorf("%v: %w", consoleExceptions, ErrConsoleExceptions)
|
return fmt.Errorf("%v: %w", consoleExceptions, ErrConsoleExceptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/913.
|
||||||
|
connectionRefusedMu.RLock()
|
||||||
|
defer connectionRefusedMu.RUnlock()
|
||||||
|
|
||||||
|
if connectionRefused != nil {
|
||||||
|
return fmt.Errorf("%v: %w", connectionRefused, ErrConnectionRefused)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browser browser
|
browser browser
|
||||||
fs *gotenberg.FileSystem
|
fs *gotenberg.FileSystem
|
||||||
options PdfOptions
|
options PdfOptions
|
||||||
|
url string
|
||||||
noDeadline bool
|
noDeadline bool
|
||||||
start bool
|
start bool
|
||||||
expectError bool
|
expectError bool
|
||||||
@@ -478,6 +479,32 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
expectError: true,
|
expectError: true,
|
||||||
expectedError: ErrConsoleExceptions,
|
expectedError: ErrConsoleExceptions,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrConnectionRefused",
|
||||||
|
browser: newChromiumBrowser(
|
||||||
|
browserArguments{
|
||||||
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
|
allowList: regexp2.MustCompile("", 0),
|
||||||
|
denyList: regexp2.MustCompile("", 0),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
fs: func() *gotenberg.FileSystem {
|
||||||
|
fs := gotenberg.NewFileSystem()
|
||||||
|
|
||||||
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}(),
|
||||||
|
url: "http://localhost:100",
|
||||||
|
noDeadline: false,
|
||||||
|
start: true,
|
||||||
|
expectError: true,
|
||||||
|
expectedError: ErrConnectionRefused,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "clear cache",
|
scenario: "clear cache",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
@@ -1243,10 +1270,15 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("file://%s/index.html", tc.fs.WorkingDirPath())
|
||||||
|
if tc.url != "" {
|
||||||
|
url = tc.url
|
||||||
|
}
|
||||||
|
|
||||||
err := tc.browser.pdf(
|
err := tc.browser.pdf(
|
||||||
ctx,
|
ctx,
|
||||||
logger,
|
logger,
|
||||||
fmt.Sprintf("file://%s/index.html", tc.fs.WorkingDirPath()),
|
url,
|
||||||
fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()),
|
fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()),
|
||||||
tc.options,
|
tc.options,
|
||||||
)
|
)
|
||||||
@@ -1286,6 +1318,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browser browser
|
browser browser
|
||||||
fs *gotenberg.FileSystem
|
fs *gotenberg.FileSystem
|
||||||
options ScreenshotOptions
|
options ScreenshotOptions
|
||||||
|
url string
|
||||||
noDeadline bool
|
noDeadline bool
|
||||||
start bool
|
start bool
|
||||||
expectError bool
|
expectError bool
|
||||||
@@ -1519,6 +1552,33 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
expectError: true,
|
expectError: true,
|
||||||
expectedError: ErrConsoleExceptions,
|
expectedError: ErrConsoleExceptions,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrConnectionRefused",
|
||||||
|
browser: newChromiumBrowser(
|
||||||
|
browserArguments{
|
||||||
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
|
allowList: regexp2.MustCompile("", 0),
|
||||||
|
denyList: regexp2.MustCompile("", 0),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
fs: func() *gotenberg.FileSystem {
|
||||||
|
fs := gotenberg.NewFileSystem()
|
||||||
|
|
||||||
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}(),
|
||||||
|
url: "http://localhost:100",
|
||||||
|
noDeadline: false,
|
||||||
|
start: true,
|
||||||
|
expectError: true,
|
||||||
|
expectedError: ErrConnectionRefused,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "clear cache",
|
scenario: "clear cache",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
@@ -2169,10 +2229,15 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("file://%s/index.html", tc.fs.WorkingDirPath())
|
||||||
|
if tc.url != "" {
|
||||||
|
url = tc.url
|
||||||
|
}
|
||||||
|
|
||||||
err := tc.browser.screenshot(
|
err := tc.browser.screenshot(
|
||||||
ctx,
|
ctx,
|
||||||
logger,
|
logger,
|
||||||
fmt.Sprintf("file://%s/index.html", tc.fs.WorkingDirPath()),
|
url,
|
||||||
fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()),
|
fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()),
|
||||||
tc.options,
|
tc.options,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ var (
|
|||||||
// is set to true.
|
// is set to true.
|
||||||
ErrConsoleExceptions = errors.New("console exceptions")
|
ErrConsoleExceptions = errors.New("console exceptions")
|
||||||
|
|
||||||
|
// ErrConnectionRefused happens when a URL cannot be reached.
|
||||||
|
ErrConnectionRefused = errors.New("connection refused")
|
||||||
|
|
||||||
// PDF specific.
|
// PDF specific.
|
||||||
|
|
||||||
// ErrOmitBackgroundWithoutPrintBackground happens if
|
// ErrOmitBackgroundWithoutPrintBackground happens if
|
||||||
@@ -76,55 +79,44 @@ type Options struct {
|
|||||||
// "networkIdle" event, drastically improving the conversion speed. It may
|
// "networkIdle" event, drastically improving the conversion speed. It may
|
||||||
// not be suitable for all HTML documents, as some may not be fully
|
// not be suitable for all HTML documents, as some may not be fully
|
||||||
// rendered until this event is fired.
|
// rendered until this event is fired.
|
||||||
// Optional.
|
|
||||||
SkipNetworkIdleEvent bool
|
SkipNetworkIdleEvent bool
|
||||||
|
|
||||||
// FailOnHttpStatusCodes sets if the conversion should fail if the status
|
// FailOnHttpStatusCodes sets if the conversion should fail if the status
|
||||||
// code from the main page matches with one of its entries.
|
// code from the main page matches with one of its entries.
|
||||||
// Optional.
|
|
||||||
FailOnHttpStatusCodes []int64
|
FailOnHttpStatusCodes []int64
|
||||||
|
|
||||||
// FailOnConsoleExceptions sets if the conversion should fail if there are
|
// FailOnConsoleExceptions sets if the conversion should fail if there are
|
||||||
// exceptions in the Chromium console.
|
// exceptions in the Chromium console.
|
||||||
// Optional.
|
|
||||||
FailOnConsoleExceptions bool
|
FailOnConsoleExceptions bool
|
||||||
|
|
||||||
// WaitDelay is the duration to wait when loading an HTML document before
|
// WaitDelay is the duration to wait when loading an HTML document before
|
||||||
// converting it.
|
// converting it.
|
||||||
// Optional.
|
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// WaitWindowStatus is the window.status value to wait for before
|
// WaitWindowStatus is the window.status value to wait for before
|
||||||
// converting an HTML document.
|
// converting an HTML document.
|
||||||
// Optional.
|
|
||||||
WaitWindowStatus string
|
WaitWindowStatus string
|
||||||
|
|
||||||
// WaitForExpression is the custom JavaScript expression to wait before
|
// WaitForExpression is the custom JavaScript expression to wait before
|
||||||
// converting an HTML document until it returns true
|
// converting an HTML document until it returns true
|
||||||
// Optional.
|
|
||||||
WaitForExpression string
|
WaitForExpression string
|
||||||
|
|
||||||
// Cookies are the cookies to put in the Chromium cookies' jar.
|
// Cookies are the cookies to put in the Chromium cookies' jar.
|
||||||
// Optional
|
|
||||||
Cookies []Cookie
|
Cookies []Cookie
|
||||||
|
|
||||||
// UserAgent overrides the default 'User-Agent' HTTP header.
|
// UserAgent overrides the default 'User-Agent' HTTP header.
|
||||||
// Optional.
|
|
||||||
UserAgent string
|
UserAgent string
|
||||||
|
|
||||||
// ExtraHttpHeaders are extra HTTP headers to send by Chromium while
|
// ExtraHttpHeaders are extra HTTP headers to send by Chromium while
|
||||||
// loading he HTML document.
|
// loading he HTML document.
|
||||||
// Optional.
|
|
||||||
ExtraHttpHeaders map[string]string
|
ExtraHttpHeaders map[string]string
|
||||||
|
|
||||||
// EmulatedMediaType is the media type to emulate, either "screen" or
|
// EmulatedMediaType is the media type to emulate, either "screen" or
|
||||||
// "print".
|
// "print".
|
||||||
// Optional.
|
|
||||||
EmulatedMediaType string
|
EmulatedMediaType string
|
||||||
|
|
||||||
// OmitBackground hides default white background and allows generating PDFs
|
// OmitBackground hides default white background and allows generating PDFs
|
||||||
// with transparency.
|
// with transparency.
|
||||||
// Optional.
|
|
||||||
OmitBackground bool
|
OmitBackground bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,48 +142,37 @@ type PdfOptions struct {
|
|||||||
Options
|
Options
|
||||||
|
|
||||||
// Landscape sets the paper orientation.
|
// Landscape sets the paper orientation.
|
||||||
// Optional.
|
|
||||||
Landscape bool
|
Landscape bool
|
||||||
|
|
||||||
// PrintBackground prints the background graphics.
|
// PrintBackground prints the background graphics.
|
||||||
// Optional.
|
|
||||||
PrintBackground bool
|
PrintBackground bool
|
||||||
|
|
||||||
// Scale is the scale of the page rendering.
|
// Scale is the scale of the page rendering.
|
||||||
// Optional.
|
|
||||||
Scale float64
|
Scale float64
|
||||||
|
|
||||||
// SinglePage defines whether to print the entire content in one single
|
// SinglePage defines whether to print the entire content in one single
|
||||||
// page.
|
// page.
|
||||||
// Optional.
|
|
||||||
SinglePage bool
|
SinglePage bool
|
||||||
|
|
||||||
// PaperWidth is the paper width, in inches.
|
// PaperWidth is the paper width, in inches.
|
||||||
// Optional.
|
|
||||||
PaperWidth float64
|
PaperWidth float64
|
||||||
|
|
||||||
// PaperHeight is the paper height, in inches.
|
// PaperHeight is the paper height, in inches.
|
||||||
// Optional.
|
|
||||||
PaperHeight float64
|
PaperHeight float64
|
||||||
|
|
||||||
// MarginTop is the top margin, in inches.
|
// MarginTop is the top margin, in inches.
|
||||||
// Optional.
|
|
||||||
MarginTop float64
|
MarginTop float64
|
||||||
|
|
||||||
// MarginBottom is the bottom margin, in inches.
|
// MarginBottom is the bottom margin, in inches.
|
||||||
// Optional.
|
|
||||||
MarginBottom float64
|
MarginBottom float64
|
||||||
|
|
||||||
// MarginLeft is the left margin, in inches.
|
// MarginLeft is the left margin, in inches.
|
||||||
// Optional.
|
|
||||||
MarginLeft float64
|
MarginLeft float64
|
||||||
|
|
||||||
// MarginRight is the right margin, in inches.
|
// MarginRight is the right margin, in inches.
|
||||||
// Optional.
|
|
||||||
MarginRight float64
|
MarginRight float64
|
||||||
|
|
||||||
// Page ranges to print, e.g., '1-5, 8, 11-13'. Empty means all pages.
|
// Page ranges to print, e.g., '1-5, 8, 11-13'. Empty means all pages.
|
||||||
// Optional.
|
|
||||||
PageRanges string
|
PageRanges string
|
||||||
|
|
||||||
// HeaderTemplate is the HTML template of the header. It should be valid
|
// HeaderTemplate is the HTML template of the header. It should be valid
|
||||||
@@ -204,17 +185,14 @@ type PdfOptions struct {
|
|||||||
// - totalPages: total pages in the document
|
// - totalPages: total pages in the document
|
||||||
// For example, <span class=title></span> would generate span containing
|
// For example, <span class=title></span> would generate span containing
|
||||||
// the title.
|
// the title.
|
||||||
// Optional.
|
|
||||||
HeaderTemplate string
|
HeaderTemplate string
|
||||||
|
|
||||||
// FooterTemplate is the HTML template of the footer. It should use the
|
// FooterTemplate is the HTML template of the footer. It should use the
|
||||||
// same format as the HeaderTemplate.
|
// same format as the HeaderTemplate.
|
||||||
// Optional.
|
|
||||||
FooterTemplate string
|
FooterTemplate string
|
||||||
|
|
||||||
// PreferCssPageSize defines whether to prefer page size as defined by CSS.
|
// PreferCssPageSize defines whether to prefer page size as defined by CSS.
|
||||||
// If false, the content will be scaled to fit the paper size.
|
// If false, the content will be scaled to fit the paper size.
|
||||||
// Optional.
|
|
||||||
PreferCssPageSize bool
|
PreferCssPageSize bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,30 +223,24 @@ type ScreenshotOptions struct {
|
|||||||
Options
|
Options
|
||||||
|
|
||||||
// Width is the device screen width in pixels.
|
// Width is the device screen width in pixels.
|
||||||
// Optional.
|
|
||||||
Width int
|
Width int
|
||||||
|
|
||||||
// Height is the device screen height in pixels.
|
// Height is the device screen height in pixels.
|
||||||
// Optional.
|
|
||||||
Height int
|
Height int
|
||||||
|
|
||||||
// Clip defines whether to clip the screenshot according to the device
|
// Clip defines whether to clip the screenshot according to the device
|
||||||
// dimensions.
|
// dimensions.
|
||||||
// Optional.
|
|
||||||
Clip bool
|
Clip bool
|
||||||
|
|
||||||
// Format is the image compression format, either "png" or "jpeg" or
|
// Format is the image compression format, either "png" or "jpeg" or
|
||||||
// "webp".
|
// "webp".
|
||||||
// Optional.
|
|
||||||
Format string
|
Format string
|
||||||
|
|
||||||
// Quality is the compression quality from range [0..100] (jpeg only).
|
// Quality is the compression quality from range [0..100] (jpeg only).
|
||||||
// Optional.
|
|
||||||
Quality int
|
Quality int
|
||||||
|
|
||||||
// OptimizeForSpeed defines whether to optimize image encoding for speed,
|
// OptimizeForSpeed defines whether to optimize image encoding for speed,
|
||||||
// not for resulting size.
|
// not for resulting size.
|
||||||
// Optional.
|
|
||||||
OptimizeForSpeed bool
|
OptimizeForSpeed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultOptions(t *testing.T) {
|
func TestDefaultOptions(t *testing.T) {
|
||||||
|
actual := DefaultOptions()
|
||||||
|
notExpect := Options{}
|
||||||
|
|
||||||
|
if reflect.DeepEqual(actual, notExpect) {
|
||||||
|
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultPdfOptions(t *testing.T) {
|
||||||
actual := DefaultPdfOptions()
|
actual := DefaultPdfOptions()
|
||||||
notExpect := PdfOptions{}
|
notExpect := PdfOptions{}
|
||||||
|
|
||||||
@@ -23,6 +32,15 @@ func TestDefaultOptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultScreenshotOptions(t *testing.T) {
|
||||||
|
actual := DefaultScreenshotOptions()
|
||||||
|
notExpect := ScreenshotOptions{}
|
||||||
|
|
||||||
|
if reflect.DeepEqual(actual, notExpect) {
|
||||||
|
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestChromium_Descriptor(t *testing.T) {
|
func TestChromium_Descriptor(t *testing.T) {
|
||||||
descriptor := new(Chromium).Descriptor()
|
descriptor := new(Chromium).Descriptor()
|
||||||
|
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// listenForEventResponseReceived listens for an invalid HTTP status code is
|
// listenForEventResponseReceived listens for an invalid HTTP status code that
|
||||||
// returned by the main page.
|
// is returned by the main page.
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/613.
|
// See https://github.com/gotenberg/gotenberg/issues/613.
|
||||||
func listenForEventResponseReceived(ctx context.Context, logger *zap.Logger, url string, failOnHttpStatusCodes []int64, invalidHttpStatusCode *error, invalidHttpStatusCodeMu *sync.RWMutex) {
|
func listenForEventResponseReceived(ctx context.Context, logger *zap.Logger, url string, failOnHttpStatusCodes []int64, invalidHttpStatusCode *error, invalidHttpStatusCodeMu *sync.RWMutex) {
|
||||||
for _, code := range []int64{199, 299, 399, 499, 599} {
|
for _, code := range []int64{199, 299, 399, 499, 599} {
|
||||||
@@ -95,6 +95,28 @@ func listenForEventResponseReceived(ctx context.Context, logger *zap.Logger, url
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listenForEventLoadingFailedOnConnectionRefused listens for an event
|
||||||
|
// indicating that the main page failed to load.
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/913.
|
||||||
|
func listenForEventLoadingFailedOnConnectionRefused(ctx context.Context, logger *zap.Logger, connectionRefused *error, connectionRefusedMu *sync.RWMutex) {
|
||||||
|
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
||||||
|
switch ev := ev.(type) {
|
||||||
|
case *network.EventLoadingFailed:
|
||||||
|
logger.Debug(fmt.Sprintf("event EventLoadingFailed fired: %+v", ev.ErrorText))
|
||||||
|
|
||||||
|
if ev.ErrorText != "net::ERR_CONNECTION_REFUSED" || ev.Type != network.ResourceTypeDocument {
|
||||||
|
logger.Debug("skip EventLoadingFailed: is not net::ERR_CONNECTION_REFUSED and/or resource type Document")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionRefusedMu.Lock()
|
||||||
|
defer connectionRefusedMu.Unlock()
|
||||||
|
|
||||||
|
*connectionRefused = fmt.Errorf("%s", ev.ErrorText)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// listenForEventExceptionThrown listens for exceptions in the console and
|
// listenForEventExceptionThrown listens for exceptions in the console and
|
||||||
// appends those exceptions to the given error pointer.
|
// appends those exceptions to the given error pointer.
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/262.
|
// See https://github.com/gotenberg/gotenberg/issues/262.
|
||||||
|
|||||||
@@ -688,5 +688,15 @@ func handleChromiumError(err error, options Options) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrConnectionRefused) {
|
||||||
|
return api.WrapError(
|
||||||
|
err,
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"Chromium returned net::ERR_CONNECTION_REFUSED",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1435,6 +1435,18 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
expectHttpStatus: http.StatusConflict,
|
expectHttpStatus: http.StatusConflict,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrConnectionRefused",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
return ErrConnectionRefused
|
||||||
|
}},
|
||||||
|
options: DefaultPdfOptions(),
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "error from Chromium",
|
scenario: "error from Chromium",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
@@ -1633,6 +1645,18 @@ func TestScreenshotUrl(t *testing.T) {
|
|||||||
expectHttpStatus: http.StatusConflict,
|
expectHttpStatus: http.StatusConflict,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrConnectionRefused",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||||
|
return ErrConnectionRefused
|
||||||
|
}},
|
||||||
|
options: DefaultScreenshotOptions(),
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "error from Chromium",
|
scenario: "error from Chromium",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
|||||||
@@ -41,40 +41,129 @@ type Api struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options gathers available options when converting a document to PDF.
|
// Options gathers available options when converting a document to PDF.
|
||||||
|
// See: https://help.libreoffice.org/latest/en-US/text/shared/guide/pdf_params.html.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Landscape allows to change the orientation of the resulting PDF.
|
// Landscape allows to change the orientation of the resulting PDF.
|
||||||
// Optional.
|
|
||||||
Landscape bool
|
Landscape bool
|
||||||
|
|
||||||
// PageRanges allows to select the pages to convert.
|
// PageRanges allows to select the pages to convert.
|
||||||
// Optional.
|
|
||||||
PageRanges string
|
PageRanges string
|
||||||
|
|
||||||
// ExportFormFields allows to... export form fields in the resulting PDF.
|
// ExportFormFields specifies whether form fields are exported as widgets
|
||||||
// Optional.
|
// or only their fixed print representation is exported.
|
||||||
ExportFormFields bool
|
ExportFormFields bool
|
||||||
|
|
||||||
// SinglePageSheets allows to output each sheet as a single page in the
|
// AllowDuplicateFieldNames specifies whether multiple form fields exported
|
||||||
// resulting PDF.
|
// are allowed to have the same field name.
|
||||||
// Optional
|
AllowDuplicateFieldNames bool
|
||||||
|
|
||||||
|
// ExportBookmarks specifies if bookmarks are exported to PDF.
|
||||||
|
ExportBookmarks bool
|
||||||
|
|
||||||
|
// ExportBookmarksToPdfDestination specifies that the bookmarks contained
|
||||||
|
// in the source LibreOffice file should be exported to the PDF file as
|
||||||
|
// Named Destination.
|
||||||
|
ExportBookmarksToPdfDestination bool
|
||||||
|
|
||||||
|
// ExportPlaceholders exports the placeholders fields visual markings only.
|
||||||
|
// The exported placeholder is ineffective.
|
||||||
|
ExportPlaceholders bool
|
||||||
|
|
||||||
|
// ExportNotes specifies if notes are exported to PDF.
|
||||||
|
ExportNotes bool
|
||||||
|
|
||||||
|
// ExportNotesPages specifies if notes pages are exported to PDF.
|
||||||
|
// Notes pages are available in Impress documents only.
|
||||||
|
ExportNotesPages bool
|
||||||
|
|
||||||
|
// ExportOnlyNotesPages specifies, if the property ExportNotesPages is set
|
||||||
|
// to true, if only notes pages are exported to PDF.
|
||||||
|
ExportOnlyNotesPages bool
|
||||||
|
|
||||||
|
// ExportNotesInMargin specifies if notes in margin are exported to PDF.
|
||||||
|
ExportNotesInMargin bool
|
||||||
|
|
||||||
|
// ConvertOooTargetToPdfTarget specifies that the target documents with
|
||||||
|
// .od[tpgs] extension, will have that extension changed to .pdf when the
|
||||||
|
// link is exported to PDF. The source document remains untouched.
|
||||||
|
ConvertOooTargetToPdfTarget bool
|
||||||
|
|
||||||
|
// ExportLinksRelativeFsys specifies that the file system related
|
||||||
|
// hyperlinks (file:// protocol) present in the document will be exported
|
||||||
|
// as relative to the source document location.
|
||||||
|
ExportLinksRelativeFsys bool
|
||||||
|
|
||||||
|
// ExportHiddenSlides exports, for LibreOffice Impress, slides that are not
|
||||||
|
// included in slide shows.
|
||||||
|
ExportHiddenSlides bool
|
||||||
|
|
||||||
|
// SkipEmptyPages specifies that automatically inserted empty pages are
|
||||||
|
// suppressed. This option is active only if storing Writer documents.
|
||||||
|
SkipEmptyPages bool
|
||||||
|
|
||||||
|
// AddOriginalDocumentAsStream specifies that a stream is inserted to the
|
||||||
|
// PDF file which contains the original document for archiving purposes.
|
||||||
|
AddOriginalDocumentAsStream bool
|
||||||
|
|
||||||
|
// SinglePageSheets ignores each sheet’s paper size, print ranges and
|
||||||
|
// shown/hidden status and puts every sheet (even hidden sheets) on exactly
|
||||||
|
// one page.
|
||||||
SinglePageSheets bool
|
SinglePageSheets bool
|
||||||
|
|
||||||
// LosslessImageCompression allows turning lossless compression on or off
|
// LosslessImageCompression specifies if images are exported to PDF using
|
||||||
// to tweak image conversion performance.
|
// a lossless compression format like PNG or compressed using the JPEG
|
||||||
// Optional
|
// format.
|
||||||
LosslessImageCompression bool
|
LosslessImageCompression bool
|
||||||
|
|
||||||
// ReduceImageResolution allows turning on or off image resolution
|
// Quality specifies the quality of the JPG export. A higher value produces
|
||||||
// reduction to tweak image conversion performance.
|
// a higher-quality image and a larger file. Between 1 and 100.
|
||||||
// Optional
|
Quality int
|
||||||
|
|
||||||
|
// ReduceImageResolution specifies if the resolution of each image is
|
||||||
|
// reduced to the resolution specified by the property MaxImageResolution.
|
||||||
ReduceImageResolution bool
|
ReduceImageResolution bool
|
||||||
|
|
||||||
|
// MaxImageResolution, if the property ReduceImageResolution is set to
|
||||||
|
// true, tells if all images will be reduced to the given value in DPI.
|
||||||
|
// Possible values are: 75, 150, 300, 600 and 1200.
|
||||||
|
MaxImageResolution int
|
||||||
|
|
||||||
// PdfFormats allows to convert the resulting PDF to PDF/A-1b, PDF/A-2b,
|
// PdfFormats allows to convert the resulting PDF to PDF/A-1b, PDF/A-2b,
|
||||||
// PDF/A-3b and PDF/UA.
|
// PDF/A-3b and PDF/UA.
|
||||||
// Optional.
|
|
||||||
PdfFormats gotenberg.PdfFormats
|
PdfFormats gotenberg.PdfFormats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultOptions returns the default values for Options.
|
||||||
|
func DefaultOptions() Options {
|
||||||
|
return Options{
|
||||||
|
Landscape: false,
|
||||||
|
PageRanges: "",
|
||||||
|
ExportFormFields: true,
|
||||||
|
AllowDuplicateFieldNames: false,
|
||||||
|
ExportBookmarks: true,
|
||||||
|
ExportBookmarksToPdfDestination: false,
|
||||||
|
ExportPlaceholders: false,
|
||||||
|
ExportNotes: false,
|
||||||
|
ExportNotesPages: false,
|
||||||
|
ExportOnlyNotesPages: false,
|
||||||
|
ExportNotesInMargin: false,
|
||||||
|
ConvertOooTargetToPdfTarget: false,
|
||||||
|
ExportLinksRelativeFsys: false,
|
||||||
|
ExportHiddenSlides: false,
|
||||||
|
SkipEmptyPages: false,
|
||||||
|
AddOriginalDocumentAsStream: false,
|
||||||
|
SinglePageSheets: false,
|
||||||
|
LosslessImageCompression: false,
|
||||||
|
Quality: 90,
|
||||||
|
ReduceImageResolution: false,
|
||||||
|
MaxImageResolution: 300,
|
||||||
|
PdfFormats: gotenberg.PdfFormats{
|
||||||
|
PdfA: "",
|
||||||
|
PdfUa: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Uno is an abstraction on top of the Universal Network Objects API.
|
// Uno is an abstraction on top of the Universal Network Objects API.
|
||||||
type Uno interface {
|
type Uno interface {
|
||||||
Pdf(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error
|
Pdf(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ import (
|
|||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestDefaultOptions(t *testing.T) {
|
||||||
|
actual := DefaultOptions()
|
||||||
|
notExpect := Options{}
|
||||||
|
|
||||||
|
if reflect.DeepEqual(actual, notExpect) {
|
||||||
|
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApi_Descriptor(t *testing.T) {
|
func TestApi_Descriptor(t *testing.T) {
|
||||||
descriptor := new(Api).Descriptor()
|
descriptor := new(Api).Descriptor()
|
||||||
|
|
||||||
|
|||||||
@@ -273,21 +273,26 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP
|
|||||||
args = append(args, "--export", fmt.Sprintf("PageRange=%s", options.PageRanges))
|
args = append(args, "--export", fmt.Sprintf("PageRange=%s", options.PageRanges))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options.ExportFormFields {
|
args = append(args, "--export", fmt.Sprintf("ExportFormFields=%t", options.ExportFormFields))
|
||||||
args = append(args, "--export", "ExportFormFields=false")
|
args = append(args, "--export", fmt.Sprintf("AllowDuplicateFieldNames=%t", options.AllowDuplicateFieldNames))
|
||||||
}
|
args = append(args, "--export", fmt.Sprintf("ExportBookmarks=%t", options.ExportBookmarks))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("ExportBookmarks=%t", options.ExportBookmarks))
|
||||||
if options.SinglePageSheets {
|
args = append(args, "--export", fmt.Sprintf("ExportBookmarksToPDFDestination=%t", options.ExportBookmarksToPdfDestination))
|
||||||
args = append(args, "--export", "SinglePageSheets=true")
|
args = append(args, "--export", fmt.Sprintf("ExportPlaceholders=%t", options.ExportPlaceholders))
|
||||||
}
|
args = append(args, "--export", fmt.Sprintf("ExportNotes=%t", options.ExportNotes))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("ExportNotesPages=%t", options.ExportNotesPages))
|
||||||
if options.LosslessImageCompression {
|
args = append(args, "--export", fmt.Sprintf("ExportOnlyNotesPages=%t", options.ExportOnlyNotesPages))
|
||||||
args = append(args, "--export", "UseLosslessCompression=true")
|
args = append(args, "--export", fmt.Sprintf("ExportNotesInMargin=%t", options.ExportNotesInMargin))
|
||||||
}
|
args = append(args, "--export", fmt.Sprintf("ConvertOOoTargetToPDFTarget=%t", options.ConvertOooTargetToPdfTarget))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("ExportLinksRelativeFsys=%t", options.ExportLinksRelativeFsys))
|
||||||
if !options.ReduceImageResolution {
|
args = append(args, "--export", fmt.Sprintf("ExportHiddenSlides=%t", options.ExportHiddenSlides))
|
||||||
args = append(args, "--export", "ReduceImageResolution=false")
|
args = append(args, "--export", fmt.Sprintf("IsSkipEmptyPages=%t", options.SkipEmptyPages))
|
||||||
}
|
args = append(args, "--export", fmt.Sprintf("IsAddStream=%t", options.AddOriginalDocumentAsStream))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("SinglePageSheets=%t", options.SinglePageSheets))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("UseLosslessCompression=%t", options.LosslessImageCompression))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("Quality=%d", options.Quality))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("ReduceImageResolution=%t", options.ReduceImageResolution))
|
||||||
|
args = append(args, "--export", fmt.Sprintf("MaxImageResolution=%d", options.MaxImageResolution))
|
||||||
|
|
||||||
switch options.PdfFormats.PdfA {
|
switch options.PdfFormats.PdfA {
|
||||||
case "":
|
case "":
|
||||||
@@ -304,12 +309,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", "PDFUACompliance=true",
|
||||||
"--export", "UseTaggedPDF=true",
|
"--export", "UseTaggedPDF=true",
|
||||||
"--export", "EnableTextAccessForAccessibilityTools=true",
|
"--export", "EnableTextAccessForAccessibilityTools=true",
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
args = append(
|
args = append(
|
||||||
args,
|
args,
|
||||||
|
"--export", "PDFUACompliance=false",
|
||||||
"--export", "UseTaggedPDF=false",
|
"--export", "UseTaggedPDF=false",
|
||||||
"--export", "EnableTextAccessForAccessibilityTools=false",
|
"--export", "EnableTextAccessForAccessibilityTools=false",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success (landscape)",
|
scenario: "success (not default options)",
|
||||||
libreOffice: newLibreOfficeProcess(
|
libreOffice: newLibreOfficeProcess(
|
||||||
libreOfficeArguments{
|
libreOfficeArguments{
|
||||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||||
@@ -352,159 +352,36 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755)
|
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
options: Options{Landscape: true},
|
options: Options{
|
||||||
cancelledCtx: false,
|
Landscape: true,
|
||||||
start: true,
|
PageRanges: "1",
|
||||||
expectError: false,
|
ExportFormFields: false,
|
||||||
},
|
AllowDuplicateFieldNames: true,
|
||||||
{
|
ExportBookmarks: false,
|
||||||
scenario: "success (disable form fields)",
|
ExportBookmarksToPdfDestination: true,
|
||||||
libreOffice: newLibreOfficeProcess(
|
ExportPlaceholders: true,
|
||||||
libreOfficeArguments{
|
ExportNotes: true,
|
||||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
ExportNotesPages: true,
|
||||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
ExportOnlyNotesPages: true,
|
||||||
startTimeout: 5 * time.Second,
|
ExportNotesInMargin: true,
|
||||||
},
|
ConvertOooTargetToPdfTarget: true,
|
||||||
),
|
ExportLinksRelativeFsys: true,
|
||||||
fs: func() *gotenberg.FileSystem {
|
ExportHiddenSlides: true,
|
||||||
fs := gotenberg.NewFileSystem()
|
SkipEmptyPages: true,
|
||||||
|
AddOriginalDocumentAsStream: true,
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
SinglePageSheets: true,
|
||||||
if err != nil {
|
LosslessImageCompression: true,
|
||||||
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
Quality: 100,
|
||||||
}
|
ReduceImageResolution: true,
|
||||||
|
MaxImageResolution: 600,
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("DisableFormFields"), 0o755)
|
},
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
options: Options{ExportFormFields: false},
|
|
||||||
cancelledCtx: false,
|
|
||||||
start: true,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "success (single page sheets)",
|
|
||||||
libreOffice: newLibreOfficeProcess(
|
|
||||||
libreOfficeArguments{
|
|
||||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
|
||||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
|
||||||
startTimeout: 5 * time.Second,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
fs: func() *gotenberg.FileSystem {
|
|
||||||
fs := gotenberg.NewFileSystem()
|
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("SinglePageSheets"), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
options: Options{SinglePageSheets: true},
|
|
||||||
cancelledCtx: false,
|
|
||||||
start: true,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "success (page ranges)",
|
|
||||||
libreOffice: newLibreOfficeProcess(
|
|
||||||
libreOfficeArguments{
|
|
||||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
|
||||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
|
||||||
startTimeout: 5 * time.Second,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
fs: func() *gotenberg.FileSystem {
|
|
||||||
fs := gotenberg.NewFileSystem()
|
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
options: Options{PageRanges: "1-1"},
|
|
||||||
cancelledCtx: false,
|
|
||||||
start: true,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "success LosslessImageCompression",
|
|
||||||
libreOffice: newLibreOfficeProcess(
|
|
||||||
libreOfficeArguments{
|
|
||||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
|
||||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
|
||||||
startTimeout: 5 * time.Second,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
fs: func() *gotenberg.FileSystem {
|
|
||||||
fs := gotenberg.NewFileSystem()
|
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("LosslessImageCompression"), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
options: Options{LosslessImageCompression: true},
|
|
||||||
cancelledCtx: false,
|
|
||||||
start: true,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "success ReduceImageResolution",
|
|
||||||
libreOffice: newLibreOfficeProcess(
|
|
||||||
libreOfficeArguments{
|
|
||||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
|
||||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
|
||||||
startTimeout: 5 * time.Second,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
fs: func() *gotenberg.FileSystem {
|
|
||||||
fs := gotenberg.NewFileSystem()
|
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("ReduceImageResolution"), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
options: Options{ReduceImageResolution: false},
|
|
||||||
cancelledCtx: false,
|
cancelledCtx: false,
|
||||||
start: true,
|
start: true,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
@@ -22,31 +24,100 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
IsMultipart: true,
|
IsMultipart: true,
|
||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
|
defaultOptions := libreofficeapi.DefaultOptions()
|
||||||
|
|
||||||
// Let's get the data from the form and validate them.
|
// Let's get the data from the form and validate them.
|
||||||
var (
|
var (
|
||||||
inputPaths []string
|
inputPaths []string
|
||||||
landscape bool
|
landscape bool
|
||||||
nativePageRanges string
|
nativePageRanges string
|
||||||
exportFormFields bool
|
exportFormFields bool
|
||||||
singlePageSheets bool
|
allowDuplicateFieldNames bool
|
||||||
losslessImageCompression bool
|
exportBookmarks bool
|
||||||
reduceImageResolution bool
|
exportBookmarksToPdfDestination bool
|
||||||
pdfa string
|
exportPlaceholders bool
|
||||||
pdfua bool
|
exportNotes bool
|
||||||
nativePdfFormats bool
|
exportNotesPages bool
|
||||||
merge bool
|
exportOnlyNotesPages bool
|
||||||
metadata map[string]interface{}
|
exportNotesInMargin bool
|
||||||
|
convertOooTargetToPdfTarget bool
|
||||||
|
exportLinksRelativeFsys bool
|
||||||
|
exportHiddenSlides bool
|
||||||
|
skipEmptyPages bool
|
||||||
|
addOriginalDocumentAsStream bool
|
||||||
|
singlePageSheets bool
|
||||||
|
losslessImageCompression bool
|
||||||
|
quality int
|
||||||
|
reduceImageResolution bool
|
||||||
|
maxImageResolution int
|
||||||
|
pdfa string
|
||||||
|
pdfua bool
|
||||||
|
nativePdfFormats bool
|
||||||
|
merge bool
|
||||||
|
metadata map[string]interface{}
|
||||||
)
|
)
|
||||||
|
|
||||||
err := ctx.FormData().
|
err := ctx.FormData().
|
||||||
MandatoryPaths(libreOffice.Extensions(), &inputPaths).
|
MandatoryPaths(libreOffice.Extensions(), &inputPaths).
|
||||||
Bool("landscape", &landscape, false).
|
Bool("landscape", &landscape, defaultOptions.Landscape).
|
||||||
String("nativePageRanges", &nativePageRanges, "").
|
String("nativePageRanges", &nativePageRanges, defaultOptions.PageRanges).
|
||||||
Bool("exportFormFields", &exportFormFields, true).
|
Bool("exportFormFields", &exportFormFields, defaultOptions.ExportFormFields).
|
||||||
Bool("singlePageSheets", &singlePageSheets, false).
|
Bool("allowDuplicateFieldNames", &allowDuplicateFieldNames, defaultOptions.AllowDuplicateFieldNames).
|
||||||
Bool("losslessImageCompression", &losslessImageCompression, false).
|
Bool("exportBookmarks", &exportBookmarks, defaultOptions.ExportBookmarks).
|
||||||
Bool("reduceImageResolution", &reduceImageResolution, true).
|
Bool("exportBookmarksToPdfDestination", &exportBookmarksToPdfDestination, defaultOptions.ExportBookmarksToPdfDestination).
|
||||||
|
Bool("exportPlaceholders", &exportPlaceholders, defaultOptions.ExportPlaceholders).
|
||||||
|
Bool("exportNotes", &exportNotes, defaultOptions.ExportNotes).
|
||||||
|
Bool("exportNotesPages", &exportNotesPages, defaultOptions.ExportNotesPages).
|
||||||
|
Bool("exportOnlyNotesPages", &exportOnlyNotesPages, defaultOptions.ExportOnlyNotesPages).
|
||||||
|
Bool("exportNotesInMargin", &exportNotesInMargin, defaultOptions.ExportNotesInMargin).
|
||||||
|
Bool("convertOooTargetToPdfTarget", &convertOooTargetToPdfTarget, defaultOptions.ConvertOooTargetToPdfTarget).
|
||||||
|
Bool("exportLinksRelativeFsys", &exportLinksRelativeFsys, defaultOptions.ExportLinksRelativeFsys).
|
||||||
|
Bool("exportHiddenSlides", &exportHiddenSlides, defaultOptions.ExportHiddenSlides).
|
||||||
|
Bool("skipEmptyPages", &skipEmptyPages, defaultOptions.SkipEmptyPages).
|
||||||
|
Bool("addOriginalDocumentAsStream", &addOriginalDocumentAsStream, defaultOptions.AddOriginalDocumentAsStream).
|
||||||
|
Bool("singlePageSheets", &singlePageSheets, defaultOptions.SinglePageSheets).
|
||||||
|
Bool("losslessImageCompression", &losslessImageCompression, defaultOptions.LosslessImageCompression).
|
||||||
|
Custom("quality", func(value string) error {
|
||||||
|
if value == "" {
|
||||||
|
quality = defaultOptions.Quality
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if intValue < 1 {
|
||||||
|
return errors.New("value is inferior to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if intValue > 100 {
|
||||||
|
return errors.New("value is superior to 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
quality = intValue
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
|
Bool("reduceImageResolution", &reduceImageResolution, defaultOptions.ReduceImageResolution).
|
||||||
|
Custom("maxImageResolution", func(value string) error {
|
||||||
|
if value == "" {
|
||||||
|
maxImageResolution = defaultOptions.MaxImageResolution
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Contains([]int{75, 150, 300, 600, 1200}, intValue) {
|
||||||
|
return errors.New("value is not 75, 150, 300, 600 or 1200")
|
||||||
|
}
|
||||||
|
|
||||||
|
maxImageResolution = intValue
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
String("pdfa", &pdfa, "").
|
String("pdfa", &pdfa, "").
|
||||||
Bool("pdfua", &pdfua, false).
|
Bool("pdfua", &pdfua, false).
|
||||||
Bool("nativePdfFormats", &nativePdfFormats, true).
|
Bool("nativePdfFormats", &nativePdfFormats, true).
|
||||||
@@ -75,12 +146,27 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
for i, inputPath := range inputPaths {
|
for i, inputPath := range inputPaths {
|
||||||
outputPaths[i] = ctx.GeneratePath(".pdf")
|
outputPaths[i] = ctx.GeneratePath(".pdf")
|
||||||
options := libreofficeapi.Options{
|
options := libreofficeapi.Options{
|
||||||
Landscape: landscape,
|
Landscape: landscape,
|
||||||
PageRanges: nativePageRanges,
|
PageRanges: nativePageRanges,
|
||||||
ExportFormFields: exportFormFields,
|
ExportFormFields: exportFormFields,
|
||||||
SinglePageSheets: singlePageSheets,
|
AllowDuplicateFieldNames: allowDuplicateFieldNames,
|
||||||
LosslessImageCompression: losslessImageCompression,
|
ExportBookmarks: exportBookmarks,
|
||||||
ReduceImageResolution: reduceImageResolution,
|
ExportBookmarksToPdfDestination: exportBookmarksToPdfDestination,
|
||||||
|
ExportPlaceholders: exportPlaceholders,
|
||||||
|
ExportNotes: exportNotes,
|
||||||
|
ExportNotesPages: exportNotesPages,
|
||||||
|
ExportOnlyNotesPages: exportOnlyNotesPages,
|
||||||
|
ExportNotesInMargin: exportNotesInMargin,
|
||||||
|
ConvertOooTargetToPdfTarget: convertOooTargetToPdfTarget,
|
||||||
|
ExportLinksRelativeFsys: exportLinksRelativeFsys,
|
||||||
|
ExportHiddenSlides: exportHiddenSlides,
|
||||||
|
SkipEmptyPages: skipEmptyPages,
|
||||||
|
AddOriginalDocumentAsStream: addOriginalDocumentAsStream,
|
||||||
|
SinglePageSheets: singlePageSheets,
|
||||||
|
LosslessImageCompression: losslessImageCompression,
|
||||||
|
Quality: quality,
|
||||||
|
ReduceImageResolution: reduceImageResolution,
|
||||||
|
MaxImageResolution: maxImageResolution,
|
||||||
}
|
}
|
||||||
|
|
||||||
if nativePdfFormats {
|
if nativePdfFormats {
|
||||||
|
|||||||
@@ -39,6 +39,116 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid quality form field (not an integer)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"quality": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
}},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid quality form field (< 1)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"quality": {
|
||||||
|
"0",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
}},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid quality form field (> 100)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"quality": {
|
||||||
|
"101",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
}},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid maxImageResolution form field (not an integer)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"maxImageResolution": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
}},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid maxImageResolution form field (not in range)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"maxImageResolution": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
}},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid metadata form field",
|
scenario: "invalid metadata form field",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -285,6 +395,12 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
"document2.docx": "/document2.docx",
|
"document2.docx": "/document2.docx",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
|
"quality": {
|
||||||
|
"100",
|
||||||
|
},
|
||||||
|
"maxImageResolution": {
|
||||||
|
"1200",
|
||||||
|
},
|
||||||
"merge": {
|
"merge": {
|
||||||
"true",
|
"true",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user