diff --git a/Cargo.lock b/Cargo.lock index f5cb08d..3df86e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -41,6 +50,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -101,25 +120,24 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" -version = "0.5.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", "itertools", "num-traits", - "once_cell", "oorandom", + "page_size", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -127,9 +145,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools", @@ -172,6 +190,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "half" version = "2.6.0" @@ -182,28 +206,11 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hermit-abi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" - -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -263,6 +270,16 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "plotters" version = "0.3.7" @@ -411,6 +428,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" version = "2.0.101" @@ -427,6 +450,7 @@ name = "tiny-clean" version = "0.1.0" dependencies = [ "criterion", + "type-state-builder", ] [[package]] @@ -439,6 +463,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "type-state-builder" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803a550dedce2b64adb78423838d4b29080b0b73f8539a576a7826e4ff00732c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -523,6 +558,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -532,6 +583,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index cabd3d9..799db90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,11 @@ keywords = ["text", "sanitizer", "encoder", "string-manipulation", "utility"] strip = "symbols" opt-level = 3 +[dependencies] +type-state-builder = "0.5.1" + [dev-dependencies] -criterion = "0.5.1" +criterion = "0.8.2" [[bench]] name = "encode_benchmarks" diff --git a/benches/encode_benchmarks.rs b/benches/encode_benchmarks.rs index 97c85f4..4a21e81 100644 --- a/benches/encode_benchmarks.rs +++ b/benches/encode_benchmarks.rs @@ -1,39 +1,62 @@ -use criterion::{Criterion, criterion_group, criterion_main}; +use criterion::{criterion_group, criterion_main, Criterion}; -use tiny_clean::{EncoderBuilder, UriEncoderMode, UriEncoder, XmlEncoderMode, XmlEncoder, JavaScriptEncoderMode, JavaScriptEncoder}; +use tiny_clean::{ + JavaScriptEncoder, JavaScriptEncoderConfig, JavaScriptEncoderMode, UriEncoder, + UriEncoderConfig, UriEncoderMode, XmlEncoder, XmlEncoderConfig, XmlEncoderMode, +}; fn xml_encode_benches(c: &mut Criterion) { let bench_data1 = std::fs::read_to_string("./benches/data/benchmark-data-1.txt").unwrap(); let bench_data2 = std::fs::read_to_string("./benches/data/benchmark-data-2.txt").unwrap(); - let encoder = XmlEncoder::new(XmlEncoderMode::Attribute); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::Attribute) + .build(), + ); c.bench_function("XmlEncoder - Attribute - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); c.bench_function("XmlEncoder - Attribute - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = XmlEncoder::new(XmlEncoderMode::All); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::All) + .build(), + ); c.bench_function("XmlEncoder - All - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); c.bench_function("XmlEncoder - All - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = XmlEncoder::new(XmlEncoderMode::Content); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::Content) + .build(), + ); c.bench_function("XmlEncoder - Content - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); c.bench_function("XmlEncoder - Content - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = XmlEncoder::new(XmlEncoderMode::SingleQuotedAttribute); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::SingleQuotedAttribute) + .build(), + ); c.bench_function("XmlEncoder - SingleQuotedAttribute - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); c.bench_function("XmlEncoder - SingleQuotedAttribute - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = XmlEncoder::new(XmlEncoderMode::DoubleQuotedAttribute); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::DoubleQuotedAttribute) + .build(), + ); c.bench_function("XmlEncoder - DoubleQuotedAttribute - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); @@ -45,19 +68,25 @@ fn xml_encode_benches(c: &mut Criterion) { fn uri_encode_benches(c: &mut Criterion) { let bench_data1 = std::fs::read_to_string("./benches/data/benchmark-data-1.txt").unwrap(); let bench_data2 = std::fs::read_to_string("./benches/data/benchmark-data-2.txt").unwrap(); - let encoder = UriEncoder::new(UriEncoderMode::FullUri); + let encoder = UriEncoder::new( + UriEncoderConfig::builder() + .mode(UriEncoderMode::FullUri) + .build(), + ); c.bench_function("UriEncoder - FullUri - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); - let encoder = UriEncoder::new(UriEncoderMode::FullUri); c.bench_function("UriEncoder - FullUri - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = UriEncoder::new(UriEncoderMode::Component); + let encoder = UriEncoder::new( + UriEncoderConfig::builder() + .mode(UriEncoderMode::Component) + .build(), + ); c.bench_function("UriEncoder - Component - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); - let encoder = UriEncoder::new(UriEncoderMode::Component); c.bench_function("UriEncoder - Component - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); @@ -67,96 +96,122 @@ fn java_script_encode_benches(c: &mut Criterion) { let bench_data1 = std::fs::read_to_string("./benches/data/benchmark-data-1.txt").unwrap(); let bench_data2 = std::fs::read_to_string("./benches/data/benchmark-data-2.txt").unwrap(); - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Block, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Block) + .ascii_only(true) + .build(), + ); c.bench_function("JavaScriptEncoder - Block - ASCII Only - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); + c.bench_function("JavaScriptEncoder - Block - ASCII Only - DS2", |b| { + b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) + }); - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Block, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Block) + .ascii_only(false) + .build(), + ); c.bench_function("JavaScriptEncoder - Block - ASCII Extended - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Block, true); - c.bench_function("JavaScriptEncoder - Block - ASCII Only - DS2", |b| { - b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) - }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Block, false); c.bench_function("JavaScriptEncoder - Block - ASCII Extended - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); ///////////// block end - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Source, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Source) + .ascii_only(true) + .build(), + ); c.bench_function("JavaScriptEncoder - Source - ASCII Only - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Source, false); - c.bench_function("JavaScriptEncoder - Source - ASCII Extended - DS1", |b| { - b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) - }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Source, true); c.bench_function("JavaScriptEncoder - Source - ASCII Only - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Source, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Source) + .ascii_only(false) + .build(), + ); + c.bench_function("JavaScriptEncoder - Source - ASCII Extended - DS1", |b| { + b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) + }); c.bench_function("JavaScriptEncoder - Source - ASCII Extended - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); ///////////// source end - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Html, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Html) + .ascii_only(true) + .build(), + ); c.bench_function("JavaScriptEncoder - Html - ASCII Only - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Html, false); - c.bench_function("JavaScriptEncoder - Html - ASCII Extended - DS1", |b| { - b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) - }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Html, true); c.bench_function("JavaScriptEncoder - Html - ASCII Only - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Html, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Html) + .ascii_only(false) + .build(), + ); + c.bench_function("JavaScriptEncoder - Html - ASCII Extended - DS1", |b| { + b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) + }); c.bench_function("JavaScriptEncoder - Html - ASCII Extended - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); ///////////// html end - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Attribute, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Attribute) + .ascii_only(true) + .build(), + ); c.bench_function("JavaScriptEncoder - Attribute - ASCII Only - DS1", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Attribute, false); - c.bench_function("JavaScriptEncoder - Attribute - ASCII Extended - DS1", |b| { - b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))) - }); - - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Attribute, true); c.bench_function("JavaScriptEncoder - Attribute - ASCII Only - DS2", |b| { b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) }); - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Attribute, false); - c.bench_function("JavaScriptEncoder - Attribute - ASCII Extended - DS2", |b| { - b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))) - }); - + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Attribute) + .ascii_only(false) + .build(), + ); + c.bench_function( + "JavaScriptEncoder - Attribute - ASCII Extended - DS1", + |b| b.iter(|| encoder.encode(std::hint::black_box(&bench_data1))), + ); + c.bench_function( + "JavaScriptEncoder - Attribute - ASCII Extended - DS2", + |b| b.iter(|| encoder.encode(std::hint::black_box(&bench_data2))), + ); ///////////// attribute end } -criterion_group!{ +criterion_group! { name = benches; config = Criterion::default().sample_size(60); targets = java_script_encode_benches,uri_encode_benches,xml_encode_benches diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index 66b9626..0000000 --- a/src/builder.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderMode}; -use crate::uri_encoder::{UriEncoder, UriEncoderMode}; -use crate::xml_encoder::{XmlEncoder, XmlEncoderMode}; - -pub struct NoBuilder; -pub struct JavaScriptEncoderBuilder { - ascii_only: bool, - mode: JavaScriptEncoderMode, -} -pub struct XmlEncoderBuilder { - mode: XmlEncoderMode, -} - -pub struct UriEncoderBuilder { - mode: UriEncoderMode, -} - -pub struct EncoderBuilder { - state: S, -} - -impl EncoderBuilder { - pub fn new() -> Self { - EncoderBuilder { state: NoBuilder } - } - - pub fn java_script(self) -> EncoderBuilder { - EncoderBuilder { - state: JavaScriptEncoderBuilder { - ascii_only: false, - mode: JavaScriptEncoderMode::Block, - }, - } - } - - pub fn xml(self) -> EncoderBuilder { - EncoderBuilder { - state: XmlEncoderBuilder { - mode: XmlEncoderMode::All, - }, - } - } - - pub fn uri(self) -> EncoderBuilder { - EncoderBuilder { - state: UriEncoderBuilder { - mode: UriEncoderMode::FullUri, - }, - } - } -} - -impl EncoderBuilder { - pub fn with_ascii_only(self, ascii_only: bool) -> Self { - let mut state = self.state; - state.ascii_only = ascii_only; - Self { state } - } - - pub fn with_encoder_mode(self, mode: JavaScriptEncoderMode) -> Self { - let mut state = self.state; - state.mode = mode; - Self { state } - } - - pub fn build(self) -> JavaScriptEncoder { - JavaScriptEncoder::new(self.state.mode, self.state.ascii_only) - } -} - -impl EncoderBuilder { - pub fn with_mode(self, mode: XmlEncoderMode) -> Self { - let mut state = self.state; - state.mode = mode; - Self { state } - } - - pub fn build(self) -> XmlEncoder { - XmlEncoder::new(self.state.mode) - } -} - -impl EncoderBuilder { - pub fn with_mode(self, mode: UriEncoderMode) -> Self { - let mut state = self.state; - state.mode = mode; - Self { state } - } - - pub fn build(self) -> UriEncoder { - UriEncoder::new(self.state.mode) - } -} - -#[cfg(test)] -mod test { - use crate::builder::EncoderBuilder; - use crate::java_script_encoder::JavaScriptEncoderMode; - use crate::java_script_encoder::JavaScriptEncoderMode::Attribute; - use crate::uri_encoder::UriEncoderMode; - use crate::xml_encoder::XmlEncoderMode; - - #[test] - fn test_build_java_script_encoder() { - let encoder = EncoderBuilder::new() - .java_script() - .with_encoder_mode(JavaScriptEncoderMode::Attribute) - .with_ascii_only(true); - - assert_eq!(true, encoder.state.ascii_only); - assert_eq!(Attribute, encoder.state.mode); - - let encoder = encoder.build(); - - assert_eq!(true, encoder.ascii_only); - assert_eq!(true, encoder.hex_encode_quotes); - } - - #[test] - fn test_build_xml_encoder() { - let encoder = EncoderBuilder::new() - .xml() - .with_mode(XmlEncoderMode::Content); - - assert_eq!(XmlEncoderMode::Content, encoder.state.mode); - } - - #[test] - fn test_build_uri_encoder() { - let encoder = EncoderBuilder::new().uri().with_mode(UriEncoderMode::Component); - - assert_eq!(UriEncoderMode::Component, encoder.state.mode); - } -} diff --git a/src/java_script_encoder.rs b/src/java_script_encoder.rs index 05dd6ef..3472443 100644 --- a/src/java_script_encoder.rs +++ b/src/java_script_encoder.rs @@ -1,8 +1,19 @@ use crate::common::{char_bucket, char_mask, encode_as_hex_byte, encode_as_unicode}; +use type_state_builder::TypeStateBuilder; +#[derive(TypeStateBuilder)] +#[builder(impl_into)] +pub struct JavaScriptEncoderConfig { + #[builder(required)] + mode: JavaScriptEncoderMode, -#[derive(Debug, Clone, Copy, PartialEq)] + #[builder(required)] + ascii_only: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum JavaScriptEncoderMode { + #[default] Source, Block, Html, @@ -17,33 +28,35 @@ pub struct JavaScriptEncoder { } impl JavaScriptEncoder { - pub fn new(mode: JavaScriptEncoderMode, ascii_only: bool) -> Self { + pub fn new(config: JavaScriptEncoderConfig) -> Self { let mut valid_masks = [ 0, u32::MAX & !(char_mask('\'') | char_mask('"')), u32::MAX & !char_mask('\\'), - if ascii_only { + if config.ascii_only { u32::MAX & !char_mask(127 as char) } else { u32::MAX }, ]; // For BLOCK or HTML mode, also escape '/' and '-' - if mode == JavaScriptEncoderMode::Block || mode == JavaScriptEncoderMode::Html { + if config.mode == JavaScriptEncoderMode::Block || config.mode == JavaScriptEncoderMode::Html + { valid_masks[1] &= !(char_mask('/') | char_mask('-')); } // For all modes except SOURCE, escape '&' - if mode != JavaScriptEncoderMode::Source { + if config.mode != JavaScriptEncoderMode::Source { valid_masks[1] &= !char_mask('&'); } #[cfg(debug_assertions)] crate::common::dump_masks_to_ascii(&valid_masks); - let hex_encode_quotes = mode == JavaScriptEncoderMode::Attribute || mode == JavaScriptEncoderMode::Html; + let hex_encode_quotes = config.mode == JavaScriptEncoderMode::Attribute + || config.mode == JavaScriptEncoderMode::Html; JavaScriptEncoder { - ascii_only, + ascii_only: config.ascii_only, valid_masks, hex_encode_quotes, } @@ -122,8 +135,9 @@ impl JavaScriptEncoder { #[cfg(test)] mod test { - use crate::java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderMode}; + use crate::JavaScriptEncoderConfig; + fn generic_tests(encoder: &JavaScriptEncoder) { assert_eq!("\\b", encoder.encode("\u{8}")); assert_eq!("\\t", encoder.encode("\t")); @@ -146,7 +160,12 @@ mod test { } #[test] fn t_java_script_block_ascii_only() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Block, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Block) + .ascii_only(true) + .build(), + ); assert_eq!("\\\"", encoder.encode("\"")); assert_eq!("\\\'", encoder.encode("\'")); assert_eq!("\\/", encoder.encode("/")); @@ -158,7 +177,12 @@ mod test { #[test] fn t_java_script_block_ascii_extended() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Block, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Block) + .ascii_only(false) + .build(), + ); assert_eq!("\\\"", encoder.encode("\"")); assert_eq!("\\\'", encoder.encode("\'")); assert_eq!("\\x26", encoder.encode("&")); @@ -169,7 +193,12 @@ mod test { #[test] fn t_java_script_source_ascii_only() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Source, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Source) + .ascii_only(true) + .build(), + ); assert_eq!("\\\"", encoder.encode("\"")); assert_eq!("\\\'", encoder.encode("\'")); assert_eq!("/", encoder.encode("/")); @@ -179,7 +208,12 @@ mod test { #[test] fn t_java_script_source_ascii_extended() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Source, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Source) + .ascii_only(false) + .build(), + ); assert_eq!("\\\"", encoder.encode("\"")); assert_eq!("\\\'", encoder.encode("\'")); assert_eq!("/", encoder.encode("/")); @@ -189,7 +223,12 @@ mod test { #[test] fn t_java_script_html_ascii_only() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Html, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Html) + .ascii_only(true) + .build(), + ); assert_eq!("\\x22", encoder.encode("\"")); assert_eq!("\\x27", encoder.encode("\'")); assert_eq!("\\/", encoder.encode("/")); @@ -201,7 +240,12 @@ mod test { #[test] fn t_java_script_html_ascii_extended() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Html, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Html) + .ascii_only(false) + .build(), + ); assert_eq!("\\x22", encoder.encode("\"")); assert_eq!("\\x27", encoder.encode("\'")); assert_eq!("\\/", encoder.encode("/")); @@ -213,7 +257,12 @@ mod test { #[test] fn t_java_script_attribute_ascii_only() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Attribute, true); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Attribute) + .ascii_only(true) + .build(), + ); assert_eq!("\\x22", encoder.encode("\"")); assert_eq!("\\x27", encoder.encode("\'")); assert_eq!("/", encoder.encode("/")); @@ -224,7 +273,12 @@ mod test { #[test] fn t_java_script_attribute_ascii_extended() { - let encoder = JavaScriptEncoder::new(JavaScriptEncoderMode::Attribute, false); + let encoder = JavaScriptEncoder::new( + JavaScriptEncoderConfig::builder() + .mode(JavaScriptEncoderMode::Attribute) + .ascii_only(false) + .build(), + ); assert_eq!("\\x22", encoder.encode("\"")); assert_eq!("\\x27", encoder.encode("\'")); assert_eq!("/", encoder.encode("/")); @@ -232,5 +286,4 @@ mod test { generic_tests(&encoder); ascii_extended_tests(&encoder); } - } diff --git a/src/lib.rs b/src/lib.rs index b5d6986..725989d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,11 @@ - mod common; -mod uri_encoder; mod java_script_encoder; +mod uri_encoder; mod xml_encoder; -mod builder; - -pub use builder::EncoderBuilder; -pub use xml_encoder::{XmlEncoderMode, XmlEncoder}; -pub use java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderMode}; -pub use uri_encoder::{UriEncoderMode, UriEncoder}; +pub use java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderConfig, JavaScriptEncoderMode}; +pub use uri_encoder::{UriEncoder, UriEncoderConfig, UriEncoderMode}; +pub use xml_encoder::{XmlEncoder, XmlEncoderConfig, XmlEncoderMode}; #[cfg(debug_assertions)] pub use common::dump_masks_to_ascii; - - - diff --git a/src/uri_encoder.rs b/src/uri_encoder.rs index 47ef046..eab70f4 100644 --- a/src/uri_encoder.rs +++ b/src/uri_encoder.rs @@ -1,4 +1,5 @@ -use crate::common::{HEX_MASK, HEX_SHIFT, U_HEX, char_bucket, char_mask, create_mask}; +use crate::common::{char_bucket, char_mask, create_mask, HEX_MASK, HEX_SHIFT, U_HEX}; +use type_state_builder::TypeStateBuilder; /// 0111_1111_1111 --> highest 2x utf 8 bytes /// 0000_1000_0000 --> most sig. utf8 byte @@ -16,8 +17,15 @@ const UTF8_4_BYTE_FIRST_MSB: u32 = 0b_0000_1111_0000; const UTF8_SHIFT: u32 = 0b_0000_0000_0110; const UTF8_MASK: u32 = 0b_0000_0011_1111; -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(TypeStateBuilder)] +pub struct UriEncoderConfig { + #[builder(required)] + mode: UriEncoderMode, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub enum UriEncoderMode { + #[default] Component, FullUri, } @@ -26,7 +34,7 @@ pub struct UriEncoder { valid_masks: [u32; 4], } impl UriEncoder { - pub fn new(mode: UriEncoderMode) -> Self { + pub fn new(config: UriEncoderConfig) -> Self { // starting from '0' + 10 bits (aka 0-9) let one_to_nine = ((1u32 << 10u32) - 1u32) << ('0' as u32 & 31u32); @@ -40,7 +48,7 @@ impl UriEncoder { let uri_unreserved_bucket2 = uppercase_a_z | char_mask('_'); let uri_unreserved_bucket3 = lowercase_a_z | char_mask('~'); - match mode { + match config.mode { UriEncoderMode::Component => { let valid_masks = [ 0, @@ -138,6 +146,7 @@ impl UriEncoder { #[cfg(test)] mod test { use crate::uri_encoder::{UriEncoder, UriEncoderMode}; + use crate::UriEncoderConfig; fn shared_test_cases(encoder: &UriEncoder) { assert_eq!("abcABC123", encoder.encode("abcABC123")); @@ -158,7 +167,11 @@ mod test { #[test] fn test_component_encode() { - let encoder = UriEncoder::new(UriEncoderMode::Component); + let encoder = UriEncoder::new( + UriEncoderConfig::builder() + .mode(UriEncoderMode::Component) + .build(), + ); assert_eq!("%3A", encoder.encode(":")); assert_eq!("%2F", encoder.encode("/")); assert_eq!("%3F", encoder.encode("?")); @@ -182,7 +195,11 @@ mod test { #[test] fn test_full_uri_encode() { - let encoder = UriEncoder::new(UriEncoderMode::FullUri); + let encoder = UriEncoder::new( + UriEncoderConfig::builder() + .mode(UriEncoderMode::FullUri) + .build(), + ); assert_eq!( "http://www.owasp.org/index.php?foo=bar&baz#fragment", encoder.encode("http://www.owasp.org/index.php?foo=bar&baz#fragment") diff --git a/src/xml_encoder.rs b/src/xml_encoder.rs index 7e49fc1..93790be 100644 --- a/src/xml_encoder.rs +++ b/src/xml_encoder.rs @@ -1,7 +1,15 @@ use crate::common::{char_bucket, char_mask, create_mask}; +use type_state_builder::TypeStateBuilder; -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(TypeStateBuilder)] +pub struct XmlEncoderConfig { + #[builder(required)] + mode: XmlEncoderMode, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub enum XmlEncoderMode { + #[default] All, Content, Attribute, @@ -14,18 +22,18 @@ pub struct XmlEncoder { } impl XmlEncoder { - pub fn new(mode: XmlEncoderMode) -> Self { + pub fn new(config: XmlEncoderConfig) -> Self { let base_mask = char_mask('\r') | char_mask('\t') | char_mask('\n'); - match mode { + match config.mode { XmlEncoderMode::All => { let to_be_encoded_mask = create_mask(['&', '<', '>', '\'', '"']); let valid_masks = [ base_mask, u32::MAX & !to_be_encoded_mask, u32::MAX, - u32::MAX + u32::MAX, ]; - Self {valid_masks} + Self { valid_masks } } XmlEncoderMode::Content => { let to_be_encoded_mask = create_mask(['&', '<', '>']); @@ -33,9 +41,9 @@ impl XmlEncoder { base_mask, u32::MAX & !to_be_encoded_mask, u32::MAX, - u32::MAX + u32::MAX, ]; - Self {valid_masks} + Self { valid_masks } } XmlEncoderMode::Attribute => { let to_be_encoded_mask = create_mask(['&', '<', '\'', '"']); @@ -43,9 +51,9 @@ impl XmlEncoder { base_mask, u32::MAX & !to_be_encoded_mask, u32::MAX, - u32::MAX + u32::MAX, ]; - Self {valid_masks} + Self { valid_masks } } XmlEncoderMode::SingleQuotedAttribute => { let to_be_encoded_mask = create_mask(['&', '<', '\'']); @@ -53,9 +61,9 @@ impl XmlEncoder { base_mask, u32::MAX & !to_be_encoded_mask, u32::MAX, - u32::MAX + u32::MAX, ]; - Self {valid_masks} + Self { valid_masks } } XmlEncoderMode::DoubleQuotedAttribute => { let to_be_encoded_mask = create_mask(['&', '<', '"']); @@ -63,9 +71,9 @@ impl XmlEncoder { base_mask, u32::MAX & !to_be_encoded_mask, u32::MAX, - u32::MAX + u32::MAX, ]; - Self {valid_masks} + Self { valid_masks } } } } @@ -131,6 +139,7 @@ impl XmlEncoder { #[cfg(test)] mod test { use crate::xml_encoder::{XmlEncoder, XmlEncoderMode}; + use crate::XmlEncoderConfig; fn generic_tests(encoder: &XmlEncoder) { assert_eq!("\u{fffd}", encoder.encode("\u{fffd}")); @@ -138,7 +147,11 @@ mod test { } #[test] fn test_all_encode() { - let encoder = XmlEncoder::new(XmlEncoderMode::All); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::All) + .build(), + ); assert_eq!("&", encoder.encode("&")); assert_eq!(">", encoder.encode(">")); assert_eq!("<", encoder.encode("<")); @@ -149,7 +162,11 @@ mod test { #[test] fn test_content_encode() { - let encoder = XmlEncoder::new(XmlEncoderMode::Content); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::Content) + .build(), + ); assert_eq!("&", encoder.encode("&")); assert_eq!(">", encoder.encode(">")); assert_eq!("<", encoder.encode("<")); @@ -160,7 +177,11 @@ mod test { #[test] fn test_attribute_encode() { - let encoder = XmlEncoder::new(XmlEncoderMode::Attribute); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::Attribute) + .build(), + ); assert_eq!("&", encoder.encode("&")); assert_eq!(">", encoder.encode(">")); assert_eq!("<", encoder.encode("<")); @@ -171,7 +192,11 @@ mod test { #[test] fn test_single_quoted_encode() { - let encoder = XmlEncoder::new(XmlEncoderMode::SingleQuotedAttribute); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::SingleQuotedAttribute) + .build(), + ); assert_eq!("&", encoder.encode("&")); assert_eq!(">", encoder.encode(">")); assert_eq!("<", encoder.encode("<")); @@ -184,7 +209,11 @@ mod test { #[test] fn test_double_quoted_encode() { - let encoder = XmlEncoder::new(XmlEncoderMode::DoubleQuotedAttribute); + let encoder = XmlEncoder::new( + XmlEncoderConfig::builder() + .mode(XmlEncoderMode::DoubleQuotedAttribute) + .build(), + ); assert_eq!("&", encoder.encode("&")); assert_eq!(">", encoder.encode(">")); assert_eq!("<", encoder.encode("<"));