|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Check if ALL urls inside manifest have correct hashes. |
| 4 | +.PARAMETER App |
| 5 | + Manifest to be checked. |
| 6 | + Wildcard is supported. |
| 7 | +.PARAMETER Dir |
| 8 | + Where to search for manifest(s). |
| 9 | +.PARAMETER Update |
| 10 | + When there are mismatched hashes, manifest will be updated. |
| 11 | +.PARAMETER ForceUpdate |
| 12 | + Manifest will be updated all the time. Not only when there are mismatched hashes. |
| 13 | +.PARAMETER SkipCorrect |
| 14 | + Manifests without mismatch will not be shown. |
| 15 | +.PARAMETER UseCache |
| 16 | + Downloaded files will not be deleted after script finish. |
| 17 | + Should not be used, because check should be used for downloading actual version of file (as normal user, not finding in some document from vendors, which could be damaged / wrong (Example: Slack@3.3.1 lukesampson/scoop-extras#1192)), not some previously downloaded. |
| 18 | +.EXAMPLE |
| 19 | + PS BUCKETDIR> .\bin\checkhashes.ps1 |
| 20 | + Check all manifests for hash mismatch. |
| 21 | +.EXAMPLE |
| 22 | + PS BUCKETDIR> .\bin\checkhashes.ps1 MANIFEST -Update |
| 23 | + Check MANIFEST and Update if there are some wrong hashes. |
| 24 | +#> |
| 25 | +param( |
| 26 | + [String] $App = '*', |
| 27 | + [ValidateScript( { |
| 28 | + if (!(Test-Path $_ -Type Container)) { |
| 29 | + throw "$_ is not a directory!" |
| 30 | + } else { |
| 31 | + $true |
| 32 | + } |
| 33 | + })] |
| 34 | + [String] $Dir = "$PSScriptRoot\..\bucket", |
| 35 | + [Switch] $Update, |
| 36 | + [Switch] $ForceUpdate, |
| 37 | + [Switch] $SkipCorrect, |
| 38 | + [Alias('k')] |
| 39 | + [Switch] $UseCache |
| 40 | +) |
| 41 | + |
| 42 | +. "$PSScriptRoot\..\lib\core.ps1" |
| 43 | +. "$PSScriptRoot\..\lib\manifest.ps1" |
| 44 | +. "$PSScriptRoot\..\lib\config.ps1" |
| 45 | +. "$PSScriptRoot\..\lib\buckets.ps1" |
| 46 | +. "$PSScriptRoot\..\lib\autoupdate.ps1" |
| 47 | +. "$PSScriptRoot\..\lib\json.ps1" |
| 48 | +. "$PSScriptRoot\..\lib\versions.ps1" |
| 49 | +. "$PSScriptRoot\..\lib\install.ps1" |
| 50 | +. "$PSScriptRoot\..\lib\unix.ps1" |
| 51 | + |
| 52 | +$Dir = Resolve-Path $Dir |
| 53 | +if ($ForceUpdate) { $Update = $true } |
| 54 | +# Cleanup |
| 55 | +if (!$UseCache) { scoop cache rm '*HASH_CHECK*' } |
| 56 | + |
| 57 | +function err ([String] $name, [String[]] $message) { |
| 58 | + Write-Host "$name`: " -ForegroundColor Red -NoNewline |
| 59 | + Write-Host ($message -join "`r`n") -ForegroundColor Red |
| 60 | +} |
| 61 | + |
| 62 | +$MANIFESTS = @() |
| 63 | +foreach ($single in Get-ChildItem $Dir "$App.json") { |
| 64 | + $name = (strip_ext $single.Name) |
| 65 | + $manifest = parse_json "$Dir\$($single.Name)" |
| 66 | + |
| 67 | + # Skip nighly manifests, since their hash validation is skipped |
| 68 | + if ($manifest.version -eq 'nightly') { continue } |
| 69 | + |
| 70 | + $urls = @() |
| 71 | + $hashes = @() |
| 72 | + |
| 73 | + if ($manifest.architecture) { |
| 74 | + # First handle 64bit |
| 75 | + url $manifest '64bit' | ForEach-Object { $urls += $_ } |
| 76 | + hash $manifest '64bit' | ForEach-Object { $hashes += $_ } |
| 77 | + url $manifest '32bit' | ForEach-Object { $urls += $_ } |
| 78 | + hash $manifest '32bit' | ForEach-Object { $hashes += $_ } |
| 79 | + } elseif ($manifest.url) { |
| 80 | + $manifest.url | ForEach-Object { $urls += $_ } |
| 81 | + $manifest.hash | ForEach-Object { $hashes += $_ } |
| 82 | + } else { |
| 83 | + err $name 'Manifest does not contain URL property.' |
| 84 | + continue |
| 85 | + } |
| 86 | + |
| 87 | + # Number of URLS and Hashes is different |
| 88 | + if ($urls.Length -ne $hashes.Length) { |
| 89 | + err $name 'URLS and hashes count mismatch.' |
| 90 | + continue |
| 91 | + } |
| 92 | + |
| 93 | + $MANIFESTS += @{ |
| 94 | + app = $name |
| 95 | + manifest = $manifest |
| 96 | + urls = $urls |
| 97 | + hashes = $hashes |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +# clear any existing events |
| 102 | +Get-Event | ForEach-Object { Remove-Event $_.SourceIdentifier } |
| 103 | + |
| 104 | +foreach ($current in $MANIFESTS) { |
| 105 | + $count = 0 |
| 106 | + # Array of indexes mismatched hashes. |
| 107 | + $mismatched = @() |
| 108 | + # Array of computed hashes |
| 109 | + $actuals = @() |
| 110 | + |
| 111 | + $current.urls | ForEach-Object { |
| 112 | + $algorithm, $expected = get_hash $current.hashes[$count] |
| 113 | + $version = 'HASH_CHECK' |
| 114 | + $tmp = $expected_hash -split ':' |
| 115 | + |
| 116 | + dl_with_cache $current.app $version $_ $null $null -use_cache:$UseCache |
| 117 | + |
| 118 | + $to_check = fullpath (cache_path $current.app $version $_) |
| 119 | + $actual_hash = compute_hash $to_check $algorithm |
| 120 | + |
| 121 | + # Append type of algorithm to both expected and actual if it's not sha256 |
| 122 | + if ($algorithm -ne 'sha256') { |
| 123 | + $actual_hash = "$algorithm`:$actual_hash" |
| 124 | + $expected = "$algorithm`:$expected" |
| 125 | + } |
| 126 | + |
| 127 | + $actuals += $actual_hash |
| 128 | + if ($actual_hash -ne $expected) { |
| 129 | + $mismatched += $count |
| 130 | + } |
| 131 | + $count++ |
| 132 | + } |
| 133 | + |
| 134 | + if ($mismatched.Length -eq 0 ) { |
| 135 | + if (!$SkipCorrect) { |
| 136 | + Write-Host "$($current.app): " -NoNewline |
| 137 | + Write-Host 'OK' -ForegroundColor Green |
| 138 | + } |
| 139 | + } else { |
| 140 | + Write-Host "$($current.app): " -NoNewline |
| 141 | + Write-Host 'Mismatch found ' -ForegroundColor Red |
| 142 | + $mismatched | ForEach-Object { |
| 143 | + $file = fullpath (cache_path $current.app $version $current.urls[$_]) |
| 144 | + Write-Host "`tURL:`t`t$($current.urls[$_])" |
| 145 | + if (Test-Path $file) { |
| 146 | + Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())" |
| 147 | + } |
| 148 | + Write-Host "`tExpected:`t$($current.urls[$_])" -ForegroundColor Green |
| 149 | + Write-Host "`tActual:`t`t$($actuals[$_])" -ForegroundColor Red |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + if ($Update) { |
| 154 | + if ($current.manifest.url -and $current.manifest.hash) { |
| 155 | + $current.manifest.hash = $actuals |
| 156 | + } else { |
| 157 | + $platforms = ($current.manifest.architecture | Get-Member -MemberType NoteProperty).Name |
| 158 | + # Defaults to zero, don't know, which architecture is available |
| 159 | + $64bit_count = 0 |
| 160 | + $32bit_count = 0 |
| 161 | + |
| 162 | + if ($platforms.Contains('64bit')) { |
| 163 | + $64bit_count = $current.manifest.architecture.'64bit'.hash.Count |
| 164 | + # 64bit is get, donwloaded and added first |
| 165 | + $current.manifest.architecture.'64bit'.hash = $actuals[0..($64bit_count - 1)] |
| 166 | + } |
| 167 | + if ($platforms.Contains('32bit')) { |
| 168 | + $32bit_count = $current.manifest.architecture.'32bit'.hash.Count |
| 169 | + $max = $64bit_count + $32bit_count - 1 # Edge case if manifest contains 64bit and 32bit. |
| 170 | + $current.manifest.architecture.'32bit'.hash = $actuals[($64bit_count)..$max] |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + Write-Host "Writing updated $($current.app) manifest" -ForegroundColor DarkGreen |
| 175 | + |
| 176 | + $current.manifest = $current.manifest | ConvertToPrettyJson |
| 177 | + $path = Resolve-Path "$Dir\$($current.app).json" |
| 178 | + [System.IO.File]::WriteAllLines($path, $current.manifest) |
| 179 | + } |
| 180 | +} |
0 commit comments