Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the http.get and http.post functions with query params, headers, form, bodies as optional params (depending on GET vs POST) #636

Merged
merged 6 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/_docs/user-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,18 @@ The <b>file.find</b> method finds all files matching the used parameters. Return

The <b>http.download</b> 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<Dict<str, str>>, headers: Option<Dict<str, str>>) -> str`

The <b>http.get</b> 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<str>, form: Option<Dict<str, str>>, headers: Option<Dict<str, str>>) -> str`

The <b>http.post</b> 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
Expand Down
212 changes: 212 additions & 0 deletions implants/lib/eldritch/src/http/get_impl.rs
Original file line number Diff line number Diff line change
@@ -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<SmallMap<String, String>>,
headers: Option<SmallMap<String, String>>,
) -> Result<String> {
let mut query_map = HashMap::new();
let mut headers_map = HeaderMap::new();
let runtime = tokio::runtime::Builder::new_current_thread()
Cictrone marked this conversation as resolved.
Show resolved Hide resolved
.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<String, String>,
headers: HeaderMap,
) -> Result<String> {
#[cfg(debug_assertions)]
log::info!(
"eldritch sending HTTP GET request to '{}' with headers '{:#?}'",

Check warning on line 41 in implants/lib/eldritch/src/http/get_impl.rs

View check run for this annotation

Codecov / codecov/patch

implants/lib/eldritch/src/http/get_impl.rs#L41

Added line #L41 was not covered by tests
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(())
}
}
13 changes: 13 additions & 0 deletions implants/lib/eldritch/src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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<SmallMap<String, String>>, headers: Option<SmallMap<String, String>>) -> anyhow::Result<String> {
get_impl::get(uri, query_params, headers)
}

#[allow(unused_variables)]
fn post(this: &HTTPLibrary, uri: String, body: Option<String>, form: Option<SmallMap<String, String>>, headers: Option<SmallMap<String, String>>) -> anyhow::Result<String> {
post_impl::post(uri, body, form, headers)
}
}
Loading
Loading