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 {} (not {}) binary `{}`.",
78                            commit_prefix(content),
79                            type_name,
80                            allowed_binary_type,
81                            diff.name,
82                        ));
83                    }
84                } else {
85                    result.add_error(format!(
86                        "{}adds the {} binary `{}`.",
87                        commit_prefix(content),
88                        type_name,
89                        diff.name,
90                    ));
91                }
92            }
93        }
94
95        Ok(result)
96    }
97}
98
99#[cfg(feature = "config")]
100pub(crate) mod config {
101    use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
102    use serde::Deserialize;
103    #[cfg(test)]
104    use serde_json::json;
105
106    use crate::RejectBinaries;
107
108    /// Configuration for the `RejectBinaries` check.
109    ///
110    /// No configuration available.
111    ///
112    /// This check is registered as a commit check with the name `"reject_binaries"` and a topic
113    /// check with the name `"reject_binaries/topic"`.
114    #[derive(Deserialize, Debug)]
115    pub struct RejectBinariesConfig {}
116
117    impl IntoCheck for RejectBinariesConfig {
118        type Check = RejectBinaries;
119
120        fn into_check(self) -> Self::Check {
121            Default::default()
122        }
123    }
124
125    register_checks! {
126        RejectBinariesConfig {
127            "reject_binaries" => CommitCheckConfig,
128            "reject_binaries/topic" => TopicCheckConfig,
129        },
130    }
131
132    #[test]
133    fn test_reject_binaries_config_empty() {
134        let json = json!({});
135        let check: RejectBinariesConfig = serde_json::from_value(json).unwrap();
136
137        let _ = check.into_check();
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use git_checks_core::{Check, TopicCheck};
144
145    use crate::test::*;
146    use crate::RejectBinaries;
147
148    const BAD_COMMIT: &str = "8ca74bdcc09a1a9e0b44c0b9e6cd8e6af9097c89";
149    const BAD_COMMIT_EXE: &str = "1de03da52b04d5394b75d222f246d3524572bd12";
150    const DELETE_COMMIT: &str = "141bc952382f9e9276e46a58dbb79aa8fe3ea435";
151    const ATTR_COMMIT: &str = "f27e6907d6c0d112e47f63b2b91178c1c23c9f4d";
152    const ATTR_COMMIT_BAD: &str = "2a9649acf5a4af7d1e8e393c11a7ab0c268a9b44";
153    const FIX_ATTR_COMMIT: &str = "1c53216257cb8af8fb0dc7b1577ef63605305318";
154
155    #[test]
156    fn test_reject_binaries_builder_default() {
157        assert!(RejectBinaries::builder().build().is_ok());
158    }
159
160    #[test]
161    fn test_reject_binaries_name_commit() {
162        let check = RejectBinaries::default();
163        assert_eq!(Check::name(&check), "reject-binaries");
164    }
165
166    #[test]
167    fn test_reject_binaries_name_topic() {
168        let check = RejectBinaries::default();
169        assert_eq!(TopicCheck::name(&check), "reject-binaries");
170    }
171
172    #[test]
173    fn test_reject_binaries() {
174        let check = RejectBinaries::default();
175        let result = run_check("test_reject_binaries", BAD_COMMIT, check);
176        test_result_errors(result, &[
177            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the ELF binary `elf-header`.",
178            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-cigam-header`.",
179            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-cigam-header`.",
180            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-magic-header`.",
181            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-magic-header`.",
182            "commit 8326e6bcc8cd6718e367d889bcb64739982b6c66 adds the AR binary `ar-header`.",
183            "commit 8ca74bdcc09a1a9e0b44c0b9e6cd8e6af9097c89 adds the PE binary `pe-le-header`.",
184        ]);
185    }
186
187    #[test]
188    fn test_reject_binaries_plus_x() {
189        let check = RejectBinaries::default();
190        let result = run_check("test_reject_binaries_plus_x", BAD_COMMIT_EXE, check);
191        test_result_errors(result, &[
192            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the ELF binary `elf-header`.",
193            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-cigam-header`.",
194            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-cigam-header`.",
195            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-magic-header`.",
196            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-magic-header`.",
197            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the ELF binary `elf-header`.",
198            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-cigam-header`.",
199            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-fat-cigam-header`.",
200            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-fat-magic-header`.",
201            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-magic-header`.",
202            "commit b8c710757905c59cf880ade2af288b6891b5723c adds the AR binary `ar-header`.",
203            "commit 1de03da52b04d5394b75d222f246d3524572bd12 adds the PE binary `pe-le-header`.",
204        ]);
205    }
206
207    #[test]
208    fn test_reject_binaries_delete_file() {
209        let check = RejectBinaries::default();
210        let conf = make_check_conf(&check);
211
212        let result = test_check_base(
213            "test_reject_binaries_delete_file",
214            DELETE_COMMIT,
215            BAD_COMMIT,
216            &conf,
217        );
218        test_result_ok(result);
219    }
220
221    #[test]
222    fn test_reject_binaries_delete_file_topic() {
223        let check = RejectBinaries::default();
224        run_topic_check_ok(
225            "test_reject_binaries_delete_file_topic",
226            DELETE_COMMIT,
227            check,
228        );
229    }
230
231    #[test]
232    fn test_reject_binaries_attr_ok() {
233        let check = RejectBinaries::default();
234        run_check_ok("test_reject_binaries_attr_ok", ATTR_COMMIT, check)
235    }
236
237    #[test]
238    fn test_reject_binaries_attr_bad() {
239        let check = RejectBinaries::default();
240        let result = run_check("test_reject_binaries_attr_bad", ATTR_COMMIT_BAD, check);
241        test_result_errors(result, &[
242            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the ELF (not Mach-O) binary `elf-header`.",
243            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-cigam-header`.",
244            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-fat-cigam-header`.",
245            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-fat-magic-header`.",
246            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-magic-header`.",
247            "commit 107c49759fe7d603d83264cd2e013054f730f534 adds the AR (not ELF) binary `ar-header`.",
248            "commit 2a9649acf5a4af7d1e8e393c11a7ab0c268a9b44 adds the PE (not ELF) binary `pe-le-header`.",
249        ]);
250    }
251
252    #[test]
253    fn test_reject_binaries_topic() {
254        let check = RejectBinaries::default();
255        let result = run_topic_check("test_reject_binaries_topic", BAD_COMMIT, check);
256        test_result_errors(
257            result,
258            &[
259                "adds the AR binary `ar-header`.",
260                "adds the ELF binary `elf-header`.",
261                "adds the Mach-O binary `macho-cigam-header`.",
262                "adds the Mach-O binary `macho-fat-cigam-header`.",
263                "adds the Mach-O binary `macho-fat-magic-header`.",
264                "adds the Mach-O binary `macho-magic-header`.",
265                "adds the PE binary `pe-le-header`.",
266            ],
267        );
268    }
269
270    #[test]
271    fn test_reject_binaries_topic_attr_ok() {
272        let check = RejectBinaries::default();
273        run_topic_check_ok("test_reject_binaries_topic_attr_ok", ATTR_COMMIT, check)
274    }
275
276    #[test]
277    fn test_reject_binaries_topic_attr_bad() {
278        let check = RejectBinaries::default();
279        let result = run_topic_check(
280            "test_reject_binaries_topic_attr_bad",
281            ATTR_COMMIT_BAD,
282            check,
283        );
284        test_result_errors(
285            result,
286            &[
287                "adds the AR (not ELF) binary `ar-header`.",
288                "adds the ELF (not Mach-O) binary `elf-header`.",
289                "adds the Mach-O (not ELF) binary `macho-cigam-header`.",
290                "adds the Mach-O (not ELF) binary `macho-fat-cigam-header`.",
291                "adds the Mach-O (not ELF) binary `macho-fat-magic-header`.",
292                "adds the Mach-O (not ELF) binary `macho-magic-header`.",
293                "adds the PE (not ELF) binary `pe-le-header`.",
294            ],
295        );
296    }
297
298    #[test]
299    fn test_reject_binaries_topic_plus_x() {
300        let check = RejectBinaries::default();
301        let result = run_topic_check("test_reject_binaries_topic_plus_x", BAD_COMMIT_EXE, check);
302        test_result_errors(
303            result,
304            &[
305                "adds the AR binary `ar-header`.",
306                "adds the ELF binary `elf-header`.",
307                "adds the Mach-O binary `macho-cigam-header`.",
308                "adds the Mach-O binary `macho-fat-cigam-header`.",
309                "adds the Mach-O binary `macho-fat-magic-header`.",
310                "adds the Mach-O binary `macho-magic-header`.",
311                "adds the PE binary `pe-le-header`.",
312            ],
313        );
314    }
315
316    #[test]
317    fn test_reject_binaries_topic_fixed() {
318        let check = RejectBinaries::default();
319        run_topic_check_ok("test_reject_binaries_topic_fixed", DELETE_COMMIT, check);
320    }
321
322    #[test]
323    fn test_reject_binaries_topic_attr_fixed() {
324        let check = RejectBinaries::default();
325        run_topic_check_ok(
326            "test_reject_binaries_topic_attr_fixed",
327            FIX_ATTR_COMMIT,
328            check,
329        );
330    }
331}