From d88a4bfce10a2fbfcb5c0f39c32178dea9b5ac37 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 13 Oct 2026 22:06:01 -0700 Subject: [PATCH 1/2] chore(deps): update Lucee to 7.0.5.41 Pin Lucee 7.0.5.41 in the framework, `wheels new` template and starter app lucee.json (was 7.0.0.395), and in the Lucee 7 test image, CI lucee.json and the IIS CI install (was 7.0.1.100). Its Express bundle ships Tomcat 11.0.22. Lucee 7.0.5 ships ESAPI extension 3.x, which encodes only the characters that matter in each HTML context: `/`, `:`, `(`, `)`, `[`, `]` and spaces stay literal, and a double quote becomes `"`. The view specs compared helper output with strings captured from the 2.x encoder, so 57 failed. They now compare through a spec helper that canonicalises character references while keeping `&`, `<`, `"` and `'` encoded, so a missing encoding still fails. The injection cases also assert, without the engine's encoder, that no attribute is injected, that attribute values and text keep no literal quote or `<`, and that the value decodes back to the input. Four inputs lose a stray backslash, which the 2.x encoder's canonicalization dropped and 3.x keeps. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- changelog.d/lucee-7-0-5-41.changed.md | 2 + cli/lucli/templates/app/lucee.json | 2 +- compose.yml | 2 +- examples/starter-app/lucee.json | 2 +- lucee.json | 2 +- tools/ci/iis/install.ps1 | 4 +- tools/ci/lucee.ci.json | 2 +- tools/docker/github/README.md | 2 +- tools/docker/lucee7/Dockerfile | 2 +- tools/docker/lucee7/server.json | 2 +- .../tests/_assets/view/HtmlEncoding.cfc | 235 ++++++++++++++++++ vendor/wheels/tests/specs/view/assetsSpec.cfc | 12 +- .../wheels/tests/specs/view/autoLinkSpec.cfc | 14 +- .../wheels/tests/specs/view/checkboxSpec.cfc | 23 +- .../tests/specs/view/dateselectSpec.cfc | 16 +- .../wheels/tests/specs/view/encodingSpec.cfc | 3 +- vendor/wheels/tests/specs/view/errorsSpec.cfc | 12 +- .../tests/specs/view/flashMessagesSpec.cfc | 9 +- vendor/wheels/tests/specs/view/formsSpec.cfc | 43 ++-- .../tests/specs/view/html5FormHelpersSpec.cfc | 5 +- .../specs/view/htmlEncodingHelperSpec.cfc | 58 +++++ .../view/linkToUrlForDifferentialSpec.cfc | 7 +- vendor/wheels/tests/specs/view/linksSpec.cfc | 3 +- .../wheels/tests/specs/view/sanitizeSpec.cfc | 8 +- .../wheels/tests/specs/view/textfieldSpec.cfc | 26 +- vendor/wheels/tests/specs/view/urlsSpec.cfc | 8 +- .../command-line-tools/configuration.mdx | 4 +- .../docs/v4-2-0/deployment/vm-deployment.mdx | 4 +- 28 files changed, 432 insertions(+), 80 deletions(-) create mode 100644 changelog.d/lucee-7-0-5-41.changed.md create mode 100644 vendor/wheels/tests/_assets/view/HtmlEncoding.cfc create mode 100644 vendor/wheels/tests/specs/view/htmlEncodingHelperSpec.cfc diff --git a/changelog.d/lucee-7-0-5-41.changed.md b/changelog.d/lucee-7-0-5-41.changed.md new file mode 100644 index 0000000000..0ccf3580df --- /dev/null +++ b/changelog.d/lucee-7-0-5-41.changed.md @@ -0,0 +1,2 @@ +- Updated Lucee to 7.0.5.41. New apps from `wheels new`, the starter app and the framework's own `lucee.json` now pin Lucee 7.0.5.41 (previously 7.0.0.395), and the Lucee 7 test image and CI lanes run 7.0.5.41 (previously 7.0.1.100). Lucee 7.0.5.41's Express bundle ships Tomcat 11.0.22. Existing apps keep the version in their own `lucee.json`; change `lucee.version` to `7.0.5.41` to update. +- On Lucee 7.0.5 and later, HTML that view helpers encode (`encode=true` or `encode="attributes"`) uses fewer character references. Lucee's ESAPI extension 3.x encodes only the characters that matter in each context, so `/`, `:`, `(`, `)`, `[`, `]` and spaces stay literal and a double quote becomes `"` rather than `"`; earlier Lucee releases encode them as `/`, `:` and so on. `<`, `&` and quotes are still encoded wherever they were, and browsers render both forms the same. Its canonicalization also keeps a backslash that precedes a quote (`\"`) instead of dropping it, so such a value is rendered as written. Tests that compare helper output against hard-coded encoded strings may need updating. diff --git a/cli/lucli/templates/app/lucee.json b/cli/lucli/templates/app/lucee.json index 95ae49956c..8300b4f2e7 100644 --- a/cli/lucli/templates/app/lucee.json +++ b/cli/lucli/templates/app/lucee.json @@ -1,7 +1,7 @@ { "name": "{{appName}}", "lucee": { - "version": "7.0.0.395" + "version": "7.0.5.41" }, "port": {{port}}, "shutdownPort": {{shutdownPort}}, diff --git a/compose.yml b/compose.yml index 7f9bc9b8c3..046b0c053d 100644 --- a/compose.yml +++ b/compose.yml @@ -115,7 +115,7 @@ services: build: context: ./ dockerfile: ./tools/docker/lucee7/Dockerfile - image: wheels-test-lucee7:v1.0.0 + image: wheels-test-lucee7:v1.0.1 environment: # The constructor test-context gate honours WHEELS_ENV; # the core suite runs in development (config/environment.cfm). Default to diff --git a/examples/starter-app/lucee.json b/examples/starter-app/lucee.json index ddc85ad926..fc37809907 100644 --- a/examples/starter-app/lucee.json +++ b/examples/starter-app/lucee.json @@ -1,7 +1,7 @@ { "name": "wheels-starter-app", "lucee": { - "version": "7.0.0.395" + "version": "7.0.5.41" }, "port": 8080, "shutdownPort": 8081, diff --git a/lucee.json b/lucee.json index a26c8f2498..732754e2cb 100644 --- a/lucee.json +++ b/lucee.json @@ -1,7 +1,7 @@ { "name": "wheels", "lucee": { - "version": "7.0.0.395" + "version": "7.0.5.41" }, "port": 8080, "shutdownPort": 8081, diff --git a/tools/ci/iis/install.ps1 b/tools/ci/iis/install.ps1 index 8799add99a..e3bc86f566 100644 --- a/tools/ci/iis/install.ps1 +++ b/tools/ci/iis/install.ps1 @@ -28,8 +28,8 @@ if ($p.ExitCode -notin 0, 3010) { throw "URL Rewrite install failed: $($p.ExitCo # 3. Lucee Express (Tomcat). The AJP connector on 8009 is enabled in its server.xml. $luceeZip = Join-Path $work 'lucee-express.zip' -Get-Pinned 'https://cdn.lucee.org/lucee-express-7.0.1.100.zip' ` - 'e2f995d8993747dc782a1c50899aeca40db3cf8993eaf025f8adc61c7dce87e9' $luceeZip +Get-Pinned 'https://cdn.lucee.org/lucee-express-7.0.5.41.zip' ` + '30d79340bbefeb36e162530ad1cd6c9d88ff6d29e7c517ebc60fbab06a90bf44' $luceeZip Expand-Archive -Path $luceeZip -DestinationPath 'C:\lucee' -Force $serverXml = 'C:\lucee\conf\server.xml' $xml = Get-Content $serverXml -Raw diff --git a/tools/ci/lucee.ci.json b/tools/ci/lucee.ci.json index 256d942c1e..8d291c0b74 100644 --- a/tools/ci/lucee.ci.json +++ b/tools/ci/lucee.ci.json @@ -1,7 +1,7 @@ { "name": "wheels-ci", "lucee": { - "version": "7.0.1.100" + "version": "7.0.5.41" }, "port": 30007, "shutdownPort": 30008, diff --git a/tools/docker/github/README.md b/tools/docker/github/README.md index e5a047efd0..a808dfec9b 100644 --- a/tools/docker/github/README.md +++ b/tools/docker/github/README.md @@ -75,7 +75,7 @@ strategy: |--------|----------------|------|--------------| | Lucee 5 | `wheels-lucee5-1` | 60005 | `wheels-test-lucee5:v1.0.2` | | Lucee 6 | `wheels-lucee6-1` | 60006 | `wheels-test-lucee6:v1.0.2` | -| Lucee 7 | `wheels-lucee7-1` | 60007 | `wheels-test-lucee7:v1.0.0` | +| Lucee 7 | `wheels-lucee7-1` | 60007 | `wheels-test-lucee7:v1.0.1` | | Adobe 2018 | `wheels-adobe2018-1` | 62018 | `wheels-test-adobe2018:v1.0.2` | | Adobe 2021 | `wheels-adobe2021-1` | 62021 | `wheels-test-adobe2021:v1.0.2` | | Adobe 2023 | `wheels-adobe2023-1` | 62023 | `wheels-test-adobe2023:v1.0.1` | diff --git a/tools/docker/lucee7/Dockerfile b/tools/docker/lucee7/Dockerfile index a2d9f08627..6c0fbb48b4 100644 --- a/tools/docker/lucee7/Dockerfile +++ b/tools/docker/lucee7/Dockerfile @@ -31,7 +31,7 @@ ENV ENV_MODE "remote" ENV BOX_SERVER_CFCONFIGFILE "/wheels-test-suite/CFConfig.json" ENV BOX_SERVER_PROFILE "none" ENV BOX_INSTALL TRUE -ENV BOX_SERVER_APP_CFENGINE "lucee@7.0.1+100" +ENV BOX_SERVER_APP_CFENGINE "lucee@7.0.5+41" # Set working directory WORKDIR ${APP_DIR} diff --git a/tools/docker/lucee7/server.json b/tools/docker/lucee7/server.json index 53701a106d..04b8af2137 100644 --- a/tools/docker/lucee7/server.json +++ b/tools/docker/lucee7/server.json @@ -14,7 +14,7 @@ } }, "app":{ - "cfengine":"lucee@7.0.1+100", + "cfengine":"lucee@7.0.5+41", "libDirs":"/opt/wheels-lib" }, "cfconfigFile":"CFConfig.json" diff --git a/vendor/wheels/tests/_assets/view/HtmlEncoding.cfc b/vendor/wheels/tests/_assets/view/HtmlEncoding.cfc new file mode 100644 index 0000000000..852e5fb252 --- /dev/null +++ b/vendor/wheels/tests/_assets/view/HtmlEncoding.cfc @@ -0,0 +1,235 @@ +/** + * Encoder-agnostic helpers for view specs that check HTML encoding. + * + * Engines encode the same input differently. Lucee's ESAPI extension 2.x encodes almost every + * non-alphanumeric character (`/` becomes `/`, a space in an attribute ` `, `"` becomes + * `"`), while the 3.x extension shipped with Lucee 7.0.5 (built on the OWASP Java Encoder) + * encodes only what the context needs (`"` becomes `"`, `/` stays literal). Both are safe. + * + * `normalize()` lets a spec compare output against one literal expectation on either encoder: + * every character reference is rewritten to a single canonical form. Characters that can change + * the meaning of markup (`&`, `<`, `"`, `'`) always stay encoded, so a missing encoding of one of + * them still fails the comparison. `>` is canonicalised to the literal character, because the + * OWASP encoder leaves it literal inside attribute values, where it cannot end the tag; specs that + * need `>` encoded in text assert that separately with `isSafeText()`. + * + * The safety helpers do not use the engine's encoder at all: they parse the generated markup. + */ +component { + + /** + * Rewrites every character reference to a canonical form: `&`, `<`, `"` and + * `'` for the markup-significant characters, the literal character for everything else. + */ + public string function normalize(required string html) { + var rv = ""; + var pos = 1; + var total = Len(arguments.html); + while (pos <= total) { + var found = REFind("&(##[xX][0-9a-fA-F]+|##[0-9]+|[a-zA-Z][a-zA-Z0-9]*);", arguments.html, pos, true); + if (found.pos[1] == 0) { + rv &= Mid(arguments.html, pos, total - pos + 1); + break; + } + if (found.pos[1] > pos) { + rv &= Mid(arguments.html, pos, found.pos[1] - pos); + } + var reference = Mid(arguments.html, found.pos[1], found.len[1]); + var codePoint = $codePoint(Mid(arguments.html, found.pos[2], found.len[2])); + rv &= codePoint == 0 ? reference : $canonical(codePoint); + pos = found.pos[1] + found.len[1]; + } + return rv; + } + + /** + * Fully decodes the character references in a string (the value a browser would see). + */ + public string function decode(required string value) { + var rv = ""; + var pos = 1; + var total = Len(arguments.value); + while (pos <= total) { + var found = REFind("&(##[xX][0-9a-fA-F]+|##[0-9]+|[a-zA-Z][a-zA-Z0-9]*);", arguments.value, pos, true); + if (found.pos[1] == 0) { + rv &= Mid(arguments.value, pos, total - pos + 1); + break; + } + if (found.pos[1] > pos) { + rv &= Mid(arguments.value, pos, found.pos[1] - pos); + } + var codePoint = $codePoint(Mid(arguments.value, found.pos[2], found.len[2])); + rv &= codePoint == 0 ? Mid(arguments.value, found.pos[1], found.len[1]) : Chr(codePoint); + pos = found.pos[1] + found.len[1]; + } + return rv; + } + + /** + * Returns the double-quoted attributes of every tag in `html`, in order, as structs with the + * attribute `name` and its `raw` (still encoded) value. An attribute that broke out of its + * quotes shows up here as an attribute of its own. + */ + public array function attributes(required string html) { + var rv = []; + var pos = 1; + while (true) { + var found = REFind("\s([a-zA-Z_:][-a-zA-Z0-9_:.]*)=""([^""]*)""", arguments.html, pos, true); + if (found.pos[1] == 0) { + break; + } + ArrayAppend(rv, { + name = LCase(Mid(arguments.html, found.pos[2], found.len[2])), + raw = found.len[3] > 0 ? Mid(arguments.html, found.pos[3], found.len[3]) : "" + }); + pos = found.pos[1] + found.len[1]; + } + return rv; + } + + /** + * Every opening `tag` in `html` (e.g. each ``), in order. A literal `>` inside a + * quoted attribute value does not end the tag. + */ + public array function tags(required string html, required string tag) { + return REMatchNoCase("<#arguments.tag#\b(?:[^>""]|""[^""]*"")*>", arguments.html); + } + + /** + * Whether any tag in `html` carries an attribute called `name`. + */ + public boolean function hasAttribute(required string html, required string name) { + for (var attribute in attributes(arguments.html)) { + if (attribute.name == LCase(arguments.name)) { + return true; + } + } + return false; + } + + /** + * The raw (still encoded) value of the first attribute called `name`, or "" when there is none. + */ + public string function attributeValue(required string html, required string name) { + for (var attribute in attributes(arguments.html)) { + if (attribute.name == LCase(arguments.name)) { + return attribute.raw; + } + } + return ""; + } + + /** + * The raw text between the first opening `tag` and the next `<`, or "" when the tag is absent. + * Encoded text never contains a literal `<`, so this is the whole encoded text node. A literal + * `>` inside a quoted attribute value does not end the tag. + */ + public string function innerText(required string html, required string tag) { + var found = REFindNoCase("<#arguments.tag#\b(?:[^>""]|""[^""]*"")*>([^<]*)", arguments.html, 1, true); + if (found.pos[1] == 0 || found.len[2] == 0) { + return ""; + } + return Mid(arguments.html, found.pos[2], found.len[2]); + } + + /** + * Whether a raw attribute value is safe inside double quotes: no literal `"`, `'` or `<`. + * A literal `>` is allowed, as it cannot end a quoted attribute value. + */ + public boolean function isSafeAttributeValue(required string raw) { + return !Find("""", arguments.raw) && !Find("'", arguments.raw) && !Find("<", arguments.raw); + } + + /** + * Whether raw text content carries no literal `<`, `>`, `"` or `'`. + */ + public boolean function isSafeText(required string raw) { + return !Find("<", arguments.raw) && !Find(">", arguments.raw) && !Find("""", arguments.raw) && !Find("'", arguments.raw); + } + + /** + * Checks that `input`, rendered into the `name` attribute of `html`, stayed one attribute value: + * no attribute the input tried to inject (`onclick=` in the input) appears on any tag, the value + * carries no literal `"`, `'` or `<`, and it decodes back to `input`. Returns "" when all hold, + * otherwise a description of the first problem. + */ + public string function attributeProblem(required string html, required string name, required string input) { + for (var injected in REMatch("[a-zA-Z][-a-zA-Z0-9]*=", arguments.input)) { + var injectedName = Left(injected, Len(injected) - 1); + if (hasAttribute(arguments.html, injectedName)) { + return "the input added a [#injectedName#] attribute: #arguments.html#"; + } + } + if (!hasAttribute(arguments.html, arguments.name)) { + return "no [#arguments.name#] attribute in: #arguments.html#"; + } + var raw = attributeValue(arguments.html, arguments.name); + if (!isSafeAttributeValue(raw)) { + return "[#arguments.name#] value has a literal quote or <: #raw#"; + } + if (Compare(decode(raw), arguments.input) != 0) { + return "[#arguments.name#] value decodes to [#decode(raw)#], not the input [#arguments.input#]"; + } + return ""; + } + + /** + * Checks that the raw text node `raw` encodes `input` safely: no literal `<`, `>`, `"` or `'`, + * and it decodes back to `input`. Returns "" when both hold, otherwise a description. + */ + public string function textProblem(required string raw, required string input) { + if (!isSafeText(arguments.raw)) { + return "text has a literal <, >, quote or apostrophe: #arguments.raw#"; + } + if (Compare(decode(arguments.raw), arguments.input) != 0) { + return "text decodes to [#decode(arguments.raw)#], not the input [#arguments.input#]"; + } + return ""; + } + + /** + * The code point of a character reference body (`#x2f`, `#47`, `quot`), or 0 when the named + * reference is not one this helper knows. + */ + public numeric function $codePoint(required string body) { + if (Left(arguments.body, 2) == "##x" || Left(arguments.body, 2) == "##X") { + return InputBaseN(Mid(arguments.body, 3, Len(arguments.body) - 2), 16); + } + if (Left(arguments.body, 1) == "##") { + return Val(Mid(arguments.body, 2, Len(arguments.body) - 1)); + } + switch (LCase(arguments.body)) { + case "quot": + return 34; + case "apos": + return 39; + case "amp": + return 38; + case "lt": + return 60; + case "gt": + return 62; + case "nbsp": + return 160; + } + return 0; + } + + /** + * The canonical form of one decoded character. + */ + public string function $canonical(required numeric codePoint) { + switch (arguments.codePoint) { + case 38: + return "&"; + case 60: + return "<"; + case 34: + return """; + case 39: + return "&##39;"; + } + return Chr(arguments.codePoint); + } + +} diff --git a/vendor/wheels/tests/specs/view/assetsSpec.cfc b/vendor/wheels/tests/specs/view/assetsSpec.cfc index 46435cf3a4..8e5f24e14e 100644 --- a/vendor/wheels/tests/specs/view/assetsSpec.cfc +++ b/vendor/wheels/tests/specs/view/assetsSpec.cfc @@ -15,6 +15,8 @@ component extends="wheels.WheelsTest" { function run() { + enc = new wheels.tests._assets.view.HtmlEncoding() + g = application.wo describe("Tests that assetDomain", () => { @@ -352,7 +354,7 @@ component extends="wheels.WheelsTest" { result = _controller.styleSheetLinkTag(argumentcollection = args) expected = '#Chr(10)##Chr(10)##Chr(10)##Chr(10)#' - expect(result).toBe(expected) + expect(enc.normalize(result)).toBe(enc.normalize(expected)) }) it("no automatic extension when cfm", () => { @@ -360,7 +362,7 @@ component extends="wheels.WheelsTest" { result = _controller.styleSheetLinkTag(argumentcollection = args) expected = '#Chr(10)##Chr(10)##Chr(10)#' - expect(result).toBe(expected) + expect(enc.normalize(result)).toBe(enc.normalize(expected)) }) it("supports external links", () => { @@ -368,7 +370,7 @@ component extends="wheels.WheelsTest" { result = _controller.styleSheetLinkTag(argumentcollection = args) expected = '#Chr(10)##Chr(10)##Chr(10)#' - expect(result).toBe(expected) + expect(enc.normalize(result)).toBe(enc.normalize(expected)) }) it("allows specification of delimeter", () => { @@ -377,7 +379,7 @@ component extends="wheels.WheelsTest" { result = _controller.styleSheetLinkTag(argumentcollection = args) expected = '#Chr(10)##Chr(10)##Chr(10)#' - expect(result).toBe(expected) + expect(enc.normalize(result)).toBe(enc.normalize(expected)) }) it("type media arguments", () => { @@ -387,7 +389,7 @@ component extends="wheels.WheelsTest" { result = _controller.styleSheetLinkTag(argumentcollection = args) expected = '#Chr(10)#' - expect(result).toBe(expected) + expect(enc.normalize(result)).toBe(enc.normalize(expected)) }) it("no encoding", () => { diff --git a/vendor/wheels/tests/specs/view/autoLinkSpec.cfc b/vendor/wheels/tests/specs/view/autoLinkSpec.cfc index e47c048f84..0f8f4f3d0a 100644 --- a/vendor/wheels/tests/specs/view/autoLinkSpec.cfc +++ b/vendor/wheels/tests/specs/view/autoLinkSpec.cfc @@ -2,6 +2,8 @@ component extends="wheels.WheelsTest" { function run() { + enc = new wheels.tests._assets.view.HtmlEncoding() + g = application.wo describe("Testing with plain text (no HTML)", () => { @@ -23,8 +25,8 @@ component extends="wheels.WheelsTest" { args.text = "Visit Wheels at http://wheels.dev" args.encode = true result = _controller.autoLink(argumentCollection=args) - expect(result).toInclude('Visit Wheels at http&##x3a;&##x2f;&##x2f;wheels.dev') - expect(result).toInclude('&##x2f;') + expect(enc.normalize(result)).toInclude(enc.normalize('Visit Wheels at http&##x3a;&##x2f;&##x2f;wheels.dev')) + expect(enc.attributeProblem(result, "href", "http://wheels.dev")).toBe("") expect(result).notToInclude('http://wheels.dev') + expect(enc.normalize(result)).toInclude(enc.normalize('Visit Wheels at http://wheels.dev')) expect(result).notToInclude('<') }) @@ -50,8 +52,8 @@ component extends="wheels.WheelsTest" { args.text = "Contact us at info@wheels.dev" args.encode = true result = _controller.autoLink(argumentCollection=args) - expect(result).toInclude('Contact us at info&##x40;wheels.dev') - expect(result).toInclude('&##x3a;') + expect(enc.normalize(result)).toInclude(enc.normalize('Contact us at info&##x40;wheels.dev')) + expect(enc.attributeProblem(result, "href", "mailto:info@wheels.dev")).toBe("") expect(result).notToInclude('info@wheels.dev') + expect(enc.normalize(result)).toInclude(enc.normalize('Contact us at info@wheels.dev')) }) }) } diff --git a/vendor/wheels/tests/specs/view/checkboxSpec.cfc b/vendor/wheels/tests/specs/view/checkboxSpec.cfc index a793d95992..589586a76a 100644 --- a/vendor/wheels/tests/specs/view/checkboxSpec.cfc +++ b/vendor/wheels/tests/specs/view/checkboxSpec.cfc @@ -2,6 +2,8 @@ component extends="wheels.WheelsTest" { function run() { + enc = new wheels.tests._assets.view.HtmlEncoding() + g = application.wo describe("Tests that checkBox with encoding options", () => { @@ -20,7 +22,8 @@ component extends="wheels.WheelsTest" { actual = _controller.checkBox(argumentcollection = args) expected = '' - expect(actual).toBe(expected) + expect(enc.normalize(actual)).toBe(enc.normalize(expected)) + expect(enc.textProblem(enc.innerText(actual, "label"), args.label)).toBe("") }) it("does not encode HTML in label when encode=false", () => { @@ -43,7 +46,8 @@ component extends="wheels.WheelsTest" { actual = _controller.checkBox(argumentcollection = args) expected = '' - expect(actual).toBe(expected) + expect(enc.normalize(actual)).toBe(enc.normalize(expected)) + expect(enc.attributeProblem(actual, "class", args.class)).toBe("") }) it("handles complex HTML content with encode=true", () => { @@ -54,7 +58,8 @@ component extends="wheels.WheelsTest" { actual = _controller.checkBox(argumentcollection = args) expected = '' - expect(actual).toBe(expected) + expect(enc.normalize(actual)).toBe(enc.normalize(expected)) + expect(enc.textProblem(enc.innerText(actual, "label"), args.label)).toBe("") }) }) @@ -73,7 +78,8 @@ component extends="wheels.WheelsTest" { ) e = '' - expect(e).toBe(r) + expect(enc.normalize(r)).toBe(enc.normalize(e)) + expect(enc.textProblem(enc.innerText(r, "label"), "lastname error with bold")).toBe("") }) it("does not encode HTML in label when encode=false", () => { @@ -98,7 +104,8 @@ component extends="wheels.WheelsTest" { ) e = '' - expect(e).toBe(r) + expect(enc.normalize(r)).toBe(enc.normalize(e)) + expect(enc.attributeProblem(r, "class", 'form-check" onclick="alert("xss")')).toBe("") }) it("encodes HTML in uncheckedvalue when encode=true", () => { @@ -111,7 +118,8 @@ component extends="wheels.WheelsTest" { ) e = '' - expect(e).toBe(r) + expect(enc.normalize(r)).toBe(enc.normalize(e)) + expect(enc.attributeProblem(enc.tags(r, "input")[2], "value", "")).toBe("") }) it("handles complex HTML content with encode=true", () => { @@ -124,7 +132,8 @@ component extends="wheels.WheelsTest" { ) e = '' - expect(e).toBe(r) + expect(enc.normalize(r)).toBe(enc.normalize(e)) + expect(enc.textProblem(enc.innerText(r, "label"), htmlLabel)).toBe("") }) }) } diff --git a/vendor/wheels/tests/specs/view/dateselectSpec.cfc b/vendor/wheels/tests/specs/view/dateselectSpec.cfc index 27ece30905..d2da83730c 100644 --- a/vendor/wheels/tests/specs/view/dateselectSpec.cfc +++ b/vendor/wheels/tests/specs/view/dateselectSpec.cfc @@ -2,6 +2,8 @@ component extends="wheels.WheelsTest" { function run() { + enc = new wheels.tests._assets.view.HtmlEncoding() + g = application.wo describe("Tests that dateSelect with encoding options", () => { @@ -27,7 +29,8 @@ component extends="wheels.WheelsTest" { actual = _controller.dateSelect(argumentcollection = args) expected = '