gix_config/parse/section/
header.rs1use bstr::{BStr, BString, ByteSlice};
2
3use crate::parse::{Span, section::HeaderData};
4
5#[derive(Debug, PartialOrd, PartialEq, Eq, thiserror::Error)]
7#[expect(missing_docs)]
8pub enum Error {
9 #[error("section names can only be ascii, '-'")]
10 InvalidName,
11 #[error("sub-section names must not contain newlines or null bytes")]
12 InvalidSubSection,
13 #[error(transparent)]
14 Span(#[from] crate::parse::span::Error),
15}
16
17impl HeaderData {
18 pub(crate) fn new_in(
19 name: impl AsRef<str>,
20 subsection: impl Into<Option<BString>>,
21 backing: &mut Vec<u8>,
22 ) -> Result<HeaderData, Error> {
23 let name = validated_name(name.as_ref().as_bytes().as_bstr())?;
24 let name = Span::append(backing, &name)?;
25 let (separator, subsection_name) = match subsection.into() {
26 Some(subsection_name) => {
27 let subsection_name = validated_subsection(subsection_name.as_ref())?;
28 let mut raw = Vec::with_capacity(subsection_name.len());
29 write_escaped_subsection(subsection_name.as_bstr(), &mut raw).expect("writing to memory cannot fail");
30 let raw_span = Span::append(backing, &raw)?;
31 let subsection_name = if raw.as_slice() == subsection_name.as_slice() {
32 crate::parse::MaybeDecoded::raw(raw_span)
33 } else {
34 crate::parse::MaybeDecoded::decoded(raw_span, subsection_name)
35 };
36 (Some(Span::append(backing, b" ")?), Some(subsection_name))
37 }
38 None => (None, None),
39 };
40 Ok(HeaderData {
41 name,
42 separator,
43 subsection_name,
44 })
45 }
46}
47
48pub fn is_valid_subsection(name: impl crate::AsBStr) -> bool {
50 name.as_bstr().find_byteset(b"\n\0").is_none()
51}
52
53fn validated_subsection(name: &BStr) -> Result<BString, Error> {
54 is_valid_subsection(name)
55 .then(|| name.into())
56 .ok_or(Error::InvalidSubSection)
57}
58
59fn validated_name(name: &BStr) -> Result<BString, Error> {
60 name.iter()
61 .all(|b| b.is_ascii_alphanumeric() || *b == b'-')
62 .then(|| name.into())
63 .ok_or(Error::InvalidName)
64}
65
66impl HeaderData {
67 pub(crate) fn rebase(&mut self, offset: usize) -> Result<(), crate::parse::span::Error> {
68 self.name.rebase(offset)?;
69 if let Some(separator) = &mut self.separator {
70 separator.rebase(offset)?;
71 }
72 if let Some(subsection_name) = &mut self.subsection_name {
73 subsection_name.rebase(offset)?;
74 }
75 Ok(())
76 }
77
78 pub(crate) fn copy_to_backing_in(
79 &self,
80 source: &[u8],
81 target: &mut Vec<u8>,
82 ) -> Result<HeaderData, crate::parse::span::Error> {
83 Ok(HeaderData {
84 name: self.name.copy_to_backing_in(source, target)?,
85 separator: self
86 .separator
87 .as_ref()
88 .map(|bytes| bytes.copy_to_backing_in(source, target))
89 .transpose()?,
90 subsection_name: self
91 .subsection_name
92 .as_ref()
93 .map(|name| name.copy_to_backing_in(source, target))
94 .transpose()?,
95 })
96 }
97
98 pub(crate) fn write_to_in(&self, backing: &[u8], mut out: impl std::io::Write) -> std::io::Result<()> {
99 out.write_all(b"[")?;
100 out.write_all(self.name.as_slice_in(backing))?;
101
102 if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) {
103 out.write_all(sep.as_slice_in(backing))?;
104 if sep.as_slice_in(backing) == b"." {
105 out.write_all(subsection.raw_span().as_slice_in(backing))?;
106 } else {
107 out.write_all(b"\"")?;
108 out.write_all(subsection.raw_span().as_slice_in(backing))?;
109 out.write_all(b"\"")?;
110 }
111 }
112
113 out.write_all(b"]")
114 }
115}
116
117pub(crate) fn write_escaped_subsection(name: &BStr, mut out: impl std::io::Write) -> std::io::Result<()> {
118 for b in name.iter().copied() {
119 match b {
120 b'\\' => out.write_all(br"\\")?,
121 b'"' => out.write_all(br#"\""#)?,
122 _ => out.write_all(&[b])?,
123 }
124 }
125 Ok(())
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn empty_header_names_are_legal() {
134 assert!(
135 HeaderData::new_in("", None, &mut Vec::new()).is_ok(),
136 "yes, git allows this, so do we"
137 );
138 }
139
140 #[test]
141 fn empty_header_sub_names_are_legal() {
142 assert!(
143 HeaderData::new_in("remote", Some("".into()), &mut Vec::new()).is_ok(),
144 "yes, git allows this, so do we"
145 );
146 }
147}