996 996 | let n = s.parse::<i64>().map_err(|e| SerdeError::InvalidInput {
|
997 997 | message: e.to_string(),
|
998 998 | })?;
|
999 999 | self.advance_by(len);
|
1000 1000 | Ok(n)
|
1001 1001 | }
|
1002 1002 |
|
1003 1003 | fn read_float_value(&mut self) -> Result<f64, SerdeError> {
|
1004 1004 | self.skip_whitespace();
|
1005 1005 | let rem = self.remaining();
|
1006 - | // Handle string-encoded special float values: "NaN", "Infinity", "-Infinity"
|
1006 + | // A string may only carry a non-finite value, which JSON cannot express as a number:
|
1007 + | // `"NaN"`, `"Infinity"`, `"-Infinity"`. A quoted finite number such as `"123"` is a
|
1008 + | // type mismatch; the restJson1 protocol tests
|
1009 + | // `RestJsonBodyFloatMalformedValueRejected_case0` and
|
1010 + | // `RestJsonBodyDoubleMalformedValueRejected_case0` require it to be rejected. This
|
1011 + | // mirrors the token-based parser (`expect_number_or_null`), which parses the string
|
1012 + | // with `aws_smithy_types::primitive::Parse` and then rejects finite values, so the
|
1013 + | // spellings Rust's `f64::from_str` accepts for non-finite values (e.g. `"nan"`) are
|
1014 + | // accepted here too.
|
1007 1015 | if rem.first() == Some(&b'"') {
|
1008 1016 | let s = self.read_string(&aws_smithy_schema::prelude::STRING)?;
|
1009 - | return match s.as_str() {
|
1010 - | "NaN" => Ok(f64::NAN),
|
1011 - | "Infinity" => Ok(f64::INFINITY),
|
1012 - | "-Infinity" => Ok(f64::NEG_INFINITY),
|
1013 - | _ => s.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
1017 + | let value = match s.as_str() {
|
1018 + | "NaN" => f64::NAN,
|
1019 + | "Infinity" => f64::INFINITY,
|
1020 + | "-Infinity" => f64::NEG_INFINITY,
|
1021 + | other => other.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
1014 1022 | message: e.to_string(),
|
1015 - | }),
|
1023 + | })?,
|
1016 1024 | };
|
1025 + | if value.is_finite() {
|
1026 + | return Err(SerdeError::TypeMismatch {
|
1027 + | message: format!(
|
1028 + | "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `{s}`"
|
1029 + | ),
|
1030 + | });
|
1031 + | }
|
1032 + | return Ok(value);
|
1017 1033 | }
|
1018 1034 | let mut len = 0;
|
1019 1035 | for &b in rem {
|
1020 1036 | if b.is_ascii_digit() || b == b'-' || b == b'+' || b == b'.' || b == b'e' || b == b'E' {
|
1021 1037 | len += 1;
|
1022 1038 | } else {
|
1023 1039 | break;
|
1024 1040 | }
|
1025 1041 | }
|
1026 1042 | if len == 0 {
|
2144 2160 | #[test]
|
2145 2161 | fn depth_limit_read_document_accepts_under_limit() {
|
2146 2162 | let payload = build_nested_with_inner(r#"{"k":"#, "{}", "}", 100);
|
2147 2163 | let mut deser = JsonDeserializer::new(&payload, Arc::new(JsonCodecSettings::default()));
|
2148 2164 | deser
|
2149 2165 | .read_document(dummy_schema())
|
2150 2166 | .expect("100-level nesting should succeed");
|
2151 2167 | }
|
2152 2168 |
|
2153 2169 | #[test]
|
2154 2170 | fn depth_limit_respects_custom_max_depth_setting() {
|
2155 2171 | // A custom, tighter limit trips before the default 128 would.
|
2156 2172 | let settings = JsonCodecSettings::builder().max_depth(16).build();
|
2157 2173 |
|
2158 2174 | // 20 levels exceeds the custom limit of 16.
|
2159 2175 | let payload = build_nested("[", "]", 20);
|
2160 2176 | let mut deser = JsonDeserializer::new(&payload, Arc::new(settings));
|
2161 2177 | let err = deser
|
2162 2178 | .read_list(dummy_schema(), &mut recursive_list_consumer)
|
2163 2179 | .expect_err("20-level nesting must exceed custom limit of 16");
|
2164 2180 | assert_depth_error(err);
|
2165 2181 |
|
2166 2182 | // 10 levels fits inside the custom limit.
|
2167 2183 | let settings_ok = JsonCodecSettings::builder().max_depth(16).build();
|
2168 2184 | let payload_ok = build_nested("[", "]", 10);
|
2169 2185 | let mut deser_ok = JsonDeserializer::new(&payload_ok, Arc::new(settings_ok));
|
2170 2186 | deser_ok
|
2171 2187 | .read_list(dummy_schema(), &mut recursive_list_consumer)
|
2172 2188 | .expect("10-level nesting should succeed under custom limit");
|
2173 2189 | }
|
2190 + |
|
2191 + | mod float_strings {
|
2192 + | //! A float or double member may carry a string only for a non-finite value
|
2193 + | //! (`"NaN"`, `"Infinity"`, `"-Infinity"`). Smithy protocol tests
|
2194 + | //! `RestJsonBodyFloatMalformedValueRejected_case0` and
|
2195 + | //! `RestJsonBodyDoubleMalformedValueRejected_case0` (`"123"`) require quoted finite
|
2196 + | //! numbers to be rejected.
|
2197 + | use super::*;
|
2198 + | use aws_smithy_schema::{shape_id, ShapeType};
|
2199 + |
|
2200 + | static F: Schema =
|
2201 + | Schema::new_member(shape_id!("test", "Input", "f"), ShapeType::Float, "f", 0);
|
2202 + | static D: Schema =
|
2203 + | Schema::new_member(shape_id!("test", "Input", "d"), ShapeType::Double, "d", 1);
|
2204 + | static INPUT: Schema =
|
2205 + | Schema::new_struct(shape_id!("test", "Input"), ShapeType::Structure, &[&F, &D]);
|
2206 + |
|
2207 + | fn read(body: &[u8]) -> Result<(Option<f32>, Option<f64>), SerdeError> {
|
2208 + | let mut deser = JsonDeserializer::new(body, Arc::new(JsonCodecSettings::default()));
|
2209 + | let (mut f, mut d) = (None, None);
|
2210 + | deser.read_struct(&INPUT, &mut |m, x| {
|
2211 + | match m.member_index() {
|
2212 + | Some(0) => f = Some(x.read_float(m)?),
|
2213 + | Some(1) => d = Some(x.read_double(m)?),
|
2214 + | _ => {}
|
2215 + | }
|
2216 + | Ok(())
|
2217 + | })?;
|
2218 + | Ok((f, d))
|
2219 + | }
|
2220 + |
|
2221 + | #[test]
|
2222 + | fn numbers_and_the_three_special_strings_are_read() {
|
2223 + | assert_eq!(
|
2224 + | read(br#"{"f": 1.5, "d": -2}"#).unwrap(),
|
2225 + | (Some(1.5), Some(-2.0))
|
2226 + | );
|
2227 + | let (f, d) = read(br#"{"f": "NaN", "d": "-Infinity"}"#).unwrap();
|
2228 + | assert!(f.unwrap().is_nan());
|
2229 + | assert_eq!(d, Some(f64::NEG_INFINITY));
|
2230 + | assert_eq!(
|
2231 + | read(br#"{"f": "Infinity"}"#).unwrap().0,
|
2232 + | Some(f32::INFINITY)
|
2233 + | );
|
2234 + | }
|
2235 + |
|
2236 + | #[test]
|
2237 + | fn quoted_finite_numbers_are_rejected() {
|
2238 + | assert!(read(br#"{"f": "123"}"#).is_err());
|
2239 + | assert!(read(br#"{"d": "123"}"#).is_err());
|
2240 + | assert!(read(br#"{"d": "1.5e3"}"#).is_err());
|
2241 + | assert!(read(br#"{"d": "-0"}"#).is_err());
|
2242 + | }
|
2243 + |
|
2244 + | #[test]
|
2245 + | fn non_numeric_strings_are_rejected() {
|
2246 + | assert!(read(br#"{"d": "abc"}"#).is_err());
|
2247 + | assert!(read(br#"{"d": ""}"#).is_err());
|
2248 + | assert!(read(br#"{"d": "1 2"}"#).is_err());
|
2249 + | }
|
2250 + |
|
2251 + | #[test]
|
2252 + | fn alternative_non_finite_spellings_match_the_legacy_parser() {
|
2253 + | // `aws_smithy_types::primitive::Parse` falls back to `f64::from_str`, which
|
2254 + | // accepts these case-insensitively; only finite results are rejected.
|
2255 + | assert!(read(br#"{"f": "nan"}"#).unwrap().0.unwrap().is_nan());
|
2256 + | assert_eq!(read(br#"{"d": "inf"}"#).unwrap().1, Some(f64::INFINITY));
|
2257 + | assert_eq!(
|
2258 + | read(br#"{"d": "-infinity"}"#).unwrap().1,
|
2259 + | Some(f64::NEG_INFINITY)
|
2260 + | );
|
2261 + | }
|
2262 + | }
|
2174 2263 | }
|