qiita-markdown 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of qiita-markdown might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 142f1f300d42442f62e84b3ffe54321caa229b2b
4
- data.tar.gz: 8029e1417074df1acc82af5cfd075efebe87c959
3
+ metadata.gz: 83c8da2e7dd55be72fce627ec6b14cb23362346e
4
+ data.tar.gz: 5752e8dae6fbbd5c9f9f94dc3e4a4320b9d222fa
5
5
  SHA512:
6
- metadata.gz: db7ea2bc3c6e992848e569c695dddb5923d8aeba7f289eb845c6feea58b7150152317df655540350db6967d0871b93ebaa6e7fc82c475742b6ad95f98c779cb1
7
- data.tar.gz: c98d93c9fd42393475a09560131d125eafe6d5f43ac429b333a2b2dfecfacf4c9fb8035c5a1cc3234c1209417591ba86aaac409501750a5c1b1c013198904b23
6
+ metadata.gz: 885e5a8cc2397dfedbe970b5f85e8dc952275986912149c3a24a1068b32b1c0922b7d1fb9a19db93133410d130ccbe4cda3d45218dc15bbc9b43baf5070cf0bb
7
+ data.tar.gz: f2a6c4b200eb88f247c16a4d87b4807a083042050e862a58fb7392bfbb25428f7a710ba3c1d1bf67fff0c02f1a1ff2e9fc443107f4f2a987e7147d4b5a965ab1
data/.travis.yml CHANGED
@@ -7,8 +7,6 @@ before_install:
7
7
  - gem update bundler
8
8
  language: ruby
9
9
  rvm:
10
- - 2.0
11
- - 2.1
12
10
  - 2.2
13
11
  - 2.3
14
12
  - 2.4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 0.19.0
4
+
5
+ - Drop 2.0 and 2.1 from support Ruby versions
6
+ - Rename `Sanitize` as `FinalSanitizer`
7
+ - Add `:strict` context for stricter sanitization
8
+
3
9
  ## 0.18.0
4
10
 
5
11
  - Extract heading decoration logic from Greenmat renderer to `Toc` filter
@@ -11,16 +11,17 @@ require "qiita/markdown/filters/checkbox"
11
11
  require "qiita/markdown/filters/code"
12
12
  require "qiita/markdown/filters/emoji"
13
13
  require "qiita/markdown/filters/external_link"
14
+ require "qiita/markdown/filters/final_sanitizer"
14
15
  require "qiita/markdown/filters/footnote"
15
16
  require "qiita/markdown/filters/greenmat"
16
17
  require "qiita/markdown/filters/group_mention"
17
18
  require "qiita/markdown/filters/image_link"
18
19
  require "qiita/markdown/filters/mention"
19
- require "qiita/markdown/filters/sanitize"
20
20
  require "qiita/markdown/filters/simplify"
21
21
  require "qiita/markdown/filters/syntax_highlight"
22
22
  require "qiita/markdown/filters/toc"
23
23
  require "qiita/markdown/filters/truncate"
24
+ require "qiita/markdown/filters/user_input_sanitizer"
24
25
  require "qiita/markdown/greenmat/heading_rendering"
25
26
  require "qiita/markdown/greenmat/html_renderer"
26
27
  require "qiita/markdown/greenmat/html_toc_renderer"
@@ -3,7 +3,13 @@ module Qiita
3
3
  module Filters
4
4
  # Sanitizes undesirable elements by whitelist-based rule.
5
5
  # You can pass optional :rule and :script context.
6
- class Sanitize < HTML::Pipeline::Filter
6
+ #
7
+ # Since this filter is applied at the end of html-pipeline, it's rules
8
+ # are intentionally weakened to allow elements and attributes which are
9
+ # generated by other filters.
10
+ #
11
+ # @see Qiita::Markdown::Filters::UserInputSanitizerr
12
+ class FinalSanitizer < HTML::Pipeline::Filter
7
13
  # Wraps a node env to transform invalid node.
8
14
  class TransformableNode
9
15
  def self.call(*args)
@@ -4,7 +4,7 @@ module Qiita
4
4
  # A filter for simplifying document structure by removing complex markups
5
5
  # (mainly block elements) and complex contents.
6
6
  #
7
- # The logic of this filter is similar to the `Sanitize` filter, but this
7
+ # The logic of this filter is similar to the `FinalSanitizer` filter, but this
8
8
  # does not use the `sanitize` gem internally for the following reasons:
9
9
  #
10
10
  # * Each filter should do only its own responsibility, and this filter is
@@ -12,7 +12,7 @@ module Qiita
12
12
  #
13
13
  # * The `sanitize` gem automatically adds extra transformers even if we
14
14
  # want to clean up only some elements, and they would be run in the
15
- # `Sanitize` filter later.
15
+ # `FinalSanitizer` filter later.
16
16
  # https://github.com/rgrove/sanitize/blob/v3.1.2/lib/sanitize.rb#L77-L100
17
17
  class Simplify < HTML::Pipeline::Filter
18
18
  SIMPLE_ELEMENTS = %w[a b code em i ins q s samp span strike strong sub sup var]
@@ -0,0 +1,102 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Filters
4
+ # Sanitizes user input if :strict context is given.
5
+ class UserInputSanitizer < HTML::Pipeline::Filter
6
+ class AttributeFilter
7
+ FILTERS = {
8
+ "a" => {
9
+ "class" => %w[autolink],
10
+ "rel" => %w[footnote url],
11
+ "rev" => %w[footnote],
12
+ },
13
+ "sup" => {
14
+ "id" => /\Afnref\d+\z/,
15
+ },
16
+ "li" => {
17
+ "id" => /\Afn\d+\z/,
18
+ },
19
+ }.freeze
20
+
21
+ DELIMITER = " ".freeze
22
+
23
+ def self.call(*args)
24
+ new(*args).transform
25
+ end
26
+
27
+ def initialize(env)
28
+ @env = env
29
+ end
30
+
31
+ def transform
32
+ return unless FILTERS.key?(name)
33
+ FILTERS[name].each_pair do |attr, pattern|
34
+ filter_attribute(attr, pattern) if node.attributes.key?(attr)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def filter_attribute(attr, pattern)
41
+ node[attr] = node[attr].split(DELIMITER).select do |value|
42
+ pattern.is_a?(Array) ? pattern.include?(value) : (pattern =~ value)
43
+ end.join(DELIMITER)
44
+ end
45
+
46
+ def name
47
+ @env[:node_name]
48
+ end
49
+
50
+ def node
51
+ @env[:node]
52
+ end
53
+ end
54
+
55
+ RULE = {
56
+ elements: %w[
57
+ a b blockquote br code dd del details div dl dt em font h1 h2 h3 h4 h5 h6
58
+ hr i img input ins kbd li ol p pre q rp rt ruby s samp strike strong sub
59
+ summary sup table tbody td tfoot th thead tr ul var
60
+ ],
61
+ attributes: {
62
+ "a" => %w[class href rel title],
63
+ "blockquote" => %w[cite],
64
+ "code" => %w[data-metadata],
65
+ "div" => %w[class],
66
+ "font" => %w[color],
67
+ "h1" => %w[id],
68
+ "h2" => %w[id],
69
+ "h3" => %w[id],
70
+ "h4" => %w[id],
71
+ "h5" => %w[id],
72
+ "h6" => %w[id],
73
+ "img" => %w[alt height src title width],
74
+ "ins" => %w[cite datetime],
75
+ "li" => %w[id],
76
+ "q" => %w[cite],
77
+ "sup" => %w[id],
78
+ "td" => %w[colspan rowspan style],
79
+ "th" => %w[colspan rowspan style],
80
+ },
81
+ protocols: {
82
+ "a" => { "href" => ["http", "https", "mailto", :relative] },
83
+ "blockquote" => { "cite" => ["http", "https", :relative] },
84
+ "q" => { "cite" => ["http", "https", :relative] },
85
+ },
86
+ css: {
87
+ properties: %w[text-align],
88
+ },
89
+ remove_contents: %w[
90
+ script
91
+ ],
92
+ transformers: AttributeFilter,
93
+ }.freeze
94
+
95
+ def call
96
+ ::Sanitize.clean_node!(doc, RULE) if context[:strict]
97
+ doc
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -10,6 +10,7 @@ module Qiita
10
10
  def self.default_filters
11
11
  [
12
12
  Filters::Greenmat,
13
+ Filters::UserInputSanitizer,
13
14
  Filters::ImageLink,
14
15
  Filters::Footnote,
15
16
  Filters::Code,
@@ -20,7 +21,7 @@ module Qiita
20
21
  Filters::Mention,
21
22
  Filters::GroupMention,
22
23
  Filters::ExternalLink,
23
- Filters::Sanitize,
24
+ Filters::FinalSanitizer,
24
25
  ]
25
26
  end
26
27
  end
@@ -16,11 +16,12 @@ module Qiita
16
16
  def self.default_filters
17
17
  [
18
18
  Filters::Greenmat,
19
+ Filters::UserInputSanitizer,
19
20
  Filters::Simplify,
20
21
  Filters::Emoji,
21
22
  Filters::Mention,
22
23
  Filters::ExternalLink,
23
- Filters::Sanitize,
24
+ Filters::FinalSanitizer,
24
25
  Filters::Truncate,
25
26
  ]
26
27
  end
@@ -1,5 +1,5 @@
1
1
  module Qiita
