tastty-core 0.1.0

Sans-IO core of the tastty terminal session library: VT parser, screen buffer, and byte encoders.
use base64::Engine as _;

use super::super::{ClipboardTarget, Screen, ScreenEvent};

pub(in crate::screen) fn handle_set(screen: &mut Screen, params: &[&[u8]]) {
    let pc = params.get(1).copied().unwrap_or(b"");
    let pd = params.get(2).copied().unwrap_or(b"");
    let targets = parse_targets(pc);
    let cap = screen.host_profile.clipboard_max_bytes;
    let event = if pd == b"?" {
        ScreenEvent::ClipboardQuery { targets }
    } else if pd.is_empty() {
        ScreenEvent::ClipboardClear { targets }
    } else if pd.len().saturating_mul(3) / 4 > cap {
        // Pre-decode rejection: a base64 payload of length
        // N decodes to at most N * 3 / 4 bytes, so this
        // bound is conservative and lets us bail before
        // allocating the decoded buffer.
        ScreenEvent::ClipboardWriteRejected {
            targets,
            decoded_len: pd.len().saturating_mul(3) / 4,
        }
    } else {
        match base64::engine::general_purpose::STANDARD.decode(pd) {
            Ok(data) if data.len() > cap => ScreenEvent::ClipboardWriteRejected {
                targets,
                decoded_len: data.len(),
            },
            Ok(data) => ScreenEvent::ClipboardWrite { targets, data },
            Err(err) => {
                tracing::debug!(?err, "dropping OSC 52 write with invalid base64");
                return;
            }
        }
    };
    screen.pending_events.push(event);
}

pub(in crate::screen) fn parse_targets(pc: &[u8]) -> Vec<ClipboardTarget> {
    if pc.is_empty() {
        return vec![ClipboardTarget::Select, ClipboardTarget::CutBuffer(0)];
    }
    let mut out: Vec<ClipboardTarget> = Vec::with_capacity(pc.len());
    for &byte in pc {
        let target = match byte {
            b'c' => ClipboardTarget::Clipboard,
            b'p' => ClipboardTarget::Primary,
            b'q' => ClipboardTarget::Secondary,
            b's' => ClipboardTarget::Select,
            b'0'..=b'7' => ClipboardTarget::CutBuffer(byte - b'0'),
            _ => continue,
        };
        if !out.contains(&target) {
            out.push(target);
        }
    }
    if out.is_empty() {
        return vec![ClipboardTarget::Select, ClipboardTarget::CutBuffer(0)];
    }
    out
}