]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/JSONSchema.pm
cgroup: cpu quota: fix resetting period length for v1
[pve-common.git] / src / PVE / JSONSchema.pm
index de2261e5a91c343fa5e19ee1b37b2cf23c0ebff4..71df690b56d846795d556583509639ed8d01461f 100644 (file)
@@ -21,6 +21,8 @@ parse_property_string
 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/
@@ -177,7 +179,7 @@ register_format('pve-configid', \&pve_verify_configid);
 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";
     }
@@ -271,20 +273,25 @@ sub parse_idmap {
     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 storagepair-list map, you need to pass the full parameter to
+# parse_idmap
+register_format('storagepair', \&verify_storagepair);
+sub verify_storagepair {
+    my ($storagepair, $noerr) = @_;
+    return $verify_idpair->($storagepair, $noerr, 'pve-storage-id');
 }
 
 register_format('mac-addr', \&pve_verify_mac_addr);
@@ -469,13 +476,25 @@ register_format('email', \&pve_verify_email);
 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) = @_;
@@ -670,6 +689,18 @@ sub pve_verify_tfa_secret {
     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) = @_;
 
@@ -695,13 +726,14 @@ sub check_format {
        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.
@@ -1164,7 +1196,10 @@ sub validate {
     # 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) {
@@ -1613,7 +1648,8 @@ sub get_options {
            $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);
                }
@@ -1622,7 +1658,19 @@ sub get_options {
                    $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;
@@ -1635,7 +1683,7 @@ sub get_options {
            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);
                }
            }
@@ -1897,9 +1945,12 @@ sub generate_typetext {
 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;
     }
 
@@ -1909,6 +1960,8 @@ sub print_property_string {
        raise "format error", errors => $errors;
     }
 
+    $data = $validator->($data) if $validator;
+
     my ($default_key, $keyAliasProps) = &$find_schema_default_key($format);
 
     my $res = '';