diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index aa7b7ef68..04fb6b3a8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -36,7 +36,7 @@ jobs:
echo "EOF" >> $GITHUB_OUTPUT
- name: Create the release
if: steps.changelog.outputs.changelog_content != ''
- uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
+ uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
name: ${{ github.ref_name }}
body: '${{ steps.changelog.outputs.changelog_content }}'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7abcd6f8..aee09ae08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
This project adheres to [Semantic Versioning](https://semver.org/).
+## 8.5.10
+
+- Fixed XSS via unescaped `` in non-bundler cases (by @TharVid).
+
## 8.5.9
- Speed up source map encoding paring in case of the error.
diff --git a/lib/input.js b/lib/input.js
index 1dab92836..4faf5fd6d 100644
--- a/lib/input.js
+++ b/lib/input.js
@@ -195,6 +195,10 @@ class Input {
}
}
+ getLineToIndex() {
+ return getLineToIndex(this)
+ }
+
mapResolve(file) {
if (/^\w+:\/\//.test(file)) {
return file
diff --git a/lib/parser.js b/lib/parser.js
index b29ff5b2d..bb48d1e07 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -28,6 +28,8 @@ class Parser {
this.current = this.root
this.spaces = ''
this.semicolon = false
+ this.lineToIndex = null
+ this.lastLine = 0
this.createTokenizer()
this.root.source = { input, start: { column: 1, line: 1, offset: 0 } }
@@ -356,10 +358,65 @@ class Parser {
// Helpers
getPosition(offset) {
- let pos = this.input.fromOffset(offset)
+ let lineToIndex = this.lineToIndex
+ if (lineToIndex === null) {
+ lineToIndex = this.input.getLineToIndex()
+ this.lineToIndex = lineToIndex
+ }
+
+ let len = lineToIndex.length
+ let min = this.lastLine
+
+ // Fast path: check if offset is still on the same line or a nearby line
+ if (offset >= lineToIndex[min]) {
+ if (min + 1 >= len || offset < lineToIndex[min + 1]) {
+ // Same line as last time
+ return {
+ column: offset - lineToIndex[min] + 1,
+ line: min + 1,
+ offset
+ }
+ }
+ // Check next few lines (common for sequential token processing)
+ let check = min + 1
+ let limit = min + 5
+ if (limit >= len) limit = len - 1
+ while (check < limit && offset >= lineToIndex[check + 1]) {
+ check++
+ }
+ if (check < len && (check + 1 >= len || offset < lineToIndex[check + 1])) {
+ this.lastLine = check
+ return {
+ column: offset - lineToIndex[check] + 1,
+ line: check + 1,
+ offset
+ }
+ }
+ }
+
+ // Fallback: binary search with hint
+ let lo = offset >= lineToIndex[min] ? min : 0
+ let hi = len - 1
+ if (offset < lineToIndex[hi]) {
+ hi = hi - 1
+ let mid
+ while (lo < hi) {
+ mid = lo + ((hi - lo) >> 1)
+ if (offset < lineToIndex[mid]) {
+ hi = mid - 1
+ } else if (offset >= lineToIndex[mid + 1]) {
+ lo = mid + 1
+ } else {
+ lo = mid
+ break
+ }
+ }
+ }
+ min = lo
+ this.lastLine = min
return {
- column: pos.col,
- line: pos.line,
+ column: offset - lineToIndex[min] + 1,
+ line: min + 1,
offset
}
}
diff --git a/lib/processor.js b/lib/processor.js
index 2afe2ef0a..5eda6c410 100644
--- a/lib/processor.js
+++ b/lib/processor.js
@@ -7,7 +7,7 @@ let Root = require('./root')
class Processor {
constructor(plugins = []) {
- this.version = '8.5.9'
+ this.version = '8.5.10'
this.plugins = this.normalize(plugins)
}
diff --git a/lib/stringifier.js b/lib/stringifier.js
index e07ad12e7..012fa622d 100644
--- a/lib/stringifier.js
+++ b/lib/stringifier.js
@@ -1,5 +1,17 @@
'use strict'
+// Escapes sequences that could break out of an HTML {
+ let root = new Root()
+ root.append(new Rule({ selector: '' }))
+ root.append(new AtRule({ name: 'media', params: '