mirror of
https://github.com/NotAShelf/stash.git
synced 2026-04-12 22:17:41 +00:00
various: validate lower and upper boundaries before storing; add CLI flags
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I6484f9579a8799d952b15adcb47c8eec6a6a6964
This commit is contained in:
parent
02ba05dc95
commit
3a14860ae1
4 changed files with 68 additions and 33 deletions
|
|
@ -2,6 +2,7 @@ use std::io::Read;
|
||||||
|
|
||||||
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub trait StoreCommand {
|
pub trait StoreCommand {
|
||||||
fn store(
|
fn store(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -10,6 +11,8 @@ pub trait StoreCommand {
|
||||||
max_items: u64,
|
max_items: u64,
|
||||||
state: Option<String>,
|
state: Option<String>,
|
||||||
excluded_apps: &[String],
|
excluded_apps: &[String],
|
||||||
|
min_size: Option<usize>,
|
||||||
|
max_size: usize,
|
||||||
) -> Result<(), crate::db::StashError>;
|
) -> Result<(), crate::db::StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,6 +24,8 @@ impl StoreCommand for SqliteClipboardDb {
|
||||||
max_items: u64,
|
max_items: u64,
|
||||||
state: Option<String>,
|
state: Option<String>,
|
||||||
excluded_apps: &[String],
|
excluded_apps: &[String],
|
||||||
|
min_size: Option<usize>,
|
||||||
|
max_size: usize,
|
||||||
) -> Result<(), crate::db::StashError> {
|
) -> Result<(), crate::db::StashError> {
|
||||||
if let Some("sensitive" | "clear") = state.as_deref() {
|
if let Some("sensitive" | "clear") = state.as_deref() {
|
||||||
self.delete_last()?;
|
self.delete_last()?;
|
||||||
|
|
@ -31,6 +36,8 @@ impl StoreCommand for SqliteClipboardDb {
|
||||||
max_dedupe_search,
|
max_dedupe_search,
|
||||||
max_items,
|
max_items,
|
||||||
Some(excluded_apps),
|
Some(excluded_apps),
|
||||||
|
min_size,
|
||||||
|
max_size,
|
||||||
)?;
|
)?;
|
||||||
log::info!("Entry stored");
|
log::info!("Entry stored");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,7 @@ fn negotiate_mime_type(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub trait WatchCommand {
|
pub trait WatchCommand {
|
||||||
fn watch(
|
fn watch(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -183,6 +184,8 @@ pub trait WatchCommand {
|
||||||
excluded_apps: &[String],
|
excluded_apps: &[String],
|
||||||
expire_after: Option<Duration>,
|
expire_after: Option<Duration>,
|
||||||
mime_type_preference: &str,
|
mime_type_preference: &str,
|
||||||
|
min_size: Option<usize>,
|
||||||
|
max_size: usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,6 +197,8 @@ impl WatchCommand for SqliteClipboardDb {
|
||||||
excluded_apps: &[String],
|
excluded_apps: &[String],
|
||||||
expire_after: Option<Duration>,
|
expire_after: Option<Duration>,
|
||||||
mime_type_preference: &str,
|
mime_type_preference: &str,
|
||||||
|
min_size: Option<usize>,
|
||||||
|
max_size: usize,
|
||||||
) {
|
) {
|
||||||
smol::block_on(async {
|
smol::block_on(async {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
@ -349,6 +354,8 @@ impl WatchCommand for SqliteClipboardDb {
|
||||||
max_dedupe_search,
|
max_dedupe_search,
|
||||||
max_items,
|
max_items,
|
||||||
Some(excluded_apps),
|
Some(excluded_apps),
|
||||||
|
min_size,
|
||||||
|
max_size,
|
||||||
) {
|
) {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
log::info!("Stored new clipboard entry (id: {id})");
|
log::info!("Stored new clipboard entry (id: {id})");
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ use rusqlite::{Connection, OptionalExtension, params};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub const DEFAULT_MAX_ENTRY_SIZE: usize = 5_000_000;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum StashError {
|
pub enum StashError {
|
||||||
#[error("Input is empty or too large, skipping store.")]
|
#[error("Input is empty or too large, skipping store.")]
|
||||||
|
|
@ -70,7 +72,7 @@ pub trait ClipboardDb {
|
||||||
max_items: u64,
|
max_items: u64,
|
||||||
excluded_apps: Option<&[String]>,
|
excluded_apps: Option<&[String]>,
|
||||||
min_size: Option<usize>,
|
min_size: Option<usize>,
|
||||||
max_size: Option<usize>,
|
max_size: usize,
|
||||||
) -> Result<i64, StashError>;
|
) -> Result<i64, StashError>;
|
||||||
|
|
||||||
fn deduplicate_by_hash(
|
fn deduplicate_by_hash(
|
||||||
|
|
@ -417,7 +419,7 @@ impl ClipboardDb for SqliteClipboardDb {
|
||||||
max_items: u64,
|
max_items: u64,
|
||||||
excluded_apps: Option<&[String]>,
|
excluded_apps: Option<&[String]>,
|
||||||
min_size: Option<usize>,
|
min_size: Option<usize>,
|
||||||
max_size: Option<usize>,
|
max_size: usize,
|
||||||
) -> Result<i64, StashError> {
|
) -> Result<i64, StashError> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
if input.read_to_end(&mut buf).is_err() || buf.is_empty() {
|
if input.read_to_end(&mut buf).is_err() || buf.is_empty() {
|
||||||
|
|
@ -432,12 +434,8 @@ impl ClipboardDb for SqliteClipboardDb {
|
||||||
return Err(StashError::TooSmall(min));
|
return Err(StashError::TooSmall(min));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(max) = max_size {
|
if size > max_size {
|
||||||
if size > max {
|
return Err(StashError::TooLarge(max_size));
|
||||||
return Err(StashError::TooLarge(max));
|
|
||||||
}
|
|
||||||
} else if size > 5 * 1_000_000 {
|
|
||||||
return Err(StashError::TooLarge(5 * 1_000_000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf.iter().all(u8::is_ascii_whitespace) {
|
if buf.iter().all(u8::is_ascii_whitespace) {
|
||||||
|
|
@ -1536,7 +1534,7 @@ mod tests {
|
||||||
let cursor = std::io::Cursor::new(test_data.to_vec());
|
let cursor = std::io::Cursor::new(test_data.to_vec());
|
||||||
|
|
||||||
let id = db
|
let id = db
|
||||||
.store_entry(cursor, 100, 1000, None, None, None)
|
.store_entry(cursor, 100, 1000, None, None, DEFAULT_MAX_ENTRY_SIZE)
|
||||||
.expect("Failed to store entry");
|
.expect("Failed to store entry");
|
||||||
|
|
||||||
let content_hash: Option<i64> = db
|
let content_hash: Option<i64> = db
|
||||||
|
|
@ -1571,7 +1569,7 @@ mod tests {
|
||||||
let test_data = b"Test content for copy";
|
let test_data = b"Test content for copy";
|
||||||
let cursor = std::io::Cursor::new(test_data.to_vec());
|
let cursor = std::io::Cursor::new(test_data.to_vec());
|
||||||
let id_a = db
|
let id_a = db
|
||||||
.store_entry(cursor, 100, 1000, None, None, None)
|
.store_entry(cursor, 100, 1000, None, None, DEFAULT_MAX_ENTRY_SIZE)
|
||||||
.expect("Failed to store entry A");
|
.expect("Failed to store entry A");
|
||||||
|
|
||||||
let original_last_accessed: i64 = db
|
let original_last_accessed: i64 = db
|
||||||
|
|
@ -1672,7 +1670,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store URI list");
|
.expect("Failed to store URI list");
|
||||||
|
|
||||||
|
|
@ -1705,7 +1703,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store image");
|
.expect("Failed to store image");
|
||||||
|
|
||||||
|
|
@ -1733,7 +1731,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store first");
|
.expect("Failed to store first");
|
||||||
let _id2 = db
|
let _id2 = db
|
||||||
|
|
@ -1743,7 +1741,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store second");
|
.expect("Failed to store second");
|
||||||
|
|
||||||
|
|
@ -1778,7 +1776,7 @@ mod tests {
|
||||||
3, // max 3 items
|
3, // max 3 items
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store");
|
.expect("Failed to store");
|
||||||
}
|
}
|
||||||
|
|
@ -1799,7 +1797,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
);
|
);
|
||||||
assert!(matches!(result, Err(StashError::EmptyOrTooLarge)));
|
assert!(matches!(result, Err(StashError::EmptyOrTooLarge)));
|
||||||
}
|
}
|
||||||
|
|
@ -1813,7 +1811,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
);
|
);
|
||||||
assert!(matches!(result, Err(StashError::AllWhitespace)));
|
assert!(matches!(result, Err(StashError::AllWhitespace)));
|
||||||
}
|
}
|
||||||
|
|
@ -1823,8 +1821,14 @@ mod tests {
|
||||||
let db = test_db();
|
let db = test_db();
|
||||||
// 5MB + 1 byte
|
// 5MB + 1 byte
|
||||||
let data = vec![b'a'; 5 * 1_000_000 + 1];
|
let data = vec![b'a'; 5 * 1_000_000 + 1];
|
||||||
let result =
|
let result = db.store_entry(
|
||||||
db.store_entry(std::io::Cursor::new(data), 100, 1000, None, None, None);
|
std::io::Cursor::new(data),
|
||||||
|
100,
|
||||||
|
1000,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
|
);
|
||||||
assert!(matches!(result, Err(StashError::TooLarge(5000000))));
|
assert!(matches!(result, Err(StashError::TooLarge(5000000))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1838,7 +1842,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store");
|
.expect("Failed to store");
|
||||||
|
|
||||||
|
|
@ -1864,7 +1868,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store");
|
.expect("Failed to store");
|
||||||
db.store_entry(
|
db.store_entry(
|
||||||
|
|
@ -1873,7 +1877,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store");
|
.expect("Failed to store");
|
||||||
|
|
||||||
|
|
@ -1900,7 +1904,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store");
|
.expect("Failed to store");
|
||||||
}
|
}
|
||||||
|
|
@ -1970,7 +1974,7 @@ mod tests {
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
DEFAULT_MAX_ENTRY_SIZE,
|
||||||
)
|
)
|
||||||
.expect("Failed to store");
|
.expect("Failed to store");
|
||||||
|
|
||||||
|
|
|
||||||
35
src/main.rs
35
src/main.rs
|
|
@ -15,15 +15,18 @@ pub(crate) mod mime;
|
||||||
mod multicall;
|
mod multicall;
|
||||||
#[cfg(feature = "use-toplevel")] mod wayland;
|
#[cfg(feature = "use-toplevel")] mod wayland;
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::{
|
||||||
decode::DecodeCommand,
|
commands::{
|
||||||
delete::DeleteCommand,
|
decode::DecodeCommand,
|
||||||
import::ImportCommand,
|
delete::DeleteCommand,
|
||||||
list::ListCommand,
|
import::ImportCommand,
|
||||||
query::QueryCommand,
|
list::ListCommand,
|
||||||
store::StoreCommand,
|
query::QueryCommand,
|
||||||
watch::WatchCommand,
|
store::StoreCommand,
|
||||||
wipe::WipeCommand,
|
watch::WatchCommand,
|
||||||
|
wipe::WipeCommand,
|
||||||
|
},
|
||||||
|
db::DEFAULT_MAX_ENTRY_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|
@ -42,6 +45,16 @@ struct Cli {
|
||||||
#[arg(long, default_value_t = 20)]
|
#[arg(long, default_value_t = 20)]
|
||||||
max_dedupe_search: u64,
|
max_dedupe_search: u64,
|
||||||
|
|
||||||
|
/// Minimum size (in bytes) for clipboard entries. Entries smaller than this
|
||||||
|
/// will not be stored.
|
||||||
|
#[arg(long, env = "STASH_MIN_SIZE")]
|
||||||
|
min_size: Option<usize>,
|
||||||
|
|
||||||
|
/// Maximum size (in bytes) for clipboard entries. Entries larger than this
|
||||||
|
/// will not be stored. Defaults to 5MB.
|
||||||
|
#[arg(long, default_value_t = DEFAULT_MAX_ENTRY_SIZE, env = "STASH_MAX_SIZE")]
|
||||||
|
max_size: usize,
|
||||||
|
|
||||||
/// Maximum width (in characters) for clipboard entry previews in list
|
/// Maximum width (in characters) for clipboard entry previews in list
|
||||||
/// output.
|
/// output.
|
||||||
#[arg(long, default_value_t = 100)]
|
#[arg(long, default_value_t = 100)]
|
||||||
|
|
@ -226,6 +239,8 @@ fn main() -> color_eyre::eyre::Result<()> {
|
||||||
&cli.excluded_apps,
|
&cli.excluded_apps,
|
||||||
#[cfg(not(feature = "use-toplevel"))]
|
#[cfg(not(feature = "use-toplevel"))]
|
||||||
&[],
|
&[],
|
||||||
|
cli.min_size,
|
||||||
|
cli.max_size,
|
||||||
),
|
),
|
||||||
"failed to store entry",
|
"failed to store entry",
|
||||||
);
|
);
|
||||||
|
|
@ -451,6 +466,8 @@ fn main() -> color_eyre::eyre::Result<()> {
|
||||||
&[],
|
&[],
|
||||||
expire_after,
|
expire_after,
|
||||||
&mime_type,
|
&mime_type,
|
||||||
|
cli.min_size,
|
||||||
|
cli.max_size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue