Skip to content
Closed
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
35 changes: 34 additions & 1 deletion ee/maintained-apps/ingesters/winget/ingester.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"log/slog"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -418,7 +419,7 @@ func (i *wingetIngester) ingestOne(ctx context.Context, input inputApp) (*mainta

out.Name = input.Name
out.Slug = input.Slug
out.InstallerURL = selectedInstaller.InstallerURL
out.InstallerURL = directDownloadURL(selectedInstaller.InstallerURL)
out.UniqueIdentifier = input.UniqueIdentifier
out.DefaultCategories = input.DefaultCategories
out.SHA256 = "no_check"
Expand Down Expand Up @@ -496,6 +497,38 @@ func firstDisplayVersion(entries []appsAndFeaturesEntries) string {
return ""
}

// directDownloadURL rewrites a SourceForge project browse URL to the
// downloads.sourceforge.net form. The browse URL is a web page that redirects
// through a mirror, and that hop intermittently serves an HTML interstitial with
// HTTP 200 instead of the installer, which then fails the SHA256 check. The
// downloads host serves the file directly. Non-SourceForge URLs are returned
// unchanged.
func directDownloadURL(installerURL string) string {
u, err := url.Parse(installerURL)
if err != nil || (u.Host != "sourceforge.net" && u.Host != "www.sourceforge.net") {
return installerURL
}

// Expected shape: /projects/<project>/files/<path...>[/download]
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) < 4 || parts[0] != "projects" || parts[2] != "files" {
return installerURL
}
project, filePath := parts[1], parts[3:]
if filePath[len(filePath)-1] == "download" {
filePath = filePath[:len(filePath)-1]
}
if len(filePath) == 0 {
return installerURL
}

return (&url.URL{
Scheme: u.Scheme,
Host: "downloads.sourceforge.net",
Path: "/project/" + project + "/" + strings.Join(filePath, "/"),
}).String()
}

func setUpExistsQuery(fuzzy fuzzyMatch, name string, publisher string) maintained_apps.FMAQueries {
// TODO - consider UpgradeCode here?
return maintained_apps.FMAQueries{
Expand Down
38 changes: 38 additions & 0 deletions ee/maintained-apps/ingesters/winget/ingester_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,3 +660,41 @@ func TestIngestOneVersionWalk(t *testing.T) {
require.True(t, isTransientGitHubError(err), "the caller must recognize this error and skip the app")
})
}

