Skip to main content

grit_lib/
upload_filter.rs

1//! Upload-pack object filter policy.
2
3use crate::config::ConfigSet;
4use crate::rev_list::ObjectFilter;
5use thiserror::Error;
6
7/// Error returned when an upload-pack filter request is not allowed by config.
8#[derive(Debug, Error)]
9pub enum UploadFilterError {
10    /// `uploadpackfilter.tree.maxDepth` exists but is not a non-negative integer.
11    #[error("unable to parse uploadpackfilter.tree.maxdepth")]
12    InvalidTreeMaxDepth,
13    /// A requested filter kind is disabled by `uploadpackfilter.*.allow`.
14    #[error("filter '{0}' not supported")]
15    UnsupportedFilter(String),
16    /// A `tree:<depth>` request exceeds `uploadpackfilter.tree.maxDepth`.
17    #[error("tree filter allows max depth {max}, but got {actual}")]
18    TreeDepthTooLarge {
19        /// Maximum tree depth allowed by repository config.
20        max: u64,
21        /// Requested tree filter depth.
22        actual: u64,
23    },
24    /// The filter specification could not be parsed.
25    #[error("{0}")]
26    InvalidFilterSpec(String),
27}
28
29/// Validate upload-pack filter-related config that must be well-formed even
30/// before a client asks for a filter.
31///
32/// # Errors
33///
34/// Returns [`UploadFilterError::InvalidTreeMaxDepth`] when
35/// `uploadpackfilter.tree.maxDepth` is set to a non-integer value.
36pub fn validate_upload_filter_config(config: &ConfigSet) -> Result<(), UploadFilterError> {
37    tree_max_depth(config).map(|_| ())
38}
39
40/// Validate a requested upload-pack object filter against repository config.
41///
42/// # Errors
43///
44/// Returns an error if the filter cannot be parsed, is disabled by
45/// `uploadpackfilter.*.allow`, or exceeds `uploadpackfilter.tree.maxDepth`.
46pub fn validate_upload_filter_request(
47    config: &ConfigSet,
48    spec: &str,
49) -> Result<(), UploadFilterError> {
50    let filter = ObjectFilter::parse(spec).map_err(UploadFilterError::InvalidFilterSpec)?;
51    validate_filter(config, &filter)
52}
53
54fn tree_max_depth(config: &ConfigSet) -> Result<Option<u64>, UploadFilterError> {
55    config
56        .get("uploadpackfilter.tree.maxdepth")
57        .map(|value| {
58            value
59                .trim()
60                .parse::<u64>()
61                .map_err(|_| UploadFilterError::InvalidTreeMaxDepth)
62        })
63        .transpose()
64}
65
66fn validate_filter(config: &ConfigSet, filter: &ObjectFilter) -> Result<(), UploadFilterError> {
67    match filter {
68        ObjectFilter::BlobNone => ensure_filter_allowed(config, "blob:none", "blob:none"),
69        ObjectFilter::BlobLimit(_) => ensure_filter_allowed(config, "blob:limit", "blob:limit"),
70        ObjectFilter::TreeDepth(depth) => {
71            ensure_filter_allowed(config, "tree", "tree")?;
72            if let Some(max) = tree_max_depth(config)? {
73                if *depth > max {
74                    return Err(UploadFilterError::TreeDepthTooLarge {
75                        max,
76                        actual: *depth,
77                    });
78                }
79            }
80            Ok(())
81        }
82        ObjectFilter::SparseOid(_) => ensure_filter_allowed(config, "sparse:oid", "sparse:oid"),
83        ObjectFilter::ObjectType(_) => ensure_filter_allowed(config, "object:type", "object:type"),
84        ObjectFilter::Combine(filters) => {
85            ensure_filter_allowed(config, "combine", "combine")?;
86            for filter in filters {
87                validate_filter(config, filter)?;
88            }
89            Ok(())
90        }
91    }
92}
93
94fn ensure_filter_allowed(
95    config: &ConfigSet,
96    config_kind: &str,
97    display_kind: &str,
98) -> Result<(), UploadFilterError> {
99    let key = format!("uploadpackfilter.{config_kind}.allow");
100    if let Some(value) = config.get_bool(&key) {
101        if value.unwrap_or(false) {
102            return Ok(());
103        }
104        return Err(UploadFilterError::UnsupportedFilter(
105            display_kind.to_owned(),
106        ));
107    }
108
109    if config
110        .get_bool("uploadpackfilter.allow")
111        .map(|value| value.unwrap_or(false))
112        .unwrap_or(true)
113    {
114        Ok(())
115    } else {
116        Err(UploadFilterError::UnsupportedFilter(
117            display_kind.to_owned(),
118        ))
119    }
120}