1use crate::git_date::tm::date_overflows;
7use crate::objects::ObjectKind;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SignatureTimestamp {
12 Valid(i64),
14 Sentinel,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ParsedSignatureTimes {
22 pub unix_seconds: i64,
24 pub tz_offset_secs: i64,
26 pub tz_hhmm_range: std::ops::Range<usize>,
28}
29
30fn scan_decimal_timestamp(bytes: &[u8], mut i: usize) -> Option<(u128, usize)> {
33 const MAX_DIGITS: usize = 21;
34 let start = i;
35 let mut count = 0usize;
36 while i < bytes.len() && bytes[i].is_ascii_digit() {
37 count += 1;
38 if count > MAX_DIGITS {
39 return Some((u128::MAX, i));
40 }
41 i += 1;
42 }
43 if count == 0 {
44 return None;
45 }
46 let s = std::str::from_utf8(&bytes[start..i]).ok()?;
47 let v: u128 = s.parse().ok()?;
48 Some((v, i))
49}
50
51fn skip_fsck_date_leading_ws(bytes: &[u8], mut i: usize) -> usize {
53 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t') {
54 i += 1;
55 }
56 i
57}
58
59fn parse_tz_hhmm_offset(offset: &str) -> Option<i64> {
60 let b = offset.as_bytes();
61 if b.len() < 5 {
62 return None;
63 }
64 if !(b[0] == b'+' || b[0] == b'-') {
65 return None;
66 }
67 let sign = if b[0] == b'-' { -1i64 } else { 1i64 };
68 let hours: i64 = std::str::from_utf8(&b[1..3]).ok()?.parse().ok()?;
69 let minutes: i64 = std::str::from_utf8(&b[3..5]).ok()?.parse().ok()?;
70 Some(sign * (hours * 3600 + minutes * 60))
71}
72
73#[must_use]
75pub fn parse_signature_times(ident: &str) -> Option<ParsedSignatureTimes> {
76 match parse_signature_tail(ident)? {
77 SignatureTail::Valid(p) => Some(p),
78 SignatureTail::Overflow | SignatureTail::NonNumeric => None,
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum SignatureTail {
85 Valid(ParsedSignatureTimes),
87 Overflow,
90 NonNumeric,
92}
93
94#[must_use]
96pub fn parse_signature_tail(ident: &str) -> Option<SignatureTail> {
97 let bytes = ident.as_bytes();
98 let gt = ident.rfind('>')?;
99 let mut i = skip_fsck_date_leading_ws(bytes, gt + 1);
100 if i >= bytes.len() || !bytes[i].is_ascii_digit() {
101 return Some(SignatureTail::NonNumeric);
102 }
103 let (raw, after_digits) = scan_decimal_timestamp(bytes, i)?;
104 if after_digits >= bytes.len() || bytes[after_digits] != b' ' {
105 return None;
106 }
107 i = after_digits + 1;
108 if i + 5 > bytes.len() {
109 return None;
110 }
111 let tz_slice = ident.get(i..i + 5)?;
112 let tz_offset_secs = parse_tz_hhmm_offset(tz_slice)?;
113 let tz_hhmm_range = i..i + 5;
114 if raw == u128::MAX || raw > u64::MAX as u128 || date_overflows(raw as u64) {
115 return Some(SignatureTail::Overflow);
116 }
117 let unix_seconds = i64::try_from(raw).ok()?;
118 Some(SignatureTail::Valid(ParsedSignatureTimes {
119 unix_seconds,
120 tz_offset_secs,
121 tz_hhmm_range,
122 }))
123}
124
125pub fn signature_timestamp_for_pretty(ident: &str) -> SignatureTimestamp {
127 match parse_signature_times(ident) {
128 Some(p) => SignatureTimestamp::Valid(p.unix_seconds),
129 None => SignatureTimestamp::Sentinel,
130 }
131}
132
133#[must_use]
137pub fn committer_timestamp_for_until_filter(ident: &str) -> i64 {
138 match signature_timestamp_for_pretty(ident) {
139 SignatureTimestamp::Valid(ts) => ts,
140 SignatureTimestamp::Sentinel => 0,
141 }
142}
143
144#[must_use]
146pub fn timestamp_for_at_ct(ts: SignatureTimestamp) -> Option<i64> {
147 match ts {
148 SignatureTimestamp::Valid(v) => Some(v),
149 SignatureTimestamp::Sentinel => None,
150 }
151}
152
153pub fn fsck_commit_idents(data: &[u8]) -> Result<(), String> {
156 crate::fsck_standalone::fsck_object(ObjectKind::Commit, data).map_err(|e| e.report_line())
157}
158
159#[must_use]
161pub fn committer_unix_seconds_for_ordering(ident: &str) -> i64 {
162 match signature_timestamp_for_pretty(ident) {
163 SignatureTimestamp::Valid(ts) => ts,
164 SignatureTimestamp::Sentinel => 0,
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn non_numeric_author_date_is_non_numeric_tail() {
174 let ident = "A <e@x> totally_bogus -0700";
175 assert!(matches!(
176 parse_signature_tail(ident),
177 Some(SignatureTail::NonNumeric)
178 ));
179 }
180
181 #[test]
182 fn u128_max_digit_count_is_overflow_tail() {
183 let ident = "A <e@x> 18446744073709551617 -0700";
184 assert!(matches!(
185 parse_signature_tail(ident),
186 Some(SignatureTail::Overflow)
187 ));
188 }
189}