From a21b69d1861fe07b4f7c9c70c2ff1947d71a9b50 Mon Sep 17 00:00:00 2001 From: hikmetba-bit Date: Sun, 20 Sep 2026 11:34:42 +0300 Subject: [PATCH] Raise a clear TypeError from HTTPUtils.escape/unescape for nil escape, unescape, escape_form, and unescape_form all eventually call str.b, so passing nil raised a confusing, undocumented: NoMethodError: undefined method 'b' for nil:NilClass making it hard to tell the actual cause when this happens deep inside third-party code, as reported in the issue. Add a small _ensure_string helper, called from _escape/_unescape (covering escape/unescape/escape_form) and from unescape_form directly (which does its own str.gsub before delegating to _unescape), that raises a clear TypeError matching the exact wording Ruby's own implicit String conversions use elsewhere (e.g. CGI.escape(nil) raises "TypeError: no implicit conversion of nil into String") when the argument isn't a String. Fixes #96. --- lib/webrick/httputils.rb | 12 +++++++++--- test/webrick/test_httputils.rb | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb index e2c2a407..1d9324fd 100644 --- a/lib/webrick/httputils.rb +++ b/lib/webrick/httputils.rb @@ -471,14 +471,20 @@ def parse_form_data(io, boundary) def _make_regex(str) /([#{Regexp.escape(str)}])/n end def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end + def _ensure_string(str) + unless str.is_a?(String) + raise TypeError, "no implicit conversion of #{str.class} into String" + end + str + end def _escape(str, regex) - str = str.b + str = _ensure_string(str).b str.gsub!(regex) {"%%%02X" % $1.ord} # %-escaped string should contain US-ASCII only str.force_encoding(Encoding::US_ASCII) end def _unescape(str, regex) - str = str.b + str = _ensure_string(str).b str.gsub!(regex) {$1.hex.chr} # encoding of %-unescaped string is unknown str @@ -519,7 +525,7 @@ def escape_form(str) # Unescapes form reserved characters in +str+ def unescape_form(str) - _unescape(str.gsub(/\+/, " "), ESCAPED) + _unescape(_ensure_string(str).gsub(/\+/, " "), ESCAPED) end ## diff --git a/test/webrick/test_httputils.rb b/test/webrick/test_httputils.rb index 00f297bd..86d9a99c 100644 --- a/test/webrick/test_httputils.rb +++ b/test/webrick/test_httputils.rb @@ -98,4 +98,14 @@ def test_escape_path assert_equal("/foo/bar/", escape_path("/foo/bar/")) assert_equal("/%25foo/bar/", escape_path("/%foo/bar/")) end + + def test_escape_with_nil_raises_clear_type_error + # [Bug: nil raised a confusing NoMethodError ("undefined method 'b' + # for nil:NilClass") instead of a clear, documented error - see + # https://github.com/ruby/webrick/issues/96 + [:escape, :unescape, :escape_form, :unescape_form].each do |meth| + err = assert_raise(TypeError) { send(meth, nil) } + assert_equal("no implicit conversion of NilClass into String", err.message) + end + end end