2
2
  module Markdown
3
- VERSION = "0.18.0"
3
+ VERSION = "0.19.0"
4
4
  end
5
5
  end
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.required_ruby_version = ">= 2.0.0"
19
+ spec.required_ruby_version = ">= 2.2.0"
20
20
 
21
21
  spec.add_dependency "gemoji"
22
22
  spec.add_dependency "github-linguist", "~> 4.0"
@@ -18,1025 +18,1164 @@ describe Qiita::Markdown::Processor do
18
18
  described_class.new(context).call(markdown)
19
19
  end
20
20
 
21
- context "with valid condition" do
22
- let(:markdown) do
23
- <<-EOS.strip_heredoc
24
- example
25
- EOS
26
- end
21
+ shared_examples_for "basic markdown syntax" do
22
+ context "with valid condition" do
23
+ let(:markdown) do
24
+ <<-EOS.strip_heredoc
25
+ example
26
+ EOS
27
+ end
27
28
 
28
- it "returns a Hash with HTML output and other metadata" do
29
- expect(result[:codes]).to be_an Array
30
- expect(result[:mentioned_usernames]).to be_an Array
31
- expect(result[:output]).to be_a Nokogiri::HTML::DocumentFragment
29
+ it "returns a Hash with HTML output and other metadata" do
30
+ expect(result[:codes]).to be_an Array
31
+ expect(result[:mentioned_usernames]).to be_an Array
32
+ expect(result[:output]).to be_a Nokogiri::HTML::DocumentFragment
33
+ end
32
34
  end
33
- end
34
35
 
35
- context "with HTML-characters" do
36
- let(:markdown) do
37
- "<>&"
38
- end
36
+ context "with HTML-characters" do
37
+ let(:markdown) do
38
+ "<>&"
39
+ end
39
40
 
40
- it "sanitizes them" do
41
- should eq <<-EOS.strip_heredoc
42
- <p>&lt;&gt;&amp;</p>
43
- EOS
41
+ it "sanitizes them" do
42
+ should eq <<-EOS.strip_heredoc
43
+ <p>&lt;&gt;&amp;</p>
44
+ EOS
45
+ end
44
46
  end
45
- end
46
47
 
47
- context "with email address" do
48
- let(:markdown) do
49
- "test@example.com"
50
- end
48
+ context "with email address" do
49
+ let(:markdown) do
50
+ "test@example.com"
51
+ end
51
52
 
52
- it "replaces with mailto link" do
53
- should eq <<-EOS.strip_heredoc
54
- <p><a href="mailto:test@example.com" class="autolink">test@example.com</a></p>
55
- EOS
53
+ it "replaces with mailto link" do
54
+ should eq <<-EOS.strip_heredoc
55
+ <p><a href="mailto:test@example.com" class="autolink">test@example.com</a></p>
56
+ EOS
57
+ end
56
58
  end
57
- end
58
59
 
59
- context "with headings" do
60
- let(:markdown) do
61
- <<-EOS.strip_heredoc
62
- # a
63
- ## a
64
- ### a
65
- ### a
66
- EOS
67
- end
60
+ context "with headings" do
61
+ let(:markdown) do
62
+ <<-EOS.strip_heredoc
63
+ # a
64
+ ## a
65
+ ### a
66
+ ### a
67
+ EOS
68
+ end
68
69
 
69
- it "adds ID for ToC" do
70
- should eq <<-EOS.strip_heredoc
70
+ it "adds ID for ToC" do
71
+ should eq <<-EOS.strip_heredoc
71
72
 
72
- <h1>
73
- <span id="a" class="fragment"></span><a href="#a"><i class="fa fa-link"></i></a>a</h1>
73
+ <h1>
74
+ <span id="a" class="fragment"></span><a href="#a"><i class="fa fa-link"></i></a>a</h1>
74
75
 
75
- <h2>
76
- <span id="a-1" class="fragment"></span><a href="#a-1"><i class="fa fa-link"></i></a>a</h2>
76
+ <h2>
77
+ <span id="a-1" class="fragment"></span><a href="#a-1"><i class="fa fa-link"></i></a>a</h2>
77
78
 
78
- <h3>
79
- <span id="a-2" class="fragment"></span><a href="#a-2"><i class="fa fa-link"></i></a>a</h3>
79
+ <h3>
80
+ <span id="a-2" class="fragment"></span><a href="#a-2"><i class="fa fa-link"></i></a>a</h3>
80
81
 
81
- <h3>
82
- <span id="a-3" class="fragment"></span><a href="#a-3"><i class="fa fa-link"></i></a>a</h3>
83
- EOS
82
+ <h3>
83
+ <span id="a-3" class="fragment"></span><a href="#a-3"><i class="fa fa-link"></i></a>a</h3>
84
+ EOS
85
+ end
84
86
  end
85
- end
86
87
 
87
- context "with heading whose title includes special HTML characters" do
88
- let(:markdown) do
89
- <<-EOS.strip_heredoc
90
- # <b>R&amp;B</b>
91
- EOS
92
- end
88
+ context "with heading whose title includes special HTML characters" do
89
+ let(:markdown) do
90
+ <<-EOS.strip_heredoc
91
+ # <b>R&amp;B</b>
92
+ EOS
93
+ end
93
94
 
94
- it "generates fragment identifier by sanitizing the special characters in the title" do
95
- should eq <<-EOS.strip_heredoc
95
+ it "generates fragment identifier by sanitizing the special characters in the title" do
96
+ should eq <<-EOS.strip_heredoc
96
97
 
97
- <h1>
98
- <span id="rb" class="fragment"></span><a href="#rb"><i class="fa fa-link"></i></a><b>R&amp;B</b>
99
- </h1>
100
- EOS
98
+ <h1>
99
+ <span id="rb" class="fragment"></span><a href="#rb"><i class="fa fa-link"></i></a><b>R&amp;B</b>
100
+ </h1>
101
+ EOS
102
+ end
101
103
  end
102
- end
103
104
 
104
- context "with manually inputted heading HTML tags without id attribute" do
105
- let(:markdown) do
106
- <<-EOS.strip_heredoc
107
- <h1>foo</h1>
108
- EOS
109
- end
105
+ context "with manually inputted heading HTML tags without id attribute" do
106
+ let(:markdown) do
107
+ <<-EOS.strip_heredoc
108
+ <h1>foo</h1>
109
+ EOS
110
+ end
110
111
 
111
- it "does nothing" do
112
- should eq <<-EOS.strip_heredoc
113
- <h1>foo</h1>
114
- EOS
112
+ it "does nothing" do
113
+ should eq <<-EOS.strip_heredoc
114
+ <h1>foo</h1>
115
+ EOS
116
+ end
115
117
  end
116
- end
117
118
 
118
- context "with code" do
119
- let(:markdown) do
120
- <<-EOS.strip_heredoc
121
- ```foo.rb
122
- puts 'hello world'
123
- ```
124
- EOS
125
- end
119
+ context "with code" do
120
+ let(:markdown) do
121
+ <<-EOS.strip_heredoc
122
+ ```foo.rb
123
+ puts 'hello world'
124
+ ```
125
+ EOS
126
+ end
126
127
 
127
- it "returns detected codes" do
128
- expect(result[:codes]).to eq [
129
- {
130
- code: "puts 'hello world'\n",
131
- filename: "foo.rb",
132
- language: "ruby",
133
- },
134
- ]
128
+ it "returns detected codes" do
129
+ expect(result[:codes]).to eq [
130
+ {
131
+ code: "puts 'hello world'\n",
132
+ filename: "foo.rb",
133
+ language: "ruby",
134
+ },
135
+ ]
136
+ end
135
137
  end
136
- end
137
138
 
138
- context "with code & filename" do
139
- let(:markdown) do
140
- <<-EOS.strip_heredoc
141
- ```example.rb
142
- 1
143
- ```
144
- EOS
145
- end
139
+ context "with code & filename" do
140
+ let(:markdown) do
141
+ <<-EOS.strip_heredoc
142
+ ```example.rb
143
+ 1
144
+ ```
145
+ EOS
146
+ end
146
147
 
147
- it "returns code-frame, code-lang, and highlighted pre element" do
148
- should eq <<-EOS.strip_heredoc
149
- <div class="code-frame" data-lang="ruby">
150
- <div class="code-lang"><span class="bold">example.rb</span></div>
151
- <div class="highlight"><pre><span></span><span class="mi">1</span>
152
- </pre></div>
153
- </div>
154
- EOS
148
+ it "returns code-frame, code-lang, and highlighted pre element" do
149
+ should eq <<-EOS.strip_heredoc
150
+ <div class="code-frame" data-lang="ruby">
151
+ <div class="code-lang"><span class="bold">example.rb</span></div>
152
+ <div class="highlight"><pre><span></span><span class="mi">1</span>
153
+ </pre></div>
154
+ </div>
155
+ EOS
156
+ end
155
157
  end
156
- end
157
158
 
158
- context "with code & filename with .php" do
159
- let(:markdown) do
160
- <<-EOS.strip_heredoc
161
- ```example.php
162
- 1
163
- ```
164
- EOS
165
- end
159
+ context "with code & filename with .php" do
160
+ let(:markdown) do
161
+ <<-EOS.strip_heredoc
162
+ ```example.php
163
+ 1
164
+ ```
165
+ EOS
166
+ end
166
167
 
