From f8f9b75f05eb26d7b06e1fab59d719865aab4c3e Mon Sep 17 00:00:00 2001 From: Richard Kuhnt Date: Thu, 14 Jun 2018 18:30:05 +0200 Subject: [PATCH] Option to skip hash validation and error message improvements (#2260) * Add show_app() function for displaying `app/bucket@version` * Add option to skip hash check on install/update Allows users to install an app when the manifest provides an outdated hash * Show filename instead of robocopy output when moving fails * Add new_issue_msg() for nicer error messages (with link bucket repo) - returns a message with a link to the buckets GitHub repository - clicking on the link opens a new issues with a predefined title * Use new_issue_msg() for errors while moving files - this removes the unreadable robocopy error dumps * Use new_issue_msg() error handling for check_hash() * Show robocopy exit code --- lib/buckets.ps1 | 22 ++++++++++++++++++ lib/core.ps1 | 12 +++++++++- lib/install.ps1 | 47 +++++++++++++++++++++++++++------------ libexec/scoop-info.ps1 | 6 +---- libexec/scoop-install.ps1 | 10 +++++---- libexec/scoop-update.ps1 | 24 +++++++++++--------- 6 files changed, 86 insertions(+), 35 deletions(-) diff --git a/lib/buckets.ps1 b/lib/buckets.ps1 index f5063a6225..f30c4faebd 100644 --- a/lib/buckets.ps1 +++ b/lib/buckets.ps1 @@ -42,3 +42,25 @@ function find_manifest($app, $bucket) { if($manifest) { return $manifest, $bucket } } } + +function new_issue_msg($app, $bucket, $title, $body) { + $app, $manifest, $bucket, $url = locate $app $bucket + $url = known_bucket_repo $bucket + if($manifest -and $null -eq $url -and $null -eq $bucket) { + $url = 'https://github.com/lukesampson/scoop' + } + if(!$url) { + return "Please contact the bucket maintainer!" + } + + $title = [System.Web.HttpUtility]::UrlEncode("$app@$($manifest.version): $title") + $body = [System.Web.HttpUtility]::UrlEncode($body) + $url = $url -replace '^(.*).git$','$1' + $url = "$url/issues/new?title=$title" + if($body) { + $url += "&body=$body" + } + + $msg = "`nPlease create a new issue by using the following link and paste your console output:" + return "$msg`n$url" +} diff --git a/lib/core.ps1 b/lib/core.ps1 index ead4108837..e52339370f 100644 --- a/lib/core.ps1 +++ b/lib/core.ps1 @@ -246,7 +246,7 @@ function movedir($from, $to) { $out = robocopy "$from" "$to" /e /move if($lastexitcode -ge 8) { - throw "Error moving directory: `n$out" + throw "Could not find '$(fname $from)'! (error $lastexitcode)" } } @@ -501,6 +501,16 @@ function parse_app([string] $app) { return $app, $null, $null } +function show_app($app, $bucket, $version) { + if($bucket) { + $app = "$bucket/$app" + } + if($version) { + $app = "$app@$version" + } + return $app +} + function last_scoop_update() { $last_update = (scoop config lastupdate) if(!$last_update) { diff --git a/lib/install.ps1 b/lib/install.ps1 index e88bf41b87..8025ee1aa9 100644 --- a/lib/install.ps1 +++ b/lib/install.ps1 @@ -9,10 +9,9 @@ function nightly_version($date, $quiet = $false) { "nightly-$date_str" } -function install_app($app, $architecture, $global, $suggested, $use_cache = $true) { +function install_app($app, $architecture, $global, $suggested, $use_cache = $true, $check_hash = $true) { $app, $bucket, $null = parse_app $app $app, $manifest, $bucket, $url = locate $app $bucket - $check_hash = $true if(!$manifest) { abort "Couldn't find manifest for '$app'$(if($url) { " at the URL $url" })." @@ -41,7 +40,7 @@ function install_app($app, $architecture, $global, $suggested, $use_cache = $tru $original_dir = $dir # keep reference to real (not linked) directory $persist_dir = persistdir $app $global - $fname = dl_urls $app $version $manifest $architecture $dir $use_cache $check_hash + $fname = dl_urls $app $version $manifest $bucket $architecture $dir $use_cache $check_hash unpack_inno $fname $manifest $dir pre_install $manifest $architecture run_installer $fname $manifest $architecture $dir $global @@ -272,7 +271,7 @@ function dl_progress($read, $total, $url) { [console]::SetCursorPosition($left, $top) } -function dl_urls($app, $version, $manifest, $architecture, $dir, $use_cache = $true, $check_hash = $true) { +function dl_urls($app, $version, $manifest, $bucket, $architecture, $dir, $use_cache = $true, $check_hash = $true) { # we only want to show this warning once if(!$use_cache) { warn "Cache is being ignored." } @@ -312,12 +311,16 @@ function dl_urls($app, $version, $manifest, $architecture, $dir, $use_cache = $t $fname = $data.$url.fname if($check_hash) { - $ok, $err = check_hash "$dir\$fname" $url $manifest $architecture + $manifest_hash = hash_for_url $manifest $url $arch + $ok, $err = check_hash "$dir\$fname" $manifest_hash $(show_app $app $bucket) if(!$ok) { - # rm cached + error $err $cached = cache_path $app $version $url - if(test-path $cached) { Remove-Item -force $cached } - abort $err + if(test-path $cached) { + # rm cached file + Remove-Item -force $cached + } + abort $(new_issue_msg $app $bucket "hash check failed") } } @@ -360,7 +363,13 @@ function dl_urls($app, $version, $manifest, $architecture, $dir, $use_cache = $t } # fails if zip contains long paths (e.g. atom.json) #cp "$dir\_tmp\$extract_dir\*" "$dir\$extract_to" -r -force -ea stop - movedir "$dir\_tmp\$extract_dir" "$dir\$extract_to" + try { + movedir "$dir\_tmp\$extract_dir" "$dir\$extract_to" + } + catch { + error $_ + abort $(new_issue_msg $app $bucket "extract_dir error") + } if(test-path "$dir\_tmp") { # might have been moved by movedir try { @@ -430,11 +439,10 @@ function hash_for_url($manifest, $url, $arch) { } # returns (ok, err) -function check_hash($file, $url, $manifest, $arch) { - $hash = hash_for_url $manifest $url $arch +function check_hash($file, $hash, $app_name) { if(!$hash) { warn "Warning: No hash in manifest. SHA256 is:`n $(compute_hash (fullpath $file) 'sha256')" - return $true + return $true, $null } write-host "Checking hash of $(url_remote_filename $url)... " -nonewline @@ -450,11 +458,21 @@ function check_hash($file, $url, $manifest, $arch) { $actual = compute_hash (fullpath $file) $type + $expected = $expected.ToLower() + $actual = $actual.ToLower() + if($actual -ne $expected) { - return $false, "Hash check failed for '$url'.`nExpected:`n $($expected)`nActual:`n $($actual)" + $msg = "Hash check failed!`n" + $msg += "App: $app_name`n" + $msg += "URL: $url`n" + if($expected -or $actual) { + $msg += "Expected: $expected`n" + $msg += "Actual: $actual" + } + return $false, $msg } write-host "ok." - return $true + return $true, $null } function compute_hash($file, $algname) { @@ -473,6 +491,7 @@ function compute_hash($file, $algname) { if($fs) { $fs.dispose() } if($alg) { $alg.dispose() } } + return '' } function cmd_available($cmd) { diff --git a/libexec/scoop-info.ps1 b/libexec/scoop-info.ps1 index 9cdbb1a6bb..301a9263f6 100644 --- a/libexec/scoop-info.ps1 +++ b/libexec/scoop-info.ps1 @@ -20,11 +20,7 @@ $status = app_status $app $global $manifest, $bucket = find_manifest $app $bucket if (!$manifest) { - if ($bucket) { - abort "Could not find manifest for '$bucket/$app'." - } else { - abort "Could not find manifest for '$app'." - } + abort "Could not find manifest for '$(show_app $app $bucket)'." } $install = install_info $app $status.version $global diff --git a/libexec/scoop-install.ps1 b/libexec/scoop-install.ps1 index ac76d720ff..3d8087e544 100644 --- a/libexec/scoop-install.ps1 +++ b/libexec/scoop-install.ps1 @@ -12,10 +12,11 @@ # When installing from your computer, you can leave the .json extension off if you like. # # Options: -# -a, --arch <32bit|64bit> Use the specified architecture, if the app supports it +# -g, --global Install the app globally # -i, --independent Don't install dependencies automatically # -k, --no-cache Don't use the download cache -# -g, --global Install the app globally +# -s, --skip Skip hash validation (use with caution!) +# -a, --arch <32bit|64bit> Use the specified architecture, if the app supports it . "$psscriptroot\..\lib\core.ps1" . "$psscriptroot\..\lib\manifest.ps1" @@ -46,10 +47,11 @@ function is_installed($app, $global) { return $false } -$opt, $apps, $err = getopt $args 'gika:' 'global', 'independent', 'no-cache', 'arch=' +$opt, $apps, $err = getopt $args 'gfiksa:' 'global', 'force', 'independent', 'no-cache', 'skip', 'arch=' if($err) { "scoop install: $err"; exit 1 } $global = $opt.g -or $opt.global +$check_hash = !($opt.s -or $opt.skip) $independent = $opt.i -or $opt.independent $use_cache = !($opt.k -or $opt.'no-cache') $architecture = default_architecture @@ -117,7 +119,7 @@ $skip | Where-Object { $explicit_apps -contains $_} | ForEach-Object { } $suggested = @{}; -$apps | ForEach-Object { install_app $_ $architecture $global $suggested $use_cache } +$apps | ForEach-Object { install_app $_ $architecture $global $suggested $use_cache $check_hash } show_suggestions $suggested diff --git a/libexec/scoop-update.ps1 b/libexec/scoop-update.ps1 index f848591ef6..65cac29510 100644 --- a/libexec/scoop-update.ps1 +++ b/libexec/scoop-update.ps1 @@ -6,11 +6,13 @@ # You can use '*' in place of to update all apps. # # Options: -# --global, -g Update a globally installed app -# --force, -f Force update even when there isn't a newer version -# --no-cache, -k Don't use the download cache -# --independent, -i Don't install dependencies automatically -# --quiet, -q Hide extraneous messages +# -f, --force Force update even when there isn't a newer version +# -g, --global Update a globally installed app +# -i, --independent Don't install dependencies automatically +# -k, --no-cache Don't use the download cache +# -s, --skip Skip hash validation (use with caution!) +# -q, --quiet Hide extraneous messages + . "$psscriptroot\..\lib\core.ps1" . "$psscriptroot\..\lib\shortcuts.ps1" . "$psscriptroot\..\lib\psmodules.ps1" @@ -26,10 +28,11 @@ reset_aliases -$opt, $apps, $err = getopt $args 'gfkqi' 'global','force', 'no-cache', 'quiet', 'independent' +$opt, $apps, $err = getopt $args 'gfiksq:' 'global', 'force', 'independent', 'no-cache', 'skip', 'quiet' if($err) { "scoop update: $err"; exit 1 } $global = $opt.g -or $opt.global $force = $opt.f -or $opt.force +$check_hash = !($opt.s -or $opt.skip) $use_cache = !($opt.k -or $opt.'no-cache') $quiet = $opt.q -or $opt.quiet $independent = $opt.i -or $opt.independent @@ -104,11 +107,10 @@ function update_scoop() { success 'Scoop was updated successfully!' } -function update($app, $global, $quiet = $false, $independent, $suggested, $use_cache = $true) { +function update($app, $global, $quiet = $false, $independent, $suggested, $use_cache = $true, $check_hash = $true) { $old_version = current_version $app $global $old_manifest = installed_manifest $app $old_version $global $install = install_info $app $old_version $global - $check_hash = $true # re-use architecture, bucket and url from first install $architecture = ensure_architecture $install.architecture @@ -118,7 +120,7 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c if(!$independent) { # check dependencies $deps = @(deps $app $architecture) | Where-Object { !(installed $_) } - $deps | ForEach-Object { install_app $_ $architecture $global $suggested $use_cache } + $deps | ForEach-Object { install_app $_ $architecture $global $suggested $use_cache $check_hash } } $version = latest_version $app $bucket $url @@ -161,7 +163,7 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c # add bucket name it was installed from $app = "$bucket/$app" } - install_app $app $architecture $global $suggested $use_cache + install_app $app $architecture $global $suggested $use_cache $check_hash } if(!$apps) { @@ -210,7 +212,7 @@ if(!$apps) { $suggested = @{}; # # $outdated is a list of ($app, $global) tuples - $outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache } + $outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache $check_hash } } exit 0