use std::borrow::Cow;
use std::sync::LazyLock;
use regex::Regex;
pub(super) fn maybe_expand_copyrighted_by_href_urls<'a>(content: &'a str) -> Cow<'a, str> {
let lower = content.to_ascii_lowercase();
if !lower.contains("copyrighted by") || !lower.contains("href=") {
return Cow::Borrowed(content);
}
if lower.contains("<html") || lower.contains("<head") {
return Cow::Borrowed(content);
}
if content.lines().count() > 40 {
return Cow::Borrowed(content);
}
static HREF_HTTP_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r#"(?is)\bhref\s*=\s*['\"](?P<url>http://[^'\">\s]+)['\"]\s*/?>?"#).unwrap()
});
Cow::Owned(HREF_HTTP_RE.replace_all(content, " ${url} ").into_owned())
}
fn contains_ascii_ci(haystack: &[u8], needle: &[u8]) -> bool {
if needle.is_empty() {
return true;
}
if needle.len() > haystack.len() {
return false;
}
haystack
.windows(needle.len())
.any(|window| window.eq_ignore_ascii_case(needle))
}
fn has_split_obfuscated_email_continuation_candidate(content: &str) -> bool {
let mut prev_has_copyright = false;
for line in content.lines() {
let bytes = line.as_bytes();
if prev_has_copyright
&& bytes.contains(&b'(')
&& contains_ascii_ci(bytes, b"at")
&& contains_ascii_ci(bytes, b"dot")
{
return true;
}
prev_has_copyright = contains_ascii_ci(bytes, b"copyright");
}
false
}
static SPLIT_EMAIL_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"(?im)(?P<head>^.*\bcopyright\b.*[A-Za-z])[ \t]*\r?\n[ \t]*(?:[/*#;]+[ \t]*)?(?P<email>\([^()\n]*\bat\b[^()\n]*\bdot\b[^()\n]*\))[ \t]*$",
)
.unwrap()
});
fn normalize_split_obfuscated_email_continuation<'a>(content: &'a str) -> Cow<'a, str> {
if !has_split_obfuscated_email_continuation_candidate(content) {
return Cow::Borrowed(content);
}
if !SPLIT_EMAIL_RE.is_match(content) {
return Cow::Borrowed(content);
}
Cow::Owned(
SPLIT_EMAIL_RE
.replace_all(content, "${head} ${email}")
.into_owned(),
)
}
pub(super) fn normalize_split_input<'a>(content: &'a str) -> Cow<'a, str> {
let normalized = normalize_split_angle_bracket_urls(content);
match normalize_split_obfuscated_email_continuation(normalized.as_ref()) {
Cow::Borrowed(_) => normalized,
Cow::Owned(joined) => Cow::Owned(joined),
}
}
fn normalize_split_angle_bracket_urls<'a>(content: &'a str) -> Cow<'a, str> {
static SPLIT_URL_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?is)<\s*(?P<url>https?://[^\s>]+)\s*\r?\n\s*(?P<tail>[^\s>]+)\s*>").unwrap()
});
if !content.contains('<') || !content.contains('>') {
return Cow::Borrowed(content);
}
if !SPLIT_URL_RE.is_match(content) {
return Cow::Borrowed(content);
}
Cow::Owned(
SPLIT_URL_RE
.replace_all(content, "${url} ${tail}")
.into_owned(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn folds_slash_comment_email_continuation() {
let input =
"// Copyright (c) 2003-2008 Christopher M. Kohlhoff\n// (chris at kohlhoff dot com)\n";
let out = normalize_split_obfuscated_email_continuation(input);
assert_eq!(
out.as_ref(),
"// Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)\n"
);
}
#[test]
fn folds_star_comment_email_continuation() {
let input =
" * Copyright (c) 2003-2008 Christopher M. Kohlhoff\n * (chris at kohlhoff dot com)\n";
let out = normalize_split_obfuscated_email_continuation(input);
assert_eq!(
out.as_ref(),
" * Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)\n"
);
}
#[test]
fn leaves_non_email_parenthetical_alone() {
let input = "// Copyright (c) 2020 Acme Inc\n// (see LICENSE for details)\n";
assert!(matches!(
normalize_split_obfuscated_email_continuation(input),
Cow::Borrowed(_)
));
}
#[test]
fn requires_a_copyright_marker_on_the_preceding_line() {
let input = "// Some heading\n// (chris at kohlhoff dot com)\n";
assert!(matches!(
normalize_split_obfuscated_email_continuation(input),
Cow::Borrowed(_)
));
}
#[test]
fn prefilter_accepts_real_continuation_shapes() {
for input in [
"// Copyright (c) 2003-2008 Christopher M. Kohlhoff\n// (chris at kohlhoff dot com)\n",
" * Copyright (c) 2003-2008 Christopher M. Kohlhoff\n * (chris at kohlhoff dot com)\n",
"Copyright 2020 ACME\n(jane AT example DOT org)\n",
] {
assert!(
has_split_obfuscated_email_continuation_candidate(input),
"prefilter rejected a real candidate: {input:?}"
);
assert!(matches!(
normalize_split_obfuscated_email_continuation(input),
Cow::Owned(_)
));
}
}
#[test]
fn prefilter_rejects_non_adjacent_or_missing_markers() {
assert!(!has_split_obfuscated_email_continuation_candidate(
"// Some heading\n// (chris at kohlhoff dot com)\n"
));
assert!(!has_split_obfuscated_email_continuation_candidate(
"Copyright 2020 ACME\nfiller line\n(chris at kohlhoff dot com)\n"
));
assert!(!has_split_obfuscated_email_continuation_candidate(
"look at the cat\nthe dog ran\nCopyright notice text\nplain tail line\n"
));
}
#[test]
fn prefilter_is_a_superset_of_the_regex() {
let inputs = [
"// Copyright (c) 1999 Foo Bar\n// (foo at bar dot com)\n",
"/* Copyright 2010 Baz */\n/* (baz at baz dot net) */\n",
"# Copyright Qux\n# (qux at qux dot io)\n",
"Copyright X\n;(y at z dot q)\n",
" Copyright Z \n (a at b dot c) \n",
"no marker here\n(a at b dot c)\n",
"Copyright but no email next\nplain text\n",
];
for input in inputs {
if SPLIT_EMAIL_RE.is_match(input) {
assert!(
has_split_obfuscated_email_continuation_candidate(input),
"prefilter must accept every regex match: {input:?}"
);
}
}
}
}