style: Preserve CSS input exactly during sanitization.

This avoids the mutation due to the different serialization in some cases.

Differential Revision: https://phabricator.services.mozilla.com/D56732
This commit is contained in:
Emilio Cobos Álvarez 2019-12-12 13:57:54 +00:00
parent e8a3a71080
commit 02c30bccbd
2 changed files with 87 additions and 4 deletions

View file

@ -60,6 +60,7 @@ pub use self::rules_iterator::{NestedRuleIterationCondition, RulesIterator};
pub use self::style_rule::StyleRule; pub use self::style_rule::StyleRule;
pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet};
pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets};
pub use self::stylesheet::{SanitizationData, SanitizationKind};
pub use self::supports_rule::SupportsRule; pub use self::supports_rule::SupportsRule;
pub use self::viewport_rule::ViewportRule; pub use self::viewport_rule::ViewportRule;

View file

@ -81,6 +81,7 @@ impl StylesheetContents {
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
line_number_offset: u32, line_number_offset: u32,
use_counters: Option<&UseCounters>, use_counters: Option<&UseCounters>,
sanitization_data: Option<&mut SanitizationData>,
) -> Self { ) -> Self {
let namespaces = RwLock::new(Namespaces::default()); let namespaces = RwLock::new(Namespaces::default());
let (rules, source_map_url, source_url) = Stylesheet::parse_rules( let (rules, source_map_url, source_url) = Stylesheet::parse_rules(
@ -94,6 +95,7 @@ impl StylesheetContents {
quirks_mode, quirks_mode,
line_number_offset, line_number_offset,
use_counters, use_counters,
sanitization_data,
); );
Self { Self {
@ -341,6 +343,71 @@ impl StylesheetInDocument for DocumentStyleSheet {
} }
} }
/// The kind of sanitization to use when parsing a stylesheet.
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SanitizationKind {
/// Perform no sanitization.
None,
/// Allow only @font-face, style rules, and @namespace.
Standard,
/// Allow everything but conditional rules.
NoConditionalRules,
}
impl SanitizationKind {
fn allows(self, rule: &CssRule) -> bool {
debug_assert_ne!(self, SanitizationKind::None);
// NOTE(emilio): If this becomes more complex (not filtering just by
// top-level rules), we should thread all the data through nested rules
// and such. But this doesn't seem necessary at the moment.
let is_standard = matches!(self, SanitizationKind::Standard);
match *rule {
CssRule::Document(..) |
CssRule::Media(..) |
CssRule::Supports(..) |
CssRule::Import(..) => false,
CssRule::FontFace(..) |
CssRule::Namespace(..) |
CssRule::Style(..) => true,
CssRule::Keyframes(..) |
CssRule::Page(..) |
CssRule::FontFeatureValues(..) |
CssRule::Viewport(..) |
CssRule::CounterStyle(..) => !is_standard,
}
}
}
/// A struct to hold the data relevant to style sheet sanitization.
#[derive(Debug)]
pub struct SanitizationData {
kind: SanitizationKind,
output: String,
}
impl SanitizationData {
/// Create a new input for sanitization.
#[inline]
pub fn new(kind: SanitizationKind) -> Option<Self> {
if matches!(kind, SanitizationKind::None) {
return None;
}
Some(Self {
kind,
output: String::new(),
})
}
/// Take the sanitized output.
#[inline]
pub fn take(self) -> String {
self.output
}
}
impl Stylesheet { impl Stylesheet {
/// Updates an empty stylesheet from a given string of text. /// Updates an empty stylesheet from a given string of text.
pub fn update_from_str( pub fn update_from_str(
@ -365,6 +432,7 @@ impl Stylesheet {
existing.contents.quirks_mode, existing.contents.quirks_mode,
line_number_offset, line_number_offset,
/* use_counters = */ None, /* use_counters = */ None,
/* sanitization_data = */ None,
); );
*existing.contents.url_data.write() = url_data; *existing.contents.url_data.write() = url_data;
@ -391,6 +459,7 @@ impl Stylesheet {
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
line_number_offset: u32, line_number_offset: u32,
use_counters: Option<&UseCounters>, use_counters: Option<&UseCounters>,
mut sanitization_data: Option<&mut SanitizationData>,
) -> (Vec<CssRule>, Option<String>, Option<String>) { ) -> (Vec<CssRule>, Option<String>, Option<String>) {
let mut rules = Vec::new(); let mut rules = Vec::new();
let mut input = ParserInput::new_with_line_number_offset(css, line_number_offset); let mut input = ParserInput::new_with_line_number_offset(css, line_number_offset);
@ -419,12 +488,24 @@ impl Stylesheet {
{ {
let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser); let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser);
while let Some(result) = iter.next() { loop {
let rule_start = iter.input.position().byte_index();
let result = match iter.next() {
Some(result) => result,
None => break,
};
match result { match result {
Ok(rule) => { Ok(rule) => {
// Use a fallible push here, and if it fails, just if let Some(ref mut data) = sanitization_data {
// fall out of the loop. This will cause the page to if !data.kind.allows(&rule) {
// be shown incorrectly, but it's better than OOMing. continue;
}
let end = iter.input.position().byte_index();
data.output.push_str(&css[rule_start..end]);
}
// Use a fallible push here, and if it fails, just fall
// out of the loop. This will cause the page to be
// shown incorrectly, but it's better than OOMing.
if rules.try_push(rule).is_err() { if rules.try_push(rule).is_err() {
break; break;
} }
@ -470,6 +551,7 @@ impl Stylesheet {
quirks_mode, quirks_mode,
line_number_offset, line_number_offset,
/* use_counters = */ None, /* use_counters = */ None,
/* sanitized_output = */ None,
); );
Stylesheet { Stylesheet {