LAN_Portal/files/common.php
2026-10-16 16:22:59 -04:00

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); }
}