A Token::Dimension whose unit begins with e or E followed by a digit serializes to text that re-parses as a Number with a different value. The unit is written straight after the numeric value with no separation, so a value of 1 with unit e5 becomes 1e5, which is scientific notation for the number 100000.
Reproducer
cssparser 0.37.0, default features.
use cssparser::{Parser, ParserInput, ToCss, Token};
fn main() {
let mut pi = ParserInput::new("1\\65 5");
let mut p = Parser::new(&mut pi);
let t = p.next().unwrap().clone();
// Dimension { value: 1.0, int_value: Some(1), unit: "e5" }
let s = t.to_css_string();
assert_eq!(s, "1e5");
let mut pi2 = ParserInput::new(&s);
let mut p2 = Parser::new(&mut pi2);
let t2 = p2.next().unwrap().clone();
// Number { value: 100000.0 }
assert!(matches!(t2, Token::Number { .. }));
}
Observed vs expected
Observed: the dimension serializes to 1e5. Re-parsing 1e5 yields Number { value: 100000.0 }, so the unit is gone and the value is scaled by 100000. With unit e90 the input serializes to 1e90, which re-parses to Number { value: inf }.
Expected: the serialized form re-parses to the same dimension. CSS Syntax Level 3 requires a serialized token to round-trip. The serializer already guards a unit that would collide with an identifier, so the same guard fits a unit that could read as an exponent. Writing the unit with a leading escape, as in 1\65 5, blocks the exponent reading.
Root cause
src/serializer.rs:107. The dimension arm writes the value and then the unit with no check for a unit that starts with e or E and a digit.
Scope
Any consumer that serializes tokens and later re-parses them can read a wrong numeric value, or an infinity where the source held a finite dimension. The value token is also reachable by constructing a Token::Dimension directly, without parsing. Present on main as of the latest commit.
A
Token::Dimensionwhose unit begins witheorEfollowed by a digit serializes to text that re-parses as aNumberwith a different value. The unit is written straight after the numeric value with no separation, so a value of1with unite5becomes1e5, which is scientific notation for the number 100000.Reproducer
cssparser 0.37.0, default features.
Observed vs expected
Observed: the dimension serializes to
1e5. Re-parsing1e5yieldsNumber { value: 100000.0 }, so the unit is gone and the value is scaled by 100000. With unite90the input serializes to1e90, which re-parses toNumber { value: inf }.Expected: the serialized form re-parses to the same dimension. CSS Syntax Level 3 requires a serialized token to round-trip. The serializer already guards a unit that would collide with an identifier, so the same guard fits a unit that could read as an exponent. Writing the unit with a leading escape, as in
1\65 5, blocks the exponent reading.Root cause
src/serializer.rs:107. The dimension arm writes the value and then the unit with no check for a unit that starts with
eorEand a digit.Scope
Any consumer that serializes tokens and later re-parses them can read a wrong numeric value, or an infinity where the source held a finite dimension. The value token is also reachable by constructing a
Token::Dimensiondirectly, without parsing. Present on main as of the latest commit.