diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md
index ab041e552..361729a12 100644
--- a/docs/_docs/user-guide/eldritch.md
+++ b/docs/_docs/user-guide/eldritch.md
@@ -420,6 +420,18 @@ The file.find method finds all files matching the used parameters. Return
The http.download method downloads a file at the URI specified in `uri` to the path specified in `dst`. If a file already exists at that location, it will be overwritten.
+### http.get
+
+`http.get(uri: str, query_params: Option>, headers: Option>) -> str`
+
+The http.get method sends an HTTP GET request to the URI specified in `uri` with the optional query paramters specified in `query_params` and headers specified in `headers`, then return the response body as a string. Note: in order to conform with HTTP2+ all header names are transmuted to lowercase.
+
+### http.post
+
+`http.post(uri: str, body: Option, form: Option>, headers: Option>) -> str`
+
+The http.post method sends an HTTP POST request to the URI specified in `uri` with the optional request body specified by `body`, form paramters specified in `form`, and headers specified in `headers`, then return the response body as a string. Note: in order to conform with HTTP2+ all header names are transmuted to lowercase. Other Note: if a `body` and a `form` are supplied the value of `body` will be used.
+
---
## Pivot
diff --git a/implants/lib/eldritch/src/http/get_impl.rs b/implants/lib/eldritch/src/http/get_impl.rs
new file mode 100644
index 000000000..e724d3f3a
--- /dev/null
+++ b/implants/lib/eldritch/src/http/get_impl.rs
@@ -0,0 +1,212 @@
+use anyhow::Result;
+use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
+use starlark::collections::SmallMap;
+use std::collections::HashMap;
+
+pub fn get(
+ uri: String,
+ query_params: Option>,
+ headers: Option>,
+) -> Result {
+ let mut query_map = HashMap::new();
+ let mut headers_map = HeaderMap::new();
+ let runtime = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()?;
+
+ if let Some(q) = query_params {
+ for (k, v) in q {
+ query_map.insert(k, v);
+ }
+ }
+
+ if let Some(h) = headers {
+ for (k, v) in h {
+ let name = HeaderName::from_bytes(k.as_bytes())?;
+ let value = HeaderValue::from_bytes(v.as_bytes())?;
+ headers_map.append(name, value);
+ }
+ }
+
+ runtime.block_on(handle_get(uri, query_map, headers_map))
+}
+
+async fn handle_get(
+ uri: String,
+ query_params: HashMap,
+ headers: HeaderMap,
+) -> Result {
+ #[cfg(debug_assertions)]
+ log::info!(
+ "eldritch sending HTTP GET request to '{}' with headers '{:#?}'",
+ uri,
+ headers
+ );
+
+ let client = reqwest::Client::new()
+ .get(uri)
+ .headers(headers)
+ .query(&query_params);
+ let resp = client.send().await?.text().await?;
+ Ok(resp)
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use httptest::{matchers::*, responders::*, Expectation, Server};
+ use starlark::collections::SmallMap;
+
+ #[test]
+ fn test_get_no_params_or_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ server.expect(
+ Expectation::matching(request::method_path("GET", "/foo"))
+ .respond_with(status_code(200).body("test body")),
+ );
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let contents = get(url, None, None)?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_empty_params_and_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ server.expect(
+ Expectation::matching(request::method_path("GET", "/foo"))
+ .respond_with(status_code(200).body("test body")),
+ );
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let contents = get(url, Some(SmallMap::new()), Some(SmallMap::new()))?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_with_params() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("GET", "/foo"),
+ request::query(url_decoded(contains(("a", "true")))),
+ request::query(url_decoded(contains(("b", "bar")))),
+ request::query(url_decoded(contains(("c", "3")))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut params = SmallMap::new();
+ params.insert("a".to_string(), "true".to_string());
+ params.insert("b".to_string(), "bar".to_string());
+ params.insert("c".to_string(), "3".to_string());
+ let contents = get(url, Some(params), None)?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_with_hybrid_params() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("GET", "/foo"),
+ request::query(url_decoded(contains(("a", "true")))),
+ request::query(url_decoded(contains(("b", "bar")))),
+ request::query(url_decoded(contains(("c", "3")))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo?a=true").to_string();
+
+ // run our code
+ let mut params = SmallMap::new();
+ params.insert("b".to_string(), "bar".to_string());
+ params.insert("c".to_string(), "3".to_string());
+ let contents = get(url, Some(params), None)?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_with_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("GET", "/foo"),
+ request::headers(contains(("a", "TRUE"))),
+ request::headers(contains(("b", "bar"))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut headers = SmallMap::new();
+ headers.insert("A".to_string(), "TRUE".to_string());
+ headers.insert("b".to_string(), "bar".to_string());
+ let contents = get(url, None, Some(headers))?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_with_params_and_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("GET", "/foo"),
+ request::headers(contains(("a", "TRUE"))),
+ request::headers(contains(("b", "bar"))),
+ request::query(url_decoded(contains(("c", "3")))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut headers = SmallMap::new();
+ headers.insert("A".to_string(), "TRUE".to_string());
+ headers.insert("b".to_string(), "bar".to_string());
+ let mut params = SmallMap::new();
+ params.insert("c".to_string(), "3".to_string());
+ let contents = get(url, Some(params), Some(headers))?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+}
diff --git a/implants/lib/eldritch/src/http/mod.rs b/implants/lib/eldritch/src/http/mod.rs
index 97b7857ac..148013415 100644
--- a/implants/lib/eldritch/src/http/mod.rs
+++ b/implants/lib/eldritch/src/http/mod.rs
@@ -1,6 +1,9 @@
mod download_impl;
+mod get_impl;
+mod post_impl;
use starlark::{
+ collections::SmallMap,
environment::MethodsBuilder,
starlark_module,
values::{none::NoneType, starlark_value},
@@ -24,4 +27,14 @@ fn methods(builder: &mut MethodsBuilder) {
download_impl::download(uri, dst)?;
Ok(NoneType{})
}
+
+ #[allow(unused_variables)]
+ fn get(this: &HTTPLibrary, uri: String, query_params: Option>, headers: Option>) -> anyhow::Result {
+ get_impl::get(uri, query_params, headers)
+ }
+
+ #[allow(unused_variables)]
+ fn post(this: &HTTPLibrary, uri: String, body: Option, form: Option>, headers: Option>) -> anyhow::Result {
+ post_impl::post(uri, body, form, headers)
+ }
}
diff --git a/implants/lib/eldritch/src/http/post_impl.rs b/implants/lib/eldritch/src/http/post_impl.rs
new file mode 100644
index 000000000..091d32b88
--- /dev/null
+++ b/implants/lib/eldritch/src/http/post_impl.rs
@@ -0,0 +1,228 @@
+use anyhow::Result;
+use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
+use starlark::collections::SmallMap;
+use std::collections::HashMap;
+
+pub fn post(
+ uri: String,
+ body: Option,
+ form: Option>,
+ headers: Option>,
+) -> Result {
+ let mut headers_map = HeaderMap::new();
+ let runtime = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()?;
+
+ if let Some(h) = headers {
+ for (k, v) in h {
+ let name = HeaderName::from_bytes(k.as_bytes())?;
+ let value = HeaderValue::from_bytes(v.as_bytes())?;
+ headers_map.append(name, value);
+ }
+ }
+
+ if body.is_some() {
+ return runtime.block_on(handle_post(uri, body, None, headers_map));
+ }
+
+ if let Some(f) = form {
+ let mut form_map = HashMap::new();
+ for (k, v) in f {
+ form_map.insert(k, v);
+ }
+
+ return runtime.block_on(handle_post(uri, None, Some(form_map), headers_map));
+ }
+
+ runtime.block_on(handle_post(uri, None, None, headers_map))
+}
+
+async fn handle_post(
+ uri: String,
+ body: Option,
+ form: Option>,
+ headers: HeaderMap,
+) -> Result {
+ #[cfg(debug_assertions)]
+ log::info!(
+ "eldritch sending HTTP POST request to '{}' with headers '{:#?}'",
+ uri,
+ headers
+ );
+
+ let client = reqwest::Client::new().post(uri).headers(headers);
+ if let Some(b) = body {
+ let resp = client.body(b).send().await?.text().await?;
+ return Ok(resp);
+ }
+ if let Some(f) = form {
+ let resp = client.form(&f).send().await?.text().await?;
+ return Ok(resp);
+ }
+ let resp = client.send().await?.text().await?;
+ Ok(resp)
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use httptest::{matchers::*, responders::*, Expectation, Server};
+ use starlark::collections::SmallMap;
+
+ #[test]
+ fn test_post_no_body_or_params_or_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ server.expect(
+ Expectation::matching(request::method_path("POST", "/foo"))
+ .respond_with(status_code(200).body("test body")),
+ );
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let contents = post(url, None, None, None)?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_post_empty_params_and_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ server.expect(
+ Expectation::matching(request::method_path("POST", "/foo"))
+ .respond_with(status_code(200).body("test body")),
+ );
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let contents = post(url, None, Some(SmallMap::new()), Some(SmallMap::new()))?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_post_with_params() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("POST", "/foo"),
+ request::body(url_decoded(contains(("a", "true")))),
+ request::body(url_decoded(contains(("b", "bar")))),
+ request::body(url_decoded(contains(("c", "3")))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut params = SmallMap::new();
+ params.insert("a".to_string(), "true".to_string());
+ params.insert("b".to_string(), "bar".to_string());
+ params.insert("c".to_string(), "3".to_string());
+ let contents = post(url, None, Some(params), None)?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_post_with_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("POST", "/foo"),
+ request::headers(contains(("a", "TRUE"))),
+ request::headers(contains(("b", "bar"))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut headers = SmallMap::new();
+ headers.insert("A".to_string(), "TRUE".to_string());
+ headers.insert("b".to_string(), "bar".to_string());
+ let contents = post(url, None, None, Some(headers))?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_post_with_params_and_headers() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("POST", "/foo"),
+ request::headers(contains(("a", "TRUE"))),
+ request::headers(contains(("b", "bar"))),
+ request::body(url_decoded(contains(("c", "3")))),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut headers = SmallMap::new();
+ headers.insert("A".to_string(), "TRUE".to_string());
+ headers.insert("b".to_string(), "bar".to_string());
+ let mut params = SmallMap::new();
+ params.insert("c".to_string(), "3".to_string());
+ let contents = post(url, None, Some(params), Some(headers))?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_post_with_body_and_header() -> anyhow::Result<()> {
+ // running test http server
+ let server = Server::run();
+ let m = all_of![
+ request::method_path("POST", "/foo"),
+ request::headers(contains(("a", "TRUE"))),
+ request::body("the quick brown fox jumps over the lazy dog"),
+ ];
+ server.expect(Expectation::matching(m).respond_with(status_code(200).body("test body")));
+
+ // reference test server uri
+ let url = server.url("/foo").to_string();
+
+ // run our code
+ let mut headers = SmallMap::new();
+ headers.insert("A".to_string(), "TRUE".to_string());
+ let contents = post(
+ url,
+ Some(String::from("the quick brown fox jumps over the lazy dog")),
+ None,
+ Some(headers),
+ )?;
+
+ // check request returned correctly
+ assert_eq!(contents, "test body");
+
+ Ok(())
+ }
+}
diff --git a/implants/lib/eldritch/src/runtime/mod.rs b/implants/lib/eldritch/src/runtime/mod.rs
index 899f70b5f..1a8d7b449 100644
--- a/implants/lib/eldritch/src/runtime/mod.rs
+++ b/implants/lib/eldritch/src/runtime/mod.rs
@@ -179,7 +179,7 @@ mod tests {
parameters: HashMap::new(),
file_names: Vec::new(),
},
- want_text: format!("{}\n", r#"["download"]"#),
+ want_text: format!("{}\n", r#"["download", "get", "post"]"#),
want_error: None,
},
}