Private/Security/Archive.ps1

function Expand-VSOArchive {
    param([string]$Archive, [string]$Destination, $Context)
    Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
    $zip=$null
    try {
        $zip=[IO.Compression.ZipFile]::OpenRead($Archive)
        if ($zip.Entries.Count -eq 0 -or $zip.Entries.Count -gt 10000) { Throw-VSOError $Context Archive @('entry count') }
        $seen=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        $entries=[Collections.Generic.List[object]]::new(); [long]$total=0
        foreach ($entry in $zip.Entries) {
            $name=$entry.FullName.Replace('\','/'); $directory=$name.EndsWith('/'); $relative=$name.TrimEnd('/')
            $target=Join-VSOChild $Destination $relative $Context
            if (-not $seen.Add($target)) { Throw-VSOError $Context Archive @('duplicate path') }
            $unixMode=([long]$entry.ExternalAttributes -shr 16) -band 61440
            if ($unixMode -notin @(0,16384,32768) -or ($entry.ExternalAttributes -band 1024)) { Throw-VSOError $Context Archive @('link or special entry') }
            if ($entry.Length -gt 268435456 -or ($entry.Length -gt 10485760 -and $entry.CompressedLength -gt 0 -and ($entry.Length / $entry.CompressedLength) -gt 200)) { Throw-VSOError $Context Archive @('expansion ratio') }
            $total += $entry.Length
            if ($total -gt 536870912) { Throw-VSOError $Context Archive @('expanded size') }
            $entries.Add([pscustomobject]@{ Entry=$entry; Target=$target; Directory=$directory })
        }
        # Validate every entry before writing any payload.
        [long]$done=0
        foreach ($item in $entries) {
            Assert-VSONoReparse $item.Target $Context
            if ($item.Directory) { [void][IO.Directory]::CreateDirectory($item.Target); continue }
            [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($item.Target))
            $input=$item.Entry.Open(); $output=$null
            try {
                $output=[IO.FileStream]::new($item.Target,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
                $buffer=[byte[]]::new(65536); [long]$written=0
                while (($read=$input.Read($buffer,0,$buffer.Length)) -gt 0) {
                    $written+=$read; $done+=$read
                    if ($written -gt $item.Entry.Length -or $done -gt 536870912) { Throw-VSOError $Context Archive @('length mismatch') }
                    $output.Write($buffer,0,$read)
                }
                if ($written -ne $item.Entry.Length) { Throw-VSOError $Context Archive @('truncated entry') }
            } finally { if ($output) { $output.Dispose() }; $input.Dispose() }
            Write-VSOProgress $Context Extract $done $total
        }
    } catch { Throw-VSOError $Context Archive @($_.Exception.Message) $_.Exception }
    finally { if ($zip) { $zip.Dispose() }; Write-VSOProgress $Context Extract -Completed }
}

function Read-VSOChecksumManifest {
    param([string]$Root, $Context)
    $path=Join-VSOChild $Root 'SHA256SUMS.txt' $Context
    if (-not [IO.File]::Exists($path) -or ([IO.FileInfo]::new($path)).Length -gt 2097152) { Throw-VSOError $Context Integrity @('SHA256SUMS.txt') }
    $files=[ordered]@{}; $seen=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    foreach ($line in [IO.File]::ReadAllLines($path)) {
        if (-not $line.Trim()) { continue }
        if ($line -notmatch '^([a-fA-F0-9]{64})[ \t]+\*?(.+)$') { Throw-VSOError $Context Integrity @('checksum syntax') }
        $hash=$Matches[1].ToLowerInvariant(); $relative=$Matches[2].Replace('\','/')
        $null=Join-VSOChild $Root $relative $Context
        if (-not $seen.Add($relative) -or $relative -eq 'SHA256SUMS.txt' -or $relative -eq '.vso-manager.json') { Throw-VSOError $Context Integrity @('duplicate or reserved entry') }
        $files[$relative]=$hash
    }
    if ($files.Count -eq 0) { Throw-VSOError $Context Integrity @('empty manifest') }
    return $files
}