diff --git a/CHANGELOG.md b/CHANGELOG.md index 849a23e..18ae742 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +* fix decoder crashing with out-of-bounds error (https://github.com/georust/polyline/pull/37): + * protect against invalid polylines + * protect against potential overflow when shifting + * performance hit: 10-12% + ## 0.10.1 * Fix dependencies to officially drop geo-types 0.6 - it was already diff --git a/src/lib.rs b/src/lib.rs index 3913497..96cfedc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,11 +118,11 @@ pub fn decode_polyline(polyline: &str, precision: u32) -> Result let chars = polyline.as_bytes(); while index < chars.len() { - let (latitude_change, new_index) = trans(chars, index); + let (latitude_change, new_index) = trans(chars, index)?; if new_index >= chars.len() { break; } - let (longitude_change, new_index) = trans(chars, new_index); + let (longitude_change, new_index) = trans(chars, new_index)?; index = new_index; lat += latitude_change; @@ -134,16 +134,18 @@ pub fn decode_polyline(polyline: &str, precision: u32) -> Result Ok(coordinates.into()) } -fn trans(chars: &[u8], mut index: usize) -> (i64, usize) { - let mut at_index; +fn trans(chars: &[u8], mut index: usize) -> Result<(i64, usize), String> { let mut shift = 0; let mut result = 0; let mut byte; loop { - at_index = chars[index]; - byte = (at_index as u64).saturating_sub(63); - index += 1; + byte = chars[index] as u64; + if byte < 63 || (shift > 64 - 5) { + return Err(format!("Cannot decode character at index {}", index)); + } + byte -= 63; result |= (byte & 0x1f) << shift; + index += 1; shift += 5; if byte < 0x20 { break; @@ -155,7 +157,7 @@ fn trans(chars: &[u8], mut index: usize) -> (i64, usize) { } else { result >> 1 } as i64; - (coordinate_change, index) + Ok((coordinate_change, index)) } #[cfg(test)] @@ -171,7 +173,7 @@ mod tests { } #[test] - fn it_works() { + fn precision5() { let test_cases = vec![ TestCase { input: vec![[2.0, 1.0], [4.0, 3.0]].into(), @@ -194,6 +196,30 @@ mod tests { } } + #[test] + fn precision6() { + let test_cases = vec![ + TestCase { + input: vec![[2.0, 1.0], [4.0, 3.0]].into(), + output: "_c`|@_gayB_gayB_gayB", + }, + TestCase { + input: vec![[-120.2, 38.5], [-120.95, 40.7], [-126.453, 43.252]].into(), + output: "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI", + }, + ]; + for test_case in test_cases { + assert_eq!( + encode_coordinates(test_case.input.clone(), 6).unwrap(), + test_case.output + ); + assert_eq!( + decode_polyline(test_case.output, 6).unwrap(), + test_case.input + ); + } + } + #[test] // coordinates close to each other (below precision) should work fn rounding_error() { @@ -207,22 +233,28 @@ mod tests { } #[test] - #[should_panic] - // emoji can't be decoded + // emoji is decodable but messes up data + // TODO: handle this case in the future? fn broken_string() { let s = "_p~iF~ps|U_u🗑lLnnqC_mqNvxq`@"; - let res = vec![[-120.2, 38.5], [-120.95, 40.7], [-126.453, 43.252]].into(); + let res = vec![[-120.2, 38.5], [-120.95, 2306360.53104], [-126.453, 2306363.08304]].into(); assert_eq!(decode_polyline(s, 5).unwrap(), res); } + #[test] + #[should_panic] + fn invalid_string() { + let s = "invalid_polyline_that_should_be_handled_gracefully"; + decode_polyline(s, 6).unwrap(); + } + #[test] #[should_panic] // Can't have a latitude > 90.0 fn bad_coords() { - let s = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; let res: LineString = vec![[-120.2, 38.5], [-120.95, 40.7], [-126.453, 430.252]].into(); - assert_eq!(encode_coordinates(res, 5).unwrap(), s); + encode_coordinates(res, 5).unwrap(); } #[test]