From 25177935ac72fe3a1eaea3faff93075ad3f0d94e Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Fri, 3 Feb 2017 02:15:54 +0100 Subject: [PATCH] [enh] add request history --- wuzz.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 154 insertions(+), 27 deletions(-) diff --git a/wuzz.go b/wuzz.go index c2fbe07..5e4e22d 100644 --- a/wuzz.go +++ b/wuzz.go @@ -35,15 +35,21 @@ var VIEWS []string = []string{ "search", } -type RequestParam struct { - Key string - Value string +type Request struct { + Url string + Method string + GetParams string + Data string + Headers string + ResponseHeaders string + RawResponseBody []byte + ContentType string } type App struct { - viewIndex int - rawResponseBody []byte - contentType string + viewIndex int + historyIndex int + history []*Request } type SearchEditor struct { @@ -141,8 +147,6 @@ func (a *App) Layout(g *gocui.Gui) error { v.Editor = &SearchEditor{a, g} v.Wrap = true } - - g.SetCurrentView(VIEWS[a.viewIndex]) return nil } @@ -183,11 +187,13 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { vrh.Clear() popup(g, "Sending request..") - go func(g *gocui.Gui) error { + var r *Request = &Request{} + + go func(g *gocui.Gui, a *App, r *Request) error { defer g.DeleteView("popup") // parse url - base_url := getViewValue(g, "url") - u, err := url.Parse(base_url) + r.Url = getViewValue(g, "url") + u, err := url.Parse(r.Url) if err != nil { g.Execute(func(g *gocui.Gui) error { vrb, _ := g.View("response-body") @@ -197,18 +203,20 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { return nil } u.RawQuery = strings.Replace(getViewValue(g, "get"), "\n", "&", -1) + r.GetParams = u.RawQuery // parse method - method := getViewValue(g, "method") + r.Method = getViewValue(g, "method") // parse POST/PUT data data := bytes.NewBufferString("") - if method == "POST" || method == "PUT" { - data.WriteString(strings.Replace(getViewValue(g, "data"), "\n", "&", -1)) + r.Data = strings.Replace(getViewValue(g, "data"), "\n", "&", -1) + if r.Method == "POST" || r.Method == "PUT" { + data.WriteString(r.Data) } // create request - req, err := http.NewRequest(method, u.String(), data) + req, err := http.NewRequest(r.Method, u.String(), data) if err != nil { g.Execute(func(g *gocui.Gui) error { vrb, _ := g.View("response-body") @@ -220,7 +228,8 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { // set headers req.Header.Set("User-Agent", "") - headers := strings.Split(getViewValue(g, "headers"), "\n") + r.Headers = getViewValue(g, "headers") + headers := strings.Split(r.Headers, "\n") for _, header := range headers { header_parts := strings.SplitN(header, ": ", 2) if len(header_parts) != 2 { @@ -241,8 +250,8 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { } defer response.Body.Close() - // print body - a.contentType = response.Header.Get("Content-Type") + // extract body + r.ContentType = response.Header.Get("Content-Type") if response.Header.Get("Content-Encoding") == "gzip" { reader, err := gzip.NewReader(response.Body) if err == nil { @@ -260,9 +269,14 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { bodyBytes, err := ioutil.ReadAll(response.Body) if err == nil { - a.rawResponseBody = bodyBytes + r.RawResponseBody = bodyBytes } + // add to history + a.history = append(a.history, r) + a.historyIndex = len(a.history) - 1 + + // render response g.Execute(func(g *gocui.Gui) error { vrh, _ := g.View("response-headers") @@ -278,31 +292,37 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { if response.StatusCode != 200 { status_color = 31 } - fmt.Fprintf(vrh, + header_str := fmt.Sprintf( "\x1b[0;%dmHTTP/1.1 %v %v\x1b[0;0m\n", status_color, response.StatusCode, http.StatusText(response.StatusCode), ) for _, hname := range hkeys { - fmt.Fprintf(vrh, "\x1b[0;33m%v:\x1b[0;0m %v\n", hname, strings.Join(response.Header[hname], ",")) + header_str += fmt.Sprintf("\x1b[0;33m%v:\x1b[0;0m %v\n", hname, strings.Join(response.Header[hname], ",")) } + fmt.Fprint(vrh, header_str) + r.ResponseHeaders = header_str return nil }) return nil - }(g) + }(g, a, r) return nil } func (a *App) PrintBody(g *gocui.Gui) { g.Execute(func(g *gocui.Gui) error { - if a.rawResponseBody == nil { + if len(a.history) == 0 { + return nil + } + req := a.history[a.historyIndex] + if req.RawResponseBody == nil { return nil } vrb, _ := g.View("response-body") vrb.Clear() - if strings.Index(a.contentType, "text") == -1 && strings.Index(a.contentType, "application") == -1 { + if strings.Index(req.ContentType, "text") == -1 && strings.Index(req.ContentType, "application") == -1 { vrb.Title = "Response body" fmt.Fprint(vrb, "[binary content]") return nil @@ -310,7 +330,7 @@ func (a *App) PrintBody(g *gocui.Gui) { search_text := getViewValue(g, "search") if search_text == "" { vrb.Title = "Response body" - vrb.Write(a.rawResponseBody) + vrb.Write(req.RawResponseBody) return nil } search_re, err := regexp.Compile(search_text) @@ -318,7 +338,7 @@ func (a *App) PrintBody(g *gocui.Gui) { fmt.Fprint(vrb, "Error: invalid search regexp") return nil } - results := search_re.FindAll(a.rawResponseBody, 1000) + results := search_re.FindAll(req.RawResponseBody, 1000) if len(results) == 0 { vrb.Title = "No results" fmt.Fprint(vrb, "Error: no results") @@ -342,6 +362,8 @@ func (a *App) SetKeys(g *gocui.Gui) { g.SetKeybinding("", gocui.KeyCtrlJ, gocui.ModNone, a.NextView) g.SetKeybinding("", gocui.KeyCtrlK, gocui.ModNone, a.PrevView) + g.SetKeybinding("", gocui.KeyCtrlH, gocui.ModNone, a.ToggleHistory) + g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, a.SubmitRequest) g.SetKeybinding("url", gocui.KeyEnter, gocui.ModNone, a.SubmitRequest) @@ -360,10 +382,114 @@ func (a *App) SetKeys(g *gocui.Gui) { return nil }) } + + // history keybindings + g.SetKeybinding("history", gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + cx, cy := v.Cursor() + v.SetCursor(cx, cy+1) + return nil + }) + g.SetKeybinding("history", gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + cx, cy := v.Cursor() + if cy > 0 { + cy -= 1 + } + v.SetCursor(cx, cy) + return nil + }) + g.SetKeybinding("history", gocui.KeyEnter, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + _, cy := v.Cursor() + // TODO error + if len(a.history) <= cy { + return nil + } + a.restoreRequest(g, cy) + return nil + }) +} + +func (a *App) closeHistory(g *gocui.Gui) { + _, err := g.View("history") + if err == nil { + g.DeleteView("history") + g.SetCurrentView(VIEWS[a.viewIndex%len(VIEWS)]) + g.Cursor = true + } +} + +func (a *App) ToggleHistory(g *gocui.Gui, _ *gocui.View) error { + _, err := g.View("history") + if err == nil { + a.closeHistory(g) + return nil + } + g.Cursor = false + var history *gocui.View + maxX, maxY := g.Size() + height := len(a.history) + if height > maxY-1 { + height = maxY - 1 + } + width := 100 + if width > maxX-2 { + width = maxX - 2 + } + if history, err = g.SetView("history", maxX/2-width/2-1, maxY/2-height/2-1, maxX/2+width/2+1, maxY/2+height/2+1); err != nil { + if err != gocui.ErrUnknownView { + return nil + } + history.Wrap = false + history.Frame = true + history.Title = "History" + history.Highlight = true + history.SelFgColor = gocui.ColorYellow + if len(a.history) == 0 { + setViewTextAndCursor(history, "[!] No items in history") + return nil + } + for i, h := range a.history { + fmt.Fprintf(history, "[%02d] %v\n", i, h.Url) + } + g.SetViewOnTop("history") + g.SetCurrentView("history") + history.SetCursor(0, a.historyIndex) + } + return nil +} + +func (a *App) restoreRequest(g *gocui.Gui, idx int) { + if idx < 0 || idx >= len(a.history) { + return + } + a.closeHistory(g) + a.historyIndex = idx + r := a.history[idx] + + v, _ := g.View("url") + setViewTextAndCursor(v, r.Url) + + v, _ = g.View("method") + setViewTextAndCursor(v, r.Method) + + v, _ = g.View("get") + setViewTextAndCursor(v, r.GetParams) + + v, _ = g.View("data") + setViewTextAndCursor(v, r.Data) + + v, _ = g.View("headers") + setViewTextAndCursor(v, r.Headers) + + v, _ = g.View("response-headers") + setViewTextAndCursor(v, r.ResponseHeaders) + + a.PrintBody(g) + } func (a *App) ParseArgs(g *gocui.Gui) error { a.Layout(g) + g.SetCurrentView(VIEWS[a.viewIndex]) vheader, _ := g.View("headers") vheader.Clear() vget, _ := g.View("get") @@ -425,7 +551,7 @@ func createApp(g *gocui.Gui) *App { g.Cursor = true g.BgColor = gocui.ColorDefault g.FgColor = gocui.ColorDefault - a := &App{} + a := &App{history: make([]*Request, 0, 15)} a.SetKeys(g) return a } @@ -481,6 +607,7 @@ Key bindings: ctrl+r Send request tab, ctrl+j Next window ctrl+k Previous window + ctrl+h Show history pageUp Scroll up the current window pageDown Scroll down the current window`, )