mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 19:32:15 +01:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4af41b33c0 | ||
|
|
0f8bbead66 | ||
|
|
804fcff5b6 | ||
|
|
d18e6920fc | ||
|
|
c95174ff11 | ||
|
|
c70329c22b | ||
|
|
34f69263a2 | ||
|
|
bf5056f8fe | ||
|
|
c47f5b06ba | ||
|
|
63055e3df2 | ||
|
|
8ee555140b | ||
|
|
859f78e986 | ||
|
|
366eebb68c |
14
go.mod
14
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-20240214232516-ad4608604e9e
|
github.com/chromedp/cdproto v0.0.0-20240226204813-532e667d868f
|
||||||
github.com/chromedp/chromedp v0.9.5
|
github.com/chromedp/chromedp v0.9.5
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
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.6 // indirect
|
github.com/klauspost/compress v1.17.7 // 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
|
||||||
@@ -21,13 +21,13 @@ require (
|
|||||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||||
github.com/pdfcpu/pdfcpu v0.6.0
|
github.com/pdfcpu/pdfcpu v0.6.0
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/prometheus/client_golang v1.18.0
|
github.com/prometheus/client_golang v1.19.0
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.19.0 // indirect
|
golang.org/x/crypto v0.20.0 // indirect
|
||||||
golang.org/x/image v0.15.0 // indirect
|
golang.org/x/image v0.15.0 // indirect
|
||||||
golang.org/x/net v0.21.0
|
golang.org/x/net v0.21.0
|
||||||
golang.org/x/sync v0.6.0
|
golang.org/x/sync v0.6.0
|
||||||
@@ -36,7 +36,7 @@ require (
|
|||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/dlclark/regexp2 v1.10.0
|
require github.com/dlclark/regexp2 v1.11.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
@@ -56,7 +56,7 @@ require (
|
|||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.0 // indirect
|
github.com/prometheus/client_model v0.6.0 // indirect
|
||||||
github.com/prometheus/common v0.47.0 // indirect
|
github.com/prometheus/common v0.49.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -10,16 +10,16 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e h1:kXEolCWQZzuEFcuaTzfqXToX+e29OcvK87BcBiBBJ1c=
|
github.com/chromedp/cdproto v0.0.0-20240226204813-532e667d868f h1:jODunjTDQHm0Srs2IsfcS3hOmNLUN7Spag3NJZQra2g=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20240226204813-532e667d868f/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
||||||
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
||||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
@@ -53,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.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
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=
|
||||||
@@ -96,12 +96,12 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||||
github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=
|
github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI=
|
||||||
github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE=
|
||||||
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=
|
||||||
@@ -128,14 +128,14 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
|||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
|||||||
69
pkg/gotenberg/sort.go
Normal file
69
pkg/gotenberg/sort.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package gotenberg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlphanumericSort implements sort.Interface and helps to sort strings
|
||||||
|
// alphanumerically.
|
||||||
|
//
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/805.
|
||||||
|
type AlphanumericSort []string
|
||||||
|
|
||||||
|
func (s AlphanumericSort) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s AlphanumericSort) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s AlphanumericSort) Less(i, j int) bool {
|
||||||
|
numI, restI := extractPrefix(s[i])
|
||||||
|
numJ, restJ := extractPrefix(s[j])
|
||||||
|
|
||||||
|
// Compares numerical prefixes if they exist.
|
||||||
|
if numI != -1 && numJ != -1 {
|
||||||
|
if numI != numJ {
|
||||||
|
return numI < numJ
|
||||||
|
}
|
||||||
|
// If numbers are equal, falls back to string comparison of the rest.
|
||||||
|
return restI < restJ
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one has a numerical prefix and the other doesn't, the one with the
|
||||||
|
// number comes first.
|
||||||
|
if numI != -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if numJ != -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither has a numerical prefix, compare as strings
|
||||||
|
return s[i] < s[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractPrefix attempts to extract a numerical prefix and the rest of the filename
|
||||||
|
func extractPrefix(filename string) (int, string) {
|
||||||
|
matches := numPrefixRegexp.FindStringSubmatch(filename)
|
||||||
|
if len(matches) > 2 {
|
||||||
|
prefix, err := strconv.Atoi(matches[1])
|
||||||
|
if err == nil {
|
||||||
|
return prefix, matches[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns -1 if no numerical prefix is found, indicating to just compare
|
||||||
|
// as strings.
|
||||||
|
return -1, filename
|
||||||
|
}
|
||||||
|
|
||||||
|
var numPrefixRegexp = regexp.MustCompile(`^(\d+)(.*)$`)
|
||||||
|
|
||||||
|
// Interface guard.
|
||||||
|
var (
|
||||||
|
_ sort.Interface = (*AlphanumericSort)(nil)
|
||||||
|
)
|
||||||
34
pkg/gotenberg/sort_test.go
Normal file
34
pkg/gotenberg/sort_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package gotenberg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlphanumericSort(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
values []string
|
||||||
|
expectedSort []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "numeric and letters",
|
||||||
|
values: []string{"10qux.pdf", "2_baz.txt", "2_aza.txt", "1bar.pdf", "Afoo.txt", "Bbar.docx", "25zeta.txt", "3.pdf", "4_foo.pdf"},
|
||||||
|
expectedSort: []string{"1bar.pdf", "2_aza.txt", "2_baz.txt", "3.pdf", "4_foo.pdf", "10qux.pdf", "25zeta.txt", "Afoo.txt", "Bbar.docx"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "hrtime (PHP library)",
|
||||||
|
values: []string{"245654773395259", "245654773395039", "245654773395149", "245654773394919", "245654773394369"},
|
||||||
|
expectedSort: []string{"245654773394369", "245654773394919", "245654773395039", "245654773395149", "245654773395259"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
sort.Sort(AlphanumericSort(tc.values))
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.values, tc.expectedSort) {
|
||||||
|
t.Fatalf("expected %+v but got: %+v", tc.expectedSort, tc.values)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormData is a helper for validating and hydrating values from a
|
// FormData is a helper for validating and hydrating values from a
|
||||||
@@ -304,7 +306,7 @@ func (form *FormData) paths(extensions []string, target *[]string) *FormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/139.
|
// See https://github.com/gotenberg/gotenberg/issues/139.
|
||||||
sort.Strings(*target)
|
sort.Sort(gotenberg.AlphanumericSort(*target))
|
||||||
|
|
||||||
return form
|
return form
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,18 @@ func ParseError(err error) (int, string) {
|
|||||||
return http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable)
|
return http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gotenberg.ErrFiltered) {
|
||||||
|
return http.StatusForbidden, http.StatusText(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) {
|
||||||
|
return http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
|
return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues"
|
||||||
|
}
|
||||||
|
|
||||||
var httpErr HttpError
|
var httpErr HttpError
|
||||||
if errors.As(err, &httpErr) {
|
if errors.As(err, &httpErr) {
|
||||||
return httpErr.HttpError()
|
return httpErr.HttpError()
|
||||||
|
|||||||
@@ -29,9 +29,19 @@ func TestParseError(t *testing.T) {
|
|||||||
expectMessage: http.StatusText(http.StatusInternalServerError),
|
expectMessage: http.StatusText(http.StatusInternalServerError),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
err: context.DeadlineExceeded,
|
err: gotenberg.ErrFiltered,
|
||||||
expectStatus: http.StatusServiceUnavailable,
|
expectStatus: http.StatusForbidden,
|
||||||
expectMessage: http.StatusText(http.StatusServiceUnavailable),
|
expectMessage: http.StatusText(http.StatusForbidden),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
err: gotenberg.ErrMaximumQueueSizeExceeded,
|
||||||
|
expectStatus: http.StatusTooManyRequests,
|
||||||
|
expectMessage: http.StatusText(http.StatusTooManyRequests),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
err: gotenberg.ErrPdfFormatNotSupported,
|
||||||
|
expectStatus: http.StatusBadRequest,
|
||||||
|
expectMessage: "A least one PDF engine does not handle one of the requested PDF format, while other have failed to convert for other reasons",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
err: WrapError(
|
err: WrapError(
|
||||||
|
|||||||
@@ -950,6 +950,41 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
"wait until 'window.globalVar === 'ready'' is true before print",
|
"wait until 'window.globalVar === 'ready'' is true before print",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "single page",
|
||||||
|
browser: newChromiumBrowser(
|
||||||
|
browserArguments{
|
||||||
|
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||||
|
wsUrlReadTimeout: 5 * time.Second,
|
||||||
|
allowList: regexp2.MustCompile("", 0),
|
||||||
|
denyList: regexp2.MustCompile("", 0),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
fs: func() *gotenberg.FileSystem {
|
||||||
|
fs := gotenberg.NewFileSystem()
|
||||||
|
|
||||||
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprintf("%s/index.html", fs.WorkingDirPath()), []byte("<h1>Custom header and footer</h1>"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}(),
|
||||||
|
options: PdfOptions{
|
||||||
|
SinglePage: true,
|
||||||
|
},
|
||||||
|
noDeadline: false,
|
||||||
|
start: true,
|
||||||
|
expectError: false,
|
||||||
|
expectedLogEntries: []string{
|
||||||
|
"single page PDF",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "custom header and footer",
|
scenario: "custom header and footer",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
|
|||||||
@@ -150,6 +150,11 @@ type PdfOptions struct {
|
|||||||
// Optional.
|
// Optional.
|
||||||
Scale float64
|
Scale float64
|
||||||
|
|
||||||
|
// SinglePage defines whether to print the entire content in one single
|
||||||
|
// page.
|
||||||
|
// Optional.
|
||||||
|
SinglePage bool
|
||||||
|
|
||||||
// PaperWidth is the paper width, in inches.
|
// PaperWidth is the paper width, in inches.
|
||||||
// Optional.
|
// Optional.
|
||||||
PaperWidth float64
|
PaperWidth float64
|
||||||
@@ -209,6 +214,7 @@ func DefaultPdfOptions() PdfOptions {
|
|||||||
Landscape: false,
|
Landscape: false,
|
||||||
PrintBackground: false,
|
PrintBackground: false,
|
||||||
Scale: 1.0,
|
Scale: 1.0,
|
||||||
|
SinglePage: false,
|
||||||
PaperWidth: 8.5,
|
PaperWidth: 8.5,
|
||||||
PaperHeight: 11,
|
PaperHeight: 11,
|
||||||
MarginTop: 0.39,
|
MarginTop: 0.39,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) {
|
|||||||
defaultPdfOptions := DefaultPdfOptions()
|
defaultPdfOptions := DefaultPdfOptions()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
landscape, printBackground bool
|
landscape, printBackground, singlePage bool
|
||||||
scale, paperWidth, paperHeight float64
|
scale, paperWidth, paperHeight float64
|
||||||
marginTop, marginBottom, marginLeft, marginRight float64
|
marginTop, marginBottom, marginLeft, marginRight float64
|
||||||
pageRanges string
|
pageRanges string
|
||||||
@@ -121,6 +121,7 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) {
|
|||||||
Bool("landscape", &landscape, defaultPdfOptions.Landscape).
|
Bool("landscape", &landscape, defaultPdfOptions.Landscape).
|
||||||
Bool("printBackground", &printBackground, defaultPdfOptions.PrintBackground).
|
Bool("printBackground", &printBackground, defaultPdfOptions.PrintBackground).
|
||||||
Float64("scale", &scale, defaultPdfOptions.Scale).
|
Float64("scale", &scale, defaultPdfOptions.Scale).
|
||||||
|
Bool("singlePage", &singlePage, defaultPdfOptions.SinglePage).
|
||||||
Float64("paperWidth", &paperWidth, defaultPdfOptions.PaperWidth).
|
Float64("paperWidth", &paperWidth, defaultPdfOptions.PaperWidth).
|
||||||
Float64("paperHeight", &paperHeight, defaultPdfOptions.PaperHeight).
|
Float64("paperHeight", &paperHeight, defaultPdfOptions.PaperHeight).
|
||||||
Float64("marginTop", &marginTop, defaultPdfOptions.MarginTop).
|
Float64("marginTop", &marginTop, defaultPdfOptions.MarginTop).
|
||||||
@@ -137,6 +138,7 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) {
|
|||||||
Landscape: landscape,
|
Landscape: landscape,
|
||||||
PrintBackground: printBackground,
|
PrintBackground: printBackground,
|
||||||
Scale: scale,
|
Scale: scale,
|
||||||
|
SinglePage: singlePage,
|
||||||
PaperWidth: paperWidth,
|
PaperWidth: paperWidth,
|
||||||
PaperHeight: paperHeight,
|
PaperHeight: paperHeight,
|
||||||
MarginTop: marginTop,
|
MarginTop: marginTop,
|
||||||
@@ -508,7 +510,7 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
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, options.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrOmitBackgroundWithoutPrintBackground) {
|
if errors.Is(err, ErrOmitBackgroundWithoutPrintBackground) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
@@ -553,26 +555,6 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
|
|
||||||
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) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("At least one PDF engine does not handle one of the PDF format in '%+v', while other have failed to convert for other reasons", pdfFormats),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("convert PDF: %w", err)
|
return fmt.Errorf("convert PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,7 +575,7 @@ func screenshotUrl(ctx *api.Context, chromium Api, url string, options Screensho
|
|||||||
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, options.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("screenshot: %w", err)
|
return fmt.Errorf("screenshot: %w", err)
|
||||||
}
|
}
|
||||||
@@ -606,31 +588,11 @@ func screenshotUrl(ctx *api.Context, chromium Api, url string, options Screensho
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleChromiumError(err error, url string, options Options) error {
|
func handleChromiumError(err error, options Options) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
|
||||||
err,
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusForbidden,
|
|
||||||
fmt.Sprintf("'%s' does not match the authorized URLs", url),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
||||||
if options.WaitForExpression == "" {
|
if options.WaitForExpression == "" {
|
||||||
// We do not expect the 'waitWindowStatus' form field to return
|
// We do not expect the 'waitWindowStatus' form field to return
|
||||||
|
|||||||
@@ -1230,30 +1230,6 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
expectHttpStatus int
|
expectHttpStatus int
|
||||||
expectOutputPathsCount int
|
expectOutputPathsCount int
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
scenario: "ErrMaximumQueueSizeExceeded",
|
|
||||||
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.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(),
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusForbidden,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "ErrOmitBackgroundWithoutPrintBackground",
|
scenario: "ErrOmitBackgroundWithoutPrintBackground",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
@@ -1353,38 +1329,6 @@ 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",
|
|
||||||
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.ErrPdfFormatNotSupported
|
|
||||||
}},
|
|
||||||
pdfFormats: gotenberg.PdfFormats{PdfA: "foo"},
|
|
||||||
options: DefaultPdfOptions(),
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "error from PDF engine",
|
scenario: "error from PDF engine",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
@@ -1490,30 +1434,6 @@ func TestScreenshotUrl(t *testing.T) {
|
|||||||
expectHttpStatus int
|
expectHttpStatus int
|
||||||
expectOutputPathsCount int
|
expectOutputPathsCount int
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
scenario: "ErrMaximumQueueSizeExceeded",
|
|
||||||
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.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(),
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusForbidden,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "ErrInvalidEvaluationExpression (without waitForExpression form field)",
|
scenario: "ErrInvalidEvaluationExpression (without waitForExpression form field)",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
|||||||
@@ -17,19 +17,37 @@ import (
|
|||||||
|
|
||||||
func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOptions) chromedp.ActionFunc {
|
func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOptions) chromedp.ActionFunc {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
|
paperHeight := options.PaperHeight
|
||||||
|
pageRanges := options.PageRanges
|
||||||
|
|
||||||
|
if options.SinglePage {
|
||||||
|
logger.Debug("single page PDF")
|
||||||
|
|
||||||
|
_, _, _, _, _, cssContentSize, err := page.GetLayoutMetrics().Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get layout metrics: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are 96 CSS pixels per inch.
|
||||||
|
// See https://issues.chromium.org/issues/40267771#comment14.
|
||||||
|
paperHeight = cssContentSize.Height / 96
|
||||||
|
pageRanges = "1" // little dirty hack to avoid leftovers.
|
||||||
|
}
|
||||||
|
|
||||||
printToPdf := page.PrintToPDF().
|
printToPdf := page.PrintToPDF().
|
||||||
WithTransferMode(page.PrintToPDFTransferModeReturnAsStream).
|
WithTransferMode(page.PrintToPDFTransferModeReturnAsStream).
|
||||||
WithLandscape(options.Landscape).
|
WithLandscape(options.Landscape).
|
||||||
WithPrintBackground(options.PrintBackground).
|
WithPrintBackground(options.PrintBackground).
|
||||||
WithScale(options.Scale).
|
WithScale(options.Scale).
|
||||||
WithPaperWidth(options.PaperWidth).
|
WithPaperWidth(options.PaperWidth).
|
||||||
WithPaperHeight(options.PaperHeight).
|
WithPaperHeight(paperHeight).
|
||||||
WithMarginTop(options.MarginTop).
|
WithMarginTop(options.MarginTop).
|
||||||
WithMarginBottom(options.MarginBottom).
|
WithMarginBottom(options.MarginBottom).
|
||||||
WithMarginLeft(options.MarginLeft).
|
WithMarginLeft(options.MarginLeft).
|
||||||
WithMarginRight(options.MarginRight).
|
WithMarginRight(options.MarginRight).
|
||||||
WithPageRanges(options.PageRanges).
|
WithPageRanges(pageRanges).
|
||||||
WithPreferCSSPageSize(options.PreferCssPageSize)
|
WithPreferCSSPageSize(options.PreferCssPageSize).
|
||||||
|
WithGenerateTaggedPDF(false)
|
||||||
|
|
||||||
hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate ||
|
hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate ||
|
||||||
options.FooterTemplate != DefaultPdfOptions().FooterTemplate
|
options.FooterTemplate != DefaultPdfOptions().FooterTemplate
|
||||||
|
|||||||
@@ -267,84 +267,86 @@ func (a *Api) Pdf(ctx context.Context, logger *zap.Logger, inputPath, outputPath
|
|||||||
func (a *Api) Extensions() []string {
|
func (a *Api) Extensions() []string {
|
||||||
return []string{
|
return []string{
|
||||||
".bib",
|
".bib",
|
||||||
".doc",
|
|
||||||
".xml",
|
|
||||||
".docx",
|
|
||||||
".fodt",
|
|
||||||
".html",
|
|
||||||
".ltx",
|
|
||||||
".txt",
|
|
||||||
".odt",
|
|
||||||
".ott",
|
|
||||||
".pdb",
|
|
||||||
".pdf",
|
|
||||||
".psw",
|
|
||||||
".rtf",
|
|
||||||
".sdw",
|
|
||||||
".stw",
|
|
||||||
".sxw",
|
|
||||||
".uot",
|
|
||||||
".vor",
|
|
||||||
".wps",
|
|
||||||
".epub",
|
|
||||||
".png",
|
|
||||||
".bmp",
|
".bmp",
|
||||||
".emf",
|
|
||||||
".eps",
|
|
||||||
".fodg",
|
|
||||||
".gif",
|
|
||||||
".jpg",
|
|
||||||
".jpeg",
|
|
||||||
".met",
|
|
||||||
".odd",
|
|
||||||
".otg",
|
|
||||||
".pbm",
|
|
||||||
".pct",
|
|
||||||
".pgm",
|
|
||||||
".ppm",
|
|
||||||
".ras",
|
|
||||||
".std",
|
|
||||||
".svg",
|
|
||||||
".svm",
|
|
||||||
".swf",
|
|
||||||
".sxd",
|
|
||||||
".sxw",
|
|
||||||
".tif",
|
|
||||||
".tiff",
|
|
||||||
".xhtml",
|
|
||||||
".xpm",
|
|
||||||
".odp",
|
|
||||||
".fodp",
|
|
||||||
".potm",
|
|
||||||
".pot",
|
|
||||||
".pptx",
|
|
||||||
".pps",
|
|
||||||
".ppt",
|
|
||||||
".pwp",
|
|
||||||
".sda",
|
|
||||||
".sdd",
|
|
||||||
".sti",
|
|
||||||
".sxi",
|
|
||||||
".uop",
|
|
||||||
".wmf",
|
|
||||||
".csv",
|
".csv",
|
||||||
".dbf",
|
".dbf",
|
||||||
".dif",
|
".dif",
|
||||||
|
".doc",
|
||||||
|
".docx",
|
||||||
|
".dotx",
|
||||||
|
".emf",
|
||||||
|
".eps",
|
||||||
|
".epub",
|
||||||
|
".fodg",
|
||||||
|
".fodp",
|
||||||
".fods",
|
".fods",
|
||||||
|
".fodt",
|
||||||
|
".gif",
|
||||||
|
".html",
|
||||||
|
".jpeg",
|
||||||
|
".jpg",
|
||||||
|
".key",
|
||||||
|
".ltx",
|
||||||
|
".met",
|
||||||
|
".odd",
|
||||||
|
".odg",
|
||||||
|
".odp",
|
||||||
".ods",
|
".ods",
|
||||||
|
".odt",
|
||||||
|
".otg",
|
||||||
".ots",
|
".ots",
|
||||||
|
".ott",
|
||||||
|
".pages",
|
||||||
|
".pbm",
|
||||||
|
".pct",
|
||||||
|
".pdb",
|
||||||
|
".pdf",
|
||||||
|
".pgm",
|
||||||
|
".png",
|
||||||
|
".pot",
|
||||||
|
".potm",
|
||||||
|
".ppm",
|
||||||
|
".pps",
|
||||||
|
".ppt",
|
||||||
|
".pptx",
|
||||||
|
".psw",
|
||||||
|
".pwp",
|
||||||
".pxl",
|
".pxl",
|
||||||
|
".ras",
|
||||||
|
".rtf",
|
||||||
|
".sda",
|
||||||
".sdc",
|
".sdc",
|
||||||
|
".sdd",
|
||||||
|
".sdw",
|
||||||
".slk",
|
".slk",
|
||||||
".stc",
|
".stc",
|
||||||
|
".std",
|
||||||
|
".sti",
|
||||||
|
".stw",
|
||||||
|
".svg",
|
||||||
|
".svm",
|
||||||
|
".swf",
|
||||||
".sxc",
|
".sxc",
|
||||||
|
".sxd",
|
||||||
|
".sxi",
|
||||||
|
".sxw",
|
||||||
|
".sxw",
|
||||||
|
".tif",
|
||||||
|
".tiff",
|
||||||
|
".txt",
|
||||||
|
".uop",
|
||||||
".uos",
|
".uos",
|
||||||
|
".uot",
|
||||||
|
".vor",
|
||||||
|
".wmf",
|
||||||
|
".wps",
|
||||||
|
".xhtml",
|
||||||
".xls",
|
".xls",
|
||||||
".xlt",
|
|
||||||
".xlsx",
|
".xlsx",
|
||||||
".odg",
|
".xlt",
|
||||||
".dotx",
|
|
||||||
".xltx",
|
".xltx",
|
||||||
|
".xml",
|
||||||
|
".xpm",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ func TestApi_Extensions(t *testing.T) {
|
|||||||
extensions := a.Extensions()
|
extensions := a.Extensions()
|
||||||
|
|
||||||
actual := len(extensions)
|
actual := len(extensions)
|
||||||
expect := 79
|
expect := 80
|
||||||
|
|
||||||
if actual != expect {
|
if actual != expect {
|
||||||
t.Errorf("expected %d extensions, but got %d", expect, actual)
|
t.Errorf("expected %d extensions, but got %d", expect, actual)
|
||||||
|
|||||||
@@ -288,8 +288,8 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP
|
|||||||
if options.PdfFormats.PdfUa {
|
if options.PdfFormats.PdfUa {
|
||||||
args = append(
|
args = append(
|
||||||
args,
|
args,
|
||||||
"--export", "EnableTextAccessForAccessibilityTools=true",
|
|
||||||
"--export", "UseTaggedPDF=true",
|
"--export", "UseTaggedPDF=true",
|
||||||
|
"--export", "EnableTextAccessForAccessibilityTools=true",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,16 +68,6 @@ 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),
|
||||||
@@ -107,16 +97,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,26 +109,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
|
|
||||||
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) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("At least one PDF engine does not handle one of the PDF format in '%+v', while other have failed to convert for other reasons", pdfFormats),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("convert PDF: %w", err)
|
return fmt.Errorf("convert PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,26 +140,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
|
|
||||||
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) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("At least one PDF engine does not handle one of the PDF format in '%+v', while other have failed to convert for other reasons", pdfFormats),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("convert PDF: %w", err)
|
return fmt.Errorf("convert PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,28 +39,6 @@ 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 {
|
||||||
@@ -131,76 +109,6 @@ 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)",
|
|
||||||
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": {
|
|
||||||
"foo",
|
|
||||||
},
|
|
||||||
"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.ErrPdfFormatNotSupported
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "PDF engine convert error (single file)",
|
scenario: "PDF engine convert error (single file)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -378,39 +286,6 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectOutputPathsCount: 2,
|
expectOutputPathsCount: 2,
|
||||||
expectOutputPaths: []string{"/document.docx.pdf", "/document2.docx.pdf"},
|
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",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -443,90 +318,6 @@ 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)",
|
|
||||||
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": {
|
|
||||||
"foo",
|
|
||||||
},
|
|
||||||
"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.ErrPdfFormatNotSupported
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "PDF engine convert error (merge)",
|
scenario: "PDF engine convert error (merge)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
|
|||||||
@@ -49,16 +49,6 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,26 +62,6 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
|
|
||||||
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) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("At least one PDF engine does not handle one of the PDF format in '%+v', while other have failed to convert for other reasons", pdfFormats),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("convert PDF: %w", err)
|
return fmt.Errorf("convert PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,26 +137,6 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
|
|
||||||
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) {
|
|
||||||
return api.WrapError(
|
|
||||||
fmt.Errorf("convert PDF: %w", err),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("At least one PDF engine does not handle one of the PDF format in '%+v', while other have failed to convert for other reasons", pdfFormats),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("convert PDF: %w", err)
|
return fmt.Errorf("convert PDF: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,27 +32,6 @@ 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 {
|
||||||
@@ -111,62 +90,6 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
scenario: "ErrMaximumQueueSizeExceeded (convert)",
|
|
||||||
ctx: func() *api.ContextMock {
|
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
|
||||||
ctx.SetFiles(map[string]string{
|
|
||||||
"file.pdf": "/file.pdf",
|
|
||||||
"file2.pdf": "/file2.pdf",
|
|
||||||
})
|
|
||||||
ctx.SetValues(map[string][]string{
|
|
||||||
"pdfa": {
|
|
||||||
gotenberg.PdfA1b,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
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{
|
|
||||||
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.ErrPdfFormatNotSupported
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "error from PDF engine (convert)",
|
scenario: "error from PDF engine (convert)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -298,54 +221,6 @@ func TestConvertHandler(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{
|
|
||||||
"file.pdf": "/file.pdf",
|
|
||||||
})
|
|
||||||
ctx.SetValues(map[string][]string{
|
|
||||||
"pdfa": {
|
|
||||||
gotenberg.PdfA1b,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
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{
|
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
|
||||||
return gotenberg.ErrPdfFormatNotSupported
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusBadRequest,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "error from PDF engine",
|
scenario: "error from PDF engine",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"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"
|
||||||
|
|
||||||
@@ -52,31 +51,12 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
|||||||
|
|
||||||
// Let's check if the webhook URLs are acceptable according to our
|
// Let's check if the webhook URLs are acceptable according to our
|
||||||
// allowed/denied lists.
|
// allowed/denied lists.
|
||||||
filter := func(url, header string, allowList, denyList *regexp2.Regexp, deadline time.Time) error {
|
err := gotenberg.FilterDeadline(w.allowList, w.denyList, webhookUrl, deadline)
|
||||||
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, deadline)
|
err = gotenberg.FilterDeadline(w.errorAllowList, w.errorDenyList, webhookErrorUrl, 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,10 +119,8 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
mod.allowList = regexp2.MustCompile("bar", 0)
|
mod.allowList = regexp2.MustCompile("bar", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusForbidden,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "webhook URL is denied",
|
scenario: "webhook URL is denied",
|
||||||
@@ -137,10 +135,8 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
mod.denyList = regexp2.MustCompile("foo", 0)
|
mod.denyList = regexp2.MustCompile("foo", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusForbidden,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "webhook error URL is not allowed",
|
scenario: "webhook error URL is not allowed",
|
||||||
@@ -155,10 +151,8 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
mod.errorAllowList = regexp2.MustCompile("foo", 0)
|
mod.errorAllowList = regexp2.MustCompile("foo", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusForbidden,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "webhook error URL is denied",
|
scenario: "webhook error URL is denied",
|
||||||
@@ -173,10 +167,8 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
|
|||||||
mod.errorDenyList = regexp2.MustCompile("bar", 0)
|
mod.errorDenyList = regexp2.MustCompile("bar", 0)
|
||||||
return mod
|
return mod
|
||||||
}(),
|
}(),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectHttpError: true,
|
|
||||||
expectHttpStatus: http.StatusForbidden,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid webhook method (GET)",
|
scenario: "invalid webhook method (GET)",
|
||||||
|
|||||||
Reference in New Issue
Block a user