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