use crate::prelude::*;
pub(crate) struct Sanitizer {
rules: Vec<SanitizerRule>,
}
struct SanitizerRule {
chars: Vec<SanitizerChar>,
replacement: Option<char>,
}
#[derive(Debug)]
pub(crate) struct SanitizerResult {
pub output: String,
pub found: HashSet<SanitizerChar>,
}
impl Sanitizer {
#[must_use]
pub fn invisible() -> Self {
Self {
rules: vec![SanitizerRule::invisible(), SanitizerRule::control()],
}
}
#[must_use]
pub fn restricted() -> Self {
Self {
rules: vec![SanitizerRule::restricted()],
}
}
#[must_use]
pub fn directional() -> Self {
Self {
rules: vec![SanitizerRule::directional()],
}
}
#[must_use]
pub fn non_printing() -> Self {
Self {
rules: vec![
SanitizerRule::invisible(),
SanitizerRule::directional(),
SanitizerRule::control(),
],
}
}
#[must_use]
pub fn name() -> Self {
Self {
rules: vec![
SanitizerRule::replace_dividers(),
SanitizerRule::restricted_without_dividers(),
SanitizerRule::invisible(),
SanitizerRule::directional(),
SanitizerRule::control(),
],
}
}
#[must_use]
pub fn libtorrent() -> Self {
Self {
rules: vec![SanitizerRule::libtorrent()],
}
}
#[must_use]
pub fn execute(&self, input: String) -> SanitizerResult {
let mut found = HashSet::new();
let output = input
.chars()
.filter_map(|x| self.sanitize_char(x, &mut found))
.collect();
SanitizerResult { output, found }
}
fn sanitize_char(&self, char: char, found: &mut HashSet<SanitizerChar>) -> Option<char> {
for rule in &self.rules {
for rule_char in &rule.chars {
if rule_char.get_char() == char {
found.insert(*rule_char);
return rule.replacement;
}
}
}
Some(char)
}
}
impl SanitizerRule {
fn libtorrent() -> Self {
SanitizerRule {
chars: vec![
SanitizerChar::ForwardSlash,
SanitizerChar::Backslash,
SanitizerChar::LeftToRightMark,
SanitizerChar::RightToLeftMark,
SanitizerChar::LeftToRightEmbedding,
SanitizerChar::RightToLeftEmbedding,
SanitizerChar::PopDirectionalFormatting,
SanitizerChar::LeftToRightOverride,
SanitizerChar::RightToLeftOverride,
],
replacement: None,
}
}
fn directional() -> Self {
SanitizerRule {
chars: vec![
SanitizerChar::LeftToRightMark,
SanitizerChar::RightToLeftMark,
SanitizerChar::LeftToRightEmbedding,
SanitizerChar::RightToLeftEmbedding,
SanitizerChar::PopDirectionalFormatting,
SanitizerChar::LeftToRightOverride,
SanitizerChar::RightToLeftOverride,
SanitizerChar::LeftToRightIsolate,
SanitizerChar::RightToLeftIsolate,
SanitizerChar::FirstStrongIsolate,
SanitizerChar::PopDirectionalIsolate,
],
replacement: None,
}
}
fn replace_dividers() -> Self {
SanitizerRule {
chars: vec![
SanitizerChar::ForwardSlash,
SanitizerChar::Backslash,
SanitizerChar::Pipe,
SanitizerChar::EnDash,
SanitizerChar::EmDash,
],
replacement: Some('-'),
}
}
fn restricted_without_dividers() -> Self {
SanitizerRule {
chars: vec![
SanitizerChar::Colon,
SanitizerChar::LessThan,
SanitizerChar::GreaterThan,
SanitizerChar::DoubleQuote,
SanitizerChar::QuestionMark,
SanitizerChar::Asterisk,
],
replacement: None,
}
}
fn restricted() -> Self {
Self::restricted_without_dividers().extend(vec![
SanitizerChar::ForwardSlash,
SanitizerChar::Backslash,
SanitizerChar::Pipe,
])
}
fn invisible() -> Self {
SanitizerRule {
chars: vec![
SanitizerChar::NonBreakingSpace,
SanitizerChar::ZeroWidthSpace,
SanitizerChar::ZeroWidthNoBreakSpace,
],
replacement: None,
}
}
fn control() -> Self {
SanitizerRule {
chars: vec![
SanitizerChar::Null,
SanitizerChar::StartOfHeading,
SanitizerChar::StartOfText,
SanitizerChar::EndOfText,
SanitizerChar::EndOfTransmission,
SanitizerChar::Enquiry,
SanitizerChar::Acknowledge,
SanitizerChar::Bell,
SanitizerChar::Backspace,
SanitizerChar::HorizontalTab,
SanitizerChar::LineFeed,
SanitizerChar::VerticalTab,
SanitizerChar::FormFeed,
SanitizerChar::CarriageReturn,
SanitizerChar::ShiftOut,
SanitizerChar::ShiftIn,
SanitizerChar::DataLinkEscape,
SanitizerChar::DeviceControl1,
SanitizerChar::DeviceControl2,
SanitizerChar::DeviceControl3,
SanitizerChar::DeviceControl4,
SanitizerChar::NegativeAcknowledge,
SanitizerChar::SynchronousIdle,
SanitizerChar::EndOfTransmissionBlock,
SanitizerChar::Cancel,
SanitizerChar::EndOfMedium,
SanitizerChar::Substitute,
SanitizerChar::Escape,
SanitizerChar::FileSeparator,
SanitizerChar::GroupSeparator,
SanitizerChar::RecordSeparator,
SanitizerChar::UnitSeparator,
SanitizerChar::Delete,
SanitizerChar::PaddingCharacter,
SanitizerChar::HighOctetPreset,
SanitizerChar::BreakPermittedHere,
SanitizerChar::NoBreakHere,
SanitizerChar::Index,
SanitizerChar::NextLine,
SanitizerChar::StartOfSelectedArea,
SanitizerChar::EndOfSelectedArea,
SanitizerChar::CharacterTabulationSet,
SanitizerChar::CharacterTabulationWithJustification,
SanitizerChar::LineTabulationSet,
SanitizerChar::PartialLineForward,
SanitizerChar::PartialLineBackward,
SanitizerChar::ReverseLineFeed,
SanitizerChar::SingleShiftTwo,
SanitizerChar::SingleShiftThree,
SanitizerChar::DeviceControlString,
SanitizerChar::PrivateUseOne,
SanitizerChar::PrivateUseTwo,
SanitizerChar::SetTransmitState,
SanitizerChar::CancelCharacter,
SanitizerChar::MessageWaiting,
SanitizerChar::StartOfGuardedArea,
SanitizerChar::EndOfGuardedArea,
SanitizerChar::StartOfString,
SanitizerChar::SingleGraphicCharacterIntroducer,
SanitizerChar::SingleCharacterIntroducer,
SanitizerChar::ControlSequenceIntroducer,
SanitizerChar::StringTerminator,
SanitizerChar::OperatingSystemCommand,
SanitizerChar::PrivacyMessage,
SanitizerChar::ApplicationProgramCommand,
],
replacement: None,
}
}
fn extend(mut self, vec: Vec<SanitizerChar>) -> Self {
self.chars.extend(vec);
self
}
}
impl SanitizerResult {
pub fn humanize(&self) -> String {
let found = self.found.iter().collect::<Vec<_>>();
join_humanized(found)
}
}
impl From<SanitizerResult> for String {
fn from(result: SanitizerResult) -> Self {
result.output
}
}
impl AsRef<str> for SanitizerResult {
fn as_ref(&self) -> &str {
&self.output
}
}
impl PartialEq<&str> for SanitizerResult {
fn eq(&self, other: &&str) -> bool {
self.output == *other
}
}
impl PartialEq<String> for SanitizerResult {
fn eq(&self, other: &String) -> bool {
self.output == *other
}
}