use derive_builder::Builder;
use git_checks_core::impl_prelude::*;
use std::char::REPLACEMENT_CHARACTER;
const UNICODE_BIDI_CHARS: &[char] = &[
'\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}',
'\u{2069}',
];
#[derive(Builder, Debug, Default, Clone, Copy)]
#[builder(field(private))]
pub struct RejectBiDi {
#[builder(default = "false")]
allow: bool,
}
impl RejectBiDi {
pub fn builder() -> RejectBiDiBuilder {
RejectBiDiBuilder::default()
}
}
impl ContentCheck for RejectBiDi {
fn name(&self) -> &str {
"reject-bidi"
}
fn check(
&self,
ctx: &CheckGitContext,
content: &dyn Content,
) -> Result<CheckResult, Box<dyn Error>> {
let mut result = CheckResult::new();
for diff in content.diffs() {
match diff.status {
StatusChange::Added | StatusChange::Modified(_) => (),
_ => continue,
}
let diff_attr = ctx.check_attr("diff", diff.name.as_path())?;
if let AttributeState::Unset = diff_attr {
continue;
}
let patch = match content.path_diff(&diff.name) {
Ok(s) => s,
Err(err) => {
result.add_alert(
format!(
"{}failed to get the diff for file `{}`: {}.",
commit_prefix(content),
diff.name,
err,
),
true,
);
continue;
},
};
for line in patch.lines().filter(|line| line.starts_with('+')) {
let line_bidi_free: String = line
.chars()
.map(|c| {
if UNICODE_BIDI_CHARS.contains(&c) {
REPLACEMENT_CHARACTER
} else {
c
}
})
.collect();
if line_bidi_free != line {
let safe_line = line_bidi_free[1..]
.replace('\\', "\\\\")
.replace('`', "\\`");
if self.allow {
result.add_warning(format!(
"{}Unicode bidirectional control character(s) added in `{}`: `{}`.",
commit_prefix_str(content, "needs checked;"),
diff.name,
safe_line,
));
} else {
result.add_error(format!(
"{}Unicode bidirectional control character(s) added in `{}`: `{}`.",
commit_prefix_str(content, "not allowed;"),
diff.name,
safe_line,
));
}
}
}
}
Ok(result)
}
}
#[cfg(feature = "config")]
pub(crate) mod config {
use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
use serde::Deserialize;
#[cfg(test)]
use serde_json::json;
use crate::RejectBiDi;
#[derive(Deserialize, Debug)]
pub struct RejectBiDiConfig {
#[serde(default)]
allow: bool,
}
impl IntoCheck for RejectBiDiConfig {
type Check = RejectBiDi;
fn into_check(self) -> Self::Check {
let mut builder = RejectBiDi::builder();
builder.allow(self.allow);
builder
.build()
.expect("configuration mismatch for `RejectBiDi`")
}
}
register_checks! {
RejectBiDiConfig {
"reject_bidi" => CommitCheckConfig,
"reject_bidi/topic" => TopicCheckConfig,
},
}
#[test]
fn test_reject_bidi_config_empty() {
let json = json!({});
let check: RejectBiDiConfig = serde_json::from_value(json).unwrap();
assert!(!check.allow);
let check = check.into_check();
assert!(!check.allow);
}
#[test]
fn test_reject_bidi_config_all_fields() {
let json = json!({
"allow": true,
});
let check: RejectBiDiConfig = serde_json::from_value(json).unwrap();
assert!(check.allow);
let check = check.into_check();
assert!(check.allow);
}
}
#[cfg(test)]
mod tests {
use git_checks_core::{Check, TopicCheck};
use crate::test::*;
use crate::RejectBiDi;
const BAD_TOPIC: &str = "678c1deeade619d52c5b0990bb05af79017f2787";
const DELETE_TOPIC: &str = "0a5b9308fcedec797d70ba78bb1b92a7b7943828";
const FIX_TOPIC: &str = "0a5b9308fcedec797d70ba78bb1b92a7b7943828";
#[test]
fn test_reject_bidi_builder_default() {
assert!(RejectBiDi::builder().build().is_ok());
}
#[test]
fn test_reject_bidi_name_commit() {
let check = RejectBiDi::default();
assert_eq!(Check::name(&check), "reject-bidi");
}
#[test]
fn test_reject_bidi_name_topic() {
let check = RejectBiDi::default();
assert_eq!(TopicCheck::name(&check), "reject-bidi");
}
#[test]
fn test_reject_bidi() {
let check = RejectBiDi::default();
let result = run_check("test_reject_bidi", BAD_TOPIC, check);
test_result_errors(result, &[
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 not allowed; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
]);
}
#[test]
fn test_reject_bidi_allow() {
let check = RejectBiDi::builder().allow(true).build().unwrap();
let result = run_check("test_reject_bidi_allow", BAD_TOPIC, check);
test_result_warnings(result, &[
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
"commit 678c1deeade619d52c5b0990bb05af79017f2787 needs checked; Unicode bidirectional \
control character(s) added in `has-bidi`: `bidi character: \u{fffd}`.",
]);
}
#[test]
fn test_reject_bidi_topic() {
let check = RejectBiDi::default();
let result = run_topic_check("test_reject_bidi_topic", BAD_TOPIC, check);
test_result_errors(
result,
&[
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
],
);
}
#[test]
fn test_reject_bidi_topic_allow() {
let check = RejectBiDi::builder().allow(true).build().unwrap();
let result = run_topic_check("test_reject_bidi_topic_allow", BAD_TOPIC, check);
test_result_warnings(
result,
&[
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
"Unicode bidirectional control character(s) added in `has-bidi`: `bidi character: \
\u{fffd}`.",
],
);
}
#[test]
fn test_reject_bidi_delete_file() {
let check = RejectBiDi::default();
let conf = make_check_conf(&check);
let result = test_check_base(
"test_reject_bidi_delete_file",
DELETE_TOPIC,
BAD_TOPIC,
&conf,
);
test_result_ok(result);
}
#[test]
fn test_reject_bidi_delete_file_topic() {
let check = RejectBiDi::default();
let result = run_topic_check("test_reject_bidi_delete_file_topic", DELETE_TOPIC, check);
test_result_ok(result);
}
#[test]
fn test_reject_bidi_topic_fixed() {
let check = RejectBiDi::default();
run_topic_check_ok("test_reject_bidi_topic_fixed", FIX_TOPIC, check);
}
}