use std::collections::HashSet;
use rand::rngs::OsRng;
use rand::seq::SliceRandom;
use rand::Rng;
use zxcvbn::zxcvbn;
use zxcvbn::Score;
use serde::{Serialize, Deserialize};
use std::fmt;
use clap::ValueEnum;
use crate::pwsmanager::PasswordPolicy;
include!(concat!(env!("OUT_DIR"), "/word_data.rs"));
#[derive(Debug, Clone, Copy, Deserialize, Serialize, ValueEnum, bincode::Encode, bincode::Decode)]
pub enum Capitalization {
#[value(name = "none")]
NoCapitalization,
#[value(name = "camel")]
CamelCase,
#[value(name = "random")]
RandomCase,
}
impl fmt::Display for Capitalization {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Capitalization::NoCapitalization => write!(f, "none"),
Capitalization::CamelCase => write!(f, "camel"),
Capitalization::RandomCase => write!(f, "random"),
}
}
}
#[derive(Debug)]
pub struct MemorablePasswordOptions {
pub word_count: usize,
pub include_numbers: bool,
pub separator: char,
pub capitalization: Capitalization,
}
impl Default for MemorablePasswordOptions {
fn default() -> Self {
Self {
word_count: 4,
include_numbers: false,
separator: '-',
capitalization: Capitalization::CamelCase,
}
}
}
pub struct PasswordOptions {
pub length: usize,
pub include_uppercase: bool,
pub include_lowercase: bool,
pub include_numbers: bool,
pub include_special: bool,
pub url_safe: bool,
pub avoid_confusion: bool,
}
impl Default for PasswordOptions {
fn default() -> Self {
Self {
length: 16,
include_uppercase: true,
include_lowercase: true,
include_numbers: true,
include_special: true,
url_safe: false,
avoid_confusion: false,
}
}
}
pub fn generate_password(options: &PasswordOptions) -> Result<String, String> {
let mut uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string();
let mut lowercase = "abcdefghijklmnopqrstuvwxyz".to_string();
let mut numbers = "0123456789".to_string();
let mut special = if options.url_safe {
"-._~".to_string()
} else {
"!@#$%^&*()_+-=[]{}|;:,.<>?~".to_string()
};
if options.avoid_confusion {
let confusing_chars: HashSet<char> = ['l', 'I', '1', 'O', '0', 'o'].iter().cloned().collect();
uppercase.retain(|c| !confusing_chars.contains(&c));
lowercase.retain(|c| !confusing_chars.contains(&c));
numbers.retain(|c| !confusing_chars.contains(&c));
special.retain(|c| !confusing_chars.contains(&c));
}
let mut required_sets = Vec::new();
if options.include_uppercase {
if uppercase.is_empty() {
return Err("Uppercase character set is empty after removing confusing characters".to_string());
}
required_sets.push(uppercase.chars().collect::<Vec<_>>());
}
if options.include_lowercase {
if lowercase.is_empty() {
return Err("Lowercase character set is empty after removing confusing characters".to_string());
}
required_sets.push(lowercase.chars().collect::<Vec<_>>());
}
if options.include_numbers {
if numbers.is_empty() {
return Err("Numbers character set is empty after removing confusing characters".to_string());
}
required_sets.push(numbers.chars().collect::<Vec<_>>());
}
if options.include_special {
if special.is_empty() {
return Err("Special character set is empty after removing confusing characters".to_string());
}
required_sets.push(special.chars().collect::<Vec<_>>());
}
if required_sets.is_empty() {
return Err("At least one character set must be included".to_string());
}
if options.length < required_sets.len() {
return Err(format!("Password length must be at least {} to include all required character sets", required_sets.len()));
}
let mut char_pool = String::new();
if options.include_uppercase { char_pool.push_str(&uppercase); }
if options.include_lowercase { char_pool.push_str(&lowercase); }
if options.include_numbers { char_pool.push_str(&numbers); }
if options.include_special { char_pool.push_str(&special); }
let all_chars: Vec<char> = char_pool.chars().collect();
let mut rng = OsRng::default();
let mut password_chars = Vec::with_capacity(options.length);
for chars in &required_sets {
password_chars.push(*chars.choose(&mut rng).unwrap());
}
for _ in 0..(options.length - required_sets.len()) {
password_chars.push(*all_chars.choose(&mut rng).unwrap());
}
password_chars.shuffle(&mut rng);
Ok(password_chars.into_iter().collect())
}
pub fn generate_memorable_password(options: &MemorablePasswordOptions) -> Result<String, String> {
if options.word_count < 1 {
return Err("Word count must be at least 1".to_string());
}
if WORDS.is_empty() {
return Err("Word list is empty".to_string());
}
let mut rng = OsRng::default();
let mut words = Vec::with_capacity(options.word_count);
for _ in 0..options.word_count {
let word = WORDS.choose(&mut rng)
.ok_or("Failed to select word from list")?;
words.push(process_word(word, options.capitalization, &mut rng));
}
let mut password_parts = words;
if options.include_numbers {
let number = rng.gen_range(0..=99);
password_parts.push(number.to_string());
}
Ok(password_parts.join(&options.separator.to_string()))
}
fn process_word(word: &str, capitalization: Capitalization, rng: &mut OsRng) -> String {
match capitalization {
Capitalization::NoCapitalization => word.to_lowercase(),
Capitalization::CamelCase => {
let mut chars: Vec<char> = word.chars().collect();
if let Some(first) = chars.get_mut(0) {
*first = first.to_ascii_uppercase();
}
chars.into_iter().collect()
},
Capitalization::RandomCase => {
word.chars()
.map(|c| {
if rng.gen_bool(0.5) {
c.to_ascii_uppercase()
} else {
c.to_ascii_lowercase()
}
})
.collect()
}
}
}
pub fn check_url_safe(password: &str) -> bool {
password.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.' | '~'))
}
pub fn check_confusing_chars(password: &str) -> Vec<char> {
let confusing_chars = ['l', 'I', '1', 'O', '0', 'o'];
password.chars().filter(|c| confusing_chars.contains(c)).collect()
}
pub fn assess_password_strength(password: &str) -> Result<(String, u8, String), String> {
let strength_result = zxcvbn(password, &[]);
let score = strength_result.score();
let feedback = strength_result.feedback().map_or_else(
|| String::new(),
|f| {
let suggestions: Vec<String> = f.suggestions().iter().map(|s| s.to_string()).collect();
let warnings: Vec<String> = f.warning().map_or_else(Vec::new, |w| vec![w.to_string()]);
[warnings, suggestions].concat().join("; ")
}
);
let rating = match score {
Score::Zero => "极低",
Score::One => "低",
Score::Two => "中",
Score::Three => "高",
Score::Four => "极高",
_ => "未知",
}.to_string();
Ok((rating, score as u8, feedback))
}
pub fn evaluate_and_display_password_strength(password: &str) -> Result<(), String> {
let (rating, score, feedback) = assess_password_strength(password)
.map_err(|e| format!("Failed to assess password strength: {}", e))?;
println!("密码安全等级: {} (评分: {}/4)", rating, score);
if score < 2 {
eprintln!("⚠️ 警告: 密码安全等级较低 - {}", feedback);
}
Ok(())
}
pub fn generate_from_policy(policy: &PasswordPolicy) -> Result<String, String> {
match policy {
PasswordPolicy::Random {
length,
include_uppercase,
include_lowercase,
include_numbers,
include_special,
url_safe,
avoid_confusion } => {
let options = PasswordOptions {
length: *length,
include_uppercase: *include_uppercase,
include_lowercase: *include_lowercase,
include_numbers: *include_numbers,
include_special: *include_special,
url_safe: *url_safe,
avoid_confusion: *avoid_confusion,
};
generate_password(&options)
},
PasswordPolicy::Memorable {
words,
separator,
include_numbers,
capitalization
} => {
let options = MemorablePasswordOptions {
word_count: *words as usize,
separator: *separator,
include_numbers: *include_numbers,
capitalization: capitalization.clone(),
};
generate_memorable_password(&options)
}
}
}