1use serde::Serialize;
5use std::collections::BTreeMap;
6
7#[link(wasm_import_module = "extism:host/env")]
8unsafe extern "C" {
9 fn http_request(request: u64, body: u64) -> u64;
10 fn http_status_code() -> i32;
11}
12
13pub struct Response {
14 pub status: u16,
15 pub body: Vec<u8>,
16}
17
18impl Response {
19 pub fn is_success(&self) -> bool {
20 (200..300).contains(&self.status)
21 }
22}
23
24pub fn request(method: &str, url: &str, headers: &[(&str, &str)], body: Option<&[u8]>) -> crate::Result<Response> {
27 #[derive(Serialize)]
28 struct Request<'a> {
29 url: &'a str,
30 method: &'a str,
31 headers: BTreeMap<&'a str, &'a str>,
32 }
33 let request = Request { url, method, headers: headers.iter().copied().collect() };
34 let request = crate::to_kernel(&serde_json::to_vec(&request)?);
35 let body = body.filter(|it| !it.is_empty()).map_or(0, crate::to_kernel);
36 let reply = unsafe { http_request(request, body) };
37 let status = unsafe { http_status_code() };
38 unsafe {
39 crate::free(request);
40 if body != 0 {
41 crate::free(body);
42 }
43 }
44 let body = if reply == 0 { Vec::new() } else { crate::from_kernel(reply) };
45 if reply != 0 {
46 unsafe { crate::free(reply) };
47 }
48 match u16::try_from(status) {
49 Ok(status) if status > 0 => Ok(Response { status, body }),
50 _ => Err(format!("No response from {url}").into()),
51 }
52}
53
54pub fn form(fields: &[(&str, &str)]) -> String {
56 fields.iter().map(|(name, value)| format!("{}={}", encode(name), encode(value))).collect::<Vec<_>>().join("&")
57}
58
59fn encode(text: &str) -> String {
60 let mut encoded = String::with_capacity(text.len());
61 for byte in text.bytes() {
62 match byte {
63 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'*' => encoded.push(byte as char),
64 b' ' => encoded.push('+'),
65 _ => encoded.push_str(&format!("%{byte:02X}")),
66 }
67 }
68 encoded
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn form_encodes_reserved_and_non_ascii() {
77 assert_eq!(form(&[("text", "a b&c=d"), ("x", "é")]), "text=a+b%26c%3Dd&x=%C3%A9");
78 }
79}