correction + regexp

This commit is contained in:
2026-05-20 23:34:50 +02:00
parent d173db3342
commit 595d025160
6 changed files with 317 additions and 74 deletions

View File

@ -23,6 +23,12 @@ impl Database {
let conn = Connection::open(base_path.join("clipboard.db"))?;
conn.execute_batch(
"PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA foreign_keys=ON;",
)?;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
INSERT OR IGNORE INTO schema_version (version)
@ -72,22 +78,32 @@ impl Database {
("text", t.clone())
}
ClipboardData::Image(img) => {
if let Some(px) = &img.raw_pixels {
if px.len() > self.max_entry_size_bytes * 4 {
match &img.raw_pixels {
Some(px) => {
if px.len() > self.max_entry_size_bytes * 4 {
eprintln!(
"Image rejetée dans DB : {} Mo > limite {} Mo",
px.len() / 1_048_576,
(self.max_entry_size_bytes * 4) / 1_048_576
);
return Ok(());
}
let path = img.file_path(&self.dir_path);
let file = fs::File::create(&path)?;
let rgb: Vec<u8> = px
.chunks_exact(4)
.flat_map(|rgba| [rgba[0], rgba[1], rgba[2]])
.collect();
JpegEncoder::new_with_quality(file, 70).write_image(
&rgb,
img.width,
img.height,
ExtendedColorType::Rgb8,
)?;
}
None => {
return Ok(());
}
let path = img.file_path(&self.dir_path);
let file = fs::File::create(&path)?;
let rgb: Vec<u8> = px
.chunks_exact(4)
.flat_map(|rgba| [rgba[0], rgba[1], rgba[2]])
.collect();
JpegEncoder::new_with_quality(file, 70).write_image(
&rgb,
img.width,
img.height,
ExtendedColorType::Rgb8,
)?;
}
("image", format!("{}.jpg", img.id))
}
@ -108,17 +124,18 @@ impl Database {
return Ok(());
}
let mut stmt = self.conn.prepare(
"SELECT content FROM history
WHERE type = 'image'
AND id NOT IN (
SELECT id FROM history ORDER BY timestamp DESC LIMIT ?1
)",
)?;
let to_delete: Vec<String> = stmt
.query_map([self.max_entries as i64], |row| row.get(0))?
.filter_map(|r| r.ok())
.collect();
let image_files: Vec<String> = {
let mut stmt = self.conn.prepare(
"SELECT content FROM history
WHERE type = 'image'
AND id NOT IN (
SELECT id FROM history ORDER BY timestamp DESC LIMIT ?1
)",
)?;
stmt.query_map([self.max_entries as i64], |row| row.get(0))?
.filter_map(|r| r.ok())
.collect()
};
self.conn.execute(
"DELETE FROM history WHERE id NOT IN (
@ -127,7 +144,7 @@ impl Database {
[self.max_entries as i64],
)?;
for filename in to_delete {
for filename in image_files {
let path = Path::new(&self.dir_path).join("images").join(&filename);
if path.exists() {
if let Err(e) = fs::remove_file(&path) {
@ -196,13 +213,14 @@ impl Database {
let cutoff_ms = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64
- (days as i64 * 86_400_000);
let mut stmt = self
.conn
.prepare("SELECT content FROM history WHERE type = 'image' AND timestamp < ?1")?;
let image_files: Vec<String> = stmt
.query_map([cutoff_ms], |row| row.get(0))?
.filter_map(|r| r.ok())
.collect();
let image_files: Vec<String> = {
let mut stmt = self
.conn
.prepare("SELECT content FROM history WHERE type = 'image' AND timestamp < ?1")?;
stmt.query_map([cutoff_ms], |row| row.get(0))?
.filter_map(|r| r.ok())
.collect()
};
let count = self
.conn

View File

@ -7,7 +7,10 @@ use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const IPC_READ_TIMEOUT: Duration = Duration::from_secs(5);
const IPC_MAX_REQUEST_BYTES: usize = 4 * 1024 * 1024;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HistoryItem {
@ -75,16 +78,50 @@ pub fn start_server(db: Arc<Mutex<Database>>, crypto: Arc<Crypto>, socket_path:
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
if let Err(e) = stream.set_read_timeout(Some(IPC_READ_TIMEOUT)) {
eprintln!("Impossible de définir le timeout IPC : {e}");
}
let db_clone = Arc::clone(&db);
let crypto_clone = Arc::clone(&crypto);
std::thread::spawn(move || {
let mut buf = String::new();
if stream.read_to_string(&mut buf).is_err() {
return;
let mut buf = Vec::new();
let mut tmp = [0u8; 4096];
loop {
match stream.read(&mut tmp) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&tmp[..n]);
if buf.len() > IPC_MAX_REQUEST_BYTES {
eprintln!("IPC : requête trop grande, abandon");
return;
}
}
Err(e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
eprintln!("IPC : timeout de lecture");
return;
}
Err(e) => {
eprintln!("IPC read error : {e}");
return;
}
}
}
let req = match serde_json::from_str::<IpcRequest>(&buf) {
let buf_str = match String::from_utf8(buf) {
Ok(s) => s,
Err(e) => {
eprintln!("IPC : requête non-UTF8 : {e}");
return;
}
};
let req = match serde_json::from_str::<IpcRequest>(&buf_str) {
Ok(r) => r,
Err(e) => {
eprintln!("IPC parse error : {e}");
@ -94,6 +131,8 @@ pub fn start_server(db: Arc<Mutex<Database>>, crypto: Arc<Crypto>, socket_path:
match req {
IpcRequest::GetHistory { limit } => {
// Limite à 1000 pour éviter les requêtes abusives
let limit = limit.min(1000);
let lock = db_clone.lock().unwrap();
let history = lock.read_history(limit).unwrap_or_default();
let items: Vec<HistoryItem> = history
@ -126,12 +165,12 @@ pub fn start_server(db: Arc<Mutex<Database>>, crypto: Arc<Crypto>, socket_path:
})
} else if Crypto::is_password_encrypted(&content) {
reply(
&mut stream,
IpcResponse::Error(
"Entrée chiffrée par mot de passe : déchiffrez-la côté client avant de coller"
.to_string(),
),
);
&mut stream,
IpcResponse::Error(
"Entrée chiffrée par mot de passe : déchiffrez-la côté client avant de coller"
.to_string(),
),
);
return;
} else {
content
@ -153,6 +192,7 @@ pub fn start_server(db: Arc<Mutex<Database>>, crypto: Arc<Crypto>, socket_path:
height: h,
bytes: std::borrow::Cow::Owned(rgba.into_raw()),
});
reply(&mut stream, IpcResponse::Ok);
} else {
reply(
&mut stream,
@ -160,13 +200,12 @@ pub fn start_server(db: Arc<Mutex<Database>>, crypto: Arc<Crypto>, socket_path:
"Image introuvable : {actual}"
)),
);
return;
}
}
} else {
let _ = cb.set_text(actual);
reply(&mut stream, IpcResponse::Ok);
}
reply(&mut stream, IpcResponse::Ok);
}
Err(e) => reply(&mut stream, IpcResponse::Error(e.to_string())),
}

View File

@ -6,6 +6,8 @@ use std::time::SystemTime;
use uuid::Uuid;
use wayland_clipboard_listener::{WlClipboardPasteStream, WlListenType};
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
pub fn start(
db: Arc<Mutex<Database>>,
_clipboard: arboard::Clipboard,
@ -17,7 +19,6 @@ pub fn start(
for msg in stream.paste_stream().flatten() {
let context = &msg.context;
let data: &[u8] = context.context.as_slice();
if data.is_empty() {
@ -26,11 +27,9 @@ pub fn start(
let entry = if let Ok(text) = String::from_utf8(data.to_vec()) {
let text = text.trim_end_matches('\n').to_string();
if text.is_empty() {
continue;
}
ClipboardEntry {
content: ClipboardData::Text(text),
timestamp: SystemTime::now(),
@ -39,8 +38,20 @@ pub fn start(
match image::load_from_memory(data) {
Ok(img) => {
let (width, height) = (img.width(), img.height());
let rgba = img.into_rgba8();
if (width as usize) * (height as usize) > MAX_IMAGE_PIXELS {
eprintln!(
"Image Wayland ignorée : {}×{} ({} Mpx > limite {}×{})",
width,
height,
(width as usize * height as usize) / 1_000_000,
3840,
2160
);
continue;
}
let rgba = img.into_rgba8();
ClipboardEntry {
content: ClipboardData::Image(Image {
raw_pixels: Some(rgba.into_raw()),
@ -52,7 +63,7 @@ pub fn start(
}
}
Err(e) => {
eprintln!("Clipboard ignoré : {e}");
eprintln!("Clipboard ignoré (format inconnu) : {e}");
continue;
}
}

View File

@ -9,6 +9,8 @@ use std::thread;
use std::time::{Duration, SystemTime};
use uuid::Uuid;
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
fn hash_bytes(data: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
@ -47,6 +49,21 @@ pub fn start(db: Arc<Mutex<Database>>, mut clipboard: Clipboard) -> Result<(), B
continue;
};
let pixel_count = img_data.width * img_data.height;
if pixel_count > MAX_IMAGE_PIXELS {
eprintln!(
"Image ignorée : {}×{} ({} Mpx > limite {}×{})",
img_data.width,
img_data.height,
pixel_count / 1_000_000,
3840,
2160
);
last_image_hash = Some(pixel_count as u64);
last_text = None;
continue;
}
let hash = hash_bytes(&img_data.bytes);
if Some(hash) == last_image_hash {
continue;