Fixed Xml encoding

- don't encode apostrophes
- only encode quotes in attributes
- encode control characters (as in PR #465)
This commit is contained in:
Phil Nash 2015-07-23 18:45:31 +01:00
parent 6de135c63a
commit d6e59cd56f
6 changed files with 762 additions and 453 deletions

View File

@ -14,9 +14,65 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include <iomanip>
namespace Catch { namespace Catch {
class XmlEncode {
public:
enum ForWhat { ForTextNodes, ForAttributes };
XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes )
: m_str( str ),
m_forWhat( forWhat )
{}
void encodeTo( std::ostream& os ) const {
// Apostrophe escaping not necessary if we always use " to write attributes
// (see: http://www.w3.org/TR/xml/#syntax)
for( std::size_t i = 0; i < m_str.size(); ++ i ) {
char c = m_str[i];
switch( c ) {
case '<': os << "&lt;"; break;
case '&': os << "&amp;"; break;
case '>':
// See: http://www.w3.org/TR/xml/#syntax
if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
os << "&gt;";
else
os << c;
break;
case '\"':
if( m_forWhat == ForAttributes )
os << "&quot;";
else
os << c;
break;
default:
// Escape control chars - based on contribution by @espenalb in PR #465
if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' )
os << "&#x" << std::uppercase << std::hex << static_cast<int>( c );
else
os << c;
}
}
}
friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
xmlEncode.encodeTo( os );
return os;
}
private:
std::string m_str;
ForWhat m_forWhat;
};
class XmlWriter { class XmlWriter {
public: public:
@ -99,11 +155,8 @@ namespace Catch {
} }
XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
if( !name.empty() && !attribute.empty() ) { if( !name.empty() && !attribute.empty() )
stream() << " " << name << "=\""; stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\"";
writeEncodedText( attribute );
stream() << "\"";
}
return *this; return *this;
} }
@ -114,9 +167,9 @@ namespace Catch {
template<typename T> template<typename T>
XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
if( !name.empty() ) std::ostringstream oss;
stream() << " " << name << "=\"" << attribute << "\""; oss << attribute;
return *this; return writeAttribute( name, oss.str() );
} }
XmlWriter& writeText( std::string const& text, bool indent = true ) { XmlWriter& writeText( std::string const& text, bool indent = true ) {
@ -125,7 +178,7 @@ namespace Catch {
ensureTagClosed(); ensureTagClosed();
if( tagWasOpen && indent ) if( tagWasOpen && indent )
stream() << m_indent; stream() << m_indent;
writeEncodedText( text ); stream() << XmlEncode( text );
m_needsNewline = true; m_needsNewline = true;
} }
return *this; return *this;
@ -170,30 +223,6 @@ namespace Catch {
} }
} }
void writeEncodedText( std::string const& text ) {
static const char* charsToEncode = "<&\"";
std::string mtext = text;
std::string::size_type pos = mtext.find_first_of( charsToEncode );
while( pos != std::string::npos ) {
stream() << mtext.substr( 0, pos );
switch( mtext[pos] ) {
case '<':
stream() << "&lt;";
break;
case '&':
stream() << "&amp;";
break;
case '\"':
stream() << "&quot;";
break;
}
mtext = mtext.substr( pos+1 );
pos = mtext.find_first_of( charsToEncode );
}
stream() << mtext;
}
bool m_tagIsOpen; bool m_tagIsOpen;
bool m_needsNewline; bool m_needsNewline;
std::vector<std::string> m_tags; std::vector<std::string> m_tags;

View File

@ -797,6 +797,6 @@ with expansion:
"first" == "second" "first" == "second"
=============================================================================== ===============================================================================
test cases: 157 | 117 passed | 39 failed | 1 failed as expected test cases: 158 | 118 passed | 39 failed | 1 failed as expected
assertions: 773 | 680 passed | 80 failed | 13 failed as expected assertions: 783 | 690 passed | 80 failed | 13 failed as expected

View File

@ -3762,6 +3762,128 @@ PASSED:
with expansion: with expansion:
""wide load"" == ""wide load"" ""wide load"" == ""wide load""
-------------------------------------------------------------------------------
XmlEncode
normal string
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "normal string" ) == "normal string" )
with expansion:
"normal string" == "normal string"
-------------------------------------------------------------------------------
XmlEncode
empty string
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "" ) == "" )
with expansion:
"" == ""
-------------------------------------------------------------------------------
XmlEncode
string with ampersand
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "smith & jones" ) == "smith &amp; jones" )
with expansion:
"smith &amp; jones" == "smith &amp; jones"
-------------------------------------------------------------------------------
XmlEncode
string with less-than
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "smith < jones" ) == "smith &lt; jones" )
with expansion:
"smith &lt; jones" == "smith &lt; jones"
-------------------------------------------------------------------------------
XmlEncode
string with greater-than
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "smith > jones" ) == "smith > jones" )
with expansion:
"smith > jones" == "smith > jones"
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "smith ]]> jones" ) == "smith ]]&gt; jones" )
with expansion:
"smith ]]&gt; jones"
==
"smith ]]&gt; jones"
-------------------------------------------------------------------------------
XmlEncode
string with quotes
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( stringWithQuotes ) == stringWithQuotes )
with expansion:
"don't "quote" me on that"
==
"don't "quote" me on that"
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( stringWithQuotes, Catch::XmlEncode::ForAttributes ) == "don't &quot;quote&quot; me on that" )
with expansion:
"don't &quot;quote&quot; me on that"
==
"don't &quot;quote&quot; me on that"
-------------------------------------------------------------------------------
XmlEncode
string with control char (1)
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "[\x01]" ) == "[&#x1]" )
with expansion:
"[&#x1]" == "[&#x1]"
-------------------------------------------------------------------------------
XmlEncode
string with control char (x7F)
-------------------------------------------------------------------------------
MiscTests.cpp:<line number>
...............................................................................
MiscTests.cpp:<line number>:
PASSED:
REQUIRE( encode( "[\x7F]" ) == "[&#x7F]" )
with expansion:
"[&#x7F]" == "[&#x7F]"
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Process can be configured on command line Process can be configured on command line
default - no arguments default - no arguments
@ -8004,6 +8126,6 @@ with expansion:
true true
=============================================================================== ===============================================================================
test cases: 157 | 101 passed | 55 failed | 1 failed as expected test cases: 158 | 102 passed | 55 failed | 1 failed as expected
assertions: 793 | 680 passed | 100 failed | 13 failed as expected assertions: 803 | 690 passed | 100 failed | 13 failed as expected

