_read_config: Add infrastructure for host-dependent validation
This is a preparatory step for separating recap variables from config variables.
This commit is contained in:
parent
0f1ea65fd7
commit
76afbb6673
1 changed files with 54 additions and 36 deletions
90
ddclient.in
90
ddclient.in
|
@ -1776,6 +1776,49 @@ sub _read_config {
|
||||||
my %globals = ();
|
my %globals = ();
|
||||||
my %config = ();
|
my %config = ();
|
||||||
my $content = '';
|
my $content = '';
|
||||||
|
# Checks a single key-value pair for a host and writes the normalized value output to an
|
||||||
|
# optional output ref.
|
||||||
|
my $check = sub {
|
||||||
|
# TODO: The checks below are incorrect for a few reasons:
|
||||||
|
#
|
||||||
|
# * It is not protocol-aware. Different protocols can have different sets of variables,
|
||||||
|
# with different normalization and validation behaviors.
|
||||||
|
# * It does not check for missing required values. Note that a later line or a
|
||||||
|
# command-line argument might define a missing required value.
|
||||||
|
# * A later line or command-line argument might override an invalid value, changing it to
|
||||||
|
# valid.
|
||||||
|
#
|
||||||
|
# Fixing this is not simple. Values should be checked and normalized after processing the
|
||||||
|
# entire file and command-line arguments, but then we lose line number context. The line
|
||||||
|
# number could be recorded along with each variable's value to provide context in case
|
||||||
|
# validation fails, but that adds considerable complexity. Fortunately, a variable's type
|
||||||
|
# is unlikely to change even if the protocol changes (`$variables{merged}{$var}{type}` will
|
||||||
|
# likely equal `$protocols{$proto}{variables}{$var}{type}` for each variable `$var` for
|
||||||
|
# each protocol `$proto`), so normalizing and validating values on a line-by-line basis is
|
||||||
|
# likely to be safe.
|
||||||
|
my ($h, $k, $v, $normout) = @_;
|
||||||
|
if (!exists $variables{'merged'}{$k}) {
|
||||||
|
warning("unrecognized keyword");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
my $def = $variables{'merged'}{$k};
|
||||||
|
my $norm;
|
||||||
|
if (!eval { $norm = check_value($v, $def); 1; }) {
|
||||||
|
my $vf = defined($v) ? "'$v'" : '<undefined>';
|
||||||
|
warning("invalid value $vf: $@");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$$normout = $norm if defined($normout);
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
# Calls $check on each entry in the given hashref, deleting any entries that don't pass.
|
||||||
|
my $checkall = sub {
|
||||||
|
my ($h, $l) = @_;
|
||||||
|
for my $k (keys(%$l)) {
|
||||||
|
local $_l = pushlogctx($k);
|
||||||
|
delete($l->{$k}) if !$check->($h, $k, $l->{$k}, \$l->{$k});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
local *FD;
|
local *FD;
|
||||||
if (!open(FD, "< $file")) {
|
if (!open(FD, "< $file")) {
|
||||||
|
@ -1858,39 +1901,8 @@ sub _read_config {
|
||||||
($_, %locals) = parse_assignments($_);
|
($_, %locals) = parse_assignments($_);
|
||||||
s/\s*,\s*/,/g;
|
s/\s*,\s*/,/g;
|
||||||
my @args = split;
|
my @args = split;
|
||||||
|
|
||||||
## verify that keywords are valid...and check the value
|
|
||||||
for my $k (keys %locals) {
|
for my $k (keys %locals) {
|
||||||
$locals{$k} = $passwords{$k} if defined $passwords{$k};
|
$locals{$k} = $passwords{$k} if defined $passwords{$k};
|
||||||
# TODO: The checks below are incorrect for a few reasons:
|
|
||||||
#
|
|
||||||
# * It is not protocol-aware. Different protocols can have different sets of
|
|
||||||
# variables, with different normalization and validation behaviors.
|
|
||||||
# * It does not check for missing required values. Note that a later line or a
|
|
||||||
# command-line argument might define a missing required value.
|
|
||||||
# * A later line or command-line argument might override an invalid value, changing
|
|
||||||
# it to valid.
|
|
||||||
#
|
|
||||||
# Fixing this is not simple. Values should be checked and normalized after processing
|
|
||||||
# the entire file and command-line arguments, but then we lose line number context.
|
|
||||||
# The line number could be recorded along with each variable's value to provide context
|
|
||||||
# in case validation fails, but that adds considerable complexity. Fortunately, a
|
|
||||||
# variable's type is unlikely to change even if the protocol changes
|
|
||||||
# (`$variables{merged}{$var}{type}` will likely equal
|
|
||||||
# `$protocols{$proto}{variables}{$var}{type}` for each variable `$var` for each
|
|
||||||
# protocol `$proto`), so normalizing and validating values on a line-by-line basis is
|
|
||||||
# likely to be safe.
|
|
||||||
if (!exists $variables{'merged'}{$k}) {
|
|
||||||
warning("unrecognized keyword '%s' (ignored)", $k);
|
|
||||||
delete $locals{$k};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
my $def = $variables{'merged'}{$k};
|
|
||||||
if (!eval { $locals{$k} = check_value($locals{$k}, $def); 1; }) {
|
|
||||||
warning("invalid variable value '$k=$locals{$k}': $@");
|
|
||||||
delete $locals{$k};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
%passwords = ();
|
%passwords = ();
|
||||||
if (defined($locals{'host'})) {
|
if (defined($locals{'host'})) {
|
||||||
|
@ -1901,15 +1913,21 @@ sub _read_config {
|
||||||
$locals{'password'} = $password if defined $password;
|
$locals{'password'} = $password if defined $password;
|
||||||
my @hosts = split_by_comma($host);
|
my @hosts = split_by_comma($host);
|
||||||
if (!@hosts) {
|
if (!@hosts) {
|
||||||
|
local $_l = pushlogctx('globals');
|
||||||
|
$checkall->(undef, \%locals);
|
||||||
%globals = (%globals, %locals);
|
%globals = (%globals, %locals);
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
for my $h (@hosts) {
|
for my $h (@hosts) {
|
||||||
# TODO: Shouldn't %locals go after $config{h}? Later lines should override earlier
|
local $_l = pushlogctx($h);
|
||||||
# lines, no? Otherwise, later assignments will have a mixed effect: assignments to new
|
# Shallow clone of %locals for host-dependent validation and normalization.
|
||||||
# variables will take effect but assignments to variables that already have a value
|
my %hlocals = %locals;
|
||||||
# will not.
|
$checkall->($h, \%hlocals);
|
||||||
$config{$h} = {%globals, %locals, %{$config{$h} // {}}, 'host' => $h};
|
# TODO: Shouldn't `%hlocals` go after `$config{h}`? Later lines should override
|
||||||
|
# earlier lines, no? Otherwise, later assignments will have a mixed effect:
|
||||||
|
# assignments to new variables will take effect but assignments to variables that
|
||||||
|
# already have a value will not.
|
||||||
|
$config{$h} = {%globals, %hlocals, %{$config{$h} // {}}, 'host' => $h};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(FD);
|
close(FD);
|
||||||
|
|
Loading…
Reference in a new issue