Fix multi-line strings are not allowed for key

In spec https://github.com/toml-lang/toml#keys

Quoted keys are clarified as

> he exact same rules as either basic strings or literal strings

TOML clearly distinguishes basic string and multi-line basic string
(literal string is also).

https://github.com/toml-lang/toml#string

So table key and quoted key should not allow multi-line basic string
and multi-line literal string.

ABNF definition also describes that.

https://github.com/toml-lang/toml/blob/master/toml.abnf

```
string = ml-basic-string / basic-string / ml-literal-string / literal-string

quoted-key = basic-string / literal-string
```

`string` contains `ml-*` but `quoted-key` doesn't.
This commit is contained in:
rhysd 2018-09-25 16:33:52 +09:00
parent 48daf82e1a
commit 935d95ce4f
3 changed files with 53 additions and 31 deletions

View file

@ -152,6 +152,9 @@ enum ErrorKind {
/// An empty table key was found. /// An empty table key was found.
EmptyTableKey, EmptyTableKey,
/// Multiline strings are not allowed for key
MultilineStringKey,
/// A custom error which could be generated when deserializing a particular /// A custom error which could be generated when deserializing a particular
/// type. /// type.
Custom, Custom,
@ -1274,6 +1277,9 @@ impl<'a> Deserializer<'a> {
TokenError::EmptyTableKey(at) => { TokenError::EmptyTableKey(at) => {
self.error(at, ErrorKind::EmptyTableKey) self.error(at, ErrorKind::EmptyTableKey)
} }
TokenError::MultilineStringKey(at) => {
self.error(at, ErrorKind::MultilineStringKey)
}
} }
} }
@ -1377,6 +1383,7 @@ impl fmt::Display for Error {
} }
ErrorKind::RedefineAsArray => "table redefined as array".fmt(f)?, ErrorKind::RedefineAsArray => "table redefined as array".fmt(f)?,
ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?, ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?,
ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?,
ErrorKind::Custom => self.inner.message.fmt(f)?, ErrorKind::Custom => self.inner.message.fmt(f)?,
ErrorKind::ExpectedString => "expected string".fmt(f)?, ErrorKind::ExpectedString => "expected string".fmt(f)?,
ErrorKind::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?, ErrorKind::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?,
@ -1421,6 +1428,7 @@ impl error::Error for Error {
ErrorKind::DuplicateTable(_) => "duplicate table", ErrorKind::DuplicateTable(_) => "duplicate table",
ErrorKind::RedefineAsArray => "table redefined as array", ErrorKind::RedefineAsArray => "table redefined as array",
ErrorKind::EmptyTableKey => "empty table key found", ErrorKind::EmptyTableKey => "empty table key found",
ErrorKind::MultilineStringKey => "invalid multiline string for key",
ErrorKind::Custom => "a custom error", ErrorKind::Custom => "a custom error",
ErrorKind::ExpectedString => "expected string", ErrorKind::ExpectedString => "expected string",
ErrorKind::DottedKeyInvalidType => "dotted key invalid type", ErrorKind::DottedKeyInvalidType => "dotted key invalid type",

View file

@ -38,7 +38,7 @@ pub enum Token<'a> {
RightBracket, RightBracket,
Keylike(&'a str), Keylike(&'a str),
String { src: &'a str, val: Cow<'a, str> }, String { src: &'a str, val: Cow<'a, str>, multiline: bool },
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
@ -51,6 +51,7 @@ pub enum Error {
Unexpected(usize, char), Unexpected(usize, char),
UnterminatedString(usize), UnterminatedString(usize),
NewlineInTableKey(usize), NewlineInTableKey(usize),
MultilineStringKey(usize),
EmptyTableKey(usize), EmptyTableKey(usize),
Wanted { at: usize, expected: &'static str, found: &'static str }, Wanted { at: usize, expected: &'static str, found: &'static str },
} }
@ -169,8 +170,11 @@ impl<'a> Tokenizer<'a> {
let current = self.current(); let current = self.current();
match self.next()? { match self.next()? {
Some((span, Token::Keylike(k))) => Ok((span, k.into())), Some((span, Token::Keylike(k))) => Ok((span, k.into())),
Some((span, Token::String { src, val })) => { Some((span, Token::String { src, val, multiline })) => {
let offset = self.substr_offset(src); let offset = self.substr_offset(src);
if multiline {
return Err(Error::MultilineStringKey(offset))
}
if val == "" { if val == "" {
return Err(Error::EmptyTableKey(offset)) return Err(Error::EmptyTableKey(offset))
} }
@ -286,6 +290,7 @@ impl<'a> Tokenizer<'a> {
return Ok(String { return Ok(String {
src: &self.input[start..start+2], src: &self.input[start..start+2],
val: Cow::Borrowed(""), val: Cow::Borrowed(""),
multiline: false,
}) })
} }
} }
@ -321,6 +326,7 @@ impl<'a> Tokenizer<'a> {
return Ok(String { return Ok(String {
src: &self.input[start..self.current()], src: &self.input[start..self.current()],
val: val.into_cow(&self.input[..i]), val: val.into_cow(&self.input[..i]),
multiline: multiline,
}) })
} }
Some((i, c)) => new_ch(self, &mut val, multiline, i, c)?, Some((i, c)) => new_ch(self, &mut val, multiline, i, c)?,
@ -497,7 +503,7 @@ impl<'a> Token<'a> {
Token::LeftBrace => "a left brace", Token::LeftBrace => "a left brace",
Token::RightBracket => "a right bracket", Token::RightBracket => "a right bracket",
Token::LeftBracket => "a left bracket", Token::LeftBracket => "a left bracket",
Token::String { .. } => "a string", Token::String { multiline, .. } => if multiline { "a multiline string" } else { "a string" },
Token::Colon => "a colon", Token::Colon => "a colon",
Token::Plus => "a plus", Token::Plus => "a plus",
} }
@ -518,56 +524,58 @@ mod tests {
#[test] #[test]
fn literal_strings() { fn literal_strings() {
fn t(input: &str, val: &str) { fn t(input: &str, val: &str, multiline: bool) {
let mut t = Tokenizer::new(input); let mut t = Tokenizer::new(input);
let (_, token) = t.next().unwrap().unwrap(); let (_, token) = t.next().unwrap().unwrap();
assert_eq!(token, Token::String { assert_eq!(token, Token::String {
src: input, src: input,
val: Cow::Borrowed(val), val: Cow::Borrowed(val),
multiline: multiline,
}); });
assert!(t.next().unwrap().is_none()); assert!(t.next().unwrap().is_none());
} }
t("''", ""); t("''", "", false);
t("''''''", ""); t("''''''", "", true);
t("'''\n'''", ""); t("'''\n'''", "", true);
t("'a'", "a"); t("'a'", "a", false);
t("'\"a'", "\"a"); t("'\"a'", "\"a", false);
t("''''a'''", "'a"); t("''''a'''", "'a", true);
t("'''\n'a\n'''", "'a\n"); t("'''\n'a\n'''", "'a\n", true);
t("'''a\n'a\r\n'''", "a\n'a\n"); t("'''a\n'a\r\n'''", "a\n'a\n", true);
} }
#[test] #[test]
fn basic_strings() { fn basic_strings() {
fn t(input: &str, val: &str) { fn t(input: &str, val: &str, multiline: bool) {
let mut t = Tokenizer::new(input); let mut t = Tokenizer::new(input);
let (_, token) = t.next().unwrap().unwrap(); let (_, token) = t.next().unwrap().unwrap();
assert_eq!(token, Token::String { assert_eq!(token, Token::String {
src: input, src: input,
val: Cow::Borrowed(val), val: Cow::Borrowed(val),
multiline: multiline,
}); });
assert!(t.next().unwrap().is_none()); assert!(t.next().unwrap().is_none());
} }
t(r#""""#, ""); t(r#""""#, "", false);
t(r#""""""""#, ""); t(r#""""""""#, "", true);
t(r#""a""#, "a"); t(r#""a""#, "a", false);
t(r#""""a""""#, "a"); t(r#""""a""""#, "a", true);
t(r#""\t""#, "\t"); t(r#""\t""#, "\t", false);
t(r#""\u0000""#, "\0"); t(r#""\u0000""#, "\0", false);
t(r#""\U00000000""#, "\0"); t(r#""\U00000000""#, "\0", false);
t(r#""\U000A0000""#, "\u{A0000}"); t(r#""\U000A0000""#, "\u{A0000}", false);
t(r#""\\t""#, "\\t"); t(r#""\\t""#, "\\t", false);
t("\"\"\"\\\n\"\"\"", ""); t("\"\"\"\\\n\"\"\"", "", true);
t("\"\"\"\\\n \t \t \\\r\n \t \n \t \r\n\"\"\"", ""); t("\"\"\"\\\n \t \t \\\r\n \t \n \t \r\n\"\"\"", "", true);
t(r#""\r""#, "\r"); t(r#""\r""#, "\r", false);
t(r#""\n""#, "\n"); t(r#""\n""#, "\n", false);
t(r#""\b""#, "\u{8}"); t(r#""\b""#, "\u{8}", false);
t(r#""a\fa""#, "a\u{c}a"); t(r#""a\fa""#, "a\u{c}a", false);
t(r#""\"a""#, "\"a"); t(r#""\"a""#, "\"a", false);
t("\"\"\"\na\"\"\"", "a"); t("\"\"\"\na\"\"\"", "a", true);
t("\"\"\"\n\"\"\"", ""); t("\"\"\"\n\"\"\"", "", true);
err(r#""\a"#, Error::InvalidEscape(2, 'a')); err(r#""\a"#, Error::InvalidEscape(2, 'a'));
err("\"\\\n", Error::InvalidEscape(2, '\n')); err("\"\\\n", Error::InvalidEscape(2, '\n'));
err("\"\\\r\n", Error::InvalidEscape(2, '\n')); err("\"\\\r\n", Error::InvalidEscape(2, '\n'));

View file

@ -275,6 +275,10 @@ fn bad_keys() {
"\"\"|=3".parse::<Value>().unwrap_err(); "\"\"|=3".parse::<Value>().unwrap_err();
"\"\n\"|=3".parse::<Value>().unwrap_err(); "\"\n\"|=3".parse::<Value>().unwrap_err();
"\"\r\"|=3".parse::<Value>().unwrap_err(); "\"\r\"|=3".parse::<Value>().unwrap_err();
"''''''=3".parse::<Value>().unwrap_err();
"\"\"\"\"\"\"=3".parse::<Value>().unwrap_err();
"'''key'''=3".parse::<Value>().unwrap_err();
"\"\"\"key\"\"\"=3".parse::<Value>().unwrap_err();
} }
#[test] #[test]
@ -290,6 +294,8 @@ fn bad_table_names() {
"[']".parse::<Value>().unwrap_err(); "[']".parse::<Value>().unwrap_err();
"[''']".parse::<Value>().unwrap_err(); "[''']".parse::<Value>().unwrap_err();
"['''''']".parse::<Value>().unwrap_err(); "['''''']".parse::<Value>().unwrap_err();
"['''foo''']".parse::<Value>().unwrap_err();
"[\"\"\"bar\"\"\"]".parse::<Value>().unwrap_err();
"['\n']".parse::<Value>().unwrap_err(); "['\n']".parse::<Value>().unwrap_err();
"['\r\n']".parse::<Value>().unwrap_err(); "['\r\n']".parse::<Value>().unwrap_err();
} }