]> git.proxmox.com Git - pve-common.git/commitdiff
tools: add download_file_from_url
authorLorenz Stechauner <l.stechauner@proxmox.com>
Mon, 14 Jun 2021 09:05:51 +0000 (11:05 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 15 Jun 2021 08:21:24 +0000 (10:21 +0200)
adds a common function to download arbitrary files from urls.

code is based on
manager:PVE/API2/Nodes.pm:aplinfo

Security notice: this function does not perform any permission
checking. The callee has to make sure, that only authorized users may
use this function.

Caution: This function is able to download files from internal
networks (which would not be visible/accessible from outside), the
callee needs to ensure that unprivileged (e.g., non root@pam or the
like) can only pass OK URLs (e.g., resolving to public routable IPs)

Signed-off-by: Lorenz Stechauner <l.stechauner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
src/PVE/Tools.pm

index 16ae3d2b4cdf282d8c7a238904dc93a036a08e9f..7b82e005d555ac999f82485059c9556d91caf698 100644 (file)
@@ -1829,4 +1829,128 @@ sub safe_compare {
     return $cmp->($left, $right);
 }
 
+
+# opts
+#  -> hash_required
+#       if 1, at least one checksum has to be specified otherwise an error will be thrown
+#  -> http_proxy
+#  -> https_proxy
+#  -> verify_certificates
+#  -> sha(1|224|256|384|512)sum
+#  -> md5sum
+sub download_file_from_url {
+    my ($dest, $url, $opts) = @_;
+
+    my $tmpdest = "$dest.tmp.$$";
+
+    my $algorithm;
+    my $expected;
+
+    for ('sha512', 'sha384', 'sha256', 'sha224', 'sha1', 'md5') {
+       if (defined($opts->{"${_}sum"})) {
+           $algorithm = $_;
+           $expected = $opts->{"${_}sum"};
+           last;
+       }
+    }
+
+    die "checksum required but not specified\n" if ($opts->{hash_required} && !$algorithm);
+
+    my $worker = sub  {
+       my $upid = shift;
+
+       print "downloading $url to $dest\n";
+
+       eval {
+           if (-f $dest && $algorithm) {
+               print "calculating checksum of existing file...\n";
+               my $correct = check_file_hash($algorithm, $expected, $dest);
+
+               if ($correct) {
+                   print "file already exists, no need to download\n";
+                   return;
+               } else {
+                   print "mismatch, downloading\n";
+               }
+           }
+
+           my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $url);
+
+           local %ENV;
+           if ($opts->{http_proxy}) {
+               $ENV{http_proxy} = $opts->{http_proxy};
+           }
+           if ($opts->{https_proxy}) {
+               $ENV{https_proxy} = $opts->{https_proxy};
+           }
+
+           my $verify = $opts->{verify_certificates} // 1;
+           if (!$verify) {
+               push @cmd, '--no-check-certificate';
+           }
+
+           if (run_command([[@cmd]]) != 0) {
+               die "download failed: $!\n";
+           }
+
+           if ($algorithm) {
+               print "calculating checksum...\n";
+
+               my $correct = check_file_hash($algorithm, $expected, $tmpdest);
+
+               if ($correct) {
+                   print "checksum verified\n";
+               } else {
+                   die "checksum mismatch\n";
+               }
+           } else {
+               print "no checksum for verification specified\n";
+           }
+
+           if (!rename($tmpdest, $dest)) {
+               die "unable to save file: $!\n";
+           }
+       };
+       my $err = $@;
+
+       unlink $tmpdest;
+
+       if ($err) {
+           print "\n";
+           die $err;
+       }
+
+       print "download finished\n";
+    };
+
+    my $rpcenv = PVE::RPCEnvironment::get();
+    my $user = $rpcenv->get_user();
+
+    (my $filename = $dest) =~ s!.*/([^/]*)$!$1!;
+
+    return $rpcenv->fork_worker('download', $filename, $user, $worker);
+}
+
+sub check_file_hash {
+    my ($algorithm, $expected, $filename) = @_;
+
+    my $algorithm_map = {
+       'md5' => sub { Digest::MD5->new },
+       'sha1' => sub { Digest::SHA->new(1) },
+       'sha224' => sub { Digest::SHA->new(224) },
+       'sha256' => sub { Digest::SHA->new(256) },
+       'sha384' => sub { Digest::SHA->new(384) },
+       'sha512' => sub { Digest::SHA->new(512) },
+    };
+
+    my $digester = $algorithm_map->{$algorithm}->() or die "unknown algorithm '$algorithm'\n";
+
+    open(my $fh, '<', $filename) or die "unable to open '$filename': $!\n";
+    binmode($fh);
+
+    my $digest = $digester->addfile($fh)->hexdigest;
+
+    return lc($digest) eq lc($expected);
+}
+
 1;