diff options
Diffstat (limited to 'internal/ipc')
| -rw-r--r-- | internal/ipc/ipc.go | 46 | ||||
| -rw-r--r-- | internal/ipc/server.go | 25 |
2 files changed, 70 insertions, 1 deletions
diff --git a/internal/ipc/ipc.go b/internal/ipc/ipc.go index dc7a1df..f5d5452 100644 --- a/internal/ipc/ipc.go +++ b/internal/ipc/ipc.go @@ -77,6 +77,18 @@ type Request struct { ClientCert *clientcert.Cert `json:"client_cert,omitempty"` ClientCertID int64 `json:"client_cert_id,omitempty"` + // For "tag_entry": ID identifies the history entry (same field + // "get"/"set_flagged"/"delete_entry" use). Plugin names who's + // tagging it - informational only, not an identity or auth + // mechanism, since anything that can reach the socket can claim any + // name. Tag is the short marker itself (e.g. "jwt", "authz-bypass"). + // Data is an opaque, plugin-defined JSON blob a panel view renders + // later without needing this plugin still connected - empty is + // fine for a plugin that only needs the tag itself, no extra detail. + TagPlugin string `json:"tag_plugin,omitempty"` + Tag string `json:"tag,omitempty"` + TagData string `json:"tag_data,omitempty"` + // For "set_flagged" and "delete_entry": ID identifies the history // entry. "clear_history" needs no fields at all. Flagged bool `json:"flagged,omitempty"` @@ -121,6 +133,9 @@ type Response struct { // per-entry insert failure is skipped, not fatal to the batch). Imported int `json:"imported,omitempty"` + // For "tag_entry": the new tag row's assigned ID. + TagID int64 `json:"tag_id,omitempty"` + // For "intrude_result": one completed attack request. IntrudeResult *IntrudeResultMsg `json:"intrude_result,omitempty"` @@ -164,6 +179,12 @@ type EntryDetail struct { // a false *Exact. RequestTruncated bool `json:"request_truncated,omitempty"` ResponseTruncated bool `json:"response_truncated,omitempty"` + // Tags is every plugin-contributed marker on this entry - see the + // "tag_entry" request. Summary.Tags (from List/Search) is just the + // comma-joined names for a compact list-view badge; this is the + // full record, including each tag's plugin and opaque Data blob, for + // a panel view to render. + Tags []store.EntryTag `json:"tags,omitempty"` } // Client talks to a mitmuxd instance for request/response queries @@ -199,6 +220,31 @@ func (c *Client) Close() error { // SetFlagged sets the flagged marker on a history entry - a simple // "mark this, revisit later" bit, filterable via flagged:true/false in // Search. +// TagEntry marks history entry id with tag, attributed to plugin (any +// non-empty name a plugin chooses to identify itself by - informational +// only), with an optional opaque data blob a panel view can render +// later. This is the core plugin-integration primitive: any process +// that can reach the control socket - the reference Go client here, or +// a plugin in any other language following the same JSON wire protocol +// (see PLAN.md) - can tag entries it finds interesting without mitmux +// needing to know anything about it in advance. Returns the new tag's +// assigned ID. +func (c *Client) TagEntry(entryID int64, plugin, tag, data string) (int64, error) { + c.mu.Lock() + defer c.mu.Unlock() + if err := c.enc.Encode(Request{Type: "tag_entry", ID: entryID, TagPlugin: plugin, Tag: tag, TagData: data}); err != nil { + return 0, err + } + var resp Response + if err := c.dec.Decode(&resp); err != nil { + return 0, err + } + if resp.Type == "error" { + return 0, errors.New(resp.Error) + } + return resp.TagID, nil +} + func (c *Client) SetFlagged(id int64, flagged bool) error { c.mu.Lock() defer c.mu.Unlock() diff --git a/internal/ipc/server.go b/internal/ipc/server.go index 378c1a9..98c2c22 100644 --- a/internal/ipc/server.go +++ b/internal/ipc/server.go @@ -141,7 +141,13 @@ func (s *Server) handleConn(conn net.Conn) { enc.Encode(Response{Type: "error", Error: err.Error()}) continue } - enc.Encode(Response{Type: "get", Detail: detailFromEntry(e)}) + detail := detailFromEntry(e) + if tags, err := s.db.ListEntryTags(req.ID); err != nil { + log.Printf("list entry tags for #%d: %v", req.ID, err) + } else { + detail.Tags = tags + } + enc.Encode(Response{Type: "get", Detail: detail}) case "ws_messages": msgs, err := s.db.ListWSMessages(req.ID) @@ -220,6 +226,23 @@ func (s *Server) handleConn(conn net.Conn) { } enc.Encode(Response{Type: "intrude_done"}) + case "tag_entry": + if req.Tag == "" { + enc.Encode(Response{Type: "error", Error: "tag_entry: missing tag"}) + continue + } + id, err := s.db.AddEntryTag(store.EntryTag{ + EntryID: req.ID, + Plugin: req.TagPlugin, + Tag: req.Tag, + Data: req.TagData, + }) + if err != nil { + enc.Encode(Response{Type: "error", Error: err.Error()}) + continue + } + enc.Encode(Response{Type: "tag_entry", TagID: id}) + case "set_flagged": if err := s.db.SetFlagged(req.ID, req.Flagged); err != nil { enc.Encode(Response{Type: "error", Error: err.Error()}) |