summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Allen <dan.j.allen@gmail.com>2022-07-05 01:55:45 -0600
committerGitHub <noreply@github.com>2022-07-05 01:55:45 -0600
commit7822fbe9223ca81e9895f6132b2274e89f26c623 (patch)
tree634d315adf29cfe5df18647d2e87fffe7afb67de
parent3c575b9de21790d3b812c74948c42eb6470ec02b (diff)
resolves #4306 disallow the use of the left square bracket in an attribute list on formatted text (PR #4307)
-rw-r--r--CHANGELOG.adoc1
-rw-r--r--lib/asciidoctor.rb36
-rw-r--r--lib/asciidoctor/rx.rb6
-rw-r--r--test/substitutions_test.rb5
4 files changed, 28 insertions, 20 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 707a6fb1..0fec8b7f 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -38,6 +38,7 @@ Compliance::
* Don't promote level-0 special section at start of document to document title (#4151)
* Disallow the use of dot (`.`) in the name of a named element attribute (#4147)
+ * Disallow the use of the left square bracket in an attribute list on formatted text (#4306)
Improvements::
diff --git a/lib/asciidoctor.rb b/lib/asciidoctor.rb
index 581c8647..45c28f6a 100644
--- a/lib/asciidoctor.rb
+++ b/lib/asciidoctor.rb
@@ -436,6 +436,8 @@ module Asciidoctor
CC_WORD = CG_WORD = '\p{Word}'
end
+ QuoteAttributeListRxt = %(\\[([^\\[\\]]+)\\])
+
QUOTE_SUBS = {}.tap do |accum|
# unconstrained quotes:: can appear anywhere
# constrained quotes:: must be bordered by non-word characters
@@ -443,46 +445,46 @@ module Asciidoctor
# the order in which they are replaced is important
accum[false] = normal = [
# **strong**
- [:strong, :unconstrained, /\\?(?:\[([^\]]+)\])?\*\*(#{CC_ALL}+?)\*\*/m],
+ [:strong, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?\*\*(#{CC_ALL}+?)\*\*/m],
# *strong*
- [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m],
+ [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m],
# "`double-quoted`"
- [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m],
+ [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m],
# '`single-quoted`'
- [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:\[([^\]]+)\])?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m],
+ [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:#{QuoteAttributeListRxt})?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m],
# ``monospaced``
- [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?``(#{CC_ALL}+?)``/m],
+ [:monospaced, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?``(#{CC_ALL}+?)``/m],
# `monospaced`
- [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m],
+ [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:#{QuoteAttributeListRxt})?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m],
# __emphasis__
- [:emphasis, :unconstrained, /\\?(?:\[([^\]]+)\])?__(#{CC_ALL}+?)__/m],
+ [:emphasis, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?__(#{CC_ALL}+?)__/m],
# _emphasis_
- [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
+ [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
# ##mark## (referred to in AsciiDoc.py as unquoted)
- [:mark, :unconstrained, /\\?(?:\[([^\]]+)\])?##(#{CC_ALL}+?)##/m],
+ [:mark, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?##(#{CC_ALL}+?)##/m],
# #mark# (referred to in AsciiDoc.py as unquoted)
- [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
+ [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:#{QuoteAttributeListRxt})?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
# ^superscript^
- [:superscript, :unconstrained, /\\?(?:\[([^\]]+)\])?\^(\S+?)\^/],
+ [:superscript, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?\^(\S+?)\^/],
# ~subscript~
- [:subscript, :unconstrained, /\\?(?:\[([^\]]+)\])?~(\S+?)~/],
+ [:subscript, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?~(\S+?)~/],
]
accum[true] = compat = normal.drop 0
# ``quoted''
- compat[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m]
+ compat[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m]
# `quoted'
- compat[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
+ compat[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
# ++monospaced++
- compat[4] = [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?\+\+(#{CC_ALL}+?)\+\+/m]
+ compat[4] = [:monospaced, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?\+\+(#{CC_ALL}+?)\+\+/m]
# +monospaced+
- compat[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m]
+ compat[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m]
# #unquoted#
#compat[8] = [:unquoted, *compat[8][1..-1]]
# ##unquoted##
#compat[9] = [:unquoted, *compat[9][1..-1]]
# 'emphasis'
- compat.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
+ compat.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
end
# NOTE order of replacements is significant
diff --git a/lib/asciidoctor/rx.rb b/lib/asciidoctor/rx.rb
index 6cc378ea..5e73b13f 100644
--- a/lib/asciidoctor/rx.rb
+++ b/lib/asciidoctor/rx.rb
@@ -573,8 +573,8 @@ module Asciidoctor
#
# NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior
InlinePassRx = {
- false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
- true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m],
+ false => ['+', '`', /(^|[^#{CC_WORD};:])(?:#{QuoteAttributeListRxt})?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
+ true => ['`', nil, /(^|[^`#{CC_WORD}])(?:#{QuoteAttributeListRxt})?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m],
}
# Matches an inline plus passthrough spanning multiple lines, but only when it occurs directly
@@ -595,7 +595,7 @@ module Asciidoctor
# pass:quotes[text]
#
# NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py
- InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
+ InlinePassMacroRx = /(?:(?:(\\?)#{QuoteAttributeListRxt})?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
# Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
#
diff --git a/test/substitutions_test.rb b/test/substitutions_test.rb
index b4b4ab09..f75abfa4 100644
--- a/test/substitutions_test.rb
+++ b/test/substitutions_test.rb
@@ -203,6 +203,11 @@ context 'Substitutions' do
assert_equal '<span class="statement">a few words</span>', para.sub_quotes(para.source)
end
+ test 'does not recognize attribute list with left square bracket on formatted text' do
+ para = block_from_string 'key: [ *before [.redacted]#redacted# after* ]'
+ assert_equal 'key: [ <strong>before <span class="redacted">redacted</span> after</strong> ]', para.sub_quotes(para.source)
+ end
+
test 'single-line constrained strong string' do
para = block_from_string '*a few strong words*'
assert_equal '<strong>a few strong words</strong>', para.sub_quotes(para.source)