确认删除

确认删除下面的内容?删除后所有访客都将无法再获取它。

<?php
/**
 * File share:文件秒共享。PHP 8.0 单文件公开中转站,核心功能不依赖 JavaScript。
 * 复制、粘贴、上传进度条、多文件 / 文件夹上传、拖拽和粘贴截图为可选增强;不可用时仍可用普通表单。
 * 上传此文件到 PHP 网站即可;数据必须放在网站目录之外。
 * 上传大文件时,宝塔里 PHP 的「上传限制」和 Nginx 的 client_max_body_size 要一起调大。
 * iOS 快捷指令接口:POST ?action=api,字段 text 发文字、file 发文件;op=text / op=file 取回最新一条。
 */
declare(strict_types=1);
ini_set('display_errors', '0');
umask(0077);

$config = [
    'storage_dir' => '', // 空值沿用原版的站外 PHP 临时目录。可填站外可写绝对路径。
    'ttl' => 24 * 3600,
    'max_file' => 100 * 1024 * 1024,
    'max_text' => 16 * 1024,
    'max_total' => 1024 * 1024 * 1024,
    'max_items' => 200,
    'min_free' => 64 * 1024 * 1024,
    'api_key' => '', // 快捷指令接口密钥。留空 = 不用密钥直接用;填了以后快捷指令要带上 key 字段。
];

final class RelayError extends RuntimeException
{
    public int $status;
    public function __construct(string $message, int $status = 400)
    {
        parent::__construct($message);
        $this->status = $status;
    }
}

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function iniBytes(string $value): int
{
    $value = trim($value);
    if ($value === '' || $value === '0' || $value === '-1') return PHP_INT_MAX;
    $number = (float)$value;
    switch (strtolower(substr($value, -1))) {
        case 'g': $number *= 1024;
        case 'm': $number *= 1024;
        case 'k': $number *= 1024;
    }
    return (int)min($number, PHP_INT_MAX);
}

function isInside(string $path, string $root): bool
{
    $root = rtrim($root, DIRECTORY_SEPARATOR);
    return $path === $root || str_starts_with($path, $root . DIRECTORY_SEPARATOR);
}

function storageDir(array $config): string
{
    // 与旧版使用同一算法:在原路径覆盖 index.php 可以继续使用原来的数据。
    $path = $config['storage_dir'] ?: rtrim(sys_get_temp_dir(), '/\\') . '/php-relay-' . substr(hash('sha256', __FILE__), 0, 20);
    if (!is_dir($path) && !is_link($path)) @mkdir($path, 0700, true);
    // 首次并发访问时,另一请求可能刚刚创建了目录;以最终状态判断。
    if (is_link($path) || !is_dir($path)) {
        throw new RelayError('无法创建私有临时目录。请检查 PHP 临时目录权限,或将 storage_dir 设置为网站目录外、PHP 允许访问的可写绝对路径。', 503);
    }
    $real = realpath($path);
    $webRoot = !empty($_SERVER['DOCUMENT_ROOT']) ? realpath($_SERVER['DOCUMENT_ROOT']) : false;
    if ($real === false || isInside($real, __DIR__) || ($webRoot !== false && isInside($real, $webRoot))) {
        throw new RelayError('存储目录必须放在网站根目录之外,请修改 storage_dir;不要关闭服务器目录限制。', 503);
    }
    @chmod($real, 0700);
    if (!is_writable($real)) throw new RelayError('私有临时目录不可写,请检查目录权限和 PHP 目录限制。', 503);
    return $real;
}

function validId(mixed $id): bool
{
    return is_string($id) && preg_match('/\A[a-f0-9]{32}\z/D', $id) === 1;
}

function saveIndex(string $dir, array $db): void
{
    $temp = $dir . '/index-' . bin2hex(random_bytes(8)) . '.tmp';
    try {
        $json = json_encode($db, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR);
        if (@file_put_contents($temp, $json) !== strlen($json) || !@rename($temp, $dir . '/index.json')) {
            throw new RelayError('保存失败,请检查磁盘空间和目录权限。', 507);
        }
    } finally {
        if (is_file($temp)) @unlink($temp);
    }
}

function locked(string $dir, callable $operation): mixed
{
    $lock = @fopen($dir . '/store.lock', 'c');
    if ($lock === false) throw new RelayError('存储暂时不可用,请稍后重试。', 503);
    if (!flock($lock, LOCK_EX)) {
        fclose($lock);
        throw new RelayError('存储暂时不可用,请稍后重试。', 503);
    }
    try {
        $db = ['items' => [], 'rates' => []];
        if (is_file($dir . '/index.json')) {
            $raw = file_get_contents($dir . '/index.json');
            $db = json_decode($raw === false ? '' : $raw, true, 512, JSON_THROW_ON_ERROR);
            if (!is_array($db) || !isset($db['items'], $db['rates']) || !is_array($db['items']) || !is_array($db['rates'])) {
                throw new RelayError('存储索引损坏,请由网站维护者检查。', 503);
            }
        }
        $now = time();
        $changed = false;
        foreach ($db['items'] as $id => $item) {
            if (!validId($id) || !is_array($item) || !isset($item['kind'], $item['expires_at'])) {
                throw new RelayError('存储索引格式不正确。', 503);
            }
            if ($item['expires_at'] <= $now) {
                $file = $dir . '/' . $id . '.bin';
                if ($item['kind'] === 'file' && is_file($file) && !@unlink($file)) continue;
                unset($db['items'][$id]);
                $changed = true;
            }
        }
        foreach ($db['rates'] as $key => $rate) {
            if ($rate['until'] <= $now) { unset($db['rates'][$key]); $changed = true; }
        }
        if (($db['last_sweep'] ?? 0) < $now - 300) {
            foreach (glob($dir . '/*.bin') ?: [] as $path) {
                $id = basename($path, '.bin');
                if (validId($id) && !isset($db['items'][$id]) && filemtime($path) < $now - 3600) @unlink($path);
            }
            foreach (glob($dir . '/index-*.tmp') ?: [] as $path) {
                if (filemtime($pa

 取消 / 返回首页


公开中转站:所有人都能查看、下载和删除。默认 24 小时后过期,请勿上传敏感资料。