]>
Commit | Line | Data |
---|---|---|
fe692bf9 FG |
1 | pub(crate) mod function { |
2 | use bstr::ByteSlice; | |
3 | use btoi::btoi; | |
781aab86 FG |
4 | use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time}; |
5 | use winnow::{ | |
6 | combinator::{alt, separated_pair, terminated}, | |
7 | error::{AddContext, ParserError, StrContext}, | |
8 | prelude::*, | |
9 | stream::AsChar, | |
10 | token::{take, take_until0, take_while}, | |
fe692bf9 FG |
11 | }; |
12 | ||
781aab86 | 13 | use crate::{IdentityRef, SignatureRef}; |
fe692bf9 FG |
14 | |
15 | const SPACE: &[u8] = b" "; | |
16 | ||
17 | /// Parse a signature from the bytes input `i` using `nom`. | |
781aab86 FG |
18 | pub fn decode<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>( |
19 | i: &mut &'a [u8], | |
20 | ) -> PResult<SignatureRef<'a>, E> { | |
21 | separated_pair( | |
22 | identity, | |
23 | b" ", | |
24 | ( | |
25 | terminated(take_until0(SPACE), take(1usize)) | |
26 | .verify_map(|v| btoi::<SecondsSinceUnixEpoch>(v).ok()) | |
27 | .context(StrContext::Expected("<timestamp>".into())), | |
28 | alt(( | |
29 | take_while(1.., b'-').map(|_| Sign::Minus), | |
30 | take_while(1.., b'+').map(|_| Sign::Plus), | |
31 | )) | |
32 | .context(StrContext::Expected("+|-".into())), | |
33 | take_while(2, AsChar::is_dec_digit) | |
34 | .verify_map(|v| btoi::<OffsetInSeconds>(v).ok()) | |
35 | .context(StrContext::Expected("HH".into())), | |
36 | take_while(1..=2, AsChar::is_dec_digit) | |
37 | .verify_map(|v| btoi::<OffsetInSeconds>(v).ok()) | |
38 | .context(StrContext::Expected("MM".into())), | |
39 | ) | |
40 | .map(|(time, sign, hours, minutes)| { | |
41 | let offset = (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 }; | |
42 | Time { | |
43 | seconds: time, | |
44 | offset, | |
45 | sign, | |
46 | } | |
fe692bf9 | 47 | }), |
781aab86 FG |
48 | ) |
49 | .context(StrContext::Expected("<name> <<email>> <timestamp> <+|-><HHMM>".into())) | |
50 | .map(|(identity, time)| SignatureRef { | |
51 | name: identity.name, | |
52 | email: identity.email, | |
53 | time, | |
54 | }) | |
55 | .parse_next(i) | |
fe692bf9 FG |
56 | } |
57 | ||
58 | /// Parse an identity from the bytes input `i` (like `name <email>`) using `nom`. | |
781aab86 FG |
59 | pub fn identity<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>( |
60 | i: &mut &'a [u8], | |
61 | ) -> PResult<IdentityRef<'a>, E> { | |
62 | ( | |
63 | terminated(take_until0(&b" <"[..]), take(2usize)).context(StrContext::Expected("<name>".into())), | |
64 | terminated(take_until0(&b">"[..]), take(1usize)).context(StrContext::Expected("<email>".into())), | |
65 | ) | |
66 | .map(|(name, email): (&[u8], &[u8])| IdentityRef { | |
fe692bf9 FG |
67 | name: name.as_bstr(), |
68 | email: email.as_bstr(), | |
781aab86 FG |
69 | }) |
70 | .context(StrContext::Expected("<name> <<email>>".into())) | |
71 | .parse_next(i) | |
fe692bf9 | 72 | } |
0a29b90c | 73 | } |
fe692bf9 | 74 | pub use function::identity; |
0a29b90c FG |
75 | |
76 | #[cfg(test)] | |
77 | mod tests { | |
78 | mod parse_signature { | |
79 | use bstr::ByteSlice; | |
781aab86 | 80 | use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch}; |
0a29b90c | 81 | use gix_testtools::to_bstr_err; |
781aab86 | 82 | use winnow::prelude::*; |
0a29b90c | 83 | |
781aab86 | 84 | use crate::{signature, SignatureRef, Time}; |
0a29b90c | 85 | |
781aab86 FG |
86 | fn decode<'i>( |
87 | i: &mut &'i [u8], | |
88 | ) -> PResult<SignatureRef<'i>, winnow::error::TreeError<&'i [u8], winnow::error::StrContext>> { | |
89 | signature::decode.parse_next(i) | |
0a29b90c FG |
90 | } |
91 | ||
92 | fn signature( | |
93 | name: &'static str, | |
94 | email: &'static str, | |
781aab86 | 95 | seconds: SecondsSinceUnixEpoch, |
0a29b90c | 96 | sign: Sign, |
781aab86 | 97 | offset: OffsetInSeconds, |
0a29b90c FG |
98 | ) -> SignatureRef<'static> { |
99 | SignatureRef { | |
100 | name: name.as_bytes().as_bstr(), | |
101 | email: email.as_bytes().as_bstr(), | |
781aab86 | 102 | time: Time { seconds, offset, sign }, |
0a29b90c FG |
103 | } |
104 | } | |
105 | ||
106 | #[test] | |
107 | fn tz_minus() { | |
108 | assert_eq!( | |
781aab86 FG |
109 | decode |
110 | .parse_peek(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0230") | |
0a29b90c FG |
111 | .expect("parse to work") |
112 | .1, | |
113 | signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, -9000) | |
114 | ); | |
115 | } | |
116 | ||
117 | #[test] | |
118 | fn tz_plus() { | |
119 | assert_eq!( | |
781aab86 FG |
120 | decode |
121 | .parse_peek(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 +0230") | |
0a29b90c FG |
122 | .expect("parse to work") |
123 | .1, | |
124 | signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Plus, 9000) | |
125 | ); | |
126 | } | |
127 | ||
128 | #[test] | |
129 | fn negative_offset_0000() { | |
130 | assert_eq!( | |
781aab86 FG |
131 | decode |
132 | .parse_peek(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0000") | |
0a29b90c FG |
133 | .expect("parse to work") |
134 | .1, | |
135 | signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, 0) | |
136 | ); | |
137 | } | |
138 | ||
781aab86 FG |
139 | #[test] |
140 | fn negative_offset_double_dash() { | |
141 | assert_eq!( | |
142 | decode | |
143 | .parse_peek(b"name <name@example.com> 1288373970 --700") | |
144 | .expect("parse to work") | |
145 | .1, | |
146 | signature("name", "name@example.com", 1288373970, Sign::Minus, -252000) | |
147 | ); | |
148 | } | |
149 | ||
0a29b90c FG |
150 | #[test] |
151 | fn empty_name_and_email() { | |
152 | assert_eq!( | |
781aab86 | 153 | decode.parse_peek(b" <> 12345 -1215").expect("parse to work").1, |
0a29b90c FG |
154 | signature("", "", 12345, Sign::Minus, -44100) |
155 | ); | |
156 | } | |
157 | ||
158 | #[test] | |
159 | fn invalid_signature() { | |
160 | assert_eq!( | |
781aab86 | 161 | decode.parse_peek(b"hello < 12345 -1215") |
0a29b90c FG |
162 | .map_err(to_bstr_err) |
163 | .expect_err("parse fails as > is missing") | |
164 | .to_string(), | |
781aab86 | 165 | "in slice at ' 12345 -1215'\n 0: expected `<email>` at ' 12345 -1215'\n 1: expected `<name> <<email>>` at ' 12345 -1215'\n 2: expected `<name> <<email>> <timestamp> <+|-><HHMM>` at ' 12345 -1215'\n" |
0a29b90c FG |
166 | ); |
167 | } | |
168 | ||
169 | #[test] | |
170 | fn invalid_time() { | |
171 | assert_eq!( | |
781aab86 | 172 | decode.parse_peek(b"hello <> abc -1215") |
0a29b90c FG |
173 | .map_err(to_bstr_err) |
174 | .expect_err("parse fails as > is missing") | |
175 | .to_string(), | |
781aab86 | 176 | "in predicate verification at 'abc -1215'\n 0: expected `<timestamp>` at 'abc -1215'\n 1: expected `<name> <<email>> <timestamp> <+|-><HHMM>` at 'abc -1215'\n" |
0a29b90c FG |
177 | ); |
178 | } | |
179 | } | |
180 | } |