167
- it "returns PHP code-frame" do
168
- should eq <<-EOS.strip_heredoc
169
- <div class="code-frame" data-lang="php">
170
- <div class="code-lang"><span class="bold">example.php</span></div>
171
- <div class="highlight"><pre><span></span><span class="mi">1</span>
172
- </pre></div>
173
- </div>
174
- EOS
168
+ it "returns PHP code-frame" do
169
+ should eq <<-EOS.strip_heredoc
170
+ <div class="code-frame" data-lang="php">
171
+ <div class="code-lang"><span class="bold">example.php</span></div>
172
+ <div class="highlight"><pre><span></span><span class="mi">1</span>
173
+ </pre></div>
174
+ </div>
175
+ EOS
176
+ end
175
177
  end
176
- end
177
178
 
178
- context "with malicious script in filename" do
179
- let(:markdown) do
180
- <<-EOS.strip_heredoc
181
- ```js:test<script>alert(1)</script>
182
- 1
183
- ```
184
- EOS
185
- end
179
+ context "with code & no filename" do
180
+ let(:markdown) do
181
+ <<-EOS.strip_heredoc
182
+ ```ruby
183
+ 1
184
+ ```
185
+ EOS
186
+ end
186
187
 
187
- it "sanitizes script element" do
188
- should eq <<-EOS.strip_heredoc
189
- <div class="code-frame" data-lang="js">
190
- <div class="code-lang"><span class="bold">test</span></div>
191
- <div class="highlight"><pre><span></span><span class="mi">1</span>
192
- </pre></div>
193
- </div>
194
- EOS
188
+ it "returns code-frame and highlighted pre element" do
189
+ should eq <<-EOS.strip_heredoc
190
+ <div class="code-frame" data-lang="ruby"><div class="highlight"><pre><span></span><span class="mi">1</span>
191
+ </pre></div></div>
192
+ EOS
193
+ end
195
194
  end
196
- end
197
195
 
198
- context "with code & no filename" do
199
- let(:markdown) do
200
- <<-EOS.strip_heredoc
201
- ```ruby
202
- 1
203
- ```
204
- EOS
205
- end
196
+ context "with undefined but aliased language" do
197
+ let(:markdown) do
198
+ <<-EOS.strip_heredoc
199
+ ```zsh
200
+ true
201
+ ```
202
+ EOS
203
+ end
206
204
 
207
- it "returns code-frame and highlighted pre element" do
208
- should eq <<-EOS.strip_heredoc
209
- <div class="code-frame" data-lang="ruby"><div class="highlight"><pre><span></span><span class="mi">1</span>
210
- </pre></div></div>
211
- EOS
205
+ it "returns aliased language name" do
206
+ expect(result[:codes]).to eq [
207
+ {
208
+ code: "true\n",
209
+ filename: nil,
210
+ language: "bash",
211
+ },
212
+ ]
213
+ end
212
214
  end
213
- end
214
215
 
215
- context "with undefined but aliased language" do
216
- let(:markdown) do
217
- <<-EOS.strip_heredoc
218
- ```zsh
219
- true
220
- ```
221
- EOS
222
- end
216
+ context "with code with leading and trailing newlines" do
217
+ let(:markdown) do
218
+ <<-EOS.strip_heredoc
219
+ ```
223
220
 
224
- it "returns aliased language name" do
225
- expect(result[:codes]).to eq [
226
- {
227
- code: "true\n",
228
- filename: nil,
229
- language: "bash",
230
- },
231
- ]
232
- end
233
- end
221
+ foo
234
222
 
235
- context "with code with leading and trailing newlines" do
236
- let(:markdown) do
237
- <<-EOS.strip_heredoc
238
- ```
223
+ ```
224
+ EOS
225
+ end
239
226
 
240
- foo
227
+ it "does not strip the newlines" do
228
+ should eq <<-EOS.strip_heredoc
229
+ <div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>
230
+ foo
241
231
 
242
- ```
243
- EOS
232
+ </pre></div></div>
233
+ EOS
234
+ end
244
235
  end
245
236
 
246
- it "does not strip the newlines" do
247
- should eq <<-EOS.strip_heredoc
248
- <div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>
249
- foo
237
+ context "with mention" do
238
+ let(:markdown) do
239
+ "@alice"
240
+ end
250
241
 
251
- </pre></div></div>
252
- EOS
242
+ it "replaces mention with link" do
243
+ should include(<<-EOS.strip_heredoc.rstrip)
244
+ <a href="/alice" class="user-mention js-hovercard" title="alice" data-hovercard-target-type="user" data-hovercard-target-name="alice">@alice</a>
245
+ EOS
246
+ end
253
247
  end
254
- end
255
248
 
256
- context "with script element" do
257
- let(:markdown) do
258
- <<-EOS.strip_heredoc
259
- <script>alert(1)</script>
260
- EOS
261
- end
249
+ context "with mention to short name user" do
250
+ let(:markdown) do
251
+ "@al"
252
+ end
262
253
 
263
- it "removes script element" do
264
- should eq "\n"
254
+ it "replaces mention with link" do
255
+ should include(<<-EOS.strip_heredoc.rstrip)
256
+ <a href="/al" class="user-mention js-hovercard" title="al" data-hovercard-target-type="user" data-hovercard-target-name="al">@al</a>
257
+ EOS
258
+ end
265
259
  end
266
- end
267
260
 
268
- context "with script context" do
269
- before do
270
- context[:script] = true
271
- end
261
+ context "with mentions in complex patterns" do
262
+ let(:markdown) do
263
+ <<-EOS.strip_heredoc
264
+ @alice
265
+
266
+ ```
267
+ @bob
268
+ ```
269
+
270
+ @charlie/@dave
271
+ @ell_en
272
+ @fran-k
273
+ @Isaac
274
+ @justin
275
+ @justin
276
+ @mallory@github
277
+ @#{'o' * 33}
278
+ @o
279
+ @o-
280
+ @-o
281
+ @o_
282
+ @_o
283
+ EOS
284
+ end
272
285
 
273
- let(:markdown) do
274
- <<-EOS.strip_heredoc
275
- <p><script>alert(1)</script></p>
276
- EOS
286
+ it "extracts mentions correctly" do
287
+ expect(result[:mentioned_usernames]).to eq %w[
288
+ alice
289
+ dave
290
+ ell_en
291
+ fran-k
292
+ Isaac
293
+ justin
294
+ mallory@github
295
+ o_
296
+ _o
297
+ ]
298
+ end
277
299
  end
278
300
 
279
- it "allows script element" do
280
- should eq markdown
281
- end
282
- end
301
+ context "with mention-like filename on code block" do
302
+ let(:markdown) do
303
+ <<-EOS.strip_heredoc
304
+ ```ruby:@alice
305
+ 1
306
+ ```
307
+ EOS
308
+ end
283
309
 
284
- context "with allowed attributes" do
285
- before do
286
- context[:script] = true
310
+ it "does not treat it as mention" do
311
+ should include(<<-EOS.strip_heredoc.rstrip)
312
+ <div class="code-frame" data-lang="ruby">
313
+ <div class="code-lang"><span class="bold">@alice</span></div>
314
+ <div class="highlight"><pre><span></span><span class="mi">1</span>
315
+ </pre></div>
316
+ </div>
317
+ EOS
318
+ end
287
319
  end
288
320
 
289
- let(:markdown) do
290
- <<-EOS.strip_heredoc
291
- <p><script async data-a="b" type="text/javascript">alert(1)</script></p>
292
- EOS
293
- end
321
+ context "with mention in blockquote" do
322
+ let(:markdown) do
323
+ "> @alice"
324
+ end
294
325
 
295
- it "allows data-attributes" do
296
- should eq markdown
326
+ it "does not replace mention with link" do
327
+ should include(<<-EOS.strip_heredoc.rstrip)
328
+ <blockquote>
329
+ <p>@alice</p>
330
+ </blockquote>
331
+ EOS
332
+ end
297
333
  end
298
- end
299
334
 
300
- context "with iframe" do
301
- before do
302
- context[:script] = true
303
- end
335
+ context "with mention to user whose name starts and ends with underscore" do
336
+ let(:markdown) do
337
+ "@_alice_"
338
+ end
304
339
 
305
- let(:markdown) do
306
- <<-EOS.strip_heredoc
307
- <iframe width="1" height="2" src="//example.com" frameborder="0" allowfullscreen></iframe>
308
- EOS
340
+ it "does not emphasize the name" do
341
+ should include(<<-EOS.strip_heredoc.rstrip)
342
+ <a href="/_alice_" class="user-mention js-hovercard" title="_alice_" data-hovercard-target-type="user" data-hovercard-target-name="_alice_">@_alice_</a>
343
+ EOS
344
+ end
309
345
  end
310
346
 
311
- it "allows iframe with some attributes" do
312
- should eq markdown
313
- end
314
- end
347
+ context "with allowed_usernames context" do
348
+ before do
349
+ context[:allowed_usernames] = ["alice"]
350
+ end
315
351
 
316
- context "with mention" do
317
- let(:markdown) do
318
- "@alice"
319
- end
352
+ let(:markdown) do
353
+ <<-EOS.strip_heredoc
354
+ @alice
355
+ @bob
356
+ EOS
357
+ end
320
358
 
