kcode-rust-bins 3.0.0

Author, locally publish, and run small Rust binaries
Documentation
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::fmt;
use std::io;
use std::path::Path;

const MAX_ERROR_BYTES: usize = 12 * 1024;

/// One complete UTF-8 source file.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct File {
    pub path: String,
    pub contents: String,
}

/// One guest request after object IDs have been resolved to bytes.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RustBinInput {
    pub text: String,
    pub objects: Vec<Vec<u8>>,
}

/// One guest response before byte objects are saved.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RustBinOutput {
    pub text: String,
    pub objects: Vec<Vec<u8>>,
}

/// Host request after the integrating server has resolved object references.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RunRequest {
    pub text: String,
    pub objects: Vec<Vec<u8>>,
}

/// Host result before the integrating server saves output objects.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RunResult {
    /// Exact published version selected for this call.
    pub version: String,
    /// Exact guest-produced UTF-8 text.
    pub text: String,
    pub objects: Vec<Vec<u8>>,
    pub diagnostics: String,
}

/// A bounded displayable crate failure.
pub struct Error {
    category: &'static str,
    message: String,
}

pub type Result<T> = std::result::Result<T, Error>;

impl Error {
    pub(crate) fn new(category: &'static str, message: impl Into<String>) -> Self {
        Self {
            category,
            message: bound(message.into()),
        }
    }

    pub(crate) fn io(operation: &'static str, path: impl AsRef<Path>, source: io::Error) -> Self {
        Self::new(
            "io",
            format!("{operation} at {}: {source}", path.as_ref().display()),
        )
    }
}

impl fmt::Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "{}: {}", self.category, self.message)
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Error")
            .field("category", &self.category)
            .field("message", &self.message)
            .finish()
    }
}

impl StdError for Error {}

pub(crate) struct ValidSource {
    pub(crate) files: Vec<File>,
    pub(crate) version: String,
}

pub(crate) fn validate_name(name: &str) -> Result<()> {
    let mut bytes = name.bytes();
    if !bytes
        .next()
        .is_some_and(|byte| byte.is_ascii_alphanumeric())
        || !bytes.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
    {
        return Err(Error::new(
            "invalid_name",
            format!("invalid managed-binary name {name:?}"),
        ));
    }
    Ok(())
}

pub(crate) fn validate_source(files: &[File], expected_name: &str) -> Result<ValidSource> {
    let mut ordered = BTreeMap::new();
    for file in files {
        validate_path(&file.path)?;
        if file.path == "Cargo.lock" {
            return Err(Error::new(
                "invalid_source",
                "Cargo.lock is ephemeral and cannot be managed source",
            ));
        }
        if ordered
            .insert(file.path.clone(), file.contents.clone())
            .is_some()
        {
            return Err(Error::new(
                "invalid_source",
                format!("duplicate source path {:?}", file.path),
            ));
        }
    }

    let manifest = ordered
        .get("Cargo.toml")
        .ok_or_else(|| Error::new("invalid_source", "root Cargo.toml is required"))?;
    if !ordered.contains_key("Documentation.md") {
        return Err(Error::new(
            "invalid_source",
            "root Documentation.md is required",
        ));
    }
    let (package_name, version) = manifest_metadata(manifest)?;
    if package_name != expected_name {
        return Err(Error::new(
            "invalid_metadata",
            format!("[package].name must be {expected_name:?}, found {package_name:?}"),
        ));
    }

    Ok(ValidSource {
        files: ordered
            .into_iter()
            .map(|(path, contents)| File { path, contents })
            .collect(),
        version,
    })
}

pub(crate) fn manifest_metadata(manifest: &str) -> Result<(String, String)> {
    let mut section = String::new();
    let mut name = None;
    let mut version = None;

    for raw_line in manifest.lines() {
        let line = strip_comment(raw_line).trim();
        if line.is_empty() {
            continue;
        }
        if line.starts_with('[') && line.ends_with(']') {
            section.clear();
            section.push_str(line[1..line.len() - 1].trim());
            continue;
        }
        if section != "package" {
            continue;
        }
        let Some((raw_key, raw_value)) = line.split_once('=') else {
            continue;
        };
        match raw_key.trim() {
            "name" => {
                if name.is_some() {
                    return Err(Error::new("invalid_metadata", "duplicate [package].name"));
                }
                name = Some(parse_basic_string(raw_value.trim(), "name")?);
            }
            "version" => {
                if version.is_some() {
                    return Err(Error::new(
                        "invalid_metadata",
                        "duplicate [package].version",
                    ));
                }
                version = Some(parse_basic_string(raw_value.trim(), "version")?);
            }
            _ => {}
        }
    }

    let name = name.ok_or_else(|| {
        Error::new(
            "invalid_metadata",
            "literal [package].name is required in root Cargo.toml",
        )
    })?;
    validate_name(&name)?;
    let version = version.ok_or_else(|| {
        Error::new(
            "invalid_metadata",
            "literal [package].version is required in root Cargo.toml",
        )
    })?;
    validate_version(&version)?;
    Ok((name, version))
}

