Skip to content

Commit 9717a25

Browse files
committed
Merge branch 'fix-1438'
2 parents 36f221b + 8dca2d4 commit 9717a25

File tree

5 files changed

+103
-15
lines changed

5 files changed

+103
-15
lines changed

gix-actor/src/signature/decode.rs

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
pub(crate) mod function {
2+
use crate::{IdentityRef, SignatureRef};
23
use bstr::ByteSlice;
34
use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time};
45
use gix_utils::btoi::to_signed;
6+
use winnow::error::{ErrMode, ErrorKind};
7+
use winnow::stream::Stream;
58
use winnow::{
69
combinator::{alt, separated_pair, terminated},
710
error::{AddContext, ParserError, StrContext},
@@ -10,8 +13,6 @@ pub(crate) mod function {
1013
token::{take, take_until, take_while},
1114
};
1215

13-
use crate::{IdentityRef, SignatureRef};
14-
1516
const SPACE: &[u8] = b" ";
1617

1718
/// Parse a signature from the bytes input `i` using `nom`.
@@ -64,16 +65,47 @@ pub(crate) mod function {
6465
pub fn identity<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>(
6566
i: &mut &'a [u8],
6667
) -> PResult<IdentityRef<'a>, E> {
67-
(
68-
terminated(take_until(0.., &b" <"[..]), take(2usize)).context(StrContext::Expected("<name>".into())),
69-
terminated(take_until(0.., &b">"[..]), take(1usize)).context(StrContext::Expected("<email>".into())),
70-
)
71-
.map(|(name, email): (&[u8], &[u8])| IdentityRef {
72-
name: name.as_bstr(),
73-
email: email.as_bstr(),
74-
})
75-
.context(StrContext::Expected("<name> <<email>>".into()))
76-
.parse_next(i)
68+
let start = i.checkpoint();
69+
let eol_idx = i.find_byte(b'\n').unwrap_or(i.len());
70+
let right_delim_idx =
71+
i[..eol_idx]
72+
.rfind_byte(b'>')
73+
.ok_or(ErrMode::Cut(E::from_error_kind(i, ErrorKind::Eof).add_context(
74+
i,
75+
&start,
76+
StrContext::Label("Closing '>' not found"),
77+
)))?;
78+
let i_name_and_email = &i[..right_delim_idx];
79+
let skip_from_right = i_name_and_email
80+
.iter()
81+
.rev()
82+
.take_while(|b| b.is_ascii_whitespace() || **b == b'>')
83+
.count();
84+
let left_delim_idx =
85+
i_name_and_email
86+
.find_byte(b'<')
87+
.ok_or(ErrMode::Cut(E::from_error_kind(i, ErrorKind::Eof).add_context(
88+
&i_name_and_email,
89+
&start,
90+
StrContext::Label("Opening '<' not found"),
91+
)))?;
92+
let skip_from_left = i[left_delim_idx..]
93+
.iter()
94+
.take_while(|b| b.is_ascii_whitespace() || **b == b'<')
95+
.count();
96+
let mut name = i[..left_delim_idx].as_bstr();
97+
name = name.strip_suffix(b" ").unwrap_or(name).as_bstr();
98+
99+
let email = i
100+
.get(left_delim_idx + skip_from_left..right_delim_idx - skip_from_right)
101+
.ok_or(ErrMode::Cut(E::from_error_kind(i, ErrorKind::Eof).add_context(
102+
&i_name_and_email,
103+
&start,
104+
StrContext::Label("Skipped parts run into each other"),
105+
)))?
106+
.as_bstr();
107+
*i = i.get(right_delim_idx + 1..).unwrap_or(&[]);
108+
Ok(IdentityRef { name, email })
77109
}
78110
}
79111
pub use function::identity;
@@ -167,7 +199,7 @@ mod tests {
167199
.map_err(to_bstr_err)
168200
.expect_err("parse fails as > is missing")
169201
.to_string(),
170-
"in slice at ' 12345 -1215'\n 0: expected `<email>` at ' 12345 -1215'\n 1: expected `<name> <<email>>` at 'hello < 12345 -1215'\n 2: expected `<name> <<email>> <timestamp> <+|-><HHMM>` at 'hello < 12345 -1215'\n"
202+
"in end of file at 'hello < 12345 -1215'\n 0: invalid Closing '>' not found at 'hello < 12345 -1215'\n 1: expected `<name> <<email>> <timestamp> <+|-><HHMM>` at 'hello < 12345 -1215'\n"
171203
);
172204
}
173205

