// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "rgw_xml.h" #include #include #include struct NameAndStatus { // these are sub-tags std::string name; bool status; // intrusive XML decoding API bool decode_xml(XMLObj *obj) { if (!RGWXMLDecoder::decode_xml("Name", name, obj, true)) { // name is mandatory return false; } if (!RGWXMLDecoder::decode_xml("Status", status, obj, false)) { // status is optional and defaults to True status = true; } return true; } }; struct Item { // these are sub-tags NameAndStatus name_and_status; int value; int extra_value; // these are attributes std::string date; std::string comment; // intrusive XML decoding API bool decode_xml(XMLObj *obj) { if (!RGWXMLDecoder::decode_xml("NameAndStatus", name_and_status, obj, true)) { // name amd status are mandatory return false; } if (!RGWXMLDecoder::decode_xml("Value", value, obj, true)) { // value is mandatory return false; } if (!RGWXMLDecoder::decode_xml("ExtraValue", extra_value, obj, false)) { // extra value is optional and defaults to zero extra_value = 0; } // date attribute is optional if (!obj->get_attr("Date", date)) { date = "no date"; } // comment attribute is optional if (!obj->get_attr("Comment", comment)) { comment = "no comment"; } return true; } }; struct Items { // these are sub-tags std::list item_list; // intrusive XML decoding API bool decode_xml(XMLObj *obj) { do_decode_xml_obj(item_list, "Item", obj); return true; } }; // in case of non-intrusive decoding class // hierarchy should reflect the XML hierarchy class NameXMLObj: public XMLObj { protected: void xml_handle_data(const char *s, int len) override { // no need to set "data", setting "name" directly value.append(s, len); } public: std::string value; ~NameXMLObj() override = default; }; class StatusXMLObj: public XMLObj { protected: void xml_handle_data(const char *s, int len) override { std::istringstream is(std::string(s, len)); is >> std::boolalpha >> value; } public: bool value; ~StatusXMLObj() override = default; }; class NameAndStatusXMLObj: public NameAndStatus, public XMLObj { public: ~NameAndStatusXMLObj() override = default; bool xml_end(const char *el) override { XMLObjIter iter = find("Name"); NameXMLObj* _name = static_cast(iter.get_next()); if (!_name) { // name is mandatory return false; } name = _name->value; iter = find("Status"); StatusXMLObj* _status = static_cast(iter.get_next()); if (!_status) { // status is optional and defaults to True status = true; } else { status = _status->value; } return true; } }; class ItemXMLObj: public Item, public XMLObj { public: ~ItemXMLObj() override = default; bool xml_end(const char *el) override { XMLObjIter iter = find("NameAndStatus"); NameAndStatusXMLObj* _name_and_status = static_cast(iter.get_next()); if (!_name_and_status) { // name and status are mandatory return false; } name_and_status = *static_cast(_name_and_status); iter = find("Value"); XMLObj* _value = iter.get_next(); if (!_value) { // value is mandatory return false; } try { value = std::stoi(_value->get_data()); } catch (const std::exception& e) { return false; } iter = find("ExtraValue"); XMLObj* _extra_value = iter.get_next(); if (_extra_value) { // extra value is optional but cannot contain garbage try { extra_value = std::stoi(_extra_value->get_data()); } catch (const std::exception& e) { return false; } } else { // if not set, it defaults to zero extra_value = 0; } // date attribute is optional if (!get_attr("Date", date)) { date = "no date"; } // comment attribute is optional if (!get_attr("Comment", comment)) { comment = "no comment"; } return true; } }; class ItemsXMLObj: public Items, public XMLObj { public: ~ItemsXMLObj() override = default; bool xml_end(const char *el) override { XMLObjIter iter = find("Item"); ItemXMLObj* item_ptr = static_cast(iter.get_next()); // mandatory to have at least one item bool item_found = false; while (item_ptr) { item_list.push_back(*static_cast(item_ptr)); item_ptr = static_cast(iter.get_next()); item_found = true; } return item_found; } }; class ItemsXMLParser: public RGWXMLParser { static const int MAX_NAME_LEN = 16; public: XMLObj *alloc_obj(const char *el) override { if (strncmp(el, "Items", MAX_NAME_LEN) == 0) { items = new ItemsXMLObj; return items; } else if (strncmp(el, "Item", MAX_NAME_LEN) == 0) { return new ItemXMLObj; } else if (strncmp(el, "NameAndStatus", MAX_NAME_LEN) == 0) { return new NameAndStatusXMLObj; } else if (strncmp(el, "Name", MAX_NAME_LEN) == 0) { return new NameXMLObj; } else if (strncmp(el, "Status", MAX_NAME_LEN) == 0) { return new StatusXMLObj; } return nullptr; } // this is a pointer to the parsed results ItemsXMLObj* items; }; static const char* good_input = "" "" "hello1" "99world2" "3foo" "442barFalse" ""; static const char* expected_output = "((hello,1),1,0),((world,1),2,99),((foo,1),3,0),((bar,0),4,42),"; std::string to_string(const Items& items) { std::stringstream ss; for (const auto& item : items.item_list) { ss << "((" << item.name_and_status.name << "," << item.name_and_status.status << ")," << item.value << "," << item.extra_value << ")" << ","; } return ss.str(); } std::string to_string_with_attributes(const Items& items) { std::stringstream ss; for (const auto& item : items.item_list) { ss << "(" << item.date << "," << item.comment << ",(" << item.name_and_status.name << "," << item.name_and_status.status << ")," << item.value << "," << item.extra_value << ")" << ","; } return ss.str(); } TEST(TestParser, BasicParsing) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(good_input, strlen(good_input), 1)); ASSERT_EQ(parser.items->item_list.size(), 4U); ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output); } static const char* malformed_input = "" "" "hello1" "99world2" "3foo" "442barFalse" ""; TEST(TestParser, MalformedInput) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_FALSE(parser.parse(good_input, strlen(malformed_input), 1)); } static const char* missing_value_input = "" "" "hello1" "99world2" "3foo" "42barFalse" ""; TEST(TestParser, MissingMandatoryTag) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_FALSE(parser.parse(missing_value_input, strlen(missing_value_input), 1)); } static const char* unknown_tag_input = "" "" "hello1" "99world2" "3foo0" "442barFalse" "0" ""; TEST(TestParser, UnknownTag) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(unknown_tag_input, strlen(unknown_tag_input), 1)); ASSERT_EQ(parser.items->item_list.size(), 4U); ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output); } static const char* invalid_value_input = "" "" "hello1" "kaboomworld2" "3foo" "442barFalse" ""; TEST(TestParser, InvalidValue) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_FALSE(parser.parse(invalid_value_input, strlen(invalid_value_input), 1)); } static const char* good_input1 = "" "" "hello1" "99world"; static const char* good_input2 = "2" "3foo" "442barFalse" ""; TEST(TestParser, MultipleChunks) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(good_input1, strlen(good_input1), 0)); ASSERT_TRUE(parser.parse(good_input2, strlen(good_input2), 1)); ASSERT_EQ(parser.items->item_list.size(), 4U); ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output); } static const char* input_with_attributes = "" "" "" "hello1" "" "" "99world2" "" "3foo" "" "442barFalse" "" ""; static const char* expected_output_with_attributes = "(Tue Dec 27 17:21:29 2011,no comment,(hello,1),1,0)," "(no date,hello world,(world,1),2,99)," "(no date,no comment,(foo,1),3,0)," "(Thu Feb 28 10:00:18 UTC 2019 ,goodbye,(bar,0),4,42),"; TEST(TestParser, Attributes) { ItemsXMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(input_with_attributes, strlen(input_with_attributes), 1)); ASSERT_EQ(parser.items->item_list.size(), 4U); ASSERT_STREQ(to_string_with_attributes(*parser.items).c_str(), expected_output_with_attributes); } TEST(TestDecoder, BasicParsing) { RGWXMLDecoder::XMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(good_input, strlen(good_input), 1)); Items result; ASSERT_NO_THROW({ ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); }); ASSERT_EQ(result.item_list.size(), 4U); ASSERT_STREQ(to_string(result).c_str(), expected_output); } TEST(TestDecoder, MalfomedInput) { RGWXMLDecoder::XMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_FALSE(parser.parse(good_input, strlen(malformed_input), 1)); } TEST(TestDecoder, MissingMandatoryTag) { RGWXMLDecoder::XMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(missing_value_input, strlen(missing_value_input), 1)); Items result; ASSERT_ANY_THROW({ ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); }); } TEST(TestDecoder, InvalidValue) { RGWXMLDecoder::XMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(invalid_value_input, strlen(invalid_value_input), 1)); Items result; ASSERT_ANY_THROW({ ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); }); } TEST(TestDecoder, MultipleChunks) { RGWXMLDecoder::XMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(good_input1, strlen(good_input1), 0)); ASSERT_TRUE(parser.parse(good_input2, strlen(good_input2), 1)); Items result; ASSERT_NO_THROW({ ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); }); ASSERT_EQ(result.item_list.size(), 4U); ASSERT_STREQ(to_string(result).c_str(), expected_output); } TEST(TestDecoder, Attributes) { RGWXMLDecoder::XMLParser parser; ASSERT_TRUE(parser.init()); ASSERT_TRUE(parser.parse(input_with_attributes, strlen(input_with_attributes), 1)); Items result; ASSERT_NO_THROW({ ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); }); ASSERT_EQ(result.item_list.size(), 4U); ASSERT_STREQ(to_string_with_attributes(result).c_str(), expected_output_with_attributes); } static const char* expected_xml_output = "" "helloTrue0" "helloFalse1" "helloTrue2" "helloFalse3" "helloTrue4" ""; TEST(TestEncoder, ListWithAttrsAndNS) { XMLFormatter f; const auto array_size = 5; f.open_array_section_in_ns("Items", "https://www.ceph.com/doc/"); for (auto i = 0; i < array_size; ++i) { FormatterAttrs item_attrs("Order", std::to_string(i).c_str(), NULL); f.open_object_section_with_attrs("Item", item_attrs); f.open_object_section("NameAndStatus"); encode_xml("Name", "hello", &f); encode_xml("Status", (i%2 == 0), &f); f.close_section(); encode_xml("Value", i, &f); f.close_section(); } f.close_section(); std::stringstream ss; f.flush(ss); ASSERT_STREQ(ss.str().c_str(), expected_xml_output); }