Xml.tests.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // Copyright Catch2 Authors
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt or copy at
  4. // https://www.boost.org/LICENSE_1_0.txt)
  5. // SPDX-License-Identifier: BSL-1.0
  6. #include <catch2/catch_test_macros.hpp>
  7. #include <catch2/internal/catch_xmlwriter.hpp>
  8. #include <catch2/internal/catch_reusable_string_stream.hpp>
  9. #include <catch2/matchers/catch_matchers_string.hpp>
  10. #include <sstream>
  11. static std::string encode( std::string const& str, Catch::XmlEncode::ForWhat forWhat = Catch::XmlEncode::ForTextNodes ) {
  12. Catch::ReusableStringStream oss;
  13. oss << Catch::XmlEncode( str, forWhat );
  14. return oss.str();
  15. }
  16. TEST_CASE( "XmlEncode", "[XML]" ) {
  17. SECTION( "normal string" ) {
  18. REQUIRE( encode( "normal string" ) == "normal string" );
  19. }
  20. SECTION( "empty string" ) {
  21. REQUIRE( encode( "" ) == "" );
  22. }
  23. SECTION( "string with ampersand" ) {
  24. REQUIRE( encode( "smith & jones" ) == "smith &amp; jones" );
  25. }
  26. SECTION( "string with less-than" ) {
  27. REQUIRE( encode( "smith < jones" ) == "smith &lt; jones" );
  28. }
  29. SECTION( "string with greater-than" ) {
  30. REQUIRE( encode( "smith > jones" ) == "smith > jones" );
  31. REQUIRE( encode( "smith ]]> jones" ) == "smith ]]&gt; jones" );
  32. }
  33. SECTION( "string with quotes" ) {
  34. std::string stringWithQuotes = "don't \"quote\" me on that";
  35. REQUIRE( encode( stringWithQuotes ) == stringWithQuotes );
  36. REQUIRE( encode( stringWithQuotes, Catch::XmlEncode::ForAttributes ) == "don't &quot;quote&quot; me on that" );
  37. }
  38. SECTION( "string with control char (1)" ) {
  39. REQUIRE( encode( "[\x01]" ) == "[\\x01]" );
  40. }
  41. SECTION( "string with control char (x7F)" ) {
  42. REQUIRE( encode( "[\x7F]" ) == "[\\x7F]" );
  43. }
  44. }
  45. // Thanks to Peter Bindels (dascandy) for some of the tests
  46. TEST_CASE("XmlEncode: UTF-8", "[XML][UTF-8][approvals]") {
  47. SECTION("Valid utf-8 strings") {
  48. CHECK(encode("Here be 👾") == "Here be 👾");
  49. CHECK(encode("šš") == "šš");
  50. CHECK(encode("\xDF\xBF") == "\xDF\xBF"); // 0x7FF
  51. CHECK(encode("\xE0\xA0\x80") == "\xE0\xA0\x80"); // 0x800
  52. CHECK(encode("\xED\x9F\xBF") == "\xED\x9F\xBF"); // 0xD7FF
  53. CHECK(encode("\xEE\x80\x80") == "\xEE\x80\x80"); // 0xE000
  54. CHECK(encode("\xEF\xBF\xBF") == "\xEF\xBF\xBF"); // 0xFFFF
  55. CHECK(encode("\xF0\x90\x80\x80") == "\xF0\x90\x80\x80"); // 0x10000
  56. CHECK(encode("\xF4\x8F\xBF\xBF") == "\xF4\x8F\xBF\xBF"); // 0x10FFFF
  57. }
  58. SECTION("Invalid utf-8 strings") {
  59. SECTION("Various broken strings") {
  60. CHECK(encode("Here \xFF be \xF0\x9F\x91\xBE") == "Here \\xFF be 👾");
  61. CHECK(encode("\xFF") == "\\xFF");
  62. CHECK(encode("\xC5\xC5\xA0") == "\\xC5Š");
  63. CHECK(encode("\xF4\x90\x80\x80") == "\\xF4\\x90\\x80\\x80"); // 0x110000 -- out of unicode range
  64. }
  65. SECTION("Overlong encodings") {
  66. CHECK(encode("\xC0\x80") == "\\xC0\\x80"); // \0
  67. CHECK(encode("\xF0\x80\x80\x80") == "\\xF0\\x80\\x80\\x80"); // Super-over-long \0
  68. CHECK(encode("\xC1\xBF") == "\\xC1\\xBF"); // ASCII char as UTF-8 (0x7F)
  69. CHECK(encode("\xE0\x9F\xBF") == "\\xE0\\x9F\\xBF"); // 0x7FF
  70. CHECK(encode("\xF0\x8F\xBF\xBF") == "\\xF0\\x8F\\xBF\\xBF"); // 0xFFFF
  71. }
  72. // Note that we actually don't modify surrogate pairs, as we do not do strict checking
  73. SECTION("Surrogate pairs") {
  74. CHECK(encode("\xED\xA0\x80") == "\xED\xA0\x80"); // Invalid surrogate half 0xD800
  75. CHECK(encode("\xED\xAF\xBF") == "\xED\xAF\xBF"); // Invalid surrogate half 0xDBFF
  76. CHECK(encode("\xED\xB0\x80") == "\xED\xB0\x80"); // Invalid surrogate half 0xDC00
  77. CHECK(encode("\xED\xBF\xBF") == "\xED\xBF\xBF"); // Invalid surrogate half 0xDFFF
  78. }
  79. SECTION("Invalid start byte") {
  80. CHECK(encode("\x80") == "\\x80");
  81. CHECK(encode("\x81") == "\\x81");
  82. CHECK(encode("\xBC") == "\\xBC");
  83. CHECK(encode("\xBF") == "\\xBF");
  84. // Out of range
  85. CHECK(encode("\xF5\x80\x80\x80") == "\\xF5\\x80\\x80\\x80");
  86. CHECK(encode("\xF6\x80\x80\x80") == "\\xF6\\x80\\x80\\x80");
  87. CHECK(encode("\xF7\x80\x80\x80") == "\\xF7\\x80\\x80\\x80");
  88. }
  89. SECTION("Missing continuation byte(s)") {
  90. // Missing first continuation byte
  91. CHECK(encode("\xDE") == "\\xDE");
  92. CHECK(encode("\xDF") == "\\xDF");
  93. CHECK(encode("\xE0") == "\\xE0");
  94. CHECK(encode("\xEF") == "\\xEF");
  95. CHECK(encode("\xF0") == "\\xF0");
  96. CHECK(encode("\xF4") == "\\xF4");
  97. // Missing second continuation byte
  98. CHECK(encode("\xE0\x80") == "\\xE0\\x80");
  99. CHECK(encode("\xE0\xBF") == "\\xE0\\xBF");
  100. CHECK(encode("\xE1\x80") == "\\xE1\\x80");
  101. CHECK(encode("\xF0\x80") == "\\xF0\\x80");
  102. CHECK(encode("\xF4\x80") == "\\xF4\\x80");
  103. // Missing third continuation byte
  104. CHECK(encode("\xF0\x80\x80") == "\\xF0\\x80\\x80");
  105. CHECK(encode("\xF4\x80\x80") == "\\xF4\\x80\\x80");
  106. }
  107. }
  108. }
  109. TEST_CASE("XmlWriter writes boolean attributes as true/false", "[XML][XmlWriter]") {
  110. using Catch::Matchers::ContainsSubstring;
  111. std::stringstream stream;
  112. {
  113. Catch::XmlWriter xml(stream);
  114. xml.scopedElement("Element1")
  115. .writeAttribute("attr1", true)
  116. .writeAttribute("attr2", false);
  117. }
  118. REQUIRE_THAT( stream.str(),
  119. ContainsSubstring(R"(attr1="true")") &&
  120. ContainsSubstring(R"(attr2="false")") );
  121. }
  122. TEST_CASE("XmlWriter does not escape comments", "[XML][XmlWriter][approvals]") {
  123. using Catch::Matchers::ContainsSubstring;
  124. std::stringstream stream;
  125. {
  126. Catch::XmlWriter xml(stream);
  127. xml.writeComment(R"(unescaped special chars: < > ' " &)");
  128. }
  129. REQUIRE_THAT( stream.str(),
  130. ContainsSubstring(R"(<!-- unescaped special chars: < > ' " & -->)"));
  131. }
  132. TEST_CASE("XmlWriter errors out when writing text without enclosing element", "[XmlWriter][approvals]") {
  133. std::stringstream stream;
  134. Catch::XmlWriter xml(stream);
  135. REQUIRE_THROWS(xml.writeText("some text"));
  136. }
  137. TEST_CASE("XmlWriter escapes text properly", "[XML][XmlWriter][approvals]") {
  138. using Catch::Matchers::ContainsSubstring;
  139. std::stringstream stream;
  140. {
  141. Catch::XmlWriter xml(stream);
  142. xml.scopedElement("root")
  143. .writeText(R"(Special chars need escaping: < > ' " &)");
  144. }
  145. REQUIRE_THAT( stream.str(),
  146. ContainsSubstring(R"(Special chars need escaping: &lt; > ' " &amp;)"));
  147. }
  148. TEST_CASE("XmlWriter escapes attributes properly", "[XML][XmlWriter][approvals]") {
  149. using Catch::Matchers::ContainsSubstring;
  150. std::stringstream stream;
  151. {
  152. Catch::XmlWriter xml(stream);
  153. xml.scopedElement("root")
  154. .writeAttribute("some-attribute", R"(Special chars need escaping: < > ' " &)");
  155. }
  156. REQUIRE_THAT(stream.str(),
  157. ContainsSubstring(R"(some-attribute="Special chars need escaping: &lt; > ' &quot; &amp;")"));
  158. }