Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add yew-validation crate #1376

Merged
merged 5 commits into from
Aug 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"yew-components",
"yew-functional",
"yew-macro",
"yew-validation",

# Router
"yew-router",
Expand Down
3 changes: 3 additions & 0 deletions ci/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ set -euxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_
(cd yewtil && cargo test)

(cd yew-components && cargo test)

(cd yew-validation \
&& cargo test --target wasm32-unknown-unknown --features wasm_test)
23 changes: 23 additions & 0 deletions yew-validation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "yew-validation"
version = "0.1.0"
authors = ["Philip Peterson <[email protected]>"]
edition = "2018"
license = "MIT/Apache-2.0"
keywords = ["web", "yew", "validation"]
categories = ["text-processing", "parsing", "web-programming"]
description = "Utilities for Yew to validate tag names and attributes"
repository = "https://github.com/yewstack/yew"

[dependencies]
wasm-bindgen = { version = "0.2.58", optional = true }

# Compat with building yew with wasm-pack support.
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.58"

[dev-dependencies]
wasm-bindgen-test = "0.3.9"

[features]
wasm_test = []
214 changes: 214 additions & 0 deletions yew-validation/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
//! Utility library for the Yew frontend web framework to handle validating strings relating
//! to HTML/SVG/MathML tags.

/// Returns true when the character provided is a "control" as defined
/// in [the WhatWG spec](https://infra.spec.whatwg.org/#control)
fn is_control(c: char) -> bool {
match c {
'\u{007F}'..='\u{009F}' => true,
_ => is_c0_control(c),
}
}

/// Returns true when the character provided is a "c0 control" as defined
/// in [the WhatWG spec](https://infra.spec.whatwg.org/#c0-control)
fn is_c0_control(c: char) -> bool {
match c {
'\u{0000}'..='\u{001F}' => true,
_ => false,
}
}

/// Returns true when the string provided is a "noncharacter" as defined
/// in [the WhatWG spec](https://infra.spec.whatwg.org/#noncharacter)
fn is_noncharacter(c: char) -> bool {
match c {
'\u{FDD0}'..='\u{FDEF}' => true,
'\u{FFFE}' | '\u{FFFF}' | '\u{1FFFE}' | '\u{1FFFF}' | '\u{2FFFE}' | '\u{2FFFF}'
| '\u{3FFFE}' | '\u{3FFFF}' | '\u{4FFFE}' | '\u{4FFFF}' | '\u{5FFFE}' | '\u{5FFFF}'
| '\u{6FFFE}' | '\u{6FFFF}' | '\u{7FFFE}' | '\u{7FFFF}' | '\u{8FFFE}' | '\u{8FFFF}'
| '\u{9FFFE}' | '\u{9FFFF}' | '\u{AFFFE}' | '\u{AFFFF}' | '\u{BFFFE}' | '\u{BFFFF}'
| '\u{CFFFE}' | '\u{CFFFF}' | '\u{DFFFE}' | '\u{DFFFF}' | '\u{EFFFE}' | '\u{EFFFF}'
| '\u{FFFFE}' | '\u{FFFFF}' | '\u{10FFFE}' | '\u{10FFFF}' => true,
_ => false,
}
}

/// Returns true when the string provided is a valid "attribute name" as defined
/// in [the WhatWG spec](https://html.spec.whatwg.org/multipage/syntax.html#syntax-attribute-name)
pub fn is_valid_html_attribute_name(attr: &str) -> bool {
for c in attr.chars() {
if is_noncharacter(c)
|| is_control(c)
|| c == '\u{0020}'
|| c == '\u{0022}'
|| c == '\u{0027}'
|| c == '\u{003E}'
|| c == '\u{002F}'
|| c == '\u{003D}'
{
return false;
}
}
true
}

/// Returns true when the character provided is a valid PCENChar as defined
/// in [the WhatWG spec](https://html.spec.whatwg.org/multipage/custom-elements.html#prod-pcenchar)
fn is_pcen_char(c: char) -> bool {
match c {
'-' | '.' | '0'..='9' | 'a'..='z' | '_' => true,
'\u{B7}' => true,
'\u{C0}'..='\u{D6}' => true,
'\u{D8}'..='\u{F6}' => true,
'\u{F8}'..='\u{37D}' => true,
'\u{37F}'..='\u{1FFF}' => true,
'\u{200C}'..='\u{200D}' => true,
'\u{203F}'..='\u{2040}' => true,
'\u{2070}'..='\u{218F}' => true,
'\u{2C00}'..='\u{2FEF}' => true,
'\u{3001}'..='\u{D7FF}' => true,
'\u{F900}'..='\u{FDCF}' => true,
'\u{FDF0}'..='\u{FFFD}' => true,
'\u{10000}'..='\u{EFFFF}' => true,
_ => false,
}
}

