Implement safe public LSL delivery (#129)
This commit is contained in:
134
crates/metacrate-lsl-tools/src/lsl_validation.rs
Normal file
134
crates/metacrate-lsl-tools/src/lsl_validation.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Bounded structural LSL source validation for native `MetaCrate` consumers.
|
||||
|
||||
use crate::MAX_SOURCE_UNITS;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum LslSyntaxError {
|
||||
Empty,
|
||||
Oversized,
|
||||
MissingDefaultState,
|
||||
UnterminatedString,
|
||||
UnterminatedComment,
|
||||
MismatchedDelimiter,
|
||||
InvalidCharacter,
|
||||
}
|
||||
|
||||
impl fmt::Display for LslSyntaxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for LslSyntaxError {}
|
||||
|
||||
/// Performs the bounded lexical and delimiter parse required before generated
|
||||
/// LSL may cross an inventory boundary. This rejects malformed lexical states
|
||||
/// and structural parse failures; it does not claim simulator runtime safety.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`LslSyntaxError`] when the source exceeds its hard bound or its
|
||||
/// lexical/structural parse does not form a complete LSL default state.
|
||||
pub fn validate_lsl_source(source: &str) -> Result<(), LslSyntaxError> {
|
||||
if source.trim().is_empty() {
|
||||
return Err(LslSyntaxError::Empty);
|
||||
}
|
||||
if source.len() > MAX_SOURCE_UNITS {
|
||||
return Err(LslSyntaxError::Oversized);
|
||||
}
|
||||
if !source
|
||||
.split(|c: char| !c.is_alphanumeric() && c != '_')
|
||||
.any(|token| token == "default")
|
||||
{
|
||||
return Err(LslSyntaxError::MissingDefaultState);
|
||||
}
|
||||
let mut stack = Vec::new();
|
||||
let mut chars = source.chars().peekable();
|
||||
let mut string = false;
|
||||
let mut escaped = false;
|
||||
while let Some(ch) = chars.next() {
|
||||
if ch == '\0' {
|
||||
return Err(LslSyntaxError::InvalidCharacter);
|
||||
}
|
||||
if string {
|
||||
if escaped {
|
||||
escaped = false;
|
||||
} else if ch == '\\' {
|
||||
escaped = true;
|
||||
} else if ch == '"' {
|
||||
string = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ch == '"' {
|
||||
string = true;
|
||||
continue;
|
||||
}
|
||||
if ch == '/' && chars.peek() == Some(&'/') {
|
||||
chars.next();
|
||||
for next in chars.by_ref() {
|
||||
if next == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ch == '/' && chars.peek() == Some(&'*') {
|
||||
chars.next();
|
||||
let mut closed = false;
|
||||
while let Some(next) = chars.next() {
|
||||
if next == '*' && chars.peek() == Some(&'/') {
|
||||
chars.next();
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !closed {
|
||||
return Err(LslSyntaxError::UnterminatedComment);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
match ch {
|
||||
'{' | '(' | '[' => stack.push(ch),
|
||||
'}' | ')' | ']' => {
|
||||
let expected = match ch {
|
||||
'}' => '{',
|
||||
')' => '(',
|
||||
_ => '[',
|
||||
};
|
||||
if stack.pop() != Some(expected) {
|
||||
return Err(LslSyntaxError::MismatchedDelimiter);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if string {
|
||||
Err(LslSyntaxError::UnterminatedString)
|
||||
} else if stack.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LslSyntaxError::MismatchedDelimiter)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn lexical_and_structural_states_are_bounded() {
|
||||
assert!(validate_lsl_source("default { state_entry() { llSay(0, \"hi\"); } }").is_ok());
|
||||
assert_eq!(
|
||||
validate_lsl_source("default { \"x"),
|
||||
Err(LslSyntaxError::UnterminatedString)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_lsl_source("default { /*"),
|
||||
Err(LslSyntaxError::UnterminatedComment)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_lsl_source("default { ]"),
|
||||
Err(LslSyntaxError::MismatchedDelimiter)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user