321
- it "replaces mention with link" do
322
- should include(<<-EOS.strip_heredoc.rstrip)
323
- <a href="/alice" class="user-mention js-hovercard" title="alice" data-hovercard-target-type="user" data-hovercard-target-name="alice">@alice</a>
324
- EOS
359
+ it "limits mentions to allowed usernames" do
360
+ expect(result[:mentioned_usernames]).to eq ["alice"]
361
+ end
325
362
  end
326
- end
327
363
 
328
- context "with mention to short name user" do
329
- let(:markdown) do
330
- "@al"
331
- end
364
+ context "with @all and allowed_usernames context" do
365
+ before do
366
+ context[:allowed_usernames] = ["alice", "bob"]
367
+ end
368
+
369
+ let(:markdown) do
370
+ "@all"
371
+ end
332
372
 
333
- it "replaces mention with link" do
334
- should include(<<-EOS.strip_heredoc.rstrip)
335
- <a href="/al" class="user-mention js-hovercard" title="al" data-hovercard-target-type="user" data-hovercard-target-name="al">@al</a>
336
- EOS
373
+ it "links it and reports all allowed users as mentioned user names" do
374
+ should include(<<-EOS.strip_heredoc.rstrip)
375
+ <a href="/" class="user-mention" title="all">@all</a>
376
+ EOS
377
+ expect(result[:mentioned_usernames]).to eq context[:allowed_usernames]
378
+ end
337
379
  end
338
- end
339
380
 
340
- context "with mentions in complex patterns" do
341
- let(:markdown) do
342
- <<-EOS.strip_heredoc
343
- @alice
381
+ context "with @all and @alice" do
382
+ before do
383
+ context[:allowed_usernames] = ["alice", "bob"]
384
+ end
344
385
 
345
- ```
346
- @bob
347
- ```
348
-
349
- @charlie/@dave
350
- @ell_en
351
- @fran-k
352
- @Isaac
353
- @justin
354
- @justin
355
- @mallory@github
356
- @#{'o' * 33}
357
- @o
358
- @o-
359
- @-o
360
- @o_
361
- @_o
362
- EOS
363
- end
364
-
365
- it "extracts mentions correctly" do
366
- expect(result[:mentioned_usernames]).to eq %w[
367
- alice
368
- dave
369
- ell_en
370
- fran-k
371
- Isaac
372
- justin
373
- mallory@github
374
- o_
375
- _o
376
- ]
377
- end
378
- end
386
+ let(:markdown) do
387
+ "@all @alice"
388
+ end
379
389
 
380
- context "with mention-like filename on code block" do
381
- let(:markdown) do
382
- <<-EOS.strip_heredoc
383
- ```ruby:@alice
384
- 1
385
- ```
386
- EOS
390
+ it "does not duplicate mentioned user names" do
391
+ expect(result[:mentioned_usernames]).to eq context[:allowed_usernames]
392
+ end
387
393
  end
388
394
 
389
- it "does not treat it as mention" do
390
- should include(<<-EOS.strip_heredoc.rstrip)
391
- <div class="code-frame" data-lang="ruby">
392
- <div class="code-lang"><span class="bold">@alice</span></div>
393
- <div class="highlight"><pre><span></span><span class="mi">1</span>
394
- </pre></div>
395
- </div>
396
- EOS
397
- end
398
- end
395
+ context "with @all and no allowed_usernames context" do
396
+ let(:markdown) do
397
+ "@all"
398
+ end
399
399
 
400
- context "with mention in blockquote" do
401
- let(:markdown) do
402
- "> @alice"
400
+ it "does not repond to @all" do
401
+ should eq "<p>@all</p>\n"
402
+ expect(result[:mentioned_usernames]).to eq []
403
+ end
403
404
  end
404
405
 
405
- it "does not replace mention with link" do
406
- should include(<<-EOS.strip_heredoc.rstrip)
407
- <blockquote>
408
- <p>@alice</p>
409
- </blockquote>
410
- EOS
411
- end
412
- end
406
+ context "with group mention without group_memberion_url_generator" do
407
+ let(:markdown) do
408
+ "@alice/bob"
409
+ end
413
410
 
414
- context "with mention to user whose name starts and ends with underscore" do
415
- let(:markdown) do
416
- "@_alice_"
411
+ it "does not replace it" do
412
+ is_expected.to eq <<-EOS.strip_heredoc
413
+ <p>@alice/bob</p>
414
+ EOS
415
+ end
417
416
  end
418
417
 
419
- it "does not emphasize the name" do
420
- should include(<<-EOS.strip_heredoc.rstrip)
421
- <a href="/_alice_" class="user-mention js-hovercard" title="_alice_" data-hovercard-target-type="user" data-hovercard-target-name="_alice_">@_alice_</a>
422
- EOS
423
- end
424
- end
418
+ context "with group mention" do
419
+ let(:context) do
420
+ super().merge(group_mention_url_generator: lambda do |group|
421
+ "https://#{group[:team_url_name]}.example.com/groups/#{group[:group_url_name]}"
422
+ end)
423
+ end
425
424
 
426
- context "with allowed_usernames context" do
427
- before do
428
- context[:allowed_usernames] = ["alice"]
429
- end
425
+ let(:markdown) do
426
+ "@alice/bob"
427
+ end
430
428
 
431
- let(:markdown) do
432
- <<-EOS.strip_heredoc
433
- @alice
434
- @bob
435
- EOS
429
+ it "replaces it with preferred link and updates :mentioned_groups" do
430
+ is_expected.to eq <<-EOS.strip_heredoc
431
+ <p><a href="https://alice.example.com/groups/bob" rel="nofollow noopener" target="_blank">@alice/bob</a></p>
432
+ EOS
433
+ expect(result[:mentioned_groups]).to eq [{
434
+ group_url_name: "bob",
435
+ team_url_name: "alice",
436
+ }]
437
+ end
436
438
  end
437
439
 
438
- it "limits mentions to allowed usernames" do
439
- expect(result[:mentioned_usernames]).to eq ["alice"]
440
- end
441
- end
440
+ context "with group mention following another text" do
441
+ let(:context) do
442
+ super().merge(group_mention_url_generator: lambda do |group|
443
+ "https://#{group[:team_url_name]}.example.com/groups/#{group[:group_url_name]}"
444
+ end)
445
+ end
442
446
 
443
- context "with @all and allowed_usernames context" do
444
- before do
445
- context[:allowed_usernames] = ["alice", "bob"]
446
- end
447
+ let(:markdown) do
448
+ "FYI @alice/bob"
449
+ end
447
450
 
448
- let(:markdown) do
449
- "@all"
451
+ it "preserves space after preceding text" do
452
+ is_expected.to start_with("<p>FYI <a ")
453
+ end
450
454
  end
451
455
 
452
- it "links it and reports all allowed users as mentioned user names" do
453
- should include(<<-EOS.strip_heredoc.rstrip)
454
- <a href="/" class="user-mention" title="all">@all</a>
455
- EOS
456
- expect(result[:mentioned_usernames]).to eq context[:allowed_usernames]
457
- end
458
- end
456
+ context "with normal link" do
457
+ let(:markdown) do
458
+ "[](/example)"
459
+ end
459
460
 
460
- context "with @all and @alice" do
461
- before do
462
- context[:allowed_usernames] = ["alice", "bob"]
461
+ it "creates link for that" do
462
+ should eq <<-EOS.strip_heredoc
463
+ <p><a href="/example"></a></p>
464
+ EOS
465
+ end
463
466
  end
464
467
 
465
- let(:markdown) do
466
- "@all @alice"
467
- end
468
+ context "with anchor link" do
469
+ let(:markdown) do
470
+ "[](#example)"
471
+ end
468
472
 
469
- it "does not duplicate mentioned user names" do
470
- expect(result[:mentioned_usernames]).to eq context[:allowed_usernames]
473
+ it "creates link for that" do
474
+ should eq <<-EOS.strip_heredoc
475
+ <p><a href="#example"></a></p>
476
+ EOS
477
+ end
471
478
  end
472
- end
473
479
 
474
- context "with @all and no allowed_usernames context" do
475
- let(:markdown) do
476
- "@all"
477
- end
480
+ context "with link with title" do
481
+ let(:markdown) do
482
+ '[](/example "Title")'
483
+ end
478
484
 
479
- it "does not repond to @all" do
480
- should eq "<p>@all</p>\n"
481
- expect(result[:mentioned_usernames]).to eq []
485
+ it "creates link for that with the title" do
486
+ should eq <<-EOS.strip_heredoc
487
+ <p><a href="/example" title="Title"></a></p>
488
+ EOS
489
+ end
482
490
  end
483
- end
484
491
 
485
- context "with group mention without group_memberion_url_generator" do
486
- let(:markdown) do
487
- "@alice/bob"
488
- end
492
+ context "with raw URL" do
493
+ let(:markdown) do
494
+ "http://example.com/search?q=日本語"
495
+ end
489
496
 
490
- it "does not replace it" do
491
- is_expected.to eq <<-EOS.strip_heredoc
492
- <p>@alice/bob</p>
493
- EOS
497
+ it "creates link for that with .autolink class" do
498
+ should eq(
499
+ '<p><a href="http://example.com/search?q=%E6%97%A5%E6%9C%AC%E8%AA%9E" class="autolink">' \
500
+ "http://example.com/search?q=日本語</a></p>\n"
501
+ )
502
+ end
494
503
  end
495
- end
496
504
 
