Skip to content

Commit 7e278cd

Browse files
authored
APPSEC-60188: gracefully accept null in APIGW response (#960)
I strongly suspect the .NET Lambda SDK (from Amazon) produces `null` values instead of omitting fields, which appears to be accepted by API Gateway but is presently rejected by our parsing logic. This addresses this problem and adds a new test case. JJ-Change-Id: vprmkv ZD: 2375557 Jira: APPSEC-60188
1 parent b260205 commit 7e278cd

File tree

1 file changed

+40
-4
lines changed

1 file changed

+40
-4
lines changed

bottlecap/src/appsec/processor/response.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::collections::HashMap;
22
use std::io::Cursor;
33

4-
use serde::Deserialize;
4+
use serde::{Deserialize, Deserializer};
55

66
use crate::appsec::processor::InvocationPayload;
7-
use crate::lifecycle::invocation::triggers::{body::Body, lowercase_key};
7+
use crate::lifecycle::invocation::triggers::body::Body;
88

99
/// The expected payload of a response. This is different from trigger to trigger.
1010
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -50,9 +50,9 @@ impl Default for ExpectedResponseFormat {
5050
#[serde(rename_all = "camelCase")]
5151
struct ApiGatewayResponse {
5252
status_code: i64,
53-
#[serde(deserialize_with = "lowercase_key", default)]
53+
#[serde(deserialize_with = "nullable_lowercase_key", default)]
5454
headers: HashMap<String, String>,
55-
#[serde(deserialize_with = "lowercase_key", default)]
55+
#[serde(deserialize_with = "nullable_lowercase_key", default)]
5656
multi_value_headers: HashMap<String, Vec<String>>,
5757
#[serde(flatten)]
5858
body: Body,
@@ -101,3 +101,39 @@ impl InvocationPayload for RawPayload {
101101
Some(Box::new(Cursor::new(&self.data)))
102102
}
103103
}
104+
105+
fn nullable_lowercase_key<'de, D, V>(deserializer: D) -> Result<HashMap<String, V>, D::Error>
106+
where
107+
D: Deserializer<'de>,
108+
V: Deserialize<'de>,
109+
{
110+
let Some(map) = Option::<HashMap<String, V>>::deserialize(deserializer)? else {
111+
return Ok(HashMap::default());
112+
};
113+
Ok(map
114+
.into_iter()
115+
.map(|(key, value)| (key.to_lowercase(), value))
116+
.collect())
117+
}
118+
119+
#[cfg(test)]
120+
mod test {
121+
use super::*;
122+
123+
#[test]
124+
fn test_null_fields_in_apigw_response() {
125+
let response = r#"{
126+
"statusCode": 0,
127+
"headers": null,
128+
"multiValueHeaders": null,
129+
"body": null
130+
}"#;
131+
let response = ExpectedResponseFormat::ApiGatewayResponse
132+
.parse(response.as_bytes())
133+
.expect("response should have parsed cleanly")
134+
.expect("response should have been Some");
135+
assert!(response.response_body().is_none());
136+
assert!(response.response_headers_no_cookies().is_empty());
137+
assert_eq!(response.response_status_code(), Some(0));
138+
}
139+
}

0 commit comments

Comments
 (0)