R2
Side upload is disabled. Set SIDE_UPLOAD_PASSWORD or SIDE_UPLOAD_PASSWORD_HASH in this script, .env, or side-upload.json.

Quick upload

Sign in with the side-upload password.

Destination

*/ function side_upload_config(Config $config): array { $side = [ 'app_name' => 'Side Upload', 'password' => SIDE_UPLOAD_PASSWORD, 'password_hash' => SIDE_UPLOAD_PASSWORD_HASH, 'prefix' => SIDE_UPLOAD_PREFIX, 'session_name' => 'r2_side_upload_session', 'permission' => 'private', 'tags' => 'source=side-upload', ]; if (is_file(SIDE_UPLOAD_CONFIG_FILE)) { $json = json_decode((string) file_get_contents(SIDE_UPLOAD_CONFIG_FILE), true); if (is_array($json)) { $side = array_replace($side, $json); } } foreach ([ 'SIDE_UPLOAD_APP_NAME' => 'app_name', 'SIDE_UPLOAD_PASSWORD' => 'password', 'SIDE_UPLOAD_PASSWORD_HASH' => 'password_hash', 'SIDE_UPLOAD_PREFIX' => 'prefix', 'SIDE_UPLOAD_SESSION_NAME' => 'session_name', 'SIDE_UPLOAD_PERMISSION' => 'permission', 'SIDE_UPLOAD_TAGS' => 'tags', ] as $env => $key) { $value = $config->get($env, ''); if ($value !== null && $value !== '') { $side[$key] = $value; } } $side['prefix'] = R2Key::normalizePrefix((string) $side['prefix']); return $side; } function side_upload_start_session(string $name): void { if (session_status() === PHP_SESSION_ACTIVE) { return; } session_name($name !== '' ? $name : 'r2_side_upload_session'); session_set_cookie_params([ 'lifetime' => 0, 'path' => '/', 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'), 'httponly' => true, 'samesite' => 'Lax', ]); session_start(); } function side_upload_csrf(): string { if (empty($_SESSION['side_upload_csrf'])) { $_SESSION['side_upload_csrf'] = bin2hex(random_bytes(32)); } return (string) $_SESSION['side_upload_csrf']; } function side_upload_validate_csrf(mixed $token): bool { return is_string($token) && hash_equals(side_upload_csrf(), $token); } /** * @param array $side */ function side_upload_enabled(array $side): bool { return trim((string) ($side['password'] ?? '')) !== '' || trim((string) ($side['password_hash'] ?? '')) !== ''; } /** * @param array $side */ function side_upload_verify_password(string $password, array $side): bool { $hash = trim((string) ($side['password_hash'] ?? '')); if ($hash !== '') { return password_verify($password, $hash); } return hash_equals((string) ($side['password'] ?? ''), $password); } function side_upload_is_resumable_action(string $action): bool { return in_array($action, [ 'resumable_start', 'resumable_part_url', 'resumable_part_done', 'resumable_complete', 'resumable_abort', ], true); } /** * @param array $side */ function side_upload_handle_resumable(string $action, string $csrf, array $side, ResumableUploadService $uploads): void { if (!side_upload_validate_csrf($_POST['csrf'] ?? null) || !hash_equals($csrf, side_upload_csrf())) { side_upload_json(['ok' => false, 'error' => 'Your session token expired. Refresh and try again.'], 419); return; } if (empty($_SESSION['side_upload_authenticated'])) { side_upload_json(['ok' => false, 'error' => 'Sign in again before uploading.'], 401); return; } try { $id = (string) ($_POST['id'] ?? ''); $partNumber = (int) ($_POST['part_number'] ?? 0); switch ($action) { case 'resumable_start': side_upload_json($uploads->start( (string) $side['prefix'], (string) ($_POST['file_name'] ?? ''), (int) ($_POST['file_size'] ?? 0), (string) ($_POST['content_type'] ?? ''), (string) ($_POST['fingerprint'] ?? ''), side_upload_tags($side), side_upload_permission((string) ($side['permission'] ?? 'private')), trim((string) ($_POST['notes'] ?? '')), 'side-upload' )); break; case 'resumable_part_url': side_upload_json($uploads->partUrl($id, $partNumber)); break; case 'resumable_part_done': side_upload_json($uploads->markPart($id, $partNumber, (string) ($_POST['etag'] ?? ''))); break; case 'resumable_complete': side_upload_json($uploads->complete($id)); break; case 'resumable_abort': side_upload_json($uploads->abort($id)); break; } } catch (R2Exception $exception) { side_upload_json(['ok' => false, 'error' => $exception->getMessage()], $exception->statusCode() ?: 500); } catch (Throwable $exception) { side_upload_json(['ok' => false, 'error' => $exception->getMessage()], 500); } } /** * @param array $side * @return array */ function side_upload_tags(array $side): array { $tags = $side['tags'] ?? 'source=side-upload'; if (is_array($tags)) { $normalized = []; foreach ($tags as $name => $value) { $normalized[(string) $name] = (string) $value; } return $normalized; } return Tags::parse((string) $tags); } function side_upload_permission(string $permission): string { return in_array($permission, ['private', 'public-read', 'signed-only', 'archived'], true) ? $permission : 'private'; } /** * @param array $payload */ function side_upload_json(array $payload, int $status = 200): void { http_response_code($status); header('Content-Type: application/json'); echo json_encode($payload, JSON_UNESCAPED_SLASHES) ?: '{"ok":false}'; } function side_upload_flash(): string { $message = (string) ($_SESSION['side_upload_flash'] ?? ''); unset($_SESSION['side_upload_flash']); return $message; } function side_upload_set_flash(string $message): void { $_SESSION['side_upload_flash'] = $message; } function side_upload_redirect(): void { header('Location: side-upload.php'); exit; } function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }