Delegate Rack::Utils.escape_html to CGI.escapeHTML#2099
Conversation
e15b24c to
d93ea0a
Compare
What about this? It should avoid 1 layer of indirection per call :) |
d93ea0a to
3bc5acb
Compare
|
@ioquatix Thank you for your suggestion. I applied it. |
|
@ioquatix truffleruby test failed. Can you guess why?? |
|
cc @eregon do you have any opinion on the best way to proxy this method and why my suggestion failed? |
|
I'll take a look. |
So it shouldn't be allowed even on CRuby it seems? Ah so because it's defined on CGI::Escape then it's allowed. Probably the difference is related to truffleruby/truffleruby#3134 |
jeremyevans
left a comment
There was a problem hiding this comment.
You cannot be sure that cgi/escape exists. You need to guard the require, and if it raises LoadError, fallback to the current implementation. You should also change the current implementation so it uses the same escape for '.
3bc5acb to
520c00d
Compare
|
@eregon @jeremyevans
I think |
Apologies, cgi/escape is supported in stdlib cgi in all versions of Ruby that Rack supports (it was added in Ruby 2.3). I thought it was added more recently, but I was mistaken. So your original version was fine. I'm sorry I didn't check that before requesting the changes and caused you to do unnecessary work. |
520c00d to
8d755c9
Compare
|
@jeremyevans No problem! I reverted the code😊 |
|
FYI define_method(:escape_html, CGI.method(:escapeHTML))
def escape_html_by_def(string)
CGI.escapeHTML(string)
endrequire 'benchmark'
require 'rack/utils'
HTML_EXAMPLES = <<HTML.lines(chomp: true)
<p>It's a beautiful day.</p>
<a href="https://example.com/page?id=123&category=5">Link</a>
<button onclick="alert('Hello, world!')">Click me</button>
<input type="text" value="It's my text">
<div class='container'>Content goes here</div>
<span>5 × 10 = 50</span>
<img src='image.jpg' alt="Nature's beauty">
<p style='color: red;'>This text is red.</p>
<a href='javascript:alert("XSS attack!");'>Click here</a>
<input type='text' placeholder='Enter your name & email'>
HTML
targets = (HTML_EXAMPLES * 10).shuffle
num_iteration = 10_000
Benchmark.bm 15 do |r|
r.report "by define_method" do
num_iteration.times do
targets.each do |string|
Rack::Utils.escape_html(string)
end
end
end
r.report "by def" do
num_iteration.times do
targets.each do |string|
Rack::Utils.escape_html_by_def(string)
end
end
end
endHere is the result: Certainly, using |
|
|
|
@ioquatix I see. Thank you for your explanation! |
|
OK I found the bug: truffleruby/truffleruby#3181 define_method(:escape_html, CGI.method(:escapeHTML))
module_function :escape_htmlOr you can wait the truffleruby fix, up to you. BTW there is also a |
|
FWIW a disadvantage of i.e. that backtrace does not include anything about vs if defined in the usual way: |
|
I'm personally fine with either option. I don't think I've actually ever seen code using |
|
FWIW that bug (truffleruby/truffleruby#3181) is fixed in truffleruby-head now. I would recommend to keep it simple, i.e., no |
|
At this point, we have three implementation patterns. Which one should we choose? I like pattern 3 because it's very straightforward. Additionally, I think we should avoid unkind backtrace because it can easily confuse developers. pattern 1define_method(:escape_html, CGI.method(:escapeHTML))
pattern 2define_method(:escape_html, CGI.method(:escapeHTML))
module_function :escape_html
pattern 3def escape_html(string)
CGI.escapeHTML(string)
end
|
|
I vote for option 1 (i.e. commit as it is). |
|
@JunichiIto thanks so much for presenting all the options so comprehensively. I really appreciate your effort. I'm fine with either option 1 or 3. Since Jeremy also agrees option 1, let's merge it. If you are at RubyKaigi next year, please come and meet me so I can buy you a drink. |
|
This change breaks Sidekiq (and I imagine a lot of other code) because it makes one very large semantic change: nil is no longer a valid argument. Is this appropriate for a minor release? |
This previously worked but unintentionally changed in rack#2099 This previously called `to_s` which made this accept almost anything (but especially `nil`)
|
@mperham thanks for your report. Unfortunately there were never any tests for non- I'm okay with changing the method to accept non- |
This previously worked but unintentionally changed in rack#2099 This previously called `to_s` which made this accept almost anything (but especially `nil`)
This previously worked but unintentionally changed in rack#2099 This previously called `to_s` which made this accept almost anything (but especially `nil`)
This previously worked but unintentionally changed in rack#2099 This previously called `to_s` which made this accept almost anything (but especially `nil`)
This previously worked but unintentionally changed in #2099. This previously called `to_s` which made this accept almost anything (but especially `nil`) Co-authored-by: Jeremy Evans <code@jeremyevans.net>
This previously worked but unintentionally changed in #2099. This previously called `to_s` which made this accept almost anything (but especially `nil`) Co-authored-by: Jeremy Evans <code@jeremyevans.net>
This PR improves the performance of
Rack::Utils.escape_html.Background
#2097 (comment)
Benchmark
I defined two methods:
I wrote benchmark script:
Here is the result on my local machine:
The new version is 6.6 times faster than old one.
Requirements
Requires Ruby 2.3.0 or higher since it depends on 'cgi/escape' library.
I'm not sure if rack should support Ruby 2.2.x or older.
Backward compatibility
'is now escaped to#39;instead of#x27;(decimal vs hexadecimal)