From f6f962c3269d0da3a50e34431596236683e2f1b3 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Thu, 26 Sep 2024 11:53:24 +0200 Subject: [PATCH 1/2] Change request-shape test: * Allow spin-full-url to begin with https * Only check spin-client-addr if present Signed-off-by: Ryan Levick --- components/request-shape/src/lib.rs | 64 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/components/request-shape/src/lib.rs b/components/request-shape/src/lib.rs index 8e7ce64..9bd605b 100644 --- a/components/request-shape/src/lib.rs +++ b/components/request-shape/src/lib.rs @@ -57,22 +57,25 @@ fn check_url(req: &IncomingRequest) -> anyhow::Result<()> { /// Check that the headers are as expected fn check_headers(req: &IncomingRequest) -> anyhow::Result<()> { - let expected_headers = [ + let expected_headers: [(&str, &[&str]); 8] = [ ( "spin-raw-component-route", - "/base/:path_segment/:path_end/...", + &["/base/:path_segment/:path_end/..."], ), ( "spin-full-url", - "http://example.com/base/path/end/rest?key=value", + &[ + "http://example.com/base/path/end/rest?key=value", + "https://example.com/base/path/end/rest?key=value", + ], ), - ("spin-path-info", "/rest"), + ("spin-path-info", &["/rest"]), // Hardcoded base path for backwards compatibility - ("spin-base-path", "/"), - ("spin-component-route", "/base/:path_segment/:path_end"), - ("spin-path-match-path-segment", "path"), - ("spin-path-match-path-end", "end"), - ("spin-matched-route", "/base/:path_segment/:path_end/..."), + ("spin-base-path", &["/"]), + ("spin-component-route", &["/base/:path_segment/:path_end"]), + ("spin-path-match-path-segment", &["path"]), + ("spin-path-match-path-end", &["end"]), + ("spin-matched-route", &["/base/:path_segment/:path_end/..."]), ]; let mut actual_headers: HashMap>> = HashMap::new(); @@ -80,19 +83,26 @@ fn check_headers(req: &IncomingRequest) -> anyhow::Result<()> { actual_headers.entry(k).or_default().push(v); } - for (name, value) in expected_headers.into_iter() { + for (name, values) in expected_headers.into_iter() { let header = header_as_string(&mut actual_headers, name)?; - anyhow::ensure!( - header == value, - "Header {name} was expected to contain value '{value}' but contained '{header}' " - ); + for (i, value) in values.iter().enumerate() { + if header != *value && i == values.len() - 1 { + anyhow::bail!( + "Header {name} was expected to contain value '{value}' but contained '{header}' " + ); + } + } } - // Check that the spin-client-addr header is a valid SocketAddr - let _: std::net::SocketAddr = header_as_string(&mut actual_headers, "spin-client-addr")? - .parse() - .context("spin-client-addr header is not a valid SocketAddr")?; + // Check that if spin-client-addr header is present, it's a valid SocketAddr + let _: Option = + optional_header_as_string(&mut actual_headers, "spin-client-addr")? + .map(|addr| { + addr.parse() + .context("spin-client-addr header is not a valid SocketAddr") + }) + .transpose()?; // Check that there are no unexpected `spin-*` headers for (name, _) in actual_headers { @@ -110,15 +120,29 @@ fn header_as_string( headers: &mut HashMap>>, name: &str, ) -> anyhow::Result { + optional_header_as_string(headers, name)? + .with_context(|| format!("expected exactly one header '{name}' but found none")) +} + +/// Fails unless there is either zero or one header with the given name, and if present it is valid UTF-8 +fn optional_header_as_string( + headers: &mut HashMap>>, + name: &str, +) -> anyhow::Result> { //TODO: handle the fact that headers are case sensitive let mut value = headers.remove(name).unwrap_or_default(); - if value.len() != 1 { + if value.len() > 1 { anyhow::bail!( - "expected exactly one header '{name}' but found {}", + "expected at most one header '{name}' but found {}", value.len() ) } + if value.is_empty() { + return Ok(None); + } + String::from_utf8(value.remove(0)) .with_context(|| format!("header '{name}' is not valid UTF-8")) + .map(Some) } From 10faf4d1be4113822a311a750d3f8c9a700b53e6 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Thu, 26 Sep 2024 15:56:40 +0200 Subject: [PATCH 2/2] Fix logic for checking acceptable values Co-authored-by: Lann Signed-off-by: Ryan Levick Signed-off-by: Ryan Levick --- components/request-shape/src/lib.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/components/request-shape/src/lib.rs b/components/request-shape/src/lib.rs index 9bd605b..f4810bb 100644 --- a/components/request-shape/src/lib.rs +++ b/components/request-shape/src/lib.rs @@ -86,13 +86,10 @@ fn check_headers(req: &IncomingRequest) -> anyhow::Result<()> { for (name, values) in expected_headers.into_iter() { let header = header_as_string(&mut actual_headers, name)?; - for (i, value) in values.iter().enumerate() { - if header != *value && i == values.len() - 1 { - anyhow::bail!( - "Header {name} was expected to contain value '{value}' but contained '{header}' " - ); - } - } + anyhow::ensure!( + values.contains(&header.as_str()), + "Header {name} value was expected to be one of {values:?} but was '{header}'", + ); } // Check that if spin-client-addr header is present, it's a valid SocketAddr