srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/mitmux/main.go223
1 files changed, 221 insertions, 2 deletions
diff --git a/cmd/mitmux/main.go b/cmd/mitmux/main.go
index 467f0d5..21209bf 100644
--- a/cmd/mitmux/main.go
+++ b/cmd/mitmux/main.go
@@ -21,6 +21,7 @@ import (
"mitmux/internal/ca"
"mitmux/internal/ipc"
"mitmux/internal/rules"
+ "mitmux/internal/scope"
"mitmux/internal/store"
)
@@ -76,6 +77,7 @@ const (
viewIntruder
viewCompare
viewDecoder
+ viewScope
viewHelp
)
@@ -160,6 +162,16 @@ type model struct {
ruleRegex bool
ruleField ruleField
+ // Target scope: which captured traffic gets recorded to history.
+ // Simpler than match-and-replace rules (one pattern + a regex
+ // toggle, no name/scope/replace fields), so add-only - no
+ // edit-in-place, delete and re-add covers changing a pattern.
+ scopeTable table.Model
+ scopeRows []scope.Rule
+ scopeForm bool
+ scopePattern textinput.Model
+ scopeIsRegex bool
+
intruderScheme string
intruderHost string
intruderTemplate viTextarea
@@ -269,6 +281,17 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string)
replaceIn := textinput.New()
replaceIn.Placeholder = "replacement"
+ scopeCols := []table.Column{
+ {Title: "On", Width: 3},
+ {Title: "Pattern", Width: 40},
+ {Title: "Regex", Width: 5},
+ }
+ scopeTbl := table.New(table.WithColumns(scopeCols), table.WithFocused(true))
+ scopeTbl.SetStyles(st)
+
+ scopePatternIn := textinput.New()
+ scopePatternIn.Placeholder = "host substring, or a regex - e.g. example.com"
+
itmpl := newViTextarea()
itmpl.ta.Placeholder = "raw request bytes - wrap positions to fuzz in § markers, e.g. /users/§123§"
itmpl.ta.ShowLineNumbers = false
@@ -311,6 +334,8 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string)
repeaterIndex: -1,
searchInput: si,
rulesTable: rt,
+ scopeTable: scopeTbl,
+ scopePattern: scopePatternIn,
ruleName: nameIn,
ruleMatch: matchIn,
ruleReplace: replaceIn,
@@ -744,6 +769,64 @@ func (m *model) focusRuleField() {
}
}
+type scopeRulesLoadedMsg struct {
+ rules []scope.Rule
+ err error
+}
+
+type scopeRuleWriteDoneMsg struct {
+ action string // "added", "deleted", "toggled" - for the status line
+ err error
+}
+
+func (m *model) loadScopeRules() tea.Msg {
+ rs, err := m.client.ListScopeRules()
+ return scopeRulesLoadedMsg{rules: rs, err: err}
+}
+
+func (m *model) addScopeRule(r scope.Rule) tea.Cmd {
+ return func() tea.Msg {
+ _, err := m.client.AddScopeRule(r)
+ return scopeRuleWriteDoneMsg{action: "added", err: err}
+ }
+}
+
+func (m *model) deleteSelectedScopeRule() tea.Cmd {
+ row := m.scopeTable.Cursor()
+ if row < 0 || row >= len(m.scopeRows) {
+ return nil
+ }
+ id := m.scopeRows[row].ID
+ return func() tea.Msg {
+ err := m.client.DeleteScopeRule(id)
+ return scopeRuleWriteDoneMsg{action: "deleted", err: err}
+ }
+}
+
+func (m *model) toggleSelectedScopeRule() tea.Cmd {
+ row := m.scopeTable.Cursor()
+ if row < 0 || row >= len(m.scopeRows) {
+ return nil
+ }
+ r := m.scopeRows[row]
+ return func() tea.Msg {
+ err := m.client.SetScopeRuleEnabled(r.ID, !r.Enabled)
+ return scopeRuleWriteDoneMsg{action: "toggled", err: err}
+ }
+}
+
+// enterScopeForm opens the (add-only) scope rule form.
+func (m *model) enterScopeForm() {
+ m.scopeForm = true
+ m.scopePattern.SetValue("")
+ m.scopeIsRegex = false
+ m.scopePattern.Focus()
+}
+
+func (m *model) scopeRuleFromForm() scope.Rule {
+ return scope.Rule{Enabled: true, Pattern: m.scopePattern.Value(), IsRegex: m.scopeIsRegex}
+}
+
func (m *model) Init() tea.Cmd {
return tea.Batch(m.loadList, m.waitForEntry, m.loadStatus)
}
@@ -779,6 +862,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ruleMatch.Width = formWidth
m.ruleReplace.Width = formWidth
+ m.scopeTable.SetWidth(msg.Width)
+ m.scopeTable.SetHeight(h - 5)
+ m.scopePattern.Width = formWidth
+
// h-9 rather than h-8: one extra line reserved for the payload
// rules / grep-match-extract status line in intruderView.
itmplHeight := (h - 9) / 3
@@ -937,6 +1024,24 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.statusMsg = "rule " + msg.action
return m, m.loadRules
+ case scopeRulesLoadedMsg:
+ if msg.err != nil {
+ m.statusMsg = "scope error: " + msg.err.Error()
+ return m, nil
+ }
+ m.scopeRows = msg.rules
+ setTableRows(&m.scopeTable, scopeRowsFor(m.scopeRows))
+ return m, nil
+
+ case scopeRuleWriteDoneMsg:
+ if msg.err != nil {
+ m.statusMsg = "scope " + msg.action + " error: " + msg.err.Error()
+ return m, nil
+ }
+ m.scopeForm = false
+ m.statusMsg = "scope rule " + msg.action
+ return m, m.loadScopeRules
+
case intrudeStartedMsg:
if msg.err != nil {
m.intruderRunning = false
@@ -1097,6 +1202,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.mode = viewRules
m.statusMsg = ""
return m, m.loadRules
+ case "s":
+ m.mode = viewScope
+ m.statusMsg = ""
+ return m, m.loadScopeRules
case "esc":
if m.query != "" {
m.query = ""
@@ -1344,6 +1453,49 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.rulesTable, cmd = m.rulesTable.Update(msg)
return m, cmd
+ case viewScope:
+ if m.scopeForm {
+ switch msg.String() {
+ case "esc":
+ m.scopeForm = false
+ return m, nil
+ case "ctrl+c":
+ return m, tea.Quit
+ case "ctrl+s":
+ return m, m.addScopeRule(m.scopeRuleFromForm())
+ case "tab":
+ m.scopeIsRegex = !m.scopeIsRegex
+ return m, nil
+ }
+ var cmd tea.Cmd
+ m.scopePattern, cmd = m.scopePattern.Update(msg)
+ return m, cmd
+ }
+
+ switch msg.String() {
+ case "q", "esc":
+ m.mode = viewList
+ return m, nil
+ case "ctrl+c":
+ return m, tea.Quit
+ case "?":
+ m.prevMode = viewScope
+ m.mode = viewHelp
+ return m, nil
+ case "a":
+ m.enterScopeForm()
+ return m, nil
+ case "d":
+ m.statusMsg = ""
+ return m, m.deleteSelectedScopeRule()
+ case " ":
+ m.statusMsg = ""
+ return m, m.toggleSelectedScopeRule()
+ }
+ var cmd tea.Cmd
+ m.scopeTable, cmd = m.scopeTable.Update(msg)
+ return m, cmd
+
case viewIntruder:
// Editing a grep pattern is a modal overlay on top of the
// normal template/payloads/results panes, same pattern as
@@ -1584,6 +1736,12 @@ func (m *model) View() string {
} else {
body = m.rulesView()
}
+ case viewScope:
+ if m.scopeForm {
+ body = m.scopeFormView()
+ } else {
+ body = m.scopeView()
+ }
case viewIntruder:
body = m.intruderView()
case viewCompare:
@@ -1610,6 +1768,7 @@ func (m *model) statusBar() string {
view := map[viewMode]string{
viewList: "history", viewDetail: "detail", viewRepeater: "repeater",
viewRules: "rules", viewIntruder: "intruder", viewCompare: "comparer", viewDecoder: "decoder",
+ viewScope: "scope",
}[m.mode]
return statusBarStyle.Render(fmt.Sprintf(" mitmux · proxy %s%s · %s ", proxy, count, view))
}
@@ -1648,6 +1807,7 @@ func (m *model) helpView() string {
" source:repeater, flagged:true",
"esc clear active search filter",
"m match-and-replace rules",
+ "s target scope (what gets recorded)",
"q quit",
)
section("Detail view",
@@ -1691,6 +1851,15 @@ func (m *model) helpView() string {
"tab/shift+tab move between form fields ctrl+s save form",
"◀▶ change scope/regex toggle esc cancel/back",
)
+ section("Scope",
+ "What gets recorded to history - out-of-scope traffic still",
+ "proxies normally, it's just not stored. No rules means",
+ "everything is recorded (today's default, unchanged).",
+ "a add rule (pattern + regex toggle)",
+ "d delete selected space toggle enabled",
+ "tab toggle regex (in the add form)",
+ "ctrl+s save form esc cancel/back",
+ )
b.WriteString(helpStyle.Render("press any key to go back"))
return b.String()
@@ -1739,9 +1908,9 @@ func (m *model) listView() string {
b.WriteString(statusStyle.Render(sanitizeLine(m.statusMsg)))
b.WriteString("\n")
}
- help := "enter view · r/i/c/d tools · f flag · x delete · X clear all · E export HAR · / search · m rules · ? help · q quit"
+ help := "enter view · r/i/c/d tools · f flag · x delete · X clear all · E export HAR · / search · m rules · s scope · ? help · q quit"
if m.query != "" {
- help = "enter view · r/i/c/d tools · f flag · x delete · X clear all · E export HAR (this filter) · / search · esc clear filter · ? help · q quit"
+ help = "enter view · r/i/c/d tools · f flag · x delete · X clear all · E export HAR (this filter) · / search · esc clear filter · s scope · ? help · q quit"
}
b.WriteString(helpStyle.Render(help))
return b.String()
@@ -1909,6 +2078,40 @@ func (m *model) ruleFormView() string {
return b.String()
}
+func (m *model) scopeView() string {
+ var b strings.Builder
+ title := fmt.Sprintf(" scope (%d) - no rules means everything is recorded ", len(m.scopeRows))
+ b.WriteString(titleStyle.Render(title))
+ b.WriteString("\n")
+ b.WriteString(m.scopeTable.View())
+ b.WriteString("\n")
+ if m.statusMsg != "" {
+ b.WriteString(statusStyle.Render(sanitizeLine(m.statusMsg)))
+ b.WriteString("\n")
+ }
+ b.WriteString(helpStyle.Render("a add · d delete · space toggle · esc back · q quit"))
+ return b.String()
+}
+
+func (m *model) scopeFormView() string {
+ var b strings.Builder
+ b.WriteString(titleStyle.Render(" add scope rule "))
+ b.WriteString("\n\n")
+ b.WriteString(tabActive.Render("Pattern (substring match, or a regex against the host)") + "\n")
+ b.WriteString(m.scopePattern.View() + "\n\n")
+ regexText := "Regex: off (tab to toggle)"
+ if m.scopeIsRegex {
+ regexText = "Regex: on (tab to toggle)"
+ }
+ b.WriteString(helpStyle.Render(regexText) + "\n\n")
+ if m.statusMsg != "" {
+ b.WriteString(statusStyle.Render(sanitizeLine(m.statusMsg)))
+ b.WriteString("\n")
+ }
+ b.WriteString(helpStyle.Render("tab toggle regex · ctrl+s save · esc cancel · ctrl+c quit"))
+ return b.String()
+}
+
func rulesRowsFor(rs []rules.Rule) []table.Row {
rows := make([]table.Row, len(rs))
for i, r := range rs {
@@ -1925,6 +2128,22 @@ func rulesRowsFor(rs []rules.Rule) []table.Row {
return rows
}
+func scopeRowsFor(rs []scope.Rule) []table.Row {
+ rows := make([]table.Row, len(rs))
+ for i, r := range rs {
+ on := " "
+ if r.Enabled {
+ on = "✓"
+ }
+ regex := ""
+ if r.IsRegex {
+ regex = "yes"
+ }
+ rows[i] = table.Row{on, r.Pattern, regex}
+ }
+ return rows
+}
+
func (m *model) intruderView() string {
var b strings.Builder
title := fmt.Sprintf(" intruder - %s://%s ", sanitizeLine(m.intruderScheme), sanitizeLine(m.intruderHost))