Files
dockur-windows/src/mido.sh
T

1041 lines
27 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
handleCurlError() {
local code="$1"
local server="$2"
local reason="${3:-}"
local signal=""
if [ -n "$reason" ] && (( code <= 125 )); then
error "Request to $server servers failed: ${reason%.}."
return 1
fi
case "$code" in
126) error "The curl command could not be executed." ;;
127) error "The curl command was not found." ;;
*)
if (( code < 129 )); then
error "Request to $server servers failed with curl exit status $code."
return 1
fi
signal=$(kill -l "$((code - 128))" 2>/dev/null || true)
case "$signal" in
INT) error "Curl was interrupted." ;;
SEGV | ABRT) error "Curl crashed with signal $signal." ;;
"") error "Curl terminated with exit status $code." ;;
*) error "Curl terminated due to signal $signal." ;;
esac
;;
esac
return 1
}
curlRequest() {
local output="$1"
local server="$2"
local agent="$3"
shift 3
local log reason
local rc=0 response=""
if ! log=$(mktemp); then
error "Failed to create a temporary curl log."
return 1
fi
{
response=$(LC_ALL=C curl \
--silent \
--show-error \
--max-time 30 \
--user-agent "$agent" \
--fail \
--proto =https \
--tlsv1.2 \
--http1.1 \
"$@" 2>"$log")
rc=$?
} || :
if (( rc != 0 )); then
reason=$(sed -nE 's/^curl: \([0-9]+\) //p' "$log" | tail -n 1)
rm -f "$log"
handleCurlError "$rc" "$server" "$reason"
return 1
fi
rm -f "$log"
if [ -n "$output" ]; then
printf -v "$output" '%s' "$response"
fi
return 0
}
getAgent() {
local browser_version
# Approximate Firefox version, increasing every two weeks
browser_version="$((152 + ($(date +%s) - 1781568000) / 1209600))"
echo "Mozilla/5.0 (X11; Linux x86_64; rv:${browser_version}.0) Gecko/20100101 Firefox/${browser_version}.0"
return 0
}
downloadWindows() {
local id="$1"
local lang="$2"
local desc="$3"
local ovToken="" ovTicks="" ovTime=""
local skuId="" skuUrl="" skuJson=""
local linkUrl="" linkJson="" link=""
local language="" orgId="" ovData=""
local instance="" vlsUrl="" ovUrl=""
local session="" agent="" type=""
local winVer="" page="" productId=""
local rc=0 profile="606624d44113"
agent=$(getAgent)
language=$(getLanguage "$lang" "name")
case "${id,,}" in
"win11x64" ) winVer="11" && type="1" ;;
"win11arm64" ) winVer="11arm64" && type="2" ;;
* ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac
local url="https://www.microsoft.com/en-us/software-download/windows$winVer"
# uuidgen: For MacOS (installed by default) and other systems (e.g. with no /proc) that don't have a kernel interface for generating random UUIDs
if ! session=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random); then
error "Failed to generate session ID!"
return 1
fi
session="${session//[![:print:]]/}"
if [ -z "$session" ]; then
error "Failed to generate session ID!"
return 1
fi
# Get product edition ID for latest release of given Windows version
# Product edition ID: This specifies both the Windows release (e.g. 22H2) and edition ("multi-edition" is default, either Home/Pro/Edu/etc., we select "Pro" in the answer files) in one number
# This is the *only* request we make that Fido doesn't. Fido manually maintains a list of all the Windows release/edition product edition IDs in its script (see: $WindowsVersions array). This is helpful for downloading older releases (e.g. Windows 10 1909, 21H1, etc.) but we always want to get the newest release which is why we get this value dynamically
# Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
# Remove "Accept" header that curl sends by default
enabled "$DEBUG" && echo "Parsing download page: ${url}"
curlRequest page "Microsoft" "$agent" \
--header "Accept:" \
--max-filesize 1M \
-- "$url" || return 1
enabled "$DEBUG" && echo -n "Getting Product edition ID: "
productId=$(echo "$page" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
enabled "$DEBUG" && echo "$productId"
if [ -z "$productId" ]; then
error "Product edition ID not found!"
return 1
fi
# Microsoft download "protection" requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags
orgId="y6jn8c31"
vlsUrl="https://vlscppe.microsoft.com/tags?org_id=$orgId&session_id=$session"
enabled "$DEBUG" && echo "Getting Session ID: $session"
# Permit Session ID
curlRequest "" "Microsoft" "$agent" \
--output /dev/null \
--header "Accept:" \
--max-filesize 100K \
-- "$vlsUrl" || return 1
# Microsoft download "protection" also requires an ov-df.microsoft.com request/reply
# 1) Request mdt.js to get w and rticks. InstanceId is (currently) constant.
instance="560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175"
ovUrl="https://ov-df.microsoft.com/mdt.js?instanceId=$instance&PageId=si&session_id=$session"
enabled "$DEBUG" && echo -n "Getting OV data: "
curlRequest ovData "Microsoft" "$agent" \
--header "Accept:" \
--max-filesize 1M \
-- "$ovUrl" || return 1
if [[ $ovData =~ [\?\&]w=([A-Fa-f0-9]+) ]]; then
ovToken="${BASH_REMATCH[1]}"
fi
if [[ $ovData =~ rticks=\"\+?([0-9]+) ]]; then
ovTicks="${BASH_REMATCH[1]}"
fi
if [[ -z $ovToken || -z $ovTicks ]]; then
error "Could not extract ov-df data from Microsoft server!"
return 1
fi
enabled "$DEBUG" && echo "$ovToken"
sleep 0.2
# 2) Send a reply with session ID, current epoch and previously retrieved w and rticks
ovTime=$(date +%s%3N)
ovUrl="https://ov-df.microsoft.com/?session_id=$session&CustomerId=$instance&PageId=si&w=$ovToken&mdt=$ovTime&rticks=$ovTicks"
enabled "$DEBUG" && echo "Sending OV reply: $instance"
curlRequest "" "Microsoft" "$agent" \
--output /dev/null \
--header "Accept:" \
--max-filesize 100K \
-- "$ovUrl" || return 1
enabled "$DEBUG" && echo -n "Getting language SKU ID: "
skuUrl="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$productId&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
curlRequest skuJson "Microsoft" "$agent" \
--referer "$url" \
--header "Accept:" \
--max-filesize 100K \
-- "$skuUrl" || return 1
{ skuId=$(echo "$skuJson" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]] || (( rc != 0 )); then
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
return 1
fi
enabled "$DEBUG" && echo "$skuId"
enabled "$DEBUG" && echo "Getting ISO download link..."
# Get ISO download link
# If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
linkUrl="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$skuId&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
curlRequest linkJson "Microsoft" "$agent" \
--referer "$url" \
--header "Accept:" \
--max-filesize 100K \
-- "$linkUrl" || return 1
if ! [ "$linkJson" ]; then
# This should only happen if there's been some change to how this API works
error "Microsoft servers gave us an empty response to our request for an automated download."
return 1
fi
if echo "$linkJson" | grep -q "Sentinel marked this request as rejected."; then
error "Microsoft blocked the automated download request based on your IP address."
return 1
fi
if echo "$linkJson" | grep -q "We are unable to complete your request at this time."; then
error "Microsoft blocked the automated download request."
return 1
fi
{ link=$(echo "$linkJson" | jq --argjson TYPE "$type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
if [ -z "$link" ] || [[ "${link,,}" == "null" ]] || (( rc != 0 )); then
error "Microsoft server gave us no download link to our request for an automated download!"
info "Response: $linkJson"
return 1
fi
MIDO_URL="$link"
return 0
}
downloadWindowsEval() {
local id="$1"
local lang="$2"
local desc="$3"
local filter="" culture="" compare="" language=""
local agent="" type="" winVer=""
case "${id,,}" in
"win11${PLATFORM,,}-enterprise-eval" )
type="enterprise"
winVer="windows-11-enterprise" ;;
"win11${PLATFORM,,}-enterprise-iot-eval" )
type="iot"
winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win11${PLATFORM,,}-enterprise-ltsc-eval" )
type="iot"
winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win2025-eval" )
type="server"
winVer="windows-server-2025" ;;
"win2022-eval" )
type="server"
winVer="windows-server-2022" ;;
"win2019-hv" )
type="server"
winVer="hyper-v-server-2019" ;;
"win2019-eval" )
type="server"
winVer="windows-server-2019" ;;
"win2016-eval" )
type="server"
winVer="windows-server-2016" ;;
"win2012r2-eval" )
type="server"
winVer="windows-server-2012-r2" ;;
* )
error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac
agent=$(getAgent)
culture=$(getLanguage "$lang" "culture")
local country="${culture#*-}"
local link=""
local links=""
local page=""
local url="https://www.microsoft.com/en-us/evalcenter/download-$winVer"
enabled "$DEBUG" && echo "Parsing download page: ${url}"
curlRequest page "Microsoft" "$agent" \
--location \
--max-filesize 1M \
-- "$url" || return 1
if ! [ "$page" ]; then
# This should only happen if there's been some change to where this download page is located
error "Windows server download page gave us an empty response"
return 1
fi
enabled "$DEBUG" && echo "Getting download link.."
filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
if ! echo "$page" | grep -io "$filter" > /dev/null; then
filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
fi
links=$(echo "$page" | grep -io "$filter") || {
# This should only happen if there's been some change to the download endpoint web address
if [[ "${lang,,}" == "en" || "${lang,,}" == "en-"* ]]; then
error "Windows server download page gave us no download link!"
else
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
fi
return 1
}
case "$type" in
"iot" | "ltsc" )
case "${PLATFORM,,}" in
"x64" )
if [[ "$winVer" != "windows-10"* ]]; then
link=$(echo "$links" | head -n 1)
else
link=$(echo "$links" | head -n 4 | tail -n 1)
fi ;;
"arm64" )
link=$(echo "$links" | head -n 2 | tail -n 1) ;;
esac ;;
"enterprise" )
case "${PLATFORM,,}" in
"x64" )
if [[ "$winVer" != "windows-10"* ]]; then
link=$(echo "$links" | head -n 1)
else
link=$(echo "$links" | head -n 2 | tail -n 1)
fi ;;
"arm64" )
link=$(echo "$links" | head -n 2 | tail -n 1) ;;
esac ;;
"server" )
case "${PLATFORM,,}" in
"x64" )
link=$(echo "$links" | head -n 1) ;;
esac ;;
* )
error "Invalid type specified, value \"$type\" is not recognized!" && return 1 ;;
esac
[ -z "$link" ] && error "Could not parse download link from page!" && return 1
# Follow redirect so proceeding log message is useful
# This is a request we make that Fido doesn't
curlRequest link "Microsoft" "$agent" \
--location \
--output /dev/null \
--write-out "%{url_effective}" \
--head \
-- "$link" || return 1
case "${PLATFORM,,}" in
"x64" )
if [[ "${link,,}" != *"x64"* ]]; then
echo "Found download link: $link"
error "Download link is for the wrong platform? Please report this at $SUPPORT/issues"
return 1
fi ;;
"arm64" )
if [[ "${link,,}" != *"a64"* && "${link,,}" != *"arm64"* ]]; then
if enabled "$DEBUG"; then
echo "Found download link: $link"
echo "Link for ARM platform currently not available!"
fi
return 1
fi ;;
esac
if enabled "$DEBUG" && enabled "$VERIFY" && [[ "${lang,,}" == "en"* ]]; then
compare=$(getMido "$id" "$lang" "")
if [[ "${link,,}" != "${compare,,}" ]]; then
echo "Retrieved link does not match the fixed link: $compare"
fi
fi
MIDO_URL="$link"
return 0
}
getWindows() {
local version="$1"
local lang="$2"
local desc="$3"
local language edition
language=$(getLanguage "$lang" "desc")
edition=$(printEdition "$version" "$desc")
local msg="Requesting $desc from the Microsoft servers..."
info "$msg" && html "$msg"
case "${version,,}" in
"win2008r2" | "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* | \
"win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then
error "No download in the $language language available for $edition!"
MIDO_URL="" && return 1
fi ;;
esac
case "${version,,}" in
"win11${PLATFORM,,}" ) ;;
"win11${PLATFORM,,}-enterprise"* ) ;;
* )
if [[ "${PLATFORM,,}" != "x64" ]]; then
error "No download for the ${PLATFORM^^} platform available for $edition!"
MIDO_URL="" && return 1
fi ;;
esac
case "${version,,}" in
"win11${PLATFORM,,}" )
downloadWindows "$version" "$lang" "$edition" && return 0
;;
"win11${PLATFORM,,}-enterprise"* )
downloadWindowsEval "$version" "$lang" "$edition" && return 0
;;
"win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" )
downloadWindowsEval "$version" "$lang" "$edition" && return 0
;;
"win2008r2" | "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* )
;;
* ) error "Invalid VERSION specified, value \"$version\" is not recognized!" ;;
esac
MIDO_URL=$(getMido "$version" "$lang" "")
[ -z "$MIDO_URL" ] && return 1
return 0
}
getBuild() {
local id="$1"
local ret="$2"
local url=""
local name=""
local build="$3"
local edition=""
local file="catalog.xml"
case "${id,,}" in
"win11${PLATFORM,,}" )
name="Windows 11 Pro"
url="https://worproject.com/dldserv/esd/getcatalog.php?build=${build}&arch=${PLATFORM^^}&edition=Professional" ;;
"win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval")
name="Windows 11 Enterprise"
url="https://worproject.com/dldserv/esd/getcatalog.php?build=${build}&arch=${PLATFORM^^}&edition=Enterprise" ;;
esac
case "${ret,,}" in
"url" ) echo "$url" ;;
"file" ) echo "$file" ;;
"name" ) echo "$name" ;;
"edition" ) echo "$edition" ;;
*) echo "";;
esac
return 0
}
getCatalog() {
local id="$1"
local ret="$2"
local url=""
local name=""
local edition=""
local file="catalog.cab"
if [[ "${id,,}" == "win11"* ]] && ! isCompatible; then
# ARMv8.0 cannot run Windows 11 builds 24H2 and up.
getBuild "$1" "$2" "22631.2861" && return 0
fi
case "${id,,}" in
"win11${PLATFORM,,}" )
edition="Professional"
name="Windows 11 Pro"
url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
"win10${PLATFORM,,}" )
edition="Professional"
name="Windows 10 Pro"
url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
"win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval")
edition="Enterprise"
name="Windows 11 Enterprise"
url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
"win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" )
edition="Enterprise"
name="Windows 10 Enterprise"
url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
esac
case "${ret,,}" in
"url" ) echo "$url" ;;
"file" ) echo "$file" ;;
"name" ) echo "$name" ;;
"edition" ) echo '[Edition="'"${edition}"'"]' ;;
*) echo "";;
esac
return 0
}
getXmlTag() {
local tag="$1"
local file="$2"
xmllint --nonet --xpath "//$tag" "$file" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true
return 0
}
getESD() {
local dir="$1"
local version="$2"
local lang="$3"
local desc="$4"
local file result culture
local language edition catalog
local xmlFile="products.xml"
local esdFile="esd_edition.xml"
local filterFile="products_filter.xml"
local log query rc=0 reason=""
file=$(getCatalog "$version" "file")
catalog=$(getCatalog "$version" "url")
culture=$(getLanguage "$lang" "culture")
edition=$(getCatalog "$version" "edition")
if [ -z "$file" ] || [ -z "$catalog" ]; then
error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi
local msg="Downloading catalog from the Microsoft servers..."
info "$msg" && html "$msg"
rm -rf "$dir"
if ! makeDir "$dir"; then
error "Failed to create directory \"$dir\" !"
return 1
fi
log=$(mktemp)
{
LC_ALL=C wget "$catalog" -O "$dir/$file" --no-verbose --timeout=30 \
--no-http-keep-alive --output-file="$log"
rc=$?
} || :
if (( rc != 0 )); then
reason=$(sed -n \
-e 's/^wget: //p' \
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$log" | tail -n 1)
msg="Failed to download $catalog"
if (( rc == 3 )); then
error "$msg because the file could not be written (disk full?)."
elif [ -n "$reason" ]; then
error "$msg: ${reason%.}."
else
error "$msg with exit status $rc."
fi
rm -f "$log"
return 1
fi
rm -f "$log"
if [[ "$file" == *".xml" ]]; then
if ! mv -f "$dir/$file" "$dir/$xmlFile"; then
error "Failed to rename $file to $xmlFile."
return 1
fi
else
if ! (
cd "$dir" || exit 1
cabextract "$file" > /dev/null
); then
error "Failed to extract $file!"
return 1
fi
fi
if [ ! -s "$dir/$xmlFile" ]; then
error "Failed to find $xmlFile in $file!"
return 1
fi
query='//File[Architecture="'${PLATFORM,,}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
if [ -z "$result" ]; then
query='//File[Architecture="'${PLATFORM^^}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
if [ -z "$result" ]; then
desc=$(printEdition "$version" "$desc")
language=$(getLanguage "$lang" "desc")
error "No download link available for $desc!"
return 1
fi
fi
echo -e '<Catalog>' > "$dir/$filterFile"
echo "$result" >> "$dir/$filterFile"
echo -e '</Catalog>'>> "$dir/$filterFile"
result=$(xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$filterFile" 2>/dev/null || true)
if [ -z "$result" ]; then
desc=$(printEdition "$version" "$desc")
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
return 1
fi
echo "$result" > "$dir/$esdFile"
ESD=$(getXmlTag "FilePath" "$dir/$esdFile")
if [ -z "$ESD" ]; then
error "Failed to find ESD URL in $esdFile!"
return 1
fi
ESD_SUM=$(getXmlTag "Sha1" "$dir/$esdFile")
if [ -z "$ESD_SUM" ]; then
error "Failed to find ESD checksum in $esdFile!"
return 1
fi
ESD_SIZE=$(getXmlTag "Size" "$dir/$esdFile")
if [ -z "$ESD_SIZE" ]; then
error "Failed to find ESD filesize in $esdFile!"
return 1
fi
rm -rf "$dir"
return 0
}
isCompressed() {
local file="$1"
case "${file,,}" in
*".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
return 0 ;;
esac
return 1
}
verifyFile() {
local iso="$1"
local size="$2"
local total="$3"
local check="$4"
if [ -n "$size" ] && [[ "$total" != "$size" && "$size" != "0" ]]; then
if enabled "$VERIFY" || enabled "$DEBUG"; then
warn "The downloaded file has a different size ( $total bytes) than expected ( $size bytes). Please report this at $SUPPORT/issues"
fi
fi
local hash=""
local algo="SHA256"
[ -z "$check" ] && return 0
! enabled "$VERIFY" && return 0
[[ "${#check}" == "40" ]] && algo="SHA1"
local msg="Verifying downloaded ISO..."
info "$msg" && html "$msg"
if [[ "${algo,,}" != "sha256" ]]; then
if ! hash=$(sha1sum "$iso" | cut -f1 -d' '); then
error "Failed to calculate SHA1 checksum for $iso!"
return 1
fi
else
if ! hash=$(sha256sum "$iso" | cut -f1 -d' '); then
error "Failed to calculate SHA256 checksum for $iso!"
return 1
fi
fi
if [[ "$hash" == "$check" ]]; then
info "Successfully verified ISO!" && return 0
fi
error "The downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
return 1
}
downloadFile() {
local iso="$1"
local url="$2"
local sum="$3"
local size="$4"
local lang="$5"
local desc="$6"
local reason=""
local msg="Downloading $desc"
local rc total total_gb log
local domain dots agent space folder
local progress=()
local output=""
agent=$(getAgent)
if [ -n "$size" ] && [[ "$size" != "0" ]]; then
folder=$(dirname -- "$iso")
if ! space=$(df --output=avail -B 1 "$folder" | tail -n 1); then
error "Failed to check free space in $folder!"
return 1
fi
total_gb=$(formatBytes "$space")
(( size > space )) && error "Not enough free space to download file, only $total_gb left!" && return 1
fi
# Use Wget's progress bar in a terminal and progress.sh in container logs.
if [ -t 1 ]; then
progress=( --show-progress --progress=bar:noscroll )
else
output="log"
fi
html "$msg..."
/run/progress.sh "$iso" "${size:-0}" "$msg ([P])..." "$output" &
domain=$(echo "$url" | awk -F/ '{print $3}')
dots=$(echo "$domain" | tr -cd '.' | wc -c)
(( dots > 1 )) && domain=$(expr "$domain" : '.*\.\(.*\..*\)')
if [ -n "$domain" ] && [[ "${domain,,}" != *"microsoft.com" ]]; then
msg="Downloading $desc from $domain"
fi
info "$msg..."
log=$(mktemp)
enabled "$DEBUG" && echo "Downloading: $url"
{
LC_ALL=C wget "$url" -O "$iso" --continue --no-verbose --timeout=30 \
--no-http-keep-alive --user-agent "$agent" \
"${progress[@]}" --output-file="$log"
rc=$?
} || :
fKill "progress.sh"
if (( rc != 0 )); then
reason=$(sed -n \
-e 's/^wget: //p' \
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$log" | tail -n 1)
fi
rm -f "$log"
if (( rc == 0 )) && [ -f "$iso" ]; then
if ! total=$(stat -c%s "$iso"); then
error "Failed to determine downloaded file size: $iso"
return 1
fi
total_gb=$(formatBytes "$total")
if [ "$total" -lt 100000000 ]; then
error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues"
return 1
fi
# Status 2 means the download completed but failed validation.
verifyFile "$iso" "$size" "$total" "$sum" || return 2
# Extract the .iso from the compressed archive if needed.
isCompressed "$url" && UNPACK="Y"
return 0
fi
msg="Failed to download $url"
if (( rc == 3 )); then
error "$msg because the file could not be written (disk full?)."
elif [ -n "$reason" ]; then
error "$msg: ${reason%.}."
else
error "$msg with exit status $rc."
fi
return 1
}
delay() {
local i
local delay="$1"
local msg="Will retry in X seconds..."
info "${msg/X/$delay}"
for i in $(seq "$delay" -1 1); do
html "${msg/X/$i}"
sleep 1
done
return 0
}
tryDownload() {
local iso="$1"
local url="$2"
local sum="$3"
local size="$4"
local lang="$5"
local desc="$6"
local seconds="$7"
local rc=0
rm -f "$iso"
if downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc"; then
return 0
else
rc=$?
fi
# Do not download the same file again when its contents failed validation.
if (( rc == 2 )); then
rm -f "$iso"
return 1
fi
delay "$seconds"
if downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc"; then
return 0
fi
rm -f "$iso"
return 1
}
downloadImage() {
local iso="$1"
local version="$2"
local lang="$3"
local tried="n"
local success="n"
local seconds="5"
local url sum size base desc language i
if [[ "${version,,}" == "http"* ]]; then
base=$(basename "$iso")
desc=$(fromFile "$base")
tryDownload "$iso" "$version" "" "" "" "$desc" "$seconds" && return 0
return 1
fi
if ! validVersion "$version" "en"; then
error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi
desc=$(printVersion "$version" "")
if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then
language=$(getLanguage "$lang" "desc")
if ! validVersion "$version" "$lang"; then
desc=$(printEdition "$version" "$desc")
error "The $language language version of $desc is not available, please switch to English."
return 1
fi
desc+=" in $language"
fi
if isMido "$version" "$lang"; then
tried="y"
success="n"
if getWindows "$version" "$lang" "$desc"; then
success="y"
else
delay "$seconds"
getWindows "$version" "$lang" "$desc" && success="y"
fi
if [[ "$success" == "y" ]]; then
url=$(getMido "$version" "$lang" "")
sum=""
size=""
# Skip verification if the retrieved URL differs from the static URL.
if [[ "${MIDO_URL%%\?*}" == "${url%%\?*}" ]]; then
size=$(getMido "$version" "$lang" "size")
sum=$(getMido "$version" "$lang" "sum")
fi
tryDownload "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" "$seconds" && return 0
fi
fi
switchEdition "$version"
if isESD "$version" "$lang"; then
if [[ "$tried" != "n" ]]; then
info "Failed to download $desc, will try a different method now..."
fi
tried="y"
success="n"
if getESD "$TMP/esd" "$version" "$lang" "$desc"; then
success="y"
else
delay "$seconds"
getESD "$TMP/esd" "$version" "$lang" "$desc" && success="y"
fi
if [[ "$success" == "y" ]]; then
ISO="${ISO%.*}.esd"
if tryDownload "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" "$seconds"; then
return 0
fi
ISO="$iso"
fi
fi
for ((i=1;i<=MIRRORS;i++)); do
url=$(getLink "$i" "$version" "$lang")
if [ -n "$url" ]; then
if [[ "$tried" != "n" ]]; then
info "Failed to download $desc, will try another mirror now..."
fi
tried="y"
size=$(getSize "$i" "$version" "$lang")
sum=$(getHash "$i" "$version" "$lang")
tryDownload "$iso" "$url" "$sum" "$size" "$lang" "$desc" "$seconds" && return 0
fi
done
return 1
}
return 0