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] }}
-
+