Skip to content

Commit

Permalink
Add method and body widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
nakabonne committed Sep 14, 2020
1 parent 28b8a34 commit e2b1d39
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 70 deletions.
2 changes: 1 addition & 1 deletion attacker/attacker.go
Expand Up @@ -10,7 +10,7 @@ import (

const (
defaultRate = 50
defaultDuration = 30 * time.Second
defaultDuration = 10 * time.Second
defaultMethod = http.MethodGet
)

Expand Down
125 changes: 74 additions & 51 deletions gui/gui.go
Expand Up @@ -3,6 +3,7 @@ package gui
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
Expand Down Expand Up @@ -59,66 +60,23 @@ func Run() error {
return termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(k), termdash.RedrawInterval(redrawInterval))
}

func keybinds(ctx context.Context, cancel context.CancelFunc, dr *drawer) func(*terminalapi.Keyboard) {
return func(k *terminalapi.Keyboard) {
switch k.Key {
case keyboard.KeyCtrlC: // Quit
cancel()
case keyboard.KeyEnter: // Attack
if dr.chartDrawing {
return
}
var (
target string
rate int
duration time.Duration
err error
)
target = dr.widgets.urlInput.Read()
if _, err := url.ParseRequestURI(target); err != nil {
dr.reportCh <- fmt.Sprintf("Bad URL: %v", err)
return
}
if s := dr.widgets.rateLimitInput.Read(); s != "" {
rate, err = strconv.Atoi(s)
if err != nil {
dr.reportCh <- fmt.Sprintf("Given rate limit %q isn't integer: %v", s, err)
return
}
}
if s := dr.widgets.durationInput.Read(); s != "" {
duration, err = time.ParseDuration(s)
if err != nil {
dr.reportCh <- fmt.Sprintf("Unparseable duration %q: %v", s, err)
return
}
}
requestNum := rate * int(duration/time.Second)
// To pre-allocate, run redrawChart on a per-attack basis.
go dr.redrawChart(ctx, requestNum)
go func(ctx context.Context, d *drawer, t string, r int, du time.Duration) {
metrics := attacker.Attack(ctx, t, d.chartsCh, attacker.Options{Rate: r, Duration: du})
d.reportCh <- metrics.String()
d.chartsCh <- &attacker.Result{End: true}
}(ctx, dr, target, rate, duration)
}
}
}

