"", "releases" => []]; } $decoded = json_decode((string) file_get_contents($manifestFile), true); if (!is_array($decoded)) { throw new RuntimeException("Manifest is not valid JSON."); } return [ "latest" => trim((string) ($decoded["latest"] ?? "")), "releases" => isset($decoded["releases"]) && is_array($decoded["releases"]) ? $decoded["releases"] : [], ]; } function updateManageWriteManifest(string $manifestFile, array $manifest): void { $json = json_encode( $manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, ); if ($json === false) { throw new RuntimeException("Manifest cannot be encoded."); } $tmpFile = $manifestFile . ".tmp"; if (file_put_contents($tmpFile, $json . PHP_EOL, LOCK_EX) === false) { throw new RuntimeException("Manifest cannot be written."); } @chmod($tmpFile, 0664); if (!rename($tmpFile, $manifestFile)) { @unlink($tmpFile); throw new RuntimeException("Manifest cannot be saved."); } @chmod($manifestFile, 0664); } function updateManagePackageFileName(string $version): string { return "psa-orderform-" . $version . ".zip"; } function updateManageUploadedFileIsZip(array $file): bool { $name = strtolower((string) ($file["name"] ?? "")); $tmpName = (string) ($file["tmp_name"] ?? ""); if (!str_ends_with($name, ".zip") || !is_uploaded_file($tmpName)) { return false; } $handle = fopen($tmpName, "rb"); if ($handle === false) { return false; } $signature = fread($handle, 4); fclose($handle); return $signature === "PK\x03\x04" || $signature === "PK\x05\x06" || $signature === "PK\x07\x08"; } function updateManagePublishUpload( string $version, array $file, string $manifestFile, string $packagesDir, ): void { if (!updateManageVersionIsValid($version)) { throw new RuntimeException("Version must use the format vX.Y.Z."); } if (($file["error"] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { throw new RuntimeException("Upload failed with error code " . (string) ($file["error"] ?? "unknown") . "."); } if (!updateManageUploadedFileIsZip($file)) { throw new RuntimeException("Uploaded file must be a ZIP package."); } updateManageEnsureDirectory($packagesDir); $fileName = updateManagePackageFileName($version); $targetPath = $packagesDir . DIRECTORY_SEPARATOR . $fileName; if (!move_uploaded_file((string) $file["tmp_name"], $targetPath)) { throw new RuntimeException("Uploaded package cannot be stored."); } @chmod($targetPath, 0664); $sha256 = strtolower(hash_file("sha256", $targetPath) ?: ""); $size = filesize($targetPath); if (!preg_match('/^[a-f0-9]{64}$/', $sha256) || $size === false || $size <= 0) { @unlink($targetPath); throw new RuntimeException("Stored package could not be verified."); } $manifest = updateManageReadManifest($manifestFile); $manifest["latest"] = $version; $manifest["releases"][$version] = [ "version" => $version, "package" => "packages/" . $fileName, "sha256" => $sha256, "size" => $size, "published_at" => date(DATE_ATOM), ]; ksort($manifest["releases"]); updateManageWriteManifest($manifestFile, $manifest); } function updateManageSetLatest(string $version, string $manifestFile): void { if (!updateManageVersionIsValid($version)) { throw new RuntimeException("Invalid release version."); } $manifest = updateManageReadManifest($manifestFile); if (!isset($manifest["releases"][$version])) { throw new RuntimeException("Release is not present in the manifest."); } $manifest["latest"] = $version; updateManageWriteManifest($manifestFile, $manifest); } function updateManageDeleteRelease( string $version, string $manifestFile, string $baseDir, ): void { if (!updateManageVersionIsValid($version)) { throw new RuntimeException("Invalid release version."); } $manifest = updateManageReadManifest($manifestFile); if (!isset($manifest["releases"][$version])) { throw new RuntimeException("Release is not present in the manifest."); } $package = trim((string) ($manifest["releases"][$version]["package"] ?? "")); unset($manifest["releases"][$version]); if ($manifest["latest"] === $version) { $manifest["latest"] = ""; } updateManageWriteManifest($manifestFile, $manifest); if ($package !== "" && !str_contains($package, "\0") && !str_starts_with($package, "/")) { $packagePath = realpath($baseDir . "/" . $package); $packagesPath = realpath($baseDir . "/packages"); if ( $packagePath !== false && $packagesPath !== false && str_starts_with($packagePath, $packagesPath . DIRECTORY_SEPARATOR) && is_file($packagePath) ) { unlink($packagePath); } } } if ($_SERVER["REQUEST_METHOD"] === "POST") { $action = (string) ($_POST["action"] ?? ""); if ($action === "login") { if (!updateManagePasswordConfigured()) { $errors[] = "No password is configured."; } elseif (updateManagePasswordMatches((string) ($_POST["password"] ?? ""))) { session_regenerate_id(true); $_SESSION["update_server_logged_in"] = true; $messages[] = "Logged in."; } else { $errors[] = "Wrong password."; } } elseif ($action === "logout") { unset($_SESSION["update_server_logged_in"], $_SESSION["update_server_csrf_token"]); $messages[] = "Logged out."; } elseif (!updateManageIsLoggedIn()) { $errors[] = "Login required."; } elseif (!updateManageCsrfIsValid((string) ($_POST["csrf_token"] ?? ""))) { $errors[] = "Invalid token. Please reload the page and try again."; } else { try { if ($action === "upload") { updateManagePublishUpload( trim((string) ($_POST["version"] ?? "")), $_FILES["package"] ?? [], $manifestFile, $packagesDir, ); $messages[] = "Release uploaded and published."; } elseif ($action === "set_latest") { updateManageSetLatest(trim((string) ($_POST["version"] ?? "")), $manifestFile); $messages[] = "Latest release updated."; } elseif ($action === "delete") { updateManageDeleteRelease(trim((string) ($_POST["version"] ?? "")), $manifestFile, $baseDir); $messages[] = "Release deleted."; } } catch (Throwable $exception) { $errors[] = $exception->getMessage(); } } } try { $manifest = updateManageReadManifest($manifestFile); } catch (Throwable $exception) { $manifest = ["latest" => "", "releases" => []]; $errors[] = $exception->getMessage(); } $releases = $manifest["releases"]; krsort($releases); ?>
Error:
Latest:
Manifest endpoint: manifest.php
No releases configured.
| Version | Package | SHA-256 | Size | Published | Actions |
|---|---|---|---|---|---|