From cd1eb08076c8b8e310d4d553d427763f2577a1b6 Mon Sep 17 00:00:00 2001
From: Hiroshi SHIBATA <hsbt@ruby-lang.org>
Date: Fri, 21 Feb 2025 15:53:31 +0900
Subject: [PATCH] Escape/unescape unclosed tags as well

Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>

Upstream-Status: Backport [https://github.com/ruby/cgi/commit/cd1eb08076c8b8e310d4d553d427763f2577a1b6]
CVE: CVE-2025-27220
Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
---
 lib/cgi/util.rb           |  4 ++--
 test/cgi/test_cgi_util.rb | 18 ++++++++++++++++++
 2 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb
index 5a5c77a..ce77a0c 100644
--- a/lib/cgi/util.rb
+++ b/lib/cgi/util.rb
@@ -178,7 +178,7 @@ module CGI::Util
   def escapeElement(string, *elements)
     elements = elements[0] if elements[0].kind_of?(Array)
     unless elements.empty?
-      string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do
+      string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do
         CGI.escapeHTML($&)
       end
     else
@@ -198,7 +198,7 @@ module CGI::Util
   def unescapeElement(string, *elements)
     elements = elements[0] if elements[0].kind_of?(Array)
     unless elements.empty?
-      string.gsub(/&lt;\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?&gt;/i) do
+      string.gsub(/&lt;\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:&gt;)?/im) do
         unescapeHTML($&)
       end
     else
diff --git a/test/cgi/test_cgi_util.rb b/test/cgi/test_cgi_util.rb
index a3be193..d058ccc 100644
--- a/test/cgi/test_cgi_util.rb
+++ b/test/cgi/test_cgi_util.rb
@@ -244,6 +244,14 @@ class CGIUtilTest < Test::Unit::TestCase
     assert_equal("<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]))
     assert_equal("<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<BR><A HREF="url"></A>', "A", "IMG"))
     assert_equal("<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<BR><A HREF="url"></A>', ["A", "IMG"]))
+
+    assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escapeElement('<A <A HREF="url"></A>', "A", "IMG"))
+    assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escapeElement('<A <A HREF="url"></A>', ["A", "IMG"]))
+    assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<A <A HREF="url"></A>', "A", "IMG"))
+    assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<A <A HREF="url"></A>', ["A", "IMG"]))
+
+    assert_equal("&lt;A &lt;A ", escapeElement('<A <A ', "A", "IMG"))
+    assert_equal("&lt;A &lt;A ", escapeElement('<A <A ', ["A", "IMG"]))
   end
 
 
@@ -252,6 +260,16 @@ class CGIUtilTest < Test::Unit::TestCase
     assert_equal('&lt;BR&gt;<A HREF="url"></A>', unescapeElement(escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]))
     assert_equal('&lt;BR&gt;<A HREF="url"></A>', unescape_element(escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG"))
     assert_equal('&lt;BR&gt;<A HREF="url"></A>', unescape_element(escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]))
+
+    assert_equal('<A <A HREF="url"></A>', unescapeElement(escapeHTML('<A <A HREF="url"></A>'), "A", "IMG"))
+    assert_equal('<A <A HREF="url"></A>', unescapeElement(escapeHTML('<A <A HREF="url"></A>'), ["A", "IMG"]))
+    assert_equal('<A <A HREF="url"></A>', unescape_element(escapeHTML('<A <A HREF="url"></A>'), "A", "IMG"))
+    assert_equal('<A <A HREF="url"></A>', unescape_element(escapeHTML('<A <A HREF="url"></A>'), ["A", "IMG"]))
+
+    assert_equal('<A <A ', unescapeElement(escapeHTML('<A <A '), "A", "IMG"))
+    assert_equal('<A <A ', unescapeElement(escapeHTML('<A <A '), ["A", "IMG"]))
+    assert_equal('<A <A ', unescape_element(escapeHTML('<A <A '), "A", "IMG"))
+    assert_equal('<A <A ', unescape_element(escapeHTML('<A <A '), ["A", "IMG"]))
   end
 end
 
-- 
2.25.1

