govspeak 7.1.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/govspeak/html_sanitizer.rb +15 -25
- data/lib/govspeak/version.rb +1 -1
- data/lib/govspeak.rb +2 -2
- data/test/govspeak_attachment_link_test.rb +11 -0
- data/test/govspeak_attachment_test.rb +18 -1
- data/test/govspeak_images_bang_test.rb +26 -0
- data/test/govspeak_images_test.rb +17 -0
- data/test/{govspeak_table_with_headers_test.rb → govspeak_tables_test.rb} +23 -9
- data/test/html_sanitizer_test.rb +17 -7
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b77227c0a115225f51dc35e8dc7e177212aae5b215400f4b0384bd99fdf04a2
|
4
|
+
data.tar.gz: dcca6a433304ceb7d1cf1281ac0d16e738e7e7fb3479551ec69c0fe2e2dfa2ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8302fc09c9160bc5ea3d8cc33af663e106ba36287d10105272c03467e5aed41c5fb7f5e76da934f93c121b79687287f1c9edc92dab2aebca486c256cd4fa76f
|
7
|
+
data.tar.gz: fc0c093d411ccb309172828f8d21671d8e31bc7458a2285b1fb7a0ba22a63a598e27397b78e52af93e756eb52353e7c53ec1c2b11e16d0b42b6e26b5a981c613
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 8.0.0
|
2
|
+
|
3
|
+
* BREAKING: HTML style attribute and style element, which were never supposed to be available, are forbidden. [#279](https://github.com/alphagov/govspeak/pull/279)
|
4
|
+
|
5
|
+
## 7.1.1
|
6
|
+
|
7
|
+
* Make image and attachment embedding syntax more consistent [#274](https://github.com/alphagov/govspeak/pull/274)
|
8
|
+
|
1
9
|
## 7.1.0
|
2
10
|
|
3
11
|
* Drop support for Ruby 2.7 [#272](https://github.com/alphagov/govspeak/pull/272)
|
@@ -17,31 +17,13 @@ class Govspeak::HtmlSanitizer
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
class TableCellTextAlignWhitelister
|
21
|
-
def call(sanitize_context)
|
22
|
-
return unless %w[td th].include?(sanitize_context[:node_name])
|
23
|
-
|
24
|
-
node = sanitize_context[:node]
|
25
|
-
|
26
|
-
# Kramdown uses text-align to allow table cells to be aligned
|
27
|
-
# http://kramdown.gettalong.org/quickref.html#tables
|
28
|
-
if invalid_style_attribute?(node["style"])
|
29
|
-
node.remove_attribute("style")
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def invalid_style_attribute?(style)
|
34
|
-
style && !style.match(/^text-align:\s*(center|left|right)$/)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
20
|
def initialize(dirty_html, options = {})
|
39
21
|
@dirty_html = dirty_html
|
40
22
|
@allowed_image_hosts = options[:allowed_image_hosts]
|
41
23
|
end
|
42
24
|
|
43
25
|
def sanitize(allowed_elements: [])
|
44
|
-
transformers = [
|
26
|
+
transformers = []
|
45
27
|
if @allowed_image_hosts && @allowed_image_hosts.any?
|
46
28
|
transformers << ImageSourceWhitelister.new(@allowed_image_hosts)
|
47
29
|
end
|
@@ -60,21 +42,29 @@ class Govspeak::HtmlSanitizer
|
|
60
42
|
end
|
61
43
|
|
62
44
|
def sanitize_config(allowed_elements: [])
|
45
|
+
# We purposefully disable style elements which Sanitize::Config::RELAXED allows
|
46
|
+
elements = Sanitize::Config::RELAXED[:elements] - %w[style] +
|
47
|
+
%w[govspeak-embed-attachment govspeak-embed-attachment-link svg path].concat(allowed_elements)
|
48
|
+
|
63
49
|
Sanitize::Config.merge(
|
64
50
|
Sanitize::Config::RELAXED,
|
65
|
-
elements:
|
51
|
+
elements: elements,
|
66
52
|
attributes: {
|
67
|
-
|
53
|
+
# We purposefully disable style attributes which Sanitize::Config::RELAXED allows
|
54
|
+
:all => Sanitize::Config::RELAXED[:attributes][:all] + %w[role aria-label] - %w[style],
|
68
55
|
"a" => Sanitize::Config::RELAXED[:attributes]["a"] + [:data] + %w[draggable],
|
69
|
-
"svg" =>
|
70
|
-
"path" =>
|
56
|
+
"svg" => %w[xmlns width height viewbox focusable],
|
57
|
+
"path" => %w[fill d],
|
71
58
|
"div" => [:data],
|
72
|
-
#
|
73
|
-
#
|
59
|
+
# The style attributes are permitted here just for the ones Kramdown for table alignment
|
60
|
+
# we replace them in a post processor.
|
74
61
|
"th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
|
75
62
|
"td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
|
76
63
|
"govspeak-embed-attachment" => %w[content-id],
|
77
64
|
},
|
65
|
+
# The only styling we permit is text-align on table cells (which is the CSS kramdown
|
66
|
+
# generates), we can therefore only allow this one CSS property
|
67
|
+
css: { properties: %w[text-align] },
|
78
68
|
)
|
79
69
|
end
|
80
70
|
end
|
data/lib/govspeak/version.rb
CHANGED
data/lib/govspeak.rb
CHANGED
@@ -424,14 +424,14 @@ module Govspeak
|
|
424
424
|
renderer.render(contact: ContactPresenter.new(contact))
|
425
425
|
end
|
426
426
|
|
427
|
-
extension("Image",
|
427
|
+
extension("Image", /^\[Image:\s*(.*?)\s*\]/) do |image_id|
|
428
428
|
image = images.detect { |c| c.is_a?(Hash) && c[:id] == image_id }
|
429
429
|
next "" unless image
|
430
430
|
|
431
431
|
render_image(ImagePresenter.new(image))
|
432
432
|
end
|
433
433
|
|
434
|
-
extension("Attachment",
|
434
|
+
extension("Attachment", /^\[Attachment:\s*(.*?)\s*\]/) do |attachment_id|
|
435
435
|
next "" if attachments.none? { |a| a[:id] == attachment_id }
|
436
436
|
|
437
437
|
%(<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>)
|
@@ -35,4 +35,15 @@ class GovspeakAttachmentLinkTest < Minitest::Test
|
|
35
35
|
assert(root.css("p").size, 0)
|
36
36
|
assert_match(/Attachment Title\s*test/, root.text)
|
37
37
|
end
|
38
|
+
|
39
|
+
test "allows spaces and special characters in the identifier" do
|
40
|
+
attachment = {
|
41
|
+
id: "This is the name of my &%$@€? attachment",
|
42
|
+
url: "http://example.com/attachment.pdf",
|
43
|
+
title: "Attachment Title",
|
44
|
+
}
|
45
|
+
|
46
|
+
rendered = render_govspeak("[AttachmentLink: This is the name of my &%$@€? attachment]", [attachment])
|
47
|
+
assert_match(/Attachment Title/, rendered)
|
48
|
+
end
|
38
49
|
end
|
@@ -21,7 +21,19 @@ class GovspeakAttachmentTest < Minitest::Test
|
|
21
21
|
assert_match(/Attachment Title/, rendered)
|
22
22
|
end
|
23
23
|
|
24
|
-
test "
|
24
|
+
test "allows spaces and special characters in the identifier" do
|
25
|
+
attachment = {
|
26
|
+
id: "This is the name of my &%$@€? attachment",
|
27
|
+
url: "http://example.com/attachment.pdf",
|
28
|
+
title: "Attachment Title",
|
29
|
+
}
|
30
|
+
|
31
|
+
rendered = render_govspeak("[Attachment: This is the name of my &%$@€? attachment]", [attachment])
|
32
|
+
assert_match(/<section class="gem-c-attachment/, rendered)
|
33
|
+
assert_match(/Attachment Title/, rendered)
|
34
|
+
end
|
35
|
+
|
36
|
+
test "only renders attachment when markdown extension starts on a new line" do
|
25
37
|
attachment = {
|
26
38
|
id: "attachment.pdf",
|
27
39
|
url: "http://example.com/attachment.pdf",
|
@@ -34,5 +46,10 @@ class GovspeakAttachmentTest < Minitest::Test
|
|
34
46
|
rendered = render_govspeak("[Attachment:attachment.pdf] some text", [attachment])
|
35
47
|
assert_match(/<section class="gem-c-attachment/, rendered)
|
36
48
|
assert_match(/<p>some text<\/p>/, rendered)
|
49
|
+
|
50
|
+
rendered = render_govspeak("some text\n[Attachment:attachment.pdf]\nsome more text", [attachment])
|
51
|
+
assert_match(/<p>some text<\/p>/, rendered)
|
52
|
+
assert_match(/<section class="gem-c-attachment/, rendered)
|
53
|
+
assert_match(/<p>some more text<\/p>/, rendered)
|
37
54
|
end
|
38
55
|
end
|
@@ -80,4 +80,30 @@ class GovspeakImagesBangTest < Minitest::Test
|
|
80
80
|
)
|
81
81
|
end
|
82
82
|
end
|
83
|
+
|
84
|
+
test "!!n syntax must start on a new line" do
|
85
|
+
given_govspeak "some text !!1", images: [Image.new] do
|
86
|
+
assert_html_output("<p>some text !!1</p>")
|
87
|
+
end
|
88
|
+
|
89
|
+
given_govspeak "!!1", images: [Image.new] do
|
90
|
+
assert_html_output(
|
91
|
+
"<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
given_govspeak "!!1 some text", images: [Image.new] do
|
96
|
+
assert_html_output(
|
97
|
+
"<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>\n<p>some text</p>",
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
given_govspeak "some text\n!!1\nsome more text", images: [Image.new] do
|
102
|
+
assert_html_output <<~HTML
|
103
|
+
<p>some text</p>
|
104
|
+
<figure class="image embedded"><div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div></figure>
|
105
|
+
<p>some more text</p>
|
106
|
+
HTML
|
107
|
+
end
|
108
|
+
end
|
83
109
|
end
|
@@ -72,6 +72,15 @@ class GovspeakImagesTest < Minitest::Test
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
+
test "allows spaces and special characters in the identifier" do
|
76
|
+
image = build_image(id: "This is the name of my &%$@€? image")
|
77
|
+
given_govspeak "[Image: This is the name of my &%$@€? image]", images: [image] do
|
78
|
+
assert_html_output(
|
79
|
+
"<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
75
84
|
test "Image is not inserted when it does not start on a new line" do
|
76
85
|
given_govspeak "some text [Image:image-id]", images: [build_image] do
|
77
86
|
assert_html_output("<p>some text [Image:image-id]</p>")
|
@@ -88,5 +97,13 @@ class GovspeakImagesTest < Minitest::Test
|
|
88
97
|
"<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>\n<p>some text</p>",
|
89
98
|
)
|
90
99
|
end
|
100
|
+
|
101
|
+
given_govspeak "some text\n[Image:image-id]\nsome more text", images: [build_image] do
|
102
|
+
assert_html_output <<~HTML
|
103
|
+
<p>some text</p>
|
104
|
+
<figure class="image embedded"><div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div></figure>
|
105
|
+
<p>some more text</p>
|
106
|
+
HTML
|
107
|
+
end
|
91
108
|
end
|
92
109
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
|
-
class
|
4
|
-
def
|
3
|
+
class GovspeakTablesTest < Minitest::Test
|
4
|
+
def expected_outcome_for_headers
|
5
5
|
%(
|
6
6
|
<table>
|
7
7
|
<thead>
|
@@ -248,30 +248,44 @@ class GovspeakTableWithHeadersTest < Minitest::Test
|
|
248
248
|
end
|
249
249
|
|
250
250
|
test "Cells with |# are headers" do
|
251
|
-
assert_equal document_body_with_hashes_for_all_headers.to_html
|
251
|
+
assert_equal expected_outcome_for_headers, document_body_with_hashes_for_all_headers.to_html
|
252
252
|
end
|
253
253
|
|
254
254
|
test "Cells outside of thead with |# are th; thead still only contains th" do
|
255
|
-
assert_equal document_body_with_hashes_for_row_headers.to_html
|
255
|
+
assert_equal expected_outcome_for_headers, document_body_with_hashes_for_row_headers.to_html
|
256
256
|
end
|
257
257
|
|
258
258
|
test "Cells are given classes to indicate alignment" do
|
259
|
-
assert_equal document_body_with_alignments.to_html
|
259
|
+
assert_equal expected_outcome_for_table_with_alignments, document_body_with_alignments.to_html
|
260
|
+
end
|
261
|
+
|
262
|
+
test "Invalid alignment properties are dropped from cells" do
|
263
|
+
html = %(<table><tbody><tr><td style="text-align: middle">middle</td></tr></tbody></table>)
|
264
|
+
expected = "<table><tbody><tr><td>middle</td></tr></tbody></table>\n"
|
265
|
+
|
266
|
+
assert_equal expected, Govspeak::Document.new(html).to_html
|
267
|
+
end
|
268
|
+
|
269
|
+
test "Styles other than text-align are ignored on a table cell" do
|
270
|
+
html = %(<table><tbody><tr><td style="text-align: center; width: 100px;">middle</td></tr></tbody></table>)
|
271
|
+
expected = %(<table><tbody><tr><td class="cell-text-center">middle</td></tr></tbody></table>\n)
|
272
|
+
|
273
|
+
assert_equal expected, Govspeak::Document.new(html).to_html
|
260
274
|
end
|
261
275
|
|
262
276
|
test "Table headers with a scope of row are only in the first column of the table" do
|
263
|
-
assert_equal document_body_with_table_headers_in_the_wrong_place.to_html
|
277
|
+
assert_equal expected_outcome_for_table_headers_in_the_wrong_place, document_body_with_table_headers_in_the_wrong_place.to_html
|
264
278
|
end
|
265
279
|
|
266
280
|
test "Table headers with a scope of row can have embedded links" do
|
267
|
-
assert_equal document_body_with_table_headers_containing_links.to_html
|
281
|
+
assert_equal expected_outcome_for_table_headers_containing_links, document_body_with_table_headers_containing_links.to_html
|
268
282
|
end
|
269
283
|
|
270
284
|
test "Table headers are not blank" do
|
271
|
-
assert_equal document_body_with_blank_table_headers.to_html
|
285
|
+
assert_equal expected_outcome_for_table_with_blank_table_headers, document_body_with_blank_table_headers.to_html
|
272
286
|
end
|
273
287
|
|
274
288
|
test "Table header superscript should parse" do
|
275
|
-
assert_equal document_body_with_table_headers_containing_superscript.to_html, expected_outcome_for_table_with_table_headers_containing_superscript
|
289
|
+
assert_equal expected_outcome_for_table_with_table_headers_containing_superscript, document_body_with_table_headers_containing_superscript.to_html, expected_outcome_for_table_with_table_headers_containing_superscript
|
276
290
|
end
|
277
291
|
end
|
data/test/html_sanitizer_test.rb
CHANGED
@@ -17,6 +17,16 @@ class HtmlSanitizerTest < Minitest::Test
|
|
17
17
|
assert_equal "<a href=\"/\">Link</a>", Govspeak::HtmlSanitizer.new(html).sanitize
|
18
18
|
end
|
19
19
|
|
20
|
+
test "disallow style attributes" do
|
21
|
+
html = '<a href="/" style="font-weight:bold">Link</a>'
|
22
|
+
assert_equal '<a href="/">Link</a>', Govspeak::HtmlSanitizer.new(html).sanitize
|
23
|
+
end
|
24
|
+
|
25
|
+
test "disallow style elements" do
|
26
|
+
html = "<style>h1 { color: pink; }</style><h1>Hi</h1>"
|
27
|
+
assert_equal "<h1>Hi</h1>", Govspeak::HtmlSanitizer.new(html).sanitize
|
28
|
+
end
|
29
|
+
|
20
30
|
test "allow non-JS HTML content" do
|
21
31
|
html = "<a href='foo'>"
|
22
32
|
assert_equal "<a href=\"foo\"></a>", Govspeak::HtmlSanitizer.new(html).sanitize
|
@@ -79,16 +89,16 @@ class HtmlSanitizerTest < Minitest::Test
|
|
79
89
|
assert_equal "<table><tbody><tr><th>thing</th><td>thing</td></tr></tbody></table>", Govspeak::HtmlSanitizer.new(html).sanitize
|
80
90
|
end
|
81
91
|
|
82
|
-
test "allows
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
92
|
+
test "allows text-align properties on the style attribute for table cells and table headings" do
|
93
|
+
html = "<table><thead><tr><th style=\"text-align: right\">thing</th></tr></thead><tbody><tr><td style=\"text-align: center\">thing</td></tr></tbody></table>"
|
94
|
+
assert_equal html, Govspeak::HtmlSanitizer.new(html).sanitize
|
95
|
+
|
96
|
+
input = "<table><thead><tr><th style=\"text-align: left;width: 100px;\">thing</th></tr></thead><tbody><tr><td style=\"text-align: center;background-color: blue;\">thing</td></tr></tbody></table>"
|
97
|
+
expected = "<table><thead><tr><th style=\"text-align: left;\">thing</th></tr></thead><tbody><tr><td style=\"text-align: center;\">thing</td></tr></tbody></table>"
|
98
|
+
assert_equal expected, Govspeak::HtmlSanitizer.new(input).sanitize
|
87
99
|
|
88
100
|
[
|
89
101
|
"width: 10000px",
|
90
|
-
"text-align: middle",
|
91
|
-
"text-align: left; width: 10px",
|
92
102
|
"background-image: url(javascript:alert('XSS'))",
|
93
103
|
"expression(alert('XSS'));",
|
94
104
|
].each do |style|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: govspeak
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 8.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GOV.UK Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionview
|
@@ -190,14 +190,14 @@ dependencies:
|
|
190
190
|
requirements:
|
191
191
|
- - '='
|
192
192
|
- !ruby/object:Gem::Version
|
193
|
-
version: 4.
|
193
|
+
version: 4.11.0
|
194
194
|
type: :development
|
195
195
|
prerelease: false
|
196
196
|
version_requirements: !ruby/object:Gem::Requirement
|
197
197
|
requirements:
|
198
198
|
- - '='
|
199
199
|
- !ruby/object:Gem::Version
|
200
|
-
version: 4.
|
200
|
+
version: 4.11.0
|
201
201
|
- !ruby/object:Gem::Dependency
|
202
202
|
name: simplecov
|
203
203
|
requirement: !ruby/object:Gem::Requirement
|
@@ -302,7 +302,7 @@ files:
|
|
302
302
|
- test/govspeak_link_extractor_test.rb
|
303
303
|
- test/govspeak_link_test.rb
|
304
304
|
- test/govspeak_structured_headers_test.rb
|
305
|
-
- test/
|
305
|
+
- test/govspeak_tables_test.rb
|
306
306
|
- test/govspeak_test.rb
|
307
307
|
- test/govspeak_test_helper.rb
|
308
308
|
- test/html_sanitizer_test.rb
|
@@ -327,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
327
327
|
- !ruby/object:Gem::Version
|
328
328
|
version: '0'
|
329
329
|
requirements: []
|
330
|
-
rubygems_version: 3.4.
|
330
|
+
rubygems_version: 3.4.15
|
331
331
|
signing_key:
|
332
332
|
specification_version: 4
|
333
333
|
summary: Markup language for single domain
|
@@ -346,7 +346,7 @@ test_files:
|
|
346
346
|
- test/govspeak_link_extractor_test.rb
|
347
347
|
- test/govspeak_link_test.rb
|
348
348
|
- test/govspeak_structured_headers_test.rb
|
349
|
-
- test/
|
349
|
+
- test/govspeak_tables_test.rb
|
350
350
|
- test/govspeak_test.rb
|
351
351
|
- test/govspeak_test_helper.rb
|
352
352
|
- test/html_sanitizer_test.rb
|