register_standard_option
);
+our $CONFIGID_RE = qr/[a-z][a-z0-9_-]+/i;
+
# Note: This class implements something similar to JSON schema, but it is not 100% complete.
# see: http://tools.ietf.org/html/draft-zyp-json-schema-02
# see: http://json-schema.org/
sub pve_verify_configid {
my ($id, $noerr) = @_;
- if ($id !~ m/^[a-z][a-z0-9_-]+$/i) {
+ if ($id !~ m/^$CONFIGID_RE$/) {
return undef if $noerr;
die "invalid configuration ID '$id'\n";
}
return $map;
}
-register_format('storagepair', \&verify_storagepair);
-sub verify_storagepair {
- my ($storagepair, $noerr) = @_;
+my $verify_idpair = sub {
+ my ($input, $noerr, $format) = @_;
- # note: this only checks a single list entry
- # when using a storagepair-list map, you need to pass the full
- # parameter to parse_idmap
- eval { parse_idmap($storagepair, 'pve-storage-id') };
+ eval { parse_idmap($input, $format) };
if ($@) {
return undef if $noerr;
die "$@\n";
}
- return $storagepair;
+ return $input;
+};
+
+# note: this only checks a single list entry
+# when using a storage-pair-list map, you need to pass the full parameter to
+# parse_idmap
+register_format('storage-pair', \&verify_storagepair);
+sub verify_storagepair {
+ my ($storagepair, $noerr) = @_;
+ return $verify_idpair->($storagepair, $noerr, 'pve-storage-id');
}
register_format('mac-addr', \&pve_verify_mac_addr);
sub pve_verify_email {
my ($email, $noerr) = @_;
- if ($email !~ /^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
+ if ($email !~ /^$PVE::Tools::EMAIL_RE$/) {
return undef if $noerr;
die "value does not look like a valid email address\n";
}
return $email;
}
+register_format('email-or-username', \&pve_verify_email_or_username);
+sub pve_verify_email_or_username {
+ my ($email, $noerr) = @_;
+
+ if ($email !~ /^$PVE::Tools::EMAIL_RE$/ &&
+ $email !~ /^$PVE::Tools::EMAIL_USER_RE$/) {
+ return undef if $noerr;
+ die "value does not look like a valid email address or user name\n";
+ }
+ return $email;
+}
+
register_format('dns-name', \&pve_verify_dns_name);
sub pve_verify_dns_name {
my ($name, $noerr) = @_;
die "unable to decode TFA secret\n";
}
+
+PVE::JSONSchema::register_format('pve-task-status-type', \&verify_task_status_type);
+sub verify_task_status_type {
+ my ($value, $noerr) = @_;
+
+ return $value if $value =~ m/^(ok|error|warning|unknown)$/i;
+
+ return undef if $noerr;
+
+ die "invalid status '$value'\n";
+}
+
sub check_format {
my ($format, $value, $path) = @_;
if $format_type ne 'none' && ref($registered) ne 'CODE';
if ($format_type eq 'list') {
+ $parsed = [];
# Note: we allow empty lists
foreach my $v (split_list($value)) {
- $parsed = $registered->($v);
+ push @{$parsed}, $registered->($v);
}
} elsif ($format_type eq 'opt') {
$parsed = $registered->($value) if $value;
- } else {
+ } else {
if (ref($registered) eq 'HASH') {
# Note: this is the only case where a validator function could be
# attached, hence it's safe to handle that in parse_property_string.
# we can disable that in the final release
# todo: is there a better/faster way to detect cycles?
my $cycles = 0;
- find_cycle($instance, sub { $cycles = 1 });
+ # 'download' responses can contain a filehandle, don't cycle-check that as
+ # it produces a warning
+ my $is_download = ref($instance) eq 'HASH' && exists($instance->{download});
+ find_cycle($instance, sub { $cycles = 1 }) if !$is_download;
if ($cycles) {
add_error($errors, undef, "data structure contains recursive cycles");
} elsif ($schema) {
$opts->{$list_param} = $args;
$args = [];
} elsif (ref($arg_param)) {
- foreach my $arg_name (@$arg_param) {
+ for (my $i = 0; $i < scalar(@$arg_param); $i++) {
+ my $arg_name = $arg_param->[$i];
if ($opts->{'extra-args'}) {
raise("internal error: extra-args must be the last argument\n", code => HTTP_BAD_REQUEST);
}
$args = [];
next;
}
- raise("not enough arguments\n", code => HTTP_BAD_REQUEST) if !@$args;
+ if (!@$args) {
+ # check if all left-over arg_param are optional, else we
+ # must die as the mapping is then ambigious
+ for (; $i < scalar(@$arg_param); $i++) {
+ my $prop = $arg_param->[$i];
+ raise("not enough arguments\n", code => HTTP_BAD_REQUEST)
+ if !$schema->{properties}->{$prop}->{optional};
+ }
+ if ($arg_param->[-1] eq 'extra-args') {
+ $opts->{'extra-args'} = [];
+ }
+ last;
+ }
$opts->{$arg_name} = shift @$args;
}
raise("too many arguments\n", code => HTTP_BAD_REQUEST) if @$args;
foreach my $arg_name (@$arg_param) {
if ($arg_name eq 'extra-args') {
$opts->{'extra-args'} = [];
- } else {
+ } elsif (!$schema->{properties}->{$arg_name}->{optional}) {
raise("not enough arguments\n", code => HTTP_BAD_REQUEST);
}
}
sub print_property_string {
my ($data, $format, $skip, $path) = @_;
+ my $validator;
if (ref($format) ne 'HASH') {
my $schema = get_format($format);
die "not a valid format: $format\n" if !$schema;
+ # named formats can have validators attached
+ $validator = $format_validators->{$format};
$format = $schema;
}
raise "format error", errors => $errors;
}
+ $data = $validator->($data) if $validator;
+
my ($default_key, $keyAliasProps) = &$find_schema_default_key($format);
my $res = '';