From d757bba23f872b56e4fe78d05f78676be308d5ae Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:25:26 -0500 Subject: [PATCH] descargar_orden: reescribir sin _helpers para evitar ob_start y Content-Type:json; fix .bin -> extension real --- api/lab/descargar_orden.php | 145 ++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/api/lab/descargar_orden.php b/api/lab/descargar_orden.php index 5270c6a..ef3f608 100644 --- a/api/lab/descargar_orden.php +++ b/api/lab/descargar_orden.php @@ -1,14 +1,147 @@ 'No autorizado']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + http_response_code(405); + exit; +} + +$fileParam = trim($_GET['file'] ?? ''); + +// Rechazar rutas con traversal +if (!$fileParam || strpos($fileParam, '..') !== false) { + http_response_code(400); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Parámetro inválido']); + exit; +} + +$projectRoot = realpath(__DIR__ . '/../../'); +$uploadsBase = realpath($projectRoot . '/uploads'); + +// Resolver ruta física +if (preg_match('#^uploads/#', $fileParam)) { + $path = realpath($projectRoot . '/' . $fileParam); +} elseif (preg_match('/^[a-zA-Z0-9_\-\.]+$/', $fileParam)) { + $path = realpath($projectRoot . '/uploads/media/' . $fileParam); +} else { + http_response_code(400); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Nombre de archivo inválido']); + exit; +} + +// Verificar que existe y está dentro de uploads/ +if (!$path || !$uploadsBase || strpos($path, $uploadsBase) !== 0 || !is_file($path)) { + http_response_code(404); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Archivo no encontrado']); + exit; +} + +// ── Detección MIME en 3 capas ───────────────────────────────────────── +$mime = ''; + +// Capa 1: finfo +if (function_exists('finfo_open')) { + $fi = finfo_open(FILEINFO_MIME_TYPE); + $mime = $fi ? (string)finfo_file($fi, $path) : ''; + if ($fi) finfo_close($fi); + // Quitar parámetros extras (ej: "image/jpeg; charset=binary") + if ($mime && strpos($mime, ';') !== false) { + $mime = trim(explode(';', $mime)[0]); + } +} + +// Capa 2: magic bytes manuales (más fiable que finfo para archivos renombrados como .bin) +if (!$mime || $mime === 'application/octet-stream') { + $fh = fopen($path, 'rb'); + $bytes = $fh ? fread($fh, 16) : ''; + if ($fh) fclose($fh); + + if (substr($bytes, 0, 3) === "\xFF\xD8\xFF") { + $mime = 'image/jpeg'; + } elseif (substr($bytes, 0, 8) === "\x89PNG\r\n\x1a\n") { + $mime = 'image/png'; + } elseif (substr($bytes, 0, 5) === '%PDF-') { + $mime = 'application/pdf'; + } elseif (substr($bytes, 0, 6) === 'GIF89a' || substr($bytes, 0, 6) === 'GIF87a') { + $mime = 'image/gif'; + } elseif (substr($bytes, 0, 4) === 'RIFF' && substr($bytes, 8, 4) === 'WEBP') { + $mime = 'image/webp'; + } elseif (substr($bytes, 0, 4) === "\xD0\xCF\x11\xE0") { + $mime = 'application/msword'; // DOC clásico + } elseif (substr($bytes, 0, 2) === 'PK') { + $mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; // DOCX/ZIP + } +} + +// Capa 3: fallback por extensión del nombre original +$ext = strtolower(pathinfo($fileParam, PATHINFO_EXTENSION)); +if (!$mime || $mime === 'application/octet-stream') { + $mimeByExt = [ + 'pdf' => 'application/pdf', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'webp' => 'image/webp', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ]; + $mime = $mimeByExt[$ext] ?? 'application/octet-stream'; +} + +// ── Nombre de descarga: si era .bin, usar la extensión real ────────── +$extByMime = [ + 'application/pdf' => 'pdf', + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + 'image/webp' => 'webp', + 'application/msword' => 'doc', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', +]; +$basename = basename($fileParam); +$downloadName = $basename; +if ($ext === 'bin' && isset($extByMime[$mime])) { + $stem = substr($basename, 0, strrpos($basename, '.')); + $downloadName = $stem . '.' . $extByMime[$mime]; +} + +// ── Disposición: inline para imágenes/PDF, attachment para el resto ── +$inlineMimes = ['application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'image/webp']; +$disposition = in_array($mime, $inlineMimes, true) ? 'inline' : 'attachment'; + +// ── Enviar archivo ─────────────────────────────────────────────────── +header('Content-Type: ' . $mime); +header('Content-Disposition: ' . $disposition . '; filename="' . addslashes($downloadName) . '"'); +header('Content-Length: ' . filesize($path)); +header('Cache-Control: private, max-age=3600'); +header('X-Content-Type-Options: nosniff'); + +readfile($path); +exit; + $fileParam = $_GET['file'] ?? '';