|
|
|
@ -5,25 +5,23 @@ define('PASSWORD', 'cttue_avj2305_44'); |
|
|
|
define('FILES_DIR', __DIR__ . '/avj2305'); |
|
|
|
define('FILES_DIR', __DIR__ . '/avj2305'); |
|
|
|
define('ACTIVE_FILE', __DIR__ . '/active.txt'); |
|
|
|
define('ACTIVE_FILE', __DIR__ . '/active.txt'); |
|
|
|
|
|
|
|
|
|
|
|
// -- helpers ------------------------------------------------------------------ |
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
// HELPERS |
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
|
|
|
|
function is_authed(): bool { |
|
|
|
function redirect(string $url): never { |
|
|
|
return !empty($_SESSION['auth']); |
|
|
|
header('Location: ' . $url); |
|
|
|
|
|
|
|
exit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function require_auth(): void { |
|
|
|
function require_auth(): void { |
|
|
|
if (!is_authed()) { |
|
|
|
if (empty($_SESSION['auth'])) redirect('?login'); |
|
|
|
header('Location: ?login'); |
|
|
|
|
|
|
|
exit; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function get_active(): string { |
|
|
|
function get_active(): string { |
|
|
|
if (file_exists(ACTIVE_FILE)) { |
|
|
|
if (!file_exists(ACTIVE_FILE)) return ''; |
|
|
|
$v = trim(file_get_contents(ACTIVE_FILE)); |
|
|
|
$v = trim(file_get_contents(ACTIVE_FILE)); |
|
|
|
if ($v !== '') return $v; |
|
|
|
return $v !== '' ? $v : ''; |
|
|
|
} |
|
|
|
|
|
|
|
return ''; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function set_active(string $filename): void { |
|
|
|
function set_active(string $filename): void { |
|
|
|
@ -32,45 +30,42 @@ function set_active(string $filename): void { |
|
|
|
|
|
|
|
|
|
|
|
function get_bin_files(): array { |
|
|
|
function get_bin_files(): array { |
|
|
|
$files = glob(FILES_DIR . '/*.bin'); |
|
|
|
$files = glob(FILES_DIR . '/*.bin'); |
|
|
|
if (!$files) return []; |
|
|
|
return $files ? array_map('basename', $files) : []; |
|
|
|
return array_map('basename', $files); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function safe_filename(string $name): bool { |
|
|
|
function safe_filename(string $name): bool { |
|
|
|
// basename only, no path traversal, must end in .bin |
|
|
|
|
|
|
|
return $name === basename($name) |
|
|
|
return $name === basename($name) |
|
|
|
&& str_ends_with($name, '.bin') |
|
|
|
&& str_ends_with($name, '.bin') |
|
|
|
&& !str_contains($name, "\0"); |
|
|
|
&& !str_contains($name, "\0"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// -- routing ------------------------------------------------------------------ |
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
// ACTIONS (POST handlers - redirect, never render) |
|
|
|
$q = array_key_first($_GET) ?? ''; // first query param key |
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
|
|
|
|
// POST: login |
|
|
|
function action_login(): never { |
|
|
|
if ($q === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') { |
|
|
|
|
|
|
|
if (hash_equals(PASSWORD, $_POST['password'] ?? '')) { |
|
|
|
if (hash_equals(PASSWORD, $_POST['password'] ?? '')) { |
|
|
|
$_SESSION['auth'] = true; |
|
|
|
$_SESSION['auth'] = true; |
|
|
|
header('Location: ?edit'); |
|
|
|
redirect('?edit'); |
|
|
|
} else { |
|
|
|
|
|
|
|
header('Location: ?login&err=1'); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
exit; |
|
|
|
redirect('?login&err=1'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// POST: set active file |
|
|
|
function action_set_file(): never { |
|
|
|
if ($q === 'edit' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['file'])) { |
|
|
|
|
|
|
|
require_auth(); |
|
|
|
require_auth(); |
|
|
|
$f = $_POST['file']; |
|
|
|
$f = $_POST['file'] ?? ''; |
|
|
|
if (safe_filename($f) && file_exists(FILES_DIR . '/' . $f)) { |
|
|
|
if (safe_filename($f) && file_exists(FILES_DIR . '/' . $f)) { |
|
|
|
set_active($f); |
|
|
|
set_active($f); |
|
|
|
} |
|
|
|
} |
|
|
|
header('Location: ?edit'); |
|
|
|
redirect('?edit'); |
|
|
|
exit; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// GET: login page |
|
|
|
// ============================================================================= |
|
|
|
if ($q === 'login') { ?> |
|
|
|
// RENDERERS (GET handlers - output HTML, never redirect) |
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function render_login(): never { |
|
|
|
|
|
|
|
$err = !empty($_GET['err']); ?> |
|
|
|
<!DOCTYPE html> |
|
|
|
<!DOCTYPE html> |
|
|
|
<html lang="en"> |
|
|
|
<html lang="en"> |
|
|
|
<head><meta charset="utf-8"><title>Login</title> |
|
|
|
<head><meta charset="utf-8"><title>Login</title> |
|
|
|
@ -88,17 +83,15 @@ if ($q === 'login') { ?> |
|
|
|
<label>Password</label> |
|
|
|
<label>Password</label> |
|
|
|
<input type="password" name="password" autofocus> |
|
|
|
<input type="password" name="password" autofocus> |
|
|
|
<button type="submit">Login</button> |
|
|
|
<button type="submit">Login</button> |
|
|
|
<?php if (!empty($_GET['err'])): ?><span class="err">Wrong password.</span><?php endif; ?> |
|
|
|
<?php if ($err): ?><span class="err">Wrong password.</span><?php endif; ?> |
|
|
|
</form> |
|
|
|
</form> |
|
|
|
</body></html> |
|
|
|
</body></html> |
|
|
|
<?php exit; } |
|
|
|
<?php exit; } |
|
|
|
|
|
|
|
|
|
|
|
// GET: edit page |
|
|
|
function render_edit(): never { |
|
|
|
if ($q === 'edit') { |
|
|
|
|
|
|
|
require_auth(); |
|
|
|
require_auth(); |
|
|
|
$files = get_bin_files(); |
|
|
|
$files = get_bin_files(); |
|
|
|
$active = get_active(); |
|
|
|
$active = get_active(); ?> |
|
|
|
?> |
|
|
|
|
|
|
|
<!DOCTYPE html> |
|
|
|
<!DOCTYPE html> |
|
|
|
<html lang="en"> |
|
|
|
<html lang="en"> |
|
|
|
<head><meta charset="utf-8"><title>Select File</title> |
|
|
|
<head><meta charset="utf-8"><title>Select File</title> |
|
|
|
@ -122,7 +115,7 @@ if ($q === 'edit') { |
|
|
|
<li> |
|
|
|
<li> |
|
|
|
<form method="post" action="?edit"> |
|
|
|
<form method="post" action="?edit"> |
|
|
|
<input type="hidden" name="file" value="<?= htmlspecialchars($f) ?>">
|
|
|
|
<input type="hidden" name="file" value="<?= htmlspecialchars($f) ?>">
|
|
|
|
<button type="submit" <?= $f === $active ? 'class="active"' : '' ?>><?= htmlspecialchars($f) ?></button>
|
|
|
|
<button type="submit"<?= $f === $active ? ' class="active"' : '' ?>><?= htmlspecialchars($f) ?></button>
|
|
|
|
</form> |
|
|
|
</form> |
|
|
|
</li> |
|
|
|
</li> |
|
|
|
<?php endforeach; ?> |
|
|
|
<?php endforeach; ?> |
|
|
|
@ -133,27 +126,53 @@ if ($q === 'edit') { |
|
|
|
</body></html> |
|
|
|
</body></html> |
|
|
|
<?php exit; } |
|
|
|
<?php exit; } |
|
|
|
|
|
|
|
|
|
|
|
// DEFAULT: serve active .bin file |
|
|
|
// ============================================================================= |
|
|
|
$active = get_active(); |
|
|
|
// FILE SERVER (default route) |
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
|
|
|
|
if ($active === '') { |
|
|
|
function serve_active_file(): never { |
|
|
|
http_response_code(404); |
|
|
|
$active = get_active(); |
|
|
|
exit('No active file set.'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!safe_filename($active)) { |
|
|
|
if ($active === '') { |
|
|
|
http_response_code(500); |
|
|
|
http_response_code(404); exit('No active file set.'); |
|
|
|
exit('Invalid filename.'); |
|
|
|
} |
|
|
|
} |
|
|
|
if (!safe_filename($active)) { |
|
|
|
|
|
|
|
http_response_code(500); exit('Invalid filename.'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$path = FILES_DIR . '/' . $active; |
|
|
|
$path = FILES_DIR . '/' . $active; |
|
|
|
|
|
|
|
|
|
|
|
if (!file_exists($path)) { |
|
|
|
if (!file_exists($path)) { |
|
|
|
http_response_code(404); |
|
|
|
http_response_code(404); exit('File not found.'); |
|
|
|
exit('File not found.'); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
header('Content-Type: application/octet-stream'); |
|
|
|
|
|
|
|
header('Content-Disposition: inline; filename="' . addslashes($active) . '"'); |
|
|
|
|
|
|
|
header('Content-Length: ' . filesize($path)); |
|
|
|
|
|
|
|
readfile($path); |
|
|
|
|
|
|
|
exit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
header('Content-Type: application/octet-stream'); |
|
|
|
|
|
|
|
header('Content-Disposition: inline; filename="' . addslashes($active) . '"'); |
|
|
|
// ============================================================================= |
|
|
|
header('Content-Length: ' . filesize($path)); |
|
|
|
// ROUTES |
|
|
|
readfile($path); |
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
// |
|
|
|
|
|
|
|
// GET /live.php -> serve active .bin file |
|
|
|
|
|
|
|
// GET /live.php?login -> login page |
|
|
|
|
|
|
|
// POST /live.php?login -> process login |
|
|
|
|
|
|
|
// GET /live.php?edit -> file picker [auth required] |
|
|
|
|
|
|
|
// POST /live.php?edit -> set active file [auth required] |
|
|
|
|
|
|
|
// |
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$method = $_SERVER['REQUEST_METHOD']; |
|
|
|
|
|
|
|
$route = array_key_first($_GET) ?? ''; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
match (true) { |
|
|
|
|
|
|
|
$method === 'POST' && $route === 'login' => action_login(), |
|
|
|
|
|
|
|
$method === 'POST' && $route === 'edit' => action_set_file(), |
|
|
|
|
|
|
|
$method === 'GET' && $route === 'login' => render_login(), |
|
|
|
|
|
|
|
$method === 'GET' && $route === 'edit' => render_edit(), |
|
|
|
|
|
|
|
default => serve_active_file(), |
|
|
|
|
|
|
|
}; |
|
|
|
|