Skip to content

git-config: secure access to paths and other values #460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 22 additions & 31 deletions git-config/src/file/access/mutate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ impl<'event> File<'event> {
.rev()
.next()
.expect("BUG: Section lookup vec was empty");
Ok(SectionMut::new(
self.sections
.get_mut(&id)
.expect("BUG: Section did not have id from lookup"),
))
let nl = self.detect_newline_style_smallvec();
Ok(self
.sections
.get_mut(&id)
.expect("BUG: Section did not have id from lookup")
.to_mut(nl))
}

/// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`.
Expand All @@ -48,7 +49,8 @@ impl<'event> File<'event> {
let s = &self.sections[id];
filter(s.meta())
});
Ok(id.and_then(move |id| self.sections.get_mut(&id).map(|s| s.to_mut())))
let nl = self.detect_newline_style_smallvec();
Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl))))
}

/// Adds a new section. If a subsection name was provided, then
Expand All @@ -63,8 +65,10 @@ impl<'event> File<'event> {
/// # use git_config::File;
/// # use std::convert::TryFrom;
/// let mut git_config = git_config::File::default();
/// let _section = git_config.new_section("hello", Some("world".into()));
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n");
/// let section = git_config.new_section("hello", Some("world".into()))?;
/// let nl = section.newline().to_owned();
/// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}"));
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Creating a new empty section and adding values to it:
Expand All @@ -77,18 +81,20 @@ impl<'event> File<'event> {
/// let mut git_config = git_config::File::default();
/// let mut section = git_config.new_section("hello", Some("world".into()))?;
/// section.push(section::Key::try_from("a")?, "b");
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n");
/// let nl = section.newline().to_owned();
/// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}"));
/// let _section = git_config.new_section("core", None);
/// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n[core]\n");
/// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}[core]{nl}"));
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn new_section(
&mut self,
name: impl Into<Cow<'event, str>>,
subsection: impl Into<Option<Cow<'event, str>>>,
) -> Result<SectionMut<'_, 'event>, section::header::Error> {
let mut section =
self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?);
let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?);
let nl = self.detect_newline_style_smallvec();
let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
section.push_newline();
Ok(section)
}
Expand Down Expand Up @@ -180,7 +186,10 @@ impl<'event> File<'event> {
&mut self,
section: file::Section<'event>,
) -> Result<SectionMut<'_, 'event>, section::header::Error> {
Ok(self.push_section_internal(section))
let id = self.push_section_internal(section);
let nl = self.detect_newline_style_smallvec();
let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
Ok(section)
}

/// Renames the section with `name` and `subsection_name`, modifying the last matching section
Expand Down Expand Up @@ -290,22 +299,4 @@ impl<'event> File<'event> {
}
self
}

fn detect_newline_style(&self) -> &BStr {
fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> {
match e {
Event::Newline(b) => b.as_ref().into(),
_ => None,
}
}

self.frontmatter_events
.iter()
.find_map(extract_newline)
.or_else(|| {
self.sections()
.find_map(|s| s.body.as_ref().iter().find_map(extract_newline))
})
.unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into())
}
}
6 changes: 4 additions & 2 deletions git-config/src/file/access/raw.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::{borrow::Cow, collections::HashMap};

use bstr::BStr;
use smallvec::ToSmallVec;

