mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 20:32:13 +01:00
feat(chromium): set cookies
This commit is contained in:
@@ -226,6 +226,7 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outp
|
|||||||
clearCacheActionFunc(logger, b.arguments.clearCache),
|
clearCacheActionFunc(logger, b.arguments.clearCache),
|
||||||
clearCookiesActionFunc(logger, b.arguments.clearCookies),
|
clearCookiesActionFunc(logger, b.arguments.clearCookies),
|
||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
|
setCookiesActionFunc(logger, options.Cookies),
|
||||||
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
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),
|
||||||
@@ -248,6 +249,7 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, ur
|
|||||||
clearCacheActionFunc(logger, b.arguments.clearCache),
|
clearCacheActionFunc(logger, b.arguments.clearCache),
|
||||||
clearCookiesActionFunc(logger, b.arguments.clearCookies),
|
clearCookiesActionFunc(logger, b.arguments.clearCookies),
|
||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
|
setCookiesActionFunc(logger, options.Cookies),
|
||||||
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
||||||
|
|||||||
@@ -579,6 +579,41 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
"JavaScript disabled, skipping wait expression",
|
"JavaScript disabled, skipping wait expression",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "set cookies",
|
||||||
|
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>Set cookies</h1>"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}(),
|
||||||
|
options: PdfOptions{
|
||||||
|
Options: Options{Cookies: []Cookie{{Name: "foo", Value: "bar", Domain: ".foo.bar"}}},
|
||||||
|
},
|
||||||
|
noDeadline: false,
|
||||||
|
start: true,
|
||||||
|
expectError: false,
|
||||||
|
expectedLogEntries: []string{
|
||||||
|
"set cookie",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "extra HTTP headers",
|
scenario: "extra HTTP headers",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
@@ -1125,6 +1160,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
"cache not cleared",
|
"cache not cleared",
|
||||||
"cookies not cleared",
|
"cookies not cleared",
|
||||||
"JavaScript not disabled",
|
"JavaScript not disabled",
|
||||||
|
"no cookies to set",
|
||||||
"no extra HTTP headers",
|
"no extra HTTP headers",
|
||||||
"navigate to",
|
"navigate to",
|
||||||
"default white background not hidden",
|
"default white background not hidden",
|
||||||
@@ -1549,6 +1585,41 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
"JavaScript disabled, skipping wait expression",
|
"JavaScript disabled, skipping wait expression",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "set cookies",
|
||||||
|
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>Set cookies</h1>"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}(),
|
||||||
|
options: ScreenshotOptions{
|
||||||
|
Options: Options{Cookies: []Cookie{{Name: "fpp", Value: "bar", Domain: ".foo.bar"}}},
|
||||||
|
},
|
||||||
|
noDeadline: false,
|
||||||
|
start: true,
|
||||||
|
expectError: false,
|
||||||
|
expectedLogEntries: []string{
|
||||||
|
"set cookie",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "extra HTTP headers",
|
scenario: "extra HTTP headers",
|
||||||
browser: newChromiumBrowser(
|
browser: newChromiumBrowser(
|
||||||
@@ -1964,6 +2035,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
"cache not cleared",
|
"cache not cleared",
|
||||||
"cookies not cleared",
|
"cookies not cleared",
|
||||||
"JavaScript not disabled",
|
"JavaScript not disabled",
|
||||||
|
"no cookies to set",
|
||||||
"no extra HTTP headers",
|
"no extra HTTP headers",
|
||||||
"navigate to",
|
"navigate to",
|
||||||
"default white background not hidden",
|
"default white background not hidden",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
|
"github.com/chromedp/cdproto/network"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -103,6 +104,10 @@ type Options struct {
|
|||||||
// Optional.
|
// Optional.
|
||||||
WaitForExpression string
|
WaitForExpression string
|
||||||
|
|
||||||
|
// Cookies are the cookies to put in the Chromium cookies' jar.
|
||||||
|
// Optional
|
||||||
|
Cookies []Cookie
|
||||||
|
|
||||||
// ExtraHttpHeaders are the HTTP headers to send by Chromium while loading
|
// ExtraHttpHeaders are the HTTP headers to send by Chromium while loading
|
||||||
// the HTML document.
|
// the HTML document.
|
||||||
// Optional.
|
// Optional.
|
||||||
@@ -128,6 +133,7 @@ func DefaultOptions() Options {
|
|||||||
WaitDelay: 0,
|
WaitDelay: 0,
|
||||||
WaitWindowStatus: "",
|
WaitWindowStatus: "",
|
||||||
WaitForExpression: "",
|
WaitForExpression: "",
|
||||||
|
Cookies: nil,
|
||||||
ExtraHttpHeaders: nil,
|
ExtraHttpHeaders: nil,
|
||||||
EmulatedMediaType: "",
|
EmulatedMediaType: "",
|
||||||
OmitBackground: false,
|
OmitBackground: false,
|
||||||
@@ -258,6 +264,38 @@ func DefaultScreenshotOptions() ScreenshotOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cookie gathers the available entries for setting a cookie in the Chromium
|
||||||
|
// cookies' jar.
|
||||||
|
type Cookie struct {
|
||||||
|
// Name is the cookie name.
|
||||||
|
// Required.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Value is the cookie value.
|
||||||
|
// Required.
|
||||||
|
Value string `json:"value"`
|
||||||
|
|
||||||
|
// Domain is the cookie domain.
|
||||||
|
// Required.
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
|
||||||
|
// Path is the cookie path.
|
||||||
|
// Optional.
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
|
||||||
|
// Secure sets the cookie secure if true.
|
||||||
|
// Optional.
|
||||||
|
Secure bool `json:"secure,omitempty"`
|
||||||
|
|
||||||
|
// HttpOnly sets the cookie as HTTP-only if true.
|
||||||
|
// Optional.
|
||||||
|
HttpOnly bool `json:"httpOnly,omitempty"`
|
||||||
|
|
||||||
|
// SameSite is cookie 'Same-Site' status.
|
||||||
|
// Optional.
|
||||||
|
SameSite network.CookieSameSite `json:"sameSite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
waitDelay time.Duration
|
waitDelay time.Duration
|
||||||
waitWindowStatus string
|
waitWindowStatus string
|
||||||
waitForExpression string
|
waitForExpression string
|
||||||
|
cookies []Cookie
|
||||||
extraHttpHeaders map[string]string
|
extraHttpHeaders map[string]string
|
||||||
emulatedMediaType string
|
emulatedMediaType string
|
||||||
omitBackground bool
|
omitBackground bool
|
||||||
@@ -58,6 +59,25 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
Duration("waitDelay", &waitDelay, defaultOptions.WaitDelay).
|
Duration("waitDelay", &waitDelay, defaultOptions.WaitDelay).
|
||||||
String("waitWindowStatus", &waitWindowStatus, defaultOptions.WaitWindowStatus).
|
String("waitWindowStatus", &waitWindowStatus, defaultOptions.WaitWindowStatus).
|
||||||
String("waitForExpression", &waitForExpression, defaultOptions.WaitForExpression).
|
String("waitForExpression", &waitForExpression, defaultOptions.WaitForExpression).
|
||||||
|
Custom("cookies", func(value string) error {
|
||||||
|
if value == "" {
|
||||||
|
cookies = defaultOptions.Cookies
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(value), &cookies)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal cookies: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, cookie := range cookies {
|
||||||
|
if strings.TrimSpace(cookie.Name) == "" || strings.TrimSpace(cookie.Value) == "" || strings.TrimSpace(cookie.Domain) == "" {
|
||||||
|
err = multierr.Append(err, fmt.Errorf("cookie %d must have its name, value and domain set", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}).
|
||||||
Custom("extraHttpHeaders", func(value string) error {
|
Custom("extraHttpHeaders", func(value string) error {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
extraHttpHeaders = defaultOptions.ExtraHttpHeaders
|
extraHttpHeaders = defaultOptions.ExtraHttpHeaders
|
||||||
@@ -94,6 +114,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
WaitDelay: waitDelay,
|
WaitDelay: waitDelay,
|
||||||
WaitWindowStatus: waitWindowStatus,
|
WaitWindowStatus: waitWindowStatus,
|
||||||
WaitForExpression: waitForExpression,
|
WaitForExpression: waitForExpression,
|
||||||
|
Cookies: cookies,
|
||||||
ExtraHttpHeaders: extraHttpHeaders,
|
ExtraHttpHeaders: extraHttpHeaders,
|
||||||
EmulatedMediaType: emulatedMediaType,
|
EmulatedMediaType: emulatedMediaType,
|
||||||
OmitBackground: omitBackground,
|
OmitBackground: omitBackground,
|
||||||
|
|||||||
@@ -62,6 +62,59 @@ func TestFormDataChromiumOptions(t *testing.T) {
|
|||||||
return options
|
return options
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid cookies form field",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"cookies": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectedOptions: DefaultOptions(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid cookies form field (missing required values)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"cookies": {
|
||||||
|
"[{}]",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectedOptions: func() Options {
|
||||||
|
options := DefaultOptions()
|
||||||
|
// No validation in this method, so it still instantiates
|
||||||
|
// an empty item.
|
||||||
|
options.Cookies = []Cookie{{}}
|
||||||
|
return options
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "valid cookies form field",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"cookies": {
|
||||||
|
`[{"name":"foo","value":"bar","domain":".foo.bar"}]`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectedOptions: func() Options {
|
||||||
|
options := DefaultOptions()
|
||||||
|
options.Cookies = []Cookie{{
|
||||||
|
Name: "foo",
|
||||||
|
Value: "bar",
|
||||||
|
Domain: ".foo.bar",
|
||||||
|
}}
|
||||||
|
return options
|
||||||
|
}(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid extraHttpHeaders form field",
|
scenario: "invalid extraHttpHeaders form field",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package chromium
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
@@ -210,6 +211,55 @@ func disableJavaScriptActionFunc(logger *zap.Logger, disable bool) chromedp.Acti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCookiesActionFunc(logger *zap.Logger, cookies []Cookie) chromedp.ActionFunc {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
if len(cookies) == 0 {
|
||||||
|
logger.Debug("no cookies to set")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline, ok := ctx.Deadline()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("context has no deadline, cannot set cookies")
|
||||||
|
}
|
||||||
|
epochTime := cdp.TimeSinceEpoch(deadline)
|
||||||
|
|
||||||
|
cookiePretty := func(c *network.SetCookieParams) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Name: '%s', Value: '%s', Domain: '%s', Path: '%s', Secure: %t, HTTPOnly: %t, SameSite: '%s', Expires: %s",
|
||||||
|
c.Name,
|
||||||
|
c.Value,
|
||||||
|
c.Domain,
|
||||||
|
c.Path,
|
||||||
|
c.Secure,
|
||||||
|
c.HTTPOnly,
|
||||||
|
c.SameSite.String(),
|
||||||
|
c.Expires.Time().String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cookie := range cookies {
|
||||||
|
cookieParams := network.
|
||||||
|
SetCookie(cookie.Name, cookie.Value).
|
||||||
|
WithDomain(cookie.Domain).
|
||||||
|
WithPath(cookie.Path).
|
||||||
|
WithSecure(cookie.Secure).
|
||||||
|
WithHTTPOnly(cookie.HttpOnly).
|
||||||
|
WithSameSite(cookie.SameSite).
|
||||||
|
WithExpires(&epochTime)
|
||||||
|
|
||||||
|
err := cookieParams.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("set cookie %s: %w", cookiePretty(cookieParams), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(fmt.Sprintf("set cookie %s", cookiePretty(cookieParams)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
|
func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if len(extraHttpHeaders) == 0 {
|
if len(extraHttpHeaders) == 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user