use std::io::{self, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::PathBuf;
use crate::error::{NdsError, Result};
use crate::session::Session;
pub fn create_listener(session_id: &str) -> Result<(UnixListener, PathBuf)> {
let socket_path = Session::socket_dir()?.join(format!("{}.sock", session_id));
if socket_path.exists() {
std::fs::remove_file(&socket_path)?;
}
let listener = UnixListener::bind(&socket_path)
.map_err(|e| NdsError::SocketError(format!("Failed to bind socket: {}", e)))?;
let metadata = std::fs::metadata(&socket_path)?;
let mut permissions = metadata.permissions();
permissions.set_mode(0o600);
std::fs::set_permissions(&socket_path, permissions)?;
Ok((listener, socket_path))
}
pub fn send_resize_command(socket: &mut UnixStream, cols: u16, rows: u16) -> io::Result<()> {
let cols = cols.min(9999).max(1);
let rows = rows.min(9999).max(1);
let resize_cmd = format!("\x1b]nds:resize:{}:{}\x07", cols, rows);
socket.write_all(resize_cmd.as_bytes())?;
socket.flush()
}
pub fn parse_nds_command(data: &[u8]) -> Option<(String, Vec<String>)> {
if data.len() > 10 && data.len() < 8192 && data.starts_with(b"\x1b]nds:") {
if let Ok(cmd_str) = std::str::from_utf8(data) {
if let Some(end_idx) = cmd_str.find('\x07') {
let cmd = &cmd_str[6..end_idx];
if !is_valid_command(cmd) {
return None;
}
let parts: Vec<String> = cmd.split(':').map(|s| sanitize_input(s)).collect();
if !parts.is_empty() && parts.len() <= 10 {
return Some((parts[0].clone(), parts[1..].to_vec()));
}
}
}
}
None
}
pub fn get_command_end(data: &[u8]) -> Option<usize> {
if data.len() < 8192 && data.starts_with(b"\x1b]nds:") {
if let Ok(cmd_str) = std::str::from_utf8(data) {
if let Some(end_idx) = cmd_str.find('\x07') {
if end_idx < 1024 {
return Some(end_idx + 1);
}
}
}
}
None
}
fn is_valid_command(cmd: &str) -> bool {
const ALLOWED_COMMANDS: &[&str] = &[
"resize",
"detach",
"attach",
"list",
"kill",
"switch",
"scrollback",
"clear",
"refresh",
];
if let Some(command) = cmd.split(':').next() {
ALLOWED_COMMANDS.contains(&command)
} else {
false
}
}
fn sanitize_input(input: &str) -> String {
input
.chars()
.filter(|c| !c.is_control() || *c == '\n' || *c == '\r' || *c == '\t')
.take(4096) .collect()
}