]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
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 | ||
eafe8130 TL |
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 |