New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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 oracle/truffleruby#3134 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
end require '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
end Here is the result:
Certainly, using |
|
@ioquatix I see. Thank you for your explanation! |
OK I found the bug: oracle/truffleruby#3181 define_method(:escape_html, CGI.method(:escapeHTML))
module_function :escape_html Or 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 (oracle/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 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)