147 lines · 4.8 KB · Rust
RawDownload
use bstr::{BStr, BString, ByteSlice}; use crate::parse::{Span, section::HeaderData}; /// The error returned when creating a section header.#[derive(Debug, PartialOrd, PartialEq, Eq, thiserror::Error)]#[expect(missing_docs)]pub enum Error {    #[error("section names can only be ascii, '-'")]    InvalidName,    #[error("sub-section names must not contain newlines or null bytes")]    InvalidSubSection,    #[error(transparent)]    Span(#[from] crate::parse::span::Error),} impl HeaderData {    pub(crate) fn new_in(        name: impl AsRef<str>,        subsection: impl Into<Option<BString>>,        backing: &mut Vec<u8>,    ) -> Result<HeaderData, Error> {        let name = validated_name(name.as_ref().as_bytes().as_bstr())?;        let name = Span::append(backing, &name)?;        let (separator, subsection_name) = match subsection.into() {            Some(subsection_name) => {                let subsection_name = validated_subsection(subsection_name.as_ref())?;                let mut raw = Vec::with_capacity(subsection_name.len());                write_escaped_subsection(subsection_name.as_bstr(), &mut raw).expect("writing to memory cannot fail");                let raw_span = Span::append(backing, &raw)?;                let subsection_name = if raw.as_slice() == subsection_name.as_slice() {                    crate::parse::MaybeDecoded::raw(raw_span)                } else {                    crate::parse::MaybeDecoded::decoded(raw_span, subsection_name)                };                (Some(Span::append(backing, b" ")?), Some(subsection_name))            }            None => (None, None),        };        Ok(HeaderData {            name,            separator,            subsection_name,        })    }} /// Return true if `name` is valid as subsection name, like `origin` in `[remote "origin"]`.pub fn is_valid_subsection(name: impl crate::AsBStr) -> bool {    name.as_bstr().find_byteset(b"\n\0").is_none()} fn validated_subsection(name: &BStr) -> Result<BString, Error> {    is_valid_subsection(name)        .then(|| name.into())        .ok_or(Error::InvalidSubSection)} fn validated_name(name: &BStr) -> Result<BString, Error> {    name.iter()        .all(|b| b.is_ascii_alphanumeric() || *b == b'-')        .then(|| name.into())        .ok_or(Error::InvalidName)} impl HeaderData {    pub(crate) fn rebase(&mut self, offset: usize) -> Result<(), crate::parse::span::Error> {        self.name.rebase(offset)?;        if let Some(separator) = &mut self.separator {            separator.rebase(offset)?;        }        if let Some(subsection_name) = &mut self.subsection_name {            subsection_name.rebase(offset)?;        }        Ok(())    }     pub(crate) fn copy_to_backing_in(        &self,        source: &[u8],        target: &mut Vec<u8>,    ) -> Result<HeaderData, crate::parse::span::Error> {        Ok(HeaderData {            name: self.name.copy_to_backing_in(source, target)?,            separator: self                .separator                .as_ref()                .map(|bytes| bytes.copy_to_backing_in(source, target))                .transpose()?,            subsection_name: self                .subsection_name                .as_ref()                .map(|name| name.copy_to_backing_in(source, target))                .transpose()?,        })    }     pub(crate) fn write_to_in(&self, backing: &[u8], mut out: impl std::io::Write) -> std::io::Result<()> {        out.write_all(b"[")?;        out.write_all(self.name.as_slice_in(backing))?;         if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) {            out.write_all(sep.as_slice_in(backing))?;            if sep.as_slice_in(backing) == b"." {                out.write_all(subsection.raw_span().as_slice_in(backing))?;            } else {                out.write_all(b"\"")?;                out.write_all(subsection.raw_span().as_slice_in(backing))?;                out.write_all(b"\"")?;            }        }         out.write_all(b"]")    }} pub(crate) fn write_escaped_subsection(name: &BStr, mut out: impl std::io::Write) -> std::io::Result<()> {    for b in name.iter().copied() {        match b {            b'\\' => out.write_all(br"\\")?,            b'"' => out.write_all(br#"\""#)?,            _ => out.write_all(&[b])?,        }    }    Ok(())} #[cfg(test)]mod tests {    use super::*;     #[test]    fn empty_header_names_are_legal() {        assert!(            HeaderData::new_in("", None, &mut Vec::new()).is_ok(),            "yes, git allows this, so do we"        );    }     #[test]    fn empty_header_sub_names_are_legal() {        assert!(            HeaderData::new_in("remote", Some("".into()), &mut Vec::new()).is_ok(),            "yes, git allows this, so do we"        );    }}