97 lines
6.6 KiB
PHP
97 lines
6.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Dateianhänge über die öffentliche Ticket-Schnittstelle (includes/integrations.php, zum Beispiel für das WordPress-Plugin): Grenzen werden im
|
|
* Formular-Angebot genannt, Anhänge werden vor dem Anlegen geprüft (Anzahl, Größe, Endung, Inhalt, Base64) und danach an die erste
|
|
* Nachricht des Tickets gehängt; ein eigenes Formular kann Anhänge abschalten.
|
|
*/
|
|
|
|
$uploads = sys_get_temp_dir() . '/deskhub-pubatt-' . bin2hex(random_bytes(4));
|
|
mkdir($uploads, 0755, true);
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_SQLITE_PATH=:memory:');
|
|
putenv('DESKHUB_CONFIG_JSON=' . json_encode(['uploads' => ['path' => $uploads, 'max_size_bytes' => 4 * 1048576, 'allowed_extensions' => ['png', 'pdf', 'txt']]]));
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
require_once dirname(__DIR__) . '/includes/integrations.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void {
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
set_setting('upload_allowed_extensions', 'png, pdf, txt');
|
|
set_setting('upload_max_size_mb', '4');
|
|
|
|
// Grenzen im Formular-Angebot
|
|
$def = public_form_definition($pdo, null, (int) get_setting('default_department_id', '1'));
|
|
$payload = public_form_payload($def);
|
|
$check(!empty($payload['attachments']['enabled']) && $payload['attachments']['maxFiles'] === 5 && $payload['attachments']['maxFileBytes'] === 4 * 1048576 && $payload['attachments']['allowedExtensions'] === ['png', 'pdf', 'txt'], 'Formular nennt die Grenzen: ' . json_encode($payload['attachments']));
|
|
|
|
$base = ['name' => 'Anna Muster', 'email' => 'anna@example.de', 'subject' => 'Mit Anhang', 'message' => 'Siehe Datei.'];
|
|
$b64 = fn(string $s) => base64_encode($s);
|
|
$count = fn() => (int) $pdo->query('SELECT COUNT(*) FROM tickets')->fetchColumn();
|
|
$fails = function (array $in) use ($pdo, $count): ?string {
|
|
$before = $count();
|
|
try {
|
|
public_ticket_create($pdo, $in, '203.0.113.5');
|
|
} catch (AdminModelException $e) {
|
|
return $e->getMessage() . ' | ' . ($count() === $before ? 'kein Ticket' : 'TICKET ANGELEGT');
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Erfolg: zwei Dateien
|
|
$r = public_ticket_create($pdo, $base + ['attachments' => [['name' => 'Bericht.pdf', 'data' => $b64("%PDF-1.4\n" . str_repeat('x', 500))], ['name' => 'notiz.txt', 'data' => $b64('Hallo')]]], '203.0.113.5');
|
|
$number = (string) ($r['ticketNumber'] ?? $r['number'] ?? '');
|
|
$tid = (int) $pdo->query('SELECT id FROM tickets WHERE ticket_number = ' . $pdo->quote($number))->fetchColumn();
|
|
$att = $pdo->query('SELECT a.* FROM ticket_attachments a JOIN ticket_threads t ON t.id = a.thread_id WHERE t.ticket_id = ' . $tid . ' ORDER BY a.id')->fetchAll(PDO::FETCH_ASSOC);
|
|
$check(count($att) === 2 && $att[0]['original_name'] === 'Bericht.pdf' && $att[1]['original_name'] === 'notiz.txt', 'Anhänge hängen an der ersten Nachricht: ' . json_encode(array_column($att, 'original_name')) . ' ' . json_encode($r));
|
|
$check($att && file_get_contents($uploads . '/' . $att[1]['stored_name']) === 'Hallo' && (int) $att[1]['filesize'] === 5, 'Inhalt wurde gespeichert');
|
|
|
|
// Ohne Anhänge wie bisher
|
|
$r2 = public_ticket_create($pdo, $base, '203.0.113.5');
|
|
$check(!empty($r2['ticketNumber'] ?? $r2['number'] ?? ''), 'ohne Anhänge funktioniert alles wie bisher');
|
|
|
|
// Fehlerfälle: nichts wird angelegt
|
|
$e = $fails($base + ['attachments' => [['name' => 'x.exe', 'data' => $b64('MZ')]]]);
|
|
$check($e !== null && str_ends_with($e, 'kein Ticket'), 'gesperrte Endung (.exe) lehnt ab, ohne Ticket: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['name' => 'a.docx', 'data' => $b64('x')]]]);
|
|
$check($e !== null && str_ends_with($e, 'kein Ticket'), 'nicht freigegebene Endung (.docx) lehnt ab: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['name' => 'a.php', 'data' => $b64('<?php')]]]);
|
|
$check($e !== null && str_ends_with($e, 'kein Ticket'), '.php lehnt ab');
|
|
$e = $fails($base + ['attachments' => array_fill(0, 6, ['name' => 'a.txt', 'data' => $b64('x')])]);
|
|
$check($e !== null && str_contains($e, 'höchstens 5') && str_ends_with($e, 'kein Ticket'), 'mehr als 5 Dateien lehnt ab: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['name' => 'gross.pdf', 'data' => $b64(str_repeat('x', 4 * 1048576 + 1))]]]);
|
|
$check($e !== null && str_contains($e, 'zu groß') && str_ends_with($e, 'kein Ticket'), 'zu große Datei lehnt ab: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['name' => 'a.txt', 'data' => $b64(str_repeat('x', 3 * 1048576))], ['name' => 'b.txt', 'data' => $b64(str_repeat('x', 3 * 1048576))], ['name' => 'c.txt', 'data' => $b64(str_repeat('x', 3 * 1048576))], ['name' => 'd.txt', 'data' => $b64(str_repeat('x', 2 * 1048576))]]]);
|
|
$check($e !== null && str_contains($e, 'zusammen') && str_ends_with($e, 'kein Ticket'), 'zusammen zu groß (über 10 MB) lehnt ab: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['name' => 'a.txt', 'data' => '!!!kein-base64!!!']]]);
|
|
$check($e !== null && str_contains($e, 'Base64') && str_ends_with($e, 'kein Ticket'), 'ungültiges Base64 lehnt ab: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['data' => $b64('x')]]]);
|
|
$check($e !== null && str_ends_with($e, 'kein Ticket'), 'Anhang ohne Namen lehnt ab');
|
|
$e = $fails($base + ['attachments' => 'text']);
|
|
$check($e !== null && str_contains($e, 'Format'), 'falsches Format lehnt ab: ' . $e);
|
|
$e = $fails($base + ['attachments' => [['name' => '../../evil.txt', 'data' => $b64('x')]]]);
|
|
$check($e === null && (int) $pdo->query("SELECT COUNT(*) FROM ticket_attachments WHERE original_name = 'evil.txt'")->fetchColumn() === 1, 'Pfadanteile im Dateinamen werden entfernt: ' . json_encode($e));
|
|
|
|
// Eigenes Formular ohne Anhänge
|
|
$deptId = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
|
|
$pdo->prepare('UPDATE departments SET own_form = 1, form_config = ? WHERE id = ?')->execute([json_encode(['title' => 'Ohne Dateien', 'attachment_show' => false, 'subject_show' => true, 'message_show' => true]), $deptId]);
|
|
$def2 = public_form_definition($pdo, null, $deptId);
|
|
$check(public_form_payload($def2)['attachments']['enabled'] === false, 'eigenes Formular kann Anhänge abschalten (im Angebot)');
|
|
$e = $fails($base + ['departmentId' => $deptId, 'fields' => [], 'attachments' => [['name' => 'a.txt', 'data' => $b64('x')]]]);
|
|
$check($e !== null && str_contains($e, 'keine Dateien') && str_ends_with($e, 'kein Ticket'), 'bei abgeschalteten Anhängen: Ablehnung: ' . $e);
|
|
|
|
$rm = function (string $d) use (&$rm) {
|
|
foreach (scandir($d) ?: [] as $x) { if ($x === '.' || $x === '..') continue; is_dir("$d/$x") ? $rm("$d/$x") : @unlink("$d/$x"); }
|
|
@rmdir($d);
|
|
};
|
|
$rm($uploads);
|
|
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo "Public API attachment checks passed.\n";
|