1pub(crate) mod function {
2 use crate::{IdentityRef, SignatureRef};
3 use bstr::ByteSlice;
4 use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time};
5 use gix_utils::btoi::to_signed;
6 use winnow::error::{ErrMode, ErrorKind};
7 use winnow::stream::Stream;
8 use winnow::{
9 combinator::{alt, opt, separated_pair, terminated},
10 error::{AddContext, ParserError, StrContext},
11 prelude::*,
12 stream::AsChar,
13 token::{take, take_until, take_while},
14 };
15
16 const SPACE: &[u8] = b" ";
17
18 pub fn decode<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>(
20 i: &mut &'a [u8],
21 ) -> PResult<SignatureRef<'a>, E> {
22 separated_pair(
23 identity,
24 opt(b" "),
25 opt((
26 terminated(take_until(0.., SPACE), take(1usize))
27 .verify_map(|v| to_signed::<SecondsSinceUnixEpoch>(v).ok())
28 .context(StrContext::Expected("<timestamp>".into())),
29 alt((
30 take_while(1.., b'-').map(|_| Sign::Minus),
31 take_while(1.., b'+').map(|_| Sign::Plus),
32 ))
33 .context(StrContext::Expected("+|-".into())),
34 take_while(2, AsChar::is_dec_digit)
35 .verify_map(|v| to_signed::<OffsetInSeconds>(v).ok())
36 .context(StrContext::Expected("HH".into())),
37 take_while(1..=2, AsChar::is_dec_digit)
38 .verify_map(|v| to_signed::<OffsetInSeconds>(v).ok())
39 .context(StrContext::Expected("MM".into())),
40 take_while(0.., AsChar::is_dec_digit).map(|v: &[u8]| v),
41 ))
42 .map(|maybe_timestamp| {
43 if let Some((time, sign, hours, minutes, trailing_digits)) = maybe_timestamp {
44 let offset = if trailing_digits.is_empty() {
45 (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 }
46 } else {
47 0
48 };
49 Time {
50 seconds: time,
51 offset,
52 sign,
53 }
54 } else {
55 Time::new(0, 0)
56 }
57 }),
58 )
59 .context(StrContext::Expected("<name> <<email>> <timestamp> <+|-><HHMM>".into()))
60 .map(|(identity, time)| SignatureRef {
61 name: identity.name,
62 email: identity.email,
63 time,
64 })
65 .parse_next(i)
66 }
67
68 pub fn identity<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>(
70 i: &mut &'a [u8],
71 ) -> PResult<IdentityRef<'a>, E> {
72 let start = i.checkpoint();
73 let eol_idx = i.find_byte(b'\n').unwrap_or(i.len());
74 let right_delim_idx =
75 i[..eol_idx]
76 .rfind_byte(b'>')
77 .ok_or(ErrMode::Cut(E::from_error_kind(i, ErrorKind::Eof).add_context(
78 i,
79 &start,
80 StrContext::Label("Closing '>' not found"),
81 )))?;
82 let i_name_and_email = &i[..right_delim_idx];
83 let skip_from_right = i_name_and_email
84 .iter()
85 .rev()
86 .take_while(|b| b.is_ascii_whitespace() || **b == b'>')
87 .count();
88 let left_delim_idx =
89 i_name_and_email
90 .find_byte(b'<')
91 .ok_or(ErrMode::Cut(E::from_error_kind(i, ErrorKind::Eof).add_context(
92 &i_name_and_email,
93 &start,
94 StrContext::Label("Opening '<' not found"),
95 )))?;
96 let skip_from_left = i[left_delim_idx..]
97 .iter()
98 .take_while(|b| b.is_ascii_whitespace() || **b == b'<')
99 .count();
100 let mut name = i[..left_delim_idx].as_bstr();
101 name = name.strip_suffix(b" ").unwrap_or(name).as_bstr();
102
103 let email = i
104 .get(left_delim_idx + skip_from_left..right_delim_idx - skip_from_right)
105 .ok_or(ErrMode::Cut(E::from_error_kind(i, ErrorKind::Eof).add_context(
106 &i_name_and_email,
107 &start,
108 StrContext::Label("Skipped parts run into each other"),
109 )))?
110 .as_bstr();
111 *i = i.get(right_delim_idx + 1..).unwrap_or(&[]);
112 Ok(IdentityRef { name, email })
113 }
114}
115pub use function::identity;
116
117#[cfg(test)]
118mod tests {
119 mod parse_signature {
120 use bstr::ByteSlice;
121 use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch};
122 use gix_testtools::to_bstr_err;
123 use winnow::prelude::*;
124
125 use crate::{signature, SignatureRef, Time};
126
127 fn decode<'i>(
128 i: &mut &'i [u8],
129 ) -> PResult<SignatureRef<'i>, winnow::error::TreeError<&'i [u8], winnow::error::StrContext>> {
130 signature::decode.parse_next(i)
131 }
132
133 fn signature(
134 name: &'static str,
135 email: &'static str,
136 seconds: SecondsSinceUnixEpoch,
137 sign: Sign,
138 offset: OffsetInSeconds,
139 ) -> SignatureRef<'static> {
140 SignatureRef {
141 name: name.as_bytes().as_bstr(),
142 email: email.as_bytes().as_bstr(),
143 time: Time { seconds, offset, sign },
144 }
145 }
146
147 #[test]
148 fn tz_minus() {
149 assert_eq!(
150 decode
151 .parse_peek(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0230")
152 .expect("parse to work")
153 .1,
154 signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, -9000)
155 );
156 }
157
158 #[test]
159 fn tz_plus() {
160 assert_eq!(
161 decode
162 .parse_peek(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 +0230")
163 .expect("parse to work")
164 .1,
165 signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Plus, 9000)
166 );
167 }
168
169 #[test]
170 fn negative_offset_0000() {
171 assert_eq!(
172 decode
173 .parse_peek(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0000")
174 .expect("parse to work")
175 .1,
176 signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, 0)
177 );
178 }
179
180 #[test]
181 fn negative_offset_double_dash() {
182 assert_eq!(
183 decode
184 .parse_peek(b"name <name@example.com> 1288373970 --700")
185 .expect("parse to work")
186 .1,
187 signature("name", "name@example.com", 1288373970, Sign::Minus, -252000)
188 );
189 }
190
191 #[test]
192 fn empty_name_and_email() {
193 assert_eq!(
194 decode.parse_peek(b" <> 12345 -1215").expect("parse to work").1,
195 signature("", "", 12345, Sign::Minus, -44100)
196 );
197 }
198
199 #[test]
200 fn invalid_signature() {
201 assert_eq!(
202 decode.parse_peek(b"hello < 12345 -1215")
203 .map_err(to_bstr_err)
204 .expect_err("parse fails as > is missing")
205 .to_string(),
206 "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"
207 );
208 }
209
210 #[test]
211 fn invalid_time() {
212 assert_eq!(
213 decode.parse_peek(b"hello <> abc -1215").expect("parse to work").1,
214 signature("hello", "", 0, Sign::Plus, 0)
215 );
216 }
217 }
218}