diff --git a/backend/composer.lock b/backend/composer.lock index 304dfce..07a740a 100644 --- a/backend/composer.lock +++ b/backend/composer.lock @@ -8,11 +8,11 @@ "packages": [ { "name": "tpsoft/apilite", - "version": "v1.0.2", + "version": "v1.0.3", "source": { "type": "git", "url": "https://gitea.tpsoft.org/TPsoft.org/APIlite.git", - "reference": "2d3f8bfdd46d0d304bac44057ff8444bfb2a4a17" + "reference": "c0fd7b3fe5270ee44a84a92e9255ada2438812b7" }, "require": { "php": ">=8.2" @@ -49,15 +49,15 @@ "type": "other" } ], - "time": "2025-06-12T05:54:37+00:00" + "time": "2025-10-13T21:36:34+00:00" }, { "name": "tpsoft/dbmodel", - "version": "v1.0.4", + "version": "v1.0.5", "source": { "type": "git", "url": "https://gitea.tpsoft.org/TPsoft.org/DBmodel.git", - "reference": "ae59ffaa97094854bcd1d863c6648a5b4dada671" + "reference": "80e889946bc4e38e987f46a13f95ee177ea934dc" }, "require": { "ext-pdo": "*", @@ -85,7 +85,9 @@ "keywords": [ "db", "model", - "pdo" + "mysql", + "pdo", + "sqlite" ], "funding": [ { @@ -93,7 +95,7 @@ "type": "other" } ], - "time": "2025-06-15T17:07:42+00:00" + "time": "2025-10-13T21:26:19+00:00" } ], "packages-dev": [], diff --git a/backend/src/API.php b/backend/src/API.php index 00e33aa..c5284ab 100644 --- a/backend/src/API.php +++ b/backend/src/API.php @@ -187,7 +187,7 @@ class API extends APIlite if (!is_array($data)) return false; $base64 = preg_replace('/^data:.*?;base64,/', '', $data['base64']); $base64_data = base64_decode($base64); - $filename = 'report_' . $report_id . '_' . time() . '_' . sanitizeFilename($data['filename']); + $filename = 'report_' . $report_id . '_' . time() . '_' . $this->sanitizeFilename($data['filename']); file_put_contents(UPLOAD_DIR_ATTACHMENTS . $filename, $base64_data); $attachment_content = $filename; } @@ -201,6 +201,26 @@ class API extends APIlite return $suc !== false; } + private function sanitizeFilename($filename, $allowedExtensions = []) + { + // Rozdelenie názvu a prípony + $pathInfo = pathinfo($filename); + $name = $pathInfo['filename'] ?? 'file'; + $extension = strtolower($pathInfo['extension'] ?? ''); + // Odstránenie nebezpečných znakov z názvu + $name = preg_replace('/[^a-zA-Z0-9_-]/', '_', $name); + $name = substr($name, 0, 100); // voliteľné obmedzenie dĺžky + // Validácia prípony, ak je zoznam povolený + if ( + $allowedExtensions + && count($allowedExtensions) > 0 + && !in_array($extension, $allowedExtensions) + ) { + $extension = 'bin'; // fallback ak prípona nie je povolená + } + return $name . '.' . $extension; + } + /** * Update report attachment * @@ -236,7 +256,7 @@ class API extends APIlite ->toArray(); if (is_array($all)) foreach ($all as $key => $row) { if ($all[$key]['attachment_type'] == 'file') { - $all[$key]['attachment_content'] = UPLOAD_URL_ATTACHMENTS . $all[$key]['attachment_content']; + $all[$key]['attachment_content'] = '?action=attachmentDownload&filename=' . $all[$key]['attachment_content']; } } return $all; @@ -254,4 +274,28 @@ class API extends APIlite $suc = $attachments->attachment($attachment_id, null); return $suc !== false; } + + /** + * Download report attachment + * + * @param string $filename + * + * @return void + */ + public function attachmentDownload(string $filename): void { + $filename = $this->sanitizeFilename($filename); + $filename = UPLOAD_DIR_ATTACHMENTS . $filename; + if (file_exists($filename)) { + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . basename($filename) . '"'); + header('Expires: 0'); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + header('Content-Length: ' . filesize($filename)); + readfile($filename); + exit; + } + } + } diff --git a/frontend/src/backend.js b/frontend/src/backend.js index f57a56c..fa3ac52 100644 --- a/frontend/src/backend.js +++ b/frontend/src/backend.js @@ -2,10 +2,10 @@ * Generated by APIlite * https://gitea.tpsoft.org/TPsoft.org/APIlite * - * 2025-10-01 00:23:37 */ + * 2025-10-16 01:38:51 */ class backend { - endpont = import.meta.env.VITE_BACKENDAPI_URL; + endpoint = import.meta.env.VITE_BACKENDAPI_URL; /* ---------------------------------------------------- * General API call @@ -28,7 +28,7 @@ class backend { if (typeof val == 'object') val = JSON.stringify(val); form_data.append(key, val); }); - xhttp.open('POST', this.endpont + '?action=' + method); + xhttp.open('POST', this.endpoint + '?action=' + method); xhttp.send(form_data); } @@ -107,6 +107,10 @@ class backend { return this.callPromise('attachmentDelete', {attachment_id: attachment_id}); } + attachmentDownload(filename) { + return this.callPromise('attachmentDownload', {filename: filename}); + } + }; diff --git a/frontend/src/views/Report.vue b/frontend/src/views/Report.vue index 981e0b9..edd6bb6 100644 --- a/frontend/src/views/Report.vue +++ b/frontend/src/views/Report.vue @@ -89,9 +89,9 @@ v-else-if="attachment.attachment_type == 'file'" class="attachment-file" > - Stiahnut {{ attachment.attachment_content.split('/').pop().split('?')[0].split('#')[0] }} + Stiahnut {{ attachment.attachment_content.split('/').pop().split('?')[0].split('#')[0] }}
- +
Neznamy typ prilohy: {{ attachment.attachment_type }} @@ -144,7 +144,9 @@
-