name = "email-validate"
description = "RFC 5322 email-address validator (Porges/email-validate-hs). Bug fixes mined from upstream history; modern HEAD is the base, each patch reverse-applies a fix to install the original bug."
language = "haskell"
strategies = ["quickcheck", "hedgehog", "falsify", "smallcheck"]
crate = "email-validate"
base_commit = "47c662ed216ca09672c92fee4a7875a8f05fa813"

# Note: a sixth candidate, "remove_domain_length_check_fcef63cc_1" (drop
# guard `BS.length parsedDomain <= 253`), was discovered in the upstream
# history but is dropped from this workload because it is semantically
# dominated by the total-length check (254). With local-part >= 1 and
# domain > 253, total = local + 1 + domain > 254, so the total-length
# guard rejects every input that would otherwise expose the
# domain-length bug. Including the variant alone (without also removing
# the total guard) produces an undetectable mutation. Keeping the patch
# file under patches/ for archival.

# ---------------------------------------------------------------------------
# Variant: remove_local_length_check_655883ca_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["remove_local_length_check_655883ca_1"]

[tasks.source]
repo = "https://github.com/Porges/email-validate-hs"
commits = ["655883cae6d2305d7fdc65b0f6192aa963e50da5"]
commit_subjects = ["Enforce length restrictions properly"]
origin = "internal"
summary = "Pre-fix, addrSpec did not check that the local-part length stayed within 64 octets (RFC 3696). The fix added `when (BS.length l > 64) (fail ...)`. Reverse-applying the patch removes that guard, so addrSpec accepts emails whose local part is 65 or more octets."

[tasks.injection]
kind = "patch"
files = ["src/Text/Email/Parser.hs"]
locations = [{ file = "src/Text/Email/Parser.hs", line = 60, symbol = "addrSpec" }]
patch = "patches/remove_local_length_check_655883ca_1.patch"

[tasks.bug]
short_name = "missing_local_part_length_check"
invariant = "If validate accepts a ByteString as a valid email, the parsed local-part must be at most 64 octets long (RFC 3696)."
how_triggered = "Reverse-applying the patch removes the `when (BS.length l > 64)` guard from addrSpec. Calling validate \"<65-a-chars>@example.com\" then returns Right instead of Left."

[[tasks.tasks]]
property = "LocalPartUnder64"
smallcheck_timeout = true
witnesses = [
  { test_fn = "witness_local_part_under64_case_65a", note = "65 a's then @example.com — must be rejected" },
  { test_fn = "witness_local_part_under64_case_70a", note = "70 a's then @example.com — must be rejected" },
]

# ---------------------------------------------------------------------------
# Variant: remove_total_length_check_655883ca_2
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["remove_total_length_check_655883ca_2"]

[tasks.source]
repo = "https://github.com/Porges/email-validate-hs"
commits = ["655883cae6d2305d7fdc65b0f6192aa963e50da5"]
commit_subjects = ["Enforce length restrictions properly"]
origin = "internal"
summary = "Pre-fix, addrSpec did not check that the total length (local + '@' + domain) stayed within 254 octets (RFC 3696 Erratum 1690). The fix added `when (BS.length l + BS.length d + 1 > 254) (fail ...)`. Reverse-applying the patch removes that guard."

[tasks.injection]
kind = "patch"
files = ["src/Text/Email/Parser.hs"]
locations = [{ file = "src/Text/Email/Parser.hs", line = 67, symbol = "addrSpec" }]
patch = "patches/remove_total_length_check_655883ca_2.patch"

[tasks.bug]
short_name = "missing_total_length_check"
invariant = "If validate accepts a ByteString as a valid email, the total length (local + 1 + domain) must be at most 254 octets (RFC 3696 Erratum 1690)."
how_triggered = "Reverse-applying the patch removes the `when (BS.length l + BS.length d + 1 > 254)` guard from addrSpec. Calling validate on a 270-char email then returns Right."

[[tasks.tasks]]
property = "TotalUnder254"
smallcheck_timeout = true
witnesses = [
  { test_fn = "witness_total_under254_case_long", note = "270-char email — must be rejected" },
]

# ---------------------------------------------------------------------------
# Variant: remove_label_length_check_fcef63cc_2
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["remove_label_length_check_fcef63cc_2"]

[tasks.source]
repo = "https://github.com/Porges/email-validate-hs"
commits = ["fcef63cc827448d108489da17de019ed2281d656", "070763d69ecca197ac38d4b06c38ee37443329b7"]
commit_subjects = ["Use a single domain parser", "Add stricter domain-name parsing"]
origin = "internal"
summary = "domainLabel must enforce that each label is at most 63 octets (RFC 1035). The fix combined this with the trailing-hyphen guard. Reverse-applying the patch removes the 63-char check (keeping the hyphen check)."

