Fix bugs when parsing format specifiers with key mappings.

* Algorithm used for parsing the key from "%(key)d" format did not
handle multiple parenthesis correctly.

* Second problem is that they way we calculated number of consumed chars
was based on str::find, making the code vulnerable to false-positives.
This commit is contained in:
Michel Heily committed 2019-06-17 19:35:09 +03:00
1 parent 6fe2c43bf7
commit adccfa63cf
1 file changed
+56 -19
+56 -19
View File
@@ -343,19 +343,41 @@ fn parse_literal(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError>
))
}
fn parse_text_inside_parentheses(text: &str) -> Option<(String, &str)> {
let mut counter = 1;
let mut chars = text.chars();
let mut contained_text = String::new();
while counter > 0 {
let c = chars.next();
match c {
Some('(') => {
counter += 1;
}
Some(')') => {
counter -= 1;
}
None => {
return None;
}
_ => (),
}
if counter > 0 {
contained_text.push(c.unwrap());
}
}
Some((contained_text, chars.as_str()))
}
fn parse_spec_mapping_key(text: &str) -> Result<(Option<String>, &str), CFormatErrorType> {
let mut chars = text.chars();
let next_char = chars.next();
if next_char == Some('(') {
// Get remaining characters after opening parentheses.
let cur_text = chars.as_str();
match cur_text.find(')') {
Some(position) => {
let (left, right) = cur_text.split_at(position);
Ok((Some(left.to_string()), &right[1..]))
}
match parse_text_inside_parentheses(chars.as_str()) {
Some((key, remaining_text)) => Ok((Some(key), remaining_text)),
None => Err(CFormatErrorType::UnmatchedKeyParentheses),
}
} else {
@@ -480,29 +502,30 @@ fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErr
}
}
fn calc_consumed(a: &str, b: &str) -> usize {
a.chars().count() - b.chars().count()
}
impl FromStr for CFormatSpec {
type Err = ParsingError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let consumed = 0;
let mut chars = text.chars();
if chars.next() != Some('%') {
return Err((CFormatErrorType::MissingModuloSign, consumed));
return Err((CFormatErrorType::MissingModuloSign, 1));
}
let consumed = consumed + 1;
let (mapping_key, after_mapping_key) =
parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?;
let after_modulo_sign = chars.as_str();
let (mapping_key, after_mapping_key) = parse_spec_mapping_key(after_modulo_sign)
.map_err(|err| (err, calc_consumed(text, after_modulo_sign)))?;
let (flags, after_flags) = parse_flags(after_mapping_key);
let (width, after_width) = parse_quantity(after_flags);
let (precision, after_precision) = parse_precision(after_width);
// A length modifier (h, l, or L) may be present,
// but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d.
let after_length = consume_length(after_precision);
let consumed = text.find(after_length).unwrap();
let (format_type, _remaining_text, format_char) =
parse_format_type(after_length).map_err(|err| (err, consumed))?;
let consumed = consumed + 1; // not using text.find because if the str is exausted consumed will be 0
let (format_type, remaining_text, format_char) = parse_format_type(after_length)
.map_err(|err| (err, calc_consumed(text, after_length)))?;
// apply default precision for float types
let precision = match precision {
@@ -520,7 +543,7 @@ impl FromStr for CFormatSpec {
precision: precision,
format_type: format_type,
format_char: format_char,
chars_consumed: consumed,
chars_consumed: calc_consumed(text, remaining_text),
})
}
}
@@ -583,6 +606,20 @@ mod tests {
flags: CConversionFlags::empty(),
});
assert_eq!("%(amount)d".parse::<CFormatSpec>(), expected);
let expected = Ok(CFormatSpec {
mapping_key: Some("m((u(((l((((ti))))p)))l))e".to_string()),
format_type: CFormatType::Number(CNumberType::Decimal),
format_char: 'd',
chars_consumed: 30,
min_field_width: None,
precision: None,
flags: CConversionFlags::empty(),
});
assert_eq!(
"%(m((u(((l((((ti))))p)))l))e)d".parse::<CFormatSpec>(),
expected
);
}
#[test]
@@ -613,7 +650,7 @@ mod tests {
"Hello %".parse::<CFormatString>(),
Err(CFormatError {
typ: CFormatErrorType::IncompleteFormat,
index: 6
index: 7
})
);
}