Skip to content

Commit f9ddee9

Browse files
committed
fix(p2p_http): safely parse JSON body and validate peer endpoint in /p2p/connect
1 parent 460d0ec commit f9ddee9

1 file changed

Lines changed: 131 additions & 5 deletions

File tree

src/P2PHttp.cpp

Lines changed: 131 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,106 @@ namespace vix::p2p_http
267267
#endif
268268
}
269269

270+
// POST /p2p/connect (connect to a peer endpoint)
271+
if (opt.enable_peers)
272+
{
273+
const std::string path = join_prefix(base, "/connect");
274+
275+
app.post(path, [&runtime](vix::vhttp::Request &req, vix::vhttp::ResponseWrapper &res)
276+
{
277+
auto node = runtime.node();
278+
if (!node)
279+
{
280+
res.status(503).json(vix::json::o(
281+
"ok", false,
282+
"error", "p2p_node_unavailable"
283+
));
284+
return;
285+
}
286+
287+
// Expect JSON: { "host": "127.0.0.1", "port": 9002, "scheme": "tcp" }
288+
vix::json::Json body;
289+
try
290+
{
291+
body = req.json();
292+
}
293+
catch (...)
294+
{
295+
res.status(400).json(vix::json::o(
296+
"ok", false,
297+
"error", "invalid_json"
298+
));
299+
return;
300+
}
301+
302+
auto get_str = [&](const char *key, const std::string &fallback = "") -> std::string
303+
{
304+
if (const auto *v = vix::json::jget(body, key))
305+
{
306+
if (v->is_string())
307+
return v->get<std::string>();
308+
309+
if (v->is_number_integer())
310+
return std::to_string(v->get<long long>());
311+
}
312+
return fallback;
313+
};
314+
315+
auto get_ll = [&](const char *key, long long fallback = 0) -> long long
316+
{
317+
if (const auto *v = vix::json::jget(body, key))
318+
{
319+
if (v->is_number_integer())
320+
return v->get<long long>();
321+
322+
if (v->is_string())
323+
{
324+
try { return std::stoll(v->get<std::string>()); }
325+
catch (...) {}
326+
}
327+
}
328+
return fallback;
329+
};
330+
331+
const std::string host = get_str("host", "");
332+
const long long port_ll = get_ll("port", 0);
333+
std::string scheme = get_str("scheme", "tcp");
334+
if (scheme.empty())
335+
scheme = "tcp";
336+
337+
if (host.empty() || port_ll <= 0 || port_ll > 65535)
338+
{
339+
res.status(400).json(vix::json::o(
340+
"ok", false,
341+
"error", "invalid_endpoint",
342+
"hint", "expected {host, port, scheme?}"
343+
));
344+
return;
345+
}
346+
347+
vix::p2p::PeerEndpoint ep;
348+
ep.host = host;
349+
ep.port = static_cast<std::uint16_t>(port_ll);
350+
ep.scheme = scheme;
351+
352+
const bool started = node->connect(ep);
353+
354+
res.json(vix::json::o(
355+
"ok", true,
356+
"started", started,
357+
"endpoint", (scheme + "://" + host + ":" + std::to_string((int)ep.port))
358+
)); });
359+
360+
#if defined(VIX_P2P_HTTP_WITH_MIDDLEWARE)
361+
{
362+
vix::p2p_http::RouteOptions ro;
363+
ro.heavy = true;
364+
ro.require_auth = false; // ou true si tu veux protéger
365+
install_route_middlewares(app, path, ro, opt);
366+
}
367+
#endif
368+
}
369+
270370
// GET /p2p/status
271371
if (opt.enable_status)
272372
{
@@ -375,6 +475,31 @@ namespace vix::p2p_http
375475
std::vector<J::token> peers_arr;
376476
peers_arr.reserve(items.size());
377477

478+
auto hex2 = [](std::uint8_t b) -> char {
479+
b &= 0x0F;
480+
return (b < 10) ? char('0' + b) : char('a' + (b - 10));
481+
};
482+
483+
auto short_fp_bytes = [hex2](const std::vector<std::uint8_t> &v) -> std::string {
484+
if (v.empty()) return "";
485+
486+
const std::size_t n = v.size();
487+
const std::size_t take = std::min<std::size_t>(4, n);
488+
489+
std::string out;
490+
out.reserve(take * 2 + 10);
491+
492+
for (std::size_t i = 0; i < take; ++i)
493+
{
494+
const std::uint8_t b = v[i];
495+
out.push_back(hex2(std::uint8_t(b >> 4)));
496+
out.push_back(hex2(b));
497+
}
498+
499+
out += "..(" + std::to_string(n) + ")";
500+
return out;
501+
};
502+
378503
for (const auto &[peer_id, p] : items)
379504
{
380505
const std::string ep_str = endpoint_to_string(p.endpoint);
@@ -388,8 +513,8 @@ namespace vix::p2p_http
388513
}
389514

390515
const bool secure = p.meta.secure;
391-
const long long public_key_len = (long long)p.meta.public_key.size();
392-
const long long session_key_len = (long long)p.meta.session_key_32.size();
516+
const std::string pub_fp = short_fp_bytes(p.meta.public_key);
517+
const std::string sess_fp = short_fp_bytes(p.meta.session_key_32);
393518
const long long capabilities_count = (long long)p.meta.capabilities.size();
394519

395520
// Handshake block (optional)
@@ -435,20 +560,21 @@ namespace vix::p2p_http
435560

436561
"secure", secure,
437562
"capabilities_count", capabilities_count,
438-
"public_key_len", public_key_len,
439-
"session_key_len", session_key_len,
563+
"public_key_len", pub_fp,
564+
"public_key_fp", pub_fp,
565+
"session_key_len", sess_fp,
440566

441567
"last_seen_ms_ago", (long long)last_seen_ms_ago,
442568

443569
"has_handshake", has_hs,
444570
"handshake_stage", hs_stage,
445571
"handshake_age_ms", (long long)hs_age_ms,
446572

447-
// debug-friendly (safe, no secrets)
448573
"nonce_a", (long long)hs_nonce_a,
449574
"nonce_b", (long long)hs_nonce_b,
450575
"ts_ms", (long long)hs_ts_ms
451576
}));
577+
452578
}
453579

454580
res.json(J::obj({

0 commit comments

Comments
 (0)