basePath = realpath(__DIR__ . '/../../data'); } else { $this->basePath = realpath($basePath); } if (!$this->basePath || !is_dir($this->basePath)) { throw new Exception("Invalid base path for FileStorage."); } } /** * Safely resolves and validates a path within the base directory. * Prevents path traversal attacks. */ private function resolvePath(string $path): string { $fullPath = $this->basePath . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR); $realPath = realpath(dirname($fullPath)); if ($realPath === false || strpos($realPath, $this->basePath) !== 0) { throw new Exception("Security Alert: Path traversal attempt detected or invalid path: " . $path); } return $fullPath; } /** * Saves an array as a JSON file. */ public function put(string $path, array $data): bool { $fullPath = $this->resolvePath($path); $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); if (file_put_contents($fullPath, $json) === false) { return false; } return true; } /** * Reads a JSON file and returns it as an associative array. */ public function get(string $path): ?array { $fullPath = $this->resolvePath($path); if (!file_exists($fullPath)) { return null; } try { $content = file_get_contents($fullPath); if ($content === false) { return null; } return json_decode($content, true, 512, JSON_THROW_ON_ERROR); } catch (Exception $e) { return null; } } /** * Checks if a file exists. */ public function exists(string $path): bool { try { $fullPath = $this->resolvePath($path); return file_exists($fullPath); } catch (Exception $e) { return false; } } /** * Deletes a file. */ public function delete(string $path): bool { $fullPath = $this->resolvePath($path); if (file_exists($fullPath)) { return unlink($fullPath); } return false; } }