Skip to content
Open
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
27 changes: 27 additions & 0 deletions bottlecap/src/appsec/processor/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ impl InvocationPayload for RawPayload {
HashMap::default()
}
fn response_body<'a>(&'a self) -> Option<Box<dyn std::io::Read + 'a>> {
if self.data.is_empty() {
return None;
}
Some(Box::new(Cursor::new(&self.data)))
}
}
Expand Down Expand Up @@ -132,4 +135,28 @@ mod test {
assert!(response.response_headers_no_cookies().is_empty());
assert_eq!(response.response_status_code(), Some(0));
}

#[test]
fn test_empty_body_in_apigw_response() {
let response = r#"{
"statusCode": 204,
"headers": {},
"multiValueHeaders": {},
"body": ""
}"#;
let response = ExpectedResponseFormat::ApiGatewayResponse
.parse(response.as_bytes())
.expect("response should have parsed cleanly")
.expect("response should have been Some");
assert!(response.response_body().is_none());
}

#[test]
fn test_empty_raw_response() {
let response = ExpectedResponseFormat::Raw
.parse(b"")
.expect("response should have parsed cleanly")
.expect("response should have been Some");
assert!(response.response_body().is_none());
}
}
49 changes: 49 additions & 0 deletions bottlecap/src/lifecycle/invocation/triggers/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@ pub struct Body {
impl Body {
/// Obtains a reader to the data contained in this [`Body`], decoded from
/// Base64 if [`Body::is_base64_encoded`] is `true`.
///
/// Returns [`None`] if there is no body, including when it is an empty
/// string, which is how many event sources represent the absence of a
/// body (e.g, `GET` requests, `204` responses, etc...).
Comment on lines +20 to +22
pub(crate) fn reader<'a>(&'a self) -> Result<Option<Box<dyn Read + 'a>>, base64::DecodeError> {
let Some(body) = &self.body else {
return Ok(None);
};

if body.is_empty() {
return Ok(None);
}

if self.is_base64_encoded {
let body = base64::engine::general_purpose::STANDARD.decode(body)?;
let reader = Bytes::from(body).reader();
Expand All @@ -30,3 +38,44 @@ impl Body {
}
}
}

#[cfg(test)]
mod tests {
use std::io::read_to_string;

use super::*;

#[test]
fn test_reader_no_body() {
let body = Body {
body: None,
is_base64_encoded: false,
};
assert!(body.reader().expect("should not fail").is_none());
}

#[test]
fn test_reader_empty_body() {
let body = Body {
body: Some(String::new()),
is_base64_encoded: false,
};
assert!(body.reader().expect("should not fail").is_none());
}

#[test]
fn test_reader_base64_body() {
let body = Body {
body: Some("eyJmb28iOiJiYXIifQ==".to_string()),
is_base64_encoded: true,
};
let reader = body
.reader()
.expect("should not fail")
.expect("should be Some");
assert_eq!(
read_to_string(reader).expect("should read cleanly"),
r#"{"foo":"bar"}"#
);
}
}