1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
// Package ipc is the protocol between mitmuxd (which owns the proxy and
// the history database) and a client such as the TUI, spoken as
// newline-agnostic JSON messages over a Unix domain socket. This keeps
// the proxy engine running independently of any UI attached to it.
package ipc
import (
"encoding/json"
"errors"
"fmt"
"net"
"mitmux/internal/store"
)
// Request is sent by a client to the daemon.
type Request struct {
Type string `json:"type"` // "list", "get", or "subscribe"
Limit int `json:"limit,omitempty"`
BeforeID int64 `json:"before_id,omitempty"`
ID int64 `json:"id,omitempty"`
}
// Response is sent by the daemon to a client.
type Response struct {
Type string `json:"type"` // "list", "get", "new", or "error"
Entries []store.Summary `json:"entries,omitempty"` // for "list"
Detail *EntryDetail `json:"detail,omitempty"` // for "get"
New *store.Summary `json:"new,omitempty"` // for "new" (subscribe push)
Error string `json:"error,omitempty"`
}
// EntryDetail is a full history entry, raw bytes included.
type EntryDetail struct {
store.Summary
RequestRaw []byte `json:"request_raw"`
ResponseRaw []byte `json:"response_raw"`
RequestExact bool `json:"request_exact"`
ResponseExact bool `json:"response_exact"`
}
// Client talks to a mitmuxd instance for request/response queries
// (list, get). Use Subscribe separately for the live-update stream.
type Client struct {
conn net.Conn
dec *json.Decoder
enc *json.Encoder
}
// Dial connects to the daemon's control socket at path.
func Dial(path string) (*Client, error) {
conn, err := net.Dial("unix", path)
if err != nil {
return nil, fmt.Errorf("dial %s: %w", path, err)
}
return &Client{conn: conn, dec: json.NewDecoder(conn), enc: json.NewEncoder(conn)}, nil
}
// Close closes the connection to the daemon.
func (c *Client) Close() error {
return c.conn.Close()
}
// List returns up to limit history summaries older than beforeID (0 for
// the most recent), newest first.
func (c *Client) List(limit int, beforeID int64) ([]store.Summary, error) {
if err := c.enc.Encode(Request{Type: "list", Limit: limit, BeforeID: beforeID}); err != nil {
return nil, err
}
var resp Response
if err := c.dec.Decode(&resp); err != nil {
return nil, err
}
if resp.Type == "error" {
return nil, errors.New(resp.Error)
}
return resp.Entries, nil
}
// Get returns the full entry (raw bytes included) for id.
func (c *Client) Get(id int64) (*EntryDetail, error) {
if err := c.enc.Encode(Request{Type: "get", ID: id}); err != nil {
return nil, err
}
var resp Response
if err := c.dec.Decode(&resp); err != nil {
return nil, err
}
if resp.Type == "error" {
return nil, errors.New(resp.Error)
}
return resp.Detail, nil
}
// Subscribe opens a dedicated connection that streams newly captured
// history entries as they happen. The returned channel is closed when
// the connection ends; call the returned close func to stop early.
func Subscribe(path string) (<-chan store.Summary, func() error, error) {
conn, err := net.Dial("unix", path)
if err != nil {
return nil, nil, fmt.Errorf("dial %s: %w", path, err)
}
if err := json.NewEncoder(conn).Encode(Request{Type: "subscribe"}); err != nil {
conn.Close()
return nil, nil, err
}
ch := make(chan store.Summary, 64)
go func() {
defer close(ch)
dec := json.NewDecoder(conn)
for {
var resp Response
if err := dec.Decode(&resp); err != nil {
return
}
if resp.Type == "new" && resp.New != nil {
select {
case ch <- *resp.New:
default:
}
}
}
}()
return ch, conn.Close, nil
}
|