/* * Copyright (c) 2010 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include #include #include #include #include #include #ifdef HAVE_EXPAT #include #endif #ifdef HAVE_LIBXML #include #endif using namespace Swift; template class XMLParserTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(XMLParserTest); CPPUNIT_TEST(testParse_NestedElements); CPPUNIT_TEST(testParse_ElementInNamespacedElement); CPPUNIT_TEST(testParse_CharacterData); CPPUNIT_TEST(testParse_XMLEntity); CPPUNIT_TEST(testParse_NamespacePrefix); CPPUNIT_TEST(testParse_UnhandledXML); CPPUNIT_TEST(testParse_InvalidXML); CPPUNIT_TEST(testParse_InErrorState); CPPUNIT_TEST(testParse_Incremental); CPPUNIT_TEST(testParse_WhitespaceInAttribute); CPPUNIT_TEST(testParse_AttributeWithoutNamespace); CPPUNIT_TEST(testParse_AttributeWithNamespace); CPPUNIT_TEST(testParse_AttributeWithNamespaceNoPrefix); CPPUNIT_TEST(testParse_BillionLaughs); CPPUNIT_TEST(testParse_InternalEntity); //CPPUNIT_TEST(testParse_UndefinedPrefix); //CPPUNIT_TEST(testParse_UndefinedAttributePrefix); CPPUNIT_TEST(testParse_AllowCommentsInXML); CPPUNIT_TEST(testParse_DisallowCommentsInXML); CPPUNIT_TEST(testParse_Doctype); CPPUNIT_TEST(testParse_ProcessingInstructions); CPPUNIT_TEST(testParse_ProcessingPrefixedElement); CPPUNIT_TEST(testParse_InvalidlyEncodedInput); CPPUNIT_TEST_SUITE_END(); public: void testParse_NestedElements() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "" "" "")); CPPUNIT_ASSERT_EQUAL(static_cast(4), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("get"), client_.events[0].attributes.getAttribute("type")); CPPUNIT_ASSERT_EQUAL(std::string(), client_.events[0].ns); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(static_cast(0), client_.events[1].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].ns); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[1].namespaces.size()); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[1].namespaces.count("")); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].namespaces[""]); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[2].data); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[2].ns); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[3].data); CPPUNIT_ASSERT_EQUAL(std::string(), client_.events[3].ns); } void testParse_ElementInNamespacedElement() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "" "Swift" "")); CPPUNIT_ASSERT_EQUAL(static_cast(5), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(static_cast(0), client_.events[0].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[0].ns); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].namespaces.size()); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[0].namespaces[""]); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("name"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].ns); CPPUNIT_ASSERT_EQUAL(static_cast(0), client_.events[1].namespaces.size()); CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string("Swift"), client_.events[2].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); CPPUNIT_ASSERT_EQUAL(std::string("name"), client_.events[3].data); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[3].ns); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type); CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[4].data); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[4].ns); } void testParse_CharacterData() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse("blabliblo")); CPPUNIT_ASSERT_EQUAL(static_cast(7), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string("i"), client_.events[2].data); CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[3].type); CPPUNIT_ASSERT_EQUAL(std::string("bli"), client_.events[3].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type); CPPUNIT_ASSERT_EQUAL(std::string("i"), client_.events[4].data); CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[5].type); CPPUNIT_ASSERT_EQUAL(std::string("blo"), client_.events[5].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[6].type); CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[6].data); } void testParse_XMLEntity() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse("<>")); CPPUNIT_ASSERT_EQUAL(static_cast(4), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("<"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string(">"), client_.events[2].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[3].data); } void testParse_NamespacePrefix() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse("")); CPPUNIT_ASSERT_EQUAL(static_cast(4), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("x"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[0].ns); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].namespaces.size()); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[0].namespaces["p"]); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("y"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].ns); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string("y"), client_.events[2].data); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[2].ns); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); CPPUNIT_ASSERT_EQUAL(std::string("x"), client_.events[3].data); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[3].ns); } void testParse_UnhandledXML() { ParserType testling(&client_, true); CPPUNIT_ASSERT(testling.parse("")); CPPUNIT_ASSERT_EQUAL(static_cast(2), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[1].data); } void testParse_InvalidXML() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("")); } void testParse_InErrorState() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("")); CPPUNIT_ASSERT(!testling.parse("")); } void testParse_Incremental() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse("")); CPPUNIT_ASSERT_EQUAL(static_cast(2), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[1].data); } void testParse_CompleteDocument() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("", true)); CPPUNIT_ASSERT(!testling.parse("foo", true)); CPPUNIT_ASSERT(testling.parse("foo", true)); } void testParse_WhitespaceInAttribute() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "")); CPPUNIT_ASSERT(testling.parse( "")); CPPUNIT_ASSERT_EQUAL(static_cast(3), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("presence"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string("presence"), client_.events[2].data); } void testParse_AttributeWithoutNamespace() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "")); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("attr"), client_.events[0].attributes.getEntries()[0].getAttribute().getName()); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].attributes.getEntries()[0].getAttribute().getNamespace()); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].attributes.getEntries()[0].getAttribute().getPrefix()); } void testParse_AttributeWithNamespace() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "")); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("attr"), client_.events[0].attributes.getEntries()[0].getAttribute().getName()); CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im/f"), client_.events[0].attributes.getEntries()[0].getAttribute().getNamespace()); CPPUNIT_ASSERT_EQUAL(std::string("f"), client_.events[0].attributes.getEntries()[0].getAttribute().getPrefix()); CPPUNIT_ASSERT_EQUAL(static_cast(2), client_.events[0].namespaces.size()); CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im"), client_.events[0].namespaces[""]); CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im/f"), client_.events[0].namespaces["f"]); } void testParse_AttributeWithNamespaceNoPrefix() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "")); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("attr"), client_.events[0].attributes.getEntries()[0].getAttribute().getName()); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].attributes.getEntries()[0].getAttribute().getNamespace()); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].attributes.getEntries()[0].getAttribute().getPrefix()); CPPUNIT_ASSERT_EQUAL(static_cast(2), client_.events[0].namespaces.size()); CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im"), client_.events[0].namespaces[""]); CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im/f"), client_.events[0].namespaces["f"]); } void testParse_BillionLaughs() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse( "" "" " " " " " " " " " " " " " " " " "]>" "&lol9;" )); } void testParse_InternalEntity() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("]>&bar;")); } void testParse_UndefinedPrefix() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse( "")); CPPUNIT_ASSERT_EQUAL(static_cast(4), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("foo:bar"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].ns); CPPUNIT_ASSERT_EQUAL(static_cast(0), client_.events[0].namespaces.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[1].ns); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[2].data); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[2].ns); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); CPPUNIT_ASSERT_EQUAL(std::string("foo:bar"), client_.events[3].data); CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[3].ns); } void testParse_UndefinedAttributePrefix() { ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse("")); CPPUNIT_ASSERT_EQUAL(static_cast(1), client_.events[0].attributes.getEntries().size()); CPPUNIT_ASSERT_EQUAL(std::string("bar:baz"), client_.events[0].attributes.getEntries()[0].getAttribute().getName()); } void testParse_AllowCommentsInXML() { ParserType testling(&client_, true); CPPUNIT_ASSERT(testling.parse("")); CPPUNIT_ASSERT_EQUAL(static_cast(2), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("message"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("message"), client_.events[1].data); } void testParse_DisallowCommentsInXML() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("")); } void testParse_Doctype() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("")); } void testParse_ProcessingInstructions() { ParserType testling(&client_); CPPUNIT_ASSERT(!testling.parse("")); } void testParse_ProcessingPrefixedElement() { client_.testingStartElementPrefix = true; ParserType testling(&client_); CPPUNIT_ASSERT(testling.parse("")); CPPUNIT_ASSERT_EQUAL(static_cast(2), client_.events.size()); CPPUNIT_ASSERT_EQUAL(Client::StartElementPrefix, client_.events[0].type); CPPUNIT_ASSERT_EQUAL(std::string("message"), client_.events[0].data); CPPUNIT_ASSERT_EQUAL(std::string("uriPrefix"), client_.events[0].ns); CPPUNIT_ASSERT_EQUAL(std::string("prefix"), client_.events[0].prefix); CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); CPPUNIT_ASSERT_EQUAL(std::string("message"), client_.events[1].data); CPPUNIT_ASSERT_EQUAL(std::string("uriPrefix"), client_.events[1].ns); } void testParse_InvalidlyEncodedInput() { ParserType testling(&client_); // The following input was generated by a fuzzer, and triggered a crash in the LibXML2 parser because // some types of error (buffer I/O errors, for instance) will not update the error in the parser context, // and the code used to rely on that error always being set if parsing failed. // This particular input will trick the parser into believing the encoding is UTF-16LE, which eventually will lead // to two invalid encodings, followed by an I/O error. The latter will end parsing without updating the // error in the parsing context, which used to trigger a crash. testling.parse(std::string("<\0?\0\x80q type='get' id='aab9a'<\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9br:i>", 271)); testling.parse("O/iq>"); testling.parse("<\xff\xff\xff\x7fype:'get' to='won\x84" "erland.lit' id='aabea'>abber.org/protocol/disco#Nnfo'/>"); } private: class Client : public XMLParserClient { public: using NamespaceMap = std::unordered_map; enum Type { StartElement, StartElementPrefix, EndElement, CharacterData, NamespaceDefined }; struct Event { Event( Type type, const std::string& data, const std::string& ns, const std::string& prefix, const AttributeMap& attributes, NamespaceMap namespaces) : type(type), data(data), ns(ns), prefix(prefix), attributes(attributes), namespaces(std::move(namespaces)) {} Event( Type type, const std::string& data, const std::string& ns, const AttributeMap& attributes, NamespaceMap namespaces = {}) : Event(type, data, ns, {}, attributes, std::move(namespaces)) {} Event(Type type, const std::string& data, const std::string& ns = std::string()) : Event(type, data, ns, "", AttributeMap(), NamespaceMap()) {} Type type; std::string data; std::string ns; std::string prefix; AttributeMap attributes; NamespaceMap namespaces; }; Client() {} void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) override { if (testingStartElementPrefix) return; events.push_back(Event(StartElement, element, ns, attributes, std::move(namespaces_))); } void handleStartElementPrefix(const std::string& prefix, const std::string& uri, const std::string& name, const AttributeMap&) override { if (!testingStartElementPrefix) return; events.push_back(Event(StartElementPrefix, name, uri, prefix, AttributeMap(), NamespaceMap())); } void handleEndElement(const std::string& element, const std::string& ns) override { events.push_back(Event(EndElement, element, ns)); } void handleCharacterData(const std::string& data) override { events.push_back(Event(CharacterData, data)); } void handleNamespaceDeclaration(const std::string& prefix, const std::string& uri) override { namespaces_[prefix] = uri; } std::vector events; bool testingStartElementPrefix = false; private: NamespaceMap namespaces_; } client_; }; #ifdef HAVE_EXPAT CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest); #endif #ifdef HAVE_LIBXML CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest); #endif