1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#![forbid(unsafe_code)]
// #![warn(missing_docs)]
// #![warn(clippy::pedantic, clippy::nursery)]

//! # `git_config`
//!
//! This crate is a high performance `git-config` file reader and writer. It
//! exposes a high level API to parse, read, and write [`git-config` files],
//! which are loosely based on the [INI file format].
//!
//! This crate has a few primary offerings and various accessory functions. The
//! table below gives a brief explanation of all offerings, loosely in order
//! from the highest to lowest abstraction.
//!
//! | Offering      | Description                                         | Zero-copy?        |
//! | ------------- | --------------------------------------------------- | ----------------- |
//! | [`GitConfig`] | Accelerated wrapper for reading and writing values. | On some reads[^1] |
//! | [`Parser`]    | Syntactic event emitter for `git-config` files.     | Yes               |
//! | [`values`]    | Wrappers for `git-config` value types.              | Yes               |
//!
//! This crate also exposes efficient value normalization which unescapes
//! characters and removes quotes through the `normalize_*` family of functions,
//! located in the [`values`] module.
//!
//! # Zero-copy versus zero-alloc
//!
//! We follow [`nom`]'s definition of "zero-copy":
//!
//! > If a parser returns a subset of its input data, it will return a slice of
//! > that input, without copying.
//!
//! Due to the syntax of `git-config`, we must allocate at the parsing level
//! (and thus higher level abstractions must allocate as well) in order to
//! provide a meaningful event stream. That being said, all operations with the
//! parser is still zero-copy. Higher level abstractions may have operations
//! that are zero-copy, but are not guaranteed to do so.
//!
//! However, we intend to be performant as possible, so allocations are
//! limited restricted and we attempt to avoid copying whenever possible.
//!
//! [^1]: When read values do not need normalization.
//!
//! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file
//! [INI file format]: https://en.wikipedia.org/wiki/INI_file
//! [`GitConfig`]: crate::file::GitConfig
//! [`Parser`]: crate::parser::Parser
//! [`values`]: crate::values
//! [`nom`]: https://github.com/Geal/nom

// Cargo.toml cannot have self-referential dependencies, so you can't just
// specify the actual serde crate when you define a feature called serde. We
// instead call the serde crate as serde_crate and then rename the crate to
// serde, to get around this in an intuitive manner.
#[cfg(feature = "serde")]
extern crate serde_crate as serde;

pub mod lookup {

    /// The error when looking up a value.
    #[derive(Debug, thiserror::Error)]
    pub enum Error<E> {
        #[error(transparent)]
        ValueMissing(#[from] crate::lookup::existing::Error),
        #[error(transparent)]
        FailedConversion(E),
    }

    pub mod existing {
        /// The error when looking up a value that doesn't exist.
        #[derive(Debug, thiserror::Error)]
        pub enum Error {
            #[error("The requested section does not exist")]
            SectionMissing,
            #[error("The requested subsection does not exist")]
            SubSectionMissing,
            #[error("The key does not exist in the requested section")]
            KeyMissing,
        }
    }
}

pub mod file;
pub mod fs;
pub mod parser;
pub mod values;
/// The future home of the `values` module (TODO).
pub mod value {
    pub mod parse {
        use bstr::BString;

        /// The error returned when creating `Integer` from byte string.
        #[derive(Debug, thiserror::Error, Eq, PartialEq)]
        #[allow(missing_docs)]
        #[error("Could not decode '{}': {}", .input, .message)]
        pub struct Error {
            pub message: &'static str,
            pub input: BString,
            #[source]
            pub utf8_err: Option<std::str::Utf8Error>,
        }

        impl Error {
            pub(crate) fn new(message: &'static str, input: impl Into<BString>) -> Self {
                Error {
                    message,
                    input: input.into(),
                    utf8_err: None,
                }
            }

            pub(crate) fn with_err(mut self, err: std::str::Utf8Error) -> Self {
                self.utf8_err = Some(err);
                self
            }
        }
    }
}

mod permissions {
    use crate::Permissions;

    impl Permissions {
        /// Allow everything which usually relates to a fully trusted environment
        pub fn all() -> Self {
            use git_sec::Permission::*;
            Permissions {
                system: Allow,
                global: Allow,
                user: Allow,
                repository: Allow,
                worktree: Allow,
                env: Allow,
                includes: Allow,
            }
        }

        /// If in doubt, this configuration can be used to safely load configuration from sources which is usually trusted,
        /// that is system and user configuration. Do load any configuration that isn't trusted as it's now owned by the current user.
        pub fn secure() -> Self {
            use git_sec::Permission::*;
            Permissions {
                system: Allow,
                global: Allow,
                user: Allow,
                repository: Deny,
                worktree: Deny,
                env: Allow,
                includes: Deny,
            }
        }
    }
}

/// Configure security relevant options when loading a git configuration.
#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Permissions {
    /// How to use the system configuration.
    /// This is defined as `$(prefix)/etc/gitconfig` on unix.
    pub system: git_sec::Permission,
    /// How to use the global configuration.
    /// This is usually `~/.gitconfig`.
    pub global: git_sec::Permission,
    /// How to use the user configuration.
    /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not
    /// set or empty, `$HOME/.config/git/config` will be used.
    pub user: git_sec::Permission,
    /// How to use the repository configuration.
    pub repository: git_sec::Permission,
    /// How to use worktree configuration from `config.worktree`.
    // TODO: figure out how this really applies and provide more information here.
    pub worktree: git_sec::Permission,
    /// How to use the configuration from environment variables.
    pub env: git_sec::Permission,
    /// What to do when include files are encountered in loaded configuration.
    pub includes: git_sec::Permission,
}

#[cfg(test)]
pub mod test_util;