fix(plugins): red de seguridad ante exit() en un gancho y globales del CMS
Tras la revision de codigo de 273c3dd:
- Un exit()/die() dentro de un gancho no es una excepcion y dejaba un cuerpo
que no era JSON. register_shutdown_function en _pluginLifecycle responde
{ok:false, error, messages:[lo impreso]} si el gancho no llego al final.
- event/plugin/origin se castean solo si son escalares (un array producia un
Warning delante del JSON).
- Los ganchos ven CURRENT_USER, menu, action, TABLE_PREFIX y SETTINGS como
un checkpoint desde addPlugins(); documentado en el contrato.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
3565abdd88
commit
ea8eb6dbcb
@@ -412,15 +412,32 @@ function _pluginLifecycle() {
|
|||||||
// respeta ficheros personalizados y puede haber entregado este y no aquel.
|
// respeta ficheros personalizados y puede haber entregado este y no aquel.
|
||||||
die(json_encode(["ok" => false, "error" => "El cargador de plugins de esta web no soporta ganchos (cms/lib/plugins/index.php desactualizado)"]));
|
die(json_encode(["ok" => false, "error" => "El cargador de plugins de esta web no soporta ganchos (cms/lib/plugins/index.php desactualizado)"]));
|
||||||
}
|
}
|
||||||
$event = (string) @$datos["event"];
|
// Red de seguridad: exit()/die() dentro de un gancho no es una excepcion y
|
||||||
$plugin = (string) @$datos["plugin"];
|
// se saltaria el die(json_encode(...)) de abajo dejando un cuerpo que no es
|
||||||
|
// JSON. Si al cerrar no hemos respondido, respondemos nosotros.
|
||||||
|
$GLOBALS["_pluginLifecycleRespondido"] = false;
|
||||||
|
register_shutdown_function(function () {
|
||||||
|
if (!empty($GLOBALS["_pluginLifecycleRespondido"])) return;
|
||||||
|
$salida = "";
|
||||||
|
while (ob_get_level() > 0) { $salida .= (string) ob_get_clean(); }
|
||||||
|
echo json_encode(array(
|
||||||
|
"ok" => false,
|
||||||
|
"error" => "El gancho ha terminado con exit()/die() sin devolver resultado",
|
||||||
|
"messages" => $salida !== "" ? array(trim($salida)) : array(),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
$event = is_scalar(@$datos["event"]) ? (string) $datos["event"] : "";
|
||||||
|
$plugin = is_scalar(@$datos["plugin"]) ? (string) $datos["plugin"] : "";
|
||||||
|
$origin = is_scalar(@$datos["origin"]) ? (string) $datos["origin"] : "";
|
||||||
$context = array(
|
$context = array(
|
||||||
"origin" => in_array(@$datos["origin"], array("forge", "legacy"), true) ? $datos["origin"] : "desconocido",
|
"origin" => in_array($origin, array("forge", "legacy"), true) ? $origin : "desconocido",
|
||||||
"dropData" => filter_var(@$datos["dropData"], FILTER_VALIDATE_BOOLEAN),
|
"dropData" => filter_var(@$datos["dropData"], FILTER_VALIDATE_BOOLEAN),
|
||||||
// Misma señal de entorno que header.php: el .acai solo existe en las copias locales.
|
// Misma señal de entorno que header.php: el .acai solo existe en las copias locales.
|
||||||
"mode" => file_exists(__DIR__ . "/../../../.acai") ? "local" : "production",
|
"mode" => file_exists(__DIR__ . "/../../../.acai") ? "local" : "production",
|
||||||
);
|
);
|
||||||
die(json_encode(runPluginLifecycle($plugin, $event, $context)));
|
$resultado = runPluginLifecycle($plugin, $event, $context);
|
||||||
|
$GLOBALS["_pluginLifecycleRespondido"] = true;
|
||||||
|
die(json_encode($resultado));
|
||||||
}
|
}
|
||||||
|
|
||||||
function _removeFileRutaSegura($ruta, $esPlugin) {
|
function _removeFileRutaSegura($ruta, $esPlugin) {
|
||||||
|
|||||||
@@ -369,6 +369,10 @@ function query($sql)
|
|||||||
// simplemente terminar. status.php devuelve semaforos:
|
// simplemente terminar. status.php devuelve semaforos:
|
||||||
// [["clave" => "...", "estado" => "ok|warn|error", "texto" => "..."], ...].
|
// [["clave" => "...", "estado" => "ok|warn|error", "texto" => "..."], ...].
|
||||||
// Lo que el gancho imprima se recoge como mensaje, nunca sale por la respuesta.
|
// Lo que el gancho imprima se recoge como mensaje, nunca sale por la respuesta.
|
||||||
|
// El gancho ve $CURRENT_USER, $menu, $action, $TABLE_PREFIX y $SETTINGS como un
|
||||||
|
// checkpoint; cualquier otro global lo declara el. NUNCA exit()/die(): no es una
|
||||||
|
// excepcion, se salta el resultado y solo la red de seguridad de handler_ws.php
|
||||||
|
// evita una respuesta que no sea JSON.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
function pluginLifecycleEvents() {
|
function pluginLifecycleEvents() {
|
||||||
return array("install", "uninstall", "enable", "disable", "status");
|
return array("install", "uninstall", "enable", "disable", "status");
|
||||||
@@ -410,7 +414,9 @@ function installPluginArtifacts($path, &$messages) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _includePluginHook($hookFile, $plugin, $pluginDir, $event, $context, $dropData) {
|
function _includePluginHook($hookFile, $plugin, $pluginDir, $event, $context, $dropData) {
|
||||||
// Ambito propio: el gancho no ve las variables de quien lo llama.
|
// Ambito propio: el gancho no ve las variables de quien lo llama, pero si
|
||||||
|
// los mismos globales del CMS que ve un checkpoint desde addPlugins().
|
||||||
|
global $CURRENT_USER, $menu, $action, $TABLE_PREFIX, $SETTINGS;
|
||||||
ob_start();
|
ob_start();
|
||||||
$ret = include $hookFile;
|
$ret = include $hookFile;
|
||||||
$salida = trim((string) ob_get_clean());
|
$salida = trim((string) ob_get_clean());
|
||||||
|
|||||||
Reference in New Issue
Block a user