mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 19:32:15 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25ce0b9aee | ||
|
|
376a7db545 | ||
|
|
f906ed741d | ||
|
|
ad152e62e5 | ||
|
|
7f3a61ef43 | ||
|
|
a7ba8c8213 | ||
|
|
b1f3d6d4c0 | ||
|
|
5707ae027f | ||
|
|
770208024d | ||
|
|
975a9f5344 | ||
|
|
a45008411e | ||
|
|
9091ffd1b3 | ||
|
|
089f161d1e | ||
|
|
e6d7131701 | ||
|
|
ed82170c2c | ||
|
|
3472d42cdd |
4
.github/workflows/continuous_integration.yml
vendored
4
.github/workflows/continuous_integration.yml
vendored
@@ -22,9 +22,9 @@ jobs:
|
|||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Run linters
|
- name: Run linters
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v4
|
||||||
with:
|
with:
|
||||||
version: v1.54.2
|
version: v1.55.2
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
18
Makefile
18
Makefile
@@ -10,9 +10,9 @@ 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.040 # See https://github.com/googlefonts/noto-emoji/releases.
|
NOTO_COLOR_EMOJI_VERSION=v2.042 # See https://github.com/googlefonts/noto-emoji/releases.
|
||||||
PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package.
|
PDFTK_VERSION=v3.3.3 # See https://gitlab.com/pdftk-java/pdftk/-/releases - Binary package.
|
||||||
GOLANGCI_LINT_VERSION=v1.54.2 # See https://github.com/golangci/golangci-lint/releases.
|
GOLANGCI_LINT_VERSION=v1.55.2 # See https://github.com/golangci/golangci-lint/releases.
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: ## Build the Gotenberg's Docker image
|
build: ## Build the Gotenberg's Docker image
|
||||||
@@ -35,6 +35,7 @@ API_ROOT_PATH=/
|
|||||||
API_TRACE_HEADER=Gotenberg-Trace
|
API_TRACE_HEADER=Gotenberg-Trace
|
||||||
API_DISABLE_HEALTH_CHECK_LOGGING=false
|
API_DISABLE_HEALTH_CHECK_LOGGING=false
|
||||||
CHROMIUM_RESTART_AFTER=0
|
CHROMIUM_RESTART_AFTER=0
|
||||||
|
CHROMIUM_MAX_QUEUE_SIZE=0
|
||||||
CHROMIUM_AUTO_START=false
|
CHROMIUM_AUTO_START=false
|
||||||
CHROMIUM_START_TIMEOUT=20s
|
CHROMIUM_START_TIMEOUT=20s
|
||||||
CHROMIUM_INCOGNITO=false
|
CHROMIUM_INCOGNITO=false
|
||||||
@@ -45,12 +46,13 @@ CHROMIUM_ALLOW_FILE_ACCESS_FROM_FILES=false
|
|||||||
CHROMIUM_HOST_RESOLVER_RULES=
|
CHROMIUM_HOST_RESOLVER_RULES=
|
||||||
CHROMIUM_PROXY_SERVER=
|
CHROMIUM_PROXY_SERVER=
|
||||||
CHROMIUM_ALLOW_LIST=
|
CHROMIUM_ALLOW_LIST=
|
||||||
CHROMIUM_DENY_LIST="^file:///[^tmp].*"
|
CHROMIUM_DENY_LIST=^file:(?!//\/tmp/).*
|
||||||
CHROMIUM_CLEAR_CACHE=false
|
CHROMIUM_CLEAR_CACHE=false
|
||||||
CHROMIUM_CLEAR_COOKIES=false
|
CHROMIUM_CLEAR_COOKIES=false
|
||||||
CHROMIUM_DISABLE_JAVASCRIPT=false
|
CHROMIUM_DISABLE_JAVASCRIPT=false
|
||||||
CHROMIUM_DISABLE_ROUTES=false
|
CHROMIUM_DISABLE_ROUTES=false
|
||||||
LIBREOFFICE_RESTART_AFTER=10
|
LIBREOFFICE_RESTART_AFTER=10
|
||||||
|
LIBREOFFICE_MAX_QUEUE_SIZE=0
|
||||||
LIBREOFFICE_AUTO_START=false
|
LIBREOFFICE_AUTO_START=false
|
||||||
LIBREOFFICE_START_TIMEOUT=20s
|
LIBREOFFICE_START_TIMEOUT=20s
|
||||||
LIBREOFFICE_DISABLE_ROUTES=false
|
LIBREOFFICE_DISABLE_ROUTES=false
|
||||||
@@ -89,6 +91,7 @@ run: ## Start a Gotenberg container
|
|||||||
--api-disable-health-check-logging=$(API_DISABLE_HEALTH_CHECK_LOGGING) \
|
--api-disable-health-check-logging=$(API_DISABLE_HEALTH_CHECK_LOGGING) \
|
||||||
--chromium-restart-after=$(CHROMIUM_RESTART_AFTER) \
|
--chromium-restart-after=$(CHROMIUM_RESTART_AFTER) \
|
||||||
--chromium-auto-start=$(CHROMIUM_AUTO_START) \
|
--chromium-auto-start=$(CHROMIUM_AUTO_START) \
|
||||||
|
--chromium-max-queue-size=$(CHROMIUM_MAX_QUEUE_SIZE) \
|
||||||
--chromium-start-timeout=$(CHROMIUM_START_TIMEOUT) \
|
--chromium-start-timeout=$(CHROMIUM_START_TIMEOUT) \
|
||||||
--chromium-incognito=$(CHROMIUM_INCOGNITO) \
|
--chromium-incognito=$(CHROMIUM_INCOGNITO) \
|
||||||
--chromium-allow-insecure-localhost=$(CHROMIUM_ALLOW_INSECURE_LOCALHOST) \
|
--chromium-allow-insecure-localhost=$(CHROMIUM_ALLOW_INSECURE_LOCALHOST) \
|
||||||
@@ -97,13 +100,14 @@ run: ## Start a Gotenberg container
|
|||||||
--chromium-allow-file-access-from-files=$(CHROMIUM_ALLOW_FILE_ACCESS_FROM_FILES) \
|
--chromium-allow-file-access-from-files=$(CHROMIUM_ALLOW_FILE_ACCESS_FROM_FILES) \
|
||||||
--chromium-host-resolver-rules=$(CHROMIUM_HOST_RESOLVER_RULES) \
|
--chromium-host-resolver-rules=$(CHROMIUM_HOST_RESOLVER_RULES) \
|
||||||
--chromium-proxy-server=$(CHROMIUM_PROXY_SERVER) \
|
--chromium-proxy-server=$(CHROMIUM_PROXY_SERVER) \
|
||||||
--chromium-allow-list=$(CHROMIUM_ALLOW_LIST) \
|
--chromium-allow-list="$(CHROMIUM_ALLOW_LIST)" \
|
||||||
--chromium-deny-list=$(CHROMIUM_DENY_LIST) \
|
--chromium-deny-list="$(CHROMIUM_DENY_LIST)" \
|
||||||
--chromium-clear-cache=$(CHROMIUM_CLEAR_CACHE) \
|
--chromium-clear-cache=$(CHROMIUM_CLEAR_CACHE) \
|
||||||
--chromium-clear-cookies=$(CHROMIUM_CLEAR_COOKIES) \
|
--chromium-clear-cookies=$(CHROMIUM_CLEAR_COOKIES) \
|
||||||
--chromium-disable-javascript=$(CHROMIUM_DISABLE_JAVASCRIPT) \
|
--chromium-disable-javascript=$(CHROMIUM_DISABLE_JAVASCRIPT) \
|
||||||
--chromium-disable-routes=$(CHROMIUM_DISABLE_ROUTES) \
|
--chromium-disable-routes=$(CHROMIUM_DISABLE_ROUTES) \
|
||||||
--libreoffice-restart-after=$(LIBREOFFICE_RESTART_AFTER) \
|
--libreoffice-restart-after=$(LIBREOFFICE_RESTART_AFTER) \
|
||||||
|
--libreoffice-max-queue-size=$(LIBREOFFICE_MAX_QUEUE_SIZE) \
|
||||||
--libreoffice-auto-start=$(LIBREOFFICE_AUTO_START) \
|
--libreoffice-auto-start=$(LIBREOFFICE_AUTO_START) \
|
||||||
--libreoffice-start-timeout=$(LIBREOFFICE_START_TIMEOUT) \
|
--libreoffice-start-timeout=$(LIBREOFFICE_START_TIMEOUT) \
|
||||||
--libreoffice-disable-routes=$(LIBREOFFICE_DISABLE_ROUTES) \
|
--libreoffice-disable-routes=$(LIBREOFFICE_DISABLE_ROUTES) \
|
||||||
@@ -116,8 +120,8 @@ run: ## Start a Gotenberg container
|
|||||||
--prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \
|
--prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \
|
||||||
--prometheus-disable-route-logging=$(PROMETHEUS_DISABLE_ROUTE_LOGGING) \
|
--prometheus-disable-route-logging=$(PROMETHEUS_DISABLE_ROUTE_LOGGING) \
|
||||||
--prometheus-disable-collect=$(PROMETHEUS_DISABLE_COLLECT) \
|
--prometheus-disable-collect=$(PROMETHEUS_DISABLE_COLLECT) \
|
||||||
--webhook-allow-list=$(WEBHOOK_ALLOW_LIST) \
|
--webhook-allow-list="$(WEBHOOK_ALLOW_LIST)" \
|
||||||
--webhook-deny-list=$(WEBHOOK_DENY_LIST) \
|
--webhook-deny-list="$(WEBHOOK_DENY_LIST)" \
|
||||||
--webhook-error-allow-list=$(WEBHOOK_ERROR_ALLOW_LIST) \
|
--webhook-error-allow-list=$(WEBHOOK_ERROR_ALLOW_LIST) \
|
||||||
--webhook-error-deny-list=$(WEBHOOK_ERROR_DENY_LIST) \
|
--webhook-error-deny-list=$(WEBHOOK_ERROR_DENY_LIST) \
|
||||||
--webhook-max-retry=$(WEBHOOK_MAX_RETRY) \
|
--webhook-max-retry=$(WEBHOOK_MAX_RETRY) \
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ func Run() {
|
|||||||
go func(app gotenberg.App) {
|
go func(app gotenberg.App) {
|
||||||
id := app.(gotenberg.Module).Descriptor().ID
|
id := app.(gotenberg.Module).Descriptor().ID
|
||||||
err = app.Start()
|
err = app.Start()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[FATAL] starting %s: %s\n", id, err)
|
fmt.Printf("[FATAL] starting %s: %s\n", id, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
24
go.mod
24
go.mod
@@ -5,13 +5,13 @@ go 1.21
|
|||||||
require (
|
require (
|
||||||
github.com/alexliesenfeld/health v0.8.0
|
github.com/alexliesenfeld/health v0.8.0
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/chromedp/cdproto v0.0.0-20240116100315-4a0ec5e4c400
|
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e
|
||||||
github.com/chromedp/chromedp v0.9.3
|
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.5.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.5
|
github.com/hashicorp/go-retryablehttp v0.7.5
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.6 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/labstack/echo/v4 v4.11.4
|
github.com/labstack/echo/v4 v4.11.4
|
||||||
github.com/labstack/gommon v0.4.2
|
github.com/labstack/gommon v0.4.2
|
||||||
@@ -27,15 +27,17 @@ require (
|
|||||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
golang.org/x/image v0.15.0 // indirect
|
golang.org/x/image v0.15.0 // indirect
|
||||||
golang.org/x/net v0.20.0
|
golang.org/x/net v0.21.0
|
||||||
golang.org/x/sync v0.6.0
|
golang.org/x/sync v0.6.0
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
golang.org/x/term v0.16.0
|
golang.org/x/term v0.17.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/dlclark/regexp2 v1.10.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
@@ -53,10 +55,10 @@ require (
|
|||||||
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/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.6.0 // indirect
|
||||||
github.com/prometheus/common v0.46.0 // indirect
|
github.com/prometheus/common v0.47.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // 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
|
||||||
|
|||||||
52
go.sum
52
go.sum
@@ -9,15 +9,17 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998/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-20240116100315-4a0ec5e4c400 h1:mHR3reslmE6J351eW8TgB/BPT+B9OzMxLe7dPa5WYSQ=
|
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e h1:kXEolCWQZzuEFcuaTzfqXToX+e29OcvK87BcBiBBJ1c=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240116100315-4a0ec5e4c400/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/chromedp v0.9.3 h1:Wq58e0dZOdHsxaj9Owmfcf+ibtpYN1N0FWVbaxa/esg=
|
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
||||||
github.com/chromedp/chromedp v0.9.3/go.mod h1:NipeUkUcuzIdFbBP8eNNvl9upcceOfWzoJn6cRe4ksA=
|
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
||||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
@@ -25,17 +27,16 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
|
|||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
|
||||||
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
||||||
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
@@ -52,8 +53,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.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
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=
|
||||||
@@ -97,15 +98,15 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||||
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
|
github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=
|
||||||
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
|
github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
@@ -133,20 +134,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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
|
||||||
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/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package gotenberg
|
package gotenberg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/labstack/gommon/bytes"
|
"github.com/labstack/gommon/bytes"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@@ -199,19 +199,19 @@ func (f *ParsedFlags) MustDeprecatedHumanReadableBytesString(deprecated string,
|
|||||||
|
|
||||||
// MustRegexp returns the regular expression of a flag given by name.
|
// MustRegexp returns the regular expression of a flag given by name.
|
||||||
// It panics if an error occurs.
|
// It panics if an error occurs.
|
||||||
func (f *ParsedFlags) MustRegexp(name string) *regexp.Regexp {
|
func (f *ParsedFlags) MustRegexp(name string) *regexp2.Regexp {
|
||||||
val, err := f.GetString(name)
|
val, err := f.GetString(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return regexp.MustCompile(val)
|
return regexp2.MustCompile(val, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustDeprecatedRegexp returns the regular expression of a deprecated flag if
|
// MustDeprecatedRegexp returns the regular expression of a deprecated flag if
|
||||||
// it was explicitly set or the regular expression of the new flag.
|
// it was explicitly set or the regular expression of the new flag.
|
||||||
// It panics if an error occurs.
|
// It panics if an error occurs.
|
||||||
func (f *ParsedFlags) MustDeprecatedRegexp(deprecated string, newName string) *regexp.Regexp {
|
func (f *ParsedFlags) MustDeprecatedRegexp(deprecated string, newName string) *regexp2.Regexp {
|
||||||
if f.Changed(deprecated) {
|
if f.Changed(deprecated) {
|
||||||
return f.MustRegexp(deprecated)
|
return f.MustRegexp(deprecated)
|
||||||
}
|
}
|
||||||
|
|||||||
53
pkg/gotenberg/regexp.go
Normal file
53
pkg/gotenberg/regexp.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package gotenberg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrFiltered happens if a value is filtered by the [FilterDeadline] function.
|
||||||
|
var ErrFiltered = errors.New("value filtered")
|
||||||
|
|
||||||
|
// FilterDeadline checks if given value is allowed and not denied according to
|
||||||
|
// regex patterns. It returns a [context.DeadlineExceeded] if it takes too long
|
||||||
|
// to process.
|
||||||
|
func FilterDeadline(allowed, denied *regexp2.Regexp, s string, deadline time.Time) error {
|
||||||
|
// FIXME: not ideal to compile everytime, but is there another way to create a clone?
|
||||||
|
if allowed.String() != "" {
|
||||||
|
allow := regexp2.MustCompile(allowed.String(), 0)
|
||||||
|
allow.MatchTimeout = time.Until(deadline)
|
||||||
|
|
||||||
|
ok, err := allow.MatchString(s)
|
||||||
|
if err != nil {
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
return context.DeadlineExceeded
|
||||||
|
}
|
||||||
|
return fmt.Errorf("'%s' cannot handle '%s': %w", allow.String(), s, err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", s, ErrFiltered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if denied.String() != "" {
|
||||||
|
deny := regexp2.MustCompile(denied.String(), 0)
|
||||||
|
deny.MatchTimeout = time.Until(deadline)
|
||||||
|
|
||||||
|
ok, err := deny.MatchString(s)
|
||||||
|
if err != nil {
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
return context.DeadlineExceeded
|
||||||
|
}
|
||||||
|
return fmt.Errorf("'%s' cannot handle '%s': %w", deny.String(), s, err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("'%s' matches the expression from the denied list: %w", s, ErrFiltered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
83
pkg/gotenberg/regexp_test.go
Normal file
83
pkg/gotenberg/regexp_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package gotenberg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilterDeadline(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
allowed *regexp2.Regexp
|
||||||
|
denied *regexp2.Regexp
|
||||||
|
s string
|
||||||
|
deadline time.Time
|
||||||
|
expectError bool
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "DeadlineExceeded (allowed)",
|
||||||
|
allowed: regexp2.MustCompile("foo", 0),
|
||||||
|
denied: regexp2.MustCompile("", 0),
|
||||||
|
s: "foo",
|
||||||
|
deadline: time.Now().Add(time.Duration(-1) * time.Hour),
|
||||||
|
expectError: true,
|
||||||
|
expectedError: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered (allowed)",
|
||||||
|
allowed: regexp2.MustCompile("foo", 0),
|
||||||
|
denied: regexp2.MustCompile("", 0),
|
||||||
|
s: "bar",
|
||||||
|
deadline: time.Now().Add(time.Duration(5) * time.Second),
|
||||||
|
expectError: true,
|
||||||
|
expectedError: ErrFiltered,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "DeadlineExceeded (denied)",
|
||||||
|
allowed: regexp2.MustCompile("", 0),
|
||||||
|
denied: regexp2.MustCompile("foo", 0),
|
||||||
|
s: "foo",
|
||||||
|
deadline: time.Now().Add(time.Duration(-1) * time.Hour),
|
||||||
|
expectError: true,
|
||||||
|
expectedError: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered (denied)",
|
||||||
|
allowed: regexp2.MustCompile("", 0),
|
||||||
|
denied: regexp2.MustCompile("foo", 0),
|
||||||
|
s: "foo",
|
||||||
|
deadline: time.Now().Add(time.Duration(5) * time.Second),
|
||||||
|
expectError: true,
|
||||||
|
expectedError: ErrFiltered,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
allowed: regexp2.MustCompile("", 0),
|
||||||
|
denied: regexp2.MustCompile("", 0),
|
||||||
|
s: "foo",
|
||||||
|
deadline: time.Now().Add(time.Duration(5) * time.Second),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
err := FilterDeadline(tc.allowed, tc.denied, tc.s, tc.deadline)
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected an error but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedError != nil && !errors.Is(err, tc.expectedError) {
|
||||||
|
t.Fatalf("expected error %v but got: %v", tc.expectedError, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,10 @@ import (
|
|||||||
// to restart an already restarting [Process].
|
// to restart an already restarting [Process].
|
||||||
var ErrProcessAlreadyRestarting = errors.New("process already restarting")
|
var ErrProcessAlreadyRestarting = errors.New("process already restarting")
|
||||||
|
|
||||||
|
// ErrMaximumQueueSizeExceeded happens if Run() is called but the maximum queue
|
||||||
|
// size is already used.
|
||||||
|
var ErrMaximumQueueSizeExceeded = errors.New("maximum queue size exceeded")
|
||||||
|
|
||||||
// Process is an interface that represents an abstract process
|
// Process is an interface that represents an abstract process
|
||||||
// and provides methods for starting, stopping, and checking the health of the
|
// and provides methods for starting, stopping, and checking the health of the
|
||||||
// process.
|
// process.
|
||||||
@@ -74,6 +78,7 @@ type processSupervisor struct {
|
|||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
process Process
|
process Process
|
||||||
maxReqLimit int64
|
maxReqLimit int64
|
||||||
|
maxQueueSize int64
|
||||||
mutexChan chan struct{}
|
mutexChan chan struct{}
|
||||||
firstStart atomic.Bool
|
firstStart atomic.Bool
|
||||||
reqCounter atomic.Int64
|
reqCounter atomic.Int64
|
||||||
@@ -83,12 +88,13 @@ type processSupervisor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProcessSupervisor initializes a new [ProcessSupervisor].
|
// NewProcessSupervisor initializes a new [ProcessSupervisor].
|
||||||
func NewProcessSupervisor(logger *zap.Logger, process Process, maxReqLimit int64) ProcessSupervisor {
|
func NewProcessSupervisor(logger *zap.Logger, process Process, maxReqLimit, maxQueueSize int64) ProcessSupervisor {
|
||||||
b := &processSupervisor{
|
b := &processSupervisor{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
process: process,
|
process: process,
|
||||||
mutexChan: make(chan struct{}, 1),
|
mutexChan: make(chan struct{}, 1),
|
||||||
maxReqLimit: maxReqLimit,
|
maxReqLimit: maxReqLimit,
|
||||||
|
maxQueueSize: maxQueueSize,
|
||||||
}
|
}
|
||||||
b.reqCounter.Store(0)
|
b.reqCounter.Store(0)
|
||||||
b.reqQueueSize.Store(0)
|
b.reqQueueSize.Store(0)
|
||||||
@@ -167,6 +173,11 @@ func (s *processSupervisor) Healthy() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *processSupervisor) Run(ctx context.Context, logger *zap.Logger, task func() error) error {
|
func (s *processSupervisor) Run(ctx context.Context, logger *zap.Logger, task func() error) error {
|
||||||
|
currentQueueSize := s.reqQueueSize.Load()
|
||||||
|
if s.maxQueueSize > 0 && currentQueueSize >= s.maxQueueSize {
|
||||||
|
return ErrMaximumQueueSizeExceeded
|
||||||
|
}
|
||||||
|
|
||||||
s.reqQueueSize.Add(1)
|
s.reqQueueSize.Add(1)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func TestProcessSupervisor_Launch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := NewProcessSupervisor(logger, process, 5).(*processSupervisor)
|
ps := NewProcessSupervisor(logger, process, 5, 0).(*processSupervisor)
|
||||||
if tc.firstStartSet {
|
if tc.firstStartSet {
|
||||||
ps.firstStart.Store(true)
|
ps.firstStart.Store(true)
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func TestProcessSupervisor_Shutdown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := NewProcessSupervisor(logger, process, 5)
|
ps := NewProcessSupervisor(logger, process, 5, 0)
|
||||||
err := ps.Shutdown()
|
err := ps.Shutdown()
|
||||||
|
|
||||||
if !tc.expectError && err != nil {
|
if !tc.expectError && err != nil {
|
||||||
@@ -154,7 +154,7 @@ func TestProcessSupervisor_restart(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := NewProcessSupervisor(logger, process, 5).(*processSupervisor)
|
ps := NewProcessSupervisor(logger, process, 5, 0).(*processSupervisor)
|
||||||
if tc.initiallyRestarting {
|
if tc.initiallyRestarting {
|
||||||
ps.isRestarting.Store(true)
|
ps.isRestarting.Store(true)
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ func TestProcessSupervisor_Healthy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := NewProcessSupervisor(logger, process, 5).(*processSupervisor)
|
ps := NewProcessSupervisor(logger, process, 5, 0).(*processSupervisor)
|
||||||
if tc.initiallyStarted {
|
if tc.initiallyStarted {
|
||||||
ps.firstStart.Store(true)
|
ps.firstStart.Store(true)
|
||||||
}
|
}
|
||||||
@@ -249,6 +249,8 @@ func TestProcessSupervisor_Run(t *testing.T) {
|
|||||||
expectedStartCalls int64
|
expectedStartCalls int64
|
||||||
expectedHealthyCalls int64
|
expectedHealthyCalls int64
|
||||||
expectedStopCalls int64
|
expectedStopCalls int64
|
||||||
|
currentQueueSize int64
|
||||||
|
maxQueueSize int64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "successfully run task on non-started process",
|
scenario: "successfully run task on non-started process",
|
||||||
@@ -348,6 +350,34 @@ func TestProcessSupervisor_Run(t *testing.T) {
|
|||||||
expectedHealthyCalls: 1,
|
expectedHealthyCalls: 1,
|
||||||
expectedStopCalls: 0,
|
expectedStopCalls: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "queue size exceeded",
|
||||||
|
initiallyStarted: false,
|
||||||
|
isRestarting: false,
|
||||||
|
processHealthy: true,
|
||||||
|
maxReqLimit: 2,
|
||||||
|
tasksToRun: 1,
|
||||||
|
expectError: true,
|
||||||
|
expectedStartCalls: 0,
|
||||||
|
expectedHealthyCalls: 0,
|
||||||
|
expectedStopCalls: 0,
|
||||||
|
currentQueueSize: 1,
|
||||||
|
maxQueueSize: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "queue size not exceeded",
|
||||||
|
initiallyStarted: false,
|
||||||
|
isRestarting: false,
|
||||||
|
processHealthy: true,
|
||||||
|
maxReqLimit: 2,
|
||||||
|
tasksToRun: 1,
|
||||||
|
expectError: true,
|
||||||
|
expectedStartCalls: 1,
|
||||||
|
expectedHealthyCalls: 1,
|
||||||
|
expectedStopCalls: 0,
|
||||||
|
currentQueueSize: 1,
|
||||||
|
maxQueueSize: 2,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
logger := zap.NewNop()
|
logger := zap.NewNop()
|
||||||
@@ -372,13 +402,16 @@ func TestProcessSupervisor_Run(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := NewProcessSupervisor(logger, process, tc.maxReqLimit).(*processSupervisor)
|
ps := NewProcessSupervisor(logger, process, tc.maxReqLimit, tc.maxQueueSize).(*processSupervisor)
|
||||||
if tc.initiallyStarted {
|
if tc.initiallyStarted {
|
||||||
ps.firstStart.Store(true)
|
ps.firstStart.Store(true)
|
||||||
}
|
}
|
||||||
if tc.isRestarting {
|
if tc.isRestarting {
|
||||||
ps.isRestarting.Store(true)
|
ps.isRestarting.Store(true)
|
||||||
}
|
}
|
||||||
|
if tc.currentQueueSize > 0 {
|
||||||
|
ps.reqQueueSize.Store(tc.currentQueueSize)
|
||||||
|
}
|
||||||
|
|
||||||
task := func() error {
|
task := func() error {
|
||||||
return tc.taskError
|
return tc.taskError
|
||||||
@@ -451,13 +484,13 @@ func TestProcessSupervisor_runWithDeadline(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
ps := NewProcessSupervisor(zap.NewNop(), new(ProcessMock), 0).(*processSupervisor)
|
ps := NewProcessSupervisor(zap.NewNop(), new(ProcessMock), 0, 0).(*processSupervisor)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if tc.ctxDone {
|
if tc.ctxDone {
|
||||||
cancel()
|
cancel()
|
||||||
|
} else {
|
||||||
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ps.runWithDeadline(ctx, func() error {
|
err := ps.runWithDeadline(ctx, func() error {
|
||||||
@@ -485,7 +518,7 @@ func TestProcessSupervisor_ReqQueueSize(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ps := NewProcessSupervisor(logger, process, 0).(*processSupervisor)
|
ps := NewProcessSupervisor(logger, process, 0, 0).(*processSupervisor)
|
||||||
|
|
||||||
// Simulating a lock.
|
// Simulating a lock.
|
||||||
ps.mutexChan <- struct{}{}
|
ps.mutexChan <- struct{}{}
|
||||||
@@ -586,7 +619,7 @@ func TestProcessSupervisor_RestartsCount(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := NewProcessSupervisor(logger, process, 0).(*processSupervisor)
|
ps := NewProcessSupervisor(logger, process, 0, 0).(*processSupervisor)
|
||||||
ps.restartsCounter.Store(tc.initialRestartsCount)
|
ps.restartsCounter.Store(tc.initialRestartsCount)
|
||||||
|
|
||||||
for i := 0; i < tc.restartAttempts; i++ {
|
for i := 0; i < tc.restartAttempts; i++ {
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
for _, files := range form.File {
|
for _, files := range form.File {
|
||||||
for _, fh := range files {
|
for _, fh := range files {
|
||||||
err = copyToDisk(fh)
|
err = copyToDisk(fh)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, cancel, fmt.Errorf("copy to disk: %w", err)
|
return ctx, cancel, fmt.Errorf("copy to disk: %w", err)
|
||||||
}
|
}
|
||||||
@@ -191,10 +190,15 @@ func (ctx *Context) FormData() *FormData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneratePath generates a path within the context's working directory. It
|
// GeneratePath generates a path within the context's working directory.
|
||||||
// does not create a file.
|
// It either generates a new UUID-based filename or uses the provided filename.
|
||||||
func (ctx *Context) GeneratePath(extension string) string {
|
// It does not create a file.
|
||||||
return fmt.Sprintf("%s/%s%s", ctx.dirPath, uuid.New(), extension)
|
func (ctx *Context) GeneratePath(filename, extension string) string {
|
||||||
|
if filename == "" {
|
||||||
|
// Generate a new UUID-based filename
|
||||||
|
filename = uuid.New().String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s%s", ctx.dirPath, filename, extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOutputPaths adds the given paths. Those paths will be used later to build
|
// AddOutputPaths adds the given paths. Those paths will be used later to build
|
||||||
@@ -246,7 +250,7 @@ func (ctx *Context) BuildOutputFile() (string, error) {
|
|||||||
ImplicitTopLevelFolder: false,
|
ImplicitTopLevelFolder: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
archivePath := ctx.GeneratePath(".zip")
|
archivePath := ctx.GeneratePath("", ".zip")
|
||||||
|
|
||||||
err := z.Archive(ctx.outputPaths, archivePath)
|
err := z.Archive(ctx.outputPaths, archivePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -190,11 +190,15 @@ func TestContext_GeneratePath(t *testing.T) {
|
|||||||
dirPath: "/foo",
|
dirPath: "/foo",
|
||||||
}
|
}
|
||||||
|
|
||||||
path := ctx.GeneratePath(".pdf")
|
path := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
if !strings.HasPrefix(path, ctx.dirPath) {
|
if !strings.HasPrefix(path, ctx.dirPath) {
|
||||||
t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath)
|
t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path = ctx.GeneratePath("foo.txt", ".pdf")
|
||||||
|
if !strings.Contains(path, "foo.txt.pdf") {
|
||||||
|
t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext_AddOutputPaths(t *testing.T) {
|
func TestContext_AddOutputPaths(t *testing.T) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/chromedp/cdproto/network"
|
"github.com/chromedp/cdproto/network"
|
||||||
"github.com/chromedp/cdproto/runtime"
|
"github.com/chromedp/cdproto/runtime"
|
||||||
"github.com/chromedp/chromedp"
|
"github.com/chromedp/chromedp"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
@@ -39,8 +39,8 @@ type browserArguments struct {
|
|||||||
wsUrlReadTimeout time.Duration
|
wsUrlReadTimeout time.Duration
|
||||||
|
|
||||||
// Tasks specific.
|
// Tasks specific.
|
||||||
allowList *regexp.Regexp
|
allowList *regexp2.Regexp
|
||||||
denyList *regexp.Regexp
|
denyList *regexp2.Regexp
|
||||||
clearCache bool
|
clearCache bool
|
||||||
clearCookies bool
|
clearCookies bool
|
||||||
disableJavaScript bool
|
disableJavaScript bool
|
||||||
@@ -263,20 +263,17 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
return errors.New("browser not started, cannot handle tasks")
|
return errors.New("browser not started, cannot handle tasks")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We validate the "main" URL against our allow / deny lists.
|
|
||||||
if !b.arguments.allowList.MatchString(url) {
|
|
||||||
return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", url, ErrUrlNotAuthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.arguments.denyList.String() != "" && b.arguments.denyList.MatchString(url) {
|
|
||||||
return fmt.Errorf("'%s' matches the expression from the denied list: %w", url, ErrUrlNotAuthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
deadline, ok := ctx.Deadline()
|
deadline, ok := ctx.Deadline()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("context has no deadline")
|
return errors.New("context has no deadline")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We validate the "main" URL against our allow / deny lists.
|
||||||
|
err := gotenberg.FilterDeadline(b.arguments.allowList, b.arguments.denyList, url, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("filter URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
b.ctxMu.RLock()
|
b.ctxMu.RLock()
|
||||||
defer b.ctxMu.RUnlock()
|
defer b.ctxMu.RUnlock()
|
||||||
|
|
||||||
@@ -310,7 +307,7 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
|
listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := chromedp.Run(taskCtx, tasks...)
|
err = chromedp.Run(taskCtx, tasks...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
@@ -268,46 +268,9 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrUrlNotAuthorized: main URL does not match the allowed list",
|
scenario: "context has no deadline",
|
||||||
browser: func() browser {
|
browser: func() browser {
|
||||||
b := new(chromiumBrowser)
|
b := new(chromiumBrowser)
|
||||||
b.arguments = browserArguments{
|
|
||||||
allowList: regexp.MustCompile("^file:///[^tmp].*"),
|
|
||||||
}
|
|
||||||
b.isStarted.Store(true)
|
|
||||||
return b
|
|
||||||
}(),
|
|
||||||
fs: gotenberg.NewFileSystem(),
|
|
||||||
noDeadline: false,
|
|
||||||
start: false,
|
|
||||||
expectError: true,
|
|
||||||
expectedError: ErrUrlNotAuthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
|
|
||||||
browser: func() browser {
|
|
||||||
b := new(chromiumBrowser)
|
|
||||||
b.arguments = browserArguments{
|
|
||||||
allowList: regexp.MustCompile(""),
|
|
||||||
denyList: regexp.MustCompile("^file:///tmp.*"),
|
|
||||||
}
|
|
||||||
b.isStarted.Store(true)
|
|
||||||
return b
|
|
||||||
}(),
|
|
||||||
fs: gotenberg.NewFileSystem(),
|
|
||||||
noDeadline: false,
|
|
||||||
start: false,
|
|
||||||
expectError: true,
|
|
||||||
expectedError: ErrUrlNotAuthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
|
|
||||||
browser: func() browser {
|
|
||||||
b := new(chromiumBrowser)
|
|
||||||
b.arguments = browserArguments{
|
|
||||||
allowList: regexp.MustCompile(""),
|
|
||||||
denyList: regexp.MustCompile(""),
|
|
||||||
}
|
|
||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
@@ -316,14 +279,48 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered: main URL does not match the allowed list",
|
||||||
|
browser: func() browser {
|
||||||
|
b := new(chromiumBrowser)
|
||||||
|
b.arguments = browserArguments{
|
||||||
|
allowList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
|
||||||
|
denyList: regexp2.MustCompile("", 0),
|
||||||
|
}
|
||||||
|
b.isStarted.Store(true)
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
fs: gotenberg.NewFileSystem(),
|
||||||
|
noDeadline: false,
|
||||||
|
start: false,
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrFiltered,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered: main URL does match the denied list",
|
||||||
|
browser: func() browser {
|
||||||
|
b := new(chromiumBrowser)
|
||||||
|
b.arguments = browserArguments{
|
||||||
|
allowList: regexp2.MustCompile("", 0),
|
||||||
|
denyList: regexp2.MustCompile("^file:///tmp.*", 0),
|
||||||
|
}
|
||||||
|
b.isStarted.Store(true)
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
fs: gotenberg.NewFileSystem(),
|
||||||
|
noDeadline: false,
|
||||||
|
start: false,
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrFiltered,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "a request does not match the allowed list",
|
scenario: "a request does not match the allowed list",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile("^file:///tmp.*"),
|
allowList: regexp2.MustCompile("^file:///tmp.*", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -354,8 +351,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile("^file:///[^tmp].*"),
|
denyList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -386,8 +383,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -421,8 +418,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -454,8 +451,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -487,8 +484,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
clearCache: true,
|
clearCache: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -520,8 +517,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
clearCookies: true,
|
clearCookies: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -553,8 +550,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
disableJavaScript: true,
|
disableJavaScript: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -588,8 +585,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -625,8 +622,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -658,8 +655,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -694,8 +691,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -727,8 +724,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -762,8 +759,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -797,8 +794,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -832,8 +829,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -878,8 +875,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -913,8 +910,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -959,8 +956,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -995,8 +992,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1033,8 +1030,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1066,8 +1063,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1202,45 +1199,12 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrUrlNotAuthorized: main URL does not match the allowed list",
|
scenario: "context has not deadline",
|
||||||
browser: func() browser {
|
browser: func() browser {
|
||||||
b := new(chromiumBrowser)
|
b := new(chromiumBrowser)
|
||||||
b.arguments = browserArguments{
|
b.arguments = browserArguments{
|
||||||
allowList: regexp.MustCompile("^file:///[^tmp].*"),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
}
|
denyList: regexp2.MustCompile("", 0),
|
||||||
b.isStarted.Store(true)
|
|
||||||
return b
|
|
||||||
}(),
|
|
||||||
fs: gotenberg.NewFileSystem(),
|
|
||||||
noDeadline: false,
|
|
||||||
start: false,
|
|
||||||
expectError: true,
|
|
||||||
expectedError: ErrUrlNotAuthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
|
|
||||||
browser: func() browser {
|
|
||||||
b := new(chromiumBrowser)
|
|
||||||
b.arguments = browserArguments{
|
|
||||||
allowList: regexp.MustCompile(""),
|
|
||||||
denyList: regexp.MustCompile("^file:///tmp.*"),
|
|
||||||
}
|
|
||||||
b.isStarted.Store(true)
|
|
||||||
return b
|
|
||||||
}(),
|
|
||||||
fs: gotenberg.NewFileSystem(),
|
|
||||||
noDeadline: false,
|
|
||||||
start: false,
|
|
||||||
expectError: true,
|
|
||||||
expectedError: ErrUrlNotAuthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
|
|
||||||
browser: func() browser {
|
|
||||||
b := new(chromiumBrowser)
|
|
||||||
b.arguments = browserArguments{
|
|
||||||
allowList: regexp.MustCompile(""),
|
|
||||||
denyList: regexp.MustCompile(""),
|
|
||||||
}
|
}
|
||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
@@ -1250,14 +1214,48 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered: main URL does not match the allowed list",
|
||||||
|
browser: func() browser {
|
||||||
|
b := new(chromiumBrowser)
|
||||||
|
b.arguments = browserArguments{
|
||||||
|
allowList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
|
||||||
|
denyList: regexp2.MustCompile("", 0),
|
||||||
|
}
|
||||||
|
b.isStarted.Store(true)
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
fs: gotenberg.NewFileSystem(),
|
||||||
|
noDeadline: false,
|
||||||
|
start: false,
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrFiltered,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered: main URL does match the denied list",
|
||||||
|
browser: func() browser {
|
||||||
|
b := new(chromiumBrowser)
|
||||||
|
b.arguments = browserArguments{
|
||||||
|
allowList: regexp2.MustCompile("", 0),
|
||||||
|
denyList: regexp2.MustCompile("^file:///tmp.*", 0),
|
||||||
|
}
|
||||||
|
b.isStarted.Store(true)
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
fs: gotenberg.NewFileSystem(),
|
||||||
|
noDeadline: false,
|
||||||
|
start: false,
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrFiltered,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "a request does not match the allowed list",
|
scenario: "a request does not match the allowed list",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile("^file:///tmp.*"),
|
allowList: regexp2.MustCompile("^file:///tmp.*", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1288,8 +1286,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile("^file:///[^tmp].*"),
|
denyList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1320,8 +1318,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1355,8 +1353,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1388,8 +1386,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1421,8 +1419,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
clearCache: true,
|
clearCache: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1454,8 +1452,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
clearCookies: true,
|
clearCookies: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1487,8 +1485,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
disableJavaScript: true,
|
disableJavaScript: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1522,8 +1520,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1559,8 +1557,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1594,8 +1592,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1627,8 +1625,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1662,8 +1660,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1697,8 +1695,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1732,8 +1730,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1778,8 +1776,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1813,8 +1811,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1859,8 +1857,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
@@ -1900,8 +1898,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
browserArguments{
|
browserArguments{
|
||||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
wsUrlReadTimeout: 5 * time.Second,
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrUrlNotAuthorized happens if a URL is not acceptable according to the
|
|
||||||
// allowed/denied lists.
|
|
||||||
ErrUrlNotAuthorized = errors.New("URL not authorized")
|
|
||||||
|
|
||||||
// ErrInvalidEmulatedMediaType happens if the emulated media type is not
|
// ErrInvalidEmulatedMediaType happens if the emulated media type is not
|
||||||
// "screen" nor "print". Empty value are allowed though.
|
// "screen" nor "print". Empty value are allowed though.
|
||||||
ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type")
|
ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type")
|
||||||
@@ -280,6 +276,7 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
FlagSet: func() *flag.FlagSet {
|
FlagSet: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("chromium", flag.ExitOnError)
|
fs := flag.NewFlagSet("chromium", flag.ExitOnError)
|
||||||
fs.Int64("chromium-restart-after", 0, "Number of conversions after which Chromium will automatically restart. Set to 0 to disable this feature")
|
fs.Int64("chromium-restart-after", 0, "Number of conversions after which Chromium will automatically restart. Set to 0 to disable this feature")
|
||||||
|
fs.Int64("chromium-max-queue-size", 0, "Maximum request queue size for Chromium. Set to 0 to disable this feature")
|
||||||
fs.Bool("chromium-auto-start", false, "Automatically launch Chromium upon initialization if set to true; otherwise, Chromium will start at the time of the first conversion")
|
fs.Bool("chromium-auto-start", false, "Automatically launch Chromium upon initialization if set to true; otherwise, Chromium will start at the time of the first conversion")
|
||||||
fs.Duration("chromium-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for Chromium to start or restart")
|
fs.Duration("chromium-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for Chromium to start or restart")
|
||||||
fs.Bool("chromium-incognito", false, "Start Chromium with incognito mode")
|
fs.Bool("chromium-incognito", false, "Start Chromium with incognito mode")
|
||||||
@@ -290,7 +287,7 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
fs.String("chromium-host-resolver-rules", "", "Set custom mappings to the host resolver")
|
fs.String("chromium-host-resolver-rules", "", "Set custom mappings to the host resolver")
|
||||||
fs.String("chromium-proxy-server", "", "Set the outbound proxy server; this switch only affects HTTP and HTTPS requests")
|
fs.String("chromium-proxy-server", "", "Set the outbound proxy server; this switch only affects HTTP and HTTPS requests")
|
||||||
fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression")
|
fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression")
|
||||||
fs.String("chromium-deny-list", "^file:///[^tmp].*", "Set the denied URLs for Chromium using a regular expression")
|
fs.String("chromium-deny-list", `^file:(?!//\/tmp/).*`, "Set the denied URLs for Chromium using a regular expression")
|
||||||
fs.Bool("chromium-clear-cache", false, "Clear Chromium cache between each conversion")
|
fs.Bool("chromium-clear-cache", false, "Clear Chromium cache between each conversion")
|
||||||
fs.Bool("chromium-clear-cookies", false, "Clear Chromium cookies between each conversion")
|
fs.Bool("chromium-clear-cookies", false, "Clear Chromium cookies between each conversion")
|
||||||
fs.Bool("chromium-disable-javascript", false, "Disable JavaScript")
|
fs.Bool("chromium-disable-javascript", false, "Disable JavaScript")
|
||||||
@@ -344,7 +341,7 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
|||||||
|
|
||||||
// Process.
|
// Process.
|
||||||
mod.browser = newChromiumBrowser(mod.args)
|
mod.browser = newChromiumBrowser(mod.args)
|
||||||
mod.supervisor = gotenberg.NewProcessSupervisor(mod.logger, mod.browser, flags.MustInt64("chromium-restart-after"))
|
mod.supervisor = gotenberg.NewProcessSupervisor(mod.logger, mod.browser, flags.MustInt64("chromium-restart-after"), flags.MustInt64("chromium-max-queue-size"))
|
||||||
|
|
||||||
// PDF Engine.
|
// PDF Engine.
|
||||||
provider, err := ctx.Module(new(gotenberg.PdfEngineProvider))
|
provider, err := ctx.Module(new(gotenberg.PdfEngineProvider))
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package chromium
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -13,14 +12,17 @@ import (
|
|||||||
"github.com/chromedp/cdproto/page"
|
"github.com/chromedp/cdproto/page"
|
||||||
"github.com/chromedp/cdproto/runtime"
|
"github.com/chromedp/cdproto/runtime"
|
||||||
"github.com/chromedp/chromedp"
|
"github.com/chromedp/chromedp"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// listenForEventRequestPaused listens for requests to check if they are
|
// listenForEventRequestPaused listens for requests to check if they are
|
||||||
// allowed or not.
|
// allowed or not.
|
||||||
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp.Regexp, denyList *regexp.Regexp) {
|
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp2.Regexp, denyList *regexp2.Regexp) {
|
||||||
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
||||||
switch e := ev.(type) {
|
switch e := ev.(type) {
|
||||||
case *fetch.EventRequestPaused:
|
case *fetch.EventRequestPaused:
|
||||||
@@ -28,13 +30,15 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
|||||||
logger.Debug(fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL))
|
logger.Debug(fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL))
|
||||||
allow := true
|
allow := true
|
||||||
|
|
||||||
if !allowList.MatchString(e.Request.URL) {
|
deadline, ok := ctx.Deadline()
|
||||||
logger.Warn(fmt.Sprintf("'%s' does not match the expression from the allowed list", e.Request.URL))
|
if !ok {
|
||||||
allow = false
|
logger.Error("context has no deadline, cannot filter URL")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if denyList.String() != "" && denyList.MatchString(e.Request.URL) {
|
err := gotenberg.FilterDeadline(allowList, denyList, e.Request.URL, deadline)
|
||||||
logger.Warn(fmt.Sprintf("'%s' matches the expression from the denied list", e.Request.URL))
|
if err != nil {
|
||||||
|
logger.Warn(err.Error())
|
||||||
allow = false
|
allow = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,16 +47,15 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
|||||||
|
|
||||||
if allow {
|
if allow {
|
||||||
req := fetch.ContinueRequest(e.RequestID)
|
req := fetch.ContinueRequest(e.RequestID)
|
||||||
err := req.Do(executorCtx)
|
err = req.Do(executorCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("continue request: %s", err))
|
logger.Error(fmt.Sprintf("continue request: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
|
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
|
||||||
err := req.Do(executorCtx)
|
err = req.Do(executorCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("fail request: %s", err))
|
logger.Error(fmt.Sprintf("fail request: %s", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,7 +494,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputPath = ctx.GeneratePath(".html")
|
inputPath = ctx.GeneratePath("", ".html")
|
||||||
|
|
||||||
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
|
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -505,14 +505,14 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, pdfFormats gotenberg.PdfFormats, options PdfOptions) error {
|
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, pdfFormats gotenberg.PdfFormats, options PdfOptions) error {
|
||||||
outputPath := ctx.GeneratePath(".pdf")
|
outputPath := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
|
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
|
||||||
err = handleChromiumError(err, url, options.Options)
|
err = handleChromiumError(err, url, options.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrOmitBackgroundWithoutPrintBackground) {
|
if errors.Is(err, ErrOmitBackgroundWithoutPrintBackground) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
err,
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
api.NewSentinelHttpError(
|
api.NewSentinelHttpError(
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
"omitBackground requires printBackground set to true",
|
"omitBackground requires printBackground set to true",
|
||||||
@@ -549,11 +549,20 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
zeroValued := gotenberg.PdfFormats{}
|
zeroValued := gotenberg.PdfFormats{}
|
||||||
if pdfFormats != zeroValued {
|
if pdfFormats != zeroValued {
|
||||||
convertInputPath := outputPath
|
convertInputPath := outputPath
|
||||||
convertOutputPath := ctx.GeneratePath(".pdf")
|
convertOutputPath := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
|
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
@@ -581,7 +590,7 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
|
|
||||||
func screenshotUrl(ctx *api.Context, chromium Api, url string, options ScreenshotOptions) error {
|
func screenshotUrl(ctx *api.Context, chromium Api, url string, options ScreenshotOptions) error {
|
||||||
ext := fmt.Sprintf(".%s", options.Format)
|
ext := fmt.Sprintf(".%s", options.Format)
|
||||||
outputPath := ctx.GeneratePath(ext)
|
outputPath := ctx.GeneratePath("", ext)
|
||||||
|
|
||||||
err := chromium.Screenshot(ctx, ctx.Log(), url, outputPath, options)
|
err := chromium.Screenshot(ctx, ctx.Log(), url, outputPath, options)
|
||||||
err = handleChromiumError(err, url, options.Options)
|
err = handleChromiumError(err, url, options.Options)
|
||||||
@@ -602,7 +611,17 @@ func handleChromiumError(err error, url string, options Options) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, ErrUrlNotAuthorized) {
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
err,
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gotenberg.ErrFiltered) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
err,
|
err,
|
||||||
api.NewSentinelHttpError(
|
api.NewSentinelHttpError(
|
||||||
|
|||||||
@@ -1231,10 +1231,22 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
expectOutputPathsCount int
|
expectOutputPathsCount int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "ErrUrlNotAuthorized",
|
scenario: "ErrMaximumQueueSizeExceeded",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
return ErrUrlNotAuthorized
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
}},
|
||||||
|
options: DefaultPdfOptions(),
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
return gotenberg.ErrFiltered
|
||||||
}},
|
}},
|
||||||
options: DefaultPdfOptions(),
|
options: DefaultPdfOptions(),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -1341,6 +1353,22 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrMaximumQueueSizeExceeded (PDF engine)",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
engine: &gotenberg.PdfEngineMock{ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
}},
|
||||||
|
pdfFormats: gotenberg.PdfFormats{PdfA: "foo"},
|
||||||
|
options: DefaultPdfOptions(),
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrPdfFormatNotSupported",
|
scenario: "ErrPdfFormatNotSupported",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
@@ -1463,10 +1491,22 @@ func TestScreenshotUrl(t *testing.T) {
|
|||||||
expectOutputPathsCount int
|
expectOutputPathsCount int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "ErrUrlNotAuthorized",
|
scenario: "ErrMaximumQueueSizeExceeded",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||||
return ErrUrlNotAuthorized
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
}},
|
||||||
|
options: DefaultScreenshotOptions(),
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrFiltered",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||||
|
return gotenberg.ErrFiltered
|
||||||
}},
|
}},
|
||||||
options: DefaultScreenshotOptions(),
|
options: DefaultScreenshotOptions(),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
FlagSet: func() *flag.FlagSet {
|
FlagSet: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("api", flag.ExitOnError)
|
fs := flag.NewFlagSet("api", flag.ExitOnError)
|
||||||
fs.Int64("libreoffice-restart-after", 10, "Number of conversions after which LibreOffice will automatically restart. Set to 0 to disable this feature")
|
fs.Int64("libreoffice-restart-after", 10, "Number of conversions after which LibreOffice will automatically restart. Set to 0 to disable this feature")
|
||||||
|
fs.Int64("libreoffice-max-queue-size", 0, "Maximum request queue size for LibreOffice. Set to 0 to disable this feature")
|
||||||
fs.Bool("libreoffice-auto-start", false, "Automatically launch LibreOffice upon initialization if set to true; otherwise, LibreOffice will start at the time of the first conversion")
|
fs.Bool("libreoffice-auto-start", false, "Automatically launch LibreOffice upon initialization if set to true; otherwise, LibreOffice will start at the time of the first conversion")
|
||||||
fs.Duration("libreoffice-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for LibreOffice to start or restart")
|
fs.Duration("libreoffice-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for LibreOffice to start or restart")
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
|
|
||||||
// Process.
|
// Process.
|
||||||
a.libreOffice = newLibreOfficeProcess(a.args)
|
a.libreOffice = newLibreOfficeProcess(a.args)
|
||||||
a.supervisor = gotenberg.NewProcessSupervisor(a.logger, a.libreOffice, flags.MustInt64("libreoffice-restart-after"))
|
a.supervisor = gotenberg.NewProcessSupervisor(a.logger, a.libreOffice, flags.MustInt64("libreoffice-restart-after"), flags.MustInt64("libreoffice-max-queue-size"))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
@@ -54,8 +55,8 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
// Alright, let's convert each document to PDF.
|
// Alright, let's convert each document to PDF.
|
||||||
outputPaths := make([]string, len(inputPaths))
|
outputPaths := make([]string, len(inputPaths))
|
||||||
for i, inputPath := range inputPaths {
|
for i, inputPath := range inputPaths {
|
||||||
outputPaths[i] = ctx.GeneratePath(".pdf")
|
// document.docx -> document.docx.pdf.
|
||||||
|
outputPaths[i] = ctx.GeneratePath(filepath.Base(inputPath), ".pdf")
|
||||||
options := libreofficeapi.Options{
|
options := libreofficeapi.Options{
|
||||||
Landscape: landscape,
|
Landscape: landscape,
|
||||||
PageRanges: nativePageRanges,
|
PageRanges: nativePageRanges,
|
||||||
@@ -67,6 +68,16 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
|
|
||||||
err = libreOffice.Pdf(ctx, ctx.Log(), inputPath, outputPaths[i], options)
|
err = libreOffice.Pdf(ctx, ctx.Log(), inputPath, outputPaths[i], options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, libreofficeapi.ErrInvalidPdfFormats) {
|
if errors.Is(err, libreofficeapi.ErrInvalidPdfFormats) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert to PDF: %w", err),
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
@@ -92,22 +103,42 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
// win: if there is only one PDF, skip this step.
|
// win: if there is only one PDF, skip this step.
|
||||||
|
|
||||||
if len(outputPaths) > 1 && merge {
|
if len(outputPaths) > 1 && merge {
|
||||||
outputPath := ctx.GeneratePath(".pdf")
|
outputPath := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
err = engine.Merge(ctx, ctx.Log(), outputPaths, outputPath)
|
err = engine.Merge(ctx, ctx.Log(), outputPaths, outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("merge PDFs: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("merge PDFs: %w", err)
|
return fmt.Errorf("merge PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, let's check if the client want to convert this result
|
// Now, let's check if the client want to convert this
|
||||||
// PDF to specific PDF formats.
|
// resulting PDF to specific PDF formats.
|
||||||
zeroValued := gotenberg.PdfFormats{}
|
zeroValued := gotenberg.PdfFormats{}
|
||||||
if !nativePdfFormats && pdfFormats != zeroValued {
|
if !nativePdfFormats && pdfFormats != zeroValued {
|
||||||
convertInputPath := outputPath
|
convertInputPath := outputPath
|
||||||
convertOutputPath := ctx.GeneratePath(".pdf")
|
convertOutputPath := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
|
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
@@ -144,10 +175,21 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
|
|
||||||
for i, outputPath := range outputPaths {
|
for i, outputPath := range outputPaths {
|
||||||
convertInputPath := outputPath
|
convertInputPath := outputPath
|
||||||
convertOutputPaths[i] = ctx.GeneratePath(".pdf")
|
// document.docx -> document.docx.pdf.
|
||||||
|
convertOutputPaths[i] = ctx.GeneratePath(filepath.Base(inputPaths[i]), ".pdf")
|
||||||
|
|
||||||
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPaths[i])
|
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPaths[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
@@ -25,6 +26,7 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpError bool
|
expectHttpError bool
|
||||||
expectHttpStatus int
|
expectHttpStatus int
|
||||||
expectOutputPathsCount int
|
expectOutputPathsCount int
|
||||||
|
expectOutputPaths []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "missing at least one mandatory file",
|
scenario: "missing at least one mandatory file",
|
||||||
@@ -37,6 +39,28 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrMaximumQueueSizeExceeded",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrPdfFormatNotSupported (nativePdfFormats)",
|
scenario: "ErrPdfFormatNotSupported (nativePdfFormats)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -107,6 +131,41 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrMaximumQueueSizeExceeded (single file)",
|
||||||
|
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{
|
||||||
|
"pdfa": {
|
||||||
|
gotenberg.PdfA1b,
|
||||||
|
},
|
||||||
|
"nativePdfFormats": {
|
||||||
|
"false",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrPdfFormatNotSupported (single file)",
|
scenario: "ErrPdfFormatNotSupported (single file)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -218,6 +277,7 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
|
expectOutputPaths: []string{"/document.docx.pdf"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success (many files)",
|
scenario: "success (many files)",
|
||||||
@@ -226,6 +286,7 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
"document.docx": "/document.docx",
|
"document.docx": "/document.docx",
|
||||||
"document2.docx": "/document2.docx",
|
"document2.docx": "/document2.docx",
|
||||||
|
"document2.doc": "/document2.doc",
|
||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
@@ -234,19 +295,21 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
ExtensionsMock: func() []string {
|
ExtensionsMock: func() []string {
|
||||||
return []string{".docx"}
|
return []string{".docx", ".doc"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 2,
|
expectOutputPathsCount: 3,
|
||||||
|
expectOutputPaths: []string{"/document.docx.pdf", "/document2.docx.pdf", "/document2.doc.pdf"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success with non-native PDF/A & PDF/UA (single file)",
|
scenario: "success with non-native PDF/A & PDF/UA (many files)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
"document.docx": "/document.docx",
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
"pdfa": {
|
"pdfa": {
|
||||||
@@ -276,14 +339,16 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 2,
|
||||||
|
expectOutputPaths: []string{"/document.docx.pdf", "/document2.docx.pdf"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success with native PDF/A & PDF/UA (single file)",
|
scenario: "success with native PDF/A & PDF/UA (many files)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
"document.docx": "/document.docx",
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
"pdfa": {
|
"pdfa": {
|
||||||
@@ -310,7 +375,41 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 2,
|
||||||
|
expectOutputPaths: []string{"/document.docx.pdf", "/document2.docx.pdf"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrMaximumQueueSizeExceeded (merge)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"merge": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "merge error",
|
scenario: "merge error",
|
||||||
@@ -344,6 +443,48 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrMaximumQueueSizeExceeded (convert post-merge)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"merge": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
"pdfa": {
|
||||||
|
gotenberg.PdfA1b,
|
||||||
|
},
|
||||||
|
"nativePdfFormats": {
|
||||||
|
"false",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrPdfFormatNotSupported (merge)",
|
scenario: "ErrPdfFormatNotSupported (merge)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -617,6 +758,12 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) {
|
if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) {
|
||||||
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths()))
|
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, path := range tc.expectOutputPaths {
|
||||||
|
if !slices.Contains(tc.ctx.OutputPaths(), path) {
|
||||||
|
t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths())
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
@@ -43,10 +45,20 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
|
|
||||||
// Alright, let's merge the PDFs.
|
// Alright, let's merge the PDFs.
|
||||||
|
|
||||||
outputPath := ctx.GeneratePath(".pdf")
|
outputPath := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath)
|
err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("merge PDFs: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("merge PDFs: %w", err)
|
return fmt.Errorf("merge PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,11 +68,20 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
zeroValued := gotenberg.PdfFormats{}
|
zeroValued := gotenberg.PdfFormats{}
|
||||||
if pdfFormats != zeroValued {
|
if pdfFormats != zeroValued {
|
||||||
convertInputPath := outputPath
|
convertInputPath := outputPath
|
||||||
convertOutputPath := ctx.GeneratePath(".pdf")
|
convertOutputPath := ctx.GeneratePath("", ".pdf")
|
||||||
|
|
||||||
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
|
err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
@@ -133,15 +154,29 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alright, let's convert the PDFs.
|
// Alright, let's convert the PDFs.s
|
||||||
outputPaths := make([]string, len(inputPaths))
|
outputPaths := make([]string, len(inputPaths))
|
||||||
|
|
||||||
for i, inputPath := range inputPaths {
|
for i, inputPath := range inputPaths {
|
||||||
outputPaths[i] = ctx.GeneratePath(".pdf")
|
if len(outputPaths) > 1 {
|
||||||
|
// If .zip archive, keep the original filenames.
|
||||||
|
outputPaths[i] = ctx.GeneratePath(strings.TrimSuffix(filepath.Base(inputPath), filepath.Ext(inputPath)), ".pdf")
|
||||||
|
} else {
|
||||||
|
outputPaths[i] = ctx.GeneratePath("", ".pdf")
|
||||||
|
}
|
||||||
|
|
||||||
err = engine.Convert(ctx, ctx.Log(), pdfFormats, inputPath, outputPaths[i])
|
err = engine.Convert(ctx, ctx.Log(), pdfFormats, inputPath, outputPaths[i])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
"The maximum queue size has been reached",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
fmt.Errorf("convert PDF: %w", err),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
@@ -31,6 +32,27 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrMaximumQueueSizeExceeded (merge)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
"file2.pdf": "/file2.pdf",
|
||||||
|
})
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "error from PDF engine",
|
scenario: "error from PDF engine",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -90,7 +112,7 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrPdfFormatNotSupported",
|
scenario: "ErrMaximumQueueSizeExceeded (convert)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
@@ -104,6 +126,34 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrPdfFormatNotSupported",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
"file2.pdf": "/file2.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"pdfa": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
engine: &gotenberg.PdfEngineMock{
|
engine: &gotenberg.PdfEngineMock{
|
||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -224,6 +274,7 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
expectHttpError bool
|
expectHttpError bool
|
||||||
expectHttpStatus int
|
expectHttpStatus int
|
||||||
expectOutputPathsCount int
|
expectOutputPathsCount int
|
||||||
|
expectOutputPaths []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "missing at least one mandatory file",
|
scenario: "missing at least one mandatory file",
|
||||||
@@ -248,7 +299,7 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "ErrPdfFormatNotSupported",
|
scenario: "ErrMaximumQueueSizeExceeded",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
@@ -261,6 +312,30 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return gotenberg.ErrMaximumQueueSizeExceeded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusTooManyRequests,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "ErrPdfFormatNotSupported",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"pdfa": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
engine: &gotenberg.PdfEngineMock{
|
engine: &gotenberg.PdfEngineMock{
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return gotenberg.ErrPdfFormatNotSupported
|
return gotenberg.ErrPdfFormatNotSupported
|
||||||
@@ -370,6 +445,7 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 2,
|
expectOutputPathsCount: 2,
|
||||||
|
expectOutputPaths: []string{"/file.pdf", "/file2.pdf"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
@@ -408,6 +484,12 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) {
|
if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) {
|
||||||
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths()))
|
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, path := range tc.expectOutputPaths {
|
||||||
|
if !slices.Contains(tc.ctx.OutputPaths(), path) {
|
||||||
|
t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths())
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,38 +45,38 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's check if the webhook URLs are acceptable according to our
|
deadline, ok := ctx.Deadline()
|
||||||
// allowed/denied lists.
|
if !ok {
|
||||||
filter := func(URL, header string, allowList, denyList *regexp.Regexp) error {
|
return errors.New("context has no deadline")
|
||||||
if !allowList.MatchString(URL) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("'%s' does not match the expression from the allowed list", URL),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusForbidden,
|
|
||||||
fmt.Sprintf("Invalid '%s' header value: '%s' does not match the authorized URLs", header, URL),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if denyList.String() != "" && denyList.MatchString(URL) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("'%s' matches the expression from the denied list", URL),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusForbidden,
|
|
||||||
fmt.Sprintf("Invalid '%s' header value: '%s' does not match the authorized URLs", header, URL),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := filter(webhookUrl, "Gotenberg-Webhook-Url", w.allowList, w.denyList)
|
// Let's check if the webhook URLs are acceptable according to our
|
||||||
|
// allowed/denied lists.
|
||||||
|
filter := func(url, header string, allowList, denyList *regexp2.Regexp, deadline time.Time) error {
|
||||||
|
err := gotenberg.FilterDeadline(allowList, denyList, url, deadline)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gotenberg.ErrFiltered) {
|
||||||
|
return api.WrapError(
|
||||||
|
err,
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusForbidden,
|
||||||
|
fmt.Sprintf("Invalid '%s' header value: '%s' does not match the authorized URL", header, url),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := filter(webhookUrl, "Gotenberg-Webhook-Url", w.allowList, w.denyList, deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filter webhook URL: %w", err)
|
return fmt.Errorf("filter webhook URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = filter(webhookErrorUrl, "Gotenberg-Webhook-Error-Url", w.errorAllowList, w.errorDenyList)
|
err = filter(webhookErrorUrl, "Gotenberg-Webhook-Error-Url", w.errorAllowList, w.errorDenyList, deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filter webhook error URL: %w", err)
|
return fmt.Errorf("filter webhook error URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -47,10 +47,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
|
|
||||||
buildWebhookModule := func() *Webhook {
|
buildWebhookModule := func() *Webhook {
|
||||||
return &Webhook{
|
return &Webhook{
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
errorAllowList: regexp.MustCompile(""),
|
errorAllowList: regexp2.MustCompile("", 0),
|
||||||
errorDenyList: regexp.MustCompile(""),
|
errorDenyList: regexp2.MustCompile("", 0),
|
||||||
maxRetry: 0,
|
maxRetry: 0,
|
||||||
retryMinWait: 0,
|
retryMinWait: 0,
|
||||||
retryMaxWait: 0,
|
retryMaxWait: 0,
|
||||||
@@ -63,6 +63,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
request *http.Request
|
request *http.Request
|
||||||
mod *Webhook
|
mod *Webhook
|
||||||
next echo.HandlerFunc
|
next echo.HandlerFunc
|
||||||
|
noDeadline bool
|
||||||
expectError bool
|
expectError bool
|
||||||
expectHttpError bool
|
expectHttpError bool
|
||||||
expectHttpStatus int
|
expectHttpStatus int
|
||||||
@@ -76,6 +77,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
},
|
},
|
||||||
@@ -87,10 +89,23 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "context has no deadline",
|
||||||
|
request: func() *http.Request {
|
||||||
|
req := buildMultipartFormDataRequest()
|
||||||
|
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||||
|
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||||
|
return req
|
||||||
|
}(),
|
||||||
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: true,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "webhook URL is not allowed",
|
scenario: "webhook URL is not allowed",
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
@@ -101,9 +116,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
}(),
|
}(),
|
||||||
mod: func() *Webhook {
|
mod: func() *Webhook {
|
||||||
mod := buildWebhookModule()
|
mod := buildWebhookModule()
|
||||||
mod.allowList = regexp.MustCompile("bar")
|
mod.allowList = regexp2.MustCompile("bar", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusForbidden,
|
expectHttpStatus: http.StatusForbidden,
|
||||||
@@ -118,9 +134,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
}(),
|
}(),
|
||||||
mod: func() *Webhook {
|
mod: func() *Webhook {
|
||||||
mod := buildWebhookModule()
|
mod := buildWebhookModule()
|
||||||
mod.denyList = regexp.MustCompile("foo")
|
mod.denyList = regexp2.MustCompile("foo", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusForbidden,
|
expectHttpStatus: http.StatusForbidden,
|
||||||
@@ -135,9 +152,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
}(),
|
}(),
|
||||||
mod: func() *Webhook {
|
mod: func() *Webhook {
|
||||||
mod := buildWebhookModule()
|
mod := buildWebhookModule()
|
||||||
mod.errorAllowList = regexp.MustCompile("foo")
|
mod.errorAllowList = regexp2.MustCompile("foo", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusForbidden,
|
expectHttpStatus: http.StatusForbidden,
|
||||||
@@ -152,9 +170,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
}(),
|
}(),
|
||||||
mod: func() *Webhook {
|
mod: func() *Webhook {
|
||||||
mod := buildWebhookModule()
|
mod := buildWebhookModule()
|
||||||
mod.errorDenyList = regexp.MustCompile("bar")
|
mod.errorDenyList = regexp2.MustCompile("bar", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusForbidden,
|
expectHttpStatus: http.StatusForbidden,
|
||||||
@@ -169,6 +188,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
@@ -183,6 +203,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
@@ -198,6 +219,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
@@ -213,6 +235,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
@@ -228,6 +251,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
@@ -242,6 +266,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
return req
|
return req
|
||||||
}(),
|
}(),
|
||||||
mod: buildWebhookModule(),
|
mod: buildWebhookModule(),
|
||||||
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
expectHttpError: true,
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
@@ -254,15 +279,20 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
|
|
||||||
c := srv.NewContext(tc.request, httptest.NewRecorder())
|
c := srv.NewContext(tc.request, httptest.NewRecorder())
|
||||||
|
|
||||||
ctx := &api.ContextMock{Context: &api.Context{}}
|
if tc.noDeadline {
|
||||||
ctx.SetEchoContext(c)
|
ctx := &api.ContextMock{Context: &api.Context{Context: context.Background()}}
|
||||||
|
ctx.SetEchoContext(c)
|
||||||
c.Set("context", ctx.Context)
|
c.Set("context", ctx.Context)
|
||||||
c.Set("cancel", func() context.CancelFunc {
|
c.Set("cancel", func() context.CancelFunc {
|
||||||
return func() {
|
return nil
|
||||||
return
|
}())
|
||||||
}
|
} else {
|
||||||
}())
|
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
|
||||||
|
ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}}
|
||||||
|
ctx.SetEchoContext(c)
|
||||||
|
c.Set("context", ctx.Context)
|
||||||
|
c.Set("cancel", cancel)
|
||||||
|
}
|
||||||
|
|
||||||
err := webhookMiddleware(tc.mod).Handler(tc.next)(c)
|
err := webhookMiddleware(tc.mod).Handler(tc.next)(c)
|
||||||
|
|
||||||
@@ -320,10 +350,10 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
|
|||||||
|
|
||||||
buildWebhookModule := func() *Webhook {
|
buildWebhookModule := func() *Webhook {
|
||||||
return &Webhook{
|
return &Webhook{
|
||||||
allowList: regexp.MustCompile(""),
|
allowList: regexp2.MustCompile("", 0),
|
||||||
denyList: regexp.MustCompile(""),
|
denyList: regexp2.MustCompile("", 0),
|
||||||
errorAllowList: regexp.MustCompile(""),
|
errorAllowList: regexp2.MustCompile("", 0),
|
||||||
errorDenyList: regexp.MustCompile(""),
|
errorDenyList: regexp2.MustCompile("", 0),
|
||||||
maxRetry: 0,
|
maxRetry: 0,
|
||||||
retryMinWait: 0,
|
retryMinWait: 0,
|
||||||
retryMaxWait: 0,
|
retryMaxWait: 0,
|
||||||
@@ -426,22 +456,17 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
|
|||||||
c.Set("trace", "foo")
|
c.Set("trace", "foo")
|
||||||
c.Set("startTime", time.Now())
|
c.Set("startTime", time.Now())
|
||||||
|
|
||||||
ctx := &api.ContextMock{Context: &api.Context{}}
|
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
|
||||||
|
ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}}
|
||||||
ctx.SetLogger(zap.NewNop())
|
ctx.SetLogger(zap.NewNop())
|
||||||
ctx.SetEchoContext(c)
|
ctx.SetEchoContext(c)
|
||||||
|
|
||||||
c.Set("context", ctx.Context)
|
c.Set("context", ctx.Context)
|
||||||
c.Set("cancel", func() context.CancelFunc {
|
c.Set("cancel", cancel)
|
||||||
return func() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}())
|
|
||||||
|
|
||||||
webhook := echo.New()
|
webhook := echo.New()
|
||||||
webhook.HideBanner = true
|
webhook.HideBanner = true
|
||||||
webhook.HidePort = true
|
webhook.HidePort = true
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
webhookPort := rand.Intn(65535-1025+1) + 1025
|
webhookPort := rand.Intn(65535-1025+1) + 1025
|
||||||
|
|
||||||
c.Request().Header.Set("Gotenberg-Webhook-Url", fmt.Sprintf("http://localhost:%d/", webhookPort))
|
c.Request().Header.Set("Gotenberg-Webhook-Url", fmt.Sprintf("http://localhost:%d/", webhookPort))
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
@@ -17,10 +17,10 @@ func init() {
|
|||||||
// Webhook is a module which provides a middleware for uploading output files
|
// Webhook is a module which provides a middleware for uploading output files
|
||||||
// to any destinations in an asynchronous fashion.
|
// to any destinations in an asynchronous fashion.
|
||||||
type Webhook struct {
|
type Webhook struct {
|
||||||
allowList *regexp.Regexp
|
allowList *regexp2.Regexp
|
||||||
denyList *regexp.Regexp
|
denyList *regexp2.Regexp
|
||||||
errorAllowList *regexp.Regexp
|
errorAllowList *regexp2.Regexp
|
||||||
errorDenyList *regexp.Regexp
|
errorDenyList *regexp2.Regexp
|
||||||
maxRetry int
|
maxRetry int
|
||||||
retryMinWait time.Duration
|
retryMinWait time.Duration
|
||||||
retryMaxWait time.Duration
|
retryMaxWait time.Duration
|
||||||
|
|||||||
4
test/testdata/chromium/html/footer.html
vendored
4
test/testdata/chromium/html/footer.html
vendored
@@ -2,8 +2,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-size: 30rem;
|
font-size: 12px;
|
||||||
margin: 4rem auto;
|
margin: auto 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
4
test/testdata/chromium/html/header.html
vendored
4
test/testdata/chromium/html/header.html
vendored
@@ -2,8 +2,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-size: 8rem;
|
font-size: 12px;
|
||||||
margin: 4rem auto;
|
margin: auto 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
8
test/testdata/chromium/html/index.html
vendored
8
test/testdata/chromium/html/index.html
vendored
@@ -59,6 +59,14 @@
|
|||||||
<p id="javascript" style="display: none">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
<p id="javascript" style="display: none">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="page-break-after">
|
||||||
|
<h2>/etc/passwd</h2>
|
||||||
|
<iframe src="/etc/passwd"></iframe>
|
||||||
|
|
||||||
|
<h2>\\localhost/etc/passwd</h2>
|
||||||
|
<iframe src="\\localhost/etc/passwd"></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
var globalVar = 'notReady'
|
var globalVar = 'notReady'
|
||||||
|
|
||||||
|
|||||||
4
test/testdata/chromium/markdown/footer.html
vendored
4
test/testdata/chromium/markdown/footer.html
vendored
@@ -2,8 +2,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-size: 8rem;
|
font-size: 12px;
|
||||||
margin: 4rem auto;
|
margin: auto 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
4
test/testdata/chromium/markdown/header.html
vendored
4
test/testdata/chromium/markdown/header.html
vendored
@@ -2,8 +2,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-size: 8rem;
|
font-size: 12px;
|
||||||
margin: 4rem auto;
|
margin: auto 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user