fn validate_path(path: &str) -> Result<()> {
    if path.is_empty()
        || path.starts_with('/')
        || path.ends_with('/')
        || path.contains('\\')
        || path.contains(':')
        || path.contains('\0')
        || path
            .split('/')
            .any(|component| component.is_empty() || matches!(component, "." | ".."))
    {
        return Err(Error::new(
            "unsafe_path",
            format!("invalid relative source path {path:?}"),
        ));
    }
    Ok(())
}

fn validate_version(version: &str) -> Result<()> {
    let mut count = 0;
    for component in version.split('.') {
        count += 1;
        if component.is_empty()
            || !component.bytes().all(|byte| byte.is_ascii_digit())
            || (component.len() > 1 && component.starts_with('0'))
            || component.parse::<u64>().is_err()
        {
            return Err(Error::new(
                "invalid_metadata",
                format!("noncanonical stable version {version:?}"),
            ));
        }
    }
    if count != 3 {
        return Err(Error::new(
            "invalid_metadata",
            format!("noncanonical stable version {version:?}"),
        ));
    }
    Ok(())
}

fn parse_basic_string(value: &str, field: &str) -> Result<String> {
    if value.len() < 2 || !value.starts_with('"') || !value.ends_with('"') {
        return Err(Error::new(
            "invalid_metadata",
            format!("[package].{field} must be a literal basic string"),
        ));
    }
    let inner = &value[1..value.len() - 1];
    if inner.contains(['"', '\\', '\n', '\r']) {
        return Err(Error::new(
            "invalid_metadata",
            format!("[package].{field} must not contain escapes or newlines"),
        ));
    }
    Ok(inner.to_owned())
}

fn strip_comment(line: &str) -> &str {
    let mut quoted = false;
    let mut escaped = false;
    for (index, character) in line.char_indices() {
        if escaped {
            escaped = false;
            continue;
        }
        if character == '\\' && quoted {
            escaped = true;
        } else if character == '"' {
            quoted = !quoted;
        } else if character == '#' && !quoted {
            return &line[..index];
        }
    }
    line
}

fn bound(mut message: String) -> String {
    if message.len() <= MAX_ERROR_BYTES {
        return message;
    }
    let mut end = MAX_ERROR_BYTES;
    while !message.is_char_boundary(end) {
        end -= 1;
    }
    message.truncate(end);
    message.push_str("… [truncated]");
    message
}

#[cfg(test)]
mod tests {
    use super::{File, manifest_metadata, validate_source};

    #[test]
    fn parses_canonical_metadata() {
        let manifest = "[package]\nname = \"demo\"\nversion = \"12.3.4\" # current\n";
        assert_eq!(
            manifest_metadata(manifest).unwrap(),
            ("demo".to_owned(), "12.3.4".to_owned())
        );
    }

    #[test]
    fn rejects_noncanonical_versions_and_lockfiles() {
        for version in ["1.2", "01.2.3", "1.2.3-beta", "1.2.3+build"] {
            let files = [
                File {
                    path: "Cargo.toml".to_owned(),
                    contents: format!("[package]\nname = \"demo\"\nversion = \"{version}\"\n"),
                },
                File {
                    path: "Documentation.md".to_owned(),
                    contents: String::new(),
                },
            ];
            assert!(validate_source(&files, "demo").is_err());
        }

        let files = [
            File {
                path: "Cargo.toml".to_owned(),
                contents: "[package]\nname = \"demo\"\nversion = \"1.2.3\"\n".to_owned(),
            },
            File {
                path: "Documentation.md".to_owned(),
                contents: String::new(),
            },
            File {
                path: "Cargo.lock".to_owned(),
                contents: "version = 4\n".to_owned(),
            },
        ];
        assert!(validate_source(&files, "demo").is_err());
    }
}