Path: blob/dev/pkg/protocols/headless/engine/page_actions_test.go
2072 views
package engine12import (3"context"4"fmt"5"io"6"math/rand"7"net/http"8"net/http/cookiejar"9"net/http/httptest"10"os"11"os/exec"12"path/filepath"13"strconv"14"strings"15"testing"16"time"1718"github.com/stretchr/testify/require"1920"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"21"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"22"github.com/projectdiscovery/nuclei/v3/pkg/testutils/testheadless"23"github.com/projectdiscovery/nuclei/v3/pkg/types"24envutil "github.com/projectdiscovery/utils/env"25stringsutil "github.com/projectdiscovery/utils/strings"26)2728func TestActionNavigate(t *testing.T) {29response := `30<html>31<head>32<title>Nuclei Test Page</title>33</head>34<body>35<h1>Nuclei Test</h1>36</body>37</html>`3839actions := []*Action{{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}}, {ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}}}4041testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {42require.Nilf(t, err, "could not run page actions")43require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")44})45}4647func TestActionScript(t *testing.T) {48response := `49<html>50<head>51<title>Nuclei Test Page</title>52</head>53<body>Nuclei Test Page</body>54<script>window.test = 'some-data';</script>55</html>`5657timeout := 180 * time.Second5859t.Run("run-and-results", func(t *testing.T) {60actions := []*Action{61{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},62{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},63{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: "test", Data: map[string]string{"code": "() => window.test"}},64}6566testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {67require.Nil(t, err, "could not run page actions")68require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")69require.Equal(t, "some-data", out["test"], "could not run js and get results correctly")70})71})7273t.Run("hook", func(t *testing.T) {74actions := []*Action{75{ActionType: ActionTypeHolder{ActionType: ActionScript}, Data: map[string]string{"code": "() => window.test = 'some-data';", "hook": "true"}},76{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},77{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},78{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: "test", Data: map[string]string{"code": "() => window.test"}},79}80testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {81require.Nil(t, err, "could not run page actions")82require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")83require.Equal(t, "some-data", out["test"], "could not run js and get results correctly with js hook")84})85})86}8788func TestActionClick(t *testing.T) {89response := `90<html>91<head>92<title>Nuclei Test Page</title>93</head>94<body>Nuclei Test Page</body>95<button onclick='this.setAttribute("a", "ok")'>click me</button>96</html>`9798actions := []*Action{99{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},100{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},101{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{"selector": "button"}}, // Use css selector for clicking102}103104testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {105require.Nil(t, err, "could not run page actions")106require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")107el := page.Page().MustElement("button")108val := el.MustAttribute("a")109require.Equal(t, "ok", *val, "could not click button")110})111}112113func TestActionRightClick(t *testing.T) {114response := `115<html>116<head>117<title>Nuclei Test Page</title>118</head>119<body>Nuclei Test Page</body>120<button id="test" onrightclick=''>click me</button>121<script>122elm = document.getElementById("test");123elm.onmousedown = function(event) {124if (event.which == 3) {125elm.setAttribute("a", "ok")126}127}128</script>129</html>`130131actions := []*Action{132{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},133{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},134{ActionType: ActionTypeHolder{ActionType: ActionRightClick}, Data: map[string]string{"selector": "button"}}, // Use css selector for clicking135}136137testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {138require.Nil(t, err, "could not run page actions")139require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")140el := page.Page().MustElement("button")141val := el.MustAttribute("a")142require.Equal(t, "ok", *val, "could not click button")143})144}145146func TestActionTextInput(t *testing.T) {147response := `148<html>149<head>150<title>Nuclei Test Page</title>151</head>152<body>Nuclei Test Page</body>153<input type="text" onchange="this.setAttribute('event', 'input-change')">154</html>`155156actions := []*Action{157{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},158{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},159{ActionType: ActionTypeHolder{ActionType: ActionTextInput}, Data: map[string]string{"selector": "input", "value": "test"}},160}161162testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {163require.Nil(t, err, "could not run page actions")164require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")165el := page.Page().MustElement("input")166val := el.MustAttribute("event")167require.Equal(t, "input-change", *val, "could not get input change")168require.Equal(t, "test", el.MustText(), "could not get input change value")169})170}171172func TestActionHeadersChange(t *testing.T) {173actions := []*Action{174{ActionType: ActionTypeHolder{ActionType: ActionSetHeader}, Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},175{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},176{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},177}178179handler := func(w http.ResponseWriter, r *http.Request) {180if r.Header.Get("Test") == "Hello" {181_, _ = fmt.Fprintln(w, `found`)182}183}184185testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {186require.Nil(t, err, "could not run page actions")187require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")188})189}190191func TestActionScreenshot(t *testing.T) {192response := `193<html>194<head>195<title>Nuclei Test Page</title>196</head>197<body>Nuclei Test Page</body>198</html>`199200// filePath where screenshot is saved201filePath := filepath.Join(os.TempDir(), "test.png")202actions := []*Action{203{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},204{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},205{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{"to": filePath}},206}207208testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {209require.Nil(t, err, "could not run page actions")210require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")211_ = page.Page()212require.FileExists(t, filePath, "could not find screenshot file %v", filePath)213if err := os.RemoveAll(filePath); err != nil {214t.Logf("got error %v while deleting temp file", err)215}216})217}218219func TestActionScreenshotToDir(t *testing.T) {220response := `221<html>222<head>223<title>Nuclei Test Page</title>224</head>225<body>Nuclei Test Page</body>226</html>`227228filePath := filepath.Join(os.TempDir(), "screenshot-"+strconv.Itoa(rand.Intn(1000)), "test.png")229230actions := []*Action{231{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},232{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},233{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{"to": filePath, "mkdir": "true"}},234}235236testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {237require.Nil(t, err, "could not run page actions")238require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")239_ = page.Page()240require.FileExists(t, filePath, "could not find screenshot file %v", filePath)241if err := os.RemoveAll(filePath); err != nil {242t.Logf("got error %v while deleting temp file", err)243}244})245}246247func TestActionTimeInput(t *testing.T) {248response := `249<html>250<head>251<title>Nuclei Test Page</title>252</head>253<body>Nuclei Test Page</body>254<input type="date">255</html>`256257actions := []*Action{258{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},259{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},260{ActionType: ActionTypeHolder{ActionType: ActionTimeInput}, Data: map[string]string{"selector": "input", "value": "2006-01-02T15:04:05Z"}},261}262263testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {264require.Nil(t, err, "could not run page actions")265require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")266el := page.Page().MustElement("input")267require.Equal(t, "2006-01-02", el.MustText(), "could not get input time value")268})269}270271func TestActionSelectInput(t *testing.T) {272response := `273<html>274<head>275<title>Nuclei Test Page</title>276</head>277<body>278<select name="test" id="test">279<option value="test1">Test1</option>280<option value="test2">Test2</option>281</select>282</body>283</html>`284285actions := []*Action{286{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},287{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},288{ActionType: ActionTypeHolder{ActionType: ActionSelectInput}, Data: map[string]string{"by": "x", "xpath": "//select[@id='test']", "value": "Test2", "selected": "true"}},289}290291testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {292require.Nil(t, err, "could not run page actions")293el := page.Page().MustElement("select")294require.Equal(t, "Test2", el.MustText(), "could not get input change value")295})296}297298func TestActionFilesInput(t *testing.T) {299response := `300<html>301<head>302<title>Nuclei Test Page</title>303</head>304<body>Nuclei Test Page</body>305<input type="file">306</html>`307308actions := []*Action{309{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},310{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},311{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{"selector": "input", "value": "test1.pdf"}},312}313314testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {315require.Nil(t, err, "could not run page actions")316require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")317el := page.Page().MustElement("input")318require.Equal(t, "C:\\fakepath\\test1.pdf", el.MustText(), "could not get input file")319})320}321322// Negative testcase for files input where it should fail323func TestActionFilesInputNegative(t *testing.T) {324response := `325<html>326<head>327<title>Nuclei Test Page</title>328</head>329<body>Nuclei Test Page</body>330<input type="file">331</html>`332333actions := []*Action{334{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},335{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},336{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{"selector": "input", "value": "test1.pdf"}},337}338t.Setenv("LOCAL_FILE_ACCESS", "false")339340testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {341require.ErrorContains(t, err, ErrLFAccessDenied.Error(), "got file access when -lfa is false")342})343}344345func TestActionWaitLoad(t *testing.T) {346response := `347<html>348<head>349<title>Nuclei Test Page</title>350</head>351<button id="test">Wait for me!</button>352<script>353window.onload = () => document.querySelector('#test').style.color = 'red';354</script>355</html>`356357actions := []*Action{358{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},359{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},360}361362testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {363require.Nil(t, err, "could not run page actions")364el := page.Page().MustElement("button")365style, attributeErr := el.Attribute("style")366require.Nil(t, attributeErr)367require.Equal(t, "color: red;", *style, "could not get color")368})369}370371func TestActionGetResource(t *testing.T) {372response := `373<html>374<head>375<title>Nuclei Test Page</title>376</head>377<body>378<img id="test" src="https://raw.githubusercontent.com/projectdiscovery/wallpapers/main/pd-floppy.jpg">379</body>380</html>`381382actions := []*Action{383{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},384{ActionType: ActionTypeHolder{ActionType: ActionGetResource}, Data: map[string]string{"by": "x", "xpath": "//img[@id='test']"}, Name: "src"},385}386387testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {388require.Nil(t, err, "could not run page actions")389390src, ok := out["src"].(string)391require.True(t, ok, "could not assert src to string")392require.Equal(t, len(src), 121808, "could not find resource")393})394}395396func TestActionExtract(t *testing.T) {397response := `398<html>399<head>400<title>Nuclei Test Page</title>401</head>402<button id="test">Wait for me!</button>403</html>`404405actions := []*Action{406{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},407{ActionType: ActionTypeHolder{ActionType: ActionExtract}, Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}, Name: "extract"},408}409410testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {411require.Nil(t, err, "could not run page actions")412require.Equal(t, "Wait for me!", out["extract"], "could not extract text")413})414}415416func TestActionSetMethod(t *testing.T) {417response := `418<html>419<head>420<title>Nuclei Test Page</title>421</head>422</html>`423424actions := []*Action{425{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},426{ActionType: ActionTypeHolder{ActionType: ActionSetMethod}, Data: map[string]string{"part": "x", "method": "SET"}},427}428429testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {430require.Nil(t, err, "could not run page actions")431require.Equal(t, "SET", page.rules[0].Args["method"], "could not find resource")432})433}434435func TestActionAddHeader(t *testing.T) {436actions := []*Action{437{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},438{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},439{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},440}441442handler := func(w http.ResponseWriter, r *http.Request) {443if r.Header.Get("Test") == "Hello" {444_, _ = fmt.Fprintln(w, `found`)445}446}447448testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {449require.Nil(t, err, "could not run page actions")450require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")451})452}453454func TestActionDeleteHeader(t *testing.T) {455actions := []*Action{456{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{"part": "request", "key": "Test1", "value": "Hello"}},457{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{"part": "request", "key": "Test2", "value": "World"}},458{ActionType: ActionTypeHolder{ActionType: ActionDeleteHeader}, Data: map[string]string{"part": "request", "key": "Test2"}},459{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},460{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},461}462463handler := func(w http.ResponseWriter, r *http.Request) {464if r.Header.Get("Test1") == "Hello" && r.Header.Get("Test2") == "" {465_, _ = fmt.Fprintln(w, `header deleted`)466}467}468469testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {470require.Nil(t, err, "could not run page actions")471require.Equal(t, "header deleted", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not delete header correctly")472})473}474475func TestActionSetBody(t *testing.T) {476actions := []*Action{477{ActionType: ActionTypeHolder{ActionType: ActionSetBody}, Data: map[string]string{"part": "request", "body": "hello"}},478{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},479{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},480}481482handler := func(w http.ResponseWriter, r *http.Request) {483body, _ := io.ReadAll(r.Body)484_, _ = fmt.Fprintln(w, string(body))485}486487testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {488require.Nil(t, err, "could not run page actions")489require.Equal(t, "hello", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")490})491}492493func TestActionKeyboard(t *testing.T) {494response := `495<html>496<head>497<title>Nuclei Test Page</title>498</head>499<body>500<input type="text" name="test" id="test">501</body>502</html>`503504actions := []*Action{505{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},506{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},507{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{"selector": "input"}},508{ActionType: ActionTypeHolder{ActionType: ActionKeyboard}, Data: map[string]string{"keys": "Test2"}},509}510511testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {512require.Nil(t, err, "could not run page actions")513el := page.Page().MustElement("input")514require.Equal(t, "Test2", el.MustText(), "could not get input change value")515})516}517518func TestActionSleep(t *testing.T) {519response := `520<html>521<head>522<title>Nuclei Test Page</title>523</head>524<button style="display:none" id="test">Wait for me!</button>525<script>526setTimeout(() => document.querySelector('#test').style.display = '', 1000);527</script>528</html>`529530actions := []*Action{531{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},532{ActionType: ActionTypeHolder{ActionType: ActionSleep}, Data: map[string]string{"duration": "2"}},533}534535testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {536require.Nil(t, err, "could not run page actions")537require.True(t, page.Page().MustElement("button").MustVisible(), "could not get button")538})539}540541func TestActionWaitVisible(t *testing.T) {542response := `543<html>544<head>545<title>Nuclei Test Page</title>546</head>547<button style="display:none" id="test">Wait for me!</button>548<script>549setTimeout(() => document.querySelector('#test').style.display = '', 1000);550</script>551</html>`552553actions := []*Action{554{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},555{ActionType: ActionTypeHolder{ActionType: ActionWaitVisible}, Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}},556}557558t.Run("wait for an element being visible", func(t *testing.T) {559testHeadlessSimpleResponse(t, response, actions, 2*time.Second, func(page *Page, err error, out ActionData) {560require.Nil(t, err, "could not run page actions")561562page.Page().MustElement("button").MustVisible()563})564})565566t.Run("timeout because of element not visible", func(t *testing.T) {567// increased timeout from time.Second/2 to time.Second due to random fails (probably due to overhead and system)568testHeadlessSimpleResponse(t, response, actions, time.Second, func(page *Page, err error, out ActionData) {569require.Error(t, err)570require.Contains(t, err.Error(), "Element did not appear in the given amount of time")571})572})573}574575func TestActionWaitDialog(t *testing.T) {576response := `<html>577<head>578<title>Nuclei Test Page</title>579</head>580<body>581<script type="text/javascript">582const urlParams = new URLSearchParams(window.location.search);583const scriptContent = urlParams.get('script');584if (scriptContent) {585const scriptElement = document.createElement('script');586scriptElement.textContent = scriptContent;587588document.body.appendChild(scriptElement);589}590</script>591</body>592</html>`593594t.Run("Triggered", func(t *testing.T) {595actions := []*Action{596{597ActionType: ActionTypeHolder{ActionType: ActionNavigate},598Data: map[string]string{"url": "{{BaseURL}}/?script=alert%281%29"},599},600{601ActionType: ActionTypeHolder{ActionType: ActionWaitDialog},602Name: "test",603},604}605606testHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {607require.Nil(t, err, "could not run page actions")608609test, ok := out["test"].(bool)610require.True(t, ok, "could not assert test to bool")611require.True(t, test, "could not find test")612})613})614615t.Run("Invalid", func(t *testing.T) {616actions := []*Action{617{618ActionType: ActionTypeHolder{ActionType: ActionNavigate},619Data: map[string]string{"url": "{{BaseURL}}/?script=foo"},620},621{622ActionType: ActionTypeHolder{ActionType: ActionWaitDialog},623Name: "test",624},625}626627testHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {628require.Nil(t, err, "could not run page actions")629630_, ok := out["test"].(bool)631require.False(t, ok, "output assertion is success")632})633})634}635636func testHeadlessSimpleResponse(t *testing.T, response string, actions []*Action, timeout time.Duration, assert func(page *Page, pageErr error, out ActionData)) {637t.Helper()638testHeadless(t, actions, timeout, func(w http.ResponseWriter, r *http.Request) {639_, _ = fmt.Fprintln(w, response)640}, assert)641}642643func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handler func(w http.ResponseWriter, r *http.Request), assert func(page *Page, pageErr error, extractedData ActionData)) {644t.Helper()645646lfa := envutil.GetEnvOrDefault("LOCAL_FILE_ACCESS", true)647rna := envutil.GetEnvOrDefault("RESTRICED_LOCAL_NETWORK_ACCESS", false)648649opts := &types.Options{AllowLocalFileAccess: lfa, RestrictLocalNetworkAccess: rna}650651_ = protocolstate.Init(opts)652653browser, err := New(&types.Options{654ShowBrowser: false,655UseInstalledChrome: testheadless.HeadlessLocal,656})657require.Nil(t, err, "could not create browser")658defer browser.Close()659660instance, err := browser.NewInstance()661require.Nil(t, err, "could not create browser instance")662defer func() {663_ = instance.Close()664}()665666ts := httptest.NewServer(http.HandlerFunc(handler))667defer ts.Close()668669input := contextargs.NewWithInput(context.Background(), ts.URL)670input.CookieJar, err = cookiejar.New(nil)671require.Nil(t, err)672673extractedData, page, err := instance.Run(input, actions, nil, &Options{Timeout: timeout, Options: opts}) // allow file access in test674assert(page, err, extractedData)675676if page != nil {677page.Close()678}679}680681func TestContainsAnyModificationActionType(t *testing.T) {682if containsAnyModificationActionType() {683t.Error("Expected false, got true")684}685if containsAnyModificationActionType(ActionClick) {686t.Error("Expected false, got true")687}688if !containsAnyModificationActionType(ActionSetMethod, ActionAddHeader, ActionExtract) {689t.Error("Expected true, got false")690}691if !containsAnyModificationActionType(ActionSetMethod, ActionAddHeader, ActionSetHeader, ActionDeleteHeader, ActionSetBody) {692t.Error("Expected true, got false")693}694}695696func TestBlockedHeadlessURLS(t *testing.T) {697698// run this test from binary since we are changing values699// of global variables700if os.Getenv("TEST_BLOCK_HEADLESS_URLS") != "1" {701cmd := exec.Command(os.Args[0], "-test.run=TestBlockedHeadlessURLS", "-test.v")702cmd.Env = append(cmd.Env, "TEST_BLOCK_HEADLESS_URLS=1")703out, err := cmd.CombinedOutput()704if !strings.Contains(string(out), "PASS\n") || err != nil {705t.Fatalf("%s\n(exit status %v)", string(out), err)706}707return708}709710opts := &types.Options{711AllowLocalFileAccess: false,712RestrictLocalNetworkAccess: true,713}714err := protocolstate.Init(opts)715require.Nil(t, err, "could not init protocol state")716717browser, err := New(&types.Options{ShowBrowser: false, UseInstalledChrome: testheadless.HeadlessLocal})718require.Nil(t, err, "could not create browser")719defer browser.Close()720721instance, err := browser.NewInstance()722require.Nil(t, err, "could not create browser instance")723defer func() {724_ = instance.Close()725}()726727ts := httptest.NewServer(nil)728defer ts.Close()729730testcases := []string{731"file:/etc/hosts",732" file:///etc/hosts\r\n",733" fILe:/../../../../etc/hosts",734ts.URL, // local test server735"fTP://example.com:21\r\n",736"ftp://example.com:21",737"chrome://settings",738" chROme://version",739"chrome-extension://version\r",740" chrOme-EXTension://settings",741"view-source:file:/etc/hosts",742}743744for _, testcase := range testcases {745actions := []*Action{746{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": testcase}},747{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},748}749750data, page, err := instance.Run(contextargs.NewWithInput(context.Background(), ts.URL), actions, nil, &Options{Timeout: 20 * time.Second, Options: opts}) // allow file access in test751require.Error(t, err, "expected error for url %s got %v", testcase, data)752require.True(t, stringsutil.ContainsAny(err.Error(), "net::ERR_ACCESS_DENIED", "failed to parse url", "Cannot navigate to invalid URL", "net::ERR_ABORTED", "net::ERR_INVALID_URL"), "found different error %v for testcases %v", err, testcase)753require.Len(t, data, 0, "expected no data for url %s got %v", testcase, data)754if page != nil {755page.Close()756}757}758}759760761