Repository: alexcrichton/toml-rs
Pull request #187 (2017-06-01 20:05:31)

More fuzzing fixes


Patch:
diff --git a/src/datetime.rs b/src/datetime.rs
index f39d9fa..83b5c0b 100644
--- a/src/datetime.rs
+++ b/src/datetime.rs
@@ -55,7 +55,7 @@ struct Time {
     hour: u8,
     minute: u8,
     second: u8,
-    secfract: f64,
+    nanosecond: u32,
 }
 
 #[derive(PartialEq, Clone)]
@@ -97,9 +97,9 @@ impl fmt::Display for Date {
 impl fmt::Display for Time {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
-        if self.secfract != 0.0 {
-            let s = format!("{}", self.secfract);
-            write!(f, "{}", s.trim_left_matches("0"))?;
+        if self.nanosecond != 0 {
+            let s = format!("{:09}", self.nanosecond);
+            write!(f, ".{}", s.trim_right_matches('0'))?;
         }
         Ok(())
     }
@@ -199,38 +199,37 @@ impl FromStr for Datetime {
             let s1 = digit(&mut chars)?;
             let s2 = digit(&mut chars)?;
 
-            let secfract = if chars.clone().next() == Some('.') {
+            let mut nanosecond = 0;
+            if chars.clone().next() == Some('.') {
                 chars.next();
-                let mut first = true;
                 let whole = chars.as_str();
+
                 let mut end = whole.len();
-                for (i, c) in whole.char_indices() {
-                    match c {
-                        '0' ... '9' => {}
+                for (i, byte) in whole.bytes().enumerate() {
+                    match byte {
+                        b'0' ... b'9' => {
+                            if i < 9 {
+                                let p = 10_u32.pow(8 - i as u32);
+                                nanosecond += p * (byte - b'0') as u32;
+                            }
+                        }
                         _ => {
                             end = i;
-                            break
+                            break;
                         }
                     }
-                    first = false;
                 }
-                if first {
+                if end == 0 {
                     return Err(DatetimeParseError { _private: () })
                 }
                 chars = whole[end..].chars();
-                match format!("0.{}", &whole[..end]).parse() {
-                    Ok(f) => f,
-                    Err(_) => return Err(DatetimeParseError { _private: () }),
-                }
-            } else {
-                0.0
-            };
+            }
 
             let time = Time {
                 hour: h1 * 10 + h2,
                 minute: m1 * 10 + m2,
                 second: s1 * 10 + s2,
-                secfract: secfract,
+                nanosecond: nanosecond,
             };
 
             if time.hour > 24 {
@@ -239,7 +238,10 @@ impl FromStr for Datetime {
             if time.minute > 59 {
                 return Err(DatetimeParseError { _private: () })
             }
-            if time.second > 60 {
+            if time.second > 59 {
+                return Err(DatetimeParseError { _private: () })
+            }
+            if time.nanosecond > 999_999_999 {
                 return Err(DatetimeParseError { _private: () })
             }
 
diff --git a/src/ser.rs b/src/ser.rs
index 8f6b366..77db867 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -107,7 +107,8 @@ pub enum Error {
     /// attempted where the key of a map was not a string.
     KeyNotString,
 
-    /// Keys in maps are not allowed to have newlines.
+    /// An error that we never omit but keep for backwards compatibility
+    #[doc(hidden)]
     KeyNewline,
 
     /// Arrays in TOML must have a homogenous type, but a heterogeneous array
@@ -624,9 +625,6 @@ impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> {
             SerializeTable::Table { ref mut key, .. } => {
                 key.truncate(0);
                 *key = input.serialize(StringExtractor)?;
-                if key.contains('\n') {
-                    return Err(Error::KeyNewline)
-                }
             }
         }
         Ok(())
@@ -1047,13 +1045,13 @@ impl fmt::Display for Error {
         match *self {
             Error::UnsupportedType => "unsupported Rust type".fmt(f),
             Error::KeyNotString => "map key was not a string".fmt(f),
-            Error::KeyNewline => "map keys cannot contain newlines".fmt(f),
             Error::ArrayMixedType => "arrays cannot have mixed types".fmt(f),
             Error::ValueAfterTable => "values must be emitted before tables".fmt(f),
             Error::DateInvalid => "a serialized date was invalid".fmt(f),
             Error::NumberInvalid => "a serialized number was invalid".fmt(f),
             Error::UnsupportedNone => "unsupported None value".fmt(f),
             Error::Custom(ref s) => s.fmt(f),
+            Error::KeyNewline => unreachable!(),
             Error::__Nonexhaustive => panic!(),
         }
     }
@@ -1064,13 +1062,13 @@ impl error::Error for Error {
         match *self {
             Error::UnsupportedType => "unsupported Rust type",
             Error::KeyNotString => "map key was not a string",
-            Error::KeyNewline => "map keys cannot contain newlines",
             Error::ArrayMixedType => "arrays cannot have mixed types",
             Error::ValueAfterTable => "values must be emitted before tables",
             Error::DateInvalid => "a serialized date was invalid",
             Error::NumberInvalid => "a serialized number was invalid",
             Error::UnsupportedNone => "unsupported None value",
             Error::Custom(_) => "custom error",
+            Error::KeyNewline => unreachable!(),
             Error::__Nonexhaustive => panic!(),
         }
     }


Test patch:
diff --git a/tests/parser.rs b/tests/parser.rs
index 2db2cfb..2282416 100644
--- a/tests/parser.rs
+++ b/tests/parser.rs
@@ -463,7 +463,7 @@ fn datetimes() {
     t!("2016-09-09T09:09:09Z");
     t!("2016-09-09T09:09:09.1Z");
     t!("2016-09-09T09:09:09.2+10:00");
-    t!("2016-09-09T09:09:09.0123456789-02:00");
+    t!("2016-09-09T09:09:09.123456789-02:00");
     bad!("foo = 2016-09-09T09:09:09.Z", "failed to parse date");
     bad!("foo = 2016-9-09T09:09:09Z", "failed to parse date");
     bad!("foo = 2016-09-09T09:09:09+2:00", "failed to parse date");
diff --git a/tests/valid.rs b/tests/valid.rs
index 676fc67..e7577ad 100644
--- a/tests/valid.rs
+++ b/tests/valid.rs
@@ -190,6 +190,12 @@ test!(example_bom,
        include_str!("valid/example-bom.toml"),
        include_str!("valid/example.json"));
 
+test!(datetime_truncate,
+      include_str!("valid/datetime-truncate.toml"),
+      include_str!("valid/datetime-truncate.json"));
+test!(key_quote_newline,
+      include_str!("valid/key-quote-newline.toml"),
+      include_str!("valid/key-quote-newline.json"));
 test!(table_array_nest_no_keys,
       include_str!("valid/table-array-nest-no-keys.toml"),
       include_str!("valid/table-array-nest-no-keys.json"));
diff --git a/tests/valid/datetime-truncate.json b/tests/valid/datetime-truncate.json
new file mode 100644
index 0000000..8c512e1
--- /dev/null
+++ b/tests/valid/datetime-truncate.json
@@ -0,0 +1,6 @@
+{
+    "bestdayever": {
+        "type": "datetime",
+        "value": "1987-07-05T17:45:00.123456789Z"
+    }
+}
diff --git a/tests/valid/datetime-truncate.toml b/tests/valid/datetime-truncate.toml
new file mode 100644
index 0000000..05de841
--- /dev/null
+++ b/tests/valid/datetime-truncate.toml
@@ -0,0 +1,1 @@
+bestdayever = 1987-07-05T17:45:00.123456789012345Z
diff --git a/tests/valid/key-quote-newline.json b/tests/valid/key-quote-newline.json
new file mode 100644
index 0000000..12473e4
--- /dev/null
+++ b/tests/valid/key-quote-newline.json
@@ -0,0 +1,3 @@
+{
+    "\n": {"type": "integer", "value": "1"}
+}
diff --git a/tests/valid/key-quote-newline.toml b/tests/valid/key-quote-newline.toml
new file mode 100644
index 0000000..a2639bf
--- /dev/null
+++ b/tests/valid/key-quote-newline.toml
@@ -0,0 +1,1 @@
+"\n" = 1