View File

@ -1,5 +1,5 @@
<testsuites> <testsuites>
<testsuite name="all tests" errors="12" failures="88" tests="793" hostname="tbd" time="{duration}" timestamp="tbd"> <testsuite name="all tests" errors="12" failures="88" tests="803" hostname="tbd" time="{duration}" timestamp="tbd">
<testcase classname="global" name="toString(enum)" time="{duration}"/> <testcase classname="global" name="toString(enum)" time="{duration}"/>
<testcase classname="global" name="toString(enum w/operator&lt;&lt;)" time="{duration}"/> <testcase classname="global" name="toString(enum w/operator&lt;&lt;)" time="{duration}"/>
<testcase classname="global" name="toString(enum class)" time="{duration}"/> <testcase classname="global" name="toString(enum class)" time="{duration}"/>
@ -463,6 +463,14 @@ MiscTests.cpp:<line number>
<testcase classname="global" name="toString on const wchar_t pointer returns the string contents" time="{duration}"/> <testcase classname="global" name="toString on const wchar_t pointer returns the string contents" time="{duration}"/>
<testcase classname="global" name="toString on wchar_t const pointer returns the string contents" time="{duration}"/> <testcase classname="global" name="toString on wchar_t const pointer returns the string contents" time="{duration}"/>
<testcase classname="global" name="toString on wchar_t returns the string contents" time="{duration}"/> <testcase classname="global" name="toString on wchar_t returns the string contents" time="{duration}"/>
<testcase classname="XmlEncode" name="normal string" time="{duration}"/>
<testcase classname="XmlEncode" name="empty string" time="{duration}"/>
<testcase classname="XmlEncode" name="string with ampersand" time="{duration}"/>
<testcase classname="XmlEncode" name="string with less-than" time="{duration}"/>
<testcase classname="XmlEncode" name="string with greater-than" time="{duration}"/>
<testcase classname="XmlEncode" name="string with quotes" time="{duration}"/>
<testcase classname="XmlEncode" name="string with control char (1)" time="{duration}"/>
<testcase classname="XmlEncode" name="string with control char (x7F)" time="{duration}"/>
<testcase classname="Process can be configured on command line" name="default - no arguments" time="{duration}"/> <testcase classname="Process can be configured on command line" name="default - no arguments" time="{duration}"/>
<testcase classname="Process can be configured on command line" name="test lists/1 test" time="{duration}"/> <testcase classname="Process can be configured on command line" name="test lists/1 test" time="{duration}"/>
<testcase classname="Process can be configured on command line" name="test lists/Specify one test case exclusion using exclude:" time="{duration}"/> <testcase classname="Process can be configured on command line" name="test lists/Specify one test case exclusion using exclude:" time="{duration}"/>

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
*/ */
#include "catch.hpp" #include "catch.hpp"
#include "catch_xmlwriter.hpp"
#include <iostream> #include <iostream>
@ -381,6 +382,42 @@ TEST_CASE( "toString on wchar_t returns the string contents", "[toString]" ) {
CHECK( result == "\"wide load\"" ); CHECK( result == "\"wide load\"" );
} }
inline std::string encode( std::string const& str, Catch::XmlEncode::ForWhat forWhat = Catch::XmlEncode::ForTextNodes ) {
std::ostringstream oss;
oss << Catch::XmlEncode( str, forWhat );
return oss.str();
}
TEST_CASE( "XmlEncode" ) {
SECTION( "normal string" ) {
REQUIRE( encode( "normal string" ) == "normal string" );
}
SECTION( "empty string" ) {
REQUIRE( encode( "" ) == "" );
}
SECTION( "string with ampersand" ) {
REQUIRE( encode( "smith & jones" ) == "smith &amp; jones" );
}
SECTION( "string with less-than" ) {
REQUIRE( encode( "smith < jones" ) == "smith &lt; jones" );
}
SECTION( "string with greater-than" ) {
REQUIRE( encode( "smith > jones" ) == "smith > jones" );
REQUIRE( encode( "smith ]]> jones" ) == "smith ]]&gt; jones" );
}
SECTION( "string with quotes" ) {
std::string stringWithQuotes = "don't \"quote\" me on that";
REQUIRE( encode( stringWithQuotes ) == stringWithQuotes );
REQUIRE( encode( stringWithQuotes, Catch::XmlEncode::ForAttributes ) == "don't &quot;quote&quot; me on that" );
}
SECTION( "string with control char (1)" ) {
REQUIRE( encode( "[\x01]" ) == "[&#x1]" );
}
SECTION( "string with control char (x7F)" ) {
REQUIRE( encode( "[\x7F]" ) == "[&#x7F]" );
}
}
//TEST_CASE( "Divide by Zero signal handler", "[.][sig]" ) { //TEST_CASE( "Divide by Zero signal handler", "[.][sig]" ) {
// int i = 0; // int i = 0;
// int x = 10/i; // This should cause the signal to fire // int x = 10/i; // This should cause the signal to fire