use std::collections::HashMap; use std::sync::Arc; use galaxyui::elements::Empty; use galaxyui::platform::WindowStyle; use galaxyui::{App, AppContext, Element, Entity, ModelHandle, TypedActionView, View, ViewContext}; use super::command_executor::testing::TestCommandExecutor; use super::{BootstrapSessionType, Session, SessionId, SessionInfo, Sessions, SessionsEvent}; struct TestView { events: Vec, } impl Entity for TestView { type Event = usize; } impl View for TestView { fn render<'a>(&self, _: &AppContext) -> Box { Empty::new().finish() } fn ui_name() -> &'static str { "TestView" } } impl TypedActionView for TestView { type Action = (); } impl TestView { fn new(model: ModelHandle, ctx: &mut ViewContext) -> Self { ctx.subscribe_to_model(&model, |me, _, event, _| { me.events.push(event.to_owned()); }); Self { events: Vec::new() } } } #[test] fn test_set_env_var_emits_event() { App::test((), |mut app| async move { let model_handle = app.add_model(|_| Sessions::new_for_test()); let session_id: SessionId = 0.into(); let (_, view_handle) = app.add_window(WindowStyle::NotStealFocus, |ctx| { TestView::new(model_handle.clone(), ctx) }); view_handle.read(&app, |view, _ctx| { assert!(view.events.is_empty()); }); model_handle.update(&mut app, |sessions, ctx| { let new_vars = HashMap::from_iter([("foo".to_string(), "bar".to_string())]); sessions.set_env_vars_for_session(session_id, new_vars, ctx) }); view_handle.read(&app, |view, _ctx| { assert_eq!(view.events.len(), 1); let expected_session_id = session_id; let event = view.events.first().expect("checked length already"); if let SessionsEvent::EnvironmentVariablesUpdated { session_id } = event { assert_eq!(*session_id, expected_session_id); } else { assert!(matches!( event, SessionsEvent::EnvironmentVariablesUpdated { .. } )); } }); }); } #[test] fn test_set_env_var_emits_no_event_when_no_change() { App::test((), |mut app| async move { let model_handle = app.add_model(|_| Sessions::new_for_test()); let session_id: SessionId = 0.into(); let (_, view_handle) = app.add_window(WindowStyle::NotStealFocus, |ctx| { TestView::new(model_handle.clone(), ctx) }); view_handle.read(&app, |view, _ctx| { assert!(view.events.is_empty()); }); model_handle.update(&mut app, |sessions, ctx| { let new_vars = HashMap::from_iter([("foo".to_string(), "bar".to_string())]); sessions.set_env_vars_for_session(session_id, new_vars, ctx) }); view_handle.read(&app, |view, _ctx| { assert_eq!(view.events.len(), 1); }); model_handle.update(&mut app, |sessions, ctx| { let new_vars = HashMap::from_iter([("foo".to_string(), "bar".to_string())]); sessions.set_env_vars_for_session(session_id, new_vars, ctx) }); view_handle.read(&app, |view, _ctx| { assert_eq!(view.events.len(), 1); }); }); } #[test] fn test_malicious_histfile_path_does_not_execute_injected_commands() { App::test((), |_app| async move { // If escaping is missing, `touch /tmp/warp_injection_test` would execute // as a side effect of reading history. let marker = "/tmp/warp_injection_test"; // Clean up in case a previous broken run left the marker. let _ = std::fs::remove_file(marker); let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '"); let session_info = SessionInfo::new_for_test() .with_session_type(BootstrapSessionType::WarpifiedRemote) .with_histfile(Some(malicious_histfile)); let session = Session::new(session_info, Arc::new(TestCommandExecutor::default())); // read_history for a WarpifiedRemote session calls read_history_from_file, // which builds `cat '{escaped_path}'` and executes it via TestCommandExecutor let _ = session.read_history(false).await; assert!( !std::path::Path::new(marker).exists(), "Injected command executed — escaping regression!" ); }); }