Skip to content

.css() splits a declaration when a data URI's semicolon is followed by a colon #5410

Description

@Jaybhade

.css() still splits a declaration when the part after a semicolon contains a colon, and rewriting an unrelated property then corrupts the style attribute.

Tested on cheerio 1.2.0 (and current main).

Reproduction

const cheerio = require('cheerio');

const style = `background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")`;
const $ = cheerio.load(`<div style='${style}'></div>`);
const $div = $('div');

console.log($div.css());
$div.css('color', 'red');
console.log($div.attr('style'));

Expected

{ background: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")` }
// background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>"); color: red;

Actual

{
  background: 'url("data:image/svg+xml',
  "utf8,<svg xmlns='http": "//www.w3.org/2000/svg'/>\")"
}
// background: url("data:image/svg+xml; utf8,<svg xmlns='http: //www.w3.org/2000/svg'/>"); color: red;

Two separate problems:

  1. The declaration is split at the ; inside the data URI, producing a bogus second property.
  2. Because .css(prop, val) re-serialises everything it parsed, setting an unrelated property writes the mangled value back to the element. The URL now contains ; and : , so the image silently stops loading. Nothing in the caller's code touched background.

Why the #1134 fix doesn't cover this

#1134 (and #907 before it) fixed the same split for url(data:image/png;base64,…). The fix keeps a fragment that has no colon as a continuation of the previous value, which works for base64,…. But a data URI whose text after the ; does contain a colon takes the other branch and becomes a new property. xmlns='http://www.w3.org/2000/svg' is the common case here, since inline SVG data URIs almost always carry that attribute.

The same applies to any quoted value holding a ; and a ::

cheerio.load(`<li style="content: 'a;b:c'">`)('li').css();
// { content: "'a", b: "c'" }   expected: { content: "'a;b:c'" }

Per CSS syntax, a ; or : inside a string or inside parentheses is part of the value and does not separate declarations. Checking the same inputs through a spec-compliant parser (postcss) returns a single declaration in each case.

Fix

Skipping over quoted sections and parentheses when scanning for the separating ; and : handles all of these, and subsumes the base64 case that motivated #1134 rather than special-casing it. Happy to open a PR — I have the change and tests ready.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions