// 建议文案:warning + suggestions。
//
// 移植来源:dropbox/zxcvbn `src/feedback.coffee`(MIT, (c) Dropbox, Inc.)
//
// 建议只针对 score <= 2 的密码给出,且绑定到序列里**最长**的那个匹配。
///|
/// 上游 `feedback.get_feedback`。
pub fn get_feedback(score : Int, sequence : Array[Match]) -> Feedback {
// 没有任何匹配 => 兜底建议
if sequence.length() == 0 {
return default_feedback
}
// score 好或很好时不给建议
if score > 2 {
return { warning: "", suggestions: [], }
}
// 建议绑定到最长匹配(长序列里它最有信息量)
let mut longest = sequence[0]
for m in sequence[1:] {
if m.token().to_array().length() > longest.token().to_array().length() {
longest = m
}
}
let is_sole_match = sequence.length() == 1
let extra = "Add another word or two. Uncommon words are better."
match get_match_feedback(longest, is_sole_match) {
Some(feedback) => {
// 前缀"再加一两个词"——任何情况下都有用
feedback.suggestions.insert(0, extra)
{ warning: feedback.warning, suggestions: feedback.suggestions, }
}
None => { warning: "", suggestions: [extra], }
}
}
///|
/// 上游 `feedback.get_match_feedback`:按最长匹配的模式给建议。
/// 上游对未覆盖的模式返回 undefined,这里对应 None。
fn get_match_feedback(m : Match, is_sole_match : Bool) -> Feedback? {
match m {
Dictionary(dm) => Some(get_dictionary_match_feedback(dm, is_sole_match))
Spatial(sm) => {
let warning = if sm.turns == 1 {
"Straight rows of keys are easy to guess"
} else {
"Short keyboard patterns are easy to guess"
}
Some({
warning,
suggestions: ["Use a longer keyboard pattern with more turns"],
})
}
Repeat(rm) => {
let warning = if rm.base_token.to_array().length() == 1 {
"Repeats like \"aaa\" are easy to guess"
} else {
"Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\""
}
Some({ warning, suggestions: ["Avoid repeated words and characters"], })
}
Sequence(_) =>
Some({
warning: "Sequences like abc or 6543 are easy to guess",
suggestions: ["Avoid sequences"],
})
Regex(rm) =>
if rm.regex_name == "recent_year" {
Some({
warning: "Recent years are easy to guess",
suggestions: [
"Avoid recent years", "Avoid years that are associated with you",
],
})
} else {
None
}
Date(_) =>
Some({
warning: "Dates are often easy to guess",
suggestions: ["Avoid dates and years that are associated with you"],
})
Bruteforce(_) => None
}
}
///|
/// 上游 `feedback.get_dictionary_match_feedback`。
fn get_dictionary_match_feedback(
m : DictionaryMatch,
is_sole_match : Bool,
) -> Feedback {
let warning = match m.dictionary_name {
Passwords =>
if is_sole_match && !m.l33t && !m.reversed {
if m.rank <= 10 {
"This is a top-10 common password"
} else if m.rank <= 100 {
"This is a top-100 common password"
} else {
"This is a very common password"
}
} else if m.guesses_log10 <= 4.0 {
"This is similar to a commonly used password"
} else {
""
}
English =>
if is_sole_match {
"A word by itself is easy to guess"
} else {
""
}
Surnames | MaleNames | FemaleNames =>
if is_sole_match {
"Names and surnames by themselves are easy to guess"
} else {
"Common names and surnames are easy to guess"
}
// UsTvAndFilm:上游没有专门文案
_ => ""
}
let suggestions : Array[String] = []
let word = m.token
if is_start_upper(word.to_array()) {
suggestions.push("Capitalization doesn't help very much")
} else if all_non_lower(word.to_array()) && word.to_lower() != word {
suggestions.push(
"All-uppercase is almost as easy to guess as all-lowercase",
)
}
if m.reversed && word.to_array().length() >= 4 {
suggestions.push("Reversed words aren't much harder to guess")
}
if m.l33t {
suggestions.push(
"Predictable substitutions like '@' instead of 'a' don't help very much",
)
}
{ warning, suggestions, }
}