use crate::core::{get_protocol, html_decode, yufull, NamedRefSet, BLACKLIST};
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
fn css_encode(c: char) -> String {
format!("\\{:x} ", c as u32)
}
fn css_blacklist(s: &str) -> String {
let chars: Vec<char> = s.chars().collect();
let mut out = String::with_capacity(s.len());
let mut i = 0;
let n = chars.len();
while i < n {
if i + 3 < n
&& chars[i] == 'u'
&& chars[i + 1] == 'r'
&& chars[i + 2] == 'l'
&& is_open_paren(chars[i + 3])
{
let mut j = i + 3;
while j < n && is_open_paren(chars[j]) {
j += 1;
}
out.push_str("-x-");
out.extend(&chars[i..j]);
i = j;
} else {
out.push(chars[i]);
i += 1;
}
}
out
}
fn is_open_paren(c: char) -> bool {
matches!(c, '(' | '\u{207D}' | '\u{208D}')
}
fn escape_chars(s: &str, pred: impl Fn(char) -> bool) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if pred(c) {
out.push_str(&css_encode(c));
} else {
out.push(c);
}
}
out
}
fn is_double_quoted_char(c: char) -> bool {
let n = c as u32;
n <= 0x1F || n == 0x7F || matches!(c, '[' | ']' | '{' | '}' | '\\' | '"' | '<')
}
fn is_single_quoted_char(c: char) -> bool {
let n = c as u32;
n <= 0x1F || n == 0x7F || matches!(c, '[' | ']' | '{' | '}' | '\\' | '\'' | '<')
}
fn css_valid_value(s: &str) -> bool {
if s.is_empty() {
return true;
}
if s.eq_ignore_ascii_case("!important") {
return true;
}
if ident_or_hexcolor(s) {
return true;
}
number_with_unit(s)
}
fn ident_or_hexcolor(s: &str) -> bool {
let mut chars = s.chars().peekable();
if chars.peek() == Some(&'#') {
chars.next();
}
let rest: String = chars.collect();
if rest.is_empty() {
return false;
}
if !rest
.chars()
.all(|c| c == '-' || c == '_' || c.is_ascii_alphanumeric())
{
return false;
}
let trimmed = rest.trim_start_matches('-');
let head: String = trimmed.chars().take("expression".len()).collect();
if head.eq_ignore_ascii_case("expression") {
return false;
}
true
}
fn number_with_unit(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
let n = bytes.len();
if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let int_start = i;
while i < n && bytes[i].is_ascii_digit() {
i += 1;
}
let int_digits = i - int_start;
let mut has_fraction = false;
if i < n && bytes[i] == b'.' {
i += 1;
let frac_start = i;
while i < n && bytes[i].is_ascii_digit() {
i += 1;
}
if i == frac_start {
return false; }
has_fraction = true;
}
if !has_fraction && int_digits == 0 {
return false;
}
let unit = &s[i..];
unit_valid(unit)
}
fn unit_valid(unit: &str) -> bool {
if unit.is_empty() {
return true;
}
const UNITS: [&str; 15] = [
"em", "rem", "ex", "ch", "cm", "mm", "in", "px", "pt", "pc", "%", "vh", "vw", "vmin",
"vmax",
];
UNITS.iter().any(|u| unit.eq_ignore_ascii_case(u))
}
pub fn css_value(s: &str) -> String {
let decoded = html_decode(s, NamedRefSet::Sensitive, false);
if css_valid_value(&decoded) {
decoded
} else {
let escaped = css_blacklist(&escape_chars(&decoded, is_single_quoted_char));
format!(";-x:'{escaped}';-v:")
}
}
pub fn css_double_quoted(s: &str) -> String {
let decoded = html_decode(s, NamedRefSet::Sensitive, false);
css_blacklist(&escape_chars(&decoded, is_double_quoted_char))
}
pub fn css_single_quoted(s: &str) -> String {
let decoded = html_decode(s, NamedRefSet::Sensitive, false);
css_blacklist(&escape_chars(&decoded, is_single_quoted_char))
}
fn css_url(s: &str) -> String {
let decoded = html_decode(s, NamedRefSet::Sensitive, false);
let encoded = yufull(&decoded);
match get_protocol(&encoded) {
Some(p) if BLACKLIST.contains(&p.to_lowercase().as_str()) => format!("##{encoded}"),
_ => encoded,
}
}
pub fn css_unquoted_url(s: &str) -> String {
let url = css_url(s);
let mut out = String::with_capacity(url.len());
for c in url.chars() {
match c {
'\'' => out.push_str("\\27 "),
'(' => out.push_str("%28"),
')' => out.push_str("%29"),
_ => out.push(c),
}
}
out
}
pub fn css_double_quoted_url(s: &str) -> String {
css_url(s)
}
pub fn css_single_quoted_url(s: &str) -> String {
css_url(s).replace('\'', "\\27 ")
}