diff --git a/public_html/entreaulas/comedor.php b/public_html/entreaulas/comedor.php index e9c103d..6b078c5 100644 --- a/public_html/entreaulas/comedor.php +++ b/public_html/entreaulas/comedor.php @@ -128,10 +128,20 @@ function safe_filename($name) { // Normalize to base name to avoid directory traversal $name = basename($name); + + // Best-effort normalize encoding to avoid odd Unicode tricks + if (function_exists('mb_convert_encoding')) { + $name = mb_convert_encoding($name, 'UTF-8', 'UTF-8'); + } + // Replace disallowed characters with underscore $name = preg_replace("/[^a-zA-Z0-9._-]/", "_", $name); + // Collapse multiple underscores introduced by replacement + $name = preg_replace('/_+/', '_', $name); + // Remove leading dots to avoid hidden/special files like ".htaccess" $name = ltrim($name, '.'); + // Ensure there is at most one dot in the filename to prevent extension confusion if (substr_count($name, '.') > 1) { $parts = explode('.', $name); @@ -144,6 +154,34 @@ function safe_filename($name) $name = ($base === '' ? 'file' : $base) . '.' . $ext; } } + + // Trim stray dots/underscores from the start and end + $name = trim($name, "._"); + + // Enforce a maximum length (common filesystem limit is 255 bytes) + $maxLen = 255; + if (strlen($name) > $maxLen) { + $dotPos = strrpos($name, '.'); + if ($dotPos !== false) { + $ext = substr($name, $dotPos); + $base = substr($name, 0, $dotPos); + $baseMaxLen = $maxLen - strlen($ext); + if ($baseMaxLen < 1) { + // Fallback if extension is unusually long + $name = substr($name, 0, $maxLen); + } else { + $name = substr($base, 0, $baseMaxLen) . $ext; + } + } else { + $name = substr($name, 0, $maxLen); + } + } + + // Ensure we never return an empty or invalid filename + if ($name === '' || $name === '.' || $name === '..') { + $name = 'file'; + } + return $name; } @@ -153,11 +191,63 @@ function handle_image_upload($fieldName, $targetBaseName, $baseDir, &$uploadErro return null; } $ext = strtolower(pathinfo($_FILES[$fieldName]["name"], PATHINFO_EXTENSION)); - $allowed = ["jpg", "jpeg", "png", "webp", "gif"]; + $allowed = ["jpg", "jpeg", "png", "webp", "gif"]; + + // Validate by extension first if (!in_array($ext, $allowed, true)) { $uploadErrors[] = "El archivo " . htmlspecialchars($_FILES[$fieldName]["name"]) . " no es una imagen válida."; return null; } + + // Also validate by MIME type / file contents to avoid spoofed extensions + $tmpPath = $_FILES[$fieldName]["tmp_name"]; + $mimeType = null; + + if (function_exists('finfo_open')) { + $finfo = @finfo_open(FILEINFO_MIME_TYPE); + if ($finfo !== false) { + $mime = @finfo_file($finfo, $tmpPath); + if ($mime !== false) { + $mimeType = $mime; + } + @finfo_close($finfo); + } + } + + // Fallback: try exif_imagetype if available and finfo did not work + if ($mimeType === null && function_exists('exif_imagetype')) { + $type = @exif_imagetype($tmpPath); + if ($type !== false) { + switch ($type) { + case IMAGETYPE_JPEG: + $mimeType = 'image/jpeg'; + break; + case IMAGETYPE_PNG: + $mimeType = 'image/png'; + break; + case IMAGETYPE_GIF: + $mimeType = 'image/gif'; + break; + case IMAGETYPE_WEBP: + $mimeType = 'image/webp'; + break; + } + } + } + + $allowedMime = [ + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'webp' => 'image/webp', + ]; + + if ($mimeType === null || !in_array($mimeType, $allowedMime, true)) { + $uploadErrors[] = "El archivo " . htmlspecialchars($_FILES[$fieldName]["name"]) . " no es una imagen válida."; + return null; + } + if (!is_dir($baseDir)) { mkdir($baseDir, 0777, true); } diff --git a/public_html/sysadmin/users.php b/public_html/sysadmin/users.php index 29b4054..c74a402 100644 --- a/public_html/sysadmin/users.php +++ b/public_html/sysadmin/users.php @@ -12,6 +12,13 @@ function safe_username($value) return $value; } +define('USERS_DIR', '/DATA/Usuarios/'); + +function get_user_file_path($username) +{ + return USERS_DIR . $username . '.json'; +} + function safe_centro_id($value) { return preg_replace('/[^0-9]/', '', (string)$value); @@ -29,7 +36,7 @@ switch ($_GET['form'] ?? '') { if (empty($username)) { die("Nombre de usuario no proporcionado."); } - $user_file = "/DATA/Usuarios/$username.json"; + $user_file = get_user_file_path($username); $userdata_old = []; if (is_readable($user_file)) { $file_contents = file_get_contents($user_file); @@ -63,7 +70,28 @@ switch ($_GET['form'] ?? '') { ]; // Merge old and new data to preserve any other fields, like password hashes or custom metadata. $userdata = array_merge($userdata_old, $userdata_new); - file_put_contents("/DATA/Usuarios/$username.json", json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + $user_dir = rtrim(USERS_DIR, '/'); + $user_file = get_user_file_path($username); + if (!is_dir($user_dir) || !is_writable($user_dir)) { + die("No se puede guardar el usuario: directorio de datos no disponible."); + } + $json_data = json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + if ($json_data === false) { + die("No se puede guardar el usuario: error al codificar los datos."); + } + $tmp_file = tempnam($user_dir, 'user_'); + if ($tmp_file === false) { + die("No se puede guardar el usuario: no se pudo crear un archivo temporal."); + } + $bytes_written = file_put_contents($tmp_file, $json_data, LOCK_EX); + if ($bytes_written === false) { + @unlink($tmp_file); + die("No se puede guardar el usuario: error al escribir en el disco."); + } + if (!rename($tmp_file, $user_file)) { + @unlink($tmp_file); + die("No se puede guardar el usuario: no se pudo finalizar la grabación del archivo."); + } header("Location: ?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Cambios guardados correctamente a las ".date("H:i:s")." (hora servidor).")); exit; break; @@ -151,11 +179,18 @@ switch ($_GET['action'] ?? '') { if (empty($username)) { die("Nombre de usuario inválido."); } - $user_file = "/DATA/Usuarios/$username.json"; + $user_file = get_user_file_path($username); if (!file_exists($user_file) || !is_readable($user_file)) { die("Usuario no encontrado o datos no disponibles."); } - $userdata = json_decode(file_get_contents($user_file), true) ?? []; + $jsonContent = file_get_contents($user_file); + if ($jsonContent === false) { + die("Error al leer los datos del usuario."); + } + $userdata = json_decode($jsonContent, true); + if (!is_array($userdata) || json_last_error() !== JSON_ERROR_NONE) { + die("Datos de usuario corruptos o con formato inválido."); + } ?>
@@ -300,9 +335,13 @@ switch ($_GET['action'] ?? '') { ";