use std::io::{self, Write};
pub const MAX_LEN: usize = 74_994;
const ST: &str = "\x1b\\";
pub fn osc52(text: &str) -> Option<String> {
if text.is_empty() || text.len() > MAX_LEN {
return None;
}
Some(format!("\x1b]52;c;{}{ST}", base64_encode(text.as_bytes())))
}
pub fn write_clipboard(out: &mut impl Write, text: &str) -> io::Result<bool> {
match osc52(text) {
Some(seq) => {
out.write_all(seq.as_bytes())?;
out.flush()?;
Ok(true)
}
None => Ok(false),
}
}
fn base64_encode(data: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHABET[((n >> 18) & 0x3f) as usize] as char);
out.push(ALPHABET[((n >> 12) & 0x3f) as usize] as char);
out.push(if chunk.len() > 1 {
ALPHABET[((n >> 6) & 0x3f) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
ALPHABET[(n & 0x3f) as usize] as char
} else {
'='
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base64_matches_known_vectors() {
assert_eq!(base64_encode(b""), "");
assert_eq!(base64_encode(b"f"), "Zg==");
assert_eq!(base64_encode(b"fo"), "Zm8=");
assert_eq!(base64_encode(b"foo"), "Zm9v");
assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
}
#[test]
fn osc52_wraps_base64_payload() {
assert_eq!(
osc52("foobar"),
Some("\x1b]52;c;Zm9vYmFy\x1b\\".to_string())
);
}
#[test]
fn osc52_refuses_empty_and_oversized() {
assert_eq!(osc52(""), None);
let big = "x".repeat(MAX_LEN + 1);
assert_eq!(osc52(&big), None);
assert!(osc52(&"x".repeat(MAX_LEN)).is_some());
}
#[test]
fn write_clipboard_reports_whether_it_wrote() {
let mut buf: Vec<u8> = Vec::new();
assert!(write_clipboard(&mut buf, "hi").expect("write"));
assert_eq!(buf, b"\x1b]52;c;aGk=\x1b\\");
let mut empty: Vec<u8> = Vec::new();
assert!(!write_clipboard(&mut empty, "").expect("write"));
assert!(empty.is_empty());
}
}