upload.php 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. /**
  3. * Public guest uploader: /upload.php?g=<slug>&k=<upload_key>
  4. *
  5. * Lets someone without an admin account upload into a gallery, gated by:
  6. * 1. a per-gallery secret key in the URL (k), compared with hash_equals,
  7. * 2. the gallery's password (if set), reusing the viewer's session unlock,
  8. * 3. the gallery's expiry.
  9. * A wrong/missing key is indistinguishable from a missing gallery — the same
  10. * neutral "not available" page as gallery.php, so links can't be enumerated.
  11. */
  12. require __DIR__ . '/app/bootstrap.php';
  13. session_boot();
  14. $slug = (string)($_GET['g'] ?? '');
  15. $gallery = $slug !== '' ? gallery_load($slug) : null;
  16. $keyOk = $gallery !== null
  17. && !empty($gallery['upload_key'])
  18. && hash_equals((string)$gallery['upload_key'], (string)($_GET['k'] ?? ''));
  19. if ($gallery === null || gallery_is_expired($gallery) || !$keyOk) {
  20. http_response_code(404);
  21. public_header('Upload not available');
  22. echo '<div class="gate"><div class="gate-card"><h1>Upload not available</h1>'
  23. . '<p class="page-sub">This upload link does not exist or is no longer active.</p></div></div>';
  24. public_footer();
  25. exit;
  26. }
  27. $needsPassword = !empty($gallery['password_hash']);
  28. $unlocked = !$needsPassword || !empty($_SESSION['gallery_unlocked'][$slug]);
  29. if ($needsPassword && !$unlocked && $_SERVER['REQUEST_METHOD'] === 'POST') {
  30. csrf_verify();
  31. if (password_verify((string)($_POST['password'] ?? ''), $gallery['password_hash'])) {
  32. $_SESSION['gallery_unlocked'][$slug] = true;
  33. redirect('upload.php?g=' . rawurlencode($slug) . '&k=' . rawurlencode((string)$gallery['upload_key']));
  34. }
  35. $error = 'Wrong password.';
  36. }
  37. if ($needsPassword && !$unlocked) {
  38. public_header(e($gallery['title']));
  39. ?>
  40. <div class="gate"><div class="gate-card">
  41. <h1><?= e($gallery['title']) ?></h1>
  42. <?php if (!empty($error)): ?><div class="flash flash-error"><?= e($error) ?></div><?php endif; ?>
  43. <form method="post">
  44. <?= csrf_field() ?>
  45. <label for="pw">Password</label>
  46. <input type="password" id="pw" name="password" autofocus autocomplete="off">
  47. <button type="submit">Continue</button>
  48. </form>
  49. </div></div>
  50. <?php
  51. public_footer();
  52. exit;
  53. }
  54. public_header(e($gallery['title']));
  55. ?>
  56. <main class="page">
  57. <h1 class="page-title"><?= e($gallery['title']) ?></h1>
  58. <p class="page-sub">Upload your photos to this gallery.</p>
  59. <div class="dropzone" id="dropzone"
  60. data-api="upload-api.php"
  61. data-slug="<?= e($slug) ?>"
  62. data-csrf="<?= e(csrf_token()) ?>"
  63. data-key="<?= e((string)$gallery['upload_key']) ?>"
  64. data-thumb-size="<?= (int)config('uploads.thumb_size', 600) ?>"
  65. data-thumb-quality="<?= e((string)config('uploads.thumb_quality', 0.8)) ?>"
  66. data-concurrency="<?= (int)config('uploads.concurrency', 3) ?>"
  67. data-max-resolution="<?= (int)($gallery['max_resolution'] ?? 0) ?>"
  68. data-resize-quality="<?= e((string)config('uploads.resize_quality', 0.9)) ?>">
  69. Drop images here or click to select.<br>
  70. <small>
  71. <?php if (isset($gallery['max_resolution'])): ?>
  72. Downscaled to <?= (int)$gallery['max_resolution'] ?> px on the longest edge.
  73. <?php else: ?>
  74. Full resolution, unmodified.
  75. <?php endif; ?>
  76. </small>
  77. </div>
  78. <input type="file" id="file-input" accept="image/*" multiple style="display:none">
  79. <div class="upload-list" id="upload-list"></div>
  80. </main>
  81. <script src="assets/admin.js"></script>
  82. <?php public_footer(); ?>