Skip to content

Commit 8231a4c

Browse files
authored
Merge pull request rails#47344 from ggmichaelgo/main
maintain html_safe? on sliced HTML safe strings
2 parents 6ab1686 + 2e52d0a commit 8231a4c

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

activesupport/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
* Maintain `html_safe?` on html_safe strings when sliced with `slice`, `slice!`, or `chr` method.
2+
3+
Previously, `html_safe?` was only maintained when the html_safe strings were sliced
4+
with `[]` method. Now, `slice`, `slice!`, and `chr` methods will maintain `html_safe?` like `[]` method.
5+
6+
```ruby
7+
string = "<div>test</div>".html_safe
8+
string.slice(0, 1).html_safe? # => true
9+
string.slice!(0, 1).html_safe? # => true
10+
# maintain html_safe? after the slice!
11+
string.html_safe? # => true
12+
string.chr # => true
13+
```
14+
15+
*Michael Go*
16+
117
* `config.i18n.raise_on_missing_translations = true` now raises on any missing translation.
218

319
Previously it would only raise when called in a view or controller. Now it will raise

activesupport/lib/active_support/core_ext/string/output_safety.rb

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module ActiveSupport # :nodoc:
1919
class SafeBuffer < String
2020
UNSAFE_STRING_METHODS = %w(
2121
capitalize chomp chop delete delete_prefix delete_suffix
22-
downcase lstrip next reverse rstrip scrub slice squeeze strip
22+
downcase lstrip next reverse rstrip scrub squeeze strip
2323
succ swapcase tr tr_s unicode_normalize upcase
2424
)
2525

@@ -41,13 +41,26 @@ def [](*args)
4141

4242
return unless new_string
4343

44-
new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
45-
new_safe_buffer.instance_variable_set :@html_safe, true
46-
new_safe_buffer
44+
string_into_safe_buffer(new_string, true)
4745
else
4846
to_str[*args]
4947
end
5048
end
49+
alias_method :slice, :[]
50+
51+
def slice!(*args)
52+
new_string = super
53+
54+
return new_string if !html_safe? || new_string.nil?
55+
56+
string_into_safe_buffer(new_string, true)
57+
end
58+
59+
def chr
60+
return super unless html_safe?
61+
62+
string_into_safe_buffer(super, true)
63+
end
5164

5265
def safe_concat(value)
5366
raise SafeConcatError unless html_safe?
@@ -209,6 +222,12 @@ def set_block_back_references(block, match_data)
209222
rescue ArgumentError
210223
# Can't create binding from C level Proc
211224
end
225+
226+
def string_into_safe_buffer(new_string, is_html_safe)
227+
new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
228+
new_safe_buffer.instance_variable_set :@html_safe, is_html_safe
229+
new_safe_buffer
230+
end
212231
end
213232
end
214233

activesupport/test/safe_buffer_test.rb

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ def test_titleize
9090
reverse: nil,
9191
rstrip: nil,
9292
scrub: nil,
93-
slice: "foo",
9493
squeeze: nil,
9594
strip: nil,
9695
sub: ["foo", "bar"],
@@ -212,28 +211,59 @@ def test_titleize
212211
end
213212

214213
test "Should continue unsafe on slice" do
215-
x = "foo".html_safe.gsub!("f", '<script>alert("lolpwnd");</script>')
214+
safe_string = "foo".html_safe.gsub!("f", '<script>alert("lolpwnd");</script>')
216215

217216
# calling gsub! makes the dirty flag true
218-
assert_not x.html_safe?, "should not be safe"
219-
220-
# getting a slice of it
221-
y = x[0..-1]
217+
assert_not safe_string.html_safe?, "should not be safe"
222218

223219
# should still be unsafe
224-
assert_not y.html_safe?, "should not be safe"
220+
assert_not safe_string[0..-1].html_safe?, "should not be safe"
221+
assert_not safe_string.slice(0..-1).html_safe?, "should not be safe"
222+
assert_not safe_string.slice!(0..-1).html_safe?, "should not be safe"
223+
# even after slice! safe_string is still unsafe
224+
assert_not safe_string.html_safe?, "should not be safe"
225225
end
226226

227227
test "Should continue safe on slice" do
228-
x = "<div>foo</div>".html_safe
229-
230-
assert_predicate x, :html_safe?
228+
safe_string = "<div>foo</div>".html_safe
231229

232-
# getting a slice of it
233-
y = x[0..-1]
230+
assert_predicate safe_string, :html_safe?
234231

235232
# should still be safe
236-
assert_predicate y, :html_safe?
233+
assert_predicate safe_string[0..-1], :html_safe?
234+
assert_predicate safe_string.slice(0..-1), :html_safe?
235+
assert_predicate safe_string.slice!(0...1), :html_safe?
236+
237+
# even after slice! safe_string is still safe
238+
assert_predicate safe_string, :html_safe?
239+
end
240+
241+
test "Should continue safe on chr" do
242+
safe_string = "<div>foo</div>".html_safe
243+
244+
assert_predicate safe_string, :html_safe?
245+
assert_predicate safe_string.chr, :html_safe?
246+
end
247+
248+
test "Should continue unsafe on chr" do
249+
safe_string = "<div>foo</div>"
250+
251+
assert_not safe_string.html_safe?, "should not be safe"
252+
assert_not safe_string.chr.html_safe?, "should not be safe"
253+
end
254+
255+
test "Should return a SafeBuffer on slice! if original value was safe" do
256+
safe_string = "<div>foo</div>".html_safe
257+
258+
assert safe_string.slice!(0...1).is_a?(ActiveSupport::SafeBuffer)
259+
end
260+
261+
test "Should return a String on slice! if original value was not safe" do
262+
unsafe_string = +'<script>alert("XSS");</script>'
263+
264+
sliced_string = unsafe_string.slice!(0...1)
265+
assert_not sliced_string.is_a?(ActiveSupport::SafeBuffer)
266+
assert sliced_string.is_a?(String)
237267
end
238268

239269
test "Should work with interpolation (array argument)" do

0 commit comments

Comments
 (0)