Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- **scoop-uninstall**: Allow access to `$bucket` in uninstall scripts ([#6380](https://github.com/ScoopInstaller/Scoop/issues/6380))
- **install:** Add separator at the end of notes, highlight suggestions ([#6418](https://github.com/ScoopInstaller/Scoop/issues/6418))
- **virustotal:** Refactor into lib and integrate pre-download checks for install and update ([#6525](https://github.com/ScoopInstaller/Scoop/issues/6525))


### Bug Fixes

Expand Down
1 change: 1 addition & 0 deletions bin/checkhashes.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ param(

. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\helper\hash.ps1" # 'get_hash'
. "$PSScriptRoot\..\lib\buckets.ps1"
. "$PSScriptRoot\..\lib\autoupdate.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
Expand Down
2 changes: 2 additions & 0 deletions lib/autoupdate.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Must included with 'json.ps1'

. "$PSScriptRoot\..\lib\helper\hash.ps1" # 'get_hash'

function format_hash([String] $hash) {
$hash = $hash.toLower()

Expand Down
67 changes: 24 additions & 43 deletions lib/download.ps1
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Description: Functions for downloading files

. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\helper\hash.ps1" # 'hash_for_url'
. "$PSScriptRoot\..\lib\helper\file-information.ps1" # 'Get-RemoteFileSize'
. "$PSScriptRoot\..\lib\virustotal.ps1"

## Meta downloader

function Invoke-ScoopDownload ($app, $version, $manifest, $bucket, $architecture, $dir, $use_cache = $true, $check_hash = $true) {
function Invoke-ScoopDownload ($app, $version, $manifest, $bucket, $architecture, $dir, $use_cache = $true, $check_hash = $true, $check_virustotal = $false) {
# we only want to show this warning once
if (!$use_cache) { warn 'Cache is being ignored.' }

Expand All @@ -14,8 +19,14 @@ function Invoke-ScoopDownload ($app, $version, $manifest, $bucket, $architecture

# download first
if (Test-Aria2Enabled) {
Invoke-CachedAria2Download $app $version $manifest $architecture $dir $cookies $use_cache $check_hash
Invoke-CachedAria2Download $app $version $manifest $architecture $dir $cookies $use_cache $check_hash $check_virustotal
} else {
$urls = if ($check_virustotal) {
Test-UrlsWithVirusTotal $app $urls $manifest $architecture
} else {
$urls
}

foreach ($url in $urls) {
$fname = url_filename $url

Expand Down Expand Up @@ -329,9 +340,14 @@ function get_filename_from_metalink($file) {
return $filename
}

function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $dir, $cookies = $null, $use_cache = $true, $check_hash = $true) {
function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $dir, $cookies = $null, $use_cache = $true, $check_hash = $true, $check_virustotal = $false) {
$data = @{}
$urls = @(script:url $manifest $architecture)
$urls = if ($check_virustotal) {
Test-UrlsWithVirusTotal $app $urls $manifest $architecture
} else {
$urls
}

# aria2 input file
$urlstxt = Join-Path $cachedir "$app.txt"
Expand Down Expand Up @@ -457,9 +473,9 @@ function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $
warn "Download failed! (Error $lastexitcode) $(aria_exit_code $lastexitcode)"
warn $urlstxt_content
warn $aria2
warn $(new_issue_msg $app $bucket "download via aria2 failed")
warn $(new_issue_msg $app $bucket 'download via aria2 failed')

Write-Host "Fallback to default downloader ..."
Write-Host 'Fallback to default downloader ...'

try {
foreach ($url in $urls) {
Expand Down Expand Up @@ -666,12 +682,6 @@ function get_magic_bytes_pretty($file, $glue = ' ') {
return (get_magic_bytes $file | ForEach-Object { $_.ToString('x2') }) -join $glue
}

Function Get-RemoteFileSize ($Uri) {
$response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
if (!$response.Headers.StatusCode) {
$response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
}
}

function ftp_file_size($url) {
$request = [net.ftpwebrequest]::create($url)
Expand All @@ -689,33 +699,18 @@ function url_remote_filename($url) {
# this function extracts the original filename from the URL.
$uri = (New-Object URI $url)
$basename = Split-Path $uri.PathAndQuery -Leaf
If ($basename -match '.*[?=]+([\w._-]+)') {
if ($basename -match '.*[?=]+([\w._-]+)') {
$basename = $matches[1]
}
If (($basename -notlike '*.*') -or ($basename -match '^[v.\d]+$')) {
if (($basename -notlike '*.*') -or ($basename -match '^[v.\d]+$')) {
$basename = Split-Path $uri.AbsolutePath -Leaf
}
If (($basename -notlike '*.*') -and ($uri.Fragment -ne '')) {
if (($basename -notlike '*.*') -and ($uri.Fragment -ne '')) {
$basename = $uri.Fragment.Trim('/', '#')
}
return $basename
}

### Hash-related functions

function hash_for_url($manifest, $url, $arch) {
$hashes = @(hash $manifest $arch) | Where-Object { $_ -ne $null }

if ($hashes.length -eq 0) { return $null }

$urls = @(script:url $manifest $arch)

$index = [array]::IndexOf($urls, $url)
if ($index -eq -1) { abort "Couldn't find hash in manifest for '$url'." }

@($hashes)[$index]
}

function check_hash($file, $hash, $app_name) {
# returns (ok, err)
if (!$hash) {
Expand Down Expand Up @@ -751,19 +746,5 @@ function check_hash($file, $hash, $app_name) {
return $true, $null
}

function get_hash([String] $multihash) {
$type, $hash = $multihash -split ':'
if (!$hash) {
# no type specified, assume sha256
$type, $hash = 'sha256', $multihash
}

if (@('md5', 'sha1', 'sha256', 'sha512') -notcontains $type) {
return $null, "Hash type '$type' isn't supported."
}

return $type, $hash.ToLower()
}

# Setup proxy globally
setup_proxy
8 changes: 8 additions & 0 deletions lib/helper/file-information.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Remote file information

function Get-RemoteFileSize ($Uri) {
$response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
if (!$response.Headers.StatusCode) {
$response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
}
}
Comment on lines +3 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the StatusCode check logic.

The condition on line 5 checks !$response.Headers.StatusCode, but StatusCode is not a header—it's a property of the response object itself ($response.StatusCode). This check will likely always evaluate to true (since Headers.StatusCode doesn't exist), causing the function to return Content-Length unconditionally.

Apply this diff to fix the logic:

 function Get-RemoteFileSize ($Uri) {
     $response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
-    if (!$response.Headers.StatusCode) {
+    if ($response.StatusCode -eq 200) {
         $response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function Get-RemoteFileSize ($Uri) {
$response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
if (!$response.Headers.StatusCode) {
$response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
}
}
function Get-RemoteFileSize ($Uri) {
$response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
if ($response.StatusCode -eq 200) {
$response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
}
}
🤖 Prompt for AI Agents
In lib/helper/file-information.ps1 around lines 3 to 8, the condition
incorrectly checks $response.Headers.StatusCode (which doesn't exist) causing
the Content-Length to be returned unconditionally; update the check to inspect
the response.StatusCode property instead (for example ensure
$response.StatusCode -ge 200 -and $response.StatusCode -lt 300 or -eq 200) and
only read and cast $response.Headers.'Content-Length' to int when the status
code indicates success.

28 changes: 28 additions & 0 deletions lib/helper/hash.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
### Hash-related functions

function hash_for_url($manifest, $url, $arch) {
$hashes = @(hash $manifest $arch) | Where-Object { $_ -ne $null }

if ($hashes.length -eq 0) { return $null }

$urls = @(script:url $manifest $arch)

$index = [array]::IndexOf($urls, $url)
if ($index -eq -1) { abort "Couldn't find hash in manifest for '$url'." }

@($hashes)[$index]
}

function get_hash([String] $multihash) {
$type, $hash = $multihash -split ':'
if (!$hash) {
# no type specified, assume sha256
$type, $hash = 'sha256', $multihash
}

if (@('md5', 'sha1', 'sha256', 'sha512') -notcontains $type) {
return $null, "Hash type '$type' isn't supported."
}

return $type, $hash.ToLower()
}
5 changes: 3 additions & 2 deletions lib/install.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@

function nightly_version($quiet = $false) {
if (!$quiet) {
warn "This is a nightly version. Downloaded files won't be verified."
}
return "nightly-$(Get-Date -Format 'yyyyMMdd')"
}

function install_app($app, $architecture, $global, $suggested, $use_cache = $true, $check_hash = $true) {
function install_app($app, $architecture, $global, $suggested, $use_cache = $true, $check_hash = $true, $check_virustotal = $false) {
$app, $manifest, $bucket, $url = Get-Manifest $app

if (!$manifest) {
Expand Down Expand Up @@ -49,7 +50,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 = Invoke-ScoopDownload $app $version $manifest $bucket $architecture $dir $use_cache $check_hash
$fname = Invoke-ScoopDownload $app $version $manifest $bucket $architecture $dir $use_cache $check_hash $check_virustotal
Invoke-Extraction -Path $dir -Name $fname -Manifest $manifest -ProcessorArchitecture $architecture
Invoke-HookScript -HookType 'pre_install' -Manifest $manifest -ProcessorArchitecture $architecture

Expand Down
Loading