gix-actor/tests/identity/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,27 @@ fn round_trip() -> gix_testtools::Result {
1616
}
1717
Ok(())
1818
}
19+
20+
#[test]
21+
fn lenient_parsing() -> gix_testtools::Result {
22+
for input in [
23+
"First Last<<fl <First Last<[email protected] >> >",
24+
"First Last<fl <First Last<[email protected]>>\n",
25+
] {
26+
let identity = gix_actor::IdentityRef::from_bytes::<()>(input.as_bytes()).unwrap();
27+
assert_eq!(identity.name, "First Last");
28+
assert_eq!(
29+
identity.email, "fl <First Last<[email protected]",
30+
"extra trailing and leading angled parens are stripped"
31+
);
32+
let signature: Identity = identity.into();
33+
let mut output = Vec::new();
34+
let err = signature.write_to(&mut output).unwrap_err();
35+
assert_eq!(
36+
err.to_string(),
37+
"Signature name or email must not contain '<', '>' or \\n",
38+
"this isn't roundtrippable as the name is technically incorrect - must not contain brackets"
39+
);
40+
}
41+
Ok(())
42+
}

gix-object/tests/commit/from_bytes.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ fn invalid_timestsamp() {
3535
);
3636
}
3737

38+
#[test]
39+
fn invalid_email_of_committer() {
40+
let actor = gix_actor::SignatureRef {
41+
name: b"Gregor Hartmann".as_bstr(),
42+
email: b"gh <Gregor Hartmann<[email protected]".as_bstr(),
43+
time: Time {
44+
seconds: 1282910542,
45+
offset: 2 * 60 * 60,
46+
sign: Sign::Plus,
47+
},
48+
};
49+
assert_eq!(
50+
CommitRef::from_bytes(&fixture_name("commit", "invalid-actor.txt"))
51+
.expect("ignore strangely formed actor format"),
52+
CommitRef {
53+
tree: b"220738fd4199e95a2b244465168366a73ebdf271".as_bstr(),
54+
parents: [b"209fbe2d632761b30b7b17422914e11b93692833".as_bstr()].into(),
55+
author: actor,
56+
committer: actor,
57+
encoding: None,
58+
message: b"build breakers".as_bstr(),
59+
extra_headers: vec![]
60+
}
61+
);
62+
}
63+
3864
#[test]
3965
fn unsigned() -> crate::Result {
4066
assert_eq!(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
tree 220738fd4199e95a2b244465168366a73ebdf271
2+
parent 209fbe2d632761b30b7b17422914e11b93692833
3+
author Gregor Hartmann<gh <Gregor Hartmann<[email protected]>> 1282910542 +0200
4+
committer Gregor Hartmann<gh <Gregor Hartmann<[email protected]>> 1282910542 +0200
5+
6+
build breakers

gix-object/tests/tag/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,14 @@ fn invalid() {
124124
assert_eq!(
125125
TagRef::from_bytes(partial_tag).unwrap_err().to_string(),
126126
if cfg!(feature = "verbose-object-parsing-errors") {
127-
"object parsing failed at `tagger Sebasti`"
127+
"object parsing failed at `Sebasti`\ninvalid Closing '>' not found\nexpected `<name> <<email>> <timestamp> <+|-><HHMM>`, `tagger <signature>`"
128128
} else {
129129
"object parsing failed"
130130
}
131131
);
132132
assert_eq!(
133133
TagRefIter::from_bytes(partial_tag).take_while(Result::is_ok).count(),
134-
4,
134+
3,
135135
"we can decode some fields before failing"
136136
);
137137
}

0 commit comments

Comments
 (0)