mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 12:42:16 +01:00
feat(chromium): add scope to extraHttpHeaders
This commit is contained in:
@@ -228,7 +228,6 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outp
|
|||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
setCookiesActionFunc(logger, options.Cookies),
|
setCookiesActionFunc(logger, options.Cookies),
|
||||||
userAgentOverride(logger, options.UserAgent),
|
userAgentOverride(logger, options.UserAgent),
|
||||||
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground),
|
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground),
|
||||||
forceExactColorsActionFunc(),
|
forceExactColorsActionFunc(),
|
||||||
@@ -252,7 +251,6 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, ur
|
|||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
setCookiesActionFunc(logger, options.Cookies),
|
setCookiesActionFunc(logger, options.Cookies),
|
||||||
userAgentOverride(logger, options.UserAgent),
|
userAgentOverride(logger, options.UserAgent),
|
||||||
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
||||||
forceExactColorsActionFunc(),
|
forceExactColorsActionFunc(),
|
||||||
@@ -291,8 +289,14 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
defer taskCancel()
|
defer taskCancel()
|
||||||
|
|
||||||
// We validate all others requests against our allow / deny lists.
|
// We validate all others requests against our allow / deny lists.
|
||||||
// If a request does not pass the validation, we make it fail.
|
// If a request does not pass the validation, we make it fail. It also set
|
||||||
listenForEventRequestPaused(taskCtx, logger, b.arguments.allowList, b.arguments.denyList)
|
// the extra HTTP headers, if any.
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/1011.
|
||||||
|
listenForEventRequestPaused(taskCtx, logger, eventRequestPausedOptions{
|
||||||
|
allowList: b.arguments.allowList,
|
||||||
|
denyList: b.arguments.denyList,
|
||||||
|
extraHttpHeaders: options.ExtraHttpHeaders,
|
||||||
|
})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
invalidHttpStatusCode error
|
invalidHttpStatusCode error
|
||||||
|
|||||||
@@ -702,8 +702,21 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
options: PdfOptions{
|
options: PdfOptions{
|
||||||
Options: Options{ExtraHttpHeaders: map[string]string{
|
Options: Options{ExtraHttpHeaders: []ExtraHttpHeader{
|
||||||
"X-Foo": "Bar",
|
{
|
||||||
|
Name: "X-Foo",
|
||||||
|
Value: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Bar",
|
||||||
|
Value: "bar",
|
||||||
|
Scope: regexp2.MustCompile(`.*index\.html.*`, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Baz",
|
||||||
|
Value: "baz",
|
||||||
|
Scope: regexp2.MustCompile(`.*another\.html.*`, 0),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
@@ -711,6 +724,10 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
expectedLogEntries: []string{
|
expectedLogEntries: []string{
|
||||||
"extra HTTP headers:",
|
"extra HTTP headers:",
|
||||||
|
"extra HTTP header 'X-Foo' will be set for request URL",
|
||||||
|
"extra HTTP header 'X-Bar' (scoped) will be set for request URL",
|
||||||
|
"extra HTTP header 'X-Baz' (scoped) will not be set for request URL",
|
||||||
|
"setting extra HTTP headers for request URL",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1716,6 +1733,41 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
"set cookie",
|
"set cookie",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "user agent override",
|
||||||
|
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>User-Agent override</h1>"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}(),
|
||||||
|
options: ScreenshotOptions{
|
||||||
|
Options: Options{UserAgent: "foo"},
|
||||||
|
},
|
||||||
|
noDeadline: false,
|
||||||
|
start: true,
|
||||||
|
expectError: false,
|
||||||
|
expectedLogEntries: []string{
|
||||||
|
fmt.Sprintf("user agent override: foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "extra HTTP headers",
|
scenario: "extra HTTP headers",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
@@ -1742,8 +1794,21 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
options: ScreenshotOptions{
|
options: ScreenshotOptions{
|
||||||
Options: Options{ExtraHttpHeaders: map[string]string{
|
Options: Options{ExtraHttpHeaders: []ExtraHttpHeader{
|
||||||
"X-Foo": "Bar",
|
{
|
||||||
|
Name: "X-Foo",
|
||||||
|
Value: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Bar",
|
||||||
|
Value: "bar",
|
||||||
|
Scope: regexp2.MustCompile(`.*index\.html.*`, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Baz",
|
||||||
|
Value: "baz",
|
||||||
|
Scope: regexp2.MustCompile(`.*another\.html.*`, 0),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
@@ -1751,6 +1816,10 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
expectedLogEntries: []string{
|
expectedLogEntries: []string{
|
||||||
"extra HTTP headers:",
|
"extra HTTP headers:",
|
||||||
|
"extra HTTP header 'X-Foo' will be set for request URL",
|
||||||
|
"extra HTTP header 'X-Bar' (scoped) will be set for request URL",
|
||||||
|
"extra HTTP header 'X-Baz' (scoped) will not be set for request URL",
|
||||||
|
"setting extra HTTP headers for request URL",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
"github.com/chromedp/cdproto/network"
|
"github.com/chromedp/cdproto/network"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ type Options struct {
|
|||||||
|
|
||||||
// ExtraHttpHeaders are extra HTTP headers to send by Chromium while
|
// ExtraHttpHeaders are extra HTTP headers to send by Chromium while
|
||||||
// loading he HTML document.
|
// loading he HTML document.
|
||||||
ExtraHttpHeaders map[string]string
|
ExtraHttpHeaders []ExtraHttpHeader
|
||||||
|
|
||||||
// EmulatedMediaType is the media type to emulate, either "screen" or
|
// EmulatedMediaType is the media type to emulate, either "screen" or
|
||||||
// "print".
|
// "print".
|
||||||
@@ -289,6 +290,22 @@ type Cookie struct {
|
|||||||
SameSite network.CookieSameSite `json:"sameSite,omitempty"`
|
SameSite network.CookieSameSite `json:"sameSite,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraHttpHeader are extra HTTP headers to send by Chromium.
|
||||||
|
type ExtraHttpHeader struct {
|
||||||
|
// Name is the header name.
|
||||||
|
// Required.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Value is the header value.
|
||||||
|
// Required.
|
||||||
|
Value string
|
||||||
|
|
||||||
|
// Scope is the header scope. If nil, the header will be applied to ALL
|
||||||
|
// requests from the page.
|
||||||
|
// Optional.
|
||||||
|
Scope *regexp2.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
// Api helps to interact with Chromium for converting HTML documents to PDF.
|
// Api helps to interact with Chromium for converting HTML documents to PDF.
|
||||||
type Api interface {
|
type Api interface {
|
||||||
Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
||||||
|
|||||||
@@ -20,10 +20,22 @@ import (
|
|||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type eventRequestPausedOptions struct {
|
||||||
|
allowList, denyList *regexp2.Regexp
|
||||||
|
extraHttpHeaders []ExtraHttpHeader
|
||||||
|
}
|
||||||
|
|
||||||
// listenForEventRequestPaused listens for requests to check if they are
|
// listenForEventRequestPaused listens for requests to check if they are
|
||||||
// allowed or not.network.SetBlockedURLS()
|
// allowed or not. It also set the extra HTTP headers, if any.
|
||||||
|
// See https://github.com/gotenberg/gotenberg/issues/1011.
|
||||||
// TODO: https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setBlockedURLs (experimental for now).
|
// TODO: https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setBlockedURLs (experimental for now).
|
||||||
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp2.Regexp, denyList *regexp2.Regexp) {
|
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, options eventRequestPausedOptions) {
|
||||||
|
if len(options.extraHttpHeaders) == 0 {
|
||||||
|
logger.Debug("no extra HTTP headers")
|
||||||
|
} else {
|
||||||
|
logger.Debug(fmt.Sprintf("extra HTTP headers: %+v", options.extraHttpHeaders))
|
||||||
|
}
|
||||||
|
|
||||||
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:
|
||||||
@@ -37,7 +49,7 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := gotenberg.FilterDeadline(allowList, denyList, e.Request.URL, deadline)
|
err := gotenberg.FilterDeadline(options.allowList, options.denyList, e.Request.URL, deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(err.Error())
|
logger.Warn(err.Error())
|
||||||
allow = false
|
allow = false
|
||||||
@@ -46,19 +58,78 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
|||||||
cctx := chromedp.FromContext(ctx)
|
cctx := chromedp.FromContext(ctx)
|
||||||
executorCtx := cdp.WithExecutor(ctx, cctx.Target)
|
executorCtx := cdp.WithExecutor(ctx, cctx.Target)
|
||||||
|
|
||||||
if allow {
|
if !allow {
|
||||||
req := fetch.ContinueRequest(e.RequestID)
|
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("continue request: %s", err))
|
logger.Error(fmt.Sprintf("fail request: %s", err))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
|
req := fetch.ContinueRequest(e.RequestID)
|
||||||
|
|
||||||
|
var extraHttpHeadersToSet []ExtraHttpHeader
|
||||||
|
if len(options.extraHttpHeaders) > 0 {
|
||||||
|
// The user want to set extra HTTP headers.
|
||||||
|
|
||||||
|
// First, we have to check if at least one header has to be
|
||||||
|
// set for current request.
|
||||||
|
for _, header := range options.extraHttpHeaders {
|
||||||
|
if header.Scope == nil {
|
||||||
|
// Non-scoped header.
|
||||||
|
logger.Debug(fmt.Sprintf("extra HTTP header '%s' will be set for request URL '%s'", header.Name, e.Request.URL))
|
||||||
|
extraHttpHeadersToSet = append(extraHttpHeadersToSet, header)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := header.Scope.MatchString(e.Request.URL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("fail to match extra HTTP header '%s' scope with URL '%s': %s", header.Name, e.Request.URL, err))
|
||||||
|
} else if ok {
|
||||||
|
logger.Debug(fmt.Sprintf("extra HTTP header '%s' (scoped) will be set for request URL '%s'", header.Name, e.Request.URL))
|
||||||
|
extraHttpHeadersToSet = append(extraHttpHeadersToSet, header)
|
||||||
|
} else {
|
||||||
|
logger.Debug(fmt.Sprintf("scoped extra HTTP header '%s' (scoped) will not be set for request URL '%s'", header.Name, e.Request.URL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(extraHttpHeadersToSet) > 0 {
|
||||||
|
logger.Debug(fmt.Sprintf("setting extra HTTP headers for request URL '%s': %+v", e.Request.URL, extraHttpHeadersToSet))
|
||||||
|
|
||||||
|
originalHeaders := e.Request.Headers
|
||||||
|
headers := make(map[string]string)
|
||||||
|
|
||||||
|
for key, value := range originalHeaders {
|
||||||
|
strValue, ok := value.(string)
|
||||||
|
if ok {
|
||||||
|
headers[key] = strValue
|
||||||
|
} else {
|
||||||
|
logger.Error(fmt.Sprintf("ignoring header '%s' for URL '%s' since it cannot be cast to a string", key, e.Request.URL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headersEntries []*fetch.HeaderEntry
|
||||||
|
for key, value := range headers {
|
||||||
|
headersEntries = append(headersEntries, &fetch.HeaderEntry{
|
||||||
|
Name: key,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, header := range extraHttpHeadersToSet {
|
||||||
|
headersEntries = append(headersEntries, &fetch.HeaderEntry{
|
||||||
|
Name: header.Name,
|
||||||
|
Value: header.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Headers = headersEntries
|
||||||
|
}
|
||||||
|
|
||||||
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("continue request: %s", err))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"github.com/russross/blackfriday/v2"
|
"github.com/russross/blackfriday/v2"
|
||||||
@@ -36,7 +37,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
waitForExpression string
|
waitForExpression string
|
||||||
cookies []Cookie
|
cookies []Cookie
|
||||||
userAgent string
|
userAgent string
|
||||||
extraHttpHeaders map[string]string
|
extraHttpHeaders []ExtraHttpHeader
|
||||||
emulatedMediaType string
|
emulatedMediaType string
|
||||||
omitBackground bool
|
omitBackground bool
|
||||||
)
|
)
|
||||||
@@ -86,12 +87,59 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(value), &extraHttpHeaders)
|
var headers map[string]string
|
||||||
|
err := json.Unmarshal([]byte(value), &headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unmarshal extraHttpHeaders: %w", err)
|
return fmt.Errorf("unmarshal extraHttpHeaders: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for k, v := range headers {
|
||||||
|
var scope string
|
||||||
|
var valueTokens []string
|
||||||
|
var invalidScopeToken bool
|
||||||
|
|
||||||
|
tokens := strings.Split(v, ";")
|
||||||
|
for _, token := range tokens {
|
||||||
|
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(token)), "scope") {
|
||||||
|
tokenNoSpaces := strings.Join(strings.Fields(token), "")
|
||||||
|
parts := strings.SplitN(tokenNoSpaces, "=", 2)
|
||||||
|
|
||||||
|
if len(parts) == 2 && strings.ToLower(parts[0]) == "scope" && parts[1] != "" {
|
||||||
|
scope = parts[1]
|
||||||
|
} else {
|
||||||
|
err = multierr.Append(err, fmt.Errorf("invalid scope '%s' for header '%s'", scope, k))
|
||||||
|
invalidScopeToken = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if token != "" {
|
||||||
|
valueTokens = append(valueTokens, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if invalidScopeToken {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var scopeRegexp *regexp2.Regexp
|
||||||
|
if len(scope) > 0 {
|
||||||
|
p, errCompile := regexp2.Compile(scope, 0)
|
||||||
|
if errCompile != nil {
|
||||||
|
err = multierr.Append(err, fmt.Errorf("invalid scope regex pattern for header '%s': %w", k, errCompile))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scopeRegexp = p
|
||||||
|
}
|
||||||
|
|
||||||
|
extraHttpHeaders = append(extraHttpHeaders, ExtraHttpHeader{
|
||||||
|
Name: k,
|
||||||
|
Value: strings.Join(valueTokens, "; "),
|
||||||
|
Scope: scopeRegexp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}).
|
}).
|
||||||
Custom("emulatedMediaType", func(value string) error {
|
Custom("emulatedMediaType", func(value string) error {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -19,14 +21,18 @@ import (
|
|||||||
|
|
||||||
func TestFormDataChromiumOptions(t *testing.T) {
|
func TestFormDataChromiumOptions(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
ctx *api.ContextMock
|
ctx *api.ContextMock
|
||||||
expectedOptions Options
|
expectedOptions Options
|
||||||
|
compareWithoutDeepEqual bool
|
||||||
|
expectValidationError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "no custom form fields",
|
scenario: "no custom form fields",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
expectedOptions: DefaultOptions(),
|
expectedOptions: DefaultOptions(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid failOnHttpStatusCodes form field",
|
scenario: "invalid failOnHttpStatusCodes form field",
|
||||||
@@ -44,6 +50,8 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
options.FailOnHttpStatusCodes = nil
|
options.FailOnHttpStatusCodes = nil
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid failOnHttpStatusCodes form field",
|
scenario: "valid failOnHttpStatusCodes form field",
|
||||||
@@ -61,6 +69,8 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
options.FailOnHttpStatusCodes = []int64{399, 499, 599}
|
options.FailOnHttpStatusCodes = []int64{399, 499, 599}
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid cookies form field",
|
scenario: "invalid cookies form field",
|
||||||
@@ -73,7 +83,9 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
expectedOptions: DefaultOptions(),
|
expectedOptions: DefaultOptions(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid cookies form field (missing required values)",
|
scenario: "invalid cookies form field (missing required values)",
|
||||||
@@ -93,6 +105,8 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
options.Cookies = []Cookie{{}}
|
options.Cookies = []Cookie{{}}
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid cookies form field",
|
scenario: "valid cookies form field",
|
||||||
@@ -114,9 +128,11 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid extraHttpHeaders form field",
|
scenario: "invalid extraHttpHeaders form field: cannot unmarshall",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
@@ -126,7 +142,39 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
expectedOptions: DefaultOptions(),
|
expectedOptions: DefaultOptions(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid extraHttpHeaders form field: invalid scope",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"extraHttpHeaders": {
|
||||||
|
`{"foo":"bar;scope;;"}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectedOptions: DefaultOptions(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid extraHttpHeaders form field: invalid scope regex pattern",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"extraHttpHeaders": {
|
||||||
|
`{"foo":"bar;scope=*."}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectedOptions: DefaultOptions(),
|
||||||
|
compareWithoutDeepEqual: false,
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid extraHttpHeaders form field",
|
scenario: "valid extraHttpHeaders form field",
|
||||||
@@ -134,18 +182,28 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
"extraHttpHeaders": {
|
"extraHttpHeaders": {
|
||||||
`{"foo":"bar"}`,
|
`{"foo":"bar","baz":"qux;scope=https?:\\/\\/([a-zA-Z0-9-]+\\.)*qux\\.com\\/.*"}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
expectedOptions: func() Options {
|
expectedOptions: func() Options {
|
||||||
options := DefaultOptions()
|
options := DefaultOptions()
|
||||||
options.ExtraHttpHeaders = map[string]string{
|
options.ExtraHttpHeaders = []ExtraHttpHeader{
|
||||||
"foo": "bar",
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Value: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "baz",
|
||||||
|
Value: "qux",
|
||||||
|
Scope: regexp2.MustCompile(`https?:\/\/([a-zA-Z0-9-]+\.)*qux\.com\/.*`, 0),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
compareWithoutDeepEqual: true,
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid emulatedMediaType form field",
|
scenario: "invalid emulatedMediaType form field",
|
||||||
@@ -158,7 +216,8 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
expectedOptions: DefaultOptions(),
|
expectedOptions: DefaultOptions(),
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid emulatedMediaType form field",
|
scenario: "valid emulatedMediaType form field",
|
||||||
@@ -176,14 +235,61 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
options.EmulatedMediaType = "screen"
|
options.EmulatedMediaType = "screen"
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
_, actual := FormDataChromiumOptions(tc.ctx.Context)
|
form, actual := FormDataChromiumOptions(tc.ctx.Context)
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.expectedOptions) {
|
if tc.compareWithoutDeepEqual {
|
||||||
t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual)
|
if len(tc.expectedOptions.ExtraHttpHeaders) != len(actual.ExtraHttpHeaders) {
|
||||||
|
t.Fatalf("expected %d extra HTTP headers, but got %d", len(tc.expectedOptions.ExtraHttpHeaders), len(actual.ExtraHttpHeaders))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(tc.expectedOptions.ExtraHttpHeaders, func(i, j int) bool {
|
||||||
|
return tc.expectedOptions.ExtraHttpHeaders[i].Name < tc.expectedOptions.ExtraHttpHeaders[j].Name
|
||||||
|
})
|
||||||
|
sort.Slice(actual.ExtraHttpHeaders, func(i, j int) bool {
|
||||||
|
return actual.ExtraHttpHeaders[i].Name < actual.ExtraHttpHeaders[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range tc.expectedOptions.ExtraHttpHeaders {
|
||||||
|
if tc.expectedOptions.ExtraHttpHeaders[i].Name != actual.ExtraHttpHeaders[i].Name {
|
||||||
|
t.Fatalf("expected '%s' extra HTTP header, but got '%s'", tc.expectedOptions.ExtraHttpHeaders[i].Name, tc.expectedOptions.ExtraHttpHeaders[i].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedOptions.ExtraHttpHeaders[i].Value != actual.ExtraHttpHeaders[i].Value {
|
||||||
|
t.Fatalf("expected '%s' as value for extra HTTP header '%s', but got '%s'", tc.expectedOptions.ExtraHttpHeaders[i].Value, tc.expectedOptions.ExtraHttpHeaders[i].Name, actual.ExtraHttpHeaders[i].Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedScope string
|
||||||
|
if tc.expectedOptions.ExtraHttpHeaders[i].Scope != nil {
|
||||||
|
expectedScope = tc.expectedOptions.ExtraHttpHeaders[i].Scope.String()
|
||||||
|
}
|
||||||
|
var actualScope string
|
||||||
|
if actual.ExtraHttpHeaders[i].Scope != nil {
|
||||||
|
actualScope = actual.ExtraHttpHeaders[i].Scope.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedScope != actualScope {
|
||||||
|
t.Fatalf("expected '%s' as scope for extra HTTP header '%s', but got '%s'", expectedScope, tc.expectedOptions.ExtraHttpHeaders[i].Name, actualScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !reflect.DeepEqual(actual, tc.expectedOptions) {
|
||||||
|
t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := form.Validate()
|
||||||
|
|
||||||
|
if tc.expectValidationError && err == nil {
|
||||||
|
t.Fatal("expected validation error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectValidationError && err != nil {
|
||||||
|
t.Fatalf("expected no validation error but got: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -191,14 +297,16 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
|
|
||||||
func TestFormDataChromiumPdfOptions(t *testing.T) {
|
func TestFormDataChromiumPdfOptions(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
ctx *api.ContextMock
|
ctx *api.ContextMock
|
||||||
expectedOptions PdfOptions
|
expectedOptions PdfOptions
|
||||||
|
expectValidationError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "no custom form fields",
|
scenario: "no custom form fields",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
expectedOptions: DefaultPdfOptions(),
|
expectedOptions: DefaultPdfOptions(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "custom form fields (Options & PdfOptions)",
|
scenario: "custom form fields (Options & PdfOptions)",
|
||||||
@@ -220,29 +328,42 @@ func TestFormDataChromiumPdfOptions(t *testing.T) {
|
|||||||
options.EmulatedMediaType = "screen"
|
options.EmulatedMediaType = "screen"
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
_, actual := FormDataChromiumPdfOptions(tc.ctx.Context)
|
form, actual := FormDataChromiumPdfOptions(tc.ctx.Context)
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.expectedOptions) {
|
if !reflect.DeepEqual(actual, tc.expectedOptions) {
|
||||||
t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual)
|
t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := form.Validate()
|
||||||
|
|
||||||
|
if tc.expectValidationError && err == nil {
|
||||||
|
t.Fatal("expected validation error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectValidationError && err != nil {
|
||||||
|
t.Fatalf("expected no validation error but got: %v", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
ctx *api.ContextMock
|
ctx *api.ContextMock
|
||||||
expectedOptions ScreenshotOptions
|
expectedOptions ScreenshotOptions
|
||||||
|
expectValidationError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "no custom form fields",
|
scenario: "no custom form fields",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
expectedOptions: DefaultScreenshotOptions(),
|
expectedOptions: DefaultScreenshotOptions(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid format form field",
|
scenario: "invalid format form field",
|
||||||
@@ -260,6 +381,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Format = ""
|
options.Format = ""
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid png format form field",
|
scenario: "valid png format form field",
|
||||||
@@ -277,6 +399,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Format = "png"
|
options.Format = "png"
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid jpeg format form field",
|
scenario: "valid jpeg format form field",
|
||||||
@@ -294,6 +417,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Format = "jpeg"
|
options.Format = "jpeg"
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid webp format form field",
|
scenario: "valid webp format form field",
|
||||||
@@ -311,6 +435,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Format = "webp"
|
options.Format = "webp"
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid quality form field (not an integer)",
|
scenario: "invalid quality form field (not an integer)",
|
||||||
@@ -328,6 +453,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Quality = 0
|
options.Quality = 0
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid quality form field (< 0)",
|
scenario: "invalid quality form field (< 0)",
|
||||||
@@ -345,6 +471,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Quality = 0
|
options.Quality = 0
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid quality form field (> 100)",
|
scenario: "invalid quality form field (> 100)",
|
||||||
@@ -362,6 +489,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Quality = 0
|
options.Quality = 0
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid quality form field",
|
scenario: "valid quality form field",
|
||||||
@@ -379,6 +507,7 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.Quality = 50
|
options.Quality = 50
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "custom form fields (Options & ScreenshotOptions)",
|
scenario: "custom form fields (Options & ScreenshotOptions)",
|
||||||
@@ -412,29 +541,42 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) {
|
|||||||
options.EmulatedMediaType = "screen"
|
options.EmulatedMediaType = "screen"
|
||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
_, actual := FormDataChromiumScreenshotOptions(tc.ctx.Context)
|
form, actual := FormDataChromiumScreenshotOptions(tc.ctx.Context)
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.expectedOptions) {
|
if !reflect.DeepEqual(actual, tc.expectedOptions) {
|
||||||
t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual)
|
t.Fatalf("expected %+v but got: %+v", tc.expectedOptions, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := form.Validate()
|
||||||
|
|
||||||
|
if tc.expectValidationError && err == nil {
|
||||||
|
t.Fatal("expected validation error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectValidationError && err != nil {
|
||||||
|
t.Fatalf("expected no validation error but got: %v", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormDataChromiumPdfFormats(t *testing.T) {
|
func TestFormDataChromiumPdfFormats(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
ctx *api.ContextMock
|
ctx *api.ContextMock
|
||||||
expectedPdfFormats gotenberg.PdfFormats
|
expectedPdfFormats gotenberg.PdfFormats
|
||||||
|
expectValidationError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "no custom form fields",
|
scenario: "no custom form fields",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
expectedPdfFormats: gotenberg.PdfFormats{},
|
expectedPdfFormats: gotenberg.PdfFormats{},
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "pdfa and pdfua form fields",
|
scenario: "pdfa and pdfua form fields",
|
||||||
@@ -450,30 +592,44 @@ func TestFormDataChromiumPdfFormats(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
expectedPdfFormats: gotenberg.PdfFormats{PdfA: "foo", PdfUa: true},
|
expectedPdfFormats: gotenberg.PdfFormats{PdfA: "foo", PdfUa: true},
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
actual := FormDataChromiumPdfFormats(tc.ctx.Context.FormData())
|
form := tc.ctx.Context.FormData()
|
||||||
|
actual := FormDataChromiumPdfFormats(form)
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.expectedPdfFormats) {
|
if !reflect.DeepEqual(actual, tc.expectedPdfFormats) {
|
||||||
t.Fatalf("expected %+v but got: %+v", tc.expectedPdfFormats, actual)
|
t.Fatalf("expected %+v but got: %+v", tc.expectedPdfFormats, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := form.Validate()
|
||||||
|
|
||||||
|
if tc.expectValidationError && err == nil {
|
||||||
|
t.Fatal("expected validation error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectValidationError && err != nil {
|
||||||
|
t.Fatalf("expected no validation error but got: %v", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormDataPdfMetadata(t *testing.T) {
|
func TestFormDataPdfMetadata(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
ctx *api.ContextMock
|
ctx *api.ContextMock
|
||||||
expectedMetadata map[string]interface{}
|
expectedMetadata map[string]interface{}
|
||||||
|
expectValidationError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "no metadata form field",
|
scenario: "no metadata form field",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
expectedMetadata: nil,
|
expectedMetadata: nil,
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid metadata form field",
|
scenario: "invalid metadata form field",
|
||||||
@@ -486,7 +642,8 @@ func TestFormDataPdfMetadata(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
expectedMetadata: nil,
|
expectedMetadata: nil,
|
||||||
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "valid metadata form field",
|
scenario: "valid metadata form field",
|
||||||
@@ -502,15 +659,27 @@ func TestFormDataPdfMetadata(t *testing.T) {
|
|||||||
expectedMetadata: map[string]interface{}{
|
expectedMetadata: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
actual := FormDataPdfMetadata(tc.ctx.Context.FormData())
|
form := tc.ctx.Context.FormData()
|
||||||
|
actual := FormDataPdfMetadata(form)
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.expectedMetadata) {
|
if !reflect.DeepEqual(actual, tc.expectedMetadata) {
|
||||||
t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual)
|
t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := form.Validate()
|
||||||
|
|
||||||
|
if tc.expectValidationError && err == nil {
|
||||||
|
t.Fatal("expected validation error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectValidationError && err != nil {
|
||||||
|
t.Fatalf("expected no validation error but got: %v", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,28 +298,33 @@ func userAgentOverride(logger *zap.Logger, userAgent string) chromedp.ActionFunc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
|
// This code has been replaced with the listenForEventRequestPaused function.
|
||||||
return func(ctx context.Context) error {
|
// Indeed, the user may want to scope the headers per domain, but using
|
||||||
if len(extraHttpHeaders) == 0 {
|
// network.SetExtraHTTPHeaders set the headers for ALL requests from the page.
|
||||||
logger.Debug("no extra HTTP headers")
|
// See https://github.com/gotenberg/gotenberg/issues/1011.
|
||||||
return nil
|
//
|
||||||
}
|
//func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
|
||||||
|
// return func(ctx context.Context) error {
|
||||||
logger.Debug(fmt.Sprintf("extra HTTP headers: %+v", extraHttpHeaders))
|
// if len(extraHttpHeaders) == 0 {
|
||||||
|
// logger.Debug("no extra HTTP headers")
|
||||||
headers := make(network.Headers, len(extraHttpHeaders))
|
// return nil
|
||||||
for key, value := range extraHttpHeaders {
|
// }
|
||||||
headers[key] = value
|
//
|
||||||
}
|
// logger.Debug(fmt.Sprintf("extra HTTP headers: %+v", extraHttpHeaders))
|
||||||
|
//
|
||||||
err := network.SetExtraHTTPHeaders(headers).Do(ctx)
|
// headers := make(network.Headers, len(extraHttpHeaders))
|
||||||
if err == nil {
|
// for key, value := range extraHttpHeaders {
|
||||||
return nil
|
// headers[key] = value
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return fmt.Errorf("set extra HTTP headers: %w", err)
|
// err := network.SetExtraHTTPHeaders(headers).Do(ctx)
|
||||||
}
|
// if err == nil {
|
||||||
}
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return fmt.Errorf("set extra HTTP headers: %w", err)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent bool) chromedp.ActionFunc {
|
func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent bool) chromedp.ActionFunc {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user