Skip to content

Add version variables to autoupdate.hash.regex#2996

Merged
r15ch13 merged 3 commits intoScoopInstaller:masterfrom
niheaven:add-version-variables-to-regex-in-hash-extract
Jan 17, 2019
Merged

Add version variables to autoupdate.hash.regex#2996
r15ch13 merged 3 commits intoScoopInstaller:masterfrom
niheaven:add-version-variables-to-regex-in-hash-extract

Conversation

@niheaven
Copy link
Copy Markdown
Member

Add version variables to autoupdate.hash.regex for some hash extract situations.

The idea is from Atom's nupkg hash extraction. Atom's releases page provides nupkg named atom-1.34.0-full.nupkg (for i686) and atom-x64-1.34.0-full.nupkg (for x64_86), and it also provides RELEASES and RELEASES-x64 files that give sha1 hash of nupkg files. Unfortunately in RELEASES-x64 the nupkg file name is atom-1.34.0-full.nupkg without -x64.

I haven't found some method to write a regex to extract the hash without $version, but get_hash_for_app() only support `$basename$ in regex. So I add the code, and it works for the following hash extract:

"url": "https://github.com/atom/atom/releases/download/v$version/atom-x64-$version-full.nupkg#dl.7z",
"hash": {
    "url": "$baseurl/RELEASES-x64",
    "regex": "([a-fA-F0-9]+)\\s+?(?:atom-$version-full.nupkg)"
}

@r15ch13
Copy link
Copy Markdown
Member

r15ch13 commented Jan 16, 2019

How about adding a $regexEscape parameter to substitute:

function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) {
    if ($entity -is [Array]) {
        return $entity | ForEach-Object { substitute $_ $params $regexEscape}
    } elseif ($entity -is [String]) {
        $params.GetEnumerator() | ForEach-Object {
            if($regexEscape -eq $false -or $null -eq $_.Value) {
                $entity = $entity.Replace($_.Name, $_.Value)
            } else {
                $entity = $entity.Replace($_.Name, [Regex]::Escape($_.Value))
            }
        }
        return $entity
    }
}

And instead of using $basename-Parameter for find_hash_in_textfile() we pass down the $substitutions. (requires adding the $basename to the hashtable)

diff --git a/lib/autoupdate.ps1 b/lib/autoupdate.ps1
index 5b77e759..b14e2d4e 100644
--- a/lib/autoupdate.ps1
+++ b/lib/autoupdate.ps1
@@ -29,7 +29,7 @@ function find_hash_in_rdf([String] $url, [String] $filename) {
     return format_hash $digest.sha256
 }
 
