54 lines
3 KiB
PHP
Executable file
54 lines
3 KiB
PHP
Executable file
<?php
|
|
// Shared only by this feature's entry file and download endpoint.
|
|
const FILES_MAX_BYTES = 50 * 1024 * 1024;
|
|
const FILES_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
const FILES_MAX_COUNT = 500;
|
|
const FILES_INTERVAL = 10;
|
|
|
|
function files_string($value): string { return is_string($value) ? $value : ''; }
|
|
function files_name(string $name): string {
|
|
$name = basename(str_replace('\\', '/', $name));
|
|
$name = preg_replace('/[^A-Za-z0-9._ ()-]/', '_', $name);
|
|
$name = trim(substr($name, 0, 140), " .\t\r\n");
|
|
return $name === '' ? 'download' : $name;
|
|
}
|
|
function files_lock(int $mode) {
|
|
if (!is_dir(__DIR__ . '/data') || !is_dir(__DIR__ . '/data/uploads')) {
|
|
throw new RuntimeException('Missing files/data or files/data/uploads. Run the setup commands.');
|
|
}
|
|
$lock = @fopen(__DIR__ . '/data/store.lock', 'c');
|
|
if (!$lock || !flock($lock, $mode)) {
|
|
if (is_resource($lock)) fclose($lock);
|
|
throw new RuntimeException('Cannot lock file storage. Check data directory permissions.');
|
|
}
|
|
return $lock;
|
|
}
|
|
function files_read(): array {
|
|
$path = __DIR__ . '/data/store.json';
|
|
if (!file_exists($path)) return ['items' => [], 'last_upload' => 0];
|
|
if (filesize($path) > 1048576) throw new RuntimeException('Metadata exceeds its 1 MiB limit.');
|
|
$raw = @file_get_contents($path);
|
|
if ($raw === false) throw new RuntimeException('Cannot read file metadata.');
|
|
try { $store = json_decode($raw, true, 32, JSON_THROW_ON_ERROR); }
|
|
catch (JsonException $ex) { throw new RuntimeException('Malformed file metadata; no changes were made.'); }
|
|
if (!is_array($store) || !isset($store['items'], $store['last_upload']) || !is_array($store['items']) || !is_int($store['last_upload']) || count($store['items']) > FILES_MAX_COUNT) {
|
|
throw new RuntimeException('Invalid file metadata structure; no changes were made.');
|
|
}
|
|
foreach ($store['items'] as $key => $item) {
|
|
if (!preg_match('/^[a-f0-9]{32}$/D', (string)$key) || !is_array($item) || !isset($item['name'], $item['size'], $item['time']) || !is_string($item['name']) || files_name($item['name']) !== $item['name'] || !is_int($item['size']) || $item['size'] < 0 || $item['size'] > FILES_MAX_BYTES || !is_int($item['time'])) {
|
|
throw new RuntimeException('Invalid file metadata entry; no changes were made.');
|
|
}
|
|
}
|
|
return $store;
|
|
}
|
|
function files_write(array $store): void {
|
|
$json = json_encode($store, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
|
|
if (strlen($json) > 1048576) throw new RuntimeException('Metadata limit reached.');
|
|
$temp = @tempnam(__DIR__ . '/data', 'save-');
|
|
if ($temp === false) throw new RuntimeException('Cannot create temporary metadata file.');
|
|
try {
|
|
if (file_put_contents($temp, $json) !== strlen($json) || !chmod($temp, 0640) || !rename($temp, __DIR__ . '/data/store.json')) {
|
|
throw new RuntimeException('Cannot save file metadata. Check permissions and free space.');
|
|
}
|
|
} finally { if (is_file($temp)) @unlink($temp); }
|
|
}
|