rs-random/src/main.rs

190 lines
No EOL
6.2 KiB
Rust

use rand::{rngs::OsRng, seq::SliceRandom};
use std::collections::HashSet;
use std::env;
use std::process;
static CHARACTER_SETS: [(&str, &str, &str); 11] = [
("lowercase", "abcdefghijklmnopqrstuvwxyz", "English lowercase letters (a-z)"),
("uppercase", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "English uppercase letters (A-Z)"),
("numbers", "0123456789", "Numbers (0-9)"),
("hex", "0123456789abcdef", "Hexadecimal (0-9, a-f)"),
("special", "!@#$%^&*()_+-=[]{}|;:,.<>?", "Special characters"),
("special-safe", "!@#$%^&*_+-=<>?", "Safe special chars (no pipes/brackets)"),
("cyrillic-lower", "абвгдежзийклмнопрстуфхцчшщъыьэюя", "Cyrillic lowercase"),
("cyrillic-upper", "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "Cyrillic uppercase"),
("greek-lower", "αβγδεζηθικλμνξοπρστυφχψω", "Greek lowercase"),
("greek-upper", "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ", "Greek uppercase"),
("symbols", "©®™€£¥§¶†‡•…‰′″‹›\"\'–—", "Extended symbols"),
];
#[inline(never)]
fn print_help() {
println!("rs-random: Secure String Generator\n");
println!("Usage:");
println!(" rs-random [LENGTH] (uses safe defaults)");
println!(" rs-random -l <LEN> [-s <SETS>] [-c <COUNT>]");
println!(" rs-random -h\n");
println!("Available sets (for -s, comma-separated):");
for &(name, _, desc) in &CHARACTER_SETS {
println!(" {:<15} - {}", name, desc);
}
}
#[inline]
fn get_chars(selected_sets: &[&str]) -> Result<Vec<char>, String> {
// Pre-allocate with estimated capacity
let mut all_chars = Vec::with_capacity(selected_sets.len() * 32);
let mut unknown = Vec::new();
for &set_name in selected_sets {
match CHARACTER_SETS.iter().find(|&&(name, _, _)| name == set_name) {
Some(&(_, chars, _)) => all_chars.extend(chars.chars()),
None => unknown.push(set_name),
}
}
if !unknown.is_empty() {
return Err(format!("Unknown sets: {}", unknown.join(",")));
}
if all_chars.is_empty() {
return Err("Empty character pool".to_string());
}
// Optimize for common case where no duplicates exist
if selected_sets.len() == 1 ||
(selected_sets.len() <= 4 && !selected_sets.iter().any(|&s|
s == "numbers" && (selected_sets.contains(&"hex") ||
selected_sets.contains(&"lowercase") ||
selected_sets.contains(&"uppercase")))) {
return Ok(all_chars);
}
// Remove duplicates only when necessary
let mut unique_chars = Vec::with_capacity(all_chars.len());
let mut seen = HashSet::with_capacity(all_chars.len());
for ch in all_chars {
if seen.insert(ch) {
unique_chars.push(ch);
}
}
unique_chars.shrink_to_fit();
Ok(unique_chars)
}
#[inline]
fn generate_random_string(chars: &[char], length: usize) -> String {
let mut rng = OsRng;
let mut result = String::with_capacity(length);
// Unroll loop for better performance on small strings
let chunks = length / 4;
let remainder = length % 4;
for _ in 0..chunks {
result.push(*chars.choose(&mut rng).unwrap());
result.push(*chars.choose(&mut rng).unwrap());
result.push(*chars.choose(&mut rng).unwrap());
result.push(*chars.choose(&mut rng).unwrap());
}
for _ in 0..remainder {
result.push(*chars.choose(&mut rng).unwrap());
}
result
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 || args.contains(&"-h".to_string()) {
print_help();
return;
}
let mut length = 16;
let mut count = 1;
let mut sets = "lowercase,uppercase,numbers,special-safe";
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"-l" => {
if i + 1 < args.len() {
match args[i + 1].parse::<usize>() {
Ok(n) => length = n,
Err(_) => {
eprintln!("Error: Invalid length");
process::exit(1);
}
}
i += 2;
} else {
eprintln!("Error: Missing length value");
process::exit(1);
}
}
"-s" => {
if i + 1 < args.len() {
sets = &args[i + 1];
i += 2;
} else {
eprintln!("Error: Missing sets value");
process::exit(1);
}
}
"-c" => {
if i + 1 < args.len() {
match args[i + 1].parse::<usize>() {
Ok(n) => count = n,
Err(_) => {
eprintln!("Error: Invalid count");
process::exit(1);
}
}
i += 2;
} else {
eprintln!("Error: Missing count value");
process::exit(1);
}
}
arg if i == 1 && !arg.starts_with('-') => {
match arg.parse::<usize>() {
Ok(n) => length = n,
Err(_) => {
eprintln!("Error: Invalid length");
process::exit(1);
}
}
i += 1;
}
_ => {
i += 1;
}
}
}
if length == 0 {
eprintln!("Error: Length must be > 0");
process::exit(1);
}
let selected_sets: Vec<&str> = sets.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
match get_chars(&selected_sets) {
Ok(chars) => {
for i in 0..count {
let random_string = generate_random_string(&chars, length);
if count > 1 {
print!("{}: ", i + 1);
}
println!("{}", random_string);
}
}
Err(e) => {
eprintln!("Error: {}", e);
process::exit(1);
}
}
}