497
- context "with group mention" do
498
- let(:context) do
499
- super().merge(group_mention_url_generator: lambda do |group|
500
- "https://#{group[:team_url_name]}.example.com/groups/#{group[:group_url_name]}"
501
- end)
502
- end
505
+ context "with javascript: link" do
506
+ let(:markdown) do
507
+ "[](javascript:alert(1))"
508
+ end
503
509
 
504
- let(:markdown) do
505
- "@alice/bob"
510
+ it "removes that link by creating empty a element" do
511
+ should eq <<-EOS.strip_heredoc
512
+ <p><a></a></p>
513
+ EOS
514
+ end
506
515
  end
507
516
 
508
- it "replaces it with preferred link and updates :mentioned_groups" do
509
- is_expected.to eq <<-EOS.strip_heredoc
510
- <p><a href="https://alice.example.com/groups/bob" rel="nofollow noopener" target="_blank">@alice/bob</a></p>
511
- EOS
512
- expect(result[:mentioned_groups]).to eq [{
513
- group_url_name: "bob",
514
- team_url_name: "alice",
515
- }]
516
- end
517
- end
517
+ context "with emoji" do
518
+ let(:markdown) do
519
+ ":+1:"
520
+ end
518
521
 
519
- context "with group mention following another text" do
520
- let(:context) do
521
- super().merge(group_mention_url_generator: lambda do |group|
522
- "https://#{group[:team_url_name]}.example.com/groups/#{group[:group_url_name]}"
523
- end)
522
+ it "replaces it with img element" do
523
+ should include("img")
524
+ end
524
525
  end
525
526
 
526
- let(:markdown) do
527
- "FYI @alice/bob"
528
- end
527
+ context "with emoji in pre or code element" do
528
+ let(:markdown) do
529
+ <<-EOS.strip_heredoc
530
+ ```
531
+ :+1:
532
+ ```
533
+ EOS
534
+ end
529
535
 
530
- it "preserves space after preceding text" do
531
- is_expected.to start_with("<p>FYI <a ")
536
+ it "does not replace it" do
537
+ should_not include("img")
538
+ end
532
539
  end
533
- end
534
540
 
535
- context "with normal link" do
536
- let(:markdown) do
537
- "[](/example)"
538
- end
541
+ context "with image notation" do
542
+ let(:markdown) do
543
+ "![a](http://example.com/b.png)"
544
+ end
539
545
 
540
- it "creates link for that" do
541
- should eq <<-EOS.strip_heredoc
542
- <p><a href="/example"></a></p>
543
- EOS
546
+ it "wraps it in a element" do
547
+ should eq %(<p><a href="http://example.com/b.png" target="_blank">) +
548
+ %(<img src="http://example.com/b.png" alt="a"></a></p>\n)
549
+ end
544
550
  end
545
- end
546
551
 
547
- context "with anchor link" do
548
- let(:markdown) do
549
- "[](#example)"
550
- end
552
+ context "with image notation with title" do
553
+ let(:markdown) do
554
+ '![a](http://example.com/b.png "Title")'
555
+ end
551
556
 
552
- it "creates link for that" do
553
- should eq <<-EOS.strip_heredoc
554
- <p><a href="#example"></a></p>
555
- EOS
557
+ it "generates <img> tag with the title" do
558
+ should eq %(<p><a href="http://example.com/b.png" target="_blank">) +
559
+ %(<img src="http://example.com/b.png" alt="a" title="Title"></a></p>\n)
560
+ end
556
561
  end
557
- end
558
562
 
559
- context "with raw URL" do
560
- let(:markdown) do
561
- "http://example.com/search?q=日本語"
562
- end
563
+ context "with <img> tag with width and height attribute (for Retina image)" do
564
+ let(:markdown) do
565
+ '<img width="80" height="48" alt="a" src="http://example.com/b.png">'
566
+ end
563
567
 
564
- it "creates link for that with .autolink class" do
565
- should eq(
566
- '<p><a href="http://example.com/search?q=%E6%97%A5%E6%9C%AC%E8%AA%9E" class="autolink">' \
567
- "http://example.com/search?q=日本語</a></p>\n"
568
- )
568
+ it "wraps it in a element while keeping the width attribute" do
569
+ should eq %(<p><a href="http://example.com/b.png" target="_blank">) +
570
+ %(<img width="80" height="48" alt="a" src="http://example.com/b.png"></a></p>\n)
571
+ end
569
572
  end
570
- end
571
573
 
572
- context "with javascript: link" do
573
- let(:markdown) do
574
- "[](javascript:alert(1))"
575
- end
574
+ context "with colon-only label" do
575
+ let(:markdown) do
576
+ <<-EOS.strip_heredoc
577
+ ```:
578
+ 1
579
+ ```
580
+ EOS
581
+ end
576
582
 
577
- it "removes that link by creating empty a element" do
578
- should eq <<-EOS.strip_heredoc
579
- <p><a></a></p>
580
- EOS
583
+ it "does not replace it" do
584
+ expect(result[:codes]).to eq [
585
+ {
586
+ code: "1\n",
587
+ filename: nil,
588
+ language: nil,
589
+ },
590
+ ]
591
+ end
581
592
  end
582
- end
583
593
 
584
- context "with emoji" do
585
- let(:markdown) do
586
- ":+1:"
587
- end
594
+ context "with font element with color attribute" do
595
+ let(:markdown) do
596
+ %[<font color="red">test</font>]
597
+ end
588
598
 
589
- it "replaces it with img element" do
590
- should include("img")
599
+ it "allows font element with color attribute" do
600
+ should eq <<-EOS.strip_heredoc
601
+ <p>#{markdown}</p>
602
+ EOS
603
+ end
591
604
  end
592
- end
593
605
 
594
- context "with emoji in pre or code element" do
595
- let(:markdown) do
596
- <<-EOS.strip_heredoc
597
- ```
598
- :+1:
599
- ```
600
- EOS
601
- end
606
+ context "with task list" do
607
+ let(:markdown) do
608
+ <<-EOS.strip_heredoc
609
+ - [ ] a
610
+ - [x] b
611
+ EOS
612
+ end
602
613
 
603
- it "does not replace it" do
604
- should_not include("img")
614
+ it "inserts checkbox" do
615
+ should eq <<-EOS.strip_heredoc
616
+ <ul>
617
+ <li class="task-list-item">
618
+ <input type="checkbox" class="task-list-item-checkbox" disabled>a</li>
619
+ <li class="task-list-item">
620
+ <input type="checkbox" class="task-list-item-checkbox" checked disabled>b</li>
621
+ </ul>
622
+ EOS
623
+ end
605
624
  end
606
- end
607
625
 
608
- context "with image notation" do
609
- let(:markdown) do
610
- "![a](http://example.com/b.png)"
611
- end
626
+ context "with nested task list" do
627
+ let(:markdown) do
628
+ <<-EOS.strip_heredoc
629
+ - [ ] a
630
+ - [ ] b
631
+ EOS
632
+ end
612
633
 
613
- it "wraps it in a element" do
614
- should eq '<p><a href="http://example.com/b.png" target="_blank">' +
615
- %(<img src="http://example.com/b.png" alt="a"></a></p>\n)
634
+ it "inserts checkbox" do
635
+ should eq <<-EOS.strip_heredoc
636
+ <ul>
637
+ <li class="task-list-item">
638
+ <input type="checkbox" class="task-list-item-checkbox" disabled>a
639
+
640
+ <ul>
641
+ <li class="task-list-item">
642
+ <input type="checkbox" class="task-list-item-checkbox" disabled>b</li>
643
+ </ul>
644
+ </li>
645
+ </ul>
646
+ EOS
647
+ end
616
648
  end
617
- end
618
649
 
619
- context "with colon-only label" do
620
- let(:markdown) do
621
- <<-EOS.strip_heredoc
622
- ```:
623
- 1
624
- ```
625
- EOS
626
- end
650
+ context "with task list in code block" do
651
+ let(:markdown) do
652
+ <<-EOS.strip_heredoc
653
+ ```
654
+ - [ ] a
655
+ - [x] b
656
+ ```
657
+ EOS
658
+ end
627
659
 
628
- it "does not replace it" do
629
- expect(result[:codes]).to eq [
630
- {
631
- code: "1\n",
632
- filename: nil,
633
- language: nil,
634
- },
635
- ]
660
+ it "does not replace checkbox" do
661
+ should eq <<-EOS.strip_heredoc
662
+ <div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>- [ ] a
663
+ - [x] b
664
+ </pre></div></div>
665
+ EOS
666
+ end
636
667
  end
637
- end
638
668
 
639
- context "with font element with color attribute" do
640
- let(:markdown) do
641
- %[<font color="red">test</font>]
642
- end
669
+ context "with empty line between task list" do
670
+ let(:markdown) do
671
+ <<-EOS.strip_heredoc
672
+ - [ ] a
643
673
 
644
- it "allows font element with color attribute" do
645
- should eq <<-EOS.strip_heredoc
646
- <p>#{markdown}</p>
647
- EOS
648
- end
649
- end
674
+ - [x] b
675
+ EOS
676
+ end
650
677
 
651
- context "with task list" do
652
- let(:markdown) do
653
- <<-EOS.strip_heredoc
654
- - [ ] a
655
- - [x] b
656
- EOS
678
+ it "inserts checkbox" do
679
+ should eq <<-EOS.strip_heredoc
680
+ <ul>
681
+ <li class="task-list-item"><p><input type="checkbox" class="task-list-item-checkbox" disabled>a</p></li>
682
+ <li class="task-list-item"><p><input type="checkbox" class="task-list-item-checkbox" checked disabled>b</p></li>
683
+ </ul>
684
+ EOS
685
+ end
657
686
  end
