use super::*;
use exfiltrate_internal::args::{ArgKind, ArgSpec};
use exfiltrate_internal::command::Command;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
static STREAMING_ARGS: &[ArgSpec] = &[ArgSpec::flag("count", "how many chunks", ArgKind::Integer)];
struct Streaming;
impl Command for Streaming {
fn name(&self) -> &'static str {
"server_test_streaming"
}
fn short_description(&self) -> &'static str {
"emits chunks"
}
fn full_description(&self) -> &'static str {
"emits chunks"
}
fn args(&self) -> &'static [ArgSpec] {
STREAMING_ARGS
}
fn execute(&self, args: Vec<String>) -> Result<Response, Response> {
self.execute_with(args, &CommandContext::detached())
}
fn execute_with(
&self,
_args: Vec<String>,
context: &CommandContext,
) -> Result<Response, Response> {
for index in 0..3 {
context.check_cancelled()?;
let _ = context.emit(format!("chunk {index}"));
}
Ok("done".into())
}
}
fn invoke(name: &str, context: &CommandContext) -> CommandResponse {
do_command(
CommandInvocation::new(name.to_string(), Vec::new(), 1),
context,
)
}
#[cfg(not(target_arch = "wasm32"))]
struct Panicking;
#[cfg(not(target_arch = "wasm32"))]
impl Command for Panicking {
fn name(&self) -> &'static str {
"server_test_panicking"
}
fn short_description(&self) -> &'static str {
"panics on purpose"
}
fn full_description(&self) -> &'static str {
"panics on purpose"
}
fn execute(&self, _args: Vec<String>) -> Result<Response, Response> {
panic!("boom")
}
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn a_panicking_command_fails_the_request_rather_than_the_connection() {
crate::try_add_command(Panicking).ok();
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let response = invoke("server_test_panicking", &CommandContext::detached());
std::panic::set_hook(previous);
assert!(!response.success);
let message = response.response.to_string();
assert!(message.contains("server_test_panicking"), "{message}");
assert!(message.contains("boom"), "{message}");
}
#[test]
fn an_unknown_command_points_at_the_list() {
let response = invoke("server_test_no_such_command", &CommandContext::detached());
assert!(!response.success);
let message = response.response.to_string();
assert!(message.contains("command not found"), "{message}");
assert!(message.contains("exfiltrate list"), "{message}");
}
#[test]
fn a_streaming_command_emits_chunks_before_its_final_response() {
crate::try_add_command(Streaming).ok();
let chunks = Arc::new(Mutex::new(Vec::new()));
let sink_chunks = chunks.clone();
let context = CommandContext::new(
Arc::new(AtomicBool::new(false)),
Arc::new(move |payload: Response| {
sink_chunks.lock().unwrap().push(payload.to_string());
Ok(())
}),
);
let response = invoke("server_test_streaming", &context);
assert!(response.success);
assert_eq!(response.response.to_string(), "done");
assert_eq!(*chunks.lock().unwrap(), ["chunk 0", "chunk 1", "chunk 2"]);
}
#[test]
fn a_cancelled_command_stops_and_says_so() {
crate::try_add_command(Streaming).ok();
let cancelled = Arc::new(AtomicBool::new(true));
let context = CommandContext::new(cancelled, Arc::new(|_| Ok(())));
let response = invoke("server_test_streaming", &context);
assert!(!response.success);
assert!(
response.response.to_string().contains("cancelled"),
"{}",
response.response
);
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn a_reachable_address_invents_a_token_rather_than_refusing_to_listen() {
use exfiltrate_internal::transport::Address;
let public = Address::parse("0.0.0.0:1337").unwrap();
let Credential::Generated(token) = credential_for(&public, None).unwrap() else {
panic!("binding every interface with no credential is the exposure this prevents")
};
assert!(!token.is_empty());
let Credential::Generated(second) = credential_for(&public, None).unwrap() else {
panic!("expected a generated token")
};
assert_ne!(token, second);
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn a_configured_token_is_honoured_everywhere_and_a_local_address_needs_none() {
use exfiltrate_internal::transport::Address;
assert_eq!(
credential_for(
&Address::parse("127.0.0.1:1337").unwrap(),
Some("TOKEN".to_string())
)
.unwrap(),
Credential::Configured("TOKEN".to_string())
);
assert_eq!(
credential_for(&Address::parse("127.0.0.1:1337").unwrap(), None).unwrap(),
Credential::None
);
#[cfg(unix)]
{
assert_eq!(
credential_for(&Address::parse("unix:/tmp/x.sock").unwrap(), None).unwrap(),
Credential::None
);
assert_eq!(
credential_for(&Address::parse("fd:3").unwrap(), None).unwrap(),
Credential::None
);
}
}
#[test]
fn a_registered_command_is_found_by_name_rather_than_by_position() {
crate::commands::register_commands(&crate::Config::default());
let response = invoke("list", &CommandContext::detached());
assert!(response.success);
}