feat: add optional allow_binary parameter to item handlers

Co-authored-by: aider (openai/andrew/openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-08-13 13:25:27 -03:00
parent 07ea7ec5a4
commit 243e77fba4
2 changed files with 19 additions and 8 deletions

View File

@@ -13,7 +13,7 @@ use anyhow::{Result, anyhow};
use crate::compression_engine::{CompressionType, get_compression_engine}; use crate::compression_engine::{CompressionType, get_compression_engine};
use crate::db; use crate::db;
use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, ItemContentInfo, TagsQuery, ListItemsQuery}; use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, ItemContentInfo, TagsQuery, ListItemsQuery, ItemQuery};
use crate::common::is_binary::is_binary; use crate::common::is_binary::is_binary;
#[utoipa::path( #[utoipa::path(
@@ -211,7 +211,8 @@ pub async fn handle_delete_item(
(status = 500, description = "Internal server error - Failed to retrieve item content") (status = 500, description = "Internal server error - Failed to retrieve item content")
), ),
params( params(
("tags" = Option<String>, Query, description = "Comma-separated list of tags to filter by (e.g., 'important,work'). If specified, returns the latest item matching ALL tags.") ("tags" = Option<String>, Query, description = "Comma-separated list of tags to filter by (e.g., 'important,work'). If specified, returns the latest item matching ALL tags."),
("allow_binary" = Option<bool>, Query, description = "Whether to include content for binary files (default: false). When false, binary files will only return metadata.")
), ),
security( security(
("bearerAuth" = []) ("bearerAuth" = [])
@@ -240,7 +241,7 @@ pub async fn handle_get_item_latest(
}; };
if let Some(item) = item { if let Some(item) = item {
match get_item_content_info(&item, &state.data_dir, &mut *conn).await { match get_item_content_info(&item, &state.data_dir, &mut *conn, params.allow_binary).await {
Ok(content_info) => { Ok(content_info) => {
let response = ApiResponse { let response = ApiResponse {
success: true, success: true,
@@ -275,7 +276,8 @@ pub async fn handle_get_item_latest(
(status = 500, description = "Internal server error - Failed to retrieve item content") (status = 500, description = "Internal server error - Failed to retrieve item content")
), ),
params( params(
("item_id" = i64, Path, description = "Unique identifier of the item to retrieve (must be positive)") ("item_id" = i64, Path, description = "Unique identifier of the item to retrieve (must be positive)"),
("allow_binary" = Option<bool>, Query, description = "Whether to include content for binary files (default: false). When false, binary files will only return metadata.")
), ),
security( security(
("bearerAuth" = []) ("bearerAuth" = [])
@@ -285,6 +287,7 @@ pub async fn handle_get_item_latest(
pub async fn handle_get_item( pub async fn handle_get_item(
State(state): State<AppState>, State(state): State<AppState>,
Path(item_id): Path<i64>, Path(item_id): Path<i64>,
Query(params): Query<ItemQuery>,
) -> Result<Json<ApiResponse<ItemContentInfo>>, StatusCode> { ) -> Result<Json<ApiResponse<ItemContentInfo>>, StatusCode> {
// Validate that item ID is positive to prevent path traversal issues // Validate that item ID is positive to prevent path traversal issues
if item_id <= 0 { if item_id <= 0 {
@@ -297,7 +300,7 @@ pub async fn handle_get_item(
warn!("Failed to get item {} for content: {}", item_id, e); warn!("Failed to get item {} for content: {}", item_id, e);
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})? { })? {
match get_item_content_info(&item, &state.data_dir, &mut *conn).await { match get_item_content_info(&item, &state.data_dir, &mut *conn, params.allow_binary).await {
Ok(content_info) => { Ok(content_info) => {
let response = ApiResponse { let response = ApiResponse {
success: true, success: true,
@@ -453,7 +456,7 @@ async fn get_item_content(item: &db::Item, data_dir: &PathBuf) -> Result<String>
Ok(content) Ok(content)
} }
async fn get_item_content_info(item: &db::Item, data_dir: &PathBuf, conn: &mut rusqlite::Connection) -> Result<ItemContentInfo> { async fn get_item_content_info(item: &db::Item, data_dir: &PathBuf, conn: &mut rusqlite::Connection, allow_binary: bool) -> Result<ItemContentInfo> {
let item_id = item.id.ok_or_else(|| anyhow!("Item missing ID"))?; let item_id = item.id.ok_or_else(|| anyhow!("Item missing ID"))?;
// Validate that item ID is positive to prevent path traversal issues // Validate that item ID is positive to prevent path traversal issues
@@ -487,8 +490,8 @@ async fn get_item_content_info(item: &db::Item, data_dir: &PathBuf, conn: &mut r
is_binary(&buffer[..bytes_read]) is_binary(&buffer[..bytes_read])
}; };
// Get content if not binary // Get content if not binary or if binary is allowed
let content = if is_binary { let content = if is_binary && !allow_binary {
None None
} else { } else {
match get_item_content(item, data_dir).await { match get_item_content(item, data_dir).await {

View File

@@ -69,6 +69,8 @@ pub struct ItemContentInfo {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct TagsQuery { pub struct TagsQuery {
pub tags: Option<String>, pub tags: Option<String>,
#[serde(default)]
pub allow_binary: bool,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -79,6 +81,12 @@ pub struct ListItemsQuery {
pub count: Option<u32>, pub count: Option<u32>,
} }
#[derive(Debug, Deserialize)]
pub struct ItemQuery {
#[serde(default)]
pub allow_binary: bool,
}
fn check_bearer_auth(auth_str: &str, expected_password: &str) -> bool { fn check_bearer_auth(auth_str: &str, expected_password: &str) -> bool {
auth_str.starts_with("Bearer ") && &auth_str[7..] == expected_password auth_str.starts_with("Bearer ") && &auth_str[7..] == expected_password
} }