]> git.proxmox.com Git - cargo.git/commitdiff
Use toml_edit to check dotted key for --config
authorJon Gjengset <jongje@amazon.com>
Tue, 14 Dec 2021 22:25:30 +0000 (14:25 -0800)
committerJon Gjengset <jongje@amazon.com>
Tue, 14 Dec 2021 22:25:32 +0000 (14:25 -0800)
Cargo.toml
src/cargo/util/config/mod.rs
tests/testsuite/config_cli.rs

index ee553f7c8081cd2faea4fe04c88efff4206816d7..88eae57c1a32de9c9b8713627cabc6c8d3dfd88e 100644 (file)
@@ -58,6 +58,7 @@ tar = { version = "0.4.35", default-features = false }
 tempfile = "3.0"
 termcolor = "1.1"
 toml = "0.5.7"
+toml_edit = "0.10.1"
 unicode-xid = "0.2.0"
 url = "2.2.2"
 walkdir = "2.2"
index 62b182ffb5acfc16347723022ce7d0150020c030..a1efe2cc8cd006b2c6f911e083bf2457703b8bec 100644 (file)
@@ -1175,103 +1175,51 @@ impl Config {
                 map.insert("include".to_string(), value);
                 CV::Table(map, Definition::Cli)
             } else {
-                // We only want to allow "dotted keys" (see https://toml.io/en/v1.0.0#keys), which
-                // are defined as .-separated "simple" keys, where a simple key is either a quoted
-                // or unquoted key, as defined in the ANBF:
-                //
-                //     unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
-                //     quoted-key = basic-string / literal-string
-                //
-                // https://github.com/toml-lang/toml/blob/1.0.0/toml.abnf#L50-L51
-                //
-                // We don't want to bring in a full parser, but luckily since we're just verifying
-                // a subset of the format here, the code isn't too hairy. The big thing we need to
-                // deal with is quoted strings and escaped characters.
-                let mut in_quoted_string = false;
-                let mut in_literal_string = false;
-                let mut chars = arg.chars();
-                let mut depth = 1;
-                while let Some(c) = chars.next() {
-                    match c {
-                        '\'' if !in_quoted_string => {
-                            in_literal_string = !in_literal_string;
-                        }
-                        _ if in_literal_string => {
-                            // The spec only allows certain characters here, but it doesn't matter
-                            // for the purposes of checking if this is indeed a dotted key. If the
-                            // user gave an invalid expression, it'll be detected when we parse as
-                            // TOML later.
-                            //
-                            // Note that escapes are not permitted in literal strings.
-                        }
-                        '"' => {
-                            in_quoted_string = !in_quoted_string;
-                        }
-                        '\\' => {
-                            // Whatever the next char is, it's not a double quote or an escape.
-                            // Technically escape can capture more than one char, but that's only
-                            // possible if uXXXX and UXXXXXXXX Unicode specfiers, which are all hex
-                            // characters anyway, and therefore won't cause a problem.
-                            let _ = chars.next();
-                        }
-                        _ if in_quoted_string => {
-                            // Anything goes within quotes as far as we're concerned
-                        }
-                        'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' => {
-                            // These are fine as part of a dotted key
-                        }
-                        '.' => {
-                            // This is a dotted key separator -- dots are okay
-                            depth += 1;
-                        }
-                        ' ' | '\t' => {
-                            // This kind of whitespace is acceptable in dotted keys.
-                            // Technically it's only allowed around the dots and =,
-                            // but there's no need for us to be picky about that here.
-                        }
-                        '=' => {
-                            // We didn't hit anything questionable before hitting the first =
-                            // (that is not within a quoted string), so this is a dotted key
-                            // expression.
+                // We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
+                // expressions followed by a value that's not an "inline table"
+                // (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
+                // parse the value as a toml_edit::Document, and check that the (single)
+                // inner-most table is set via dotted keys.
+                let doc: toml_edit::Document = arg.parse().with_context(|| {
+                    format!("failed to parse value from --config argument `{}` as a dotted key expression", arg)
+                })?;
+                let ok = {
+                    let mut got_to_value = false;
+                    let mut table = doc.as_table();
+                    let mut is_root = true;
+                    while table.is_dotted() || is_root {
+                        is_root = false;
+                        if table.len() != 1 {
                             break;
                         }
-                        _ => {
-                            // We hit some character before the = that isn't permitted in a dotted
-                            // key expression, so the user is trying to pass something more
-                            // involved.
-                            bail!(
-                                "--config argument `{}` was not a TOML dotted key expression (a.b.c = _)",
-                                arg
-                            );
-                        }
-                    }
-                }
-                let toml_v: toml::Value = toml::de::from_str(arg)
-                    .with_context(|| format!("failed to parse --config argument `{}`", arg))?;
-
-                // To avoid questions around nested table merging, we disallow tables with more
-                // than one value -- such changes should instead take the form of multiple dotted
-                // key expressions passed as separate --config arguments.
-                {
-                    let mut table = toml_v.as_table();
-                    while let Some(t) = table {
-                        if t.len() != 1 {
-                            bail!(
-                                "--config argument `{}` expected exactly one key=value pair, got {} keys",
-                                arg,
-                                t.len()
-                            );
-                        }
-                        if depth == 0 {
+                        let (_, n) = table.iter().next().expect("len() == 1 above");
+                        if let Some(nt) = n.as_table() {
+                            table = nt;
+                        } else if n.is_inline_table() {
                             bail!(
-                                "--config argument `{}` uses inline table values, which are not accepted",
+                                "--config argument `{}` sets a value to an inline table, which is not accepted",
                                 arg,
                             );
+                        } else {
+                            got_to_value = true;
+                            break;
                         }
-                        depth -= 1;
-                        table = t.values().next().unwrap().as_table();
                     }
+                    got_to_value
+                };
+                if !ok {
+                    bail!(
+                        "--config argument `{}` was not a TOML dotted key expression (a.b.c = _)",
+                        arg
+                    );
                 }
+
+                // Rather than trying to bridge between toml_edit::Document and toml::Value, we
+                // just parse the whole argument again now that we know it has the right format.
+                let toml_v = toml::from_str(arg).with_context(|| {
+                    format!("failed to parse value from --config argument `{}`", arg)
+                })?;
+
                 CV::from_toml(Definition::Cli, toml_v)
                     .with_context(|| format!("failed to convert --config argument `{}`", arg))?
             };
index be0f3fec387dc988600ec181fb81761a27cb8c03..ee37ab16126623c5e36bf322f65ad3b364b72bc4 100644 (file)
@@ -337,10 +337,21 @@ fn bad_parse() {
     assert_error(
         config.unwrap_err(),
         "\
-failed to parse --config argument `abc`
+failed to parse value from --config argument `abc` as a dotted key expression
 
 Caused by:
-  expected an equals, found eof at line 1 column 4",
+  TOML parse error at line 1, column 4
+  |
+1 | abc
+  |    ^
+Unexpected `end of input`
+Expected `.` or `=`",
+    );
+
+    let config = ConfigBuilder::new().config_arg("").build_err();
+    assert_error(
+        config.unwrap_err(),
+        "--config argument `` was not a TOML dotted key expression (a.b.c = _)",
     );
 }
 
@@ -352,14 +363,33 @@ fn too_many_values() {
         config.unwrap_err(),
         "\
 --config argument `a=1
-b=2` expected exactly one key=value pair, got 2 keys",
+b=2` was not a TOML dotted key expression (a.b.c = _)",
     );
+}
 
-    let config = ConfigBuilder::new().config_arg("").build_err();
+#[cargo_test]
+fn no_inline_table_value() {
+    // Disallow inline tables
+    let config = ConfigBuilder::new()
+        .config_arg("a.b={c = \"d\"}")
+        .build_err();
+    assert_error(
+        config.unwrap_err(),
+        "--config argument `a.b={c = \"d\"}` sets a value to an inline table, which is not accepted"
+    );
+}
+
+#[cargo_test]
+fn no_array_of_tables_values() {
+    // Disallow array-of-tables when not in dotted form
+    let config = ConfigBuilder::new()
+        .config_arg("[[a.b]]\nc = \"d\"")
+        .build_err();
     assert_error(
         config.unwrap_err(),
         "\
-         --config argument `` expected exactly one key=value pair, got 0 keys",
+--config argument `[[a.b]]
+c = \"d\"` was not a TOML dotted key expression (a.b.c = _)",
     );
 }