Add safe text editing to preview window
This commit is contained in:
@@ -15,6 +15,68 @@ if ($type === null) {
|
||||
unav_error(415, 'Preview is not available for this file type');
|
||||
}
|
||||
|
||||
$editable = in_array($type, ['text', 'markdown', 'csv'], true);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
unav_assert_writable();
|
||||
if (!$editable) {
|
||||
unav_error(415, 'Dieser Dateityp kann nicht bearbeitet werden');
|
||||
}
|
||||
$data = unav_request_data();
|
||||
if (!array_key_exists('content', $data)) {
|
||||
unav_error(400, 'Missing file content');
|
||||
}
|
||||
$content = (string)$data['content'];
|
||||
if (strlen($content) > UNAV_TEXT_PREVIEW_MAX_BYTES) {
|
||||
unav_error(413, 'Die bearbeitete Datei ist größer als 1 MB');
|
||||
}
|
||||
if (strpos($content, "\0") !== false) {
|
||||
unav_error(415, 'Binärdaten können nicht im Texteditor gespeichert werden');
|
||||
}
|
||||
$expectedRevision = trim((string)($data['revision'] ?? ''));
|
||||
if ($expectedRevision === '') {
|
||||
unav_error(400, 'Missing file revision');
|
||||
}
|
||||
$handle = fopen($resolved['path'], 'r+b');
|
||||
if ($handle === false || !flock($handle, LOCK_EX)) {
|
||||
if (is_resource($handle)) {
|
||||
fclose($handle);
|
||||
}
|
||||
unav_error(500, 'Could not lock file for editing');
|
||||
}
|
||||
rewind($handle);
|
||||
$current = stream_get_contents($handle);
|
||||
if ($current === false) {
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
unav_error(500, 'Could not read file before saving');
|
||||
}
|
||||
if (!hash_equals($expectedRevision, hash('sha256', $current))) {
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
unav_error(409, 'Die Datei wurde außerhalb von U-Navigator geändert. Bitte vor dem Speichern neu laden.');
|
||||
}
|
||||
rewind($handle);
|
||||
if (!ftruncate($handle, 0) || !unav_write_all($handle, $content) || !fflush($handle)) {
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
unav_error(500, 'Could not save file');
|
||||
}
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
clearstatcache(true, $resolved['path']);
|
||||
unav_json([
|
||||
'path' => $resolved['path'],
|
||||
'size' => strlen($content),
|
||||
'modifiedAt' => gmdate('c', filemtime($resolved['path']) ?: time()),
|
||||
'revision' => hash('sha256', $content),
|
||||
'content' => $content,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
unav_error(405, 'Method not allowed');
|
||||
}
|
||||
|
||||
$size = filesize($resolved['path']);
|
||||
if ($size === false) {
|
||||
unav_error(500, 'Could not read file size');
|
||||
@@ -35,6 +97,7 @@ $payload = [
|
||||
'size' => $size,
|
||||
'extension' => $extension,
|
||||
'type' => $type,
|
||||
'editable' => $editable,
|
||||
'mime' => $mime,
|
||||
'rawUrl' => 'preview.php?raw=1&path=' . rawurlencode($resolved['path']),
|
||||
];
|
||||
@@ -51,10 +114,24 @@ if (in_array($type, ['text', 'markdown', 'csv'], true)) {
|
||||
unav_error(415, 'Preview is not available for binary files');
|
||||
}
|
||||
$payload['content'] = $content;
|
||||
$payload['revision'] = hash('sha256', $content);
|
||||
}
|
||||
|
||||
unav_json($payload);
|
||||
|
||||
function unav_write_all($handle, string $content): bool {
|
||||
$length = strlen($content);
|
||||
$offset = 0;
|
||||
while ($offset < $length) {
|
||||
$written = fwrite($handle, substr($content, $offset));
|
||||
if ($written === false || $written === 0) {
|
||||
return false;
|
||||
}
|
||||
$offset += $written;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function unav_preview_type(string $extension, string $name): ?string {
|
||||
$base = strtolower($name);
|
||||
if (in_array($base, ['dockerfile', 'makefile'], true)) {
|
||||
|
||||
Reference in New Issue
Block a user