Skip to content

Commit 66dadf8

Browse files
committed
fix: regression that could cause non-linear parsing behaviour.
2 parents 2372437 + a743c5d commit 66dadf8

File tree

2 files changed

+68
-54
lines changed

2 files changed

+68
-54
lines changed

gix-config/src/parse/nom/mod.rs

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ fn section<'i>(
121121
dispatch(Event::Newline(Cow::Borrowed(v.as_bstr())));
122122
}
123123

124-
let _ = key_value_pair(i, node, dispatch);
124+
key_value_pair(i, node, dispatch)?;
125125

126126
if let Some(comment) = opt(comment).parse_next(i)? {
127127
dispatch(Event::Comment(comment));
@@ -213,16 +213,18 @@ fn key_value_pair<'i>(
213213
dispatch: &mut impl FnMut(Event<'i>),
214214
) -> PResult<(), NomError<&'i [u8]>> {
215215
*node = ParseNode::Name;
216-
let name = config_name.parse_next(i)?;
216+
if let Some(name) = opt(config_name).parse_next(i)? {
217+
dispatch(Event::SectionKey(section::Key(Cow::Borrowed(name))));
217218

218-
dispatch(Event::SectionKey(section::Key(Cow::Borrowed(name))));
219+
if let Some(whitespace) = opt(take_spaces1).parse_next(i)? {
220+
dispatch(Event::Whitespace(Cow::Borrowed(whitespace)));
221+
}
219222

220-
if let Some(whitespace) = opt(take_spaces1).parse_next(i)? {
221-
dispatch(Event::Whitespace(Cow::Borrowed(whitespace)));
223+
*node = ParseNode::Value;
224+
config_value(i, dispatch)
225+
} else {
226+
Ok(())
222227
}
223-
224-
*node = ParseNode::Value;
225-
config_value(i, dispatch)
226228
}
227229

228230
/// Parses the config name of a config pair. Assumes the input has already been
@@ -265,62 +267,67 @@ fn value_impl<'i>(i: &mut &'i [u8], dispatch: &mut impl FnMut(Event<'i>)) -> PRe
265267
// Used to determine if we return a Value or Value{Not,}Done
266268
let mut partial_value_found = false;
267269

268-
while let Some(c) = i.next_token() {
269-
match c {
270-
b'\n' => {
271-
value_end = Some(i.offset_from(&value_start_checkpoint) - 1);
272-
break;
273-
}
274-
b';' | b'#' if !is_in_quotes => {
275-
value_end = Some(i.offset_from(&value_start_checkpoint) - 1);
276-
break;
277-
}
278-
b'\\' => {
279-
let escaped_index = i.offset_from(&value_start_checkpoint);
280-
let escape_index = escaped_index - 1;
281-
let Some(mut c) = i.next_token() else {
282-
i.reset(start_checkpoint);
283-
return Err(winnow::error::ErrMode::from_error_kind(i, ErrorKind::Token));
284-
};
285-
let mut consumed = 1;
286-
if c == b'\r' {
287-
c = i.next_token().ok_or_else(|| {
288-
i.reset(start_checkpoint);
289-
winnow::error::ErrMode::from_error_kind(i, ErrorKind::Token)
290-
})?;
291-
if c != b'\n' {
270+
loop {
271+
let _ = take_while(0.., |c| !matches!(c, b'\n' | b'\\' | b'"' | b';' | b'#')).parse_next(i)?;
272+
if let Some(c) = i.next_token() {
273+
match c {
274+
b'\n' => {
275+
value_end = Some(i.offset_from(&value_start_checkpoint) - 1);
276+
break;
277+
}
278+
b';' | b'#' if !is_in_quotes => {
279+
value_end = Some(i.offset_from(&value_start_checkpoint) - 1);
280+
break;
281+
}
282+
b'\\' => {
283+
let escaped_index = i.offset_from(&value_start_checkpoint);
284+
let escape_index = escaped_index - 1;
285+
let Some(mut c) = i.next_token() else {
292286
i.reset(start_checkpoint);
293-
return Err(winnow::error::ErrMode::from_error_kind(i, ErrorKind::Slice));
287+
return Err(winnow::error::ErrMode::from_error_kind(i, ErrorKind::Token));
288+
};
289+
let mut consumed = 1;
290+
if c == b'\r' {
291+
c = i.next_token().ok_or_else(|| {
292+
i.reset(start_checkpoint);
293+
winnow::error::ErrMode::from_error_kind(i, ErrorKind::Token)
294+
})?;
295+
if c != b'\n' {
296+
i.reset(start_checkpoint);
297+
return Err(winnow::error::ErrMode::from_error_kind(i, ErrorKind::Slice));
298+
}
299+
consumed += 1;
294300
}
295-
consumed += 1;
296-
}
297301

298-
match c {
299-
b'\n' => {
300-
partial_value_found = true;
302+
match c {
303+
b'\n' => {
304+
partial_value_found = true;
301305

302-
i.reset(value_start_checkpoint);
306+
i.reset(value_start_checkpoint);
303307

304-
let value = i.next_slice(escape_index).as_bstr();
305-
dispatch(Event::ValueNotDone(Cow::Borrowed(value)));
308+
let value = i.next_slice(escape_index).as_bstr();
309+
dispatch(Event::ValueNotDone(Cow::Borrowed(value)));
306310

307-
i.next_token();
311+
i.next_token();
308312

309-
let nl = i.next_slice(consumed).as_bstr();
310-
dispatch(Event::Newline(Cow::Borrowed(nl)));
313+
let nl = i.next_slice(consumed).as_bstr();
314+
dispatch(Event::Newline(Cow::Borrowed(nl)));
311315

312-
value_start_checkpoint = i.checkpoint();
313-
value_end = None;
314-
}
315-
b'n' | b't' | b'\\' | b'b' | b'"' => {}
316-
_ => {
317-
i.reset(start_checkpoint);
318-
return Err(winnow::error::ErrMode::from_error_kind(i, ErrorKind::Token));
316+
value_start_checkpoint = i.checkpoint();
317+
value_end = None;
318+
}
319+
b'n' | b't' | b'\\' | b'b' | b'"' => {}
320+
_ => {
321+
i.reset(start_checkpoint);
322+
return Err(winnow::error::ErrMode::from_error_kind(i, ErrorKind::Token));
323+
}
319324
}
320325
}
326+
b'"' => is_in_quotes = !is_in_quotes,
327+
_ => {}
321328
}
322-
b'"' => is_in_quotes = !is_in_quotes,
323-
_ => {}
329+
} else {
330+
break;
324331
}
325332
}
326333
if is_in_quotes {

gix-config/src/parse/nom/tests.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,14 @@ mod key_value_pair {
808808
fn nonascii_is_allowed_for_values_but_not_for_keys() {
809809
let mut node = ParseNode::SectionHeader;
810810
let mut vec = Default::default();
811-
assert!(key_value("你好".as_bytes(), &mut node, &mut vec).is_err());
811+
assert!(
812+
key_value("你好".as_bytes(), &mut node, &mut vec).is_ok(),
813+
"Verifying `is_ok` because bad keys get ignored, the caller parser handles this as error"
814+
);
815+
assert_eq!(vec, into_events(vec![]));
816+
817+
let mut node = ParseNode::SectionHeader;
818+
let mut vec = Default::default();
812819
assert!(key_value("a = 你好 ".as_bytes(), &mut node, &mut vec).is_ok());
813820
assert_eq!(
814821
vec,

0 commit comments

Comments
 (0)