mirror of https://github.com/daffainfo/nuclei.git
Add/Expose Headless action that waits until an element appears #1096
* add timeout and pollTime optionsdev
parent
88ee74d68a
commit
c0ef419048
|
@ -1,6 +1,7 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/proto"
|
||||
"github.com/go-rod/rod/lib/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/segmentio/ksuid"
|
||||
"github.com/valyala/fasttemplate"
|
||||
|
@ -46,7 +48,7 @@ func (p *Page) ExecuteActions(baseURL *url.URL, actions []*Action) (map[string]s
|
|||
case ActionGetResource:
|
||||
err = p.GetResource(act, outData)
|
||||
case ActionExtract:
|
||||
err = p.SelectInputElement(act, outData)
|
||||
err = p.ExtractElement(act, outData)
|
||||
case ActionWaitEvent:
|
||||
err = p.WaitEvent(act, outData)
|
||||
case ActionFilesInput:
|
||||
|
@ -85,18 +87,83 @@ type requestRule struct {
|
|||
Args map[string]string
|
||||
}
|
||||
|
||||
const elementDidNotAppearMessage = "Element did not appear in the given amount of time"
|
||||
|
||||
// WaitVisible waits until an element appears.
|
||||
func (p *Page) WaitVisible(act *Action, out map[string]string) error {
|
||||
element, err := p.pageElementBy(act.Data)
|
||||
timeout, err := getTimeout(act)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not find element")
|
||||
return errors.Wrap(err, "Wrong timeout given")
|
||||
}
|
||||
if err = element.WaitVisible(); err != nil {
|
||||
return errors.Wrap(err, "could not wait element")
|
||||
|
||||
pollTime, err := getPollTime(act)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Wrong polling time given")
|
||||
}
|
||||
|
||||
element, _ := p.Sleeper(pollTime, timeout).
|
||||
Timeout(timeout).
|
||||
pageElementBy(act.Data)
|
||||
|
||||
if element != nil {
|
||||
if err := element.WaitVisible(); err != nil {
|
||||
return errors.Wrap(err, elementDidNotAppearMessage)
|
||||
}
|
||||
} else {
|
||||
return errors.New(elementDidNotAppearMessage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Page) Sleeper(pollTimeout, timeout time.Duration) *Page {
|
||||
page := *p
|
||||
page.page = page.Page().Sleeper(func() utils.Sleeper {
|
||||
return createBackOffSleeper(pollTimeout, timeout)
|
||||
})
|
||||
return &page
|
||||
}
|
||||
|
||||
func (p *Page) Timeout(timeout time.Duration) *Page {
|
||||
page := *p
|
||||
page.page = page.Page().Timeout(timeout)
|
||||
return &page
|
||||
}
|
||||
|
||||
func createBackOffSleeper(pollTimeout, timeout time.Duration) utils.Sleeper {
|
||||
backoffSleeper := utils.BackoffSleeper(pollTimeout, timeout, func(duration time.Duration) time.Duration {
|
||||
return duration
|
||||
})
|
||||
|
||||
return func(ctx context.Context) error {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return backoffSleeper(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func getTimeout(act *Action) (time.Duration, error) {
|
||||
return geTimeParameter(act, "timeout", 3, time.Second)
|
||||
}
|
||||
|
||||
func getPollTime(act *Action) (time.Duration, error) {
|
||||
return geTimeParameter(act, "pollTime", 100, time.Millisecond)
|
||||
}
|
||||
|
||||
func geTimeParameter(act *Action, parameterName string, defaultValue time.Duration, duration time.Duration) (time.Duration, error) {
|
||||
pollTimeString := act.GetArg(parameterName)
|
||||
if pollTimeString == "" {
|
||||
return defaultValue * duration, nil
|
||||
}
|
||||
timeout, err := strconv.Atoi(pollTimeString)
|
||||
if err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
return time.Duration(timeout) * duration, nil
|
||||
}
|
||||
|
||||
// ActionAddHeader executes a AddHeader action.
|
||||
func (p *Page) ActionAddHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
|
||||
in := act.GetArg("part")
|
||||
|
|
|
@ -9,9 +9,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionNavigate(t *testing.T) {
|
||||
|
@ -281,17 +282,24 @@ func TestActionHeadersChange(t *testing.T) {
|
|||
|
||||
func TestActionWaitVisible(t *testing.T) {
|
||||
t.Run("wait for an element being visible", func(t *testing.T) {
|
||||
_ = protocolstate.Init(&types.Options{})
|
||||
testWaitVisible(t, 2*time.Second, func(page *Page, err error) {
|
||||
require.Nil(t, err, "could not run page actions")
|
||||
|
||||
browser, err := New(&types.Options{ShowBrowser: false})
|
||||
require.Nil(t, err, "could not create browser")
|
||||
defer browser.Close()
|
||||
page.Page().MustElement("button").MustVisible()
|
||||
page.Close()
|
||||
})
|
||||
})
|
||||
|
||||
instance, err := browser.NewInstance()
|
||||
require.Nil(t, err, "could not create browser instance")
|
||||
t.Run("timeout because of element not visible", func(t *testing.T) {
|
||||
testWaitVisible(t, time.Second/2, func(page *Page, err error) {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "Element did not appear in the given amount of time")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `
|
||||
func testWaitVisible(t *testing.T, timeout time.Duration, assert func(page *Page, err error)) {
|
||||
response := `
|
||||
<html>
|
||||
<head>
|
||||
<title>Nuclei Test Page</title>
|
||||
|
@ -300,25 +308,8 @@ func TestActionWaitVisible(t *testing.T) {
|
|||
<script>
|
||||
setTimeout(() => document.querySelector('#test').style.display = '', 1000);
|
||||
</script>
|
||||
</html>`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
</html>`
|
||||
|
||||
parsed, err := url.Parse(ts.URL)
|
||||
require.Nil(t, err, "could not parse URL")
|
||||
|
||||
actions := []*Action{
|
||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||
{ActionType: "waitvisible", Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}},
|
||||
}
|
||||
_, page, err := instance.Run(parsed, actions, 20*time.Second)
|
||||
require.Nil(t, err, "could not run page actions")
|
||||
defer page.Close()
|
||||
|
||||
page.Page().MustElement("button").MustVisible()
|
||||
})
|
||||
|
||||
t.Run("timeout because of element not visible", func(t *testing.T) {
|
||||
_ = protocolstate.Init(&types.Options{})
|
||||
|
||||
browser, err := New(&types.Options{ShowBrowser: false})
|
||||
|
@ -329,13 +320,7 @@ func TestActionWaitVisible(t *testing.T) {
|
|||
require.Nil(t, err, "could not create browser instance")
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `
|
||||
<html>
|
||||
<head>
|
||||
<title>Nuclei Test Page</title>
|
||||
</head>
|
||||
<button style="display:none" id="test">Wait for me!</button>
|
||||
</html>`)
|
||||
fmt.Fprintln(w, response)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
|
@ -346,8 +331,6 @@ func TestActionWaitVisible(t *testing.T) {
|
|||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||
{ActionType: "waitvisible", Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}},
|
||||
}
|
||||
_, _, err = instance.Run(parsed, actions, 2*time.Second)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "could not wait element")
|
||||
})
|
||||
_, page, err := instance.Run(parsed, actions, timeout)
|
||||
assert(page, err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue