extism_runtime/
pdk.rs

1/// All the functions in the file are exposed from inside WASM plugins
2use crate::*;
3
4// This macro unwraps input arguments to prevent functions from panicking,
5// it should be used instead of `Val::unwrap_*` functions
6#[macro_export]
7macro_rules! args {
8    ($input:expr, $index:expr, $ty:ident) => {
9        match $input[$index].$ty() {
10            Some(x) => x,
11            None => return Err($crate::Error::msg("Invalid input type"))
12        }
13    };
14    ($input:expr, $(($index:expr, $ty:ident)),*$(,)?) => {
15        ($(
16            $crate::args!($input, $index, $ty),
17        )*)
18    };
19}
20
21/// Get a configuration value
22/// Params: i64 (offset)
23/// Returns: i64 (offset)
24pub(crate) fn config_get(
25    mut caller: Caller<Internal>,
26    input: &[Val],
27    output: &mut [Val],
28) -> Result<(), Error> {
29    let data: &mut Internal = caller.data_mut();
30
31    let offset = args!(input, 0, i64) as u64;
32    let key = data.memory_read_str(offset)?;
33    let key = unsafe {
34        std::str::from_utf8_unchecked(std::slice::from_raw_parts(key.as_ptr(), key.len()))
35    };
36    let val = data.internal().manifest.as_ref().config.get(key);
37    let ptr = val.map(|x| (x.len(), x.as_ptr()));
38    let mem = match ptr {
39        Some((len, ptr)) => {
40            let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
41            data.memory_alloc_bytes(bytes)?
42        }
43        None => {
44            output[0] = Val::I64(0);
45            return Ok(());
46        }
47    };
48    output[0] = Val::I64(mem as i64);
49    Ok(())
50}
51
52/// Get a variable
53/// Params: i64 (offset)
54/// Returns: i64 (offset)
55pub(crate) fn var_get(
56    mut caller: Caller<Internal>,
57    input: &[Val],
58    output: &mut [Val],
59) -> Result<(), Error> {
60    let data: &mut Internal = caller.data_mut();
61
62    let offset = args!(input, 0, i64) as u64;
63    let key = data.memory_read_str(offset)?;
64    let key = unsafe {
65        std::str::from_utf8_unchecked(std::slice::from_raw_parts(key.as_ptr(), key.len()))
66    };
67    let val = data.internal().vars.get(key);
68    let ptr = val.map(|x| (x.len(), x.as_ptr()));
69    let mem = match ptr {
70        Some((len, ptr)) => {
71            let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
72            data.memory_alloc_bytes(bytes)?
73        }
74        None => {
75            output[0] = Val::I64(0);
76            return Ok(());
77        }
78    };
79    output[0] = Val::I64(mem as i64);
80    Ok(())
81}
82
83/// Set a variable, if the value offset is 0 then the provided key will be removed
84/// Params: i64 (key offset), i64 (value offset)
85/// Returns: none
86pub(crate) fn var_set(
87    mut caller: Caller<Internal>,
88    input: &[Val],
89    _output: &mut [Val],
90) -> Result<(), Error> {
91    let data: &mut Internal = caller.data_mut();
92
93    let mut size = 0;
94    for v in data.vars.values() {
95        size += v.len();
96    }
97
98    let voffset = args!(input, 1, i64) as u64;
99
100    // If the store is larger than 100MB then stop adding things
101    if size > 1024 * 1024 * 100 && voffset != 0 {
102        return Err(Error::msg("Variable store is full"));
103    }
104
105    let key_offs = args!(input, 0, i64) as u64;
106    let key = {
107        let key = data.memory_read_str(key_offs)?;
108        let key_len = key.len();
109        let key_ptr = key.as_ptr();
110        unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(key_ptr, key_len)) }
111    };
112
113    // Remove if the value offset is 0
114    if voffset == 0 {
115        data.vars.remove(key);
116        return Ok(());
117    }
118
119    let vlen = data.memory_length(voffset);
120    let value = data.memory_read(voffset, vlen).to_vec();
121
122    // Insert the value from memory into the `vars` map
123    data.vars.insert(key.to_string(), value);
124
125    Ok(())
126}
127
128/// Make an HTTP request
129/// Params: i64 (offset to JSON encoded HttpRequest), i64 (offset to body or 0)
130/// Returns: i64 (offset)
131pub(crate) fn http_request(
132    #[allow(unused_mut)] mut caller: Caller<Internal>,
133    input: &[Val],
134    output: &mut [Val],
135) -> Result<(), Error> {
136    #[cfg(not(feature = "http"))]
137    {
138        let _ = (caller, input);
139
140        output[0] = Val::I64(0);
141        error!("http_request is not enabled");
142        return Ok(());
143    }
144
145    #[cfg(feature = "http")]
146    {
147        use std::io::Read;
148        let data: &mut Internal = caller.data_mut();
149        let http_req_offset = args!(input, 0, i64) as u64;
150
151        let http_req_len = data.memory_length(http_req_offset);
152        let req: extism_manifest::HttpRequest =
153            serde_json::from_slice(data.memory_read(http_req_offset, http_req_len))?;
154
155        let body_offset = args!(input, 1, i64) as u64;
156
157        let url = match url::Url::parse(&req.url) {
158            Ok(u) => u,
159            Err(e) => return Err(Error::msg(format!("Invalid URL: {e:?}"))),
160        };
161        let allowed_hosts = &data.internal().manifest.as_ref().allowed_hosts;
162        let host_str = url.host_str().unwrap_or_default();
163        let host_matches = if let Some(allowed_hosts) = allowed_hosts {
164            allowed_hosts.iter().any(|url| {
165                let pat = match glob::Pattern::new(url) {
166                    Ok(x) => x,
167                    Err(_) => return url == host_str,
168                };
169
170                pat.matches(host_str)
171            })
172        } else {
173            false
174        };
175
176        if !host_matches {
177            return Err(Error::msg(format!(
178                "HTTP request to {} is not allowed",
179                req.url
180            )));
181        }
182
183        let mut r = ureq::request(req.method.as_deref().unwrap_or("GET"), &req.url);
184
185        for (k, v) in req.headers.iter() {
186            r = r.set(k, v);
187        }
188
189        let res = if body_offset > 0 {
190            let len = data.memory_length(body_offset);
191            let buf = data.memory_read(body_offset, len);
192            r.send_bytes(buf)
193        } else {
194            r.call()
195        };
196
197        let reader = match res {
198            Ok(res) => {
199                data.http_status = res.status();
200                Some(res.into_reader())
201            }
202            Err(e) => {
203                log::error!("Unable to make HTTP request: {:?}", e);
204                if let Some(res) = e.into_response() {
205                    data.http_status = res.status();
206                    Some(res.into_reader())
207                } else {
208                    None
209                }
210            }
211        };
212
213        if let Some(reader) = reader {
214            let mut buf = Vec::new();
215            reader
216                .take(1024 * 1024 * 50) // TODO: make this limit configurable
217                .read_to_end(&mut buf)?;
218
219            let mem = data.memory_alloc_bytes(buf)?;
220            output[0] = Val::I64(mem as i64);
221        } else {
222            output[0] = Val::I64(0);
223        }
224
225        Ok(())
226    }
227}
228
229/// Get the status code of the last HTTP request
230/// Params: none
231/// Returns: i32 (status code)
232pub(crate) fn http_status_code(
233    mut caller: Caller<Internal>,
234    _input: &[Val],
235    output: &mut [Val],
236) -> Result<(), Error> {
237    let data: &mut Internal = caller.data_mut();
238    output[0] = Val::I32(data.http_status as i32);
239    Ok(())
240}
241
242pub fn log(
243    level: log::Level,
244    mut caller: Caller<Internal>,
245    input: &[Val],
246    _output: &mut [Val],
247) -> Result<(), Error> {
248    let data: &mut Internal = caller.data_mut();
249    let offset = args!(input, 0, i64) as u64;
250    let buf = data.memory_read_str(offset);
251
252    match buf {
253        Ok(buf) => log::log!(level, "{}", buf),
254        Err(_) => log::log!(level, "{:?}", buf),
255    }
256    Ok(())
257}
258
259/// Write to logs (warning)
260/// Params: i64 (offset)
261/// Returns: none
262pub(crate) fn log_warn(
263    caller: Caller<Internal>,
264    input: &[Val],
265    _output: &mut [Val],
266) -> Result<(), Error> {
267    log(log::Level::Warn, caller, input, _output)
268}
269
270/// Write to logs (info)
271/// Params: i64 (offset)
272/// Returns: none
273pub(crate) fn log_info(
274    caller: Caller<Internal>,
275    input: &[Val],
276    _output: &mut [Val],
277) -> Result<(), Error> {
278    log(log::Level::Info, caller, input, _output)
279}
280
281/// Write to logs (debug)
282/// Params: i64 (offset)
283/// Returns: none
284pub(crate) fn log_debug(
285    caller: Caller<Internal>,
286    input: &[Val],
287    _output: &mut [Val],
288) -> Result<(), Error> {
289    log(log::Level::Debug, caller, input, _output)
290}
291
292/// Write to logs (error)
293/// Params: i64 (offset)
294/// Returns: none
295pub(crate) fn log_error(
296    caller: Caller<Internal>,
297    input: &[Val],
298    _output: &mut [Val],
299) -> Result<(), Error> {
300    log(log::Level::Error, caller, input, _output)
301}