Fix disallowing duplicate table headers

This commit fixes #279 where a case of duplicate table headers slipped
through the cracks. This also adds an option to disable this new
validation to allow Cargo to preserve backwards compatibility.
This commit is contained in:
Alex Crichton 2019-01-07 09:06:04 -08:00
parent ffa27fe817
commit 184d16b4a3
5 changed files with 66 additions and 7 deletions

View file

@ -15,6 +15,9 @@ facilitate deserializing and serializing Rust structures.
""" """
categories = ["config", "encoding", "parser-implementations"] categories = ["config", "encoding", "parser-implementations"]
[workspace]
members = ['test-suite']
[badges] [badges]
travis-ci = { repository = "alexcrichton/toml-rs" } travis-ci = { repository = "alexcrichton/toml-rs" }

View file

@ -197,6 +197,7 @@ enum ErrorKind {
/// Deserialization implementation for TOML. /// Deserialization implementation for TOML.
pub struct Deserializer<'a> { pub struct Deserializer<'a> {
require_newline_after_table: bool, require_newline_after_table: bool,
allow_duplciate_after_longer_table: bool,
input: &'a str, input: &'a str,
tokens: Tokenizer<'a>, tokens: Tokenizer<'a>,
} }
@ -335,12 +336,24 @@ impl<'de, 'b> de::MapAccess<'de> for MapVisitor<'de, 'b> {
// Test to see if we're duplicating our parent's table, and if so // Test to see if we're duplicating our parent's table, and if so
// then this is an error in the toml format // then this is an error in the toml format
if self.cur_parent != pos if self.cur_parent != pos {
&& self.tables[self.cur_parent].header == self.tables[pos].header if self.tables[self.cur_parent].header == self.tables[pos].header {
{ let at = self.tables[pos].at;
let at = self.tables[pos].at; let name = self.tables[pos].header.join(".");
let name = self.tables[pos].header.join("."); return Err(self.de.error(at, ErrorKind::DuplicateTable(name)));
return Err(self.de.error(at, ErrorKind::DuplicateTable(name))); }
// If we're here we know we should share the same prefix, and if
// the longer table was defined first then we want to narrow
// down our parent's length if possible to ensure that we catch
// duplicate tables defined afterwards.
if !self.de.allow_duplciate_after_longer_table {
let parent_len = self.tables[self.cur_parent].header.len();
let cur_len = self.tables[pos].header.len();
if cur_len < parent_len {
self.cur_parent = pos;
}
}
} }
let table = &mut self.tables[pos]; let table = &mut self.tables[pos];
@ -965,6 +978,7 @@ impl<'a> Deserializer<'a> {
tokens: Tokenizer::new(input), tokens: Tokenizer::new(input),
input: input, input: input,
require_newline_after_table: true, require_newline_after_table: true,
allow_duplciate_after_longer_table: false,
} }
} }
@ -986,6 +1000,16 @@ impl<'a> Deserializer<'a> {
self.require_newline_after_table = require; self.require_newline_after_table = require;
} }
/// Historical versions of toml-rs accidentally allowed a duplicate table
/// header after a longer table header was previously defined. This is
/// invalid according to the TOML spec, however.
///
/// This option can be set to `true` (the default is `false`) to emulate
/// this behavior for backwards compatibility with older toml-rs versions.
pub fn set_allow_duplicate_after_longer_table(&mut self, allow: bool) {
self.allow_duplciate_after_longer_table = allow;
}
fn tables(&mut self) -> Result<Vec<Table<'a>>, Error> { fn tables(&mut self) -> Result<Vec<Table<'a>>, Error> {
let mut tables = Vec::new(); let mut tables = Vec::new();
let mut cur_table = Table { let mut cur_table = Table {

View file

@ -4,7 +4,7 @@ extern crate serde;
use serde::de::Deserialize; use serde::de::Deserialize;
#[test] #[test]
fn main() { fn newlines_after_tables() {
let s = " let s = "
[a] foo = 1 [a] foo = 1
[[b]] foo = 1 [[b]] foo = 1
@ -17,3 +17,25 @@ fn main() {
assert_eq!(value["a"]["foo"].as_integer(), Some(1)); assert_eq!(value["a"]["foo"].as_integer(), Some(1));
assert_eq!(value["b"][0]["foo"].as_integer(), Some(1)); assert_eq!(value["b"][0]["foo"].as_integer(), Some(1));
} }
#[test]
fn allow_duplicate_after_longer() {
let s = "
[dependencies.openssl-sys]
version = 1
[dependencies]
libc = 1
[dependencies]
bitflags = 1
";
assert!(s.parse::<toml::Value>().is_err());
let mut d = toml::de::Deserializer::new(s);
d.set_allow_duplicate_after_longer_table(true);
let value = toml::Value::deserialize(&mut d).unwrap();
assert_eq!(value["dependencies"]["openssl-sys"]["version"].as_integer(), Some(1));
assert_eq!(value["dependencies"]["libc"].as_integer(), Some(1));
assert_eq!(value["dependencies"]["bitflags"].as_integer(), Some(1));
}

View file

@ -96,3 +96,5 @@ test!(text_before_array_separator,
include_str!("invalid/text-before-array-separator.toml")); include_str!("invalid/text-before-array-separator.toml"));
test!(text_in_array, test!(text_in_array,
include_str!("invalid/text-in-array.toml")); include_str!("invalid/text-in-array.toml"));
test!(duplicate_table,
include_str!("invalid/duplicate-table.toml"));

View file

@ -0,0 +1,8 @@
[dependencies.openssl-sys]
version = "0.5.2"
[dependencies]
libc = "0.1"
[dependencies]
bitflags = "0.1.1"