658
687
 
659
- it "inserts checkbox" do
660
- should eq <<-EOS.strip_heredoc
661
- <ul>
662
- <li class="task-list-item">
663
- <input type="checkbox" class="task-list-item-checkbox" disabled>a</li>
664
- <li class="task-list-item">
665
- <input type="checkbox" class="task-list-item-checkbox" checked disabled>b</li>
666
- </ul>
667
- EOS
668
- end
669
- end
688
+ context "with empty list" do
689
+ let(:markdown) do
690
+ "- \n"
691
+ end
670
692
 
671
- context "with nested task list" do
672
- let(:markdown) do
673
- <<-EOS.strip_heredoc
674
- - [ ] a
675
- - [ ] b
676
- EOS
693
+ it "inserts checkbox" do
694
+ should eq <<-EOS.strip_heredoc
695
+ <ul>
696
+ <li>
697
+ </ul>
698
+ EOS
699
+ end
677
700
  end
678
701
 
679
- it "inserts checkbox" do
680
- should eq <<-EOS.strip_heredoc
681
- <ul>
682
- <li class="task-list-item">
683
- <input type="checkbox" class="task-list-item-checkbox" disabled>a
702
+ context "with text-aligned table" do
703
+ let(:markdown) do
704
+ <<-EOS.strip_heredoc
705
+ | a | b | c |
706
+ |:---|---:|:---:|
707
+ | a | b | c |
708
+ EOS
709
+ end
684
710
 
685
- <ul>
686
- <li class="task-list-item">
687
- <input type="checkbox" class="task-list-item-checkbox" disabled>b</li>
688
- </ul>
689
- </li>
690
- </ul>
691
- EOS
711
+ it "creates table element with text-align style" do
712
+ should eq <<-EOS.strip_heredoc
713
+ <table>
714
+ <thead>
715
+ <tr>
716
+ <th style="text-align: left">a</th>
717
+ <th style="text-align: right">b</th>
718
+ <th style="text-align: center">c</th>
719
+ </tr>
720
+ </thead>
721
+ <tbody>
722
+ <tr>
723
+ <td style="text-align: left">a</td>
724
+ <td style="text-align: right">b</td>
725
+ <td style="text-align: center">c</td>
726
+ </tr>
727
+ </tbody>
728
+ </table>
729
+ EOS
730
+ end
692
731
  end
693
- end
694
732
 
695
- context "with task list in code block" do
696
- let(:markdown) do
697
- <<-EOS.strip_heredoc
698
- ```
699
- - [ ] a
700
- - [x] b
701
- ```
702
- EOS
703
- end
733
+ context "with footenotes syntax" do
734
+ let(:markdown) do
735
+ <<-EOS.strip_heredoc
736
+ [^1]
737
+ [^1]: test
738
+ EOS
739
+ end
704
740
 
705
- it "does not replace checkbox" do
706
- should eq <<-EOS.strip_heredoc
707
- <div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>- [ ] a
708
- - [x] b
709
- </pre></div></div>
710
- EOS
711
- end
712
- end
741
+ it "generates footnotes elements" do
742
+ should eq <<-EOS.strip_heredoc
743
+ <p><sup id="fnref1"><a href="#fn1" rel="footnote" title="test">1</a></sup></p>
713
744
 
714
- context "with empty line between task list" do
715
- let(:markdown) do
716
- <<-EOS.strip_heredoc
717
- - [ ] a
745
+ <div class="footnotes">
746
+ <hr>
747
+ <ol>
718
748
 
719
- - [x] b
720
- EOS
721
- end
749
+ <li id="fn1">
750
+ <p>test <a href="#fnref1">↩</a></p>
751
+ </li>
722
752
 
723
- it "inserts checkbox" do
724
- should eq <<-EOS.strip_heredoc
725
- <ul>
726
- <li class="task-list-item"><p><input type="checkbox" class="task-list-item-checkbox" disabled>a</p></li>
727
- <li class="task-list-item"><p><input type="checkbox" class="task-list-item-checkbox" checked disabled>b</p></li>
728
- </ul>
729
- EOS
753
+ </ol>
754
+ </div>
755
+ EOS
756
+ end
730
757
  end
731
- end
732
758
 
