Skip to main content

git_checks/
reject_binaries.rs

1// Copyright Kitware, Inc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use derive_builder::Builder;
10use git_checks_core::impl_prelude::*;
11
12use crate::binary_format;
13
14/// Reject binary files.
15///
16/// This check detects binaries in various formats and rejects content where they are found.
17///
18/// Note that new formats may be detected in the future, so this check may start rejecting commits
19/// which had previously been accepted.
20#[derive(Builder, Debug, Default, Clone, Copy)]
21#[builder(field(private))]
22pub struct RejectBinaries {}
23
24impl RejectBinaries {
25    /// Create a new builder.
26    pub fn builder() -> RejectBinariesBuilder {
27        Default::default()
28    }
29}
30
31impl ContentCheck for RejectBinaries {
32    fn name(&self) -> &str {
33        "reject-binaries"
34    }
35
36    fn check(
37        &self,
38        ctx: &CheckGitContext,
39        content: &dyn Content,
40    ) -> Result<CheckResult, Box<dyn Error>> {
41        let mut result = CheckResult::new();
42
43        for diff in content.diffs() {
44            match diff.status {
45                StatusChange::Added | StatusChange::Modified(_) => (),
46                _ => continue,
47            }
48
49            let binary_attr = ctx.check_attr("hooks-allow-binary", diff.name.as_path())?;
50
51            let allowed_binary_type = match binary_attr {
52                // Set without a value means "any".
53                AttributeState::Set => continue,
54                AttributeState::Value(ref v) => Some(v),
55                // Any other setting means "no binary allowed".
56                _ => None,
57            };
58
59            let binary_type = {
60                let cat_file = ctx
61                    .git()
62                    .arg("cat-file")
63                    .arg("blob")
64                    .arg(diff.new_blob.as_str())
65                    .output()
66                    .map_err(|err| GitError::subcommand("cat-file", err))?;
67
68                let stdout = cat_file.stdout;
69                binary_format::detect_binary_format(stdout)
70            };
71
72            if let Some(binary_type) = binary_type {
73                let type_name = binary_type.name();
74                if let Some(allowed_binary_type) = allowed_binary_type {
75                    if allowed_binary_type != type_name {
76                        result.add_error(format!(
77                            "{}adds the {type_name} (not {allowed_binary_type}) binary `{}`.",
78                            commit_prefix(content),
79                            diff.name,
80                        ));
81                    }
82                } else {
83                    result.add_error(format!(
84                        "{}adds the {type_name} binary `{}`.",
85                        commit_prefix(content),
86                        diff.name,
87                    ));
88                }
89            }
90        }
91
92        Ok(result)
93    }
94}
95
96#[cfg(feature = "config")]
97pub(crate) mod config {
98    use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
99    use serde::Deserialize;
100    #[cfg(test)]
101    use serde_json::json;
102
103    use crate::RejectBinaries;
104
105    /// Configuration for the `RejectBinaries` check.
106    ///
107    /// No configuration available.
108    ///
109    /// This check is registered as a commit check with the name `"reject_binaries"` and a topic
110    /// check with the name `"reject_binaries/topic"`.
111    #[derive(Deserialize, Debug)]
112    pub struct RejectBinariesConfig {}
113
114    impl IntoCheck for RejectBinariesConfig {
115        type Check = RejectBinaries;
116
117        fn into_check(self) -> Self::Check {
118            Default::default()
119        }
120    }
121
122    register_checks! {
123        RejectBinariesConfig {
124            "reject_binaries" => CommitCheckConfig,
125            "reject_binaries/topic" => TopicCheckConfig,
126        },
127    }
128
129    #[test]
130    fn test_reject_binaries_config_empty() {
131        let json = json!({});
132        let check: RejectBinariesConfig = serde_json::from_value(json).unwrap();
133
134        let _ = check.into_check();
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use git_checks_core::{Check, TopicCheck};
141
142    use crate::test::*;
143    use crate::RejectBinaries;
144
145    const BAD_COMMIT: &str = "8ca74bdcc09a1a9e0b44c0b9e6cd8e6af9097c89";
146    const BAD_COMMIT_EXE: &str = "1de03da52b04d5394b75d222f246d3524572bd12";
147    const DELETE_COMMIT: &str = "141bc952382f9e9276e46a58dbb79aa8fe3ea435";
148    const ATTR_COMMIT: &str = "f27e6907d6c0d112e47f63b2b91178c1c23c9f4d";
149    const ATTR_COMMIT_BAD: &str = "2a9649acf5a4af7d1e8e393c11a7ab0c268a9b44";
150    const FIX_ATTR_COMMIT: &str = "1c53216257cb8af8fb0dc7b1577ef63605305318";
151
152    #[test]
153    fn test_reject_binaries_builder_default() {
154        assert!(RejectBinaries::builder().build().is_ok());
155    }
156
157    #[test]
158    fn test_reject_binaries_name_commit() {
159        let check = RejectBinaries::default();
160        assert_eq!(Check::name(&check), "reject-binaries");
161    }
162
163    #[test]
164    fn test_reject_binaries_name_topic() {
165        let check = RejectBinaries::default();
166        assert_eq!(TopicCheck::name(&check), "reject-binaries");
167    }
168
169    #[test]
170    fn test_reject_binaries() {
171        let check = RejectBinaries::default();
172        let result = run_check("test_reject_binaries", BAD_COMMIT, check);
173        test_result_errors(result, &[
174            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the ELF binary `elf-header`.",
175            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-cigam-header`.",
176            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-cigam-header`.",
177            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-magic-header`.",
178            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-magic-header`.",
179            "commit 8326e6bcc8cd6718e367d889bcb64739982b6c66 adds the AR binary `ar-header`.",
180            "commit 8ca74bdcc09a1a9e0b44c0b9e6cd8e6af9097c89 adds the PE binary `pe-le-header`.",
181        ]);
182    }
183
184    #[test]
185    fn test_reject_binaries_plus_x() {
186        let check = RejectBinaries::default();
187        let result = run_check("test_reject_binaries_plus_x", BAD_COMMIT_EXE, check);
188        test_result_errors(result, &[
189            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the ELF binary `elf-header`.",
190            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-cigam-header`.",
191            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-cigam-header`.",
192            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-magic-header`.",
193            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-magic-header`.",
194            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the ELF binary `elf-header`.",
195            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-cigam-header`.",
196            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-fat-cigam-header`.",
197            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-fat-magic-header`.",
198            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-magic-header`.",
199            "commit b8c710757905c59cf880ade2af288b6891b5723c adds the AR binary `ar-header`.",
200            "commit 1de03da52b04d5394b75d222f246d3524572bd12 adds the PE binary `pe-le-header`.",
201        ]);
202    }
203
204    #[test]
205    fn test_reject_binaries_delete_file() {
206        let check = RejectBinaries::default();
207        let conf = make_check_conf(&check);
208
209        let result = test_check_base(
210            "test_reject_binaries_delete_file",
211            DELETE_COMMIT,
212            BAD_COMMIT,
213            &conf,
214        );
215        test_result_ok(result);
216    }
217
218    #[test]
219    fn test_reject_binaries_delete_file_topic() {
220        let check = RejectBinaries::default();
221        run_topic_check_ok(
222            "test_reject_binaries_delete_file_topic",
223            DELETE_COMMIT,
224            check,
225        );
226    }
227
228    #[test]
229    fn test_reject_binaries_attr_ok() {
230        let check = RejectBinaries::default();
231        run_check_ok("test_reject_binaries_attr_ok", ATTR_COMMIT, check)
232    }
233
234    #[test]
235    fn test_reject_binaries_attr_bad() {
236        let check = RejectBinaries::default();
237        let result = run_check("test_reject_binaries_attr_bad", ATTR_COMMIT_BAD, check);
238        test_result_errors(result, &[
239            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the ELF (not Mach-O) binary `elf-header`.",
240            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-cigam-header`.",
241            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-fat-cigam-header`.",
242            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-fat-magic-header`.",
243            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-magic-header`.",
244            "commit 107c49759fe7d603d83264cd2e013054f730f534 adds the AR (not ELF) binary `ar-header`.",
245            "commit 2a9649acf5a4af7d1e8e393c11a7ab0c268a9b44 adds the PE (not ELF) binary `pe-le-header`.",
246        ]);
247    }
248
249    #[test]
250    fn test_reject_binaries_topic() {
251        let check = RejectBinaries::default();
252        let result = run_topic_check("test_reject_binaries_topic", BAD_COMMIT, check);
253        test_result_errors(
254            result,
255            &[
256                "adds the AR binary `ar-header`.",
257                "adds the ELF binary `elf-header`.",
258                "adds the Mach-O binary `macho-cigam-header`.",
259                "adds the Mach-O binary `macho-fat-cigam-header`.",
260                "adds the Mach-O binary `macho-fat-magic-header`.",
261                "adds the Mach-O binary `macho-magic-header`.",
262                "adds the PE binary `pe-le-header`.",
263            ],
264        );
265    }
266
267    #[test]
268    fn test_reject_binaries_topic_attr_ok() {
269        let check = RejectBinaries::default();
270        run_topic_check_ok("test_reject_binaries_topic_attr_ok", ATTR_COMMIT, check)
271    }
272
273    #[test]
274    fn test_reject_binaries_topic_attr_bad() {
275        let check = RejectBinaries::default();
276        let result = run_topic_check(
277            "test_reject_binaries_topic_attr_bad",
278            ATTR_COMMIT_BAD,
279            check,
280        );
281        test_result_errors(
282            result,
283            &[
284                "adds the AR (not ELF) binary `ar-header`.",
285                "adds the ELF (not Mach-O) binary `elf-header`.",
286                "adds the Mach-O (not ELF) binary `macho-cigam-header`.",
287                "adds the Mach-O (not ELF) binary `macho-fat-cigam-header`.",
288                "adds the Mach-O (not ELF) binary `macho-fat-magic-header`.",
289                "adds the Mach-O (not ELF) binary `macho-magic-header`.",
290                "adds the PE (not ELF) binary `pe-le-header`.",
291            ],
292        );
293    }
294
295    #[test]
296    fn test_reject_binaries_topic_plus_x() {
297        let check = RejectBinaries::default();
298        let result = run_topic_check("test_reject_binaries_topic_plus_x", BAD_COMMIT_EXE, check);
299        test_result_errors(
300            result,
301            &[
302                "adds the AR binary `ar-header`.",
303                "adds the ELF binary `elf-header`.",
304                "adds the Mach-O binary `macho-cigam-header`.",
305                "adds the Mach-O binary `macho-fat-cigam-header`.",
306                "adds the Mach-O binary `macho-fat-magic-header`.",
307                "adds the Mach-O binary `macho-magic-header`.",
308                "adds the PE binary `pe-le-header`.",
309            ],
310        );
311    }
312
313    #[test]
314    fn test_reject_binaries_topic_fixed() {
315        let check = RejectBinaries::default();
316        run_topic_check_ok("test_reject_binaries_topic_fixed", DELETE_COMMIT, check);
317    }
318
319    #[test]
320    fn test_reject_binaries_topic_attr_fixed() {
321        let check = RejectBinaries::default();
322        run_topic_check_ok(
323            "test_reject_binaries_topic_attr_fixed",
324            FIX_ATTR_COMMIT,
325            check,
326        );
327    }
328}