Skip to main content

git_zero_server/receive/
headers.rs

1//! Streaming identity/header checks used by strict receive hosting.
2use 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
25/// Additional fsck header policy, independent of structural link decoding.
26/// Feed exactly the canonical bytes accepted by the link decoder, then finish.
27/// Identity names/emails can be arbitrarily long; this retains only ten prefix
28/// bytes and a fixed-size identity state. Missing historical taggers are allowed.
29/// This is not a complete fsck implementation; tree and designated-blob policy
30/// must be checked separately by the host before publication.
31pub 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}