-function find_hash_in_textfile([String] $url, [String] $basename, [String] $regex) {
+function find_hash_in_textfile([String] $url, [Hashtable] $substitutions, [String] $regex = '^([a-fA-F0-9]+)$') {
     $hashfile = $null
 
     try {
@@ -43,15 +43,8 @@ function find_hash_in_textfile([String] $url, [String] $basename, [String] $rege
         return
     }
 
-    # find single line hash in $hashfile (will be overridden by $regex)
-    if ($regex.Length -eq 0) {
-        $normalRegex = "^([a-fA-F0-9]+)$"
-    } else {
-        $normalRegex = $regex
-    }
-
-    $normalRegex = substitute $normalRegex @{'$basename' = [regex]::Escape($basename)}
-    if ($hashfile -match $normalRegex) {
+    $regex = substitute $regex $substitutions $true
+    if ($hashfile -match $regex) {
         $hash = $matches[1] -replace ' ',''
     }
 
@@ -70,7 +63,7 @@ function find_hash_in_textfile([String] $url, [String] $basename, [String] $rege
     # find hash with filename in $hashfile (will be overridden by $regex)
     if ($hash.Length -eq 0 -and $regex.Length -eq 0) {
         $filenameRegex = "([a-fA-F0-9]{32,128})[\x20\t]+.*`$basename(?:[\x20\t]+\d+)?"
-        $filenameRegex = substitute $filenameRegex @{'$basename' = [regex]::Escape($basename)}
+        $filenameRegex = substitute $filenameRegex $substitutions $true
         if ($hashfile -match $filenameRegex) {
             $hash = $matches[1]
         }
@@ -142,12 +135,12 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
     $hashmode = $config.mode
     $basename = url_remote_filename($url)
 
-    $hashfile_url = substitute $config.url @{
-        '$url' = (strip_fragment $url);
-        '$baseurl' = (strip_filename (strip_fragment $url)).TrimEnd('/')
-        '$basename' = $basename
-    }
-    $hashfile_url = substitute $hashfile_url $substitutions
+    $substitutions = $substitutions.Clone()
+    $substitutions.Add('$url', (strip_fragment $url))
+    $substitutions.Add('$baseurl', (strip_filename (strip_fragment $url)).TrimEnd('/'))
+    $substitutions.Add('$basename', $basename)
+
+    $hashfile_url = substitute $config.url $substitutions
     if($hashfile_url) {
         write-host -f DarkYellow 'Searching hash for ' -NoNewline
         write-host -f Green $(url_remote_filename $url) -NoNewline
@@ -176,22 +169,16 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
         $regex = $config.regex
     }
 
-    $substitutions.GetEnumerator() | ForEach-Object {
-        if ($_.Value) {
-            $regex = $regex.Replace($_.Name, [regex]::Escape($_.Value))
-        }
-    }
-
     if (!$hashfile_url -and $url -match "(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*)") {
         $hashmode = 'sourceforge'
         # change the URL because downloads.sourceforge.net doesn't have checksums
         $hashfile_url = (strip_filename (strip_fragment "https://sourceforge.net/projects/$($matches['project'])/files/$($matches['file'])")).TrimEnd('/')
-        $hash = find_hash_in_textfile $hashfile_url $basename '"$basename":.*?"sha1":\s"([a-fA-F0-9]{40})"'
+        $hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s"([a-fA-F0-9]{40})"'
     }
 
     switch ($hashmode) {
         'extract' {
-            $hash = find_hash_in_textfile $hashfile_url $basename $regex
+            $hash = find_hash_in_textfile $hashfile_url $substitutions $regex
         }
         'json' {
             $hash = find_hash_in_json $hashfile_url $basename $jsonpath
@@ -202,7 +189,7 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
         'metalink' {
             $hash = find_hash_in_headers $url
             if(!$hash) {
-                $hash = find_hash_in_textfile "$url.meta4"
+                $hash = find_hash_in_textfile "$url.meta4" $substitutions
             }
         }
     }
diff --git a/lib/core.ps1 b/lib/core.ps1
index 2205ce08..25980198 100644
--- a/lib/core.ps1
+++ b/lib/core.ps1
@@ -612,12 +612,16 @@ function is_scoop_outdated() {
     return $last_update.AddHours(3) -lt $now.ToLocalTime()
 }
 
-function substitute($entity, [Hashtable] $params) {
+function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) {
     if ($entity -is [Array]) {
-        return $entity | ForEach-Object { substitute $_ $params }
+        return $entity | ForEach-Object { substitute $_ $params $regexEscape}
     } elseif ($entity -is [String]) {
         $params.GetEnumerator() | ForEach-Object {
-            $entity = $entity.Replace($_.Name, $_.Value)
+            if($regexEscape -eq $false -or $null -eq $_.Value) {
+                $entity = $entity.Replace($_.Name, $_.Value)
+            } else {
+                $entity = $entity.Replace($_.Name, [Regex]::Escape($_.Value))
+            }
         }
         return $entity
     }

@niheaven
Copy link
Copy Markdown
Member Author

Thanks @r15ch13, I've just made a minimal change to achieve my goal, and your implement are more systemic and readable. I've adopted your advise and extended it to JSONPath (though may not needed now).

I've tested the code on atom's nupkg version (for regex), nuget (for jsonpath checkver) and openssl (for jsonpath hash extraction), everything is okay, the new logic works well and the old logic is not affected.

It's good for me now, and if merged, I'll submit a pr about new hash extract method of atom.

@r15ch13 r15ch13 merged commit 3a22526 into ScoopInstaller:master Jan 17, 2019
@niheaven niheaven deleted the add-version-variables-to-regex-in-hash-extract branch January 21, 2019 01:52
r15ch13 pushed a commit that referenced this pull request Jan 21, 2019
Fix a bug introduced in #2996 that single line hash extraction is broken.

When `find_hash_in_textfile()` is called in `get_hash_for_app()`, `$null` is used as `$regex` value for default single line hash extraction and the default value (`'^([a-fA-F0-9]+)$'`) is overriden, which leads a error of "hash not found".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants