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: '