Skip to content

Commit b4d75db

Browse files
Merge pull request #6448 from alphagov/attributes-double-escape
2 parents cfc0dc2 + 7e94201 commit b4d75db

File tree

3 files changed

+95
-31
lines changed

3 files changed

+95
-31
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ For advice on how to use these release notes, see [our guidance on staying up to
44

55
## Unreleased
66

7+
### Fixes
8+
9+
We've made fixes to GOV.UK Frontend in the following pull requests:
10+
11+
- [#6351: Preserve already escaped `attributes` values to prevent double escaping](https://github.com/alphagov/govuk-frontend/pull/6351) thanks to @colinrotherham for fixing this issue
12+
713
## v6.0.0-beta.1 (Beta breaking release)
814

915
### Breaking changes
@@ -272,7 +278,6 @@ We've made fixes to GOV.UK Frontend in the following pull requests:
272278

273279
- [#5311: Remove non-operational value parameter from file upload component](https://github.com/alphagov/govuk-frontend/pull/5311)
274280
- [#6434: Fix rebranded header background being visible when printed](https://github.com/alphagov/govuk-frontend/pull/6434) - thanks to @lewis-softwire for reporting this issue
275-
- [#6447: Fix pagination outputting empty links when provided a null or empty value](https://github.com/alphagov/govuk-frontend/pull/6447) – thanks to @NikhilNanjappa for reporting this issue
276281

277282
## v6.0.0-beta.0 (Beta breaking release)
278283

packages/govuk-frontend/src/govuk/macros/attributes.njk

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,30 +62,46 @@
6262
@param {{ [attribute: string]: string | { value: string, optional?: boolean } } | string} attributes - Component attributes param
6363
#}
6464
{% macro govukAttributes(attributes) -%}
65-
{#- Default attributes output -#}
65+
{#- Default attributes output as string -#}
6666
{% set attributesHtml = attributes if attributes is string else "" %}
6767

68-
{#- Append attribute name/value pairs -#}
69-
{%- if attributes is mapping %}
70-
{% for name, value in attributes %}
71-
{#- Detect if this is a `safe` filtered value. Just pass the value through if so. -#}
72-
{#- https://github.com/alphagov/govuk-frontend/issues/4937 -#}
73-
{% if value is mapping and not [undefined, null].includes(value.val) and value.length %}
74-
{% set value = value.val %}
75-
{% endif %}
68+
{#- Default attributes output as `safe` filtered string -#}
69+
{%- if attributes is escaped and attributes.val is string %}
70+
{% set attributesHtml = attributes %}
7671

72+
{#- Append attribute name/value pairs -#}
73+
{%- elif attributes is mapping and attributes is not escaped %}
74+
{% for name, item in attributes %}
7775
{#- Set default attribute options -#}
78-
{% set options = value if value is mapping else {
79-
value: value,
76+
{% set options = item if item is mapping and item is not escaped else {
77+
value: item,
8078
optional: false
8179
} %}
8280

81+
{#- Remove values when not provided -#}
82+
{%- if options.value in [undefined, null] %}
83+
{% set value = null %}
84+
{% set valueEscaped = "" %}
85+
86+
{#- Extract unescaped value when `safe` filtered -#}
87+
{#- https://github.com/alphagov/govuk-frontend/issues/4937 -#}
88+
{%- elif options.value is escaped %}
89+
{% set value = options.value.val %}
90+
{% set valueEscaped = options.value %}
91+
92+
{#- Otherwise assume escaping is necessary -#}
93+
{#- https://github.com/alphagov/govuk-frontend/issues/4940 -#}
94+
{%- else %}
95+
{% set value = options.value %}
96+
{% set valueEscaped = options.value | escape %}
97+
{% endif %}
98+
8399
{#- Output ` name` only (no value) for boolean attributes -#}
84-
{% if options.optional === true and options.value === true %}
100+
{% if options.optional === true and value === true %}
85101
{% set attributesHtml = attributesHtml + " " + name | escape %}
86102
{#- Skip optional empty attributes or output ` name="value"` pair by default -#}
87-
{% elif (options.optional === true and not [undefined, null, false].includes(options.value)) or options.optional !== true %}
88-
{% set attributesHtml = attributesHtml + " " + name | escape + '="' + options.value | escape + '"' %}
103+
{% elif (options.optional === true and value not in [undefined, null, false]) or options.optional !== true %}
104+
{% set attributesHtml = attributesHtml + " " + name | escape + '="' + valueEscaped + '"' %}
89105
{% endif %}
90106
{% endfor %}
91107
{% endif -%}

packages/govuk-frontend/src/govuk/macros/attributes.unit.test.mjs

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { renderMacro } from '@govuk-frontend/lib/components'
2-
import nunjucks from 'nunjucks'
1+
import { renderMacro, renderString } from '@govuk-frontend/lib/components'
2+
import { outdent } from 'outdent'
33

44
describe('attributes.njk', () => {
55
describe('govukAttributes', () => {
@@ -245,11 +245,22 @@ describe('attributes.njk', () => {
245245
'govukAttributes',
246246
'govuk/macros/attributes.njk',
247247
{
248-
context: ' data-attribute="value"'
248+
context: ' data-attribute="Testing & more"'
249249
}
250250
)
251251

252-
expect(attributes).toBe(' data-attribute="value"')
252+
expect(attributes).toBe(' data-attribute="Testing & more"')
253+
})
254+
255+
it('outputs attributes from the `safe` filter if already stringified', () => {
256+
// Render directly otherwise nunjucks `renderMacro()` will stringify
257+
// safe `is escaped` instances into plain `is mapping` objects
258+
const attributes = renderString(outdent`
259+
{%- from "govuk/macros/attributes.njk" import govukAttributes -%}
260+
{{- govukAttributes(' data-attribute="Testing & more"' | safe) -}}
261+
`)
262+
263+
expect(attributes).toBe(' data-attribute="Testing & more"')
253264
})
254265

255266
it('outputs nothing if there are no attributes', () => {
@@ -265,21 +276,53 @@ describe('attributes.njk', () => {
265276
})
266277

267278
it('outputs values that are passed from the `safe` filter', () => {
268-
// Set up a tiny Nunjucks environment, just so we can get the native `safe` filter
269-
const nunjucksEnv = nunjucks.configure([])
279+
// Render directly otherwise nunjucks `renderMacro()` will stringify
280+
// safe `is escaped` instances into plain `is mapping` objects
281+
const attributes = renderString(outdent`
282+
{%- from "govuk/macros/attributes.njk" import govukAttributes -%}
270283
271-
const attributes = renderMacro(
272-
'govukAttributes',
273-
'govuk/macros/attributes.njk',
274-
{
275-
context: {
276-
'data-text': 'Testing',
277-
'data-safe-text': nunjucksEnv.getFilter('safe')('Testing')
278-
}
279-
}
284+
{{- govukAttributes({
285+
'data-text': 'Testing',
286+
'data-unsafe-text': 'Testing & more',
287+
'data-safe-text': 'Testing & more' | safe,
288+
'data-escaped-text': 'Testing & more' | escape,
289+
'data-double-escaped-text': 'Testing & more' | escape
290+
}) -}}
291+
`)
292+
293+
expect(attributes).toBe(
294+
' data-text="Testing" data-unsafe-text="Testing & more" data-safe-text="Testing & more" data-escaped-text="Testing & more" data-double-escaped-text="Testing & more"'
280295
)
296+
})
297+
298+
it('outputs values from mappings that are passed from the `safe` filter', () => {
299+
// Render directly otherwise nunjucks `renderMacro()` will stringify
300+
// safe `is escaped` instances into plain `is mapping` objects
301+
const attributes = renderString(outdent`
302+
{%- from "govuk/macros/attributes.njk" import govukAttributes -%}
281303
282-
expect(attributes).toBe(' data-text="Testing" data-safe-text="Testing"')
304+
{{- govukAttributes({
305+
'data-text': {
306+
value: 'Testing'
307+
},
308+
'data-unsafe-text': {
309+
value: 'Testing & more'
310+
},
311+
'data-safe-text': {
312+
value: 'Testing & more' | safe
313+
},
314+
'data-escaped-text': {
315+
value: 'Testing & more' | escape
316+
},
317+
'data-double-escaped-text': {
318+
value: 'Testing & more' | escape
319+
}
320+
}) -}}
321+
`)
322+
323+
expect(attributes).toBe(
324+
' data-text="Testing" data-unsafe-text="Testing & more" data-safe-text="Testing & more" data-escaped-text="Testing & more" data-double-escaped-text="Testing & more"'
325+
)
283326
})
284327
})
285328
})

0 commit comments

Comments
 (0)