func gridLayout(w *widgets) ([]container.Option, error) {
raw1 := grid.RowHeightPerc(60,
grid.ColWidthPerc(99, grid.Widget(w.latencyChart, container.Border(linestyle.Light), container.BorderTitle("Latency (ms)"))),
)
raw2 := grid.RowHeightPerc(36,
//grid.ColWidthPerc(50, grid.Widget(w.urlInput, container.Border(linestyle.Light), container.BorderTitle("Input"))),
grid.ColWidthPerc(50,
grid.RowHeightPerc(20, grid.Widget(w.urlInput, container.Border(linestyle.None))),
grid.RowHeightPerc(20,
grid.ColWidthPerc(30,
grid.RowHeightPerc(33, grid.Widget(w.urlInput, container.Border(linestyle.None))),
grid.RowHeightPerc(33,
grid.ColWidthPerc(50, grid.Widget(w.rateLimitInput, container.Border(linestyle.None))),
grid.ColWidthPerc(49, grid.Widget(w.durationInput, container.Border(linestyle.None))),
),
grid.RowHeightPerc(32,
grid.ColWidthPerc(50, grid.Widget(w.methodInput, container.Border(linestyle.None))),
grid.ColWidthPerc(49, grid.Widget(w.bodyInput, container.Border(linestyle.None))),
),
),
grid.ColWidthPerc(49, grid.Widget(w.reportText, container.Border(linestyle.Light), container.BorderTitle("Report"))),
grid.ColWidthPerc(69, grid.Widget(w.reportText, container.Border(linestyle.Light), container.BorderTitle("Report"))),
)
raw3 := grid.RowHeightFixed(1,
grid.ColWidthFixed(100, grid.Widget(w.navi, container.Border(linestyle.Light))),
Expand All @@ -133,3 +91,68 @@ func gridLayout(w *widgets) ([]container.Option, error) {

return builder.Build()
}

func keybinds(ctx context.Context, cancel context.CancelFunc, dr *drawer) func(*terminalapi.Keyboard) {
return func(k *terminalapi.Keyboard) {
switch k.Key {
case keyboard.KeyCtrlC: // Quit
cancel()
case keyboard.KeyEnter: // Attack
attack(ctx, dr)
}
}
}

func attack(ctx context.Context, dr *drawer) {
if dr.chartDrawing {
return
}
var (
target string
rate int
duration time.Duration
method string
err error
)
target = dr.widgets.urlInput.Read()
if _, err := url.ParseRequestURI(target); err != nil {
dr.reportCh <- fmt.Sprintf("Bad URL: %v", err)
return
}
if s := dr.widgets.rateLimitInput.Read(); s != "" {
rate, err = strconv.Atoi(s)
if err != nil {
dr.reportCh <- fmt.Sprintf("Given rate limit %q isn't integer: %v", s, err)
return
}
}
if s := dr.widgets.durationInput.Read(); s != "" {
duration, err = time.ParseDuration(s)
if err != nil {
dr.reportCh <- fmt.Sprintf("Unparseable duration %q: %v", s, err)
return
}
}
if method = dr.widgets.methodInput.Read(); method != "" {
if !validateMethod(method) {
dr.reportCh <- fmt.Sprintf("Given method %q isn't an HTTP request method", method)
return
}
}
requestNum := rate * int(duration/time.Second)
// To pre-allocate, run redrawChart on a per-attack basis.
go dr.redrawChart(ctx, requestNum)
go func(ctx context.Context, d *drawer, t string, r int, du time.Duration) {
metrics := attacker.Attack(ctx, t, d.chartsCh, attacker.Options{Rate: r, Duration: du, Method: method})
d.reportCh <- metrics.String()
d.chartsCh <- &attacker.Result{End: true}
}(ctx, dr, target, rate, duration)
}

func validateMethod(method string) bool {
switch method {
case http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace:
return true
}
return false
}
33 changes: 15 additions & 18 deletions gui/widgets.go
Expand Up @@ -4,8 +4,6 @@ import (
"time"

"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/keyboard"
"github.com/mum4k/termdash/widgets/button"
"github.com/mum4k/termdash/widgets/linechart"
"github.com/mum4k/termdash/widgets/text"
"github.com/mum4k/termdash/widgets/textinput"
Expand All @@ -20,6 +18,8 @@ type widgets struct {
urlInput *textinput.TextInput
rateLimitInput *textinput.TextInput
durationInput *textinput.TextInput
methodInput *textinput.TextInput
bodyInput *textinput.TextInput
latencyChart *linechart.LineChart
reportText *text.Text
navi *text.Text
Expand All @@ -38,28 +38,32 @@ func newWidgets() (*widgets, error) {
if err != nil {
return nil, err
}
urlInput, err := newTextInput("Target URL: ", "", 100)
urlInput, err := newTextInput("Target URL: ", "", 60)
if err != nil {
return nil, err
}
rateLimitInput, err := newTextInput("Rate limit: ", "50", 50)
rateLimitInput, err := newTextInput("Rate limit: ", "50", 30)
if err != nil {
return nil, err
}
durationInput, err := newTextInput("Duration: ", "30s", 50)
durationInput, err := newTextInput("Duration: ", "10s", 30)
if err != nil {
return nil, err
}
methodInput, err := newTextInput("Method: ", "GET", 30)
if err != nil {
return nil, err
}
bodyInput, err := newTextInput("Body: ", "", 30)
if err != nil {
return nil, err
}
/*attackButton, err := newButton("Attack", func() error {
target := urlInput.Read()
pp.Println(target)
// TODO: Call Attack.
return nil
})*/
return &widgets{
urlInput: urlInput,
rateLimitInput: rateLimitInput,
durationInput: durationInput,
methodInput: methodInput,
bodyInput: bodyInput,
latencyChart: latencyChart,
reportText: reportText,
navi: navi,
Expand All @@ -85,13 +89,6 @@ func newRollText(s string) (*text.Text, error) {
return t, nil
}

func newButton(text string, onSubmit func() error) (*button.Button, error) {
return button.New(text, onSubmit,
button.GlobalKey(keyboard.KeyEnter),
button.FillColor(cell.ColorNumber(196)),
)
}

func newTextInput(label, placeHolder string, cells int) (*textinput.TextInput, error) {
return textinput.New(
textinput.Label(label, cell.FgColor(cell.ColorWhite)),
Expand Down

0 comments on commit e2b1d39

Please sign in to comment.