@piecemaker-legal/piecemaker 2.0.9 → 2.0.10
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.
- package/dist/assets/FindReplaceDialog-KU5TEIBJ-CQ76sPaJ.js +1 -0
- package/dist/assets/FootnotePropertiesDialog-W3YHM4BV-BpgYQvvd.js +1 -0
- package/dist/assets/HyperlinkDialog-ZYH7ZHRF-qIPBOssF.css +1 -0
- package/dist/assets/HyperlinkDialog-ZYH7ZHRF-z08UbYbL.js +144 -0
- package/dist/assets/ImagePositionDialog-HEWYR6Q3-CFzOnzAt.js +1 -0
- package/dist/assets/ImagePropertiesDialog-RAB47C6M-D-ThGU-b.js +1 -0
- package/dist/assets/PageSetupDialog-AIGIXO3E-_PYIO8eb.js +1 -0
- package/dist/assets/SplitCellDialog-IFBKYCD6-Cb9uQOB8.js +1 -0
- package/dist/assets/TablePropertiesDialog-TMLKGOOW-12vmjb6B.js +1 -0
- package/dist/assets/WatermarkDialog-KRL7WMIS-C-d8rzt2.js +1 -0
- package/dist/assets/{abnfDiagram-VCTEODGH-WrsSAHOe.js → abnfDiagram-VCTEODGH-B4OsX7eF.js} +1 -1
- package/dist/assets/arc-BK1oUIk7.js +1 -0
- package/dist/assets/architectureDiagram-5GKGNRK7-CfaFI6L7.js +36 -0
- package/dist/assets/{blockDiagram-I7D4REHJ-zGMnF7Ox.js → blockDiagram-I7D4REHJ-DQEI2azY.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-ClWw3bqt.js → c4Diagram-7LVT6UL2-DbravpVi.js} +1 -1
- package/dist/assets/channel-DVyNr2Yg.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-C-9ICPHv.js → chunk-2Q5K7J3B-BLpZ9huC.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-Bawy3teT.js → chunk-5VM5RSS4-BLlEdmX9.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-DQ375RQO.js → chunk-F27PBJKO-D-ZNhI6q.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-CflNF2f6.js → chunk-IMKFNOWR-vzSiKnhh.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CIXQv0gc.js → chunk-JWPE2WC7-DfF0k4nb.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-Cza5j6Qw.js → chunk-POPQ4Y6H-BnwCYsPP.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-GQ_VvWZR.js → chunk-SVP7TREG-DRxc8fhq.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-BEmU16W5.js → chunk-TICWLB2K-Dd2Q-md-.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-C1KpkLYH.js → chunk-XXDRQBXY-DBjkEUp_.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-2pGPHl5H.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-2pGPHl5H.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-CZE48TRG.js → cose-bilkent-JH36ORCC-By6PS4oe.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-L-tx1eC-.js → cynefin-OW5HDTMX-YxWpjc5W.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-Coa8oJwR.js → cynefinDiagram-5FMLGOSQ-DrtXzz3p.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-Bu_3Y75K.js → dagre-GXQ25YYZ-BDpTLKqZ.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-CFiiV4es.js → diagram-S7CK7UJ4-C4LvqeCs.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-CCXheGAA.js → diagram-UQ7AKVKN-BTpnMeHp.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-B70m4lr_.js → diagram-VSXAHHWV-DucHHWev.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-CHkiMOVA.js → diagram-VX7I27RA-DYHZNmmH.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-_awpc4Bs.js → diagram-Z3DM3KII-BHIH2BGN.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-CZzjH86H.js → ebnfDiagram-PWID7BFC-CGiEkwj0.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-9DgiHZg9.js → erDiagram-RLTQ6QDP-AJMHi2Us.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DHY_503b.js → flowDiagram-HODETNUW-BqzANBh2.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-DPiiYZ_l.js → ganttDiagram-EL5Y4UJY-B0xPDY2_.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-Ddd_evm7.js → gitGraphDiagram-WWUBYQGX-CzPX5CNO.js} +1 -1
- package/dist/assets/{index-tibHq7Qp.js → index-Bk2iXKnJ.js} +140 -140
- package/dist/assets/index-Dc9wNuS4.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-BPRNhfwb.js → infoDiagram-27XIBGKW-Cjc44H0O.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-BXx6aqkq.js → ishikawaDiagram-5VMMS53U-BWhr2v7r.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-BK8TZM29.js → journeyDiagram-3NMN7TZE-Bl4r6RrP.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-CHioRmxz.js → kanban-definition-UXKFOSKX-FV-T0Vy1.js} +6 -6
- package/dist/assets/layout-CTfb1sAF.js +1 -0
- package/dist/assets/{linear-CTEhUcKG.js → linear-Dfgi6vlo.js} +1 -1
- package/dist/assets/{mermaid.core-DYUb_UUB.js → mermaid.core-DOCGsdlo.js} +7 -7
- package/dist/assets/{mindmap-definition-YA3MSWOX-D7ODZ4oS.js → mindmap-definition-YA3MSWOX-CymQ86ir.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-8J-_2_kw.js → pegDiagram-XKGWAZYB-1VMq_vi0.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-ebg0yfMi.js → pieDiagram-E7YTZNPT-C5GgXtJU.js} +1 -1
- package/dist/assets/processTemplate-GPFDLQMX-B1sVV2O0.js +22 -0
- package/dist/assets/{quadrantDiagram-AXDQQJYC-BoWkDiFb.js → quadrantDiagram-AXDQQJYC-C6zpryYf.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-BRJ77ldz.js → railroadDiagram-O6MQD6OU-D-sv4SWi.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-Z0JJgktA.js → requirementDiagram-BXWQKSXE-CkjY-z4B.js} +1 -1
- package/dist/assets/sankeyDiagram-P5KCCOFB-De-d8Q6L.js +40 -0
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-BPAjtXwp.js → sequenceDiagram-WJ2MYXX4-DCsGSJa9.js} +1 -1
- package/dist/assets/{server.browser-BzI1jrZw.js → server.browser-BY3bL-IX.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BGZPCzM4.js → sizeCapture-INFHLROL-xINZhgER.js} +1 -1
- package/dist/assets/stateDiagram-D77RDMKH-CuLUTWSL.js +1 -0
- package/dist/assets/stateDiagram-v2-MP3YSRHH-Cnbs-M92.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-DvuB15qF.js → swimlanes-42K2YHIH-BwxARD7N.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-D7p8qZI2.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-ZIziYCIF.js → timeline-definition-24CTP7MA-BIOvnn-l.js} +3 -3
- package/dist/assets/{vendor-codemirror-CjW0lHR5.js → vendor-codemirror-CN-sJePf.js} +2 -2
- package/dist/assets/{vendor-react-D809iwXR.js → vendor-react-B22wqN3J.js} +5 -5
- package/dist/assets/{vennDiagram-4TSXK5OY-C5_B9gWa.js → vennDiagram-4TSXK5OY-BSD6Pfmt.js} +4 -4
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DMlH9Afw.js → wardleyDiagram-VM6X3IG4-7Zz2iRkF.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-BldhJt7i.js → xychartDiagram-S5SC5T6Z-Ctx17Y1D.js} +1 -1
- package/dist/index.html +4 -4
- package/dist/sw.js +1 -1
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +4 -53
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +2 -3
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js +30 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js.map +1 -0
- package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js +24 -0
- package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js +2 -3
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -1
- package/dist-server/server/piecemaker/docx-document.js +50 -0
- package/dist-server/server/piecemaker/docx-document.js.map +1 -0
- package/dist-server/server/piecemaker/index.js +9 -2
- package/dist-server/server/piecemaker/index.js.map +1 -1
- package/dist-server/server/piecemaker/model-discovery/codex-catalog.js +46 -0
- package/dist-server/server/piecemaker/model-discovery/codex-catalog.js.map +1 -0
- package/dist-server/server/piecemaker/model-discovery/index.js +42 -0
- package/dist-server/server/piecemaker/model-discovery/index.js.map +1 -0
- package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js +19 -0
- package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js.map +1 -0
- package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js +61 -0
- package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js.map +1 -0
- package/dist-server/server/piecemaker/project-registry.js +29 -0
- package/dist-server/server/piecemaker/project-registry.js.map +1 -0
- package/package.json +12 -1
- package/scripts/piecemaker/cli/disable-anonymizer.mjs +19 -0
- package/scripts/piecemaker/cli/lib/config.mjs +0 -1
- package/scripts/piecemaker/cli/lib/services.mjs +1 -10
- package/scripts/piecemaker/cli/piecemaker.mjs +0 -3
- package/server/modules/providers/list/mistral/mistral-models.provider.ts +4 -53
- package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +2 -3
- package/server/modules/providers/list/mistral/mistral-vibe-models.ts +43 -0
- package/server/modules/providers/tests/mistral-vibe-models.test.ts +27 -0
- package/server/piecemaker/anonymizer/client-config.cjs +92 -92
- package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +9 -16
- package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +1 -1
- package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +251 -268
- package/server/piecemaker/anonymizer/lifecycle.ts +2 -3
- package/server/piecemaker/anonymizer/rewriter-bridge.cjs +6 -0
- package/server/piecemaker/anonymizer/service.cjs +103 -174
- package/server/piecemaker/docx-document.ts +54 -0
- package/server/piecemaker/harness/decisions.cjs +1 -1
- package/server/piecemaker/harness/index.cjs +2 -3
- package/server/piecemaker/harness/verification.cjs +1 -1
- package/server/piecemaker/index.ts +11 -3
- package/server/piecemaker/model-discovery/codex-catalog.ts +67 -0
- package/server/piecemaker/model-discovery/index.ts +69 -0
- package/server/piecemaker/model-discovery/mistral-catalog.ts +36 -0
- package/server/piecemaker/model-discovery/stdio-rpc.ts +79 -0
- package/server/piecemaker/project-registry.ts +34 -0
- package/server/piecemaker/router.cjs +6 -7
- package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +31 -77
- package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +2 -84
- package/server/piecemaker/vendor/installer/lib/service.mjs +4 -24
- package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +10 -21
- package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -11
- package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +6 -36
- package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +30 -12
- package/server/piecemaker/vendor/piecemaker-plugin/README.md +2 -9
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +5 -5
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +8 -8
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +2 -2
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +4 -6
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +71 -38
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +8 -38
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +82 -61
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +4 -55
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +24 -117
- package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +2 -2
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +15 -121
- package/server/piecemaker/vendor/websocket-server/case-registry.cjs +16 -32
- package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +0 -73
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +0 -7
- package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +3 -12
- package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +6 -6
- package/dist/assets/arc-DAA6wH-C.js +0 -1
- package/dist/assets/architectureDiagram-5GKGNRK7-BGNHr7ef.js +0 -36
- package/dist/assets/channel-BAQyfMll.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BNGCuvrR.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BNGCuvrR.js +0 -1
- package/dist/assets/index-WwknP0l4.css +0 -1
- package/dist/assets/layout-DEnkyfTY.js +0 -1
- package/dist/assets/sankeyDiagram-P5KCCOFB-4PW5HTOP.js +0 -40
- package/dist/assets/stateDiagram-D77RDMKH-o-vEcKwz.js +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BsuDaKkN.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-DcCAyKlT.js +0 -8
- package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +0 -78
- package/scripts/piecemaker/hooks/proxy-guard.mjs +0 -251
- package/server/piecemaker/anonymizer/providers.cjs +0 -120
- package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +0 -482
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
- package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +0 -602
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
use std::collections::{HashMap, HashSet};
|
|
2
|
+
use std::io::Read;
|
|
2
3
|
use std::net::SocketAddr;
|
|
3
4
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
4
5
|
use std::sync::Arc;
|
|
6
|
+
use std::time::Duration;
|
|
5
7
|
|
|
6
8
|
use bytes::Bytes;
|
|
7
|
-
use futures::StreamExt;
|
|
9
|
+
use futures::{SinkExt, StreamExt};
|
|
8
10
|
use http_body_util::{BodyExt, BodyStream};
|
|
9
11
|
use hudsucker::certificate_authority::RcgenAuthority;
|
|
10
|
-
use hudsucker::hyper::{Request, Response, StatusCode, Uri};
|
|
12
|
+
use hudsucker::hyper::{Method, Request, Response, StatusCode, Uri};
|
|
11
13
|
use hudsucker::rcgen::{Issuer, KeyPair};
|
|
12
14
|
use hudsucker::rustls::crypto::aws_lc_rs;
|
|
13
|
-
use hudsucker::tokio_tungstenite::tungstenite::
|
|
15
|
+
use hudsucker::tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
|
16
|
+
use hudsucker::tokio_tungstenite::tungstenite::protocol::CloseFrame;
|
|
17
|
+
use hudsucker::tokio_tungstenite::tungstenite::{Error as WsError, Message};
|
|
14
18
|
use hudsucker::{
|
|
15
|
-
Body, HttpContext, HttpHandler, Proxy, RequestOrResponse,
|
|
19
|
+
decode_request, decode_response, Body, HttpContext, HttpHandler, Proxy, RequestOrResponse,
|
|
20
|
+
WebSocketContext, WebSocketHandler,
|
|
16
21
|
};
|
|
17
22
|
use tokio::sync::mpsc;
|
|
18
23
|
use tokio_stream::wrappers::ReceiverStream;
|
|
@@ -27,7 +32,7 @@ struct Handler {
|
|
|
27
32
|
hosts: Arc<HashSet<String>>,
|
|
28
33
|
upstream: Arc<HashMap<String, String>>,
|
|
29
34
|
http: reqwest::Client,
|
|
30
|
-
provider:
|
|
35
|
+
provider: &'static str,
|
|
31
36
|
ws_id: Option<String>,
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -54,9 +59,6 @@ fn request_host(req: &Request<Body>) -> String {
|
|
|
54
59
|
if let Some(host) = req.uri().host() {
|
|
55
60
|
return host.to_ascii_lowercase();
|
|
56
61
|
}
|
|
57
|
-
if let Some(authority) = req.uri().authority() {
|
|
58
|
-
return authority.host().to_ascii_lowercase();
|
|
59
|
-
}
|
|
60
62
|
req.headers()
|
|
61
63
|
.get("host")
|
|
62
64
|
.and_then(|value| value.to_str().ok())
|
|
@@ -72,44 +74,25 @@ fn provider_for(host: &str) -> &'static str {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
fn
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.to_string()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
fn response_type(res: &Response<Body>) -> String {
|
|
84
|
-
res.headers()
|
|
77
|
+
fn content_type<T>(message: &hudsucker::hyper::http::HeaderMap<T>) -> String
|
|
78
|
+
where
|
|
79
|
+
T: AsRef<[u8]>,
|
|
80
|
+
{
|
|
81
|
+
message
|
|
85
82
|
.get("content-type")
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
.to_ascii_lowercase()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
fn is_json(value: &str) -> bool {
|
|
92
|
-
value.contains("json")
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fn is_sse(value: &str) -> bool {
|
|
96
|
-
value.contains("text/event-stream")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
fn health_request(req: &Request<Body>) -> bool {
|
|
100
|
-
let host = request_host(req);
|
|
101
|
-
req.method() == hudsucker::hyper::Method::GET
|
|
102
|
-
&& (req.uri().path() == "/" || req.uri().path().is_empty())
|
|
103
|
-
&& (host.is_empty() || host == "127.0.0.1" || host == "localhost")
|
|
83
|
+
.map(|value| String::from_utf8_lossy(value.as_ref()).to_ascii_lowercase())
|
|
84
|
+
.unwrap_or_default()
|
|
104
85
|
}
|
|
105
86
|
|
|
106
|
-
fn
|
|
87
|
+
fn refusal(status: StatusCode, host: &str, path: &str, cause: &str) -> Response<Body> {
|
|
88
|
+
eprintln!("refus {} {host}{path} : {cause}", status.as_u16());
|
|
89
|
+
let message = serde_json::json!({
|
|
90
|
+
"error": { "type": "piecemaker_proxy", "message": format!("Relais d'anonymisation PieceMaker : {cause}.") }
|
|
91
|
+
});
|
|
107
92
|
Response::builder()
|
|
108
|
-
.status(
|
|
93
|
+
.status(status)
|
|
109
94
|
.header("content-type", "application/json")
|
|
110
|
-
.body(Body::from(
|
|
111
|
-
"{\"error\":{\"type\":\"piecemaker_proxy\",\"message\":\"Relais d'anonymisation indisponible.\"}}",
|
|
112
|
-
))
|
|
95
|
+
.body(Body::from(message.to_string()))
|
|
113
96
|
.unwrap()
|
|
114
97
|
}
|
|
115
98
|
|
|
@@ -120,18 +103,40 @@ fn uri_path(uri: &Uri) -> String {
|
|
|
120
103
|
}
|
|
121
104
|
}
|
|
122
105
|
|
|
106
|
+
async fn collect(body: Body) -> Result<Bytes, &'static str> {
|
|
107
|
+
let bytes = body
|
|
108
|
+
.collect()
|
|
109
|
+
.await
|
|
110
|
+
.map_err(|_| "corps illisible")?
|
|
111
|
+
.to_bytes();
|
|
112
|
+
if bytes.len() > MAX_BODY {
|
|
113
|
+
return Err("corps trop volumineux");
|
|
114
|
+
}
|
|
115
|
+
Ok(bytes)
|
|
116
|
+
}
|
|
117
|
+
|
|
123
118
|
impl Handler {
|
|
124
119
|
fn allowed(&self, host: &str) -> bool {
|
|
125
120
|
self.hosts.contains(host)
|
|
126
121
|
}
|
|
127
122
|
|
|
123
|
+
fn intercepted_host(&self, uri: &Uri) -> Option<String> {
|
|
124
|
+
let host = uri.host().map(str::to_ascii_lowercase).unwrap_or_default();
|
|
125
|
+
if self.allowed(&host) {
|
|
126
|
+
return Some(host);
|
|
127
|
+
}
|
|
128
|
+
let authority = uri.authority()?.as_str();
|
|
129
|
+
self.upstream
|
|
130
|
+
.iter()
|
|
131
|
+
.find(|(_, dest)| dest.as_str() == authority)
|
|
132
|
+
.map(|(host, _)| host.clone())
|
|
133
|
+
}
|
|
134
|
+
|
|
128
135
|
fn point_upstream(&self, host: &str, req: &mut Request<Body>) {
|
|
129
136
|
let Some(dest) = self.upstream.get(host) else {
|
|
130
137
|
return;
|
|
131
138
|
};
|
|
132
|
-
let
|
|
133
|
-
let rewritten = format!("http://{dest}{path}");
|
|
134
|
-
if let Ok(uri) = rewritten.parse() {
|
|
139
|
+
if let Ok(uri) = format!("http://{dest}{}", uri_path(req.uri())).parse() {
|
|
135
140
|
*req.uri_mut() = uri;
|
|
136
141
|
}
|
|
137
142
|
if let Ok(value) = dest.parse() {
|
|
@@ -139,25 +144,47 @@ impl Handler {
|
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
|
|
142
|
-
async fn
|
|
147
|
+
async fn rewrite(&self, path: &str, extra: &[(&str, &str)], body: Bytes) -> Result<Bytes, &'static str> {
|
|
143
148
|
let mut request = self
|
|
144
149
|
.http
|
|
145
150
|
.post(format!("{}{path}", self.rewriter))
|
|
146
|
-
.header("x-piecemaker-provider", provider)
|
|
151
|
+
.header("x-piecemaker-provider", self.provider)
|
|
147
152
|
.body(body);
|
|
148
153
|
for (name, value) in extra {
|
|
149
154
|
request = request.header(*name, *value);
|
|
150
155
|
}
|
|
151
|
-
let response = request.send().await.map_err(|_|
|
|
156
|
+
let response = request.send().await.map_err(|_| "réécrivain injoignable")?;
|
|
152
157
|
if !response.status().is_success() {
|
|
153
|
-
return Err(
|
|
158
|
+
return Err("réécriture en échec");
|
|
154
159
|
}
|
|
155
|
-
response.bytes().await.map_err(|_|
|
|
160
|
+
response.bytes().await.map_err(|_| "réécriture interrompue")
|
|
156
161
|
}
|
|
157
162
|
|
|
158
|
-
fn
|
|
159
|
-
let
|
|
160
|
-
|
|
163
|
+
async fn health(&self) -> Response<Body> {
|
|
164
|
+
let alive = self
|
|
165
|
+
.http
|
|
166
|
+
.get(format!("{}/health", self.rewriter))
|
|
167
|
+
.send()
|
|
168
|
+
.await
|
|
169
|
+
.map(|response| response.status().is_success())
|
|
170
|
+
.unwrap_or(false);
|
|
171
|
+
let (status, text) = if alive {
|
|
172
|
+
(StatusCode::OK, format!("{{\"pid\":{},\"rewriter\":\"ok\"}}", std::process::id()))
|
|
173
|
+
} else {
|
|
174
|
+
(StatusCode::SERVICE_UNAVAILABLE, format!("{{\"pid\":{},\"rewriter\":\"down\"}}", std::process::id()))
|
|
175
|
+
};
|
|
176
|
+
Response::builder()
|
|
177
|
+
.status(status)
|
|
178
|
+
.header("content-type", "application/json")
|
|
179
|
+
.body(Body::from(text))
|
|
180
|
+
.unwrap()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn spawn_sse(&self, body: Body) -> Body {
|
|
184
|
+
let request = self
|
|
185
|
+
.http
|
|
186
|
+
.post(format!("{}/v1/sse", self.rewriter))
|
|
187
|
+
.header("x-piecemaker-provider", self.provider);
|
|
161
188
|
let (tx, rx) = mpsc::channel::<Result<Bytes, std::io::Error>>(8);
|
|
162
189
|
tokio::spawn(async move {
|
|
163
190
|
let incoming = BodyStream::new(body).filter_map(|frame| async move {
|
|
@@ -166,29 +193,58 @@ impl Handler {
|
|
|
166
193
|
Err(error) => Some(Err(std::io::Error::other(error.to_string()))),
|
|
167
194
|
}
|
|
168
195
|
});
|
|
169
|
-
|
|
170
|
-
.post(url)
|
|
171
|
-
.header("x-piecemaker-provider", provider)
|
|
172
|
-
.body(reqwest::Body::wrap_stream(incoming))
|
|
173
|
-
.send()
|
|
174
|
-
.await;
|
|
175
|
-
match sent {
|
|
196
|
+
match request.body(reqwest::Body::wrap_stream(incoming)).send().await {
|
|
176
197
|
Ok(response) => {
|
|
177
198
|
let mut stream = response.bytes_stream();
|
|
178
199
|
while let Some(chunk) = stream.next().await {
|
|
179
|
-
|
|
180
|
-
if tx.send(mapped).await.is_err() {
|
|
200
|
+
if tx.send(chunk.map_err(std::io::Error::other)).await.is_err() {
|
|
181
201
|
break;
|
|
182
202
|
}
|
|
183
203
|
}
|
|
184
204
|
}
|
|
185
205
|
Err(error) => {
|
|
186
|
-
|
|
206
|
+
eprintln!("refus flux SSE : réécrivain injoignable");
|
|
207
|
+
let _ = tx.send(Err(std::io::Error::other(error))).await;
|
|
187
208
|
}
|
|
188
209
|
}
|
|
189
210
|
});
|
|
190
211
|
Body::from_stream(ReceiverStream::new(rx))
|
|
191
212
|
}
|
|
213
|
+
|
|
214
|
+
async fn rewrite_ws(&mut self, host: &str, outbound: bool, text: &str) -> Result<Option<Message>, &'static str> {
|
|
215
|
+
let id = self
|
|
216
|
+
.ws_id
|
|
217
|
+
.get_or_insert_with(|| format!("ws-{}", WS_IDS.fetch_add(1, Ordering::Relaxed)))
|
|
218
|
+
.clone();
|
|
219
|
+
self.provider = provider_for(host);
|
|
220
|
+
let direction = if outbound { "out" } else { "in" };
|
|
221
|
+
let rewritten = self
|
|
222
|
+
.rewrite(
|
|
223
|
+
"/v1/ws",
|
|
224
|
+
&[("x-piecemaker-ws", id.as_str()), ("x-piecemaker-direction", direction)],
|
|
225
|
+
Bytes::from(text.to_string()),
|
|
226
|
+
)
|
|
227
|
+
.await?;
|
|
228
|
+
if rewritten.is_empty() {
|
|
229
|
+
return Ok(None);
|
|
230
|
+
}
|
|
231
|
+
Ok(Some(Message::Text(String::from_utf8_lossy(&rewritten).into_owned().into())))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async fn flush_ws(&self, host: &str) -> Vec<String> {
|
|
235
|
+
let Some(id) = self.ws_id.clone() else {
|
|
236
|
+
return Vec::new();
|
|
237
|
+
};
|
|
238
|
+
let mut handler = self.clone();
|
|
239
|
+
handler.provider = provider_for(host);
|
|
240
|
+
match handler
|
|
241
|
+
.rewrite("/v1/ws-end", &[("x-piecemaker-ws", id.as_str())], Bytes::new())
|
|
242
|
+
.await
|
|
243
|
+
{
|
|
244
|
+
Ok(tail) => pending_messages(&tail),
|
|
245
|
+
Err(_) => Vec::new(),
|
|
246
|
+
}
|
|
247
|
+
}
|
|
192
248
|
}
|
|
193
249
|
|
|
194
250
|
impl HttpHandler for Handler {
|
|
@@ -208,55 +264,41 @@ impl HttpHandler for Handler {
|
|
|
208
264
|
}
|
|
209
265
|
|
|
210
266
|
async fn handle_request(&mut self, _ctx: &HttpContext, req: Request<Body>) -> RequestOrResponse {
|
|
211
|
-
if health_request(&req) {
|
|
212
|
-
return Response::builder()
|
|
213
|
-
.status(StatusCode::OK)
|
|
214
|
-
.body(Body::from("ok"))
|
|
215
|
-
.unwrap()
|
|
216
|
-
.into();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if req.method() == hudsucker::hyper::Method::CONNECT {
|
|
220
|
-
return req.into();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
267
|
let host = request_host(&req);
|
|
224
|
-
if
|
|
268
|
+
if req.method() == Method::GET && req.uri().path() == "/health" && (host == "127.0.0.1" || host == "localhost") {
|
|
269
|
+
return self.health().await.into();
|
|
270
|
+
}
|
|
271
|
+
if req.method() == Method::CONNECT || !self.allowed(&host) {
|
|
225
272
|
return req.into();
|
|
226
273
|
}
|
|
227
|
-
self.provider = provider_for(&host)
|
|
274
|
+
self.provider = provider_for(&host);
|
|
275
|
+
let path = uri_path(req.uri());
|
|
228
276
|
|
|
229
|
-
let
|
|
277
|
+
let req = match decode_request(req) {
|
|
278
|
+
Ok(req) => req,
|
|
279
|
+
Err(_) => return refusal(StatusCode::UNSUPPORTED_MEDIA_TYPE, &host, &path, "encodage de corps inconnu").into(),
|
|
280
|
+
};
|
|
281
|
+
let kind = content_type(req.headers());
|
|
230
282
|
let (mut parts, body) = req.into_parts();
|
|
231
283
|
parts.headers.remove("accept-encoding");
|
|
232
284
|
parts.headers.remove("sec-websocket-extensions");
|
|
233
285
|
|
|
234
|
-
let
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
match self
|
|
246
|
-
.post_bytes("/v1/request", &self.provider.clone(), &[], collected)
|
|
247
|
-
.await
|
|
248
|
-
{
|
|
249
|
-
Ok(rewritten) => {
|
|
250
|
-
if let Ok(length) = rewritten.len().to_string().parse() {
|
|
251
|
-
parts.headers.insert("content-length", length);
|
|
252
|
-
}
|
|
253
|
-
Body::from(rewritten)
|
|
254
|
-
}
|
|
255
|
-
Err(_) => return failure().into(),
|
|
286
|
+
let collected = match collect(body).await {
|
|
287
|
+
Ok(bytes) => bytes,
|
|
288
|
+
Err(cause) => return refusal(StatusCode::BAD_GATEWAY, &host, &path, cause).into(),
|
|
289
|
+
};
|
|
290
|
+
let body = if collected.is_empty() {
|
|
291
|
+
Body::empty()
|
|
292
|
+
} else if kind.contains("json") {
|
|
293
|
+
match self.rewrite("/v1/request", &[], collected).await {
|
|
294
|
+
Ok(rewritten) => {
|
|
295
|
+
parts.headers.insert("content-length", rewritten.len().into());
|
|
296
|
+
Body::from(rewritten)
|
|
256
297
|
}
|
|
298
|
+
Err(cause) => return refusal(StatusCode::BAD_GATEWAY, &host, &path, cause).into(),
|
|
257
299
|
}
|
|
258
300
|
} else {
|
|
259
|
-
|
|
301
|
+
return refusal(StatusCode::UNSUPPORTED_MEDIA_TYPE, &host, &path, "corps non JSON impossible à anonymiser").into();
|
|
260
302
|
};
|
|
261
303
|
|
|
262
304
|
let mut req = Request::from_parts(parts, body);
|
|
@@ -265,144 +307,93 @@ impl HttpHandler for Handler {
|
|
|
265
307
|
}
|
|
266
308
|
|
|
267
309
|
async fn handle_response(&mut self, _ctx: &HttpContext, res: Response<Body>) -> Response<Body> {
|
|
268
|
-
let
|
|
269
|
-
|
|
310
|
+
let res = match decode_response(res) {
|
|
311
|
+
Ok(res) => res,
|
|
312
|
+
Err(_) => return refusal(StatusCode::BAD_GATEWAY, self.provider, "", "réponse compressée illisible"),
|
|
313
|
+
};
|
|
314
|
+
let kind = content_type(res.headers());
|
|
270
315
|
let (mut parts, body) = res.into_parts();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
parts.headers.remove("transfer-encoding");
|
|
274
|
-
parts.headers.remove("connection");
|
|
275
|
-
|
|
276
|
-
if is_sse(&kind) {
|
|
277
|
-
parts.headers.remove("content-length");
|
|
278
|
-
parts.headers.insert(
|
|
279
|
-
"content-type",
|
|
280
|
-
"text/event-stream".parse().unwrap(),
|
|
281
|
-
);
|
|
282
|
-
return Response::from_parts(parts, self.spawn_sse(provider, body));
|
|
316
|
+
for name in ["content-encoding", "content-length", "transfer-encoding", "connection"] {
|
|
317
|
+
parts.headers.remove(name);
|
|
283
318
|
}
|
|
284
319
|
|
|
285
|
-
if
|
|
286
|
-
|
|
287
|
-
Ok(collected) => collected.to_bytes(),
|
|
288
|
-
Err(_) => return failure(),
|
|
289
|
-
};
|
|
290
|
-
if collected.len() > MAX_BODY {
|
|
291
|
-
return failure();
|
|
292
|
-
}
|
|
293
|
-
let rewritten = match self.post_bytes("/v1/response", &provider, &[], collected).await {
|
|
294
|
-
Ok(rewritten) => rewritten,
|
|
295
|
-
Err(_) => return failure(),
|
|
296
|
-
};
|
|
297
|
-
return Response::from_parts(parts, Body::from(rewritten));
|
|
320
|
+
if kind.contains("text/event-stream") {
|
|
321
|
+
return Response::from_parts(parts, self.spawn_sse(body));
|
|
298
322
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
impl WebSocketHandler for Handler {
|
|
305
|
-
async fn handle_message(&mut self, ctx: &WebSocketContext, message: Message) -> Option<Message> {
|
|
306
|
-
let (outbound, uri) = match ctx {
|
|
307
|
-
WebSocketContext::ClientToServer { dst, .. } => (true, dst),
|
|
308
|
-
WebSocketContext::ServerToClient { src, .. } => (false, src),
|
|
309
|
-
};
|
|
310
|
-
let host = uri.host().map(|value| value.to_ascii_lowercase()).unwrap_or_default();
|
|
311
|
-
if !self.allowed(&host) {
|
|
312
|
-
return Some(message);
|
|
323
|
+
if !kind.contains("json") {
|
|
324
|
+
return Response::from_parts(parts, body);
|
|
313
325
|
}
|
|
314
|
-
let
|
|
315
|
-
|
|
326
|
+
let rewritten = match collect(body).await {
|
|
327
|
+
Ok(bytes) => self.rewrite("/v1/response", &[], bytes).await,
|
|
328
|
+
Err(cause) => Err(cause),
|
|
316
329
|
};
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
let id = self.ws_id.clone().unwrap_or_default();
|
|
321
|
-
let direction = if outbound { "out" } else { "in" };
|
|
322
|
-
let provider = provider_for(&host);
|
|
323
|
-
match self
|
|
324
|
-
.post_bytes(
|
|
325
|
-
"/v1/ws",
|
|
326
|
-
provider,
|
|
327
|
-
&[
|
|
328
|
-
("x-piecemaker-ws", id.as_str()),
|
|
329
|
-
("x-piecemaker-direction", direction),
|
|
330
|
-
],
|
|
331
|
-
Bytes::from(text.to_string()),
|
|
332
|
-
)
|
|
333
|
-
.await
|
|
334
|
-
{
|
|
335
|
-
Ok(rewritten) if !rewritten.is_empty() => {
|
|
336
|
-
Some(Message::Text(String::from_utf8_lossy(&rewritten).into_owned().into()))
|
|
337
|
-
}
|
|
338
|
-
Ok(_) => None,
|
|
339
|
-
Err(_) => None,
|
|
330
|
+
match rewritten {
|
|
331
|
+
Ok(bytes) => Response::from_parts(parts, Body::from(bytes)),
|
|
332
|
+
Err(cause) => refusal(StatusCode::BAD_GATEWAY, self.provider, "", cause),
|
|
340
333
|
}
|
|
341
334
|
}
|
|
335
|
+
}
|
|
342
336
|
|
|
337
|
+
impl WebSocketHandler for Handler {
|
|
343
338
|
async fn handle_websocket(
|
|
344
339
|
mut self,
|
|
345
340
|
ctx: WebSocketContext,
|
|
346
|
-
mut stream: impl futures::Stream<Item = Result<Message,
|
|
347
|
-
|
|
348
|
-
+ Send
|
|
349
|
-
+ 'static,
|
|
350
|
-
mut sink: impl futures::Sink<Message, Error = hudsucker::tokio_tungstenite::tungstenite::Error>
|
|
351
|
-
+ Unpin
|
|
352
|
-
+ Send
|
|
353
|
-
+ 'static,
|
|
341
|
+
mut stream: impl futures::Stream<Item = Result<Message, WsError>> + Unpin + Send + 'static,
|
|
342
|
+
mut sink: impl futures::Sink<Message, Error = WsError> + Unpin + Send + 'static,
|
|
354
343
|
) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if closed {
|
|
374
|
-
break;
|
|
344
|
+
let (outbound, uri) = match &ctx {
|
|
345
|
+
WebSocketContext::ClientToServer { dst, .. } => (true, dst),
|
|
346
|
+
WebSocketContext::ServerToClient { src, .. } => (false, src),
|
|
347
|
+
};
|
|
348
|
+
let target = self.intercepted_host(uri);
|
|
349
|
+
let intercepted = target.is_some();
|
|
350
|
+
let host = target.unwrap_or_default();
|
|
351
|
+
|
|
352
|
+
while let Some(Ok(message)) = stream.next().await {
|
|
353
|
+
let frames = match (intercepted, message) {
|
|
354
|
+
(false, message) => vec![message],
|
|
355
|
+
(true, Message::Text(text)) => match self.rewrite_ws(&host, outbound, &text).await {
|
|
356
|
+
Ok(Some(message)) if outbound => vec![message],
|
|
357
|
+
Ok(Some(message)) => json_line_frames(message),
|
|
358
|
+
Ok(None) => continue,
|
|
359
|
+
Err(cause) => {
|
|
360
|
+
close_ws(&mut sink, &host, cause).await;
|
|
361
|
+
return;
|
|
375
362
|
}
|
|
363
|
+
},
|
|
364
|
+
(true, Message::Binary(_)) => {
|
|
365
|
+
close_ws(&mut sink, &host, "trame binaire impossible à anonymiser").await;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
(true, message) => vec![message],
|
|
369
|
+
};
|
|
370
|
+
for frame in frames {
|
|
371
|
+
if sink.send(frame).await.is_err() {
|
|
372
|
+
return;
|
|
376
373
|
}
|
|
377
|
-
Err(_) => break,
|
|
378
374
|
}
|
|
379
375
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if let Ok(tail) = self
|
|
386
|
-
.post_bytes(
|
|
387
|
-
"/v1/ws-end",
|
|
388
|
-
provider_for(&host_of(&host)),
|
|
389
|
-
&[("x-piecemaker-ws", id.as_str())],
|
|
390
|
-
Bytes::new(),
|
|
391
|
-
)
|
|
392
|
-
.await
|
|
393
|
-
{
|
|
394
|
-
if let Ok(messages) = serde_json_messages(&tail) {
|
|
395
|
-
for message in messages {
|
|
396
|
-
if sink.send(Message::Text(message.into())).await.is_err() {
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
376
|
+
|
|
377
|
+
if intercepted && !outbound {
|
|
378
|
+
for message in self.flush_ws(&host).await {
|
|
379
|
+
if sink.send(Message::Text(message.into())).await.is_err() {
|
|
380
|
+
return;
|
|
400
381
|
}
|
|
401
382
|
}
|
|
402
383
|
}
|
|
403
384
|
}
|
|
404
385
|
}
|
|
405
386
|
|
|
387
|
+
async fn close_ws(sink: &mut (impl futures::Sink<Message, Error = WsError> + Unpin), host: &str, cause: &str) {
|
|
388
|
+
eprintln!("refus websocket {host} : {cause}");
|
|
389
|
+
let frame = CloseFrame {
|
|
390
|
+
code: CloseCode::Error,
|
|
391
|
+
reason: "PieceMaker: anonymisation impossible".into(),
|
|
392
|
+
};
|
|
393
|
+
let _ = sink.send(Message::Close(Some(frame))).await;
|
|
394
|
+
let _ = sink.close().await;
|
|
395
|
+
}
|
|
396
|
+
|
|
406
397
|
fn json_line_frames(message: Message) -> Vec<Message> {
|
|
407
398
|
let Message::Text(text) = &message else {
|
|
408
399
|
return vec![message];
|
|
@@ -421,25 +412,23 @@ fn json_line_frames(message: Message) -> Vec<Message> {
|
|
|
421
412
|
.collect()
|
|
422
413
|
}
|
|
423
414
|
|
|
424
|
-
fn
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
.get("messages")
|
|
429
|
-
.and_then(|entry| entry.as_array())
|
|
415
|
+
fn pending_messages(bytes: &Bytes) -> Vec<String> {
|
|
416
|
+
serde_json::from_slice::<serde_json::Value>(bytes)
|
|
417
|
+
.ok()
|
|
418
|
+
.and_then(|value| value.get("messages").and_then(|entry| entry.as_array()).cloned())
|
|
430
419
|
.map(|entries| {
|
|
431
420
|
entries
|
|
432
421
|
.iter()
|
|
433
422
|
.filter_map(|entry| entry.as_str().map(str::to_string))
|
|
434
423
|
.collect()
|
|
435
424
|
})
|
|
436
|
-
.unwrap_or_default()
|
|
425
|
+
.unwrap_or_default()
|
|
437
426
|
}
|
|
438
427
|
|
|
439
428
|
fn parse_list(value: &str) -> HashSet<String> {
|
|
440
429
|
value
|
|
441
430
|
.split(',')
|
|
442
|
-
.map(
|
|
431
|
+
.map(host_of)
|
|
443
432
|
.filter(|entry| !entry.is_empty())
|
|
444
433
|
.collect()
|
|
445
434
|
}
|
|
@@ -449,17 +438,30 @@ fn parse_map(value: &str) -> HashMap<String, String> {
|
|
|
449
438
|
.split(',')
|
|
450
439
|
.filter_map(|entry| {
|
|
451
440
|
let (host, dest) = entry.split_once('=')?;
|
|
452
|
-
let host = host_of(host.trim());
|
|
453
|
-
|
|
454
|
-
if host.is_empty() || dest.is_empty() {
|
|
455
|
-
None
|
|
456
|
-
} else {
|
|
457
|
-
Some((host, dest))
|
|
458
|
-
}
|
|
441
|
+
let (host, dest) = (host_of(host), dest.trim().to_string());
|
|
442
|
+
(!host.is_empty() && !dest.is_empty()).then_some((host, dest))
|
|
459
443
|
})
|
|
460
444
|
.collect()
|
|
461
445
|
}
|
|
462
446
|
|
|
447
|
+
fn exit_when_parent_dies() {
|
|
448
|
+
std::thread::spawn(|| {
|
|
449
|
+
let mut buffer = [0u8; 256];
|
|
450
|
+
let mut stdin = std::io::stdin();
|
|
451
|
+
loop {
|
|
452
|
+
match stdin.read(&mut buffer) {
|
|
453
|
+
Ok(0) | Err(_) => std::process::exit(0),
|
|
454
|
+
Ok(_) => {}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
fn fail(message: String) -> ! {
|
|
461
|
+
eprintln!("{message}");
|
|
462
|
+
std::process::exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
463
465
|
#[tokio::main]
|
|
464
466
|
async fn main() {
|
|
465
467
|
let listen = arg("--listen");
|
|
@@ -469,63 +471,44 @@ async fn main() {
|
|
|
469
471
|
let hosts = parse_list(&arg("--hosts"));
|
|
470
472
|
let upstream = parse_map(&arg("--upstream-map"));
|
|
471
473
|
if listen.is_empty() || rewriter.is_empty() || ca_cert.is_empty() || ca_key.is_empty() || hosts.is_empty() {
|
|
472
|
-
eprintln!("usage: piecemaker-hudsucker --listen 127.0.0.1:
|
|
474
|
+
eprintln!("usage: piecemaker-hudsucker --listen 127.0.0.1:0 --rewriter http://127.0.0.1:port --ca-cert ca.crt --ca-key ca.key --hosts api.anthropic.com,api.openai.com,chatgpt.com");
|
|
473
475
|
std::process::exit(2);
|
|
474
476
|
}
|
|
477
|
+
exit_when_parent_dies();
|
|
475
478
|
|
|
476
|
-
let key = std::fs::read_to_string(&ca_key).unwrap_or_else(|error| {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
});
|
|
480
|
-
let cert = std::fs::read_to_string(&ca_cert).unwrap_or_else(|error| {
|
|
481
|
-
eprintln!("certificat d'autorité illisible : {error}");
|
|
482
|
-
std::process::exit(1);
|
|
483
|
-
});
|
|
484
|
-
let key_pair = KeyPair::from_pem(&key).unwrap_or_else(|error| {
|
|
485
|
-
eprintln!("clé d'autorité refusée : {error}");
|
|
486
|
-
std::process::exit(1);
|
|
487
|
-
});
|
|
488
|
-
let issuer = Issuer::from_ca_cert_pem(&cert, key_pair).unwrap_or_else(|error| {
|
|
489
|
-
eprintln!("certificat d'autorité refusé : {error}");
|
|
490
|
-
std::process::exit(1);
|
|
491
|
-
});
|
|
479
|
+
let key = std::fs::read_to_string(&ca_key).unwrap_or_else(|error| fail(format!("clé d'autorité illisible : {error}")));
|
|
480
|
+
let cert = std::fs::read_to_string(&ca_cert).unwrap_or_else(|error| fail(format!("certificat d'autorité illisible : {error}")));
|
|
481
|
+
let key_pair = KeyPair::from_pem(&key).unwrap_or_else(|error| fail(format!("clé d'autorité refusée : {error}")));
|
|
482
|
+
let issuer = Issuer::from_ca_cert_pem(&cert, key_pair).unwrap_or_else(|error| fail(format!("certificat d'autorité refusé : {error}")));
|
|
492
483
|
let authority = RcgenAuthority::new(issuer, 1_000, aws_lc_rs::default_provider());
|
|
493
|
-
let addr: SocketAddr = listen.parse().unwrap_or_else(|_|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
484
|
+
let addr: SocketAddr = listen.parse().unwrap_or_else(|_| fail("adresse d'écoute invalide".into()));
|
|
485
|
+
let listener = tokio::net::TcpListener::bind(addr)
|
|
486
|
+
.await
|
|
487
|
+
.unwrap_or_else(|error| fail(format!("écoute impossible sur {addr} : {error}")));
|
|
488
|
+
let bound = listener.local_addr().unwrap_or_else(|error| fail(format!("adresse d'écoute inconnue : {error}")));
|
|
497
489
|
let http = reqwest::Client::builder()
|
|
498
490
|
.no_proxy()
|
|
491
|
+
.connect_timeout(Duration::from_secs(2))
|
|
499
492
|
.build()
|
|
500
|
-
.unwrap_or_else(|error| {
|
|
501
|
-
eprintln!("client interne : {error}");
|
|
502
|
-
std::process::exit(1);
|
|
503
|
-
});
|
|
493
|
+
.unwrap_or_else(|error| fail(format!("client interne : {error}")));
|
|
504
494
|
let handler = Handler {
|
|
505
495
|
rewriter,
|
|
506
496
|
hosts: Arc::new(hosts),
|
|
507
497
|
upstream: Arc::new(upstream),
|
|
508
498
|
http,
|
|
509
|
-
provider: "claude"
|
|
499
|
+
provider: "claude",
|
|
510
500
|
ws_id: None,
|
|
511
501
|
};
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
.with_addr(addr)
|
|
502
|
+
let proxy = Proxy::builder()
|
|
503
|
+
.with_listener(listener)
|
|
515
504
|
.with_ca(authority)
|
|
516
505
|
.with_rustls_connector(aws_lc_rs::default_provider())
|
|
517
506
|
.with_http_handler(handler.clone())
|
|
518
507
|
.with_websocket_handler(handler)
|
|
519
508
|
.build()
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
Err(error) => {
|
|
523
|
-
eprintln!("proxy : {error}");
|
|
524
|
-
std::process::exit(1);
|
|
525
|
-
}
|
|
526
|
-
};
|
|
509
|
+
.unwrap_or_else(|error| fail(format!("proxy : {error}")));
|
|
510
|
+
println!("listening {bound}");
|
|
527
511
|
if let Err(error) = proxy.start().await {
|
|
528
|
-
|
|
529
|
-
std::process::exit(1);
|
|
512
|
+
fail(format!("proxy arrêté : {error}"));
|
|
530
513
|
}
|
|
531
514
|
}
|