Skip to main content

hara_native/
invoke_hta.rs

1//! Binary-safe invocation of already-loaded, fully qualified Hara Vars.
2//!
3//! This boundary deliberately does not parse, compile, macroexpand, load, or
4//! evaluate source text. Embedding hosts remain responsible for a closed Var
5//! allowlist before calling it.
6
7use crate::core::{self, PromiseState, Value};
8use crate::lang::data::Symbol;
9use crate::{hta, Runtime};
10use std::error::Error;
11use std::fmt;
12use std::rc::Rc;
13
14pub const MAX_INVOKE_HTA_RESULT_BYTES: usize = 256 * 1024;
15const MAX_PROMISE_UNWRAP_DEPTH: usize = 32;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum InvokeHtaError {
19    InvalidQualifiedVar,
20    MalformedInput(String),
21    NoncanonicalInput,
22    ArgumentsNotVector,
23    NamespaceMissing(String),
24    VarMissing(String),
25    VarNotCallable(String),
26    Execution(String),
27    PromiseRejected(String),
28    PromisePending,
29    PromiseDepthExceeded,
30    UnsupportedResult(String),
31    ResultTooLarge { actual: usize, maximum: usize },
32    SessionMissing(String),
33    BrokerClosed,
34    BrokerStopped,
35}
36
37impl InvokeHtaError {
38    pub const fn code(&self) -> &'static str {
39        match self {
40            Self::InvalidQualifiedVar => "invoke-hta/qualified-var-invalid",
41            Self::MalformedInput(_) => "invoke-hta/input-malformed",
42            Self::NoncanonicalInput => "invoke-hta/input-noncanonical",
43            Self::ArgumentsNotVector => "invoke-hta/arguments-not-vector",
44            Self::NamespaceMissing(_) => "invoke-hta/namespace-missing",
45            Self::VarMissing(_) => "invoke-hta/var-missing",
46            Self::VarNotCallable(_) => "invoke-hta/var-not-callable",
47            Self::Execution(_) => "invoke-hta/execution-failed",
48            Self::PromiseRejected(_) => "invoke-hta/promise-rejected",
49            Self::PromisePending => "invoke-hta/promise-pending",
50            Self::PromiseDepthExceeded => "invoke-hta/promise-depth-exceeded",
51            Self::UnsupportedResult(_) => "invoke-hta/result-unsupported",
52            Self::ResultTooLarge { .. } => "invoke-hta/result-too-large",
53            Self::SessionMissing(_) => "invoke-hta/session-missing",
54            Self::BrokerClosed => "invoke-hta/broker-closed",
55            Self::BrokerStopped => "invoke-hta/broker-stopped",
56        }
57    }
58}
59
60impl fmt::Display for InvokeHtaError {
61    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(formatter, "{}", self.code())?;
63        match self {
64            Self::MalformedInput(detail)
65            | Self::Execution(detail)
66            | Self::PromiseRejected(detail)
67            | Self::UnsupportedResult(detail) => write!(formatter, ": {detail}"),
68            Self::NamespaceMissing(namespace) => write!(formatter, ": {namespace}"),
69            Self::VarMissing(path) | Self::VarNotCallable(path) => {
70                write!(formatter, ": {path}")
71            }
72            Self::ResultTooLarge { actual, maximum } => {
73                write!(formatter, ": {actual} exceeds {maximum} bytes")
74            }
75            Self::SessionMissing(session) => write!(formatter, ": {session}"),
76            _ => Ok(()),
77        }
78    }
79}
80
81impl Error for InvokeHtaError {}
82
83impl Runtime {
84    pub fn invoke_hta(
85        &mut self,
86        qualified_var: &str,
87        arguments_hta: &[u8],
88    ) -> Result<Vec<u8>, InvokeHtaError> {
89        let (namespace_name, var_name) = split_qualified_var(qualified_var)?;
90        let decoded = match hta::decode_canonical(arguments_hta) {
91            Ok(value) => value,
92            Err(error) if error.starts_with("hta/value-noncanonical:") => {
93                return Err(InvokeHtaError::NoncanonicalInput)
94            }
95            Err(error) => return Err(InvokeHtaError::MalformedInput(error)),
96        };
97        let arguments = match decoded {
98            Value::Vector(values) => values.iter().cloned().collect::<Vec<_>>(),
99            _ => return Err(InvokeHtaError::ArgumentsNotVector),
100        };
101
102        let namespace = self
103            .namespace_registry
104            .find(namespace_name)
105            .ok_or_else(|| InvokeHtaError::NamespaceMissing(namespace_name.to_owned()))?;
106        let symbol = Symbol::parse(var_name);
107        let var = namespace
108            .resolve(&symbol)
109            .ok_or_else(|| InvokeHtaError::VarMissing(qualified_var.to_owned()))?;
110        let function = match var.deref_value() {
111            Value::Function(function) => function,
112            _ => return Err(InvokeHtaError::VarNotCallable(qualified_var.to_owned())),
113        };
114
115        let result = self
116            .invoke_loaded_function(function, arguments)
117            .map_err(InvokeHtaError::Execution)?;
118        encode_result(settle_result(result)?)
119    }
120
121    fn invoke_loaded_function(
122        &mut self,
123        function: Rc<core::Function>,
124        arguments: Vec<Value>,
125    ) -> Result<Value, String> {
126        let namespace_source = self.namespace_source();
127        core::with_capability_providers(
128            self.providers.file(),
129            self.providers.socket(),
130            self.providers.process(),
131            self.providers.kernel(),
132            || {
133                core::with_package_catalog(&self.package_catalog, || {
134                    core::with_promise_provider(self.providers.promise(), || {
135                        core::with_macros(self.macros.clone(), || {
136                            core::with_namespace_registry(&self.namespace_registry, || {
137                                core::with_namespace_source(namespace_source, || {
138                                    core::with_protocols(&self.protocols, || {
139                                        if let Some(handler) = &self.native_host_handler {
140                                            return core::with_host_calls(handler.clone(), || {
141                                                core::invoke_function_sync(function, arguments)
142                                            });
143                                        }
144                                        core::invoke_function_sync(function, arguments)
145                                    })
146                                })
147                            })
148                        })
149                    })
150                })
151            },
152        )
153    }
154}
155
156fn split_qualified_var(value: &str) -> Result<(&str, &str), InvokeHtaError> {
157    let Some((namespace, name)) = value.split_once('/') else {
158        return Err(InvokeHtaError::InvalidQualifiedVar);
159    };
160    if namespace.is_empty()
161        || name.is_empty()
162        || name.contains('/')
163        || value.bytes().any(|byte| byte.is_ascii_whitespace())
164    {
165        return Err(InvokeHtaError::InvalidQualifiedVar);
166    }
167    Ok((namespace, name))
168}
169
170fn settle_result(mut value: Value) -> Result<Value, InvokeHtaError> {
171    for _ in 0..MAX_PROMISE_UNWRAP_DEPTH {
172        let Value::Promise(promise) = value else {
173            return Ok(value);
174        };
175        value = match promise.wait_state() {
176            PromiseState::Fulfilled(value) => value,
177            PromiseState::Rejected(error) => {
178                return Err(InvokeHtaError::PromiseRejected(error.message().to_owned()))
179            }
180            PromiseState::Pending => return Err(InvokeHtaError::PromisePending),
181        };
182    }
183    Err(InvokeHtaError::PromiseDepthExceeded)
184}
185
186fn encode_result(result: Value) -> Result<Vec<u8>, InvokeHtaError> {
187    let encoded = hta::encode(&result).map_err(InvokeHtaError::UnsupportedResult)?;
188    if encoded.len() > MAX_INVOKE_HTA_RESULT_BYTES {
189        return Err(InvokeHtaError::ResultTooLarge {
190            actual: encoded.len(),
191            maximum: MAX_INVOKE_HTA_RESULT_BYTES,
192        });
193    }
194    Ok(encoded)
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn fulfilled_and_rejected_promises_are_typed() {
203        let fulfilled = core::Promise::new();
204        assert!(fulfilled.resolve(Value::Number(42)));
205        assert_eq!(
206            settle_result(Value::Promise(fulfilled)),
207            Ok(Value::Number(42))
208        );
209
210        let rejected = core::Promise::new();
211        assert!(rejected.reject("no"));
212        assert_eq!(
213            settle_result(Value::Promise(rejected)),
214            Err(InvokeHtaError::PromiseRejected("no".to_owned()))
215        );
216    }
217
218    #[test]
219    fn encoded_results_are_bounded() {
220        let result = Value::String("x".repeat(MAX_INVOKE_HTA_RESULT_BYTES));
221        assert!(matches!(
222            encode_result(result),
223            Err(InvokeHtaError::ResultTooLarge {
224                maximum: MAX_INVOKE_HTA_RESULT_BYTES,
225                ..
226            })
227        ));
228    }
229}