CGI.escape and "Undefined method 'bytesize' for nil:NilClass"

May 01, 2013

Recently I’ve begun to encounter the above error more often. The issue is that Rails uses SafeBuffer instead of String in lots of places which then occasionally get passed to CGI.escape which internally calls #gsub and mucks with the magic variables $1, etc.

Annoyingly, CGI.escape only does this depending on the input.

SafeBuffer does not like this at all. Behold…

1.9.3p327 > s = ActiveSupport::SafeBuffer.new("foo")
 => "foo"
1.9.3p327 > CGI.escape(s)
 => "foo"
1.9.3p327 > s = ActiveSupport::SafeBuffer.new("foo>bar")
 => "foo>bar"
1.9.3p327 > CGI.escape(s)
NoMethodError: undefined method `bytesize' for nil:NilClass
  from .../lib/ruby/1.9.1/cgi/util.rb:8:in `block in escape'
  from .../active_support/core_ext/string/output_safety.rb:169:in `gsub'
  from .../active_support/core_ext/string/output_safety.rb:169:in `gsub'
  from .../lib/ruby/1.9.1/cgi/util.rb:7:in `escape'
  ...
1.9.3p327 > CGI.escape(s.to_s)
NoMethodError: undefined method `bytesize' for nil:NilClass
  from .../lib/ruby/1.9.1/cgi/util.rb:8:in `block in escape'
  from .../active_support/core_ext/string/output_safety.rb:169:in `gsub'
  from .../active_support/core_ext/string/output_safety.rb:169:in `gsub'
  from .../lib/ruby/1.9.1/cgi/util.rb:7:in `escape'
  ...
1.9.3p327 > CGI.escape(s.to_str)
 => "foo%3Ebar"

Rails by itself is pretty decent at dealing with this, but I’ve noticed that several of the gems I use call CGI.escape without checking their arguments. One could argue of course that CGI.escape should do it’s best to convert the arguments it gets into a pure string, but I won’t get into that.

The solution is to call #to_str on the arguments passed to CGI.escape, but if that call is in a gem you don’t control that’s problematic.

My solution was to create config/initializers/cgi_escape_fix.rb with the following contents. Now every call to CGI.escape will be getting a pure string.

require 'cgi'
class << CGI
  alias_method :orig_escape, :escape
  def escape(str)
    orig_escape(str.to_str)
  end
end

More information and technical details can be found here and here.