1 // Copyright (C) 2010-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <cc/data.h>
10 
11 #include <cstring>
12 #include <cassert>
13 #include <climits>
14 #include <list>
15 #include <map>
16 #include <cstdio>
17 #include <iostream>
18 #include <iomanip>
19 #include <string>
20 #include <sstream>
21 #include <fstream>
22 #include <cerrno>
23 
24 #include <boost/lexical_cast.hpp>
25 
26 #include <cmath>
27 
28 using namespace std;
29 
30 namespace {
31 const char* const WHITESPACE = " \b\f\n\r\t";
32 } // end anonymous namespace
33 
34 namespace isc {
35 namespace data {
36 
37 std::string
str() const38 Element::Position::str() const {
39     std::ostringstream ss;
40     ss << file_ << ":" << line_ << ":" << pos_;
41     return (ss.str());
42 }
43 
44 std::ostream&
operator <<(std::ostream & out,const Element::Position & pos)45 operator<<(std::ostream& out, const Element::Position& pos) {
46     out << pos.str();
47     return (out);
48 }
49 
50 std::string
str() const51 Element::str() const {
52     std::stringstream ss;
53     toJSON(ss);
54     return (ss.str());
55 }
56 
57 std::string
toWire() const58 Element::toWire() const {
59     std::stringstream ss;
60     toJSON(ss);
61     return (ss.str());
62 }
63 
64 void
toWire(std::ostream & ss) const65 Element::toWire(std::ostream& ss) const {
66     toJSON(ss);
67 }
68 
69 bool
getValue(int64_t &) const70 Element::getValue(int64_t&) const {
71     return (false);
72 }
73 
74 bool
getValue(double &) const75 Element::getValue(double&) const {
76     return (false);
77 }
78 
79 bool
getValue(bool &) const80 Element::getValue(bool&) const {
81     return (false);
82 }
83 
84 bool
getValue(std::string &) const85 Element::getValue(std::string&) const {
86     return (false);
87 }
88 
89 bool
getValue(std::vector<ElementPtr> &) const90 Element::getValue(std::vector<ElementPtr>&) const {
91     return (false);
92 }
93 
94 bool
getValue(std::map<std::string,ConstElementPtr> &) const95 Element::getValue(std::map<std::string, ConstElementPtr>&) const {
96     return (false);
97 }
98 
99 bool
setValue(const long long int)100 Element::setValue(const long long int) {
101     return (false);
102 }
103 
104 bool
setValue(const double)105 Element::setValue(const double) {
106     return (false);
107 }
108 
109 bool
setValue(const bool)110 Element::setValue(const bool) {
111     return (false);
112 }
113 
114 bool
setValue(const std::string &)115 Element::setValue(const std::string&) {
116     return (false);
117 }
118 
119 bool
setValue(const std::vector<ElementPtr> &)120 Element::setValue(const std::vector<ElementPtr>&) {
121     return (false);
122 }
123 
124 bool
setValue(const std::map<std::string,ConstElementPtr> &)125 Element::setValue(const std::map<std::string, ConstElementPtr>&) {
126     return (false);
127 }
128 
129 ConstElementPtr
get(const int) const130 Element::get(const int) const {
131     throwTypeError("get(int) called on a non-container Element");
132 }
133 
134 ElementPtr
getNonConst(const int) const135 Element::getNonConst(const int) const {
136     throwTypeError("get(int) called on a non-container Element");
137 }
138 
139 void
set(const size_t,ElementPtr)140 Element::set(const size_t, ElementPtr) {
141     throwTypeError("set(int, element) called on a non-list Element");
142 }
143 
144 void
add(ElementPtr)145 Element::add(ElementPtr) {
146     throwTypeError("add() called on a non-list Element");
147 }
148 
149 void
remove(const int)150 Element::remove(const int) {
151     throwTypeError("remove(int) called on a non-container Element");
152 }
153 
154 size_t
size() const155 Element::size() const {
156     throwTypeError("size() called on a non-list Element");
157 }
158 
159 bool
empty() const160 Element::empty() const {
161     throwTypeError("empty() called on a non-container Element");
162 }
163 
164 ConstElementPtr
get(const std::string &) const165 Element::get(const std::string&) const {
166     throwTypeError("get(string) called on a non-map Element");
167 }
168 
169 void
set(const std::string &,ConstElementPtr)170 Element::set(const std::string&, ConstElementPtr) {
171     throwTypeError("set(name, element) called on a non-map Element");
172 }
173 
174 void
remove(const std::string &)175 Element::remove(const std::string&) {
176     throwTypeError("remove(string) called on a non-map Element");
177 }
178 
179 bool
contains(const std::string &) const180 Element::contains(const std::string&) const {
181     throwTypeError("contains(string) called on a non-map Element");
182 }
183 
184 ConstElementPtr
find(const std::string &) const185 Element::find(const std::string&) const {
186     throwTypeError("find(string) called on a non-map Element");
187 }
188 
189 bool
find(const std::string &,ConstElementPtr &) const190 Element::find(const std::string&, ConstElementPtr&) const {
191     return (false);
192 }
193 
194 namespace {
195 inline void
throwJSONError(const std::string & error,const std::string & file,int line,int pos)196 throwJSONError(const std::string& error, const std::string& file, int line,
197                int pos) {
198     std::stringstream ss;
199     ss << error << " in " + file + ":" << line << ":" << pos;
200     isc_throw(JSONError, ss.str());
201 }
202 } // end anonymous namespace
203 
204 std::ostream&
operator <<(std::ostream & out,const Element & e)205 operator<<(std::ostream& out, const Element& e) {
206     return (out << e.str());
207 }
208 
209 bool
operator ==(const Element & a,const Element & b)210 operator==(const Element& a, const Element& b) {
211     return (a.equals(b));
212 }
213 
operator !=(const Element & a,const Element & b)214 bool operator!=(const Element& a, const Element& b) {
215     return (!a.equals(b));
216 }
217 
218 bool
operator <(Element const & a,Element const & b)219 operator<(Element const& a, Element const& b) {
220     if (a.getType() != b.getType()) {
221         isc_throw(BadValue, "cannot compare Elements of different types");
222     }
223     switch (a.getType()) {
224     case Element::integer:
225         return a.intValue() < b.intValue();
226     case Element::real:
227         return a.doubleValue() < b.doubleValue();
228     case Element::boolean:
229         return b.boolValue() || !a.boolValue();
230     case Element::string:
231         return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0;
232     }
233     isc_throw(BadValue, "cannot compare Elements of type " <<
234                             std::to_string(a.getType()));
235 }
236 
237 //
238 // factory functions
239 //
240 ElementPtr
create(const Position & pos)241 Element::create(const Position& pos) {
242     return (ElementPtr(new NullElement(pos)));
243 }
244 
245 ElementPtr
create(const long long int i,const Position & pos)246 Element::create(const long long int i, const Position& pos) {
247     return (ElementPtr(new IntElement(static_cast<int64_t>(i), pos)));
248 }
249 
250 ElementPtr
create(const int i,const Position & pos)251 Element::create(const int i, const Position& pos) {
252     return (create(static_cast<long long int>(i), pos));
253 }
254 
255 ElementPtr
create(const long int i,const Position & pos)256 Element::create(const long int i, const Position& pos) {
257     return (create(static_cast<long long int>(i), pos));
258 }
259 
260 ElementPtr
create(const double d,const Position & pos)261 Element::create(const double d, const Position& pos) {
262     return (ElementPtr(new DoubleElement(d, pos)));
263 }
264 
265 ElementPtr
create(const bool b,const Position & pos)266 Element::create(const bool b, const Position& pos) {
267     return (ElementPtr(new BoolElement(b, pos)));
268 }
269 
270 ElementPtr
create(const std::string & s,const Position & pos)271 Element::create(const std::string& s, const Position& pos) {
272     return (ElementPtr(new StringElement(s, pos)));
273 }
274 
275 ElementPtr
create(const char * s,const Position & pos)276 Element::create(const char *s, const Position& pos) {
277     return (create(std::string(s), pos));
278 }
279 
280 ElementPtr
createList(const Position & pos)281 Element::createList(const Position& pos) {
282     return (ElementPtr(new ListElement(pos)));
283 }
284 
285 ElementPtr
createMap(const Position & pos)286 Element::createMap(const Position& pos) {
287     return (ElementPtr(new MapElement(pos)));
288 }
289 
290 
291 //
292 // helper functions for fromJSON factory
293 //
294 namespace {
295 bool
charIn(const int c,const char * chars)296 charIn(const int c, const char* chars) {
297     const size_t chars_len = std::strlen(chars);
298     for (size_t i = 0; i < chars_len; ++i) {
299         if (chars[i] == c) {
300             return (true);
301         }
302     }
303     return (false);
304 }
305 
306 void
skipChars(std::istream & in,const char * chars,int & line,int & pos)307 skipChars(std::istream& in, const char* chars, int& line, int& pos) {
308     int c = in.peek();
309     while (charIn(c, chars) && c != EOF) {
310         if (c == '\n') {
311             ++line;
312             pos = 1;
313         } else {
314             ++pos;
315         }
316         in.ignore();
317         c = in.peek();
318     }
319 }
320 
321 // skip on the input stream to one of the characters in chars
322 // if another character is found this function throws JSONError
323 // unless that character is specified in the optional may_skip
324 //
325 // It returns the found character (as an int value).
326 int
skipTo(std::istream & in,const std::string & file,int & line,int & pos,const char * chars,const char * may_skip="")327 skipTo(std::istream& in, const std::string& file, int& line, int& pos,
328        const char* chars, const char* may_skip="") {
329     int c = in.get();
330     ++pos;
331     while (c != EOF) {
332         if (c == '\n') {
333             pos = 1;
334             ++line;
335         }
336         if (charIn(c, may_skip)) {
337             c = in.get();
338             ++pos;
339         } else if (charIn(c, chars)) {
340             while (charIn(in.peek(), may_skip)) {
341                 if (in.peek() == '\n') {
342                     pos = 1;
343                     ++line;
344                 } else {
345                     ++pos;
346                 }
347                 in.ignore();
348             }
349             return (c);
350         } else {
351             throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos);
352         }
353     }
354     throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
355     return (c); // shouldn't reach here, but some compilers require it
356 }
357 
358 // TODO: Should we check for all other official escapes here (and
359 // error on the rest)?
360 std::string
strFromStringstream(std::istream & in,const std::string & file,const int line,int & pos)361 strFromStringstream(std::istream& in, const std::string& file,
362                     const int line, int& pos) {
363     std::stringstream ss;
364     int c = in.get();
365     ++pos;
366     if (c == '"') {
367         c = in.get();
368         ++pos;
369     } else {
370         throwJSONError("String expected", file, line, pos);
371     }
372 
373     while (c != EOF && c != '"') {
374         if (c == '\\') {
375             // see the spec for allowed escape characters
376             int d;
377             switch (in.peek()) {
378             case '"':
379                 c = '"';
380                 break;
381             case '/':
382                 c = '/';
383                 break;
384             case '\\':
385                 c = '\\';
386                 break;
387             case 'b':
388                 c = '\b';
389                 break;
390             case 'f':
391                 c = '\f';
392                 break;
393             case 'n':
394                 c = '\n';
395                 break;
396             case 'r':
397                 c = '\r';
398                 break;
399             case 't':
400                 c = '\t';
401                 break;
402             case 'u':
403                 // skip first 0
404                 in.ignore();
405                 ++pos;
406                 c = in.peek();
407                 if (c != '0') {
408                     throwJSONError("Unsupported unicode escape", file, line, pos);
409                 }
410                 // skip second 0
411                 in.ignore();
412                 ++pos;
413                 c = in.peek();
414                 if (c != '0') {
415                     throwJSONError("Unsupported unicode escape", file, line, pos - 2);
416                 }
417                 // get first digit
418                 in.ignore();
419                 ++pos;
420                 d = in.peek();
421                 if ((d >= '0') && (d <= '9')) {
422                     c = (d - '0') << 4;
423                 } else if ((d >= 'A') && (d <= 'F')) {
424                     c = (d - 'A' + 10) << 4;
425                 } else if ((d >= 'a') && (d <= 'f')) {
426                     c = (d - 'a' + 10) << 4;
427                 } else {
428                     throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 3);
429                 }
430                 // get second digit
431                 in.ignore();
432                 ++pos;
433                 d = in.peek();
434                 if ((d >= '0') && (d <= '9')) {
435                     c |= d - '0';
436                 } else if ((d >= 'A') && (d <= 'F')) {
437                     c |= d - 'A' + 10;
438                 } else if ((d >= 'a') && (d <= 'f')) {
439                     c |= d - 'a' + 10;
440                 } else {
441                     throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 4);
442                 }
443                 break;
444             default:
445                 throwJSONError("Bad escape", file, line, pos);
446             }
447             // drop the escaped char
448             in.ignore();
449             ++pos;
450         }
451         ss.put(c);
452         c = in.get();
453         ++pos;
454     }
455     if (c == EOF) {
456         throwJSONError("Unterminated string", file, line, pos);
457     }
458     return (ss.str());
459 }
460 
461 std::string
wordFromStringstream(std::istream & in,int & pos)462 wordFromStringstream(std::istream& in, int& pos) {
463     std::stringstream ss;
464     while (isalpha(in.peek())) {
465         ss << (char) in.get();
466     }
467     pos += ss.str().size();
468     return (ss.str());
469 }
470 
471 std::string
numberFromStringstream(std::istream & in,int & pos)472 numberFromStringstream(std::istream& in, int& pos) {
473     std::stringstream ss;
474     while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
475            in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
476         ss << (char) in.get();
477     }
478     pos += ss.str().size();
479     return (ss.str());
480 }
481 
482 // Should we change from IntElement and DoubleElement to NumberElement
483 // that can also hold an e value? (and have specific getters if the
484 // value is larger than an int can handle)
485 //
486 ElementPtr
fromStringstreamNumber(std::istream & in,const std::string & file,const int line,int & pos)487 fromStringstreamNumber(std::istream& in, const std::string& file,
488                        const int line, int& pos) {
489     // Remember position where the value starts. It will be set in the
490     // Position structure of the Element to be created.
491     const uint32_t start_pos = pos;
492     // This will move the pos to the end of the value.
493     const std::string number = numberFromStringstream(in, pos);
494 
495     if (number.find_first_of(".eE") < number.size()) {
496         try {
497             return (Element::create(boost::lexical_cast<double>(number),
498                                     Element::Position(file, line, start_pos)));
499         } catch (const boost::bad_lexical_cast&) {
500             throwJSONError(std::string("Number overflow: ") + number,
501                            file, line, start_pos);
502         }
503     } else {
504         try {
505             return (Element::create(boost::lexical_cast<int64_t>(number),
506                                     Element::Position(file, line, start_pos)));
507         } catch (const boost::bad_lexical_cast&) {
508             throwJSONError(std::string("Number overflow: ") + number, file,
509                            line, start_pos);
510         }
511     }
512     return (ElementPtr());
513 }
514 
515 ElementPtr
fromStringstreamBool(std::istream & in,const std::string & file,const int line,int & pos)516 fromStringstreamBool(std::istream& in, const std::string& file,
517                      const int line, int& pos) {
518     // Remember position where the value starts. It will be set in the
519     // Position structure of the Element to be created.
520     const uint32_t start_pos = pos;
521     // This will move the pos to the end of the value.
522     const std::string word = wordFromStringstream(in, pos);
523 
524     if (word == "true") {
525         return (Element::create(true, Element::Position(file, line,
526                                                         start_pos)));
527     } else if (word == "false") {
528         return (Element::create(false, Element::Position(file, line,
529                                                          start_pos)));
530     } else {
531         throwJSONError(std::string("Bad boolean value: ") + word, file,
532                        line, start_pos);
533     }
534     return (ElementPtr());
535 }
536 
537 ElementPtr
fromStringstreamNull(std::istream & in,const std::string & file,const int line,int & pos)538 fromStringstreamNull(std::istream& in, const std::string& file,
539                      const int line, int& pos) {
540     // Remember position where the value starts. It will be set in the
541     // Position structure of the Element to be created.
542     const uint32_t start_pos = pos;
543     // This will move the pos to the end of the value.
544     const std::string word = wordFromStringstream(in, pos);
545     if (word == "null") {
546         return (Element::create(Element::Position(file, line, start_pos)));
547     } else {
548         throwJSONError(std::string("Bad null value: ") + word, file,
549                        line, start_pos);
550         return (ElementPtr());
551     }
552 }
553 
554 ElementPtr
fromStringstreamString(std::istream & in,const std::string & file,int & line,int & pos)555 fromStringstreamString(std::istream& in, const std::string& file, int& line,
556                        int& pos) {
557     // Remember position where the value starts. It will be set in the
558     // Position structure of the Element to be created.
559     const uint32_t start_pos = pos;
560     // This will move the pos to the end of the value.
561     const std::string string_value = strFromStringstream(in, file, line, pos);
562     return (Element::create(string_value, Element::Position(file, line,
563                                                             start_pos)));
564 }
565 
566 ElementPtr
fromStringstreamList(std::istream & in,const std::string & file,int & line,int & pos)567 fromStringstreamList(std::istream& in, const std::string& file, int& line,
568                      int& pos) {
569     int c = 0;
570     ElementPtr list = Element::createList(Element::Position(file, line, pos));
571     ElementPtr cur_list_element;
572 
573     skipChars(in, WHITESPACE, line, pos);
574     while (c != EOF && c != ']') {
575         if (in.peek() != ']') {
576             cur_list_element = Element::fromJSON(in, file, line, pos);
577             list->add(cur_list_element);
578             c = skipTo(in, file, line, pos, ",]", WHITESPACE);
579         } else {
580             c = in.get();
581             ++pos;
582         }
583     }
584     return (list);
585 }
586 
587 ElementPtr
fromStringstreamMap(std::istream & in,const std::string & file,int & line,int & pos)588 fromStringstreamMap(std::istream& in, const std::string& file, int& line,
589                     int& pos) {
590     ElementPtr map = Element::createMap(Element::Position(file, line, pos));
591     skipChars(in, WHITESPACE, line, pos);
592     int c = in.peek();
593     if (c == EOF) {
594         throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
595     } else if (c == '}') {
596         // empty map, skip closing curly
597         in.ignore();
598     } else {
599         while (c != EOF && c != '}') {
600             std::string key = strFromStringstream(in, file, line, pos);
601 
602             skipTo(in, file, line, pos, ":", WHITESPACE);
603             // skip the :
604 
605             ConstElementPtr value = Element::fromJSON(in, file, line, pos);
606             map->set(key, value);
607 
608             c = skipTo(in, file, line, pos, ",}", WHITESPACE);
609         }
610     }
611     return (map);
612 }
613 } // end anonymous namespace
614 
615 std::string
typeToName(Element::types type)616 Element::typeToName(Element::types type) {
617     switch (type) {
618     case Element::integer:
619         return (std::string("integer"));
620     case Element::real:
621         return (std::string("real"));
622     case Element::boolean:
623         return (std::string("boolean"));
624     case Element::string:
625         return (std::string("string"));
626     case Element::list:
627         return (std::string("list"));
628     case Element::map:
629         return (std::string("map"));
630     case Element::null:
631         return (std::string("null"));
632     case Element::any:
633         return (std::string("any"));
634     default:
635         return (std::string("unknown"));
636     }
637 }
638 
639 Element::types
nameToType(const std::string & type_name)640 Element::nameToType(const std::string& type_name) {
641     if (type_name == "integer") {
642         return (Element::integer);
643     } else if (type_name == "real") {
644         return (Element::real);
645     } else if (type_name == "boolean") {
646         return (Element::boolean);
647     } else if (type_name == "string") {
648         return (Element::string);
649     } else if (type_name == "list") {
650         return (Element::list);
651     } else if (type_name == "map") {
652         return (Element::map);
653     } else if (type_name == "named_set") {
654         return (Element::map);
655     } else if (type_name == "null") {
656         return (Element::null);
657     } else if (type_name == "any") {
658         return (Element::any);
659     } else {
660         isc_throw(TypeError, type_name + " is not a valid type name");
661     }
662 }
663 
664 ElementPtr
fromJSON(std::istream & in,bool preproc)665 Element::fromJSON(std::istream& in, bool preproc) {
666 
667     int line = 1, pos = 1;
668     stringstream filtered;
669     if (preproc) {
670         preprocess(in, filtered);
671     }
672 
673     ElementPtr value = fromJSON(preproc ? filtered : in, "<istream>", line, pos);
674 
675     return (value);
676 }
677 
678 ElementPtr
fromJSON(std::istream & in,const std::string & file_name,bool preproc)679 Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) {
680     int line = 1, pos = 1;
681     stringstream filtered;
682     if (preproc) {
683         preprocess(in, filtered);
684     }
685     return (fromJSON(preproc ? filtered : in, file_name, line, pos));
686 }
687 
688 ElementPtr
fromJSON(std::istream & in,const std::string & file,int & line,int & pos)689 Element::fromJSON(std::istream& in, const std::string& file, int& line,
690                   int& pos) {
691     int c = 0;
692     ElementPtr element;
693     bool el_read = false;
694     skipChars(in, WHITESPACE, line, pos);
695     while (c != EOF && !el_read) {
696         c = in.get();
697         pos++;
698         switch(c) {
699             case '1':
700             case '2':
701             case '3':
702             case '4':
703             case '5':
704             case '6':
705             case '7':
706             case '8':
707             case '9':
708             case '0':
709             case '-':
710             case '+':
711             case '.':
712                 in.putback(c);
713                 --pos;
714                 element = fromStringstreamNumber(in, file, line, pos);
715                 el_read = true;
716                 break;
717             case 't':
718             case 'f':
719                 in.putback(c);
720                 --pos;
721                 element = fromStringstreamBool(in, file, line, pos);
722                 el_read = true;
723                 break;
724             case 'n':
725                 in.putback(c);
726                 --pos;
727                 element = fromStringstreamNull(in, file, line, pos);
728                 el_read = true;
729                 break;
730             case '"':
731                 in.putback('"');
732                 --pos;
733                 element = fromStringstreamString(in, file, line, pos);
734                 el_read = true;
735                 break;
736             case '[':
737                 element = fromStringstreamList(in, file, line, pos);
738                 el_read = true;
739                 break;
740             case '{':
741                 element = fromStringstreamMap(in, file, line, pos);
742                 el_read = true;
743                 break;
744             case EOF:
745                 break;
746             default:
747                 throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos);
748                 break;
749         }
750     }
751     if (el_read) {
752         return (element);
753     } else {
754         isc_throw(JSONError, "nothing read");
755     }
756 }
757 
758 ElementPtr
fromJSON(const std::string & in,bool preproc)759 Element::fromJSON(const std::string& in, bool preproc) {
760     std::stringstream ss;
761     ss << in;
762 
763     int line = 1, pos = 1;
764     stringstream filtered;
765     if (preproc) {
766         preprocess(ss, filtered);
767     }
768     ElementPtr result(fromJSON(preproc ? filtered : ss, "<string>", line, pos));
769     skipChars(ss, WHITESPACE, line, pos);
770     // ss must now be at end
771     if (ss.peek() != EOF) {
772         throwJSONError("Extra data", "<string>", line, pos);
773     }
774     return result;
775 }
776 
777 ElementPtr
fromJSONFile(const std::string & file_name,bool preproc)778 Element::fromJSONFile(const std::string& file_name, bool preproc) {
779     // zero out the errno to be safe
780     errno = 0;
781 
782     std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
783     if (!infile.is_open()) {
784         const char* error = strerror(errno);
785         isc_throw(InvalidOperation, "failed to read file '" << file_name
786                   << "': " << error);
787     }
788 
789     return (fromJSON(infile, file_name, preproc));
790 }
791 
792 // to JSON format
793 
794 void
toJSON(std::ostream & ss) const795 IntElement::toJSON(std::ostream& ss) const {
796     ss << intValue();
797 }
798 
799 void
toJSON(std::ostream & ss) const800 DoubleElement::toJSON(std::ostream& ss) const {
801     // The default output for doubles nicely drops off trailing
802     // zeros, however this produces strings without decimal points
803     // for whole number values.  When reparsed this will create
804     // IntElements not DoubleElements.  Rather than used a fixed
805     // precision, we'll just tack on an ".0" when the decimal point
806     // is missing.
807     ostringstream val_ss;
808     val_ss << doubleValue();
809     ss << val_ss.str();
810     if (val_ss.str().find_first_of('.') == string::npos) {
811         ss << ".0";
812     }
813 }
814 
815 void
toJSON(std::ostream & ss) const816 BoolElement::toJSON(std::ostream& ss) const {
817     if (boolValue()) {
818         ss << "true";
819     } else {
820         ss << "false";
821     }
822 }
823 
824 void
toJSON(std::ostream & ss) const825 NullElement::toJSON(std::ostream& ss) const {
826     ss << "null";
827 }
828 
829 void
toJSON(std::ostream & ss) const830 StringElement::toJSON(std::ostream& ss) const {
831     ss << "\"";
832     const std::string& str = stringValue();
833     for (size_t i = 0; i < str.size(); ++i) {
834         const char c = str[i];
835         // Escape characters as defined in JSON spec
836         // Note that we do not escape forward slash; this
837         // is allowed, but not mandatory.
838         switch (c) {
839         case '"':
840             ss << '\\' << c;
841             break;
842         case '\\':
843             ss << '\\' << c;
844             break;
845         case '\b':
846             ss << '\\' << 'b';
847             break;
848         case '\f':
849             ss << '\\' << 'f';
850             break;
851         case '\n':
852             ss << '\\' << 'n';
853             break;
854         case '\r':
855             ss << '\\' << 'r';
856             break;
857         case '\t':
858             ss << '\\' << 't';
859             break;
860         default:
861             if (((c >= 0) && (c < 0x20)) || (c < 0) || (c >= 0x7f)) {
862                 std::ostringstream esc;
863                 esc << "\\u"
864                     << hex
865                     << setw(4)
866                     << setfill('0')
867                     << (static_cast<unsigned>(c) & 0xff);
868                 ss << esc.str();
869             } else {
870                 ss << c;
871             }
872         }
873     }
874     ss << "\"";
875 }
876 
877 void
toJSON(std::ostream & ss) const878 ListElement::toJSON(std::ostream& ss) const {
879     ss << "[ ";
880 
881     const std::vector<ElementPtr>& v = listValue();
882     for (auto it = v.begin(); it != v.end(); ++it) {
883         if (it != v.begin()) {
884             ss << ", ";
885         }
886         (*it)->toJSON(ss);
887     }
888     ss << " ]";
889 }
890 
891 void
toJSON(std::ostream & ss) const892 MapElement::toJSON(std::ostream& ss) const {
893     ss << "{ ";
894 
895     const std::map<std::string, ConstElementPtr>& m = mapValue();
896     for (auto it = m.begin(); it != m.end(); ++it) {
897         if (it != m.begin()) {
898             ss << ", ";
899         }
900         ss << "\"" << (*it).first << "\": ";
901         if ((*it).second) {
902             (*it).second->toJSON(ss);
903         } else {
904             ss << "None";
905         }
906     }
907     ss << " }";
908 }
909 
910 // throws when one of the types in the path (except the one
911 // we're looking for) is not a MapElement
912 // returns 0 if it could simply not be found
913 // should that also be an exception?
914 ConstElementPtr
find(const std::string & id) const915 MapElement::find(const std::string& id) const {
916     const size_t sep = id.find('/');
917     if (sep == std::string::npos) {
918         return (get(id));
919     } else {
920         ConstElementPtr ce = get(id.substr(0, sep));
921         if (ce) {
922             // ignore trailing slash
923             if  (sep + 1 != id.size()) {
924                 return (ce->find(id.substr(sep + 1)));
925             } else {
926                 return (ce);
927             }
928         } else {
929             return (ElementPtr());
930         }
931     }
932 }
933 
934 ElementPtr
fromWire(const std::string & s)935 Element::fromWire(const std::string& s) {
936     std::stringstream ss;
937     ss << s;
938     int line = 0, pos = 0;
939     return (fromJSON(ss, "<wire>", line, pos));
940 }
941 
942 ElementPtr
fromWire(std::stringstream & in,int)943 Element::fromWire(std::stringstream& in, int) {
944     //
945     // Check protocol version
946     //
947     //for (int i = 0 ; i < 4 ; ++i) {
948     //    const unsigned char version_byte = get_byte(in);
949     //    if (PROTOCOL_VERSION[i] != version_byte) {
950     //        throw DecodeError("Protocol version incorrect");
951     //    }
952     //}
953     //length -= 4;
954     int line = 0, pos = 0;
955     return (fromJSON(in, "<wire>", line, pos));
956 }
957 
958 void
set(const std::string & key,ConstElementPtr value)959 MapElement::set(const std::string& key, ConstElementPtr value) {
960     m[key] = value;
961 }
962 
963 bool
find(const std::string & id,ConstElementPtr & t) const964 MapElement::find(const std::string& id, ConstElementPtr& t) const {
965     try {
966         ConstElementPtr p = find(id);
967         if (p) {
968             t = p;
969             return (true);
970         }
971     } catch (const TypeError&) {
972         // ignore
973     }
974     return (false);
975 }
976 
977 bool
equals(const Element & other) const978 IntElement::equals(const Element& other) const {
979     return (other.getType() == Element::integer) &&
980            (i == other.intValue());
981 }
982 
983 bool
equals(const Element & other) const984 DoubleElement::equals(const Element& other) const {
985     return (other.getType() == Element::real) &&
986            (fabs(d - other.doubleValue()) < 1e-14);
987 }
988 
989 bool
equals(const Element & other) const990 BoolElement::equals(const Element& other) const {
991     return (other.getType() == Element::boolean) &&
992            (b == other.boolValue());
993 }
994 
995 bool
equals(const Element & other) const996 NullElement::equals(const Element& other) const {
997     return (other.getType() == Element::null);
998 }
999 
1000 bool
equals(const Element & other) const1001 StringElement::equals(const Element& other) const {
1002     return (other.getType() == Element::string) &&
1003            (s == other.stringValue());
1004 }
1005 
1006 bool
equals(const Element & other) const1007 ListElement::equals(const Element& other) const {
1008     if (other.getType() == Element::list) {
1009         const size_t s = size();
1010         if (s != other.size()) {
1011             return (false);
1012         }
1013         for (size_t i = 0; i < s; ++i) {
1014             if (!get(i)->equals(*other.get(i))) {
1015                 return (false);
1016             }
1017         }
1018         return (true);
1019     } else {
1020         return (false);
1021     }
1022 }
1023 
1024 void
sort(std::string const & index)1025 ListElement::sort(std::string const& index /* = std::string() */) {
1026     if (l.empty()) {
1027         return;
1028     }
1029 
1030     int const t(l.at(0)->getType());
1031     std::function<bool(ElementPtr, ElementPtr)> comparator;
1032     if (t == map) {
1033         if (index.empty()) {
1034             isc_throw(BadValue, "index required when sorting maps");
1035         }
1036         comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1037             ConstElementPtr const& ai(a->get(index));
1038             ConstElementPtr const& bi(b->get(index));
1039             if (ai && bi) {
1040                 return *ai < *bi;
1041             }
1042             return true;
1043         };
1044     } else if (t == list) {
1045         // Nested lists. Not supported.
1046         return;
1047     } else {
1048         // Assume scalars.
1049         if (!index.empty()) {
1050             isc_throw(BadValue, "index given when sorting scalars?");
1051         }
1052         comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1053             return *a < *b;
1054         };
1055     }
1056 
1057     std::sort(l.begin(), l.end(), comparator);
1058 }
1059 
1060 bool
equals(const Element & other) const1061 MapElement::equals(const Element& other) const {
1062     if (other.getType() == Element::map) {
1063         if (size() != other.size()) {
1064             return (false);
1065         }
1066         for (auto kv : mapValue()) {
1067             auto key = kv.first;
1068             if (other.contains(key)) {
1069                 if (!get(key)->equals(*other.get(key))) {
1070                     return (false);
1071                 }
1072             } else {
1073                 return (false);
1074             }
1075         }
1076         return (true);
1077     } else {
1078         return (false);
1079     }
1080 }
1081 
1082 bool
isNull(ConstElementPtr p)1083 isNull(ConstElementPtr p) {
1084     return (!p);
1085 }
1086 
1087 void
removeIdentical(ElementPtr a,ConstElementPtr b)1088 removeIdentical(ElementPtr a, ConstElementPtr b) {
1089     if (!b) {
1090         return;
1091     }
1092     if (a->getType() != Element::map || b->getType() != Element::map) {
1093         isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1094     }
1095 
1096     // As maps do not allow entries with multiple keys, we can either iterate
1097     // over a checking for identical entries in b or vice-versa.  As elements
1098     // are removed from a if a match is found, we choose to iterate over b to
1099     // avoid problems with element removal affecting the iterator.
1100     for (auto kv : b->mapValue()) {
1101         auto key = kv.first;
1102         if (a->contains(key)) {
1103             if (a->get(key)->equals(*b->get(key))) {
1104                 a->remove(key);
1105             }
1106         }
1107     }
1108 }
1109 
1110 ConstElementPtr
removeIdentical(ConstElementPtr a,ConstElementPtr b)1111 removeIdentical(ConstElementPtr a, ConstElementPtr b) {
1112     ElementPtr result = Element::createMap();
1113 
1114     if (!b) {
1115         return (result);
1116     }
1117 
1118     if (a->getType() != Element::map || b->getType() != Element::map) {
1119         isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1120     }
1121 
1122     for (auto kv : a->mapValue()) {
1123         auto key = kv.first;
1124         if (!b->contains(key) ||
1125             !a->get(key)->equals(*b->get(key))) {
1126             result->set(key, kv.second);
1127         }
1128     }
1129 
1130     return (result);
1131 }
1132 
1133 void
merge(ElementPtr element,ConstElementPtr other)1134 merge(ElementPtr element, ConstElementPtr other) {
1135     if (element->getType() != Element::map ||
1136         other->getType() != Element::map) {
1137         isc_throw(TypeError, "merge arguments not MapElements");
1138     }
1139 
1140     for (auto kv : other->mapValue()) {
1141         auto key = kv.first;
1142         auto value = kv.second;
1143         if (value && value->getType() != Element::null) {
1144             element->set(key, value);
1145         } else if (element->contains(key)) {
1146             element->remove(key);
1147         }
1148     }
1149 }
1150 
1151 ElementPtr
copy(ConstElementPtr from,int level)1152 copy(ConstElementPtr from, int level) {
1153     if (!from) {
1154         isc_throw(BadValue, "copy got a null pointer");
1155     }
1156     int from_type = from->getType();
1157     if (from_type == Element::integer) {
1158         return (ElementPtr(new IntElement(from->intValue())));
1159     } else if (from_type == Element::real) {
1160         return (ElementPtr(new DoubleElement(from->doubleValue())));
1161     } else if (from_type == Element::boolean) {
1162         return (ElementPtr(new BoolElement(from->boolValue())));
1163     } else if (from_type == Element::null) {
1164         return (ElementPtr(new NullElement()));
1165     } else if (from_type == Element::string) {
1166         return (ElementPtr(new StringElement(from->stringValue())));
1167     } else if (from_type == Element::list) {
1168         ElementPtr result = ElementPtr(new ListElement());
1169         for (auto elem : from->listValue()) {
1170             if (level == 0) {
1171                 result->add(elem);
1172             } else {
1173                 result->add(copy(elem, level - 1));
1174             }
1175         }
1176         return (result);
1177     } else if (from_type == Element::map) {
1178         ElementPtr result = ElementPtr(new MapElement());
1179         for (auto kv : from->mapValue()) {
1180             auto key = kv.first;
1181             auto value = kv.second;
1182             if (level == 0) {
1183                 result->set(key, value);
1184             } else {
1185                 result->set(key, copy(value, level - 1));
1186             }
1187         }
1188         return (result);
1189     } else {
1190         isc_throw(BadValue, "copy got an element of type: " << from_type);
1191     }
1192 }
1193 
1194 namespace {
1195 
1196 // Helper function which blocks infinite recursion
1197 bool
isEquivalent0(ConstElementPtr a,ConstElementPtr b,unsigned level)1198 isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level) {
1199     // check looping forever on cycles
1200     if (!level) {
1201         isc_throw(BadValue, "isEquivalent got infinite recursion: "
1202                   "arguments include cycles");
1203     }
1204     if (!a || !b) {
1205         isc_throw(BadValue, "isEquivalent got a null pointer");
1206     }
1207     // check types
1208     if (a->getType() != b->getType()) {
1209         return (false);
1210     }
1211     if (a->getType() == Element::list) {
1212         // check empty
1213         if (a->empty()) {
1214             return (b->empty());
1215         }
1216         // check size
1217         if (a->size() != b->size()) {
1218             return (false);
1219         }
1220 
1221         // copy b into a list
1222         const size_t s = a->size();
1223         std::list<ConstElementPtr> l;
1224         for (size_t i = 0; i < s; ++i) {
1225             l.push_back(b->get(i));
1226         }
1227 
1228         // iterate on a
1229         for (size_t i = 0; i < s; ++i) {
1230             ConstElementPtr item = a->get(i);
1231             // lookup this item in the list
1232             bool found = false;
1233             for (auto it = l.begin(); it != l.end(); ++it) {
1234                 // if found in the list remove it
1235                 if (isEquivalent0(item, *it, level - 1)) {
1236                     found = true;
1237                     l.erase(it);
1238                     break;
1239                 }
1240             }
1241             // if not found argument differs
1242             if (!found) {
1243                 return (false);
1244             }
1245         }
1246 
1247         // sanity check: the list must be empty
1248         if (!l.empty()) {
1249             isc_throw(Unexpected, "isEquivalent internal error");
1250         }
1251         return (true);
1252     } else if (a->getType() == Element::map) {
1253         // check sizes
1254         if (a->size() != b->size()) {
1255             return (false);
1256         }
1257         // iterate on the first map
1258         for (auto kv : a->mapValue()) {
1259             // get the b value for the given keyword and recurse
1260             ConstElementPtr item = b->get(kv.first);
1261             if (!item || !isEquivalent0(kv.second, item, level - 1)) {
1262                 return (false);
1263             }
1264         }
1265         return (true);
1266     } else {
1267         return (a->equals(*b));
1268     }
1269 }
1270 
1271 } // end anonymous namespace
1272 
1273 bool
isEquivalent(ConstElementPtr a,ConstElementPtr b)1274 isEquivalent(ConstElementPtr a, ConstElementPtr b) {
1275     return (isEquivalent0(a, b, 100));
1276 }
1277 
1278 void
prettyPrint(ConstElementPtr element,std::ostream & out,unsigned indent,unsigned step)1279 prettyPrint(ConstElementPtr element, std::ostream& out,
1280             unsigned indent, unsigned step) {
1281     if (!element) {
1282         isc_throw(BadValue, "prettyPrint got a null pointer");
1283     }
1284     if (element->getType() == Element::list) {
1285         // empty list case
1286         if (element->empty()) {
1287             out << "[ ]";
1288             return;
1289         }
1290 
1291         // complex ? multiline : oneline
1292         if (!element->get(0)) {
1293             isc_throw(BadValue, "prettyPrint got a null pointer");
1294         }
1295         int first_type = element->get(0)->getType();
1296         bool complex = false;
1297         if ((first_type == Element::list) || (first_type == Element::map)) {
1298             complex = true;
1299         }
1300         std::string separator = complex ? ",\n" : ", ";
1301 
1302         // open the list
1303         out << "[" << (complex ? "\n" : " ");
1304 
1305         // iterate on items
1306         const auto& l = element->listValue();
1307         for (auto it = l.begin(); it != l.end(); ++it) {
1308             // add the separator if not the first item
1309             if (it != l.begin()) {
1310                 out << separator;
1311             }
1312             // add indentation
1313             if (complex) {
1314                 out << std::string(indent + step, ' ');
1315             }
1316             // recursive call
1317             prettyPrint(*it, out, indent + step, step);
1318         }
1319 
1320         // close the list
1321         if (complex) {
1322             out << "\n" << std::string(indent, ' ');
1323         } else {
1324             out << " ";
1325         }
1326         out << "]";
1327     } else if (element->getType() == Element::map) {
1328         // empty map case
1329         if (element->size() == 0) {
1330             out << "{ }";
1331             return;
1332         }
1333 
1334         // open the map
1335         out << "{\n";
1336 
1337         // iterate on keyword: value
1338         const auto& m = element->mapValue();
1339         bool first = true;
1340         for (auto it = m.begin(); it != m.end(); ++it) {
1341             // add the separator if not the first item
1342             if (first) {
1343                 first = false;
1344             } else {
1345                 out << ",\n";
1346             }
1347             // add indentation
1348             out << std::string(indent + step, ' ');
1349             // add keyword:
1350             out << "\"" << it->first << "\": ";
1351             // recursive call
1352             prettyPrint(it->second, out, indent + step, step);
1353         }
1354 
1355         // close the map
1356         out << "\n" << std::string(indent, ' ') << "}";
1357     } else {
1358         // not a list or a map
1359         element->toJSON(out);
1360     }
1361 }
1362 
1363 std::string
prettyPrint(ConstElementPtr element,unsigned indent,unsigned step)1364 prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
1365     std::stringstream ss;
1366     prettyPrint(element, ss, indent, step);
1367     return (ss.str());
1368 }
1369 
preprocess(std::istream & in,std::stringstream & out)1370 void Element::preprocess(std::istream& in, std::stringstream& out) {
1371 
1372     std::string line;
1373 
1374     while (std::getline(in, line)) {
1375         // If this is a comments line, replace it with empty line
1376         // (so the line numbers will still match
1377         if (!line.empty() && line[0] == '#') {
1378             line = "";
1379         }
1380 
1381         // getline() removes end line characters. Unfortunately, we need
1382         // it for getting the line numbers right (in case we report an
1383         // error.
1384         out << line;
1385         out << "\n";
1386     }
1387 }
1388 
1389 } // end of isc::data namespace
1390 } // end of isc namespace
1391