[tasks.injection]
kind = "patch"
files = ["src/Text/Email/Parser.hs"]
locations = [{ file = "src/Text/Email/Parser.hs", line = 94, symbol = "domainLabel" }]
patch = "patches/remove_label_length_check_fcef63cc_2.patch"

[tasks.bug]
short_name = "missing_label_length_check"
invariant = "If validate accepts a ByteString as a valid email, every dot-separated label of the parsed domain must be at most 63 octets (RFC 1035)."
how_triggered = "Reverse-applying the patch drops the `BS.length content <= 63` clause from domainLabel's guard. Calling validate \"a@<64-char>.com\" then returns Right."

[[tasks.tasks]]
property = "DomainLabelUnder63"
smallcheck_timeout = true
witnesses = [
  { test_fn = "witness_domain_label_under63_case_64a", note = "domain with 64-char first label — must be rejected" },
  { test_fn = "witness_domain_label_under63_case_70a", note = "domain with 70-char first label — must be rejected" },
]

# ---------------------------------------------------------------------------
# Variant: remove_label_hyphen_check_fcef63cc_3
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["remove_label_hyphen_check_fcef63cc_3"]

[tasks.source]
repo = "https://github.com/Porges/email-validate-hs"
commits = ["fcef63cc827448d108489da17de019ed2281d656", "070763d69ecca197ac38d4b06c38ee37443329b7"]
commit_subjects = ["Use a single domain parser", "Add stricter domain-name parsing"]
origin = "internal"
summary = "RFC 1035 forbids domain labels from ending in '-'. The fix's domainLabel guard is `BS.length content <= 63 && BS.last content /= '-'`. Reverse-applying the patch drops the trailing-hyphen check (keeping the length check)."

[tasks.injection]
kind = "patch"
files = ["src/Text/Email/Parser.hs"]
locations = [{ file = "src/Text/Email/Parser.hs", line = 94, symbol = "domainLabel" }]
patch = "patches/remove_label_hyphen_check_fcef63cc_3.patch"

[tasks.bug]
short_name = "missing_label_trailing_hyphen_check"
invariant = "If validate accepts a ByteString as a valid email, no dot-separated label of the parsed domain may end with '-' (RFC 1035)."
how_triggered = "Reverse-applying the patch drops `BS.last content /= '-'` from domainLabel's guard. Calling validate \"a@foo-.com\" then returns Right."

[[tasks.tasks]]
property = "DomainLabelNoTrailingHyphen"
witnesses = [
  { test_fn = "witness_domain_label_no_trailing_hyphen_case_dash",   note = "a@foo-.com — must be rejected" },
  { test_fn = "witness_domain_label_no_trailing_hyphen_case_double", note = "a@foo--.com — must be rejected" },
]

# ---------------------------------------------------------------------------
# Variant: relax_domain_to_dotted_atoms_070763d6_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["relax_domain_to_dotted_atoms_070763d6_1"]

[tasks.source]
repo = "https://github.com/Porges/email-validate-hs"
commits = ["070763d69ecca197ac38d4b06c38ee37443329b7", "fcef63cc827448d108489da17de019ed2281d656"]
commit_subjects = ["Add stricter domain-name parsing", "Use a single domain parser"]
origin = "internal"
summary = "Pre-fix, the `domain` parser was `dottedAtoms <|> domainLiteral`, which accepted any RFC 5322 atom-text characters in domain labels (`!#$%&'*+/=?^_\\`{|}~-`). The fix routes through the strict `domainName` parser that only accepts ASCII alphanumerics + '-'. Reverse-applying the patch reinstates the lax dottedAtoms domain."

[tasks.injection]
kind = "patch"
files = ["src/Text/Email/Parser.hs"]
locations = [{ file = "src/Text/Email/Parser.hs", line = 76, symbol = "domain" }]
patch = "patches/relax_domain_to_dotted_atoms_070763d6_1.patch"

[tasks.bug]
short_name = "domain_accepts_atom_text"
invariant = "If validate accepts a ByteString as a valid email and the domain is not a bracketed literal, every domain label must consist only of ASCII alphanumerics and '-' (RFC 1035)."
how_triggered = "Reverse-applying the patch swaps the strict `domainName` for `dottedAtoms` in the `domain` definition. Calling validate \"a@foo!.com\" then returns Right because `!` is part of atom-text."

[[tasks.tasks]]
property = "DomainNameAlphanumeric"
witnesses = [
  { test_fn = "witness_domain_name_alphanumeric_case_bang",   note = "a@foo!.com — must be rejected" },
  { test_fn = "witness_domain_name_alphanumeric_case_dollar", note = "a@foo$.com — must be rejected" },
]