/// Returns true when the tag name provided would be a valid "custom element" per
/// [the WhatWG spec](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name).
/// Only technically returns correct results if called with a string that is not one of the following:
/// - annotation-xml
/// - color-profile
/// - font-face
/// - font-face-src
/// - font-face-uri
/// - font-face-format
/// - font-face-name
/// - missing-glyph
/// But, given the way it is used in this file, as of this writing, this limitation does not affect the
/// behavior of the program.
fn is_valid_html_custom_element_name(tag: &str) -> bool {
let mut chars = tag.chars();
let first_char = chars.next();

match first_char {
None => false,
Some(first_char) => {
// must begin with [a-z]
if first_char < 'a' || first_char > 'z' {
return false;
}

let mut seen_hyphen = false;
for c in chars {
if c == '-' {
seen_hyphen = true
}

// all characters must be valid PCENChar's
if !is_pcen_char(c) {
return false;
}
}

// must contain at least one hyphen
seen_hyphen
}
}
}

/// Returns true when the tag name provided looks like a valid non-custom HTML element or valid SVG element.
/// There's no official spec here, it's just arbitrary.
fn resembles_standard_html_element_name(tag: &str) -> bool {
// must contain at least one character
if tag.is_empty() {
return false;
}

let mut saw_non_hyphen = false;
for c in tag.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' => saw_non_hyphen = true,
'-' => {}
_ => {
return false;
}
}
}

saw_non_hyphen
}

/// Returns true when you could validly construct a tag using this name in an HTML document
pub fn is_valid_sgml_tag(tag: &str) -> bool {
resembles_standard_html_element_name(tag) || is_valid_html_custom_element_name(tag)
}

#[cfg(test)]
mod tests {
use super::*;

#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};

#[cfg(feature = "wasm_test")]
wasm_bindgen_test_configure!(run_in_browser);

#[test]
fn valid_custom_element() {
assert_eq!(is_valid_html_custom_element_name("foo-bar"), true);
assert_eq!(is_valid_html_custom_element_name("foo-"), true);
assert_eq!(is_valid_html_custom_element_name("bar-baz"), true);
}

#[test]
fn invalid_custom_element() {
assert_eq!(is_valid_html_custom_element_name("foobar"), false);
assert_eq!(is_valid_html_custom_element_name("-bar"), false);
assert_eq!(is_valid_html_custom_element_name("foo bar"), false);
assert_eq!(is_valid_html_custom_element_name(""), false);
assert_eq!(is_valid_html_custom_element_name("foo\nbar"), false);
assert_eq!(is_valid_html_custom_element_name("-"), false);
}

#[test]
fn valid_html_element() {
assert_eq!(resembles_standard_html_element_name("section"), true);
assert_eq!(resembles_standard_html_element_name("h2"), true);
assert_eq!(resembles_standard_html_element_name("applet"), true);
assert_eq!(resembles_standard_html_element_name("appLET"), true);
assert_eq!(resembles_standard_html_element_name("aPPlet"), true);
assert_eq!(resembles_standard_html_element_name("foo-bar"), true);
}

#[test]
fn invalid_html_element() {
assert_eq!(resembles_standard_html_element_name(" foo"), false);
assert_eq!(resembles_standard_html_element_name("foo "), false);
assert_eq!(resembles_standard_html_element_name("-"), false);
assert_eq!(resembles_standard_html_element_name("!doctype"), false);
}

#[test]
fn valid_html_attribute() {
assert_eq!(is_valid_html_attribute_name("-foo-bar"), true);
assert_eq!(is_valid_html_attribute_name("data-foobar"), true);
assert_eq!(is_valid_html_attribute_name("foo<bar"), true); // shocking but true
}

#[test]
fn invalid_html_attribute() {
assert_eq!(is_valid_html_attribute_name("foo=bar"), false);
assert_eq!(is_valid_html_attribute_name("\"foo\""), false);
assert_eq!(is_valid_html_attribute_name("foo bar"), false);
assert_eq!(is_valid_html_attribute_name("foo>bar"), false);
}

#[test]
fn invalid_sgml_tag() {
assert_eq!(is_valid_sgml_tag("f>bar"), false);
assert_eq!(is_valid_sgml_tag("f<bar"), false);
assert_eq!(is_valid_sgml_tag("/>"), false);
}
}
1 change: 1 addition & 0 deletions yew/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ toml = { version = "0.5", optional = true }
wasm-bindgen = { version = "0.2.60", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
yew-macro = { version = "0.17.0", path = "../yew-macro" }
yew-validation = { version = "0.1.0", path = "../yew-validation" }

[dependencies.web-sys]
version = "0.3"
Expand Down