]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/rgw/test_rgw_xml.cc
import ceph 14.2.5
[ceph.git] / ceph / src / test / rgw / test_rgw_xml.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "rgw/rgw_xml.h"
5 #include <gtest/gtest.h>
6 #include <list>
7 #include <stdexcept>
8
9 struct NameAndStatus {
10 // these are sub-tags
11 std::string name;
12 bool status;
13
14 // intrusive XML decoding API
15 bool decode_xml(XMLObj *obj) {
16 if (!RGWXMLDecoder::decode_xml("Name", name, obj, true)) {
17 // name is mandatory
18 return false;
19 }
20 if (!RGWXMLDecoder::decode_xml("Status", status, obj, false)) {
21 // status is optional and defaults to True
22 status = true;
23 }
24 return true;
25 }
26 };
27
28 struct Item {
29 // these are sub-tags
30 NameAndStatus name_and_status;
31 int value;
32 int extra_value;
33
34 // these are attributes
35 std::string date;
36 std::string comment;
37
38 // intrusive XML decoding API
39 bool decode_xml(XMLObj *obj) {
40 if (!RGWXMLDecoder::decode_xml("NameAndStatus", name_and_status, obj, true)) {
41 // name amd status are mandatory
42 return false;
43 }
44 if (!RGWXMLDecoder::decode_xml("Value", value, obj, true)) {
45 // value is mandatory
46 return false;
47 }
48 if (!RGWXMLDecoder::decode_xml("ExtraValue", extra_value, obj, false)) {
49 // extra value is optional and defaults to zero
50 extra_value = 0;
51 }
52
53 // date attribute is optional
54 if (!obj->get_attr("Date", date)) {
55 date = "no date";
56 }
57 // comment attribute is optional
58 if (!obj->get_attr("Comment", comment)) {
59 comment = "no comment";
60 }
61
62 return true;
63 }
64 };
65
66 struct Items {
67 // these are sub-tags
68 std::list<Item> item_list;
69
70 // intrusive XML decoding API
71 bool decode_xml(XMLObj *obj) {
72 do_decode_xml_obj(item_list, "Item", obj);
73 return true;
74 }
75 };
76
77 // in case of non-intrusive decoding class
78 // hierarchy should reflect the XML hierarchy
79
80 class NameXMLObj: public XMLObj {
81 protected:
82 void xml_handle_data(const char *s, int len) override {
83 // no need to set "data", setting "name" directly
84 value.append(s, len);
85 }
86
87 public:
88 std::string value;
89 ~NameXMLObj() override = default;
90 };
91
92 class StatusXMLObj: public XMLObj {
93 protected:
94 void xml_handle_data(const char *s, int len) override {
95 std::istringstream is(std::string(s, len));
96 is >> std::boolalpha >> value;
97 }
98
99 public:
100 bool value;
101 ~StatusXMLObj() override = default;
102 };
103
104 class NameAndStatusXMLObj: public NameAndStatus, public XMLObj {
105 public:
106 ~NameAndStatusXMLObj() override = default;
107
108 bool xml_end(const char *el) override {
109 XMLObjIter iter = find("Name");
110 NameXMLObj* _name = static_cast<NameXMLObj*>(iter.get_next());
111 if (!_name) {
112 // name is mandatory
113 return false;
114 }
115 name = _name->value;
116 iter = find("Status");
117 StatusXMLObj* _status = static_cast<StatusXMLObj*>(iter.get_next());
118 if (!_status) {
119 // status is optional and defaults to True
120 status = true;
121 } else {
122 status = _status->value;
123 }
124 return true;
125 }
126 };
127
128 class ItemXMLObj: public Item, public XMLObj {
129 public:
130 ~ItemXMLObj() override = default;
131
132 bool xml_end(const char *el) override {
133 XMLObjIter iter = find("NameAndStatus");
134 NameAndStatusXMLObj* _name_and_status = static_cast<NameAndStatusXMLObj*>(iter.get_next());
135 if (!_name_and_status) {
136 // name and status are mandatory
137 return false;
138 }
139 name_and_status = *static_cast<NameAndStatus*>(_name_and_status);
140 iter = find("Value");
141 XMLObj* _value = iter.get_next();
142 if (!_value) {
143 // value is mandatory
144 return false;
145 }
146 try {
147 value = std::stoi(_value->get_data());
148 } catch (const std::exception& e) {
149 return false;
150 }
151 iter = find("ExtraValue");
152 XMLObj* _extra_value = iter.get_next();
153 if (_extra_value) {
154 // extra value is optional but cannot contain garbage
155 try {
156 extra_value = std::stoi(_extra_value->get_data());
157 } catch (const std::exception& e) {
158 return false;
159 }
160 } else {
161 // if not set, it defaults to zero
162 extra_value = 0;
163 }
164
165 // date attribute is optional
166 if (!get_attr("Date", date)) {
167 date = "no date";
168 }
169 // comment attribute is optional
170 if (!get_attr("Comment", comment)) {
171 comment = "no comment";
172 }
173
174 return true;
175 }
176 };
177
178 class ItemsXMLObj: public Items, public XMLObj {
179 public:
180 ~ItemsXMLObj() override = default;
181
182 bool xml_end(const char *el) override {
183 XMLObjIter iter = find("Item");
184 ItemXMLObj* item_ptr = static_cast<ItemXMLObj*>(iter.get_next());
185 // mandatory to have at least one item
186 bool item_found = false;
187 while (item_ptr) {
188 item_list.push_back(*static_cast<Item*>(item_ptr));
189 item_ptr = static_cast<ItemXMLObj*>(iter.get_next());
190 item_found = true;
191 }
192 return item_found;
193 }
194 };
195
196 class ItemsXMLParser: public RGWXMLParser {
197 static const int MAX_NAME_LEN = 16;
198 public:
199 XMLObj *alloc_obj(const char *el) override {
200 if (strncmp(el, "Items", MAX_NAME_LEN) == 0) {
201 items = new ItemsXMLObj;
202 return items;
203 } else if (strncmp(el, "Item", MAX_NAME_LEN) == 0) {
204 return new ItemXMLObj;
205 } else if (strncmp(el, "NameAndStatus", MAX_NAME_LEN) == 0) {
206 return new NameAndStatusXMLObj;
207 } else if (strncmp(el, "Name", MAX_NAME_LEN) == 0) {
208 return new NameXMLObj;
209 } else if (strncmp(el, "Status", MAX_NAME_LEN) == 0) {
210 return new StatusXMLObj;
211 }
212 return nullptr;
213 }
214 // this is a pointer to the parsed results
215 ItemsXMLObj* items;
216 };
217
218 static const char* good_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
219 "<Items>"
220 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
221 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
222 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
223 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
224 "</Items>";
225
226 static const char* expected_output = "((hello,1),1,0),((world,1),2,99),((foo,1),3,0),((bar,0),4,42),";
227
228 std::string to_string(const Items& items) {
229 std::stringstream ss;
230 for (const auto& item : items.item_list) {
231 ss << "((" << item.name_and_status.name << "," << item.name_and_status.status << ")," << item.value << "," << item.extra_value << ")" << ",";
232 }
233 return ss.str();
234 }
235
236 std::string to_string_with_attributes(const Items& items) {
237 std::stringstream ss;
238 for (const auto& item : items.item_list) {
239 ss << "(" << item.date << "," << item.comment << ",(" << item.name_and_status.name << "," << item.name_and_status.status << "),"
240 << item.value << "," << item.extra_value << ")" << ",";
241 }
242 return ss.str();
243 }
244
245 TEST(TestParser, BasicParsing)
246 {
247 ItemsXMLParser parser;
248 ASSERT_TRUE(parser.init());
249 ASSERT_TRUE(parser.parse(good_input, strlen(good_input), 1));
250 ASSERT_EQ(parser.items->item_list.size(), 4U);
251 ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output);
252 }
253
254 static const char* malformed_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
255 "<Items>"
256 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value><Item>"
257 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
258 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
259 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
260 "</Items>";
261
262 TEST(TestParser, MalformedInput)
263 {
264 ItemsXMLParser parser;
265 ASSERT_TRUE(parser.init());
266 ASSERT_FALSE(parser.parse(good_input, strlen(malformed_input), 1));
267 }
268
269 static const char* missing_value_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
270 "<Items>"
271 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
272 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
273 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
274 "<Item><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
275 "</Items>";
276
277 TEST(TestParser, MissingMandatoryTag)
278 {
279 ItemsXMLParser parser;
280 ASSERT_TRUE(parser.init());
281 ASSERT_FALSE(parser.parse(missing_value_input, strlen(missing_value_input), 1));
282 }
283
284 static const char* unknown_tag_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
285 "<Items>"
286 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
287 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
288 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus><Kaboom>0</Kaboom></Item>"
289 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
290 "<Kaboom>0</Kaboom>"
291 "</Items>";
292
293 TEST(TestParser, UnknownTag)
294 {
295 ItemsXMLParser parser;
296 ASSERT_TRUE(parser.init());
297 ASSERT_TRUE(parser.parse(unknown_tag_input, strlen(unknown_tag_input), 1));
298 ASSERT_EQ(parser.items->item_list.size(), 4U);
299 ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output);
300 }
301
302 static const char* invalid_value_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
303 "<Items>"
304 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
305 "<Item><ExtraValue>kaboom</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
306 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
307 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
308 "</Items>";
309
310 TEST(TestParser, InvalidValue)
311 {
312 ItemsXMLParser parser;
313 ASSERT_TRUE(parser.init());
314 ASSERT_FALSE(parser.parse(invalid_value_input, strlen(invalid_value_input), 1));
315 }
316
317 static const char* good_input1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
318 "<Items>"
319 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
320 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name>";
321
322 static const char* good_input2 = "</NameAndStatus><Value>2</Value></Item>"
323 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
324 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
325 "</Items>";
326
327 TEST(TestParser, MultipleChunks)
328 {
329 ItemsXMLParser parser;
330 ASSERT_TRUE(parser.init());
331 ASSERT_TRUE(parser.parse(good_input1, strlen(good_input1), 0));
332 ASSERT_TRUE(parser.parse(good_input2, strlen(good_input2), 1));
333 ASSERT_EQ(parser.items->item_list.size(), 4U);
334 ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output);
335 }
336
337 static const char* input_with_attributes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
338 "<Items>"
339 "<Item Date=\"Tue Dec 27 17:21:29 2011\" Kaboom=\"just ignore\">"
340 "<NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value>"
341 "</Item>"
342 "<Item Comment=\"hello world\">"
343 "<ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value>"
344 "</Item>"
345 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
346 "<Item Comment=\"goodbye\" Date=\"Thu Feb 28 10:00:18 UTC 2019 \">"
347 "<Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus>"
348 "</Item>"
349 "</Items>";
350
351 static const char* expected_output_with_attributes = "(Tue Dec 27 17:21:29 2011,no comment,(hello,1),1,0),"
352 "(no date,hello world,(world,1),2,99),"
353 "(no date,no comment,(foo,1),3,0),"
354 "(Thu Feb 28 10:00:18 UTC 2019 ,goodbye,(bar,0),4,42),";
355
356 TEST(TestParser, Attributes)
357 {
358 ItemsXMLParser parser;
359 ASSERT_TRUE(parser.init());
360 ASSERT_TRUE(parser.parse(input_with_attributes, strlen(input_with_attributes), 1));
361 ASSERT_EQ(parser.items->item_list.size(), 4U);
362 ASSERT_STREQ(to_string_with_attributes(*parser.items).c_str(),
363 expected_output_with_attributes);
364 }
365
366 TEST(TestDecoder, BasicParsing)
367 {
368 RGWXMLDecoder::XMLParser parser;
369 ASSERT_TRUE(parser.init());
370 ASSERT_TRUE(parser.parse(good_input, strlen(good_input), 1));
371 Items result;
372 ASSERT_NO_THROW({
373 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true));
374 });
375 ASSERT_EQ(result.item_list.size(), 4U);
376 ASSERT_STREQ(to_string(result).c_str(), expected_output);
377 }
378
379 TEST(TestDecoder, MalfomedInput)
380 {
381 RGWXMLDecoder::XMLParser parser;
382 ASSERT_TRUE(parser.init());
383 ASSERT_FALSE(parser.parse(good_input, strlen(malformed_input), 1));
384 }
385
386 TEST(TestDecoder, MissingMandatoryTag)
387 {
388 RGWXMLDecoder::XMLParser parser;
389 ASSERT_TRUE(parser.init());
390 ASSERT_TRUE(parser.parse(missing_value_input, strlen(missing_value_input), 1));
391 Items result;
392 ASSERT_ANY_THROW({
393 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true));
394 });
395 }
396
397 TEST(TestDecoder, InvalidValue)
398 {
399 RGWXMLDecoder::XMLParser parser;
400 ASSERT_TRUE(parser.init());
401 ASSERT_TRUE(parser.parse(invalid_value_input, strlen(invalid_value_input), 1));
402 Items result;
403 ASSERT_ANY_THROW({
404 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true));
405 });
406 }
407
408 TEST(TestDecoder, MultipleChunks)
409 {
410 RGWXMLDecoder::XMLParser parser;
411 ASSERT_TRUE(parser.init());
412 ASSERT_TRUE(parser.parse(good_input1, strlen(good_input1), 0));
413 ASSERT_TRUE(parser.parse(good_input2, strlen(good_input2), 1));
414 Items result;
415 ASSERT_NO_THROW({
416 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true));
417 });
418 ASSERT_EQ(result.item_list.size(), 4U);
419 ASSERT_STREQ(to_string(result).c_str(), expected_output);
420 }
421
422 TEST(TestDecoder, Attributes)
423 {
424 RGWXMLDecoder::XMLParser parser;
425 ASSERT_TRUE(parser.init());
426 ASSERT_TRUE(parser.parse(input_with_attributes, strlen(input_with_attributes), 1));
427 Items result;
428 ASSERT_NO_THROW({
429 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true));
430 });
431 ASSERT_EQ(result.item_list.size(), 4U);
432 ASSERT_STREQ(to_string_with_attributes(result).c_str(),
433 expected_output_with_attributes);
434 }
435
436 static const char* expected_xml_output = "<Items xmlns=\"https://www.ceph.com/doc/\">"
437 "<Item Order=\"0\"><NameAndStatus><Name>hello</Name><Status>True</Status></NameAndStatus><Value>0</Value></Item>"
438 "<Item Order=\"1\"><NameAndStatus><Name>hello</Name><Status>False</Status></NameAndStatus><Value>1</Value></Item>"
439 "<Item Order=\"2\"><NameAndStatus><Name>hello</Name><Status>True</Status></NameAndStatus><Value>2</Value></Item>"
440 "<Item Order=\"3\"><NameAndStatus><Name>hello</Name><Status>False</Status></NameAndStatus><Value>3</Value></Item>"
441 "<Item Order=\"4\"><NameAndStatus><Name>hello</Name><Status>True</Status></NameAndStatus><Value>4</Value></Item>"
442 "</Items>";
443 TEST(TestEncoder, ListWithAttrsAndNS)
444 {
445 XMLFormatter f;
446 const auto array_size = 5;
447 f.open_array_section_in_ns("Items", "https://www.ceph.com/doc/");
448 for (auto i = 0; i < array_size; ++i) {
449 FormatterAttrs item_attrs("Order", std::to_string(i).c_str(), NULL);
450 f.open_object_section_with_attrs("Item", item_attrs);
451 f.open_object_section("NameAndStatus");
452 encode_xml("Name", "hello", &f);
453 encode_xml("Status", (i%2 == 0), &f);
454 f.close_section();
455 encode_xml("Value", i, &f);
456 f.close_section();
457 }
458 f.close_section();
459 std::stringstream ss;
460 f.flush(ss);
461 ASSERT_STREQ(ss.str().c_str(), expected_xml_output);
462 }
463