Skip to content

Commit

Permalink
Release 4.3.2 (#249)
Browse files Browse the repository at this point in the history
Co-authored-by: Jean-Philippe Zolesio <zolesio@adobe.com>
  • Loading branch information
holblin and Jean-Philippe Zolesio committed Nov 30, 2023
1 parent 0c9695a commit 1d90dea
Show file tree
Hide file tree
Showing 8 changed files with 616 additions and 514 deletions.
7 changes: 7 additions & 0 deletions History.md
@@ -1,3 +1,10 @@
4.3.2 / 2023-11-28
==================

* Fix redos vulnerability with specific crafted css string - CVE-2023-48631
* Fix Problem parsing with :is() and nested :nth-child() #211


4.3.1 / 2023-03-14
==================

Expand Down
8 changes: 4 additions & 4 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "@adobe/css-tools",
"version": "4.3.1",
"version": "4.3.2",
"description": "CSS parser / stringifier",
"source": "src/index.ts",
"main": "./dist/index.cjs",
Expand All @@ -16,8 +16,8 @@
"Readme.md"
],
"devDependencies": {
"@parcel/packager-ts": "2.9.3",
"@parcel/transformer-typescript-types": "2.9.3",
"@parcel/packager-ts": "2.10.3",
"@parcel/transformer-typescript-types": "2.10.3",
"@types/benchmark": "^2.1.1",
"@types/bytes": "^3.1.1",
"@types/jest": "^29.5.3",
Expand All @@ -26,7 +26,7 @@
"bytes": "^3.1.0",
"gts": "^5.0.0",
"jest": "^29.6.2",
"parcel": "^2.9.3",
"parcel": "^2.10.3",
"ts-jest": "^29.1.1",
"typescript": "^5.0.2"
},
Expand Down
92 changes: 70 additions & 22 deletions src/parse/index.ts
Expand Up @@ -197,6 +197,35 @@ export const parse = (
});
}

function findClosingParenthese(
str: string,
start: number,
depth: number
): number {
let ptr = start + 1;
let found = false;
let closeParentheses = str.indexOf(')', ptr);
while (!found && closeParentheses !== -1) {
const nextParentheses = str.indexOf('(', ptr);
if (nextParentheses !== -1 && nextParentheses < closeParentheses) {
const nextSearch = findClosingParenthese(
str,
nextParentheses + 1,
depth + 1
);
ptr = nextSearch + 1;
closeParentheses = str.indexOf(')', ptr);
} else {
found = true;
}
}
if (found && closeParentheses !== -1) {
return closeParentheses;
} else {
return -1;
}
}

/**
* Parse selector.
*/
Expand All @@ -207,35 +236,54 @@ export const parse = (
}

// remove comment in selector;
const res = trim(m[0]).replace(commentre, '');
let res = trim(m[0]).replace(commentre, '');

// Optimisation: If there is no ',' no need to split or post-process (this is less costly)
if (res.indexOf(',') === -1) {
return [res];
}

// Replace all the , in the parentheses by \u200C
let ptr = 0;
let startParentheses = res.indexOf('(', ptr);
while (startParentheses !== -1) {
const closeParentheses = findClosingParenthese(res, startParentheses, 0);
if (closeParentheses === -1) {
break;
}
ptr = closeParentheses + 1;
res =
res.substring(0, startParentheses) +
res
.substring(startParentheses, closeParentheses)
.replace(/,/g, '\u200C') +
res.substring(closeParentheses);
startParentheses = res.indexOf('(', ptr);
}

// Replace all the , in ' and " by \u200C
res = res
/**
* replace ',' by \u200C for data selector (div[data-lang="fr,de,us"])
*
* Examples:
* div[data-lang="fr,\"de,us"]
* div[data-lang='fr,\'de,us']
*
* Regex logic:
* ("|')(?:\\\1|.)*?\1 => Handle the " and '
*
* Optimization 1:
* No greedy capture (see docs about the difference between .* and .*?)
*
* Optimization 2:
* ("|')(?:\\\1|.)*?\1 this use reference to capture group, it work faster.
*/
.replace(/("|')(?:\\\1|.)*?\1/g, m => m.replace(/,/g, '\u200C'));

// Split all the left , and replace all the \u200C by ,
return (
res
/**
* replace ',' by \u200C for data selector (div[data-lang="fr,de,us"])
* replace ',' by \u200C for nthChild and other selector (div:nth-child(2,3,4))
*
* Examples:
* div[data-lang="fr,\"de,us"]
* div[data-lang='fr,\'de,us']
* div:matches(.toto, .titi:matches(.toto, .titi))
*
* Regex logic:
* ("|')(?:\\\1|.)*?\1 => Handle the " and '
* \(.*?\) => Handle the ()
*
* Optimization 1:
* No greedy capture (see docs about the difference between .* and .*?)
*
* Optimization 2:
* ("|')(?:\\\1|.)*?\1 this use reference to capture group, it work faster.
*/
.replace(/("|')(?:\\\1|.)*?\1|\(.*?\)/g, m => m.replace(/,/g, '\u200C'))
// Split the selector by ','
.split(',')
// Replace back \u200C by ','
Expand Down Expand Up @@ -522,7 +570,7 @@ export const parse = (
*/
function atcustommedia(): CssCustomMediaAST | void {
const pos = position();
const m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);
const m = match(/^@custom-media\s+(--\S+)\s*([^{;\s][^{;]*);/);
if (!m) {
return;
}
Expand Down
42 changes: 42 additions & 0 deletions test/cases/selector-double-is/ast.json
@@ -0,0 +1,42 @@
{
"type": "stylesheet",
"stylesheet": {
"rules": [
{
"type": "rule",
"selectors": [
".klass:is(:nth-child(1), :nth-child(2))"
],
"declarations": [
{
"type": "declaration",
"property": "margin",
"value": "0 !important",
"position": {
"start": {
"line": 1,
"column": 42
},
"end": {
"line": 1,
"column": 62
},
"source": "input.css"
}
}
],
"position": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 63
},
"source": "input.css"
}
}
]
}
}
1 change: 1 addition & 0 deletions test/cases/selector-double-is/compressed.css
@@ -0,0 +1 @@
.klass:is(:nth-child(1), :nth-child(2)){margin:0 !important;}
1 change: 1 addition & 0 deletions test/cases/selector-double-is/input.css
@@ -0,0 +1 @@
.klass:is(:nth-child(1), :nth-child(2)) {margin: 0 !important}
3 changes: 3 additions & 0 deletions test/cases/selector-double-is/output.css
@@ -0,0 +1,3 @@
.klass:is(:nth-child(1), :nth-child(2)) {
margin: 0 !important;
}

0 comments on commit 1d90dea

Please sign in to comment.