func TestDirectDownloadURL(t *testing.T) {
for _, tc := range []struct {
name string
in string
want string
}{
{
name: "sourceforge browse URL",
in: "https://sourceforge.net/projects/crystaldiskmark/files/9.0.3/CrystalDiskMark9_0_3.exe",
want: "https://downloads.sourceforge.net/project/crystaldiskmark/9.0.3/CrystalDiskMark9_0_3.exe",
},
{
name: "sourceforge browse URL with /download suffix",
in: "https://sourceforge.net/projects/winscp/files/WinSCP/6.5.6/WinSCP-6.5.6-Setup.exe/download",
want: "https://downloads.sourceforge.net/project/winscp/WinSCP/6.5.6/WinSCP-6.5.6-Setup.exe",
},
{
name: "already a direct download URL",
in: "https://downloads.sourceforge.net/project/winscp/WinSCP/6.5.6/WinSCP-6.5.6-Setup.exe",
want: "https://downloads.sourceforge.net/project/winscp/WinSCP/6.5.6/WinSCP-6.5.6-Setup.exe",
},
{
name: "non-sourceforge URL is untouched",
in: "https://github.com/owner/repo/releases/download/v1.0/app.exe",
want: "https://github.com/owner/repo/releases/download/v1.0/app.exe",
},
{
name: "sourceforge URL with an unexpected shape is untouched",
in: "https://sourceforge.net/projects/someproject/",
want: "https://sourceforge.net/projects/someproject/",
},
} {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, directDownloadURL(tc.in))
})
}
}
13 changes: 13 additions & 0 deletions ee/maintained-apps/inputs/winget/crystaldiskmark.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "CrystalDiskMark",
"slug": "crystaldiskmark/windows",
"package_identifier": "CrystalDewWorld.CrystalDiskMark",
"unique_identifier": "CrystalDiskMark",
"exists_query": "SELECT 1 FROM programs WHERE name LIKE 'CrystalDiskMark %' AND name NOT LIKE '%Edition%';",
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/crystaldiskmark_install.ps1",
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/crystaldiskmark_uninstall.ps1",
"installer_arch": "x64",
"installer_type": "exe",
"installer_scope": "machine",
"default_categories": ["Developer tools"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts

$exeFilePath = "${env:INSTALLER_PATH}"

$installTimeoutSeconds = 420
$registrationTimeoutSeconds = 120

$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

function Get-CrystalDiskMarkEntry {
Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
Where-Object { $_.DisplayName -like "CrystalDiskMark*" -and $_.DisplayName -notlike "*Edition*" } |
Select-Object -First 1
}

try {

# -Wait also waits on descendants, so wait on the installer process alone.
$process = Start-Process -FilePath "$exeFilePath" `
-ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" `
-PassThru
# Keeps .ExitCode readable after the process ends.
$null = $process.Handle

$killed = $false
if (-not $process.WaitForExit($installTimeoutSeconds * 1000)) {
Write-Host "Installer process did not exit within ${installTimeoutSeconds}s, stopping it."
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
$null = $process.WaitForExit(30 * 1000)
$killed = $true
}

$exitCode = $null
if ($process.HasExited) {
$exitCode = $process.ExitCode
Write-Host "Install exit code: $exitCode"
}

# The installer can return before the ARP entry is written.
$elapsed = 0
while (-not (Get-CrystalDiskMarkEntry) -and ($elapsed -lt $registrationTimeoutSeconds)) {
Start-Sleep -Seconds 5
$elapsed += 5
Write-Host "Waiting for CrystalDiskMark to register... ($elapsed seconds)"
}

$entry = Get-CrystalDiskMarkEntry
if (-not $entry) {
Write-Host "CrystalDiskMark did not register in Add/Remove Programs."
Exit 1
}
Write-Host "Registered '$($entry.DisplayName)' by '$($entry.Publisher)', version $($entry.DisplayVersion)."

# Registration above is the success signal; a killed process's code means nothing.
if ($killed -or $null -eq $exitCode) { Exit 0 }

# 3010 (reboot required) and 1641 (reboot initiated) are successful installs.
if ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }

Exit $exitCode

} catch {
Write-Host "Error: $_"
Exit 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
$softwareName = "CrystalDiskMark"
$softwareNameLike = "$softwareName*"
$uninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"

$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
$exitCode = 0

try {
[array]$uninstallKeys = Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }

$foundUninstaller = $false
foreach ($key in $uninstallKeys) {
# The Aoi and Shizuku editions are separate products that share the prefix.
if ($key.DisplayName -like $softwareNameLike -and $key.DisplayName -notlike "*Edition*") {
$foundUninstaller = $true
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
}
Write-Host "Uninstall command: $uninstallCommand"; Write-Host "Uninstall args: $uninstallArgs"
$processOptions = @{ FilePath = $uninstallCommand; PassThru = $true; Wait = $true }
if ($uninstallArgs -ne '') { $processOptions.ArgumentList = $uninstallArgs }
$process = Start-Process @processOptions
$exitCode = $process.ExitCode; Write-Host "Uninstall exit code: $exitCode"; break
}
}
if (-not $foundUninstaller) { Write-Host "Uninstall entry not found for '$softwareName'."; Exit 0 }
} catch { Write-Host "Error: $_"; Exit 1 }