733
- context "with empty list" do
734
- let(:markdown) do
735
- "- \n"
736
- end
759
+ context "with manually written link inside of <sup> tag" do
760
+ let(:markdown) do
761
+ <<-EOS.strip_heredoc
762
+ <sup>[Example](http://example.com/)</sup>
763
+ EOS
764
+ end
737
765
 
738
- it "inserts checkbox" do
739
- should eq <<-EOS.strip_heredoc
740
- <ul>
741
- <li>
742
- </ul>
743
- EOS
766
+ it "does not confuse the structure with automatically generated footnote reference" do
767
+ should eq <<-EOS.strip_heredoc
768
+ <p><sup><a href="http://example.com/">Example</a></sup></p>
769
+ EOS
770
+ end
744
771
  end
745
- end
746
772
 
747
- context "with text-aligned table" do
748
- let(:markdown) do
749
- <<-EOS.strip_heredoc
750
- | a | b | c |
751
- |:---|---:|:---:|
752
- | a | b | c |
753
- EOS
754
- end
755
-
756
- it "creates table element with text-align style" do
757
- should eq <<-EOS.strip_heredoc
758
- <table>
759
- <thead>
760
- <tr>
761
- <th style="text-align: left">a</th>
762
- <th style="text-align: right">b</th>
763
- <th style="text-align: center">c</th>
764
- </tr>
765
- </thead>
766
- <tbody>
767
- <tr>
768
- <td style="text-align: left">a</td>
769
- <td style="text-align: right">b</td>
770
- <td style="text-align: center">c</td>
771
- </tr>
772
- </tbody>
773
- </table>
774
- EOS
773
+ context "with manually written <a> tag with strange href inside of <sup> tag" do
774
+ let(:markdown) do
775
+ <<-EOS.strip_heredoc
776
+ <sup><a href="#foo.1">Link</a></sup>
777
+ EOS
778
+ end
779
+
780
+ it "does not confuse the structure with automatically generated footnote reference" do
781
+ should eq <<-EOS.strip_heredoc
782
+ <p><sup><a href="#foo.1">Link</a></sup></p>
783
+ EOS
784
+ end
775
785
  end
776
- end
777
786
 
778
- context "with footenotes syntax" do
779
- let(:markdown) do
780
- <<-EOS.strip_heredoc
781
- [^1]
782
- [^1]: test
783
- EOS
787
+ context "with markdown: { footnotes: false } context" do
788
+ before do
789
+ context[:markdown] = { footnotes: false }
790
+ end
791
+
792
+ let(:markdown) do
793
+ <<-EOS.strip_heredoc
794
+ [^1]
795
+ [^1]: test
796
+ EOS
797
+ end
798
+
799
+ it "does not generate footnote elements" do
800
+ should eq <<-EOS.strip_heredoc
801
+ <p><a href="test">^1</a></p>
802
+ EOS
803
+ end
784
804
  end
785
805
 
786
- it "generates footnotes elements" do
787
- should eq <<-EOS.strip_heredoc
788
- <p><sup id="fnref1"><a href="#fn1" rel="footnote" title="test">1</a></sup></p>
806
+ context "with emoji_names and emoji_url_generator context" do
807
+ before do
808
+ context[:emoji_names] = %w(foo o)
789
809
 
790
- <div class="footnotes">
791
- <hr>
792
- <ol>
810
+ context[:emoji_url_generator] = proc do |emoji_name|
811
+ "https://example.com/foo.png" if emoji_name == "foo"
812
+ end
813
+ end
793
814
 
794
- <li id="fn1">
795
- <p>test <a href="#fnref1">↩</a></p>
796
- </li>
815
+ let(:markdown) do
816
+ <<-EOS.strip_heredoc
817
+ :foo: :o: :x:
818
+ EOS
819
+ end
797
820
 
798
- </ol>
799
- </div>
800
- EOS
801
- end
802
- end
821
+ it "replaces only the specified emoji names with img elements with custom URL" do
822
+ should include(
823
+ '<img class="emoji" title=":foo:" alt=":foo:" src="https://example.com/foo.png"',
824
+ '<img class="emoji" title=":o:" alt=":o:" src="/images/emoji/unicode/2b55.png"'
825
+ )
803
826
 
804
- context "with manually written link inside of <sup> tag" do
805
- let(:markdown) do
806
- <<-EOS.strip_heredoc
807
- <sup>[Example](http://example.com/)</sup>
808
- EOS
827
+ should_not include(
828
+ '<img class="emoji" title=":x:" alt=":x:"'
829
+ )
830
+ end
809
831
  end
810
832
 
811
- it "does not confuse the structure with automatically generated footnote reference" do
812
- should eq <<-EOS.strip_heredoc
813
- <p><sup><a href="http://example.com/">Example</a></sup></p>
814
- EOS
815
- end
816
- end
833
+ context "with internal URL" do
834
+ let(:markdown) do
835
+ "http://qiita.com/?a=b"
836
+ end
817
837
 
818
- context "with manually written <a> tag with strange href inside of <sup> tag" do
819
- let(:markdown) do
820
- <<-EOS.strip_heredoc
821
- <sup><a href="#foo.1">Link</a></sup>
822
- EOS
823
- end
838
+ let(:context) do
839
+ { hostname: "qiita.com" }
840
+ end
824
841
 
825
- it "does not confuse the structure with automatically generated footnote reference" do
826
- should eq <<-EOS.strip_heredoc
827
- <p><sup><a href="#foo.1">Link</a></sup></p>
828
- EOS
842
+ it "creates link which does not have rel='nofollow noopener' and target='_blank'" do
843
+ should eq(
844
+ '<p><a href="http://qiita.com/?a=b" class="autolink">' \
845
+ "http://qiita.com/?a=b</a></p>\n"
846
+ )
847
+ end
829
848
  end
830
- end
831
849
 
832
- context "with markdown: { footnotes: false } context" do
833
- before do
834
- context[:markdown] = { footnotes: false }
835
- end
850
+ context "with external URL" do
851
+ let(:markdown) do
852
+ "http://external.com/?a=b"
853
+ end
836
854
 
837
- let(:markdown) do
838
- <<-EOS.strip_heredoc
839
- [^1]
840
- [^1]: test
841
- EOS
842
- end
855
+ let(:context) do
856
+ { hostname: "qiita.com" }
857
+ end
843
858
 
844
- it "does not generate footnote elements" do
845
- should eq <<-EOS.strip_heredoc
846
- <p><a href="test">^1</a></p>
847
- EOS
859
+ it "creates link which has rel='nofollow noopener' and target='_blank'" do
860
+ should eq(
861
+ '<p><a href="http://external.com/?a=b" class="autolink" rel="nofollow noopener" target="_blank">' \
862
+ "http://external.com/?a=b</a></p>\n"
863
+ )
864
+ end
848
865
  end
849
- end
850
866
 
851
- context "with data-attributes" do
852
- let(:markdown) do
853
- <<-EOS.strip_heredoc
854
- <div data-a="b"></div>
855
- EOS
856
- end
867
+ context "with internal anchor tag" do
868
+ let(:markdown) do
869
+ '<a href="http://qiita.com/?a=b">foobar</a>'
870
+ end
857
871
 
858
- it "sanitizes data-attributes" do
859
- should eq <<-EOS.strip_heredoc
860
- <div></div>
861
- EOS
862
- end
863
- end
872
+ let(:context) do
873
+ { hostname: "qiita.com" }
874
+ end
864
875
 
865
- context "with data-attributes and :script option" do
866
- before do
867
- context[:script] = true
876
+ it "creates link which does not have rel='nofollow noopener' and target='_blank'" do
877
+ should eq(
878
+ "<p><a href=\"http://qiita.com/?a=b\">foobar</a></p>\n"
879
+ )
880
+ end
868
881
  end
869
882
 
870
- let(:markdown) do
871
- <<-EOS.strip_heredoc
872
- <div data-a="b"></div>
873
- EOS
874
- end
883
+ context "with external anchor tag" do
884
+ let(:markdown) do
885
+ '<a href="http://external.com/?a=b">foobar</a>'
886
+ end
887
+
888
+ let(:context) do
889
+ { hostname: "qiita.com" }
890
+ end
875
891
 
876
- it "does not sanitize data-attributes" do
877
- should eq <<-EOS.strip_heredoc
878
- <div data-a="b"></div>
879
- EOS
892
+ it "creates link which has rel='nofollow noopener' and target='_blank'" do
893
+ should eq(
894
+ "<p><a href=\"http://external.com/?a=b\" rel=\"nofollow noopener\" target=\"_blank\">foobar</a></p>\n"
895
+ )
896
+ end
880
897
  end
881
- end
882
898
 
883
- context "with emoji_names and emoji_url_generator context" do
884
- before do
885
- context[:emoji_names] = %w(foo o)
899
+ context "with external URL which ends with the hostname parameter" do
900
+ let(:markdown) do
901
+ "http://qqqqqqiita.com/?a=b"
902
+ end
886
903
 
887
- context[:emoji_url_generator] = proc do |emoji_name|
888
- "https://example.com/foo.png" if emoji_name == "foo"
904
+ let(:context) do
905
+ { hostname: "qiita.com" }
889
906
  end
890
- end
891
907
 
892
- let(:markdown) do
893
- <<-EOS.strip_heredoc
894
- :foo: :o: :x:
895
- EOS
908
+ it "creates link which has rel='nofollow noopener' and target='_blank'" do
909
+ should eq(
910
+ '<p><a href="http://qqqqqqiita.com/?a=b" class="autolink" rel="nofollow noopener" target="_blank">' \
911
+ "http://qqqqqqiita.com/?a=b</a></p>\n"
912
+ )
913
+ end
896
914
  end
897
915
 
898
- it "replaces only the specified emoji names with img elements with custom URL" do
899
- should include(
900
- '<img class="emoji" title=":foo:" alt=":foo:" src="https://example.com/foo.png"',
901
- '<img class="emoji" title=":o:" alt=":o:" src="/images/emoji/unicode/2b55.png"'
902
- )
916
+ context "with external anchor tag which ends with the hostname parameter" do
917
+ let(:markdown) do
918
+ '<a href="http://qqqqqqiita.com/?a=b">foobar</a>'
919
+ end
903
920
 
904
- should_not include(
905
- '<img class="emoji" title=":x:" alt=":x:"'
906
- )
907
- end
908
- end
921
+ let(:context) do
922
+ { hostname: "qiita.com" }
923
+ end
909
924
 
910
- context "with internal URL" do
911
- let(:markdown) do
912
- "http://qiita.com/?a=b"
925
+ it "creates link which has rel='nofollow noopener' and target='_blank'" do
926
+ should eq(
927
+ "<p><a href=\"http://qqqqqqiita.com/?a=b\" rel=\"nofollow noopener\" target=\"_blank\">foobar</a></p>\n"
928
+ )
929
+ end
913
930
  end
914
931
 
915
- let(:context) do
916
- { hostname: "qiita.com" }
917
- end
932
+ context "with sub-domain URL of hostname parameter" do
933
+ let(:markdown) do
934
+ "http://sub.qiita.com/?a=b"
935
+ end
918
936
 
919
- it "creates link which does not have rel='nofollow noopener' and target='_blank'" do
920
- should eq(
921
- '<p><a href="http://qiita.com/?a=b" class="autolink">' \
922
- "http://qiita.com/?a=b</a></p>\n"
923
- )
924
- end
925
- end
937
+ let(:context) do
938
+ { hostname: "qiita.com" }
939
+ end
926
940
 
927
- context "with external URL" do
928
- let(:markdown) do
929
- "http://external.com/?a=b"
941
+ it "creates link which has rel='nofollow noopener' and target='_blank'" do
942
+ should eq(
943
+ '<p><a href="http://sub.qiita.com/?a=b" class="autolink" rel="nofollow noopener" target="_blank">' \
944
+ "http://sub.qiita.com/?a=b</a></p>\n"
945
+ )
946
+ end
930
947
  end
931
948
 
932
- let(:context) do
933
- { hostname: "qiita.com" }
934
- end
949
+ context "with external anchor tag which has rel attribute" do
950
+ let(:markdown) do
951
+ '<a href="http://external.com/?a=b" rel="url">foobar</a>'
952
+ end
935
953
 
936
- it "creates link which has rel='nofollow noopener' and target='_blank'" do
937
- should eq(
938
- '<p><a href="http://external.com/?a=b" class="autolink" rel="nofollow noopener" target="_blank">' \
939
- "http://external.com/?a=b</a></p>\n"
940
- )
941
- end
942
- end
954
+ let(:context) do
955
+ { hostname: "qiita.com" }
956
+ end
943
957
 
944
- context "with internal anchor tag" do
945
- let(:markdown) do
946
- '<a href="http://qiita.com/?a=b">foobar</a>'
958
+ it "creates link which has rel='nofollow noopener' and target='_blank', and rel value is overwritten" do
959
+ should eq(
960
+ "<p><a href=\"http://external.com/?a=b\" rel=\"nofollow noopener\" target=\"_blank\">foobar</a></p>\n"
961
+ )
962
+ end
947
963
  end
948
964
 
949
- let(:context) do
950
- { hostname: "qiita.com" }
951
- end
965
+ context "with blockquote syntax" do
966
+ let(:markdown) do
967
+ "> foo"
968
+ end
952
969
 
953
- it "creates link which does not have rel='nofollow noopener' and target='_blank'" do
954
- should eq(
955
- "<p><a href=\"http://qiita.com/?a=b\">foobar</a></p>\n"
956
- )
970
+ it "does not confuse it with HTML tag angle brackets" do
971
+ should eq "<blockquote>\n<p>foo</p>\n</blockquote>\n"
972
+ end
957
973
  end
958
974
  end
959
975
 
960
- context "with external anchor tag" do
961
- let(:markdown) do
962
- '<a href="http://external.com/?a=b">foobar</a>'
963
- end
976
+ shared_examples_for "script element" do |allowed:|
977
+ context "with script element" do
978
+ let(:markdown) do
979
+ <<-EOS.strip_heredoc
980
+ <script>alert(1)</script>
981
+ EOS
982
+ end
964
983
 
965
- let(:context) do
966
- { hostname: "qiita.com" }
984
+ if allowed
985
+ it "allows script element" do
986
+ should eq markdown
987
+ end
988
+
989
+ context "and allowed attributes" do
990
+ let(:markdown) do
991
+ <<-EOS.strip_heredoc
992
+ <p><script async data-a="b" type="text/javascript">alert(1)</script></p>
993
+ EOS
994
+ end
995
+
996
+ it "allows data-attributes" do
997
+ should eq markdown
998
+ end
999
+ end
1000
+ else
1001
+ it "removes script element" do
1002
+ should eq "\n"
1003
+ end
1004
+ end
967
1005
  end
1006
+ end
968
1007
 
969
- it "creates link which has rel='nofollow noopener' and target='_blank'" do
970
- should eq(
971
- "<p><a href=\"http://external.com/?a=b\" rel=\"nofollow noopener\" target=\"_blank\">foobar</a></p>\n"
972
- )
1008
+ shared_examples_for "malicious script in filename" do |allowed:|
1009
+ context "with malicious script in filename" do
1010
+ let(:markdown) do
1011
+ <<-EOS.strip_heredoc
1012
+ ```js:test<script>alert(1)</script>
1013
+ 1
1014
+ ```
1015
+ EOS
1016
+ end
1017
+
1018
+ if allowed
1019
+ it "does not sanitize script element" do
1020
+ should eq <<-EOS.strip_heredoc
1021
+ <div class="code-frame" data-lang="js">
1022
+ <div class="code-lang"><span class="bold">test<script>alert(1)</script></span></div>
1023
+ <div class="highlight"><pre><span></span><span class="mi">1</span>
1024
+ </pre></div>
1025
+ </div>
1026
+ EOS
1027
+ end
1028
+ else
1029
+ it "sanitizes script element" do
1030
+ should eq <<-EOS.strip_heredoc
1031
+ <div class="code-frame" data-lang="js">
1032
+ <div class="code-lang"><span class="bold">test</span></div>
1033
+ <div class="highlight"><pre><span></span><span class="mi">1</span>
1034
+ </pre></div>
1035
+ </div>
1036
+ EOS
1037
+ end
1038
+ end
973
1039
  end
974
1040
  end
975
1041
 
976
- context "with external URL which ends with the hostname parameter" do
977
- let(:markdown) do
978
- "http://qqqqqqiita.com/?a=b"
979
- end
1042
+ shared_examples_for "iframe element" do |allowed:|
1043
+ context "with iframe" do
1044
+ let(:markdown) do
1045
+ <<-EOS.strip_heredoc
1046
+ <iframe width="1" height="2" src="//example.com" frameborder="0" allowfullscreen></iframe>
1047
+ EOS
1048
+ end
980
1049
 
981
- let(:context) do
982
- { hostname: "qiita.com" }
1050
+ if allowed
1051
+ it "allows iframe with some attributes" do
1052
+ should eq markdown
1053
+ end
1054
+ else
1055
+ it "sanitizes iframe element" do
1056
+ should eq "\n"
1057
+ end
1058
+ end
983
1059
  end
1060
+ end
1061
+
1062
+ shared_examples_for "data-attributes" do |allowed:|
1063
+ context "with data-attributes" do
1064
+ let(:markdown) do
1065
+ <<-EOS.strip_heredoc
1066
+ <div data-a="b"></div>
1067
+ EOS
1068
+ end
984
1069
 
985
- it "creates link which has rel='nofollow noopener' and target='_blank'" do
986
- should eq(
987
- '<p><a href="http://qqqqqqiita.com/?a=b" class="autolink" rel="nofollow noopener" target="_blank">' \
988
- "http://qqqqqqiita.com/?a=b</a></p>\n"
989
- )
1070
+ if allowed
1071
+ it "does not sanitize data-attributes" do
1072
+ should eq <<-EOS.strip_heredoc
1073
+ <div data-a="b"></div>
1074
+ EOS
1075
+ end
1076
+ else
1077
+ it "sanitizes data-attributes" do
1078
+ should eq <<-EOS.strip_heredoc
1079
+ <div></div>
1080
+ EOS
1081
+ end
1082
+ end
990
1083
  end
991
1084
  end
992
1085
 
993
- context "with external anchor tag which ends with the hostname parameter" do
994
- let(:markdown) do
995
- '<a href="http://qqqqqqiita.com/?a=b">foobar</a>'
996
- end
1086
+ shared_examples_for "class attribute" do |allowed:|
1087
+ context "with class attribute for general tags" do
1088
+ let(:markdown) do
1089
+ '<i class="fa fa-user"></i>user'
1090
+ end
997
1091
 
998
- let(:context) do
999
- { hostname: "qiita.com" }
1092
+ if allowed
1093
+ it "does not sanitize the attribute" do
1094
+ should eq "<p><i class=\"fa fa-user\"></i>user</p>\n"
1095
+ end
1096
+ else
1097
+ it "sanitizes the attribute" do
1098
+ should eq "<p><i></i>user</p>\n"
1099
+ end
1100
+ end
1000
1101
  end
1001
1102
 
1002
- it "creates link which has rel='nofollow noopener' and target='_blank'" do
1003
- should eq(
1004
- "<p><a href=\"http://qqqqqqiita.com/?a=b\" rel=\"nofollow noopener\" target=\"_blank\">foobar</a></p>\n"
1005
- )
1103
+ context "with class attribute for <a> tag" do
1104
+ let(:markdown) do
1105
+ <<-EOS.strip_heredoc
1106
+ <a href="foo" class="malicious-class">foo</a>
1107
+ http://qiita.com/
1108
+ EOS
1109
+ end
1110
+
1111
+ if allowed
1112
+ it "does not sanitize the classes" do
1113
+ should eq <<-EOS.strip_heredoc
1114
+ <p><a href="foo" class="malicious-class">foo</a><br>
1115
+ <a href="http://qiita.com/" class="autolink" rel="nofollow noopener" target="_blank">http://qiita.com/</a></p>
1116
+ EOS
1117
+ end
1118
+ else
1119
+ it "sanitizes classes except `autolink`" do
1120
+ should eq <<-EOS.strip_heredoc
1121
+ <p><a href="foo" class="">foo</a><br>
1122
+ <a href="http://qiita.com/" class="autolink" rel="nofollow noopener" target="_blank">http://qiita.com/</a></p>
1123
+ EOS
1124
+ end
1125
+ end
1006
1126
  end
1007
1127
  end
1008
1128
 
1009
- context "with sub-domain URL of hostname parameter" do
1010
- let(:markdown) do
1011
- "http://sub.qiita.com/?a=b"
1129
+ context "without script and strict context" do
1130
+ let(:context) do
1131
+ super().merge(script: false, strict: false)
1012
1132
  end
1013
1133
 
1134
+ include_examples "basic markdown syntax"
1135
+ include_examples "script element", allowed: false
1136
+ include_examples "malicious script in filename", allowed: false
1137
+ include_examples "iframe element", allowed: false
1138
+ include_examples "data-attributes", allowed: false
1139
+ include_examples "class attribute", allowed: true
1140
+ end
1141
+
1142
+ context "with script context" do
1014
1143
  let(:context) do
1015
- { hostname: "qiita.com" }
1144
+ super().merge(script: true, strict: false)
1016
1145
  end
1017
1146
 
1018
- it "creates link which has rel='nofollow noopener' and target='_blank'" do
1019
- should eq(
1020
- '<p><a href="http://sub.qiita.com/?a=b" class="autolink" rel="nofollow noopener" target="_blank">' \
1021
- "http://sub.qiita.com/?a=b</a></p>\n"
1022
- )
1023
- end
1147
+ include_examples "basic markdown syntax"
1148
+ include_examples "script element", allowed: true
1149
+ include_examples "malicious script in filename", allowed: true
1150
+ include_examples "iframe element", allowed: true
1151
+ include_examples "data-attributes", allowed: true
1152
+ include_examples "class attribute", allowed: true
1024
1153
  end
1025
1154
 
1026
- context "with external anchor tag which has rel attribute" do
1027
- let(:markdown) do
1028
- '<a href="http://external.com/?a=b" rel="url">foobar</a>'
1155
+ context "with strict context" do
1156
+ let(:context) do
1157
+ super().merge(script: false, strict: true)
1029
1158
  end
1030
1159
 
1160
+ include_examples "basic markdown syntax"
1161
+ include_examples "script element", allowed: false
1162
+ include_examples "malicious script in filename", allowed: false
1163
+ include_examples "iframe element", allowed: false
1164
+ include_examples "data-attributes", allowed: false
1165
+ include_examples "class attribute", allowed: false
1166
+ end
1167
+
1168
+ context "with script and strict context" do
1031
1169
  let(:context) do
1032
- { hostname: "qiita.com" }
1170
+ super().merge(script: true, strict: true)
1033
1171
  end
1034
1172
 
1035
- it "creates link which has rel='nofollow noopener' and target='_blank', and rel value is overwritten" do
1036
- should eq(
1037
- "<p><a href=\"http://external.com/?a=b\" rel=\"nofollow noopener\" target=\"_blank\">foobar</a></p>\n"
1038
- )
1039
- end
1173
+ include_examples "basic markdown syntax"
1174
+ include_examples "script element", allowed: false
1175
+ include_examples "malicious script in filename", allowed: true
1176
+ include_examples "iframe element", allowed: false
1177
+ include_examples "data-attributes", allowed: false
1178
+ include_examples "class attribute", allowed: false
1040
1179
  end
1041
1180
  end
1042
1181
  end