Browse Source

[master] Merge branch 'trac1626'

Jelte Jansen 13 years ago
parent
commit
3b09268518
3 changed files with 140 additions and 14 deletions
  1. 88 11
      src/lib/cc/data.cc
  2. 51 2
      src/lib/cc/tests/data_unittests.cc
  3. 1 1
      src/lib/python/isc/acl/tests/dns_test.py

+ 88 - 11
src/lib/cc/data.cc

@@ -30,6 +30,10 @@
 
 using namespace std;
 
+namespace {
+const char* WHITESPACE = " \b\f\n\r\t";
+} // end anonymous namespace
+
 namespace isc {
 namespace data {
 
@@ -314,15 +318,49 @@ str_from_stringstream(std::istream &in, const std::string& file, const int line,
     } else {
         throwJSONError("String expected", file, line, pos);
     }
+
     while (c != EOF && c != '"') {
-        ss << c;
-        if (c == '\\' && in.peek() == '"') {
-            ss << in.get();
+        if (c == '\\') {
+            // see the spec for allowed escape characters
+            switch (in.peek()) {
+            case '"':
+                c = '"';
+                break;
+            case '/':
+                c = '/';
+                break;
+            case '\\':
+                c = '\\';
+                break;
+            case 'b':
+                c = '\b';
+                break;
+            case 'f':
+                c = '\f';
+                break;
+            case 'n':
+                c = '\n';
+                break;
+            case 'r':
+                c = '\r';
+                break;
+            case 't':
+                c = '\t';
+                break;
+            default:
+                throwJSONError("Bad escape", file, line, pos);
+            }
+            // drop the escaped char
+            in.get();
             ++pos;
         }
+        ss << c;
         c = in.get();
         ++pos;
     }
+    if (c == EOF) {
+        throwJSONError("Unterminated string", file, line, pos);
+    }
     return (ss.str());
 }
 
@@ -427,12 +465,12 @@ from_stringstream_list(std::istream &in, const std::string& file, int& line,
     ElementPtr list = Element::createList();
     ConstElementPtr cur_list_element;
 
-    skip_chars(in, " \t\n", line, pos);
+    skip_chars(in, WHITESPACE, line, pos);
     while (c != EOF && c != ']') {
         if (in.peek() != ']') {
             cur_list_element = Element::fromJSON(in, file, line, pos);
             list->add(cur_list_element);
-            skip_to(in, file, line, pos, ",]", " \t\n");
+            skip_to(in, file, line, pos, ",]", WHITESPACE);
         }
         c = in.get();
         pos++;
@@ -445,7 +483,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
                       int& pos)
 {
     ElementPtr map = Element::createMap();
-    skip_chars(in, " \t\n", line, pos);
+    skip_chars(in, WHITESPACE, line, pos);
     char c = in.peek();
     if (c == EOF) {
         throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
@@ -456,7 +494,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
         while (c != EOF && c != '}') {
             std::string key = str_from_stringstream(in, file, line, pos);
 
-            skip_to(in, file, line, pos, ":", " \t\n");
+            skip_to(in, file, line, pos, ":", WHITESPACE);
             // skip the :
             in.get();
             pos++;
@@ -464,7 +502,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
             ConstElementPtr value = Element::fromJSON(in, file, line, pos);
             map->set(key, value);
             
-            skip_to(in, file, line, pos, ",}", " \t\n");
+            skip_to(in, file, line, pos, ",}", WHITESPACE);
             c = in.get();
             pos++;
         }
@@ -543,7 +581,7 @@ Element::fromJSON(std::istream &in, const std::string& file, int& line,
     char c = 0;
     ElementPtr element;
     bool el_read = false;
-    skip_chars(in, " \n\t", line, pos);
+    skip_chars(in, WHITESPACE, line, pos);
     while (c != EOF && !el_read) {
         c = in.get();
         pos++;
@@ -610,7 +648,14 @@ ElementPtr
 Element::fromJSON(const std::string &in) {
     std::stringstream ss;
     ss << in;
-    return (fromJSON(ss, "<string>"));
+    int line = 1, pos = 1;
+    ElementPtr result(fromJSON(ss, "<string>", line, pos));
+    skip_chars(ss, WHITESPACE, line, pos);
+    // ss must now be at end
+    if (ss.peek() != EOF) {
+        throwJSONError("Extra data", "<string>", line, pos);
+    }
+    return result;
 }
 
 // to JSON format
@@ -642,7 +687,39 @@ NullElement::toJSON(std::ostream& ss) const {
 void
 StringElement::toJSON(std::ostream& ss) const {
     ss << "\"";
-    ss << stringValue();
+    char c;
+    const std::string& str = stringValue();
+    for (size_t i = 0; i < str.size(); ++i) {
+        c = str[i];
+        // Escape characters as defined in JSON spec
+        // Note that we do not escape forward slash; this
+        // is allowed, but not mandatory.
+        switch (c) {
+        case '"':
+            ss << '\\' << c;
+            break;
+        case '\\':
+            ss << '\\' << c;
+            break;
+        case '\b':
+            ss << '\\' << 'b';
+            break;
+        case '\f':
+            ss << '\\' << 'f';
+            break;
+        case '\n':
+            ss << '\\' << 'n';
+            break;
+        case '\r':
+            ss << '\\' << 'r';
+            break;
+        case '\t':
+            ss << '\\' << 't';
+            break;
+        default:
+            ss << c;
+        }
+    }
     ss << "\"";
 }
 

+ 51 - 2
src/lib/cc/tests/data_unittests.cc

@@ -20,6 +20,7 @@
 
 using namespace isc::data;
 
+#include <sstream>
 #include <iostream>
 using std::oct;
 #include <iomanip>
@@ -90,7 +91,7 @@ TEST(Element, from_and_to_json) {
     sv.push_back("-1");
     sv.push_back("-1.234");
     sv.push_back("-123.456");
-    
+
     BOOST_FOREACH(const std::string& s, sv) {
         // test << operator, which uses Element::str()
         std::ostringstream stream;
@@ -122,8 +123,16 @@ TEST(Element, from_and_to_json) {
     sv.push_back("{ \"a\": None}");
     sv.push_back("");
     sv.push_back("nul");
+    sv.push_back("hello\"foobar\"");
+    sv.push_back("\"foobar\"hello");
+    sv.push_back("[]hello");
+    sv.push_back("{}hello");
+    // String not delimited correctly
+    sv.push_back("\"hello");
+    sv.push_back("hello\"");
+
+
     BOOST_FOREACH(std::string s, sv) {
-        
         EXPECT_THROW(el = Element::fromJSON(s), isc::data::JSONError);
     }
 
@@ -150,6 +159,9 @@ TEST(Element, from_and_to_json) {
     EXPECT_EQ("false", Element::fromJSON("FALSE")->str());
     EXPECT_EQ("true", Element::fromJSON("True")->str());
     EXPECT_EQ("true", Element::fromJSON("TRUE")->str());
+    EXPECT_EQ("\"\"", Element::fromJSON("  \n \t \r \f \b \"\" \n \f \t \r \b")->str());
+    EXPECT_EQ("{  }", Element::fromJSON("{  \n  \r \t  \b \f }")->str());
+    EXPECT_EQ("[  ]", Element::fromJSON("[  \n  \r \f \t  \b  ]")->str());
 
     // number overflows
     EXPECT_THROW(Element::fromJSON("12345678901234567890")->str(), JSONError);
@@ -299,6 +311,43 @@ TEST(Element, create_and_value_throws) {
 
 }
 
+// Helper for escape check; it puts the given string in a StringElement,
+// then checks for the following conditions:
+// stringValue() must be same as input
+// toJSON() output must be escaped
+// fromJSON() on the previous output must result in original input
+void
+escapeHelper(const std::string& input, const std::string& expected) {
+    StringElement str_element = StringElement(input);
+    EXPECT_EQ(input, str_element.stringValue());
+    std::stringstream os;
+    str_element.toJSON(os);
+    EXPECT_EQ(expected, os.str());
+    ElementPtr str_element2 = Element::fromJSON(os.str());
+    EXPECT_EQ(str_element.stringValue(), str_element2->stringValue());
+}
+
+TEST(Element, escape) {
+    // Test whether quotes are escaped correctly when creating direct
+    // String elements.
+    escapeHelper("foo\"bar", "\"foo\\\"bar\"");
+    escapeHelper("foo\\bar", "\"foo\\\\bar\"");
+    escapeHelper("foo\bbar", "\"foo\\bbar\"");
+    escapeHelper("foo\fbar", "\"foo\\fbar\"");
+    escapeHelper("foo\nbar", "\"foo\\nbar\"");
+    escapeHelper("foo\rbar", "\"foo\\rbar\"");
+    escapeHelper("foo\tbar", "\"foo\\tbar\"");
+    // Bad escapes
+    EXPECT_THROW(Element::fromJSON("\\a"), JSONError);
+    EXPECT_THROW(Element::fromJSON("\\"), JSONError);
+    // Can't have escaped quotes outside strings
+    EXPECT_THROW(Element::fromJSON("\\\"\\\""), JSONError);
+    // Inside strings is OK
+    EXPECT_NO_THROW(Element::fromJSON("\"\\\"\\\"\""));
+    // A whitespace test
+    EXPECT_NO_THROW(Element::fromJSON("\"  \n  \r \t \f  \n \n    \t\""));
+}
+
 TEST(Element, ListElement) {
     // this function checks the specific functions for ListElements
     ElementPtr el = Element::fromJSON("[ 1, \"bar\", 3 ]");

+ 1 - 1
src/lib/python/isc/acl/tests/dns_test.py

@@ -321,7 +321,7 @@ class RequestACLTest(unittest.TestCase):
                                   '     "from": "192.0.2.0/24"},' +
                                   '    {"action": "DROP",' +
                                   '     "from": "2001:db8::1"},' +
-                                  '] }')
+                                  ']')
         self.assertEqual(ACCEPT, acl.execute(CONTEXT4))
         self.assertEqual(REJECT, acl.execute(get_context('192.0.2.2')))
         self.assertEqual(DROP, acl.execute(get_context('2001:db8::1')))