git-checks 3.9.1

Checks to run against a topic in git to enforce coding standards.
Documentation
// Copyright 2016 Kitware, Inc.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use impl_prelude::*;

/// Reject binary files.
///
/// This check detects ELF and Mach-O binaries and rejects content where they are found.
#[derive(Debug, Default, Clone, Copy)]
pub struct RejectBinaries;

impl RejectBinaries {
    /// Create a check which detects ELF and Mach-O binaries.
    pub fn new() -> Self {
        RejectBinaries
    }
}

const ELF_MAGIC: &[u8] = &[0x7f, 0x45, 0x4c, 0x46]; // 0x7f ELF
const MACHO_MAGIC: &[u8] = &[0xcf, 0xfa, 0xed, 0xfe];
const MACHO_CIGAM: &[u8] = &[0xfe, 0xed, 0xfa, 0xcf];
const MACHO_FAT_MAGIC: &[u8] = &[0xca, 0xfe, 0xba, 0xbe];
const MACHO_FAT_CIGAM: &[u8] = &[0xbe, 0xba, 0xfe, 0xca];

impl ContentCheck for RejectBinaries {
    fn name(&self) -> &str {
        "reject-binaries"
    }

    fn check(&self, ctx: &CheckGitContext, content: &Content) -> Result<CheckResult> {
        let mut result = CheckResult::new();

        for diff in content.diffs() {
            match diff.status {
                StatusChange::Added |
                StatusChange::Modified(_) => (),
                _ => continue,
            }

            let binary_attr = ctx.check_attr("hooks-allow-binary", diff.name.as_path())?;

            let allowed_binary_type = match binary_attr {
                // Set without a value means "any".
                AttributeState::Set => continue,
                AttributeState::Value(ref v) => Some(v),
                // Any other setting means "no binary allowed".
                _ => None,
            };

            let binary_type = {
                let cat_file = ctx.git()
                    .arg("cat-file")
                    .arg("blob")
                    .arg(diff.new_blob.as_str())
                    .output()
                    .chain_err(|| "failed to contruct cat-file command")?;

                let stdout = cat_file.stdout;
                if stdout.starts_with(ELF_MAGIC) {
                    Some("ELF")
                } else if stdout.starts_with(MACHO_MAGIC)
                    || stdout.starts_with(MACHO_CIGAM)
                    || stdout.starts_with(MACHO_FAT_MAGIC)
                    || stdout.starts_with(MACHO_FAT_CIGAM) {
                    Some("Mach-O")
                } else {
                    None
                }
            };

            if let Some(binary_type) = binary_type {
                if let Some(allowed_binary_type) = allowed_binary_type {
                    if allowed_binary_type != binary_type {
                        result.add_error(format!("{}adds the {} (not {}) binary `{}`.",
                                                 commit_prefix(content),
                                                 binary_type,
                                                 allowed_binary_type,
                                                 diff.name));
                    }
                } else {
                    result.add_error(format!("{}adds the {} binary `{}`.",
                                             commit_prefix(content),
                                             binary_type,
                                             diff.name));
                }
            }
        }

        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    use checks::RejectBinaries;
    use checks::test::*;

    const BAD_COMMIT: &str = "e5e1e2c8db62ac8f50f249d3cf3f334ddf158936";
    const BAD_COMMIT_EXE: &str = "261577469c6790190d866a928dc3bd8e91d238cf";
    const FIX_COMMIT: &str = "b9d46ccc02c1d484e8b619e210226597591fbd35";
    const ATTR_COMMIT: &str = "f5fd493ca51556d6cd0c42dfc8003925d77441f3";
    const ATTR_COMMIT_BAD: &str = "71fa463c4bedeb40807b6c73b08ce207b0fe0309";
    const FIX_ATTR_COMMIT: &str = "f534f59e97607581eb35eb10b65499ec3f44800b";

    #[test]
    fn test_reject_binaries() {
        let check = RejectBinaries::new();
        let result = run_check("test_reject_binaries", BAD_COMMIT, check);
        test_result_errors(result, &[
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the ELF binary `elf-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-cigam-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-cigam-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-magic-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-magic-header`.",
        ]);
    }

    #[test]
    fn test_reject_binaries_plus_x() {
        let check = RejectBinaries::new();
        let result = run_check("test_reject_binaries_plus_x", BAD_COMMIT_EXE, check);
        test_result_errors(result, &[
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the ELF binary `elf-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-cigam-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-cigam-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-fat-magic-header`.",
            "commit e5e1e2c8db62ac8f50f249d3cf3f334ddf158936 adds the Mach-O binary `macho-magic-header`.",
            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the ELF binary `elf-header`.",
            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-cigam-header`.",
            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-fat-cigam-header`.",
            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-fat-magic-header`.",
            "commit 261577469c6790190d866a928dc3bd8e91d238cf adds the Mach-O binary `macho-magic-header`.",
        ]);
    }

    #[test]
    fn test_reject_binaries_attr_ok() {
        let check = RejectBinaries::new();
        run_check_ok("test_reject_binaries_attr_ok", ATTR_COMMIT, check)
    }

    #[test]
    fn test_reject_binaries_attr_bad() {
        let check = RejectBinaries::new();
        let result = run_check("test_reject_binaries_attr_bad", ATTR_COMMIT_BAD, check);
        test_result_errors(result, &[
            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the ELF (not Mach-O) binary `elf-header`.",
            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-cigam-header`.",
            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-fat-cigam-header`.",
            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-fat-magic-header`.",
            "commit 71fa463c4bedeb40807b6c73b08ce207b0fe0309 adds the Mach-O (not ELF) binary `macho-magic-header`.",
        ]);
    }

    #[test]
    fn test_reject_binaries_topic() {
        let check = RejectBinaries::new();
        let result = run_topic_check("test_reject_binaries_topic", BAD_COMMIT, check);
        test_result_errors(result, &[
            "adds the ELF binary `elf-header`.",
            "adds the Mach-O binary `macho-cigam-header`.",
            "adds the Mach-O binary `macho-fat-cigam-header`.",
            "adds the Mach-O binary `macho-fat-magic-header`.",
            "adds the Mach-O binary `macho-magic-header`.",
        ]);
    }

    #[test]
    fn test_reject_binaries_topic_attr_ok() {
        let check = RejectBinaries::new();
        run_topic_check_ok("test_reject_binaries_topic_attr_ok", ATTR_COMMIT, check)
    }

    #[test]
    fn test_reject_binaries_topic_attr_bad() {
        let check = RejectBinaries::new();
        let result = run_topic_check("test_reject_binaries_topic_attr_bad", ATTR_COMMIT_BAD, check);
        test_result_errors(result, &[
            "adds the ELF (not Mach-O) binary `elf-header`.",
            "adds the Mach-O (not ELF) binary `macho-cigam-header`.",
            "adds the Mach-O (not ELF) binary `macho-fat-cigam-header`.",
            "adds the Mach-O (not ELF) binary `macho-fat-magic-header`.",
            "adds the Mach-O (not ELF) binary `macho-magic-header`.",
        ]);
    }

    #[test]
    fn test_reject_binaries_topic_plus_x() {
        let check = RejectBinaries::new();
        let result = run_topic_check("test_reject_binaries_topic_plus_x", BAD_COMMIT_EXE, check);
        test_result_errors(result, &[
            "adds the ELF binary `elf-header`.",
            "adds the Mach-O binary `macho-cigam-header`.",
            "adds the Mach-O binary `macho-fat-cigam-header`.",
            "adds the Mach-O binary `macho-fat-magic-header`.",
            "adds the Mach-O binary `macho-magic-header`.",
        ]);
    }

    #[test]
    fn test_reject_binaries_topic_fixed() {
        let check = RejectBinaries::new();
        run_topic_check_ok("test_reject_binaries_topic_fixed", FIX_COMMIT, check);
    }

    #[test]
    fn test_reject_binaries_topic_attr_fixed() {
        let check = RejectBinaries::new();
        run_topic_check_ok("test_reject_binaries_topic_attr_fixed", FIX_ATTR_COMMIT, check);
    }
}