70 lines
3.4 KiB
PHP
70 lines
3.4 KiB
PHP
<?php
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
// PURPOSE
|
|
// Accept one mesh message from a partner and store it. Invoked over SSH by the sending node's
|
|
// vv_nc_deliver(), with the record on stdin.
|
|
//
|
|
// OPERATIONAL MODEL
|
|
// php node_chat_receive.php --channel=<id> record on stdin, one JSON object
|
|
// php node_chat_receive.php --flush retry this host's own undelivered spool
|
|
//
|
|
// Exit 0 means stored. The sender treats anything else as undelivered and spools for retry, so
|
|
// a non-zero exit here is a message that will arrive later rather than one that is lost.
|
|
//
|
|
// OPERATIONAL SAFEGUARDS
|
|
// Reached only over SSH with a key this mesh installed, so the caller already has root. This
|
|
// file therefore validates shape, not authority — there is no privilege here to protect that
|
|
// the transport has not already granted.
|
|
//
|
|
// The channel is resolved against this host's own membership. A name that is not a channel
|
|
// this machine belongs to is refused, so the argument cannot address a path outside the store.
|
|
//
|
|
// Storage is append-and-trim through vv_nc_append(), which is idempotent on message id — a
|
|
// retry of something that already landed is a no-op rather than a duplicate.
|
|
//
|
|
// DEPENDS ON
|
|
// include/node_chat.php vv_nc_append(), vv_nc_channel(), vv_nc_flush_spool()
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
require_once dirname(__DIR__) . '/include/node_chat.php';
|
|
|
|
$args = $argv ?? [];
|
|
|
|
if (in_array('--flush', $args, true)) {
|
|
$n = vv_nc_flush_spool();
|
|
if ($n) echo "delivered $n queued message(s)\n";
|
|
exit(0);
|
|
}
|
|
|
|
$channel = '';
|
|
foreach ($args as $a) {
|
|
if (str_starts_with($a, '--channel=')) $channel = substr($a, 10);
|
|
}
|
|
if ($channel === '' || !vv_nc_channel($channel)) {
|
|
fwrite(STDERR, "unknown channel\n");
|
|
exit(2);
|
|
}
|
|
|
|
$raw = stream_get_contents(STDIN);
|
|
$msg = json_decode((string)$raw, true);
|
|
if (!is_array($msg) || empty($msg['id']) || !isset($msg['text'])) {
|
|
fwrite(STDERR, "malformed message\n");
|
|
exit(2);
|
|
}
|
|
|
|
// Rebuilt rather than stored as sent: a record is only ever the fields this store understands,
|
|
// so a sender running newer code cannot write keys this one will later hand to a page.
|
|
$clean = [
|
|
'id' => preg_replace('/[^a-f0-9]/i', '', (string)$msg['id']),
|
|
'ts' => (int)($msg['ts'] ?? time()),
|
|
'from' => preg_replace('/[^a-z0-9]/i', '', strtolower((string)($msg['from'] ?? ''))),
|
|
'kind' => in_array($msg['kind'] ?? 'msg', ['msg', 'question', 'notice'], true) ? $msg['kind'] : 'msg',
|
|
'text' => mb_substr((string)$msg['text'], 0, 4000),
|
|
'style' => [
|
|
'color' => preg_match('/^#[0-9a-f]{6}$/i', $msg['style']['color'] ?? '') ? $msg['style']['color'] : '',
|
|
'font' => in_array($msg['style']['font'] ?? '', ['mono','sans','serif'], true) ? $msg['style']['font'] : '',
|
|
],
|
|
];
|
|
if ($clean['id'] === '' || $clean['from'] === '') { fwrite(STDERR, "malformed message\n"); exit(2); }
|
|
|
|
exit(vv_nc_append($channel, $clean) ? 0 : 1);
|