74 lines
2.1 KiB
Rust
74 lines
2.1 KiB
Rust
use anyhow::{bail, Result};
|
|
use std::path::{Path, PathBuf};
|
|
use tracing::info;
|
|
|
|
const DATA_DIR: &str = "/data";
|
|
const MAX_READ_SIZE: u64 = 10 * 1024 * 1024; // 10 MB
|
|
|
|
/// Read a file from the sandboxed /data directory.
|
|
pub async fn read(params: &serde_json::Value) -> Result<serde_json::Value> {
|
|
let path_str = params
|
|
.get("path")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or_else(|| anyhow::anyhow!("path parameter is required"))?;
|
|
|
|
let path = sanitize_path(path_str)?;
|
|
info!(path = %path.display(), "Reading file");
|
|
|
|
let metadata = tokio::fs::metadata(&path).await?;
|
|
if metadata.len() > MAX_READ_SIZE {
|
|
bail!("File too large: {} bytes (max {})", metadata.len(), MAX_READ_SIZE);
|
|
}
|
|
|
|
let content = tokio::fs::read_to_string(&path).await?;
|
|
|
|
Ok(serde_json::json!({
|
|
"path": path_str,
|
|
"size": metadata.len(),
|
|
"content": content,
|
|
}))
|
|
}
|
|
|
|
/// Write a file to the sandboxed /data directory.
|
|
pub async fn write(params: &serde_json::Value) -> Result<serde_json::Value> {
|
|
let path_str = params
|
|
.get("path")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or_else(|| anyhow::anyhow!("path parameter is required"))?;
|
|
|
|
let content = params
|
|
.get("content")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or_else(|| anyhow::anyhow!("content parameter is required"))?;
|
|
|
|
let path = sanitize_path(path_str)?;
|
|
info!(path = %path.display(), size = content.len(), "Writing file");
|
|
|
|
// Create parent directories if needed
|
|
if let Some(parent) = path.parent() {
|
|
tokio::fs::create_dir_all(parent).await?;
|
|
}
|
|
|
|
tokio::fs::write(&path, content).await?;
|
|
|
|
Ok(serde_json::json!({
|
|
"path": path_str,
|
|
"size": content.len(),
|
|
"written": true,
|
|
}))
|
|
}
|
|
|
|
/// Sanitize and validate that the path stays within /data.
|
|
fn sanitize_path(input: &str) -> Result<PathBuf> {
|
|
let base = Path::new(DATA_DIR);
|
|
let full = base.join(input.trim_start_matches('/'));
|
|
let canonical_base = base.to_path_buf();
|
|
|
|
// Prevent path traversal
|
|
if !full.starts_with(&canonical_base) {
|
|
bail!("Path traversal detected: {input}");
|
|
}
|
|
|
|
Ok(full)
|
|
}
|