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
106 changes: 89 additions & 17 deletions src/dotnet-install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1013,33 +1013,45 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern
}
$akaMsLink += "/$Product-win-$Architecture.zip"
Say-Verbose "Constructed aka.ms link: '$akaMsLink'."

# Collect observed status codes and the last Location header observed
$statusCodes = @()
$akaMsDownloadLink = $null
$finalResponse = $null

for ($maxRedirections = 9; $maxRedirections -ge 0; $maxRedirections--) {
#get HTTP response
#do not pass credentials as a part of the $akaMsLink and do not apply credentials in the GetHTTPResponse function
#otherwise the redirect link would have credentials as well
#it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link
for ($attempt = 0; $attempt -le 9; $attempt++) {
# get HTTP response header-only; do not auto-follow redirects; do not apply feed credentials
$Response = GetHTTPResponse -Uri $akaMsLink -HeaderOnly $true -DisableRedirect $true -DisableFeedCredential $true
Say-Verbose "Received response:`n$Response"

if ([string]::IsNullOrEmpty($Response)) {
if ($null -eq $Response) {
Say-Verbose "The link '$akaMsLink' is not valid: failed to get redirect location. The resource is not available."
return $null
}

#if HTTP code is 301 (Moved Permanently), the redirect link exists
if ($Response.StatusCode -eq 301) {
$code = [int]$Response.StatusCode
$statusCodes += $code

if ($code -eq 301) {
# Capture Location header and follow it
try {
$akaMsDownloadLink = $Response.Headers.GetValues("Location")[0]
$locValues = $null
try {
$locValues = $Response.Headers.GetValues("Location")
}
catch {
# No Location found
$locValues = $null
}

if ([string]::IsNullOrEmpty($akaMsDownloadLink)) {
if ($null -eq $locValues -or $locValues.Count -eq 0 -or [string]::IsNullOrEmpty($locValues[0])) {
Say-Verbose "The link '$akaMsLink' is not valid: server returned 301 (Moved Permanently), but the headers do not contain the redirect location."
return $null
}

$akaMsDownloadLink = $locValues[0]
Say-Verbose "The redirect location retrieved: '$akaMsDownloadLink'."
# This may yet be a link to another redirection. Attempt to retrieve the page again.
# follow it for the next iteration (it may redirect again)
$akaMsLink = $akaMsDownloadLink
continue
}
Expand All @@ -1048,18 +1060,78 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern
return $null
}
}
elseif ((($Response.StatusCode -lt 300) -or ($Response.StatusCode -ge 400)) -and (-not [string]::IsNullOrEmpty($akaMsDownloadLink))) {
# Redirections have ended.
return $akaMsDownloadLink

# Non-301 response observed — treat it as terminal for analysis
$finalResponse = $Response
break
}

if ($statusCodes.Count -eq 0) {
Say-Verbose "The link '$akaMsLink' is not valid: no HTTP responses were observed."
return $null
}

# Find index of last terminal status (2xx, 4xx, 5xx)
$lastTerminalIdx = -1
for ($i = $statusCodes.Count - 1; $i -ge 0; $i--) {
$s = $statusCodes[$i]
if ((($s -ge 200) -and ($s -le 299)) -or (($s -ge 400) -and ($s -le 599))) {
$lastTerminalIdx = $i
break
}
}

# Build redirect candidate list: codes before last terminal (or the whole list if no terminal found)
if ($lastTerminalIdx -ge 0) {
if ($lastTerminalIdx -gt 0) {
$redirectCandidates = $statusCodes[0..($lastTerminalIdx - 1)]
}
else {
$redirectCandidates = @()
}

Say-Verbose "The link '$akaMsLink' is not valid: failed to retrieve the redirection location."
# Trim trailing 1xx/2xx entries from redirectCandidates (likely proxy "Connection Established" markers)
while ($redirectCandidates.Count -gt 0) {
$last = $redirectCandidates[-1]
if (($last -ge 100) -and ($last -le 299)) {
if ($redirectCandidates.Count -eq 1) {
$redirectCandidates = @()
}
else {
$redirectCandidates = $redirectCandidates[0..($redirectCandidates.Count - 2)]
}
}
else {
break
}
}
}
else {
# No terminal code found — conservative fallback: require all codes to be 301
$redirectCandidates = $statusCodes
}

# Any redirect candidate that is not 301 indicates a broken redirect chain.
$brokenRedirects = $redirectCandidates | Where-Object { $_ -ne 301 }

if ($brokenRedirects.Count -gt 0) {
Say-Verbose "The aka.ms link '$akaMsLink' is not valid: received HTTP code(s): $(( $brokenRedirects -join ',' ))"
return $null
}

Say-Verbose "Aka.ms links have redirected more than the maximum allowed redirections. This may be caused by a cyclic redirection of aka.ms links."
return $null
# Success: return the last captured Location (if any)
if (-not [string]::IsNullOrEmpty($akaMsDownloadLink)) {
Say-Verbose "Final redirect location (aka.ms): '$akaMsDownloadLink'."
return $akaMsDownloadLink
}

# If we had a final non-301 response and a previously captured Location, return it; otherwise fail
if ($finalResponse -ne $null -and -not [string]::IsNullOrEmpty($akaMsDownloadLink)) {
return $akaMsDownloadLink
}

Say-Verbose "The link '$akaMsLink' is not valid: failed to retrieve the redirection location."
return $null
}

function Get-AkaMsLink-And-Version([string] $NormalizedChannel, [string] $NormalizedQuality, [bool] $Internal, [string] $ProductName, [string] $Architecture) {
Expand Down
47 changes: 38 additions & 9 deletions src/dotnet-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1310,17 +1310,46 @@ get_download_link_from_aka_ms() {
response="$(get_http_header $aka_ms_link $disable_feed_credential)"

say_verbose "Received response: $response"
# Get results of all the redirects.
http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
# They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404).
broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' )
# The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused.
# In this case it should not exclude the last.
last_http_code=$( echo "$http_codes" | tail -n 1 )
if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then
broken_redirects=$( echo "$http_codes" | grep -v '301' )
# Get results of all the HTTP status codes in the response (one per line).
# strip CRs before parsing to avoid \r interfering with regex matches
local http_codes
http_codes=$( printf '%s' "$response" | tr -d '\r' | awk '$1 ~ /^HTTP/ {print $2}' )

# Find the index (line number) of the last terminal HTTP status code (2xx, 4xx, 5xx).
local last_terminal_idx
last_terminal_idx=$( printf '%s' "$http_codes" | awk '/^(2|4|5)[0-9][0-9]$/{idx=NR} END{ if(idx) print idx }' )

local redirect_candidates
if [[ -n "$last_terminal_idx" ]]; then
if [[ "$last_terminal_idx" -gt 1 ]]; then
# Candidate redirect codes are those that appear before the last terminal code.
redirect_candidates=$( printf '%s' "$http_codes" | awk -v n="$last_terminal_idx" 'NR < n {print}' )
else
redirect_candidates=""
fi

# Trim trailing 1xx/2xx entries from redirect_candidates that are likely proxy markers.
redirect_candidates=$( printf '%s' "$redirect_candidates" | awk '{
lines[NR] = $0
}
END {
last = NR
while (last > 0 && lines[last] ~ /^(1|2)[0-9][0-9]$/) last--
for (i = 1; i <= last; i++) print lines[i]
}' )

else
# No terminal code found — fall back to conservative behavior.
redirect_candidates="$http_codes"
fi

# Any redirect candidate that is not '301' indicates a broken redirect chain.
local broken_redirects
broken_redirects=$( printf '%s' "$redirect_candidates" | grep -v '^301$' || true )

# Keep last_http_code for diagnostics
last_http_code=$( printf '%s' "$http_codes" | tail -n 1 )

# All HTTP codes are 301 (Moved Permanently), the redirect link exists.
if [[ -z "$broken_redirects" ]]; then
aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r')
Expand Down
Loading