Exit $exitCode
7 changes: 7 additions & 0 deletions ee/maintained-apps/outputs/apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -2087,6 +2087,13 @@
"unique_identifier": "Cryptomator",
"description": "Cryptomator is a multi-platform client-side cloud file encryption tool."
},
{
"name": "CrystalDiskMark",
"slug": "crystaldiskmark/windows",
"platform": "windows",
"unique_identifier": "CrystalDiskMark",
"description": "CrystalDiskMark is a disk benchmark tool that measures sequential and random read and write speeds."
},
{
"name": "Crystalfetch",
"slug": "crystalfetch/darwin",
Expand Down
22 changes: 22 additions & 0 deletions ee/maintained-apps/outputs/crystaldiskmark/windows.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"versions": [
{
"version": "9.0.3",
"queries": {
"exists": "SELECT 1 FROM programs WHERE name LIKE 'CrystalDiskMark %' AND name NOT LIKE '%Edition%';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'CrystalDiskMark %' AND name NOT LIKE '%Edition%' AND version_compare(version, '9.0.3') < 0);"
},
"installer_url": "https://downloads.sourceforge.net/project/crystaldiskmark/9.0.3/CrystalDiskMark9_0_3.exe",
"install_script_ref": "0842a653",
"uninstall_script_ref": "d7453dc1",
"sha256": "1a255154e116a533f86535bc362f101e32d6171604cde9b6b554335856917e5e",
"default_categories": [
"Developer tools"
]
}
],
"refs": {
"0842a653": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\n$installTimeoutSeconds = 420\n$registrationTimeoutSeconds = 120\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\nfunction Get-CrystalDiskMarkEntry {\n Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object { $_.DisplayName -like \"CrystalDiskMark*\" -and $_.DisplayName -notlike \"*Edition*\" } |\n Select-Object -First 1\n}\n\ntry {\n\n# -Wait also waits on descendants, so wait on the installer process alone.\n$process = Start-Process -FilePath \"$exeFilePath\" `\n -ArgumentList \"/VERYSILENT /SUPPRESSMSGBOXES /NORESTART\" `\n -PassThru\n# Keeps .ExitCode readable after the process ends.\n$null = $process.Handle\n\n$killed = $false\nif (-not $process.WaitForExit($installTimeoutSeconds * 1000)) {\n Write-Host \"Installer process did not exit within ${installTimeoutSeconds}s, stopping it.\"\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n $null = $process.WaitForExit(30 * 1000)\n $killed = $true\n}\n\n$exitCode = $null\nif ($process.HasExited) {\n $exitCode = $process.ExitCode\n Write-Host \"Install exit code: $exitCode\"\n}\n\n# The installer can return before the ARP entry is written.\n$elapsed = 0\nwhile (-not (Get-CrystalDiskMarkEntry) -and ($elapsed -lt $registrationTimeoutSeconds)) {\n Start-Sleep -Seconds 5\n $elapsed += 5\n Write-Host \"Waiting for CrystalDiskMark to register... ($elapsed seconds)\"\n}\n\n$entry = Get-CrystalDiskMarkEntry\nif (-not $entry) {\n Write-Host \"CrystalDiskMark did not register in Add/Remove Programs.\"\n Exit 1\n}\nWrite-Host \"Registered '$($entry.DisplayName)' by '$($entry.Publisher)', version $($entry.DisplayVersion).\"\n\n# Registration above is the success signal; a killed process's code means nothing.\nif ($killed -or $null -eq $exitCode) { Exit 0 }\n\n# 3010 (reboot required) and 1641 (reboot initiated) are successful installs.\nif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }\n\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
"d7453dc1": "$softwareName = \"CrystalDiskMark\"\n$softwareNameLike = \"$softwareName*\"\n$uninstallArgs = \"/VERYSILENT /SUPPRESSMSGBOXES /NORESTART\"\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$exitCode = 0\n\ntry {\n [array]$uninstallKeys = Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }\n\n $foundUninstaller = $false\n foreach ($key in $uninstallKeys) {\n # The Aoi and Shizuku editions are separate products that share the prefix.\n if ($key.DisplayName -like $softwareNameLike -and $key.DisplayName -notlike \"*Edition*\") {\n $foundUninstaller = $true\n $uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }\n if ($uninstallCommand -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = \"$($Matches[2]) $uninstallArgs\".Trim() }\n } elseif ($uninstallCommand -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = \"$($Matches[2]) $uninstallArgs\".Trim() }\n } elseif ($uninstallCommand -match '^\\s*(\\S+)\\s*(.*)$') {\n $uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = \"$($Matches[2]) $uninstallArgs\".Trim() }\n }\n Write-Host \"Uninstall command: $uninstallCommand\"; Write-Host \"Uninstall args: $uninstallArgs\"\n $processOptions = @{ FilePath = $uninstallCommand; PassThru = $true; Wait = $true }\n if ($uninstallArgs -ne '') { $processOptions.ArgumentList = $uninstallArgs }\n $process = Start-Process @processOptions\n $exitCode = $process.ExitCode; Write-Host \"Uninstall exit code: $exitCode\"; break\n }\n }\n if (-not $foundUninstaller) { Write-Host \"Uninstall entry not found for '$softwareName'.\"; Exit 0 }\n} catch { Write-Host \"Error: $_\"; Exit 1 }\n\nExit $exitCode\n"
}
}
2 changes: 1 addition & 1 deletion ee/maintained-apps/outputs/winscp/windows.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"exists": "SELECT 1 FROM programs WHERE name LIKE 'WinSCP %' AND publisher = 'Martin Prikryl';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'WinSCP %' AND publisher = 'Martin Prikryl' AND version_compare(version, '6.5.6') < 0);"
},
"installer_url": "https://sourceforge.net/projects/winscp/files/WinSCP/6.5.6/WinSCP-6.5.6-Setup.exe/download",
"installer_url": "https://downloads.sourceforge.net/project/winscp/WinSCP/6.5.6/WinSCP-6.5.6-Setup.exe",
"install_script_ref": "d07f6d6d",
"uninstall_script_ref": "24fcb8df",
"sha256": "4488c493bafca6af4e7ae54ed39cb71479e65dc192c4d1a471647bf9cb9d6db0",
Expand Down

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions frontend/pages/SoftwarePage/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ import CriblEdge from "./CriblEdge";
import Crisisgo from "./Crisisgo";
import Crossover from "./Crossover";
import Cryptomator from "./Cryptomator";
import Crystaldiskmark from "./Crystaldiskmark";
import Crystalfetch from "./Crystalfetch";
import CubeBrowser from "./CubeBrowser";
import Cursor from "./Cursor";
Expand Down Expand Up @@ -1390,6 +1391,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
crisisgo: Crisisgo,
crossover: Crossover,
cryptomator: Cryptomator,
crystaldiskmark: Crystaldiskmark,
crystalfetch: Crystalfetch,
"cube browser": CubeBrowser,
cursor: Cursor,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading