diff --git a/Makefile b/Makefile index a862a856..3d2203fb 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ CHROMIUM_USER_AGENT= CHROMIUM_INCOGNITO=false CHROMIUM_IGNORE_CERTIFICATE_ERRORS=false CHROMIUM_ALLOW_LIST= -CHROMIUM_DENY_LIST= +CHROMIUM_DENY_LIST="^file:///[^tmp].*" CHROMIUM_DISABLE_ROUTES=false LIBREOFFICE_DISABLES_ROUTES=false LOG_LEVEL=info diff --git a/pkg/modules/chromium/chromium.go b/pkg/modules/chromium/chromium.go index b5c796ab..6b54d789 100644 --- a/pkg/modules/chromium/chromium.go +++ b/pkg/modules/chromium/chromium.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/chromedp/cdproto/fetch" "github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/page" "github.com/chromedp/chromedp" @@ -183,7 +184,7 @@ func (mod Chromium) Descriptor() gotenberg.ModuleDescriptor { fs.Bool("chromium-incognito", false, "Start Chromium with incognito mode") fs.Bool("chromium-ignore-certificate-errors", false, "Ignore the certificate errors") fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression") - fs.String("chromium-deny-list", "", "Set the denied URLs for Chromium using a regular expression") + fs.String("chromium-deny-list", "^file:///[^tmp].*", "Set the denied URLs for Chromium using a regular expression") fs.Bool("chromium-disable-routes", false, "Disable the routes") return fs @@ -286,6 +287,7 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath taskCtx, cancel := chromedp.NewContext(allocatorCtx) defer cancel() + // We validate the "main" URL against our allow / deny lists. if !mod.allowList.MatchString(URL) { return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", URL, ErrURLNotAuthorized) } @@ -295,8 +297,13 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath } printToPDF := func(URL string, options Options, result *[]byte) chromedp.Tasks { + // We validate the underlying requests against our allow / deny lists. + // If a request does not pass the validation, we make it fail. + listenForEventRequestPaused(taskCtx, logger, mod.allowList, mod.denyList) + return chromedp.Tasks{ network.Enable(), + fetch.Enable(), chromedp.ActionFunc(func(ctx context.Context) error { if len(options.ExtraHTTPHeaders) == 0 { logger.Debug("no extra HTTP headers") diff --git a/pkg/modules/chromium/chromium_test.go b/pkg/modules/chromium/chromium_test.go index 5e937a05..8c1e374d 100644 --- a/pkg/modules/chromium/chromium_test.go +++ b/pkg/modules/chromium/chromium_test.go @@ -222,26 +222,25 @@ func TestChromium_PDF(t *testing.T) { expectErr bool }{ { - URL: "https://google.com", - allowList: regexp.MustCompile("https://google.fr"), + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", + allowList: regexp.MustCompile("file:///tmp/*"), expectErr: true, }, { - URL: "https://google.com", - denyList: regexp.MustCompile("https://google.com"), + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", + denyList: regexp.MustCompile("file:///tests/*"), expectErr: true, }, { - URL: "", + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", options: Options{ ExtraHTTPHeaders: map[string]string{ "foo": "bar", }, }, - expectErr: true, }, { - URL: "https://google.com", + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", options: Options{ WaitDelay: time.Duration(1) * time.Nanosecond, }, @@ -262,21 +261,21 @@ func TestChromium_PDF(t *testing.T) { }, }, { - URL: "https://google.com", + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", options: Options{ MarginBottom: 100, }, expectErr: true, }, { - URL: "https://google.com", + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", options: Options{ PageRanges: "foo", }, expectErr: true, }, { - URL: "https://google.com", + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", userAgent: "foo", incognito: true, ignoreCertificateErrors: true, @@ -285,7 +284,15 @@ func TestChromium_PDF(t *testing.T) { URL: "file:///tests/test/testdata/chromium/html/sample1/index.html", }, { - URL: "https://google.com", + URL: "file:///tests/test/testdata/chromium/html/sample3/index.html", + allowList: regexp.MustCompile("file:///tests/*"), + }, + { + URL: "file:///tests/test/testdata/chromium/html/sample3/index.html", + denyList: regexp.MustCompile("file:///etc/*"), + }, + { + URL: "file:///tests/test/testdata/chromium/html/sample4/index.html", options: Options{ HeaderTemplate: func() string { b, err := ioutil.ReadFile("/tests/test/testdata/chromium/url/sample2/header.html") diff --git a/pkg/modules/chromium/events.go b/pkg/modules/chromium/events.go index 165eab0e..510a7820 100644 --- a/pkg/modules/chromium/events.go +++ b/pkg/modules/chromium/events.go @@ -3,7 +3,10 @@ package chromium import ( "context" "fmt" + "regexp" + "github.com/chromedp/cdproto/cdp" + "github.com/chromedp/cdproto/fetch" "github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/page" "github.com/chromedp/chromedp" @@ -11,6 +14,51 @@ import ( "golang.org/x/sync/errgroup" ) +// listenForEventRequestPaused listens for requests to check if they are +// allowed or not. +func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp.Regexp, denyList *regexp.Regexp) { + chromedp.ListenTarget(ctx, func(ev interface{}) { + switch e := ev.(type) { + case *fetch.EventRequestPaused: + go func() { + logger.Debug(fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL)) + allow := true + + if !allowList.MatchString(e.Request.URL) { + logger.Warn(fmt.Sprintf("'%s' does not match the expression from the allowed list", e.Request.URL)) + allow = false + } + + if denyList.String() != "" && denyList.MatchString(e.Request.URL) { + logger.Warn(fmt.Sprintf("'%s' matches the expression from the denied list", e.Request.URL)) + allow = false + } + + cctx := chromedp.FromContext(ctx) + executorCtx := cdp.WithExecutor(ctx, cctx.Target) + + if allow { + req := fetch.ContinueRequest(e.RequestID) + err := req.Do(executorCtx) + + if err != nil { + logger.Error(fmt.Sprintf("continue request: %s", err)) + } + + return + } + + req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied) + err := req.Do(executorCtx) + + if err != nil { + logger.Error(fmt.Sprintf("fail request: %s", err)) + } + }() + } + }) +} + // waitForEventDomContentEventFired waits until the event DomContentEventFired // is fired or the context timeout. func waitForEventDomContentEventFired(ctx context.Context, logger *zap.Logger) func() error { diff --git a/test/testdata/chromium/html/sample3/index.html b/test/testdata/chromium/html/sample3/index.html new file mode 100644 index 00000000..9be80e2b --- /dev/null +++ b/test/testdata/chromium/html/sample3/index.html @@ -0,0 +1,10 @@ + + +
+ +Hello, world!
+ + \ No newline at end of file