diff options
| author | Eric Yue <jiacheng.yue@foxmail.com> | 2026-09-16 20:41:41 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-16 14:41:41 +0200 |
| commit | ab15da285e9d74bea476375f635f73148d551d24 (patch) | |
| tree | 57549bb52ebee34fa4015268710a7e6973193377 /src | |
| parent | 68ae6adea0add496cea227030c94e5d3172f4e86 (diff) | |
fix: require exact session names before deletion (#4233)
* fix: require exact session names before deletion
refs #3819
* test: isolate session deletion regression tests
refs #3819
---------
Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/session.rs | 31 |
1 files changed, 29 insertions, 2 deletions
diff --git a/src/session.rs b/src/session.rs index 09657fa0..29e3e17e 100644 --- a/src/session.rs +++ b/src/session.rs @@ -301,14 +301,16 @@ pub fn delete_session(name: &str) -> Result<SessionInfo, String> { return Err("deleting the default session is not supported".to_string()); } validate_name(name)?; - let socket_path = api_socket_path_for(Some(name)); + let Some(dir) = exact_session_dir_for_delete(name)? else { + return Ok(session_info(Some(name))); + }; + let socket_path = dir.join("herdr.sock"); if is_running_at(&socket_path) { return Err(format!( "session {name} is running; stop it before deleting" )); } let info = session_info(Some(name)); - let dir = data_dir_for(Some(name)); match std::fs::remove_dir_all(&dir) { Ok(()) => Ok(info), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(info), @@ -316,6 +318,31 @@ pub fn delete_session(name: &str) -> Result<SessionInfo, String> { } } +fn exact_session_dir_for_delete(name: &str) -> Result<Option<PathBuf>, String> { + let sessions_dir = crate::config::config_dir().join("sessions"); + let entries = match std::fs::read_dir(&sessions_dir) { + Ok(entries) => entries, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err.to_string()), + }; + for entry in entries { + let entry = entry.map_err(|err| err.to_string())?; + if entry.file_name() == std::ffi::OsStr::new(name) { + return Ok(Some(entry.path())); + } + } + + // A path lookup alone can resolve a different spelling on case-insensitive + // filesystems. Never probe its socket or delete it without an exact entry. + match std::fs::symlink_metadata(sessions_dir.join(name)) { + Ok(_) => Err(format!( + "session {name} does not match an exact session name; use the spelling shown by `herdr session list`" + )), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err.to_string()), + } +} + fn send_stop_request( mut stream: LocalStream, request: &serde_json::Value, |
