mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-15 11:52:14 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa57b17254 | ||
|
|
7df786c5c6 | ||
|
|
b315464e98 | ||
|
|
1252ea076b | ||
|
|
d970e446a8 | ||
|
|
fe40f3727b | ||
|
|
10a290b065 |
4
Makefile
4
Makefile
@@ -11,7 +11,7 @@ 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.047 # 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.60.3 # See https://github.com/golangci/golangci-lint/releases.
|
GOLANGCI_LINT_VERSION=v1.60.3 # See https://github.com/golangci/golangci-lint/releases.
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ API_PORT=3000
|
|||||||
API_PORT_FROM_ENV=
|
API_PORT_FROM_ENV=
|
||||||
API_START_TIMEOUT=30s
|
API_START_TIMEOUT=30s
|
||||||
API_TIMEOUT=30s
|
API_TIMEOUT=30s
|
||||||
|
API_BODY_LIMIT=
|
||||||
API_ROOT_PATH=/
|
API_ROOT_PATH=/
|
||||||
API_TRACE_HEADER=Gotenberg-Trace
|
API_TRACE_HEADER=Gotenberg-Trace
|
||||||
API_ENABLE_BASIC_AUTH=false
|
API_ENABLE_BASIC_AUTH=false
|
||||||
@@ -96,6 +97,7 @@ run: ## Start a Gotenberg container
|
|||||||
--api-port-from-env=$(API_PORT_FROM_ENV) \
|
--api-port-from-env=$(API_PORT_FROM_ENV) \
|
||||||
--api-start-timeout=$(API_START_TIMEOUT) \
|
--api-start-timeout=$(API_START_TIMEOUT) \
|
||||||
--api-timeout=$(API_TIMEOUT) \
|
--api-timeout=$(API_TIMEOUT) \
|
||||||
|
--api-body-limit="$(API_BODY_LIMIT)" \
|
||||||
--api-root-path=$(API_ROOT_PATH) \
|
--api-root-path=$(API_ROOT_PATH) \
|
||||||
--api-trace-header=$(API_TRACE_HEADER) \
|
--api-trace-header=$(API_TRACE_HEADER) \
|
||||||
--api-enable-basic-auth=$(API_ENABLE_BASIC_AUTH) \
|
--api-enable-basic-auth=$(API_ENABLE_BASIC_AUTH) \
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -6,13 +6,13 @@ 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-20240919203636-12af5e8a671f
|
github.com/chromedp/cdproto v0.0.0-20241003230502-a4a8f7c660df
|
||||||
github.com/chromedp/chromedp v0.10.0
|
github.com/chromedp/chromedp v0.10.0
|
||||||
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
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
github.com/klauspost/compress v1.17.10 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/labstack/echo/v4 v4.12.0
|
github.com/labstack/echo/v4 v4.12.0
|
||||||
github.com/labstack/gommon v0.4.2
|
github.com/labstack/gommon v0.4.2
|
||||||
@@ -28,13 +28,13 @@ 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.27.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
golang.org/x/image v0.20.0 // indirect
|
golang.org/x/image v0.21.0 // indirect
|
||||||
golang.org/x/net v0.29.0
|
golang.org/x/net v0.30.0
|
||||||
golang.org/x/sync v0.8.0
|
golang.org/x/sync v0.8.0
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/term v0.24.0
|
golang.org/x/term v0.25.0
|
||||||
golang.org/x/text v0.18.0
|
golang.org/x/text v0.19.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/dlclark/regexp2 v1.11.4
|
require github.com/dlclark/regexp2 v1.11.4
|
||||||
@@ -59,13 +59,13 @@ require (
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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.59.1 // indirect
|
github.com/prometheus/common v0.60.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
|
||||||
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
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
40
go.sum
40
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-20240801214329-3f85d328b335/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20240801214329-3f85d328b335/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240919203636-12af5e8a671f h1:dEjjp+iN34En5Pl9XIi978DmR2/CMwuOxoPWtiHixKQ=
|
github.com/chromedp/cdproto v0.0.0-20241003230502-a4a8f7c660df h1:cbtSn19AtqQha1cxmP2Qvgd3fFMz51AeAEKLJMyEUhc=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240919203636-12af5e8a671f/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20241003230502-a4a8f7c660df/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/chromedp v0.10.0 h1:bRclRYVpMm/UVD76+1HcRW9eV3l58rFfy7AdBvKab1E=
|
github.com/chromedp/chromedp v0.10.0 h1:bRclRYVpMm/UVD76+1HcRW9eV3l58rFfy7AdBvKab1E=
|
||||||
github.com/chromedp/chromedp v0.10.0/go.mod h1:ei/1ncZIqXX1YnAYDkxhD4gzBgavMEUu7JCKvztdomE=
|
github.com/chromedp/chromedp v0.10.0/go.mod h1:ei/1ncZIqXX1YnAYDkxhD4gzBgavMEUu7JCKvztdomE=
|
||||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||||
@@ -60,8 +60,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
|||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
@@ -111,8 +111,8 @@ github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zI
|
|||||||
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
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.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0=
|
github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
|
||||||
github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
|
github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||||
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=
|
||||||
@@ -146,25 +146,25 @@ 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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
|
||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.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.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
|||||||
@@ -168,33 +168,37 @@ func (f *ParsedFlags) MustDeprecatedDuration(deprecated string, newName string)
|
|||||||
return f.MustDuration(newName)
|
return f.MustDuration(newName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustHumanReadableBytesString returns the human-readable bytes string of a
|
// MustHumanReadableBytes returns the human-readable bytes string of a flag
|
||||||
// flag given by name.
|
// given by name.
|
||||||
// It panics if an error occurs.
|
// It panics if an error occurs.
|
||||||
func (f *ParsedFlags) MustHumanReadableBytesString(name string) string {
|
func (f *ParsedFlags) MustHumanReadableBytes(name string) int64 {
|
||||||
val, err := f.GetString(name)
|
val, err := f.GetString(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = bytes.Parse(val)
|
if val == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := bytes.Parse(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return val
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustDeprecatedHumanReadableBytesString returns the human-readable bytes
|
// MustDeprecatedHumanReadableBytes returns the human-readable bytes of a
|
||||||
// string of a deprecated flag if it was explicitly set or the human-readable
|
// deprecated flag if it was explicitly set or the human-readable bytes string
|
||||||
// bytes string of the new flag.
|
// of the new flag.
|
||||||
// It panics if an error occurs.
|
// It panics if an error occurs.
|
||||||
func (f *ParsedFlags) MustDeprecatedHumanReadableBytesString(deprecated string, newName string) string {
|
func (f *ParsedFlags) MustDeprecatedHumanReadableBytes(deprecated string, newName string) int64 {
|
||||||
if f.Changed(deprecated) {
|
if f.Changed(deprecated) {
|
||||||
return f.MustHumanReadableBytesString(deprecated)
|
return f.MustHumanReadableBytes(deprecated)
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.MustHumanReadableBytesString(newName)
|
return f.MustHumanReadableBytes(newName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustRegexp returns the regular expression of a flag given by name.
|
// MustRegexp returns the regular expression of a flag given by name.
|
||||||
|
|||||||
@@ -644,10 +644,11 @@ func TestParsedFlags_MustDeprecatedDuration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsedFlags_MustHumanReadableBytesString(t *testing.T) {
|
func TestParsedFlags_MustHumanReadableBytes(t *testing.T) {
|
||||||
fs := flag.NewFlagSet("tests", flag.ContinueOnError)
|
fs := flag.NewFlagSet("tests", flag.ContinueOnError)
|
||||||
fs.String("foo", "1MB", "")
|
fs.String("foo", "1MB", "")
|
||||||
fs.String("bar", "1MB", "")
|
fs.String("bar", "1MB", "")
|
||||||
|
fs.String("qux", "", "")
|
||||||
|
|
||||||
err := fs.Parse([]string{"--foo=1GB", "--bar=foo"})
|
err := fs.Parse([]string{"--foo=1GB", "--bar=foo"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -671,6 +672,11 @@ func TestParsedFlags_MustHumanReadableBytesString(t *testing.T) {
|
|||||||
name: "bar",
|
name: "bar",
|
||||||
expectPanic: true,
|
expectPanic: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "success: empty value",
|
||||||
|
name: "qux",
|
||||||
|
expectPanic: false,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
if tc.expectPanic {
|
if tc.expectPanic {
|
||||||
@@ -689,31 +695,31 @@ func TestParsedFlags_MustHumanReadableBytesString(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedFlags.MustHumanReadableBytesString(tc.name)
|
parsedFlags.MustHumanReadableBytes(tc.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsedFlags_MustDeprecatedHumanReadableBytesString(t *testing.T) {
|
func TestParsedFlags_MustDeprecatedHumanReadableBytes(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
rawFlags []string
|
rawFlags []string
|
||||||
expectValue string
|
expectValue int64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "deprecated flag value",
|
scenario: "deprecated flag value",
|
||||||
rawFlags: []string{"--foo=1MB"},
|
rawFlags: []string{"--foo=1MB"},
|
||||||
expectValue: "1MB",
|
expectValue: 1000000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "non-deprecated flag value",
|
scenario: "non-deprecated flag value",
|
||||||
rawFlags: []string{"--bar=2MB"},
|
rawFlags: []string{"--bar=2MB"},
|
||||||
expectValue: "2MB",
|
expectValue: 2000000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "deprecated flag value > non-deprecated flag value",
|
scenario: "deprecated flag value > non-deprecated flag value",
|
||||||
rawFlags: []string{"--foo=1MB", "--bar=2MB"},
|
rawFlags: []string{"--foo=1MB", "--bar=2MB"},
|
||||||
expectValue: "1MB",
|
expectValue: 1000000,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
@@ -728,9 +734,9 @@ func TestParsedFlags_MustDeprecatedHumanReadableBytesString(t *testing.T) {
|
|||||||
t.Fatalf("expected no error but got: %v", err)
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := parsedFlags.MustDeprecatedHumanReadableBytesString("foo", "bar")
|
actual := parsedFlags.MustDeprecatedHumanReadableBytes("foo", "bar")
|
||||||
if actual != tc.expectValue {
|
if actual != tc.expectValue {
|
||||||
t.Errorf("expected '%s' but got '%s'", tc.expectValue, actual)
|
t.Errorf("expected %d but got %d", tc.expectValue, actual)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ type Api struct {
|
|||||||
tlsCertFile string
|
tlsCertFile string
|
||||||
tlsKeyFile string
|
tlsKeyFile string
|
||||||
startTimeout time.Duration
|
startTimeout time.Duration
|
||||||
|
bodyLimit int64
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
rootPath string
|
rootPath string
|
||||||
traceHeader string
|
traceHeader string
|
||||||
@@ -174,6 +175,7 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
fs.String("api-tls-key-file", "", "Path to the TLS/SSL key file - for HTTPS support")
|
fs.String("api-tls-key-file", "", "Path to the TLS/SSL key file - for HTTPS support")
|
||||||
fs.Duration("api-start-timeout", time.Duration(30)*time.Second, "Set the time limit for the API to start")
|
fs.Duration("api-start-timeout", time.Duration(30)*time.Second, "Set the time limit for the API to start")
|
||||||
fs.Duration("api-timeout", time.Duration(30)*time.Second, "Set the time limit for requests")
|
fs.Duration("api-timeout", time.Duration(30)*time.Second, "Set the time limit for requests")
|
||||||
|
fs.String("api-body-limit", "", "Set the body limit for multipart/form-data requests")
|
||||||
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
|
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
|
||||||
fs.String("api-trace-header", "Gotenberg-Trace", "Set the header name to use for identifying requests")
|
fs.String("api-trace-header", "Gotenberg-Trace", "Set the header name to use for identifying requests")
|
||||||
fs.Bool("api-enable-basic-auth", false, "Enable basic authentication - will look for the GOTENBERG_API_BASIC_AUTH_USERNAME and GOTENBERG_API_BASIC_AUTH_PASSWORD environment variables")
|
fs.Bool("api-enable-basic-auth", false, "Enable basic authentication - will look for the GOTENBERG_API_BASIC_AUTH_USERNAME and GOTENBERG_API_BASIC_AUTH_PASSWORD environment variables")
|
||||||
@@ -196,6 +198,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
a.tlsKeyFile = flags.MustString("api-tls-key-file")
|
a.tlsKeyFile = flags.MustString("api-tls-key-file")
|
||||||
a.startTimeout = flags.MustDuration("api-start-timeout")
|
a.startTimeout = flags.MustDuration("api-start-timeout")
|
||||||
a.timeout = flags.MustDuration("api-timeout")
|
a.timeout = flags.MustDuration("api-timeout")
|
||||||
|
a.bodyLimit = flags.MustHumanReadableBytes("api-body-limit")
|
||||||
a.rootPath = flags.MustString("api-root-path")
|
a.rootPath = flags.MustString("api-root-path")
|
||||||
a.traceHeader = flags.MustString("api-trace-header")
|
a.traceHeader = flags.MustString("api-trace-header")
|
||||||
a.downloadFromCfg = downloadFromConfig{
|
a.downloadFromCfg = downloadFromConfig{
|
||||||
@@ -455,7 +458,7 @@ func (a *Api) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if route.IsMultipart {
|
if route.IsMultipart {
|
||||||
middlewares = append(middlewares, contextMiddleware(a.fs, a.timeout, a.downloadFromCfg))
|
middlewares = append(middlewares, contextMiddleware(a.fs, a.timeout, a.bodyLimit, a.downloadFromCfg))
|
||||||
|
|
||||||
for _, externalMultipartMiddleware := range externalMultipartMiddlewares {
|
for _, externalMultipartMiddleware := range externalMultipartMiddlewares {
|
||||||
middlewares = append(middlewares, externalMultipartMiddleware.Handler)
|
middlewares = append(middlewares, externalMultipartMiddleware.Handler)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -50,6 +51,28 @@ type Context struct {
|
|||||||
context.Context
|
context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type trackingReader struct {
|
||||||
|
R io.Reader
|
||||||
|
AddReadBytes func(n int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *trackingReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := t.R.Read(p)
|
||||||
|
if n > 0 {
|
||||||
|
errAddRead := t.AddReadBytes(int64(n))
|
||||||
|
if errAddRead != nil {
|
||||||
|
return n, fmt.Errorf("add read bytes: %w", errAddRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// It's a common practice in Go to return io.EOF unwrapped to signal
|
||||||
|
// the end of a data stream. Wrapping it can lead to unexpected
|
||||||
|
// behavior in standard library functions.
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
type downloadFrom struct {
|
type downloadFrom struct {
|
||||||
// Url is the URL to download a file from.
|
// Url is the URL to download a file from.
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
@@ -65,9 +88,25 @@ func (o *osPathRename) Rename(oldpath, newpath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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, downloadFromCfg downloadFromConfig, traceHeader, trace string) (*Context, context.CancelFunc, error) {
|
func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSystem, timeout time.Duration, bodyLimit int64, downloadFromCfg downloadFromConfig, traceHeader, trace string) (*Context, context.CancelFunc, error) {
|
||||||
processCtx, processCancel := context.WithTimeout(context.Background(), timeout)
|
processCtx, processCancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
|
||||||
|
// We want to make sure the multipart/form-data does not exceed a given
|
||||||
|
// limit. We consider: form fields (keys, values, files) and files
|
||||||
|
// downloaded remotely ("download from" feature).
|
||||||
|
var totalBytesRead atomic.Int64
|
||||||
|
|
||||||
|
addReadBytes := func(n int64) error {
|
||||||
|
newTotal := totalBytesRead.Add(n)
|
||||||
|
if bodyLimit != 0 && newTotal > bodyLimit {
|
||||||
|
return WrapError(
|
||||||
|
fmt.Errorf("body limit reached (> %d)", bodyLimit),
|
||||||
|
NewSentinelHttpError(http.StatusRequestEntityTooLarge, http.StatusText(http.StatusRequestEntityTooLarge)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ctx := &Context{
|
ctx := &Context{
|
||||||
outputPaths: make([]string, 0),
|
outputPaths: make([]string, 0),
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
@@ -129,6 +168,19 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
return nil, cancel, fmt.Errorf("get multipart form: %w", err)
|
return nil, cancel, fmt.Errorf("get multipart form: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will ensure we do not exceed the body limit.
|
||||||
|
var formValuesSize int64
|
||||||
|
for key, valArray := range form.Value {
|
||||||
|
formValuesSize += int64(len(key))
|
||||||
|
for _, val := range valArray {
|
||||||
|
formValuesSize += int64(len(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = addReadBytes(formValuesSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, cancel, fmt.Errorf("add read bytes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
dirPath, err := fs.MkdirAll()
|
dirPath, err := fs.MkdirAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cancel, fmt.Errorf("create working directory: %w", err)
|
return nil, cancel, fmt.Errorf("create working directory: %w", err)
|
||||||
@@ -262,9 +314,12 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err = io.Copy(out, resp.Body)
|
// This will ensure we do not exceed the body limit.
|
||||||
|
reader := &trackingReader{R: resp.Body, AddReadBytes: addReadBytes}
|
||||||
|
|
||||||
|
_, err = io.Copy(out, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copy downloaded file from '%s' to local file: %v", dl.Url, err)
|
return fmt.Errorf("copy downloaded file from '%s' to local file: %w", dl.Url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.files[filename] = path
|
ctx.files[filename] = path
|
||||||
@@ -292,6 +347,9 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// This will ensure we do not exceed the body limit.
|
||||||
|
reader := &trackingReader{R: in, AddReadBytes: addReadBytes}
|
||||||
|
|
||||||
// Avoid directory traversal and make sure filename characters are
|
// Avoid directory traversal and make sure filename characters are
|
||||||
// normalized.
|
// normalized.
|
||||||
// See: https://github.com/gotenberg/gotenberg/issues/662.
|
// See: https://github.com/gotenberg/gotenberg/issues/662.
|
||||||
@@ -309,7 +367,7 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err = io.Copy(out, in)
|
_, err = io.Copy(out, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copy multipart file to local file: %w", err)
|
return fmt.Errorf("copy multipart file to local file: %w", err)
|
||||||
}
|
}
|
||||||
@@ -331,6 +389,7 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
|
|
||||||
ctx.Log().Debug(fmt.Sprintf("form fields: %+v", ctx.values))
|
ctx.Log().Debug(fmt.Sprintf("form fields: %+v", ctx.values))
|
||||||
ctx.Log().Debug(fmt.Sprintf("form files: %+v", ctx.files))
|
ctx.Log().Debug(fmt.Sprintf("form files: %+v", ctx.files))
|
||||||
|
ctx.Log().Debug(fmt.Sprintf("total bytes: %d", totalBytesRead.Load()))
|
||||||
|
|
||||||
return ctx, cancel, err
|
return ctx, cancel, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ func TestNewContext(t *testing.T) {
|
|||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
request *http.Request
|
request *http.Request
|
||||||
|
bodyLimit int64
|
||||||
downloadFromCfg downloadFromConfig
|
downloadFromCfg downloadFromConfig
|
||||||
downloadFromSrv *echo.Echo
|
downloadFromSrv *echo.Echo
|
||||||
expectContext *Context
|
expectContext *Context
|
||||||
@@ -143,6 +144,95 @@ func TestNewContext(t *testing.T) {
|
|||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "request entity too large: form values",
|
||||||
|
request: func() *http.Request {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
defer func() {
|
||||||
|
err := writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err := writer.WriteField("key", "value")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||||
|
return req
|
||||||
|
}(),
|
||||||
|
bodyLimit: 1,
|
||||||
|
downloadFromCfg: defaultDownloadFromCfg,
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusRequestEntityTooLarge,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "request entity too large: downloadFrom",
|
||||||
|
request: func() *http.Request {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
defer func() {
|
||||||
|
err := writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||||
|
return req
|
||||||
|
}(),
|
||||||
|
bodyLimit: 45, // form values = 44 bytes.
|
||||||
|
downloadFromSrv: func() *echo.Echo {
|
||||||
|
srv := echo.New()
|
||||||
|
srv.HideBanner = true
|
||||||
|
srv.GET("/", func(c echo.Context) error {
|
||||||
|
c.Response().Header().Set(echo.HeaderContentDisposition, `attachment; filename="bar.txt"`)
|
||||||
|
c.Response().Header().Set(echo.HeaderContentType, "text/plain")
|
||||||
|
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||||
|
})
|
||||||
|
return srv
|
||||||
|
}(),
|
||||||
|
downloadFromCfg: defaultDownloadFromCfg,
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusRequestEntityTooLarge,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "request entity too large: form files",
|
||||||
|
request: func() *http.Request {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
defer func() {
|
||||||
|
err := writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
part, err := writer.CreateFormFile("foo.txt", "foo.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
_, err = part.Write([]byte("foo"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||||
|
return req
|
||||||
|
}(),
|
||||||
|
bodyLimit: 1,
|
||||||
|
downloadFromCfg: defaultDownloadFromCfg,
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusRequestEntityTooLarge,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid downloadFrom form field: cannot unmarshal",
|
scenario: "invalid downloadFrom form field: cannot unmarshal",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
@@ -458,7 +548,7 @@ func TestNewContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler := func(c echo.Context) error {
|
handler := func(c echo.Context) error {
|
||||||
ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(), time.Duration(10)*time.Second, tc.downloadFromCfg, "Gotenberg-Trace", "123")
|
ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(), time.Duration(10)*time.Second, tc.bodyLimit, tc.downloadFromCfg, "Gotenberg-Trace", "123")
|
||||||
defer cancel()
|
defer cancel()
|
||||||
// Context already cancelled.
|
// Context already cancelled.
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ func basicAuthMiddleware(username, password string) echo.MiddlewareFunc {
|
|||||||
//
|
//
|
||||||
// ctx := c.Get("context").(*api.Context)
|
// ctx := c.Get("context").(*api.Context)
|
||||||
// cancel := c.Get("cancel").(context.CancelFunc)
|
// cancel := c.Get("cancel").(context.CancelFunc)
|
||||||
func contextMiddleware(fs *gotenberg.FileSystem, timeout time.Duration, downloadFromCfg downloadFromConfig) echo.MiddlewareFunc {
|
func contextMiddleware(fs *gotenberg.FileSystem, timeout time.Duration, bodyLimit int64, downloadFromCfg downloadFromConfig) echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
logger := c.Get("logger").(*zap.Logger)
|
logger := c.Get("logger").(*zap.Logger)
|
||||||
@@ -245,7 +245,7 @@ func contextMiddleware(fs *gotenberg.FileSystem, timeout time.Duration, download
|
|||||||
|
|
||||||
// We create a context with a timeout so that underlying processes are
|
// We create a context with a timeout so that underlying processes are
|
||||||
// able to stop early and handle correctly a timeout scenario.
|
// able to stop early and handle correctly a timeout scenario.
|
||||||
ctx, cancel, err := newContext(c, logger, fs, timeout, downloadFromCfg, traceHeader, trace)
|
ctx, cancel, err := newContext(c, logger, fs, timeout, bodyLimit, downloadFromCfg, traceHeader, trace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ func TestContextMiddleware(t *testing.T) {
|
|||||||
c.Set("trace", "foo")
|
c.Set("trace", "foo")
|
||||||
c.Set("startTime", time.Now())
|
c.Set("startTime", time.Now())
|
||||||
|
|
||||||
err := contextMiddleware(gotenberg.NewFileSystem(), time.Duration(10)*time.Second, downloadFromConfig{})(tc.next)(c)
|
err := contextMiddleware(gotenberg.NewFileSystem(), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c)
|
||||||
|
|
||||||
if tc.expectErr && err == nil {
|
if tc.expectErr && err == nil {
|
||||||
t.Errorf("test %d: expected error but got: %v", i, err)
|
t.Errorf("test %d: expected error but got: %v", i, err)
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "skip networkIdle event",
|
scenario: "do not skip networkIdle event",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
@@ -404,13 +404,13 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
options: PdfOptions{
|
options: PdfOptions{
|
||||||
Options: Options{SkipNetworkIdleEvent: true},
|
Options: Options{SkipNetworkIdleEvent: false},
|
||||||
},
|
},
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: true,
|
start: true,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
expectedLogEntries: []string{
|
expectedLogEntries: []string{
|
||||||
"skipping network idle event",
|
"event networkIdle fired",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1225,6 +1225,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
"no cookies to set",
|
"no cookies to set",
|
||||||
"no extra HTTP headers",
|
"no extra HTTP headers",
|
||||||
"navigate to",
|
"navigate to",
|
||||||
|
"skipping network idle event",
|
||||||
"default white background not hidden",
|
"default white background not hidden",
|
||||||
"no emulated media type",
|
"no emulated media type",
|
||||||
"no wait delay",
|
"no wait delay",
|
||||||
@@ -1452,7 +1453,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "skip networkIdle event",
|
scenario: "do not skip networkIdle event",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
@@ -1477,13 +1478,13 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
options: ScreenshotOptions{
|
options: ScreenshotOptions{
|
||||||
Options: Options{SkipNetworkIdleEvent: true},
|
Options: Options{SkipNetworkIdleEvent: false},
|
||||||
},
|
},
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: true,
|
start: true,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
expectedLogEntries: []string{
|
expectedLogEntries: []string{
|
||||||
"skipping network idle event",
|
"event networkIdle fired",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2184,6 +2185,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
"no user agent override",
|
"no user agent override",
|
||||||
"no extra HTTP headers",
|
"no extra HTTP headers",
|
||||||
"navigate to",
|
"navigate to",
|
||||||
|
"skipping network idle event",
|
||||||
"default white background not hidden",
|
"default white background not hidden",
|
||||||
"no emulated media type",
|
"no emulated media type",
|
||||||
"no wait delay",
|
"no wait delay",
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ type Options struct {
|
|||||||
// DefaultOptions returns the default values for Options.
|
// DefaultOptions returns the default values for Options.
|
||||||
func DefaultOptions() Options {
|
func DefaultOptions() Options {
|
||||||
return Options{
|
return Options{
|
||||||
SkipNetworkIdleEvent: false,
|
SkipNetworkIdleEvent: true,
|
||||||
FailOnHttpStatusCodes: []int64{499, 599},
|
FailOnHttpStatusCodes: []int64{499, 599},
|
||||||
FailOnConsoleExceptions: false,
|
FailOnConsoleExceptions: false,
|
||||||
WaitDelay: 0,
|
WaitDelay: 0,
|
||||||
|
|||||||
@@ -191,14 +191,14 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
if errors.Is(err, libreofficeapi.ErrUnoException) {
|
if errors.Is(err, libreofficeapi.ErrUnoException) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert to PDF: %w", err),
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
api.NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("LibreOffice failed to process the document: possible causes include malformed page ranges '%s' (nativePageRanges) or the document might not be password-protected, but the exact cause is uncertain", options.PageRanges)),
|
api.NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("LibreOffice failed to process a document: possible causes include malformed page ranges '%s' (nativePageRanges), or, if a password has been provided, it may not be required. In any case, the exact cause is uncertain.", options.PageRanges)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, libreofficeapi.ErrRuntimeException) {
|
if errors.Is(err, libreofficeapi.ErrRuntimeException) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert to PDF: %w", err),
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
api.NewSentinelHttpError(http.StatusBadRequest, "LibreOffice failed to process a document: a password may be invalid or required, but the exact cause is uncertain"),
|
api.NewSentinelHttpError(http.StatusBadRequest, "LibreOffice failed to process a document: a password may be required, or, if one has been given, it is invalid. In any case, the exact cause is uncertain."),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,13 +113,19 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve values from echo.Context before it get recycled.
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/1000.
|
||||||
|
startTime := c.Get("startTime").(time.Time)
|
||||||
|
traceHeader := c.Get("traceHeader").(string)
|
||||||
|
trace := c.Get("trace").(string)
|
||||||
|
|
||||||
client := &client{
|
client := &client{
|
||||||
url: webhookUrl,
|
url: webhookUrl,
|
||||||
method: webhookMethod,
|
method: webhookMethod,
|
||||||
errorUrl: webhookErrorUrl,
|
errorUrl: webhookErrorUrl,
|
||||||
errorMethod: webhookErrorMethod,
|
errorMethod: webhookErrorMethod,
|
||||||
extraHttpHeaders: extraHttpHeaders,
|
extraHttpHeaders: extraHttpHeaders,
|
||||||
startTime: c.Get("startTime").(time.Time),
|
startTime: startTime,
|
||||||
|
|
||||||
client: &retryablehttp.Client{
|
client: &retryablehttp.Client{
|
||||||
HTTPClient: &http.Client{
|
HTTPClient: &http.Client{
|
||||||
@@ -157,8 +163,8 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
echo.HeaderContentType: echo.MIMEApplicationJSON,
|
echo.HeaderContentType: echo.MIMEApplicationJSON,
|
||||||
c.Get("traceHeader").(string): c.Get("trace").(string),
|
traceHeader: trace,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.send(bytes.NewReader(b), headers, true)
|
err = client.send(bytes.NewReader(b), headers, true)
|
||||||
@@ -236,7 +242,7 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
|||||||
echo.HeaderContentDisposition: fmt.Sprintf("attachement; filename=%q", ctx.OutputFilename(outputPath)),
|
echo.HeaderContentDisposition: fmt.Sprintf("attachement; filename=%q", ctx.OutputFilename(outputPath)),
|
||||||
echo.HeaderContentType: http.DetectContentType(fileHeader),
|
echo.HeaderContentType: http.DetectContentType(fileHeader),
|
||||||
echo.HeaderContentLength: strconv.FormatInt(fileStat.Size(), 10),
|
echo.HeaderContentLength: strconv.FormatInt(fileStat.Size(), 10),
|
||||||
c.Get("traceHeader").(string): c.Get("trace").(string),
|
traceHeader: trace,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the output file to the webhook.
|
// Send the output file to the webhook.
|
||||||
|
|||||||
Reference in New Issue
Block a user