1use derive_builder::Builder;
10use git_checks_core::impl_prelude::*;
11
12use crate::binary_format;
13
14#[derive(Builder, Debug, Default, Clone, Copy)]
21#[non_exhaustive]
22#[builder(field(private))]
23pub struct RejectBinaries {}
24
25impl RejectBinaries {
26 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 AttributeState::Set => continue,
55 AttributeState::Value(ref v) => Some(v),
56 _ => 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 #[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}