storage.php 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. <?php
  2. declare(strict_types=1);
  3. // Flat-file storage primitives. Ported from the PSA order system
  4. // (includes/functions.php readJsonFile/writeJsonFile): atomic writes via a
  5. // temp file plus rename, so a crashed request can never leave a half-written
  6. // index behind.
  7. function manageEnsureDirectory(string $dir): void
  8. {
  9. if (!is_dir($dir) && !mkdir($dir, 02775, true) && !is_dir($dir)) {
  10. throw new RuntimeException("Verzeichnis konnte nicht erstellt werden: " . $dir);
  11. }
  12. @chmod($dir, 02775);
  13. }
  14. function manageReadJsonFile(string $file): array
  15. {
  16. if (!is_file($file)) {
  17. return [];
  18. }
  19. $content = file_get_contents($file);
  20. if ($content === false || trim($content) === "") {
  21. return [];
  22. }
  23. $decoded = json_decode($content, true);
  24. if (!is_array($decoded)) {
  25. throw new RuntimeException("JSON-Datei ist beschädigt: " . basename($file));
  26. }
  27. return $decoded;
  28. }
  29. function manageWriteJsonFile(string $file, array $data): void
  30. {
  31. manageEnsureDirectory(dirname($file));
  32. if (is_file($file) && !is_writable($file)) {
  33. @chmod($file, 0664);
  34. }
  35. $json = json_encode(
  36. $data,
  37. JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
  38. );
  39. if ($json === false) {
  40. throw new RuntimeException("JSON konnte nicht kodiert werden.");
  41. }
  42. $tmpFile = $file . ".tmp";
  43. if (file_put_contents($tmpFile, $json . PHP_EOL, LOCK_EX) === false) {
  44. throw new RuntimeException("JSON konnte nicht geschrieben werden.");
  45. }
  46. @chmod($tmpFile, 0664);
  47. // rename() is atomic on the same filesystem.
  48. if (!rename($tmpFile, $file)) {
  49. @unlink($tmpFile);
  50. throw new RuntimeException("JSON konnte nicht gespeichert werden.");
  51. }
  52. @chmod($file, 0664);
  53. }
  54. function manageRemoveDirectory(string $dir): void
  55. {
  56. if (!is_dir($dir)) {
  57. return;
  58. }
  59. $items = new RecursiveIteratorIterator(
  60. new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
  61. RecursiveIteratorIterator::CHILD_FIRST,
  62. );
  63. foreach ($items as $item) {
  64. if ($item->isDir()) {
  65. @rmdir($item->getPathname());
  66. } else {
  67. @unlink($item->getPathname());
  68. }
  69. }
  70. @rmdir($dir);
  71. }
  72. // Reads the first bytes of a file and reports whether they look like a ZIP
  73. // local file / empty archive / spanned archive header.
  74. function manageFileIsZip(string $path): bool
  75. {
  76. $handle = @fopen($path, "rb");
  77. if ($handle === false) {
  78. return false;
  79. }
  80. $signature = fread($handle, 4);
  81. fclose($handle);
  82. return $signature === "PK\x03\x04" ||
  83. $signature === "PK\x05\x06" ||
  84. $signature === "PK\x07\x08";
  85. }