Files
whatsapp/api/lab/descargar_orden.php
T

125 lines
4.3 KiB
PHP

<?php
/**
* GET /api/lab/descargar_orden.php?file=ruta_relativa_o_nombre
* Sirve archivos de órdenes médicas con el Content-Type correcto.
*
* El parámetro 'file' puede ser:
* - Un nombre simple: orden_20240101_abcd.pdf
* - Una ruta relativa: uploads/media/2026/03/m_XXXX.bin
*/
require_once __DIR__ . '/_helpers.php';
requireMethod('GET');
$fileParam = $_GET['file'] ?? '';
// Rechazar inmediatamente rutas con ..
if (!$fileParam || strpos($fileParam, '..') !== false) {
http_response_code(400);
echo json_encode(['error' => 'Archivo inválido']);
exit;
}
$projectRoot = realpath(__DIR__ . '/../../');
$uploadsBase = realpath($projectRoot . '/uploads');
// Resolver la ruta completa:
// - Si ya empieza con uploads/ → resolver desde raíz del proyecto
// - Si es solo un nombre de archivo → buscar en uploads/media/
if (preg_match('#^uploads/#', $fileParam)) {
$path = realpath($projectRoot . '/' . $fileParam);
} else {
// Solo nombre: solo aceptar alfanumérico + guiones/puntos, sin slashes
if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $fileParam)) {
http_response_code(400);
echo json_encode(['error' => 'Archivo inválido']);
exit;
}
$path = realpath($projectRoot . '/uploads/media/' . $fileParam);
}
$base = $uploadsBase;
// Verificar que el archivo esté dentro del directorio permitido
if (!$path || !$base || strpos($path, $base) !== 0 || !is_file($path)) {
http_response_code(404);
echo json_encode(['error' => 'Archivo no encontrado']);
exit;
}
// Detectar MIME real — 3 capas para máxima fiabilidad con archivos .bin
// Capa 1: finfo (usa magic bytes internamente)
$mime = '';
if (function_exists('finfo_open')) {
$fi = finfo_open(FILEINFO_MIME_TYPE);
$mime = $fi ? (string)finfo_file($fi, $path) : '';
if ($fi) finfo_close($fi);
}
// Capa 2: Lectura manual de magic bytes (funciona aunque finfo falle o devuelva octet-stream)
if (!$mime || $mime === 'application/octet-stream') {
$handle = fopen($path, 'rb');
$bytes = $handle ? fread($handle, 12) : '';
if ($handle) fclose($handle);
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, 2) === 'PK') {
$mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
}
// Capa 3: Fallback por extensión declarada
$ext = strtolower(pathinfo($fileParam, PATHINFO_EXTENSION));
$mimeMap = [
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
];
if (!$mime || $mime === 'application/octet-stream') {
$mime = $mimeMap[$ext] ?? 'application/octet-stream';
}
// Tipos que el navegador puede mostrar inline
$inlineTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$disposition = in_array($mime, $inlineTypes) ? 'inline' : 'attachment';
// Si el archivo fue guardado con extensión .bin, usar la correcta según MIME
$mimeToExt = [
'application/pdf' => 'pdf',
'application/msword' => 'doc',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
];
$downloadExt = ($ext === 'bin' && isset($mimeToExt[$mime])) ? $mimeToExt[$mime] : $ext;
$downloadName = basename($fileParam);
if ($ext === 'bin' && isset($mimeToExt[$mime])) {
$downloadName = substr($downloadName, 0, strrpos($downloadName, '.') + 1) . $downloadExt;
}
header('Content-Type: ' . $mime);
header('Content-Disposition: ' . $disposition . '; filename="' . rawurlencode($downloadName) . '"');
header('Content-Length: ' . filesize($path));
header('Cache-Control: private, max-age=3600');
header('X-Content-Type-Options: nosniff');
readfile($path);
exit;