pypproxy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. pypproxy/__init__.py +0 -0
  2. pypproxy/api/__init__.py +0 -0
  3. pypproxy/api/server.py +427 -0
  4. pypproxy/bulk/__init__.py +0 -0
  5. pypproxy/bulk/sender.py +97 -0
  6. pypproxy/cert/__init__.py +0 -0
  7. pypproxy/cert/ca.py +144 -0
  8. pypproxy/cert/client_cert.py +65 -0
  9. pypproxy/codec.py +176 -0
  10. pypproxy/config/__init__.py +0 -0
  11. pypproxy/config/config.py +106 -0
  12. pypproxy/dns/__init__.py +0 -0
  13. pypproxy/dns/server.py +149 -0
  14. pypproxy/exporter/__init__.py +0 -0
  15. pypproxy/exporter/exporter.py +122 -0
  16. pypproxy/exporter/importer.py +169 -0
  17. pypproxy/graphql/__init__.py +0 -0
  18. pypproxy/graphql/detector.py +76 -0
  19. pypproxy/graphql/introspection.py +217 -0
  20. pypproxy/graphql/modifier.py +98 -0
  21. pypproxy/graphql/schema_store.py +33 -0
  22. pypproxy/intercept/__init__.py +0 -0
  23. pypproxy/intercept/manager.py +142 -0
  24. pypproxy/interceptor/__init__.py +0 -0
  25. pypproxy/interceptor/interceptor.py +172 -0
  26. pypproxy/proto/__init__.py +0 -0
  27. pypproxy/proto/grpc.py +48 -0
  28. pypproxy/proto/mqtt.py +119 -0
  29. pypproxy/proto/ws.py +120 -0
  30. pypproxy/proto/ws_intercept.py +117 -0
  31. pypproxy/proxy/__init__.py +0 -0
  32. pypproxy/proxy/proxy.py +407 -0
  33. pypproxy/replay/__init__.py +0 -0
  34. pypproxy/replay/replay.py +77 -0
  35. pypproxy/rule/__init__.py +0 -0
  36. pypproxy/rule/rule.py +198 -0
  37. pypproxy/scan/__init__.py +0 -0
  38. pypproxy/scan/scanner.py +296 -0
  39. pypproxy/script/__init__.py +0 -0
  40. pypproxy/script/engine.py +49 -0
  41. pypproxy/security/__init__.py +0 -0
  42. pypproxy/security/header_checker.py +308 -0
  43. pypproxy/security/int_overflow.py +193 -0
  44. pypproxy/security/jwt_checker.py +273 -0
  45. pypproxy/security/plugin.py +152 -0
  46. pypproxy/security/randomness.py +165 -0
  47. pypproxy/store/__init__.py +0 -0
  48. pypproxy/store/db.py +189 -0
  49. pypproxy/store/filter_parser.py +181 -0
  50. pypproxy/store/fts.py +105 -0
  51. pypproxy/store/models.py +81 -0
  52. pypproxy/store/scope.py +63 -0
  53. pypproxy/store/store.py +120 -0
  54. pypproxy/ui/__init__.py +0 -0
  55. pypproxy/ui/app.py +386 -0
  56. pypproxy/ui/bulk_sender_ui.py +125 -0
  57. pypproxy/ui/cui.py +162 -0
  58. pypproxy/ui/detail.py +179 -0
  59. pypproxy/ui/diff_view.py +118 -0
  60. pypproxy/ui/graphql_tab.py +265 -0
  61. pypproxy/ui/import_tab.py +136 -0
  62. pypproxy/ui/intercept_dialog.py +74 -0
  63. pypproxy/ui/resender.py +140 -0
  64. pypproxy/ui/scan_tab.py +98 -0
  65. pypproxy/ui/security_tab.py +356 -0
  66. pypproxy/ui/settings.py +413 -0
  67. pypproxy/ui/theme.py +59 -0
  68. pypproxy-0.1.0.dist-info/METADATA +19 -0
  69. pypproxy-0.1.0.dist-info/RECORD +72 -0
  70. pypproxy-0.1.0.dist-info/WHEEL +4 -0
  71. pypproxy-0.1.0.dist-info/entry_points.txt +2 -0
  72. pypproxy-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,413 @@
