Skip to content

Commit 15699ea

Browse files
authored
refactor(gctx): extract toml dotted keys validation (#15998)
### What does this PR try to resolve? These small refactors were found during the experiment of making ConfigValue span-aware/ ### How to test and review this PR? No functional changes. Tests should all pass.
2 parents 1c714ca + 397bdb7 commit 15699ea

File tree

1 file changed

+89
-85
lines changed

1 file changed

+89
-85
lines changed

src/cargo/util/context/mod.rs

Lines changed: 89 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,98 +1469,20 @@ impl GlobalContext {
14691469
self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
14701470
.with_context(|| format!("failed to load config from `{}`", str_path))?
14711471
} else {
1472-
// We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
1473-
// expressions followed by a value that's not an "inline table"
1474-
// (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
1475-
// parse the value as a toml_edit::DocumentMut, and check that the (single)
1476-
// inner-most table is set via dotted keys.
1477-
let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
1478-
format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
1479-
})?;
1480-
fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
1481-
d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
1482-
}
1483-
fn non_empty_decor(d: &toml_edit::Decor) -> bool {
1484-
non_empty(d.prefix()) || non_empty(d.suffix())
1485-
}
1486-
fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
1487-
non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
1488-
}
1489-
let ok = {
1490-
let mut got_to_value = false;
1491-
let mut table = doc.as_table();
1492-
let mut is_root = true;
1493-
while table.is_dotted() || is_root {
1494-
is_root = false;
1495-
if table.len() != 1 {
1496-
break;
1497-
}
1498-
let (k, n) = table.iter().next().expect("len() == 1 above");
1499-
match n {
1500-
Item::Table(nt) => {
1501-
if table.key(k).map_or(false, non_empty_key_decor)
1502-
|| non_empty_decor(nt.decor())
1503-
{
1504-
bail!(
1505-
"--config argument `{arg}` \
1506-
includes non-whitespace decoration"
1507-
)
1508-
}
1509-
table = nt;
1510-
}
1511-
Item::Value(v) if v.is_inline_table() => {
1512-
bail!(
1513-
"--config argument `{arg}` \
1514-
sets a value to an inline table, which is not accepted"
1515-
);
1516-
}
1517-
Item::Value(v) => {
1518-
if table
1519-
.key(k)
1520-
.map_or(false, |k| non_empty(k.leaf_decor().prefix()))
1521-
|| non_empty_decor(v.decor())
1522-
{
1523-
bail!(
1524-
"--config argument `{arg}` \
1525-
includes non-whitespace decoration"
1526-
)
1527-
}
1528-
got_to_value = true;
1529-
break;
1530-
}
1531-
Item::ArrayOfTables(_) => {
1532-
bail!(
1533-
"--config argument `{arg}` \
1534-
sets a value to an array of tables, which is not accepted"
1535-
);
1536-
}
1537-
1538-
Item::None => {
1539-
bail!("--config argument `{arg}` doesn't provide a value")
1540-
}
1541-
}
1542-
}
1543-
got_to_value
1544-
};
1545-
if !ok {
1546-
bail!(
1547-
"--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
1548-
);
1549-
}
1550-
1551-
let toml_v: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1472+
let doc = toml_dotted_keys(arg)?;
1473+
let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
15521474
.with_context(|| {
15531475
format!("failed to parse value from --config argument `{arg}`")
15541476
})?;
15551477

1556-
if toml_v
1478+
if doc
15571479
.get("registry")
15581480
.and_then(|v| v.as_table())
15591481
.and_then(|t| t.get("token"))
15601482
.is_some()
15611483
{
15621484
bail!("registry.token cannot be set through --config for security reasons");
1563-
} else if let Some((k, _)) = toml_v
1485+
} else if let Some((k, _)) = doc
15641486
.get("registries")
15651487
.and_then(|v| v.as_table())
15661488
.and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
@@ -1571,7 +1493,7 @@ impl GlobalContext {
15711493
);
15721494
}
15731495

1574-
if toml_v
1496+
if doc
15751497
.get("registry")
15761498
.and_then(|v| v.as_table())
15771499
.and_then(|t| t.get("secret-key"))
@@ -1580,7 +1502,7 @@ impl GlobalContext {
15801502
bail!(
15811503
"registry.secret-key cannot be set through --config for security reasons"
15821504
);
1583-
} else if let Some((k, _)) = toml_v
1505+
} else if let Some((k, _)) = doc
15841506
.get("registries")
15851507
.and_then(|v| v.as_table())
15861508
.and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
@@ -1591,7 +1513,7 @@ impl GlobalContext {
15911513
);
15921514
}
15931515

1594-
CV::from_toml(Definition::Cli(None), toml_v)
1516+
CV::from_toml(Definition::Cli(None), doc)
15951517
.with_context(|| format!("failed to convert --config argument `{arg}`"))?
15961518
};
15971519
let tmp_table = self
@@ -3056,6 +2978,88 @@ fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResul
30562978
toml.parse().map_err(Into::into)
30572979
}
30582980

2981+
fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2982+
// We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
2983+
// expressions followed by a value that's not an "inline table"
2984+
// (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
2985+
// parse the value as a toml_edit::DocumentMut, and check that the (single)
2986+
// inner-most table is set via dotted keys.
2987+
let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2988+
format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2989+
})?;
2990+
fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2991+
d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2992+
}
2993+
fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2994+
non_empty(d.prefix()) || non_empty(d.suffix())
2995+
}
2996+
fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2997+
non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2998+
}
2999+
let ok = {
3000+
let mut got_to_value = false;
3001+
let mut table = doc.as_table();
3002+
let mut is_root = true;
3003+
while table.is_dotted() || is_root {
3004+
is_root = false;
3005+
if table.len() != 1 {
3006+
break;
3007+
}
3008+
let (k, n) = table.iter().next().expect("len() == 1 above");
3009+
match n {
3010+
Item::Table(nt) => {
3011+
if table.key(k).map_or(false, non_empty_key_decor)
3012+
|| non_empty_decor(nt.decor())
3013+
{
3014+
bail!(
3015+
"--config argument `{arg}` \
3016+
includes non-whitespace decoration"
3017+
)
3018+
}
3019+
table = nt;
3020+
}
3021+
Item::Value(v) if v.is_inline_table() => {
3022+
bail!(
3023+
"--config argument `{arg}` \
3024+
sets a value to an inline table, which is not accepted"
3025+
);
3026+
}
3027+
Item::Value(v) => {
3028+
if table
3029+
.key(k)
3030+
.map_or(false, |k| non_empty(k.leaf_decor().prefix()))
3031+
|| non_empty_decor(v.decor())
3032+
{
3033+
bail!(
3034+
"--config argument `{arg}` \
3035+
includes non-whitespace decoration"
3036+
)
3037+
}
3038+
got_to_value = true;
3039+
break;
3040+
}
3041+
Item::ArrayOfTables(_) => {
3042+
bail!(
3043+
"--config argument `{arg}` \
3044+
sets a value to an array of tables, which is not accepted"
3045+
);
3046+
}
3047+
3048+
Item::None => {
3049+
bail!("--config argument `{arg}` doesn't provide a value")
3050+
}
3051+
}
3052+
}
3053+
got_to_value
3054+
};
3055+
if !ok {
3056+
bail!(
3057+
"--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
3058+
);
3059+
}
3060+
Ok(doc)
3061+
}
3062+
30593063
/// A type to deserialize a list of strings from a toml file.
30603064
///
30613065
/// Supports deserializing either a whitespace-separated list of arguments in a

0 commit comments

Comments
 (0)