Files
moli/moli-parser/src/lib.rs
T
ldm0 a9830ab24b fix(csp): reject nonnonceable script elements
Apply the shared script nonceability check to parser scripts, buffered preloads and runtime script preparation. Suspicious script attribute names and values must not authorize execution merely because a nonce matches. Includes parser, preload and dynamic-preparation regressions.

The attribute checks follow the [CSP3 nonceability algorithm](https://www.w3.org/TR/CSP3/#is-element-nonceable), including case-insensitive `<link`, `<script` and `<style` matches and parser duplicate attributes.

Validation: all branch checks passed. Summary [ 115.690s] 18925 tests run: 18925 passed (9 slow), 16 skipped
2026-09-29 16:27:50 +08:00

128 lines
4.6 KiB
Rust

//! HTML/XML parsing and parser-neutral handoff types for Moli.
//!
//! This crate owns document construction, parser streaming, and parse-time
//! discovery outputs without depending on the renderer implementation.
mod html;
mod html_input;
mod live_target;
mod script_planning;
mod session;
mod stream;
mod stylesheet_blocking;
mod xml;
mod xml_stream;
mod xml_tree_viewer;
use std::collections::{HashMap, HashSet, VecDeque};
use html5ever::tree_builder::QuirksMode;
use moli_dom::native::NativeNodeId;
use moli_stylesheet_blocking::{
DocumentBlockingStylesheetSignature, DocumentOwnedBlockingStylesheetDiscoveryInput,
};
pub use html::{
DocumentStream, HtmlParser, ParserBlockingStylesheetPause,
ParserCustomElementConstructionHandoff, ParserFinishDiscoverySignals, ParserInputContext,
ParserInputQueue, ParserInputSession, ParserPumpOutcome, ParserPumpStep,
ParserScriptElementStateTransition, ParserScriptHandoff, ParserScriptNoExecutionOutcome,
ParserScriptPreparationFailure, ParserStreamDocumentSnapshot, ParserYield,
};
pub use live_target::{
ParserDomMutation, ParserDomMutationConsumer, ParserDomReadConsumer,
ParserElementCreationConsumer, ParserElementCreationRequest, ParserMutationEffectConsumer,
};
pub use script_planning::{
ParserPlanningReadView, ParserScriptRead, PrepareScriptOutcome, PreparedImportMap,
PreparedImportMapSource, PreparedScript, ScriptFetchMetadata, ScriptFilterSkipReason,
ScriptSource, build_prepared_import_map, build_prepared_script, classify_parser_script,
};
pub use xml::XmlParser;
pub use xml_stream::XmlDocumentStream;
const STREAM_CHUNK_BYTES: usize = 8 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ParserSourcePosition {
line: u64,
column: u64,
}
impl ParserSourcePosition {
const UNKNOWN: Self = Self { line: 0, column: 0 };
const fn line_only(line: u64) -> Self {
Self { line, column: 0 }
}
}
impl Default for ParserSourcePosition {
fn default() -> Self {
Self::line_only(1)
}
}
#[derive(Debug)]
struct HtmlTreeSinkState {
html_quirks_mode: QuirksMode,
ready_parser_scripts: VecDeque<NativeNodeId>,
discovered_async_prefetch_candidates: VecDeque<NativeNodeId>,
discovered_modulepreload_link_candidates: VecDeque<NativeNodeId>,
parser_meta_csp_candidates: HashSet<NativeNodeId>,
discovered_parser_meta_csp_candidates: VecDeque<NativeNodeId>,
defined_autonomous_custom_elements: HashSet<String>,
pending_custom_element_construction_handoffs:
VecDeque<html::ParserCustomElementConstructionHandoff>,
pending_blocking_stylesheet_pause: Option<NativeNodeId>,
discovered_blocking_stylesheet_inputs: VecDeque<DocumentOwnedBlockingStylesheetDiscoveryInput>,
captured_blocking_stylesheet_nodes: HashSet<NativeNodeId>,
captured_blocking_stylesheet_signatures: HashSet<DocumentBlockingStylesheetSignature>,
finishing_tree_builder: bool,
non_nonceable_parser_scripts: HashSet<NativeNodeId>,
current_position: ParserSourcePosition,
script_start_positions: HashMap<NativeNodeId, ParserSourcePosition>,
}
impl Default for HtmlTreeSinkState {
fn default() -> Self {
Self {
html_quirks_mode: QuirksMode::NoQuirks,
ready_parser_scripts: VecDeque::new(),
discovered_async_prefetch_candidates: VecDeque::new(),
discovered_modulepreload_link_candidates: VecDeque::new(),
parser_meta_csp_candidates: HashSet::new(),
discovered_parser_meta_csp_candidates: VecDeque::new(),
defined_autonomous_custom_elements: HashSet::new(),
pending_custom_element_construction_handoffs: VecDeque::new(),
pending_blocking_stylesheet_pause: None,
discovered_blocking_stylesheet_inputs: VecDeque::new(),
captured_blocking_stylesheet_nodes: HashSet::new(),
captured_blocking_stylesheet_signatures: HashSet::new(),
finishing_tree_builder: false,
non_nonceable_parser_scripts: HashSet::new(),
current_position: ParserSourcePosition::default(),
script_start_positions: HashMap::new(),
}
}
}
pub(crate) fn html_chunks(html: &str) -> impl Iterator<Item = &str> {
let mut chunks = Vec::new();
let mut start = 0;
while start < html.len() {
let mut end = (start + STREAM_CHUNK_BYTES).min(html.len());
while end > start && !html.is_char_boundary(end) {
end -= 1;
}
if end == start {
end = html.len();
}
chunks.push(&html[start..end]);
start = end;
}
chunks.into_iter()
}