use crate::file::MetadataFilter;
use crate::{
file::{mutable::multi_value::EntryData, Index, MultiValueMut, SectionMut, Size, ValueMut},
file::{mutable::multi_value::EntryData, Index, MultiValueMut, Size, ValueMut},
lookup,
parse::{section, Event},
File,
Expand Down Expand Up @@ -120,8 +121,9 @@ impl<'event> File<'event> {
}

drop(section_ids);
let nl = self.detect_newline_style().to_smallvec();
return Ok(ValueMut {
section: SectionMut::new(self.sections.get_mut(&section_id).expect("known section-id")),
section: self.sections.get_mut(&section_id).expect("known section-id").to_mut(nl),
key,
index: Index(index),
size: Size(size),
Expand Down
35 changes: 31 additions & 4 deletions git-config/src/file/access/read_only.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::iter::FromIterator;
use std::{borrow::Cow, convert::TryFrom};

use bstr::BStr;
use git_features::threading::OwnShared;
use smallvec::SmallVec;

use crate::file::{Metadata, MetadataFilter};
use crate::parse::Event;
use crate::{file, lookup, File};

/// Read-only low-level access methods, as it requires generics for converting into
Expand Down Expand Up @@ -254,9 +257,7 @@ impl<'event> File<'event> {
///
/// This allows to reproduce the look of sections perfectly when serializing them with
/// [`write_to()`][file::Section::write_to()].
pub fn sections_and_postmatter(
&self,
) -> impl Iterator<Item = (&file::Section<'event>, Vec<&crate::parse::Event<'event>>)> {
pub fn sections_and_postmatter(&self) -> impl Iterator<Item = (&file::Section<'event>, Vec<&Event<'event>>)> {
self.section_order.iter().map(move |id| {
let s = &self.sections[id];
let pm: Vec<_> = self
Expand All @@ -269,7 +270,33 @@ impl<'event> File<'event> {
}

/// Return all events which are in front of the first of our sections, or `None` if there are none.
pub fn frontmatter(&self) -> Option<impl Iterator<Item = &crate::parse::Event<'event>>> {
pub fn frontmatter(&self) -> Option<impl Iterator<Item = &Event<'event>>> {
(!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter())
}

/// Return the newline characters that have been detected in this config file or the default ones
/// for the current platform.
///
/// Note that the first found newline is the one we use in the assumption of consistency.
pub fn detect_newline_style(&self) -> &BStr {
fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> {
match e {
Event::Newline(b) => b.as_ref().into(),
_ => None,
}
}

self.frontmatter_events
.iter()
.find_map(extract_newline)
.or_else(|| {
self.sections()
.find_map(|s| s.body.as_ref().iter().find_map(extract_newline))
})
.unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into())
}

pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> {
SmallVec::from_iter(self.detect_newline_style().iter().copied())
}
}
42 changes: 16 additions & 26 deletions git-config/src/file/init/from_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::convert::TryFrom;
use std::{borrow::Cow, path::PathBuf};

use crate::file::{init, Metadata};
use crate::{file, parse::section, path::interpolate, File, Source};
use crate::{file, parse, parse::section, path::interpolate, File, Source};

/// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()].
#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -129,33 +129,23 @@ impl File<'static> {
for i in 0..count {
let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?;
let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?;
match key.split_once('.') {
Some((section_name, maybe_subsection)) => {
let (subsection, key) = match maybe_subsection.rsplit_once('.') {
Some((subsection, key)) => (Some(subsection), key),
None => (None, maybe_subsection),
};
let key = parse::key(&key).ok_or_else(|| Error::InvalidKeyValue {
key_id: i,
key_val: key.to_string(),
})?;

let mut section = match config.section_mut(section_name, subsection) {
Ok(section) => section,
Err(_) => config.new_section(
section_name.to_string(),
subsection.map(|subsection| Cow::Owned(subsection.to_string())),
)?,
};
let mut section = match config.section_mut(key.section_name, key.subsection_name) {
Ok(section) => section,
Err(_) => config.new_section(
key.section_name.to_owned(),
key.subsection_name.map(|subsection| Cow::Owned(subsection.to_owned())),
)?,
};

section.push(
section::Key::try_from(key.to_owned())?,
git_path::os_str_into_bstr(&value).expect("no illformed UTF-8").as_ref(),
);
}
None => {
return Err(Error::InvalidKeyValue {
key_id: i,
key_val: key.to_string(),
})
}
}
section.push(
section::Key::try_from(key.value_name.to_owned())?,
git_path::os_str_into_bstr(&value).expect("no illformed UTF-8").as_ref(),
);
}

let mut buf = Vec::new();
Expand Down
4 changes: 1 addition & 3 deletions git-config/src/file/mutable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,8 @@ impl<'a> Whitespace<'a> {
}
out
}
}

impl<'a> From<&file::section::Body<'a>> for Whitespace<'a> {
fn from(s: &file::section::Body<'a>) -> Self {
fn from_body(s: &file::section::Body<'a>) -> Self {
let key_pos =
s.0.iter()
.enumerate()
Expand Down
2 changes: 1 addition & 1 deletion git-config/src/file/mutable/multi_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> {
value: &BStr,
) {
let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index);
let whitespace: Whitespace<'_> = (&*section).into();
let whitespace = Whitespace::from_body(section);
let section = section.as_mut();
section.drain(offset..offset + size);

Expand Down
23 changes: 17 additions & 6 deletions git-config/src/file/mutable/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::{
ops::{Deref, Range},
};

use bstr::{BStr, BString, ByteVec};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use smallvec::SmallVec;

use crate::file::{self, Section};
use crate::{
Expand All @@ -22,6 +23,7 @@ pub struct SectionMut<'a, 'event> {
section: &'a mut Section<'event>,
implicit_newline: bool,
whitespace: Whitespace<'event>,
newline: SmallVec<[u8; 2]>,
}

/// Mutating methods.
Expand All @@ -37,7 +39,7 @@ impl<'a, 'event> SectionMut<'a, 'event> {
body.extend(self.whitespace.key_value_separators());
body.push(Event::Value(escape_value(value.into()).into()));
if self.implicit_newline {
body.push(Event::Newline(BString::from("\n").into()));
body.push(Event::Newline(BString::from(self.newline.to_vec()).into()));
}
}

Expand Down Expand Up @@ -109,7 +111,15 @@ impl<'a, 'event> SectionMut<'a, 'event> {
/// Adds a new line event. Note that you don't need to call this unless
/// you've disabled implicit newlines.
pub fn push_newline(&mut self) {
self.section.body.0.push(Event::Newline(Cow::Borrowed("\n".into())));
self.section
.body
.0
.push(Event::Newline(Cow::Owned(BString::from(self.newline.to_vec()))));
}

/// Return the newline used when calling [`push_newline()`][Self::push_newline()].
pub fn newline(&self) -> &BStr {
self.newline.as_slice().as_bstr()
}

/// Enables or disables automatically adding newline events after adding
Expand Down Expand Up @@ -158,12 +168,13 @@ impl<'a, 'event> SectionMut<'a, 'event> {

// Internal methods that may require exact indices for faster operations.
impl<'a, 'event> SectionMut<'a, 'event> {
pub(crate) fn new(section: &'a mut Section<'event>) -> Self {
let whitespace = (&section.body).into();
pub(crate) fn new(section: &'a mut Section<'event>, newline: SmallVec<[u8; 2]>) -> Self {
let whitespace = Whitespace::from_body(&section.body);
Self {
section,
implicit_newline: true,
whitespace,
newline,
}
}

Expand Down Expand Up @@ -231,7 +242,7 @@ impl<'a, 'event> SectionMut<'a, 'event> {
}

impl<'event> Deref for SectionMut<'_, 'event> {
type Target = file::section::Body<'event>;
type Target = file::Section<'event>;

fn deref(&self) -> &Self::Target {
self.section
Expand Down
11 changes: 11 additions & 0 deletions git-config/src/file/mutable/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::borrow::Cow;
use bstr::BStr;

use crate::{
file,
file::{mutable::section::SectionMut, Index, Size},
lookup,
parse::section,
Expand Down Expand Up @@ -49,4 +50,14 @@ impl<'borrow, 'lookup, 'event> ValueMut<'borrow, 'lookup, 'event> {
self.size = Size(0);
}
}

/// Return the section containing the value.
pub fn section(&self) -> &file::Section<'event> {
&self.section
}

/// Convert this value into its owning mutable section.
pub fn into_section_mut(self) -> file::SectionMut<'borrow, 'event> {
self.section
}
}
5 changes: 3 additions & 2 deletions git-config/src/file/section/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::file::{Metadata, Section, SectionMut};
use crate::parse::section;
use crate::{file, parse};
use bstr::BString;
use smallvec::SmallVec;
use std::borrow::Cow;
use std::ops::Deref;

Expand Down Expand Up @@ -71,7 +72,7 @@ impl<'a> Section<'a> {
}

/// Returns a mutable version of this section for adjustment of values.
pub fn to_mut(&mut self) -> SectionMut<'_, 'a> {
SectionMut::new(self)
pub fn to_mut(&mut self, newline: SmallVec<[u8; 2]>) -> SectionMut<'_, 'a> {
SectionMut::new(self, newline)
}
}
Loading