rmux-server 0.9.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{
    command_inventory::RMUX_EXTENSION_COMMANDS, command_parser::SOURCE_FILE_MAX_COMMAND_BYTES,
};
use rmux_proto::{
    decode_internal_runtime_command_arguments, CommandOutput, ErrorResponse, Response, RmuxError,
    SourceFileRequest, SourceFileResponse, INTERNAL_CANONICAL_COMMAND_EXECUTION_PATH,
    INTERNAL_PARSE_TIME_ASSIGNMENTS_PATH, INTERNAL_RUNTIME_COMMAND_EXPANSION_PATH,
};

use super::super::RequestHandler;
use super::parser_context::command_parser_from_state;

const INTERNAL_SOURCE_FILE_PATHS: [&str; 3] = [
    INTERNAL_RUNTIME_COMMAND_EXPANSION_PATH,
    INTERNAL_PARSE_TIME_ASSIGNMENTS_PATH,
    INTERNAL_CANONICAL_COMMAND_EXECUTION_PATH,
];

impl RequestHandler {
    pub(super) async fn handle_internal_source_file_request(
        &self,
        requester_pid: u32,
        request: &SourceFileRequest,
    ) -> Option<Response> {
        if request.paths.as_slice() == [INTERNAL_RUNTIME_COMMAND_EXPANSION_PATH] {
            return Some(match runtime_command_expansion_payload(request) {
                Ok(payload) => {
                    self.handle_internal_runtime_command_expansion(payload)
                        .await
                }
                Err(error) => Response::Error(ErrorResponse { error }),
            });
        }
        if request.paths.as_slice() == [INTERNAL_PARSE_TIME_ASSIGNMENTS_PATH] {
            return Some(match parse_time_assignments_payload(request) {
                Ok(payload) => {
                    self.handle_internal_parse_time_assignments(requester_pid, payload)
                        .await
                }
                Err(error) => Response::Error(ErrorResponse { error }),
            });
        }
        None
    }

    async fn handle_internal_runtime_command_expansion(&self, payload: &str) -> Response {
        let arguments = match decode_internal_runtime_command_arguments(payload) {
            Ok(arguments) if !arguments.is_empty() => arguments,
            Ok(_) => {
                return server_error("invalid empty runtime command argument vector");
            }
            Err(_) => {
                return server_error("invalid internal runtime command expansion payload");
            }
        };

        let state = self.state.lock().await;
        let parser = command_parser_from_state(&state)
            .with_exact_commands(RMUX_EXTENSION_COMMANDS)
            .with_max_command_bytes(SOURCE_FILE_MAX_COMMAND_BYTES);
        let parsed = match parser.parse_arguments_with_assignments(&arguments) {
            Ok(parsed) => parsed,
            Err(error) => return server_error(error.message()),
        };
        let canonical = parsed.to_tmux_reparse_string();
        if canonical.is_empty() {
            Response::SourceFile(SourceFileResponse::no_output())
        } else {
            Response::SourceFile(SourceFileResponse::from_output(CommandOutput::from_stdout(
                canonical,
            )))
        }
    }

    async fn handle_internal_parse_time_assignments(
        &self,
        requester_pid: u32,
        payload: &str,
    ) -> Response {
        let parsed = {
            let state = self.state.lock().await;
            let parser = command_parser_from_state(&state)
                .with_exact_commands(RMUX_EXTENSION_COMMANDS)
                .with_max_command_bytes(SOURCE_FILE_MAX_COMMAND_BYTES);
            match parser.parse_one_group(payload) {
                Ok(parsed) if parsed.commands().is_empty() && !parsed.assignments().is_empty() => {
                    parsed
                }
                Ok(_) => return server_error("invalid internal parse-time assignment payload"),
                Err(error) => return server_error(error.message()),
            }
        };

        match self
            .apply_parse_time_assignments(requester_pid, &parsed, None)
            .await
        {
            Ok(()) => Response::SourceFile(SourceFileResponse::no_output()),
            Err(error) => Response::Error(ErrorResponse { error }),
        }
    }
}

pub(super) fn validate_internal_source_file_path(
    request: &SourceFileRequest,
) -> Result<(), RmuxError> {
    if !request.paths.iter().any(|path| path.contains('\0')) {
        return Ok(());
    }
    if request.paths.len() == 1
        && INTERNAL_SOURCE_FILE_PATHS
            .iter()
            .any(|internal| request.paths[0] == *internal)
    {
        return Ok(());
    }
    Err(RmuxError::Server(
        "invalid internal source-file request path".to_owned(),
    ))
}

pub(super) fn canonical_command_execution_request(
    request: &SourceFileRequest,
) -> Result<bool, RmuxError> {
    if !request
        .paths
        .iter()
        .any(|path| path == INTERNAL_CANONICAL_COMMAND_EXECUTION_PATH)
    {
        return Ok(false);
    }
    if request.paths.as_slice() != [INTERNAL_CANONICAL_COMMAND_EXECUTION_PATH]
        || request.quiet
        || request.parse_only
        || request.verbose
        || request.expand_paths
        || request.target.is_some()
        || request.stdin.is_none()
    {
        return Err(RmuxError::Server(
            "invalid internal canonical command execution request".to_owned(),
        ));
    }
    Ok(true)
}

fn runtime_command_expansion_payload(request: &SourceFileRequest) -> Result<&str, RmuxError> {
    if request.quiet
        || !request.parse_only
        || !request.verbose
        || request.expand_paths
        || request.target.is_some()
    {
        return Err(RmuxError::Server(
            "invalid internal runtime command expansion request".to_owned(),
        ));
    }
    request.stdin.as_deref().ok_or_else(|| {
        RmuxError::Server("missing internal runtime command expansion payload".to_owned())
    })
}

fn parse_time_assignments_payload(request: &SourceFileRequest) -> Result<&str, RmuxError> {
    if request.quiet
        || request.parse_only
        || request.verbose
        || request.expand_paths
        || request.target.is_some()
    {
        return Err(RmuxError::Server(
            "invalid internal parse-time assignment request".to_owned(),
        ));
    }
    request.stdin.as_deref().ok_or_else(|| {
        RmuxError::Server("missing internal parse-time assignment payload".to_owned())
    })
}

fn server_error(message: &str) -> Response {
    Response::Error(ErrorResponse {
        error: RmuxError::Server(message.to_owned()),
    })
}