1use git_zero_core::{Error, ObjectKind, Result};
3
4#[derive(Clone, Copy)]
5enum Phase {
6 Tree,
7 Parents,
8 Committer,
9 CommitExtra,
10 TagObject,
11 TagType,
12 TagName,
13 Tagger,
14 TagExtra,
15 Message,
16}
17
18enum Line {
19 Prefix,
20 Parent,
21 Skip,
22 Identity(Identity),
23}
24
25pub struct FsckHeaders {
32 phase: Phase,
33 line: Line,
34 prefix: [u8; 10],
35 used: usize,
36 nonempty: bool,
37 commit: bool,
38 failure: Option<Error>,
39}
40
41impl FsckHeaders {
42 pub fn new(kind: ObjectKind) -> Result<Self> {
43 let phase = match kind {
44 ObjectKind::Commit => Phase::Tree,
45 ObjectKind::Tag => Phase::TagObject,
46 _ => return Err(Error::InvalidObject),
47 };
48 Ok(Self {
49 phase,
50 line: Line::Prefix,
51 prefix: [0; 10],
52 used: 0,
53 nonempty: false,
54 commit: kind == ObjectKind::Commit,
55 failure: None,
56 })
57 }
58
59 pub fn update(&mut self, bytes: &[u8]) -> Result<()> {
60 if let Some(error) = self.failure {
61 return Err(error);
62 }
63 let result = self.feed(bytes);
64 if let Err(error) = result {
65 self.failure = Some(error);
66 }
67 result
68 }
69
70 fn feed(&mut self, bytes: &[u8]) -> Result<()> {
71 for (index, &byte) in bytes.iter().enumerate() {
72 if matches!(self.phase, Phase::Message) {
73 return if self.commit && bytes[index..].contains(&0) {
74 Err(Error::InvalidObject)
75 } else {
76 Ok(())
77 };
78 }
79 if byte == 0 {
80 return Err(Error::InvalidObject);
81 }
82 if byte == b'\n' {
83 self.end_line()?;
84 } else {
85 self.nonempty = true;
86 self.byte(byte)?;
87 }
88 }
89 Ok(())
90 }
91
92 fn byte(&mut self, byte: u8) -> Result<()> {
93 match &mut self.line {
94 Line::Identity(identity) => return identity.byte(byte),
95 Line::Skip | Line::Parent => return Ok(()),
96 Line::Prefix => {}
97 }
98 let expected: &[u8] = match self.phase {
99 Phase::Parents => b"author ",
100 Phase::Committer => b"committer ",
101 Phase::Tagger => b"tagger ",
102 _ => {
103 self.line = Line::Skip;
104 return Ok(());
105 }
106 };
107 self.prefix[self.used] = byte;
108 self.used += 1;
109 let prefix = &self.prefix[..self.used];
110 if matches!(self.phase, Phase::Parents) && b"parent ".starts_with(prefix) {
111 if self.used == 7 {
112 self.line = Line::Parent;
113 }
114 return Ok(());
115 }
116 if expected.starts_with(prefix) {
117 if self.used == expected.len() {
118 self.line = Line::Identity(Identity::new());
119 }
120 return Ok(());
121 }
122 if !matches!(self.phase, Phase::Tagger) {
123 return Err(Error::InvalidObject);
124 }
125 self.line = Line::Skip;
126 Ok(())
127 }
128
129 fn end_line(&mut self) -> Result<()> {
130 if let Line::Identity(identity) = &self.line {
131 identity.finish()?;
132 }
133 self.phase = match self.phase {
134 Phase::Tree => Phase::Parents,
135 Phase::Parents => match self.line {
136 Line::Parent => Phase::Parents,
137 Line::Identity(_) => Phase::Committer,
138 _ => return Err(Error::InvalidObject),
139 },
140 Phase::Committer if matches!(self.line, Line::Identity(_)) => Phase::CommitExtra,
141 Phase::Committer => return Err(Error::InvalidObject),
142 Phase::TagObject => Phase::TagType,
143 Phase::TagType => Phase::TagName,
144 Phase::TagName => Phase::Tagger,
145 Phase::CommitExtra | Phase::Tagger | Phase::TagExtra if !self.nonempty => {
146 Phase::Message
147 }
148 Phase::CommitExtra => Phase::CommitExtra,
149 Phase::Tagger | Phase::TagExtra => Phase::TagExtra,
150 Phase::Message => Phase::Message,
151 };
152 self.line = Line::Prefix;
153 self.used = 0;
154 self.nonempty = false;
155 Ok(())
156 }
157
158 pub fn finish(&self) -> Result<()> {
159 if let Some(error) = self.failure {
160 return Err(error);
161 }
162 if matches!(self.phase, Phase::Message)
163 || (!self.nonempty
164 && matches!(
165 self.phase,
166 Phase::CommitExtra | Phase::Tagger | Phase::TagExtra
167 ))
168 {
169 Ok(())
170 } else {
171 Err(Error::InvalidObject)
172 }
173 }
174}
175
176#[derive(Clone, Copy)]
177enum IdentPhase {
178 Name,
179 Email,
180 Space,
181 DateStart,
182 Date,
183 Sign,
184 Zone(u8),
185 End,
186}
187struct Identity {
188 phase: IdentPhase,
189 previous: Option<u8>,
190 date: u64,
191 zero: bool,
192}
193impl Identity {
194 fn new() -> Self {
195 Self {
196 phase: IdentPhase::Name,
197 previous: None,
198 date: 0,
199 zero: false,
200 }
201 }
202 fn byte(&mut self, byte: u8) -> Result<()> {
203 self.phase = match self.phase {
204 IdentPhase::Name if byte == b'<' && self.previous == Some(b' ') => IdentPhase::Email,
205 IdentPhase::Name if byte != b'<' && byte != b'>' => IdentPhase::Name,
206 IdentPhase::Email if byte == b'>' => IdentPhase::Space,
207 IdentPhase::Email if byte != b'<' => IdentPhase::Email,
208 IdentPhase::Space if byte == b' ' => IdentPhase::DateStart,
209 IdentPhase::DateStart if byte == b' ' || byte == b'\t' => IdentPhase::DateStart,
210 IdentPhase::DateStart if byte.is_ascii_digit() => {
211 self.date = u64::from(byte - b'0');
212 self.zero = byte == b'0';
213 IdentPhase::Date
214 }
215 IdentPhase::Date if byte == b' ' => IdentPhase::Sign,
216 IdentPhase::Date if byte.is_ascii_digit() && !self.zero => {
217 self.date = self
218 .date
219 .checked_mul(10)
220 .and_then(|date| date.checked_add(u64::from(byte - b'0')))
221 .filter(|&date| date <= i64::MAX as u64)
222 .ok_or(Error::InvalidObject)?;
223 IdentPhase::Date
224 }
225 IdentPhase::Sign if byte == b'+' || byte == b'-' => IdentPhase::Zone(0),
226 IdentPhase::Zone(digits) if byte.is_ascii_digit() => {
227 if digits == 3 {
228 IdentPhase::End
229 } else {
230 IdentPhase::Zone(digits + 1)
231 }
232 }
233 _ => return Err(Error::InvalidObject),
234 };
235 self.previous = Some(byte);
236 Ok(())
237 }
238 fn finish(&self) -> Result<()> {
239 if matches!(self.phase, IdentPhase::End) {
240 Ok(())
241 } else {
242 Err(Error::InvalidObject)
243 }
244 }
245}