grit_lib/objects/
extra_headers.rs1#[must_use]
12pub fn next_header_block(extra: &[u8], start: usize) -> (&[u8], usize, bool) {
13 let pos = start;
14 let line_start = pos;
15 let mut line_end = pos;
16 while line_end < extra.len() && extra[line_end] != b'\n' {
17 line_end += 1;
18 }
19 let after_nl = (line_end + 1).min(extra.len());
20 let line = &extra[line_start..line_end];
21 if line.first() == Some(&b' ') {
22 return (b"", after_nl, true);
23 }
24 let key_end = line.iter().position(|&b| b == b' ').unwrap_or(line.len());
25 let key = &line[..key_end];
26 let mut end = after_nl;
27 while end < extra.len() {
28 let ls = end;
29 let mut le = end;
30 while le < extra.len() && extra[le] != b'\n' {
31 le += 1;
32 }
33 let an = (le + 1).min(extra.len());
34 if extra.get(ls) == Some(&b' ') {
35 end = an;
36 } else {
37 break;
38 }
39 }
40 (key, end, false)
41}
42
43#[derive(Debug, Clone)]
45pub struct ExtraHeaderIter<'a> {
46 extra: &'a [u8],
47 pos: usize,
48}
49
50impl<'a> ExtraHeaderIter<'a> {
51 #[must_use]
53 pub fn new(extra: &'a [u8]) -> Self {
54 Self { extra, pos: 0 }
55 }
56}
57
58#[must_use]
60pub fn iter_extra_headers(extra: &[u8]) -> ExtraHeaderIter<'_> {
61 ExtraHeaderIter::new(extra)
62}
63
64impl<'a> Iterator for ExtraHeaderIter<'a> {
65 type Item = (&'a [u8], &'a [u8]);
66
67 fn next(&mut self) -> Option<Self::Item> {
68 if self.pos >= self.extra.len() {
69 return None;
70 }
71 let line_start = self.pos;
72 let mut line_end = self.pos;
73 while line_end < self.extra.len() && self.extra[line_end] != b'\n' {
74 line_end += 1;
75 }
76 if self.extra[line_start..line_end].first() == Some(&b' ') {
77 self.pos = (line_end + 1).min(self.extra.len());
78 return self.next();
79 }
80 let key_end = self.extra[line_start..line_end]
81 .iter()
82 .position(|&b| b == b' ')
83 .unwrap_or(line_end - line_start);
84 let key = &self.extra[line_start..line_start + key_end];
85 let (_, block_end, _) = next_header_block(self.extra, line_start);
86 let value_start = line_start
87 + key_end
88 + if key_end < line_end - line_start {
89 1
90 } else {
91 0
92 };
93 let mut value_end = block_end;
94 if value_end > value_start && self.extra[value_end - 1] == b'\n' {
95 value_end -= 1;
96 }
97 let value = &self.extra[value_start..value_end];
98 self.pos = block_end;
99 Some((key, value))
100 }
101}
102
103#[must_use]
105pub fn extra_header_values<'a>(extra: &'a [u8], name: &str) -> Vec<&'a [u8]> {
106 let name = name.as_bytes();
107 ExtraHeaderIter::new(extra)
108 .filter(|(k, _)| *k == name)
109 .map(|(_, v)| v)
110 .collect()
111}
112
113#[must_use]
115pub fn remove_header_lines(extra: &[u8], drop_key: impl Fn(&[u8]) -> bool) -> Vec<u8> {
116 let mut out = Vec::new();
117 let mut pos = 0usize;
118 while pos < extra.len() {
119 let (key, block_end, _) = next_header_block(extra, pos);
120 if drop_key(key) {
121 pos = block_end;
122 continue;
123 }
124 out.extend_from_slice(&extra[pos..block_end]);
125 pos = block_end;
126 }
127 out
128}
129
130pub fn set_extra_header(extra: &mut Vec<u8>, name: &str, line: &[u8]) {
136 let name_bytes = name.as_bytes();
137 *extra = remove_header_lines(extra, |key| key == name_bytes);
138 extra.extend_from_slice(line);
139}
140
141#[must_use]
144pub fn insert_header_before_signature(
145 extra: &[u8],
146 line: &[u8],
147 drop_key: impl Fn(&[u8]) -> bool,
148) -> Vec<u8> {
149 let mut out = Vec::new();
150 let mut pos = 0usize;
151 let mut inserted = false;
152 while pos < extra.len() {
153 let (key, block_end, _) = next_header_block(extra, pos);
154 if !inserted && (key == b"gpgsig" || key == b"gpgsig-sha256") {
155 out.extend_from_slice(line);
156 inserted = true;
157 }
158 if drop_key(key) {
159 pos = block_end;
160 continue;
161 }
162 out.extend_from_slice(&extra[pos..block_end]);
163 pos = block_end;
164 }
165 if !inserted {
166 out.extend_from_slice(line);
167 }
168 out
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn iter_multivalued_and_continuation() {
177 let raw = b"foo one\nfoo two\nbar multi\n line\n";
178 let pairs: Vec<_> = ExtraHeaderIter::new(raw).collect();
179 assert_eq!(pairs.len(), 3);
180 assert_eq!(pairs[0], (b"foo".as_slice(), b"one".as_slice()));
181 assert_eq!(pairs[1], (b"foo".as_slice(), b"two".as_slice()));
182 assert_eq!(pairs[2], (b"bar".as_slice(), b"multi\n line".as_slice()));
183 assert_eq!(
184 extra_header_values(raw, "foo"),
185 vec![b"one".as_slice(), b"two".as_slice()]
186 );
187 }
188
189 #[test]
190 fn set_replaces_all_same_name() {
191 let mut extra = b"foo old\nbar keep\nfoo also\n".to_vec();
192 set_extra_header(&mut extra, "foo", b"foo new\n");
193 assert_eq!(extra, b"bar keep\nfoo new\n");
194 }
195
196 #[test]
197 fn insert_before_gpgsig_drops_matching() {
198 let extra = b"change-id old\nencoding UTF-8\n";
199 let out = insert_header_before_signature(extra, b"change-id new\n", |key| {
200 key == b"change-id" || key == b"gitbutler-change-id"
201 });
202 assert_eq!(out, b"encoding UTF-8\nchange-id new\n");
203
204 let signed = b"change-id x\nencoding UTF-8\ngpgsig sig\n line\n";
205 let out2 =
206 insert_header_before_signature(signed, b"change-id y\n", |key| key == b"change-id");
207 assert_eq!(out2, b"encoding UTF-8\nchange-id y\ngpgsig sig\n line\n");
208 }
209}