55
66#include < catch2/catch_test_macros.hpp>
77
8- #include < format>
8+ #include < regex>
9+ #include < string>
910
1011using namespace std ::string_view_literals;
1112using crispy::fromHexString;
@@ -32,3 +33,85 @@ TEST_CASE("Capabilities.get")
3233 auto const bce = tcap.numericCapability (" bce" );
3334 REQUIRE (bce);
3435}
36+
37+ // Issue #1861: Empty string terminfo capabilities cause input to be swallowed in programs
38+ // like less and bat. When capabilities are defined as empty strings (e.g., "ka1=,"),
39+ // buggy parsers match any input against them.
40+ TEST_CASE (" Capabilities.terminfo_no_empty_string_values" , " [issue-1861]" )
41+ {
42+ vtbackend::capabilities::StaticDatabase const tcap;
43+ auto const terminfo = tcap.terminfo ();
44+
45+ // The regex matches sequences like "\n name=,\n" where the value after '=' is empty.
46+ // In terminfo format, "name=value," defines a string capability.
47+ // "name=,\n" means the value is an empty string — this must never appear.
48+ // Note: We avoid std::regex::multiline because MSVC does not support it.
49+ auto const emptyValuePattern = std::regex (R"( \n\s+(\w+)=,\n)" );
50+
51+ auto begin = std::sregex_iterator (terminfo.begin (), terminfo.end (), emptyValuePattern);
52+ auto end = std::sregex_iterator ();
53+
54+ std::string emptyCapNames;
55+ for (auto it = begin; it != end; ++it)
56+ {
57+ if (!emptyCapNames.empty ())
58+ emptyCapNames += " , " ;
59+ emptyCapNames += (*it)[1 ].str ();
60+ }
61+
62+ INFO (" Capabilities with empty string values: " << emptyCapNames);
63+ CHECK (emptyCapNames.empty ());
64+ }
65+
66+ // Verify that previously-empty keypad capabilities (ka1, ka3, kc1, kc3) are no longer
67+ // present in the terminfo output at all — they should be omitted, not set to empty.
68+ TEST_CASE (" Capabilities.keypad_caps_not_in_terminfo" , " [issue-1861]" )
69+ {
70+ vtbackend::capabilities::StaticDatabase const tcap;
71+ auto const terminfo = tcap.terminfo ();
72+
73+ CHECK (terminfo.find (" ka1=" ) == std::string::npos);
74+ CHECK (terminfo.find (" ka3=" ) == std::string::npos);
75+ CHECK (terminfo.find (" kc1=" ) == std::string::npos);
76+ CHECK (terminfo.find (" kc3=" ) == std::string::npos);
77+ }
78+
79+ // Verify that khlp and kund (which were also empty) are omitted.
80+ TEST_CASE (" Capabilities.help_undo_caps_not_in_terminfo" , " [issue-1861]" )
81+ {
82+ vtbackend::capabilities::StaticDatabase const tcap;
83+ auto const terminfo = tcap.terminfo ();
84+
85+ CHECK (terminfo.find (" khlp=" ) == std::string::npos);
86+ CHECK (terminfo.find (" kund=" ) == std::string::npos);
87+ }
88+
89+ // Verify that non-empty string capabilities are still present in terminfo output.
90+ TEST_CASE (" Capabilities.non_empty_caps_still_present" , " [issue-1861]" )
91+ {
92+ vtbackend::capabilities::StaticDatabase const tcap;
93+ auto const terminfo = tcap.terminfo ();
94+
95+ // These are well-known capabilities that must still be present.
96+ CHECK (terminfo.find (" bold=" ) != std::string::npos);
97+ CHECK (terminfo.find (" clear=" ) != std::string::npos);
98+ CHECK (terminfo.find (" kcub1=" ) != std::string::npos);
99+ CHECK (terminfo.find (" kcud1=" ) != std::string::npos);
100+ CHECK (terminfo.find (" kf1=" ) != std::string::npos);
101+ CHECK (terminfo.find (" smkx=" ) != std::string::npos);
102+ CHECK (terminfo.find (" rmkx=" ) != std::string::npos);
103+ }
104+
105+ // Verify that the stringCapability() API returns an empty view for removed capabilities,
106+ // confirming they are no longer part of the static database.
107+ TEST_CASE (" Capabilities.removed_caps_return_empty_from_api" , " [issue-1861]" )
108+ {
109+ vtbackend::capabilities::StaticDatabase const tcap;
110+
111+ CHECK (tcap.stringCapability (" ka1" ).empty ());
112+ CHECK (tcap.stringCapability (" ka3" ).empty ());
113+ CHECK (tcap.stringCapability (" kc1" ).empty ());
114+ CHECK (tcap.stringCapability (" kc3" ).empty ());
115+ CHECK (tcap.stringCapability (" khlp" ).empty ());
116+ CHECK (tcap.stringCapability (" kund" ).empty ());
117+ }
0 commit comments