root = rtrim($root, '/'); } public function id(): string { return 'local'; } public function validateConfig(): string { return is_dir($this->root) || @mkdir($this->root, 0755, true) ? '' : '本地存储目录不可写'; } private function abs(string $key): string { // 挡目录穿越:.. 一律拒绝 $key = ltrim(str_replace('\\', '/', $key), '/'); if (strpos($key, '..') !== false) { return $this->root . '/__invalid__'; } return $this->root . '/' . $key; } public function putFile(string $srcPath, string $key, ?string $mime = null): array { $dst = $this->abs($key); $dir = dirname($dst); if (!is_dir($dir) && !@mkdir($dir, 0755, true)) { return ['ok' => false, 'error' => '建不了目录:' . $dir]; } if (!@copy($srcPath, $dst)) { return ['ok' => false, 'error' => '复制文件失败']; } @chmod($dst, 0644); return ['ok' => true, 'error' => '']; } public function putContents(string $binary, string $key, ?string $mime = null): array { $dst = $this->abs($key); $dir = dirname($dst); if (!is_dir($dir) && !@mkdir($dir, 0755, true)) { return ['ok' => false, 'error' => '建不了目录:' . $dir]; } if (@file_put_contents($dst, $binary) === false) { return ['ok' => false, 'error' => '写入文件失败']; } @chmod($dst, 0644); return ['ok' => true, 'error' => '']; } public function delete(string $key): array { $p = $this->abs($key); if (is_file($p) && !@unlink($p)) { return ['ok' => false, 'error' => '删不掉:' . $key]; } return ['ok' => true, 'error' => '']; } public function get(string $key, array $extra = []): array { $p = $this->abs($key); if (!is_file($p)) { return ['ok' => false, 'status' => 404, 'headers' => [], 'body' => '', 'error' => '文件不存在']; } $body = (string)@file_get_contents($p); return ['ok' => true, 'status' => 200, 'headers' => [], 'body' => $body, 'error' => '']; } public function exists(string $key): bool { return is_file($this->abs($key)); } /** 本地文件返回站点相对路径(相对站点根,前面带 /) */ public function url(string $key): string { return '/' . ltrim(str_replace('\\', '/', $key), '/'); } }