diff --git a/.gitignore b/.gitignore index c90ad4e..e484ad2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .DS_Store *.swp -uploads/ +uploads/* +!uploads/.htaccess diff --git a/admin/view-doc.php b/admin/view-doc.php index 31eaff8..4ff155f 100644 --- a/admin/view-doc.php +++ b/admin/view-doc.php @@ -19,7 +19,6 @@ if (!$ref || !$type) { } $col = $type === 'license' ? 'license_file' : 'insurance_file'; -$row = db()->prepare("SELECT {$col} AS file_path FROM bookings WHERE booking_ref=?")->execute([$ref]) ? null : null; $stmt = db()->prepare("SELECT {$col} AS file_path FROM bookings WHERE booking_ref=?"); $stmt->execute([$ref]); $row = $stmt->fetch(); @@ -30,17 +29,27 @@ if (!$row || !$row['file_path']) { exit('No document on file.'); } -// Path stored as uploads/{ref}/{filename} -$path = __DIR__ . '/../' . ltrim($row['file_path'], '/'); -if (!file_exists($path)) { +$base = realpath(dirname(__DIR__) . '/uploads'); +$path = realpath(dirname(__DIR__) . '/' . $row['file_path']); + +if (!$path || !$base || strpos($path, $base . DIRECTORY_SEPARATOR) !== 0) { http_response_code(404); header('Content-Type: text/plain'); exit('File not found.'); } -$mime = mime_content_type($path) ?: 'application/octet-stream'; +$finfo = new finfo(FILEINFO_MIME_TYPE); +$mime = $finfo->file($path); +$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf']; +if (!isset($allowed[$mime])) { + http_response_code(403); + header('Content-Type: text/plain'); + exit('Invalid file type.'); +} + +$fname = $type . '-' . $ref . '.' . $allowed[$mime]; header('Content-Type: ' . $mime); -header('Content-Disposition: inline; filename="' . basename($path) . '"'); +header('Content-Disposition: inline; filename="' . $fname . '"'); header('Content-Length: ' . filesize($path)); header('Cache-Control: no-store, no-cache'); readfile($path); diff --git a/uploads/.htaccess b/uploads/.htaccess new file mode 100644 index 0000000..1975525 --- /dev/null +++ b/uploads/.htaccess @@ -0,0 +1,8 @@ +# Block direct web access — documents served only through admin/view-doc.php + + Require all denied + + + Order deny,allow + Deny from all +