@@ -1176,17 +1176,102 @@ impl Config {
1176
1176
map. insert ( "include" . to_string ( ) , value) ;
1177
1177
CV :: Table ( map, Definition :: Cli )
1178
1178
} else {
1179
- // TODO: This should probably use a more narrow parser, reject
1180
- // comments, blank lines, [headers], etc.
1179
+ // We only want to allow "dotted keys" (see https://toml.io/en/v1.0.0#keys), which
1180
+ // are defined as .-separated "simple" keys, where a simple key is either a quoted
1181
+ // or unquoted key, as defined in the ANBF:
1182
+ //
1183
+ // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
1184
+ // quoted-key = basic-string / literal-string
1185
+ //
1186
+ // https://github.com/toml-lang/toml/blob/1.0.0/toml.abnf#L50-L51
1187
+ //
1188
+ // We don't want to bring in a full parser, but luckily since we're just verifying
1189
+ // a subset of the format here, the code isn't too hairy. The big thing we need to
1190
+ // deal with is quoted strings and escaped characters.
1191
+ let mut in_quoted_string = false ;
1192
+ let mut in_literal_string = false ;
1193
+ let mut chars = arg. chars ( ) ;
1194
+ let mut depth = 1 ;
1195
+ while let Some ( c) = chars. next ( ) {
1196
+ match c {
1197
+ '\'' if !in_quoted_string => {
1198
+ in_literal_string = !in_literal_string;
1199
+ }
1200
+ _ if in_literal_string => {
1201
+ // The spec only allows certain characters here, but it doesn't matter
1202
+ // for the purposes of checking if this is indeed a dotted key. If the
1203
+ // user gave an invalid expression, it'll be detected when we parse as
1204
+ // TOML later.
1205
+ //
1206
+ // Note that escapes are not permitted in literal strings.
1207
+ }
1208
+ '"' => {
1209
+ in_quoted_string = !in_quoted_string;
1210
+ }
1211
+ '\\' => {
1212
+ // Whatever the next char is, it's not a double quote or an escape.
1213
+ // Technically escape can capture more than one char, but that's only
1214
+ // possible if uXXXX and UXXXXXXXX Unicode specfiers, which are all hex
1215
+ // characters anyway, and therefore won't cause a problem.
1216
+ let _ = chars. next ( ) ;
1217
+ }
1218
+ _ if in_quoted_string => {
1219
+ // Anything goes within quotes as far as we're concerned
1220
+ }
1221
+ 'A' ..='Z' | 'a' ..='z' | '0' ..='9' | '-' | '_' => {
1222
+ // These are fine as part of a dotted key
1223
+ }
1224
+ '.' => {
1225
+ // This is a dotted key separator -- dots are okay
1226
+ depth += 1 ;
1227
+ }
1228
+ ' ' | '\t' => {
1229
+ // This kind of whitespace is acceptable in dotted keys.
1230
+ // Technically it's only allowed around the dots and =,
1231
+ // but there's no need for us to be picky about that here.
1232
+ }
1233
+ '=' => {
1234
+ // We didn't hit anything questionable before hitting the first =
1235
+ // (that is not within a quoted string), so this is a dotted key
1236
+ // expression.
1237
+ break ;
1238
+ }
1239
+ _ => {
1240
+ // We hit some character before the = that isn't permitted in a dotted
1241
+ // key expression, so the user is trying to pass something more
1242
+ // involved.
1243
+ bail ! (
1244
+ "--config argument `{}` was not a TOML dotted key expression (a.b.c = _)" ,
1245
+ arg
1246
+ ) ;
1247
+ }
1248
+ }
1249
+ }
1181
1250
let toml_v: toml:: Value = toml:: de:: from_str ( arg)
1182
1251
. with_context ( || format ! ( "failed to parse --config argument `{}`" , arg) ) ?;
1183
- let toml_table = toml_v. as_table ( ) . unwrap ( ) ;
1184
- if toml_table. len ( ) != 1 {
1185
- bail ! (
1186
- "--config argument `{}` expected exactly one key=value pair, got {} keys" ,
1187
- arg,
1188
- toml_table. len( )
1189
- ) ;
1252
+
1253
+ // To avoid questions around nested table merging, we disallow tables with more
1254
+ // than one value -- such changes should instead take the form of multiple dotted
1255
+ // key expressions passed as separate --config arguments.
1256
+ {
1257
+ let mut table = toml_v. as_table ( ) ;
1258
+ while let Some ( t) = table {
1259
+ if t. len ( ) != 1 {
1260
+ bail ! (
1261
+ "--config argument `{}` expected exactly one key=value pair, got {} keys" ,
1262
+ arg,
1263
+ t. len( )
1264
+ ) ;
1265
+ }
1266
+ if depth == 0 {
1267
+ bail ! (
1268
+ "--config argument `{}` uses inline table values, which are not accepted" ,
1269
+ arg,
1270
+ ) ;
1271
+ }
1272
+ depth -= 1 ;
1273
+ table = t. values ( ) . next ( ) . unwrap ( ) . as_table ( ) ;
1274
+ }
1190
1275
}
1191
1276
CV :: from_toml ( Definition :: Cli , toml_v)
1192
1277
. with_context ( || format ! ( "failed to convert --config argument `{}`" , arg) ) ?
0 commit comments