1
+ from __future__ import annotations
2
+
3
+ from nicegui import ui
4
+
5
+ from pypproxy.cert.client_cert import ClientCert, ClientCertManager
6
+ from pypproxy.config.config import Config
7
+ from pypproxy.dns.server import DNSServer
8
+ from pypproxy.rule.rule import RuleManager
9
+
10
+
11
+ def build_settings_page(
12
+ cfg: Config,
13
+ rules: RuleManager,
14
+ cert_mgr: ClientCertManager,
15
+ dns_server: DNSServer | None = None,
16
+ scope_mgr=None,
17
+ ) -> None:
18
+ @ui.page("/settings")
19
+ async def settings() -> None:
20
+ ui.dark_mode().enable()
21
+
22
+ with ui.header().classes("items-center q-px-md gap-4").style("background:#1a1a2e"):
23
+ ui.button(icon="arrow_back", on_click=lambda: ui.navigate.to("/")).props("flat dark")
24
+ ui.label("Settings").classes("text-h6 text-weight-bold")
25
+
26
+ with ui.tabs().props("dense dark").classes("bg-dark") as tabs:
27
+ rules_tab = ui.tab("Rules", icon="rule")
28
+ scope_tab = ui.tab("Scope", icon="target")
29
+ passthrough_tab = ui.tab("SSL Passthrough", icon="lock_open")
30
+ dns_tab = ui.tab("DNS Overwrite", icon="dns")
31
+ ports_tab = ui.tab("Listen Ports", icon="router")
32
+ certs_tab = ui.tab("Client Certs", icon="badge")
33
+
34
+ with (
35
+ ui.tab_panels(tabs, value=rules_tab)
36
+ .classes("w-full")
37
+ .style("height:calc(100vh - 96px)")
38
+ ):
39
+ # --- Rules tab ---
40
+ with ui.tab_panel(rules_tab).classes("q-pa-md"):
41
+ _build_rules_panel(rules)
42
+
43
+ # --- Scope tab ---
44
+ with ui.tab_panel(scope_tab).classes("q-pa-md"):
45
+ _build_scope_panel(scope_mgr)
46
+
47
+ # --- SSL Passthrough tab ---
48
+ with ui.tab_panel(passthrough_tab).classes("q-pa-md"):
49
+ _build_passthrough_panel(cfg)
50
+
51
+ # --- DNS Overwrite tab ---
52
+ with ui.tab_panel(dns_tab).classes("q-pa-md"):
53
+ _build_dns_panel(cfg, dns_server)
54
+
55
+ # --- Listen Ports tab ---
56
+ with ui.tab_panel(ports_tab).classes("q-pa-md"):
57
+ _build_ports_panel(cfg)
58
+
59
+ # --- Client Certs tab ---
60
+ with ui.tab_panel(certs_tab).classes("q-pa-md"):
61
+ _build_certs_panel(cert_mgr)
62
+
63
+
64
+ def _build_scope_panel(scope_mgr) -> None: # noqa: ANN001
65
+ from pypproxy.store.scope import ScopeRule
66
+
67
+ ui.label("Scope").classes("text-subtitle1 q-mb-sm")
68
+ ui.label(
69
+ "When enabled, only in-scope hosts are captured. All others are proxied without recording."
70
+ ).classes("text-caption text-grey q-mb-md")
71
+
72
+ if scope_mgr is None:
73
+ ui.label("Scope manager not initialized.").classes("text-grey")
74
+ return
75
+
76
+ enabled_toggle = ui.switch("Enable scope filtering", value=scope_mgr.enabled).props(
77
+ "dense dark color=primary"
78
+ )
79
+ enabled_toggle.on("update:model-value", lambda e: scope_mgr.set_enabled(e.args))
80
+
81
+ ui.separator().classes("q-my-md")
82
+ container = ui.column().classes("w-full")
83
+
84
+ def _refresh() -> None:
85
+ container.clear()
86
+ with container:
87
+ for rule in scope_mgr.list():
88
+ with ui.row().classes("items-center gap-2"):
89
+ ui.badge(rule.mode, color="grey-7")
90
+ ui.label(rule.pattern).classes("flex-1 font-mono")
91
+ ui.button(
92
+ icon="remove",
93
+ on_click=lambda p=rule.pattern: (scope_mgr.remove(p), _refresh()),
94
+ ).props("flat dense color=negative size=sm")
95
+
96
+ _refresh()
97
+
98
+ with ui.row().classes("gap-2 items-center q-mt-sm"):
99
+ pattern_input = (
100
+ ui.input(label="Pattern (e.g. *.example.com)")
101
+ .props("dense outlined dark")
102
+ .classes("w-64")
103
+ )
104
+ mode_select = (
105
+ ui.select(["glob", "regex"], value="glob", label="Mode")
106
+ .props("dense outlined dark")
107
+ .classes("w-24")
108
+ )
109
+
110
+ def _add() -> None:
111
+ p = pattern_input.value.strip()
112
+ if p:
113
+ scope_mgr.add(ScopeRule(pattern=p, mode=mode_select.value))
114
+ pattern_input.value = ""
115
+ _refresh()
116
+
117
+ ui.button("Add", icon="add", on_click=_add).props("color=primary size=sm")
118
+
119
+
120
+ def _build_rules_panel(rules: RuleManager) -> None:
121
+ ui.label("Intercept Rules").classes("text-subtitle1 q-mb-sm")
122
+ ui.label("Rules are evaluated in priority order. Higher number = higher priority.").classes(
123
+ "text-caption text-grey q-mb-md"
124
+ )
125
+
126
+ rules_container = ui.column().classes("w-full")
127
+
128
+ def _refresh() -> None:
129
+ rules_container.clear()
130
+ with rules_container:
131
+ for rule in rules.list():
132
+ with (
133
+ ui.card().classes("w-full q-mb-xs"),
134
+ ui.row().classes("items-center gap-2 w-full"),
135
+ ):
136
+ ui.switch(value=rule.enabled).on(
137
+ "update:model-value",
138
+ lambda v, r=rule: (setattr(r, "enabled", v.args), rules.update(r)),
139
+ )
140
+ ui.label(rule.name).classes("text-weight-medium flex-1")
141
+ ui.badge(
142
+ rule.action.value,
143
+ color={
144
+ "block": "negative",
145
+ "modify": "warning",
146
+ "redirect": "info",
147
+ "passthrough": "positive",
148
+ }.get(rule.action.value, "grey"),
149
+ )
150
+ ui.label(f"p={rule.priority}").classes("text-caption text-grey")
151
+ ui.button(
152
+ icon="delete",
153
+ on_click=lambda r=rule: (rules.delete(r.id), _refresh()),
154
+ ).props("flat dense color=negative size=sm")
155
+
156
+ _refresh()
157
+
158
+ ui.separator().classes("q-my-md")
159
+ ui.label("Add Rule").classes("text-subtitle2")
160
+ with ui.row().classes("gap-2 items-end q-mt-sm"):
161
+ name_input = ui.input(label="Name").props("dense outlined dark").classes("w-48")
162
+ action_select = (
163
+ ui.select(
164
+ ["block", "passthrough", "modify", "redirect"],
165
+ value="block",
166
+ label="Action",
167
+ )
168
+ .props("dense outlined dark")
169
+ .classes("w-32")
170
+ )
171
+ priority_input = (
172
+ ui.number(label="Priority", value=0).props("dense outlined dark").classes("w-24")
173
+ )
174
+ field_select = (
175
+ ui.select(
176
+ ["host", "path", "method", "header", "body"],
177
+ value="host",
178
+ label="Field",
179
+ )
180
+ .props("dense outlined dark")
181
+ .classes("w-24")
182
+ )
183
+ op_select = (
184
+ ui.select(
185
+ ["contains", "equals", "prefix", "regex"],
186
+ value="contains",
187
+ label="Op",
188
+ )
189
+ .props("dense outlined dark")
190
+ .classes("w-28")
191
+ )
192
+ value_input = ui.input(label="Value").props("dense outlined dark").classes("w-48")
193
+
194
+ def _add() -> None:
195
+ from pypproxy.rule.rule import Condition, MatchField, Rule
196
+
197
+ if not name_input.value or not value_input.value:
198
+ ui.notify("Name and Value are required", type="warning")
199
+ return
200
+ rule = Rule(
201
+ name=name_input.value,
202
+ enabled=True,
203
+ priority=int(priority_input.value or 0),
204
+ action=action_select.value,
205
+ conditions=[
206
+ Condition(
207
+ field=MatchField(field_select.value),
208
+ op=op_select.value,
209
+ value=value_input.value,
210
+ )
211
+ ],
212
+ )
213
+ rules.add(rule)
214
+ name_input.value = ""
215
+ value_input.value = ""
216
+ _refresh()
217
+ ui.notify(f"Rule '{rule.name}' added", type="positive")
218
+
219
+ ui.button("Add", icon="add", on_click=_add).props("color=primary size=sm")
220
+
221
+
222
+ def _build_passthrough_panel(cfg: Config) -> None:
223
+ ui.label("SSL Passthrough").classes("text-subtitle1 q-mb-sm")
224
+ ui.label(
225
+ "Hosts listed here will be tunneled without TLS interception (for certificate pinning, etc.)."
226
+ ).classes("text-caption text-grey q-mb-md")
227
+
228
+ container = ui.column().classes("w-full")
229
+
230
+ def _refresh() -> None:
231
+ container.clear()
232
+ with container:
233
+ for host in cfg.proxy.ignore:
234
+ with ui.row().classes("items-center gap-2"):
235
+ ui.label(host).classes("flex-1 font-mono")
236
+ ui.button(
237
+ icon="remove",
238
+ on_click=lambda h=host: (cfg.proxy.ignore.remove(h), _refresh()),
239
+ ).props("flat dense color=negative size=sm")
240
+
241
+ _refresh()
242
+
243
+ with ui.row().classes("gap-2 items-center q-mt-sm"):
244
+ host_input = ui.input(label="Host").props("dense outlined dark").classes("w-64")
245
+
246
+ def _add() -> None:
247
+ h = host_input.value.strip()
248
+ if h and h not in cfg.proxy.ignore:
249
+ cfg.proxy.ignore.append(h)
250
+ host_input.value = ""
251
+ _refresh()
252
+
253
+ ui.button("Add", icon="add", on_click=_add).props("color=primary size=sm")
254
+
255
+
256
+ def _build_dns_panel(cfg: Config, dns_server: DNSServer | None) -> None:
257
+ ui.label("DNS Overwrite").classes("text-subtitle1 q-mb-sm")
258
+ ui.label(
259
+ "Redirect specific domains to a target IP. Start the DNS server and point devices to this machine."
260
+ ).classes("text-caption text-grey q-mb-md")
261
+
262
+ overrides: dict[str, str] = {}
263
+ container = ui.column().classes("w-full")
264
+
265
+ with ui.row().classes("items-center gap-2 q-mb-md"):
266
+ dns_status = ui.badge("Stopped", color="grey")
267
+ if dns_server:
268
+ ui.button(
269
+ "Start DNS",
270
+ icon="play_arrow",
271
+ on_click=lambda: _start_dns(dns_server, dns_status, overrides),
272
+ ).props("size=sm color=positive")
273
+ ui.label("Port 53153 (use iptables/pfctl to redirect port 53)").classes(
274
+ "text-caption text-grey"
275
+ )
276
+
277
+ def _refresh() -> None:
278
+ container.clear()
279
+ with container:
280
+ for domain, ip in overrides.items():
281
+ with ui.row().classes("items-center gap-2"):
282
+ ui.label(domain).classes("flex-1 font-mono")
283
+ ui.label("→").classes("text-grey")
284
+ ui.label(ip).classes("font-mono text-positive")
285
+ ui.button(
286
+ icon="remove",
287
+ on_click=lambda d=domain: (
288
+ overrides.pop(d, None),
289
+ _refresh_dns(dns_server, overrides),
290
+ _refresh(),
291
+ ),
292
+ ).props("flat dense color=negative size=sm")
293
+
294
+ _refresh()
295
+
296
+ with ui.row().classes("gap-2 items-center q-mt-sm"):
297
+ domain_input = ui.input(label="Domain").props("dense outlined dark").classes("w-48")
298
+ ip_input = ui.input(label="Target IP").props("dense outlined dark").classes("w-36")
299
+
300
+ def _add() -> None:
301
+ d, ip = domain_input.value.strip(), ip_input.value.strip()
302
+ if d and ip:
303
+ overrides[d] = ip
304
+ domain_input.value = ""
305
+ ip_input.value = ""
306
+ _refresh_dns(dns_server, overrides)
307
+ _refresh()
308
+
309
+ ui.button("Add", icon="add", on_click=_add).props("color=primary size=sm")
310
+
311
+
312
+ async def _start_dns(server: DNSServer, badge: ui.badge, overrides: dict) -> None:
313
+ try:
314
+ server.set_overrides(overrides)
315
+ await server.start()
316
+ badge.text = "Running"
317
+ badge.props("color=positive")
318
+ ui.notify("DNS server started on :53153", type="positive")
319
+ except Exception as e:
320
+ ui.notify(f"Failed to start DNS: {e}", type="negative")
321
+
322
+
323
+ def _refresh_dns(server: DNSServer | None, overrides: dict) -> None:
324
+ if server:
325
+ server.set_overrides(overrides)
326
+
327
+
328
+ def _build_ports_panel(cfg: Config) -> None:
329
+ ui.label("Listen Ports").classes("text-subtitle1 q-mb-sm")
330
+ ui.label("Configure proxy listen address and port.").classes("text-caption text-grey q-mb-md")
331
+
332
+ with ui.row().classes("gap-4 items-end"):
333
+ addr_input = (
334
+ ui.input(label="Proxy Address", value=cfg.proxy.addr)
335
+ .props("dense outlined dark")
336
+ .classes("w-48")
337
+ )
338
+ port_input = (
339
+ ui.number(label="Proxy Port", value=cfg.proxy.port)
340
+ .props("dense outlined dark")
341
+ .classes("w-28")
342
+ )
343
+ ui_port_input = (
344
+ ui.number(label="UI Port", value=cfg.ui.port)
345
+ .props("dense outlined dark")
346
+ .classes("w-28")
347
+ )
348
+
349
+ def _save() -> None:
350
+ cfg.proxy.addr = addr_input.value
351
+ cfg.proxy.port = int(port_input.value or 8080)
352
+ cfg.ui.port = int(ui_port_input.value or 8081)
353
+ ui.notify("Port settings saved (takes effect on restart)", type="info")
354
+
355
+ ui.button("Save", icon="save", on_click=_save).props("color=primary size=sm")
356
+
357
+ ui.label("Note: Port changes take effect after restarting paxy.").classes(
358
+ "text-caption text-grey q-mt-sm"
359
+ )
360
+
361
+
362
+ def _build_certs_panel(cert_mgr: ClientCertManager) -> None:
363
+ ui.label("Client Certificates").classes("text-subtitle1 q-mb-sm")
364
+ ui.label("Client certificates are sent to matching hosts during TLS handshake.").classes(
365
+ "text-caption text-grey q-mb-md"
366
+ )
367
+
368
+ container = ui.column().classes("w-full")
369
+
370
+ def _refresh() -> None:
371
+ container.clear()
372
+ with container:
373
+ for cert in cert_mgr.list():
374
+ with ui.card().classes("w-full q-mb-xs"), ui.row().classes("items-center gap-2"):
375
+ ui.icon("badge").classes("text-primary")
376
+ with ui.column().classes("flex-1"):
377
+ ui.label(cert.name).classes("text-weight-medium")
378
+ ui.label(f"{cert.host_pattern} • {cert.cert_path}").classes(
379
+ "text-caption text-grey"
380
+ )
381
+ ui.button(
382
+ icon="delete",
383
+ on_click=lambda n=cert.name: (cert_mgr.remove(n), _refresh()),
384
+ ).props("flat dense color=negative size=sm")
385
+
386
+ _refresh()
387
+
388
+ ui.separator().classes("q-my-md")
389
+ ui.label("Add Client Certificate").classes("text-subtitle2")
390
+ with ui.grid(columns=2).classes("gap-2 q-mt-sm"):
391
+ name_input = ui.input(label="Name").props("dense outlined dark")
392
+ pattern_input = ui.input(label="Host Pattern", value="*").props("dense outlined dark")
393
+ cert_input = ui.input(label="Certificate Path (.pem)").props("dense outlined dark")
394
+ key_input = ui.input(label="Key Path (.pem)").props("dense outlined dark")
395
+
396
+ def _add() -> None:
397
+ if not name_input.value or not cert_input.value or not key_input.value:
398
+ ui.notify("All fields are required", type="warning")
399
+ return
400
+ cert_mgr.add(
401
+ ClientCert(
402
+ name=name_input.value,
403
+ cert_path=cert_input.value,
404
+ key_path=key_input.value,
405
+ host_pattern=pattern_input.value or "*",
406
+ )
407
+ )
408
+ for inp in (name_input, cert_input, key_input):
409
+ inp.value = ""
410
+ _refresh()
411
+ ui.notify("Certificate added", type="positive")
412
+
413
+ ui.button("Add", icon="add", on_click=_add).props("color=primary size=sm q-mt-sm")
pypproxy/ui/theme.py ADDED
@@ -0,0 +1,59 @@
1
+ from nicegui import ui
2
+
3
+ METHOD_COLORS: dict[str, str] = {
4
+ "GET": "blue",
5
+ "POST": "green",
6
+ "PUT": "orange",
7
+ "PATCH": "purple",
8
+ "DELETE": "red",
9
+ "HEAD": "grey",
10
+ "OPTIONS": "grey",
11
+ }
12
+
13
+ STATUS_COLORS: dict[int, str] = {}
14
+
15
+
16
+ def status_color(code: int) -> str:
17
+ if 200 <= code < 300:
18
+ return "positive"
19
+ if 300 <= code < 400:
20
+ return "info"
21
+ if 400 <= code < 500:
22
+ return "warning"
23
+ if code >= 500:
24
+ return "negative"
25
+ return "grey"
26
+
27
+
28
+ def method_badge(method: str) -> None:
29
+ color = METHOD_COLORS.get(method.upper(), "grey")
30
+ ui.badge(method, color=color).props("rounded")
31
+
32
+
33
+ def status_badge(code: int) -> None:
34
+ if code == 0:
35
+ return
36
+ ui.badge(str(code), color=status_color(code)).props("rounded")
37
+
38
+
39
+ def apply_dark_theme() -> None:
40
+ ui.add_head_html("""
41
+ <style>
42
+ .paxy-row-modified { background: rgba(255, 200, 0, 0.08) !important; }
43
+ .paxy-row-blocked { background: rgba(255, 50, 50, 0.08) !important; }
44
+ .paxy-row-selected { background: rgba(100, 150, 255, 0.15) !important; }
45
+ .paxy-body-pre {
46
+ font-family: monospace;
47
+ font-size: 12px;
48
+ white-space: pre-wrap;
49
+ word-break: break-all;
50
+ max-height: 400px;
51
+ overflow-y: auto;
52
+ background: rgba(0,0,0,0.2);
53
+ padding: 8px;
54
+ border-radius: 4px;
55
+ }
56
+ .paxy-header-table td { padding: 2px 8px; font-size: 12px; }
57
+ .paxy-header-table td:first-child { color: #aaa; min-width: 160px; }
58
+ </style>
59
+ """)
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: pypproxy
3
+ Version: 0.1.0
4
+ Summary: MITM HTTP/HTTPS proxy for inspecting and modifying traffic
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aiosqlite>=0.20.0
8
+ Requires-Dist: brotli>=1.1.0
9
+ Requires-Dist: cbor2>=5.6.0
10
+ Requires-Dist: cryptography>=48.0.0
11
+ Requires-Dist: fastapi>=0.111.0
12
+ Requires-Dist: httpx>=0.27.0
13
+ Requires-Dist: httpx[http2]>=0.27.0
14
+ Requires-Dist: msgpack>=1.1.0
15
+ Requires-Dist: nicegui>=3.12.1
16
+ Requires-Dist: pyyaml>=6.0.3
17
+ Requires-Dist: rich>=15.0.0
18
+ Requires-Dist: uvicorn[standard]>=0.30.0
19
+ Requires-Dist: websockets>=16.0
@@ -0,0 +1,72 @@
1
+ pypproxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pypproxy/codec.py,sha256=VTwo3WutemcU3v5qjY_zCIS17P_vtqwXde3QcXfiKY0,5617
3
+ pypproxy/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ pypproxy/api/server.py,sha256=sRYu98BslZqFPLvw1fm6xW4GVMppivewFbS8_kWJxgA,12125
5
+ pypproxy/bulk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pypproxy/bulk/sender.py,sha256=t905m7vIliqKMckiKFLE4mC3JUxm7veQEcBJVTj4Fmg,2752
7
+ pypproxy/cert/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ pypproxy/cert/ca.py,sha256=O0C-Er_WCrQXemd-Km1xfPFcrlSsjRxydNgstFX-PfE,5314
9
+ pypproxy/cert/client_cert.py,sha256=zNMEXLL_OuoonFeG8MpJOAaoAfgMTrA3gVD6Jf7gjF4,1726
10
+ pypproxy/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ pypproxy/config/config.py,sha256=bfH7V-SZDtKzBtdzP3f7ST2LT6cq1Xv8p0fkvKvaqSc,2862
12
+ pypproxy/dns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ pypproxy/dns/server.py,sha256=I1R084TjS-DuC01NIcPkXoEIkJglRKGivKeH3m5XeOk,4973
14
+ pypproxy/exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ pypproxy/exporter/exporter.py,sha256=96ZhTkk93HUxMFrwUfwrc8FyRVja2dNtRTDAJD2bZT4,3810
16
+ pypproxy/exporter/importer.py,sha256=oWFwRmA-4aUy4zeZbWanwbbNE1ICgxBmVHuu757N2ao,4991
17
+ pypproxy/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ pypproxy/graphql/detector.py,sha256=jJgSLoHP1wOtRkVW4d0flsCsXZfp0Hv_tCjj7VZ49oc,2575
19
+ pypproxy/graphql/introspection.py,sha256=HpbEW4_a_n0FX-6EeDsiZfuJ6TXel22NjSn3TyQlAxc,5683
20
+ pypproxy/graphql/modifier.py,sha256=qzJ5fNavNKtI2102ZcLDpZQtT_WpcgROgIeh-cHwNiI,3093
21
+ pypproxy/graphql/schema_store.py,sha256=BQWf1sveEvUnk489yjFPt8LCL701W1CP-T2TdG68ER8,856
22
+ pypproxy/intercept/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ pypproxy/intercept/manager.py,sha256=_dBiqMotuvfN-7UMiP7vN2hCdteXQkcK9TJ9_VcH6As,4066
24
+ pypproxy/interceptor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ pypproxy/interceptor/interceptor.py,sha256=rSA_B2jKACPbAQCk_G7UUX1J8BcW-GR815rQHcv7cFQ,5108
26
+ pypproxy/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ pypproxy/proto/grpc.py,sha256=gZdkGA9LlnQMPs4lk-uzEouQgiCPFxfei3aMIYFRlKM,1278
28
+ pypproxy/proto/mqtt.py,sha256=7PUkcvEMiW56bwyOKCHNbnSNiLf_Ts6uq6ylWCk8kVo,2913
29
+ pypproxy/proto/ws.py,sha256=5SdhChNLC-KHUzJPXLem8vqm9DvDg2xRIden1N_AzEU,3230
30
+ pypproxy/proto/ws_intercept.py,sha256=2d_R_fYfAeaeP0JJq3YnkSKTH_zIWvJEN8UtogM-VPY,3388
31
+ pypproxy/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ pypproxy/proxy/proxy.py,sha256=3oeEdJEipDTQ20nAu-Ka_RCCaZ3JRxjBOVnuZD38v1A,13118
33
+ pypproxy/replay/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ pypproxy/replay/replay.py,sha256=y3Yo9QVbufaT4VskKyxeWG59bZHwrg5RLa6jVOIRgK0,2100
35
+ pypproxy/rule/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ pypproxy/rule/rule.py,sha256=V7EJir77KhpSzmWViaReF6ttsxm6YiGtlBQzL2jx2TM,5763
37
+ pypproxy/scan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ pypproxy/scan/scanner.py,sha256=OinfUDJQUfpf4IYm2VtScrtbbj60yoSAgnCLtUUeQ4E,8433
39
+ pypproxy/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ pypproxy/script/engine.py,sha256=YjN63iFqrguFuLlS7B1f0eUOcHfWS7bcCiGvGakBjqs,1607
41
+ pypproxy/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ pypproxy/security/header_checker.py,sha256=MUBH0J3R88fHr9JPcnpSLi6PdoBgySnjfzfjks7mCVg,8757
43
+ pypproxy/security/int_overflow.py,sha256=ZaaIqYwvSfrm02C6HweMLQ3s9n95Laeh0AieiWPCt0o,6321
44
+ pypproxy/security/jwt_checker.py,sha256=02Y3gLfBWfyVo8H-9vAbEoy07ZAu4JM9uJbdsOeJ1Qc,8606
45
+ pypproxy/security/plugin.py,sha256=65v6xLv9SBnuAapwrxDNH-V3DNH2KH9waqFEN4ZgMu4,4953
46
+ pypproxy/security/randomness.py,sha256=7T8X9JA3jZ58kMWcAW2oJV8_7d70IAQhGPzhpTwc9-c,4805
47
+ pypproxy/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
+ pypproxy/store/db.py,sha256=FhCZPH9g-G-3zSTaGoA60jYtp_ebAccnXejv3o3GPo0,6070
49
+ pypproxy/store/filter_parser.py,sha256=i0kHfAEUh4Ij0qiXHMYiguQavc80I6-I1oKq1Ylhoo0,5089
50
+ pypproxy/store/fts.py,sha256=98ZJgsro0WGDLtqdhx_HKWSv9TnUd9uZ2hp3RJeBONw,3142
51
+ pypproxy/store/models.py,sha256=HdeONRuosHt6YmzlGh6lLCYyvvC5jYPYdTF8jb8jNxQ,2633
52
+ pypproxy/store/scope.py,sha256=NOO1Nf4t8d3F8m6ZJLfhinlcvpHhUsh8hLS1-CBZEN0,1685
53
+ pypproxy/store/store.py,sha256=yk69W9ezw4JqK0s8MPHtjYAF9cRrf3Ypd41ruUtS9z0,3743
54
+ pypproxy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ pypproxy/ui/app.py,sha256=khCpYrxk4-xuYIQj16XKcmD-aWlTGY2S4DqsSDIRkRc,14514
56
+ pypproxy/ui/bulk_sender_ui.py,sha256=LI9bwS_tTxMwUCfKpa0IgNypn7IWsGrphW4AmYLfBW4,4809
57
+ pypproxy/ui/cui.py,sha256=84oVg5s2ca1XEo_MyNmwU042wQpxLSr0Ra4AvGbzYwc,4674
58
+ pypproxy/ui/detail.py,sha256=B0pIf5Ep_cEtEdUdLf5t6m0S-hi51KvjhCno13foig0,6275
59
+ pypproxy/ui/diff_view.py,sha256=ewN9UmGGpxJf-8OtgFZ7YsRXWa__mEhI7pNWVUWgV4E,3823
60
+ pypproxy/ui/graphql_tab.py,sha256=Yw-0Bxzsrviu2PPZH00obbYKHv3ONrHvXj609gCKiIY,10658
61
+ pypproxy/ui/import_tab.py,sha256=RbzVEKyN2oG4hvcIqH63nBuurdLnF4T5L7Fsxa4jtlU,5226
62
+ pypproxy/ui/intercept_dialog.py,sha256=aSca1vX6R2DB6Krs7HnJ6hVQvic-cBJsoYitW1z1zDI,2666
63
+ pypproxy/ui/resender.py,sha256=obiFQ_dKZN6RFpNh82oLSna7YRVmS2xlFqBJP4R7Vyg,5182
64
+ pypproxy/ui/scan_tab.py,sha256=_OPjOLC6Gkz1oNFlqzcUdUjKsuLiBMjykzSeDozmF5E,3823
65
+ pypproxy/ui/security_tab.py,sha256=jl05lGgZtwv1sYuoRQMxlI64jB4PmYUwAE78tcJtL3Q,16292
66
+ pypproxy/ui/settings.py,sha256=W0Fl8xKdEi6ow5GRP9tYF7N5Nor-LMgRMfSTtDKAF_M,15744
67
+ pypproxy/ui/theme.py,sha256=Yda_pGxT-WgcGH1jWmW1fpm-NqjwHEBRBxOWVSflwLo,1541
68
+ pypproxy-0.1.0.dist-info/METADATA,sha256=OXYPv3FNFqLZ0nOg6F5-p4WpXMWoWhAicf56zV7XqWQ,580
69
+ pypproxy-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
70
+ pypproxy-0.1.0.dist-info/entry_points.txt,sha256=qGPuutdrIlY_LGUMNLmcBseWjgnGuNRX0P8oqnK1Es8,39
71
+ pypproxy-0.1.0.dist-info/licenses/LICENSE,sha256=hO5vafKkFOjcGnOjmv0SScAeG3c7jTDD7EFT_KOLiHA,1061
72
+ pypproxy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pypproxy = main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kuma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.