mido.sh 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. handle_curl_error() {
  4. local error_code="$1"
  5. case "$error_code" in
  6. 1) error "Unsupported protocol!" ;;
  7. 2) error "Failed to initialize curl!" ;;
  8. 3) error "The URL format is malformed!" ;;
  9. 5) error "Failed to resolve address of proxy host!" ;;
  10. 6) error "Failed to resolve Microsoft servers! Is there an Internet connection?" ;;
  11. 7) error "Failed to contact Microsoft servers! Is there an Internet connection or is the server down?" ;;
  12. 8) error "Microsoft servers returned a malformed HTTP response!" ;;
  13. 16) error "A problem was detected in the HTTP2 framing layer!" ;;
  14. 22) error "Microsoft servers returned a failing HTTP status code!" ;;
  15. 23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;;
  16. 26) error "Failed to read Windows media from disk!" ;;
  17. 27) error "Ran out of memory during download!" ;;
  18. 28) error "Connection timed out to Microsoft server!" ;;
  19. 35) error "SSL connection error from Microsoft server!" ;;
  20. 36) error "Failed to continue earlier download!" ;;
  21. 52) error "Received no data from the Microsoft server!" ;;
  22. 63) error "Microsoft servers returned an unexpectedly large response!" ;;
  23. # POSIX defines exit statuses 1-125 as usable by us
  24. # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
  25. $((error_code <= 125)))
  26. # Must be some other server or network error (possibly with this specific request/file)
  27. # This is when accounting for all possible errors in the curl manual assuming a correctly formed curl command and an HTTP(S) request, using only the curl features we're using, and a sane build
  28. error "Miscellaneous server or network error, reason: $error_code"
  29. ;;
  30. 126 | 127 ) error "Curl command not found!" ;;
  31. # Exit statuses are undefined by POSIX beyond this point
  32. *)
  33. case "$(kill -l "$error_code")" in
  34. # Signals defined to exist by POSIX:
  35. # https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
  36. INT) error "Curl was interrupted!" ;;
  37. # There could be other signals but these are most common
  38. SEGV | ABRT ) error "Curl crashed! Please report any core dumps to curl developers." ;;
  39. *) error "Curl terminated due to fatal signal $error_code !" ;;
  40. esac
  41. esac
  42. return 1
  43. }
  44. get_agent() {
  45. local user_agent
  46. # Determine approximate latest Firefox release
  47. browser_version="$((124 + ($(date +%s) - 1710892800) / 2419200))"
  48. echo "Mozilla/5.0 (X11; Linux x86_64; rv:${browser_version}.0) Gecko/20100101 Firefox/${browser_version}.0"
  49. return 0
  50. }
  51. download_windows() {
  52. local id="$1"
  53. local lang="$2"
  54. local desc="$3"
  55. local sku_id=""
  56. local language=""
  57. local session_id=""
  58. local user_agent=""
  59. local windows_version=""
  60. local iso_download_link=""
  61. local product_edition_id=""
  62. local iso_download_link_html=""
  63. local iso_download_page_html=""
  64. local language_skuid_table_html=""
  65. case "${id,,}" in
  66. "win11x64" ) windows_version="11" ;;
  67. "win10x64" ) windows_version="10" ;;
  68. "win81x64" ) windows_version="8" ;;
  69. * ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
  70. esac
  71. user_agent=$(get_agent)
  72. language=$(getLanguage "$lang" "name")
  73. local url="https://www.microsoft.com/en-us/software-download/windows$windows_version"
  74. case "$windows_version" in
  75. 8 | 10) url+="ISO";;
  76. esac
  77. # 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
  78. session_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)
  79. # Get product edition ID for latest release of given Windows version
  80. # 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
  81. # 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
  82. # Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
  83. # Remove "Accept" header that curl sends by default
  84. [[ "$DEBUG" == [Yy1]* ]] && echo " - Parsing download page: ${url}"
  85. iso_download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
  86. handle_curl_error $?
  87. return $?
  88. }
  89. [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: "
  90. # tr: Filter for only numerics to prevent HTTP parameter injection
  91. # head -c was recently added to POSIX: https://austingroupbugs.net/view.php?id=407
  92. product_edition_id=$(echo "$iso_download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
  93. [[ "$DEBUG" == [Yy1]* ]] && echo "$product_edition_id"
  94. [[ "$DEBUG" == [Yy1]* ]] && echo "Permit Session ID: $session_id"
  95. # Permit Session ID
  96. # "org_id" is always the same value
  97. curl --silent --max-time 30 --output /dev/null --user-agent "$user_agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=$session_id" || {
  98. # This should only happen if there's been some change to how this API works
  99. handle_curl_error $?
  100. return $?
  101. }
  102. # Extract everything after the last slash
  103. local url_segment_parameter="${url##*/}"
  104. [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: "
  105. # Get language -> skuID association table
  106. # SKU ID: This specifies the language of the ISO. We always use "English (United States)", however, the SKU for this changes with each Windows release
  107. # We must make this request so our next one will be allowed
  108. # --data "" is required otherwise no "Content-Length" header will be sent causing HTTP response "411 Length Required"
  109. language_skuid_table_html=$(curl --silent --max-time 30 --request POST --user-agent "$user_agent" --data "" --header "Accept:" --max-filesize 10K --fail --proto =https --tlsv1.2 --http1.1 -- "https://www.microsoft.com/en-US/api/controls/contentinclude/html?pageId=a8f8f489-4c7f-463a-9ca6-5cff94d8d041&host=www.microsoft.com&segments=software-download,$url_segment_parameter&query=&action=getskuinformationbyproductedition&sessionId=$session_id&productEditionId=$product_edition_id&sdVersion=2") || {
  110. handle_curl_error $?
  111. return $?
  112. }
  113. # tr: Filter for only alphanumerics or "-" to prevent HTTP parameter injection
  114. sku_id=$(echo "$language_skuid_table_html" | grep -m 1 ">${language}<" | sed 's/&quot;//g' | cut -d ',' -f 1 | cut -d ':' -f 2 | tr -cd '[:alnum:]-' | head -c 16)
  115. if [ -z "$sku_id" ]; then
  116. language=$(getLanguage "$lang" "desc")
  117. error "No download in the $language language available for $desc!"
  118. return 1
  119. fi
  120. [[ "$DEBUG" == [Yy1]* ]] && echo "$sku_id"
  121. [[ "$DEBUG" == [Yy1]* ]] && echo "Getting ISO download link..."
  122. # Get ISO download link
  123. # If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
  124. # --referer: Required by Microsoft servers to allow request
  125. iso_download_link_html=$(curl --silent --max-time 30 --request POST --user-agent "$user_agent" --data "" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://www.microsoft.com/en-US/api/controls/contentinclude/html?pageId=6e2a1789-ef16-4f27-a296-74ef7ef5d96b&host=www.microsoft.com&segments=software-download,$url_segment_parameter&query=&action=GetProductDownloadLinksBySku&sessionId=$session_id&skuId=$sku_id&language=English&sdVersion=2")
  126. if ! [ "$iso_download_link_html" ]; then
  127. # This should only happen if there's been some change to how this API works
  128. error "Microsoft servers gave us an empty response to our request for an automated download."
  129. return 1
  130. fi
  131. if echo "$iso_download_link_html" | grep -q "We are unable to complete your request at this time."; then
  132. error "Microsoft blocked the automated download request based on your IP address."
  133. return 1
  134. fi
  135. # Filter for 64-bit ISO download URL
  136. # sed: HTML decode "&" character
  137. # tr: Filter for only alphanumerics or punctuation
  138. iso_download_link=$(echo "$iso_download_link_html" | grep -o "https://software.download.prss.microsoft.com.*IsoX64" | cut -d '"' -f 1 | sed 's/&amp;/\&/g' | tr -cd '[:alnum:][:punct:]')
  139. if ! [ "$iso_download_link" ]; then
  140. # This should only happen if there's been some change to the download endpoint web address
  141. error "Microsoft servers gave us no download link to our request for an automated download!"
  142. return 1
  143. fi
  144. MIDO_URL="$iso_download_link"
  145. return 0
  146. }
  147. download_windows_eval() {
  148. local id="$1"
  149. local lang="$2"
  150. local desc="$3"
  151. local filter=""
  152. local culture=""
  153. local language=""
  154. local user_agent=""
  155. local enterprise_type=""
  156. local windows_version=""
  157. case "${id,,}" in
  158. "win11${PLATFORM,,}-enterprise-eval" )
  159. enterprise_type="enterprise"
  160. windows_version="windows-11-enterprise" ;;
  161. "win11${PLATFORM,,}-enterprise-iot-eval" )
  162. enterprise_type="iot"
  163. windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
  164. "win11${PLATFORM,,}-enterprise-ltsc-eval" )
  165. enterprise_type="iot"
  166. windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
  167. "win10${PLATFORM,,}-enterprise-eval" )
  168. enterprise_type="enterprise"
  169. windows_version="windows-10-enterprise" ;;
  170. "win10${PLATFORM,,}-enterprise-ltsc-eval" )
  171. enterprise_type="ltsc"
  172. windows_version="windows-10-enterprise" ;;
  173. "win2025-eval" )
  174. enterprise_type="server"
  175. windows_version="windows-server-2025" ;;
  176. "win2022-eval" )
  177. enterprise_type="server"
  178. windows_version="windows-server-2022" ;;
  179. "win2019-eval" )
  180. enterprise_type="server"
  181. windows_version="windows-server-2019" ;;
  182. "win2016-eval" )
  183. enterprise_type="server"
  184. windows_version="windows-server-2016" ;;
  185. "win2012r2-eval" )
  186. enterprise_type="server"
  187. windows_version="windows-server-2012-r2" ;;
  188. * )
  189. error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
  190. esac
  191. user_agent=$(get_agent)
  192. culture=$(getLanguage "$lang" "culture")
  193. local country="${culture#*-}"
  194. local iso_download_page_html=""
  195. local url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version"
  196. [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
  197. iso_download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
  198. handle_curl_error $?
  199. return $?
  200. }
  201. if ! [ "$iso_download_page_html" ]; then
  202. # This should only happen if there's been some change to where this download page is located
  203. error "Windows server download page gave us an empty response"
  204. return 1
  205. fi
  206. [[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.."
  207. if [[ "$enterprise_type" == "iot" ]]; then
  208. filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country^^}"
  209. else
  210. filter="https://go.microsoft.com/fwlink/p/?LinkID=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country^^}"
  211. fi
  212. iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || {
  213. # This should only happen if there's been some change to the download endpoint web address
  214. if [[ "${lang,,}" == "en" ]] || [[ "${lang,,}" == "en-"* ]]; then
  215. error "Windows server download page gave us no download link!"
  216. else
  217. language=$(getLanguage "$lang" "desc")
  218. error "No download in the $language language available for $desc!"
  219. fi
  220. return 1
  221. }
  222. case "$enterprise_type" in
  223. "enterprise" )
  224. iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
  225. ;;
  226. "iot" )
  227. if [[ "${PLATFORM,,}" == "x64" ]]; then
  228. iso_download_link=$(echo "$iso_download_links" | head -n 1)
  229. fi
  230. if [[ "${PLATFORM,,}" == "arm64" ]]; then
  231. iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
  232. fi
  233. ;;
  234. "ltsc" )
  235. iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1)
  236. ;;
  237. "server" )
  238. iso_download_link=$(echo "$iso_download_links" | head -n 1)
  239. ;;
  240. * )
  241. error "Invalid type specified, value \"$enterprise_type\" is not recognized!" && return 1 ;;
  242. esac
  243. [[ "$DEBUG" == [Yy1]* ]] && echo "Found download link: $iso_download_link"
  244. # Follow redirect so proceeding log message is useful
  245. # This is a request we make this Fido doesn't
  246. # We don't need to set "--max-filesize" here because this is a HEAD request and the output is to /dev/null anyway
  247. iso_download_link=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_download_link") || {
  248. # This should only happen if the Microsoft servers are down
  249. handle_curl_error $?
  250. return $?
  251. }
  252. MIDO_URL="$iso_download_link"
  253. return 0
  254. }
  255. getWindows() {
  256. local version="$1"
  257. local lang="$2"
  258. local desc="$3"
  259. local language edition
  260. language=$(getLanguage "$lang" "desc")
  261. edition=$(printEdition "$version" "$desc")
  262. local msg="Requesting $desc from Microsoft server..."
  263. info "$msg" && html "$msg"
  264. case "${version,,}" in
  265. "win2008r2" | "win81${PLATFORM,,}-enterprise"* | "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
  266. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  267. error "No download in the $language language available for $edition!"
  268. MIDO_URL="" && return 1
  269. fi ;;
  270. esac
  271. case "${version,,}" in
  272. "win11${PLATFORM,,}-enterprise-iot"* ) ;;
  273. "win11${PLATFORM,,}-enterprise-ltsc"* ) ;;
  274. * )
  275. if [[ "${PLATFORM,,}" != "x64" ]]; then
  276. error "No download for the ${PLATFORM^^} platform available for $edition!"
  277. MIDO_URL="" && return 1
  278. fi ;;
  279. esac
  280. case "${version,,}" in
  281. "win81${PLATFORM,,}" | "win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
  282. download_windows "$version" "$lang" "$edition" && return 0
  283. ;;
  284. "win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* )
  285. download_windows_eval "$version" "$lang" "$edition" && return 0
  286. ;;
  287. "win2025-eval" | "win2022-eval" | "win2019-eval" | "win2016-eval" | "win2012r2-eval" )
  288. download_windows_eval "$version" "$lang" "$edition" && return 0
  289. ;;
  290. "win81${PLATFORM,,}-enterprise"* | "win2008r2" )
  291. ;;
  292. * ) error "Invalid VERSION specified, value \"$version\" is not recognized!" ;;
  293. esac
  294. if [[ "${PLATFORM,,}" != "x64" ]]; then
  295. MIDO_URL=""
  296. return 1
  297. fi
  298. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  299. MIDO_URL=""
  300. return 1
  301. fi
  302. case "${version,,}" in
  303. "win81${PLATFORM,,}-enterprise"* )
  304. MIDO_URL="https://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO"
  305. return 0
  306. ;;
  307. "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
  308. MIDO_URL="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_CLIENT_IOT_LTSC_EVAL_x64FRE_en-us.iso"
  309. return 0
  310. ;;
  311. "win2025-eval" )
  312. MIDO_URL="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_SERVER_EVAL_x64FRE_en-us.iso"
  313. return 0
  314. ;;
  315. "win2022-eval" )
  316. MIDO_URL="https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso"
  317. return 0
  318. ;;
  319. "win2019-eval" )
  320. MIDO_URL="https://software-download.microsoft.com/download/pr/17763.737.190906-2324.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us_1.iso"
  321. return 0
  322. ;;
  323. "win2016-eval" )
  324. MIDO_URL="https://software-download.microsoft.com/download/pr/Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO"
  325. return 0
  326. ;;
  327. "win2012r2-eval" )
  328. MIDO_URL="https://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_SERVER_EVAL_EN-US-IR3_SSS_X64FREE_EN-US_DV9.ISO"
  329. return 0
  330. ;;
  331. "win2008r2" )
  332. MIDO_URL="https://download.microsoft.com/download/4/1/D/41DEA7E0-B30D-4012-A1E3-F24DC03BA1BB/7601.17514.101119-1850_x64fre_server_eval_en-us-GRMSXEVAL_EN_DVD.iso"
  333. return 0
  334. ;;
  335. esac
  336. MIDO_URL=""
  337. return 1
  338. }
  339. getCatalog() {
  340. local id="$1"
  341. local ret="$2"
  342. local url=""
  343. local name=""
  344. local edition=""
  345. case "${id,,}" in
  346. "win11${PLATFORM,,}" )
  347. edition="Professional"
  348. name="Windows 11 Pro"
  349. url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
  350. "win10${PLATFORM,,}" )
  351. edition="Professional"
  352. name="Windows 10 Pro"
  353. url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
  354. "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval")
  355. edition="Enterprise"
  356. name="Windows 11 Enterprise"
  357. url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
  358. "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" )
  359. edition="Enterprise"
  360. name="Windows 10 Enterprise"
  361. url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
  362. esac
  363. case "${ret,,}" in
  364. "url" ) echo "$url" ;;
  365. "name" ) echo "$name" ;;
  366. "edition" ) echo "$edition" ;;
  367. *) echo "";;
  368. esac
  369. return 0
  370. }
  371. getESD() {
  372. local dir="$1"
  373. local version="$2"
  374. local lang="$3"
  375. local desc="$4"
  376. local culture
  377. local language
  378. local editionName
  379. local winCatalog size
  380. culture=$(getLanguage "$lang" "culture")
  381. winCatalog=$(getCatalog "$version" "url")
  382. editionName=$(getCatalog "$version" "edition")
  383. if [ -z "$winCatalog" ] || [ -z "$editionName" ]; then
  384. error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
  385. fi
  386. local msg="Downloading product information from Microsoft server..."
  387. info "$msg" && html "$msg"
  388. rm -rf "$dir"
  389. mkdir -p "$dir"
  390. local wFile="catalog.cab"
  391. local xFile="products.xml"
  392. local eFile="esd_edition.xml"
  393. local fFile="products_filter.xml"
  394. { wget "$winCatalog" -O "$dir/$wFile" -q --timeout=30; rc=$?; } || :
  395. msg="Failed to download $winCatalog"
  396. (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
  397. (( rc == 4 )) && error "$msg , network failure!" && return 1
  398. (( rc == 8 )) && error "$msg , server issued an error response!" && return 1
  399. (( rc != 0 )) && error "$msg , reason: $rc" && return 1
  400. cd "$dir"
  401. if ! cabextract "$wFile" > /dev/null; then
  402. cd /run
  403. error "Failed to extract $wFile!" && return 1
  404. fi
  405. cd /run
  406. if [ ! -s "$dir/$xFile" ]; then
  407. error "Failed to find $xFile in $wFile!" && return 1
  408. fi
  409. local edQuery='//File[Architecture="'${PLATFORM}'"][Edition="'${editionName}'"]'
  410. echo -e '<Catalog>' > "$dir/$fFile"
  411. xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" >> "$dir/$fFile" 2>/dev/null
  412. echo -e '</Catalog>'>> "$dir/$fFile"
  413. xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$fFile" >"$dir/$eFile"
  414. size=$(stat -c%s "$dir/$eFile")
  415. if ((size<20)); then
  416. desc=$(printEdition "$version" "$desc")
  417. language=$(getLanguage "$lang" "desc")
  418. error "No download in the $language language available for $desc!" && return 1
  419. fi
  420. local tag="FilePath"
  421. ESD=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  422. if [ -z "$ESD" ]; then
  423. error "Failed to find ESD URL in $eFile!" && return 1
  424. fi
  425. tag="Sha1"
  426. ESD_SUM=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  427. tag="Size"
  428. ESD_SIZE=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  429. rm -rf "$dir"
  430. return 0
  431. }
  432. verifyFile() {
  433. local iso="$1"
  434. local size="$2"
  435. local total="$3"
  436. local check="$4"
  437. if [ -n "$size" ] && [[ "$total" != "$size" ]] && [[ "$size" != "0" ]]; then
  438. warn "The downloaded file has an unexpected size: $total bytes, while expected value was: $size bytes. Please report this at $SUPPORT/issues"
  439. fi
  440. local hash=""
  441. local algo="SHA256"
  442. [ -z "$check" ] && return 0
  443. [[ "$VERIFY" != [Yy1]* ]] && return 0
  444. [[ "${#check}" == "40" ]] && algo="SHA1"
  445. local msg="Verifying downloaded ISO..."
  446. info "$msg" && html "$msg"
  447. if [[ "${algo,,}" != "sha256" ]]; then
  448. hash=$(sha1sum "$iso" | cut -f1 -d' ')
  449. else
  450. hash=$(sha256sum "$iso" | cut -f1 -d' ')
  451. fi
  452. if [[ "$hash" == "$check" ]]; then
  453. info "Succesfully verified ISO!" && return 0
  454. fi
  455. error "The downloaded file has an invalid $algo checksum: $hash , while expected value was: $check. Please report this at $SUPPORT/issues"
  456. return 1
  457. }
  458. downloadFile() {
  459. local iso="$1"
  460. local url="$2"
  461. local sum="$3"
  462. local size="$4"
  463. local lang="$5"
  464. local desc="$6"
  465. local rc total progress domain dots space folder
  466. rm -f "$iso"
  467. if [ -n "$size" ] && [[ "$size" != "0" ]]; then
  468. folder=$(dirname -- "$iso")
  469. space=$(df --output=avail -B 1 "$folder" | tail -n 1)
  470. (( size > space )) && error "Not enough free space left to download file!" && return 1
  471. fi
  472. # Check if running with interactive TTY or redirected to docker log
  473. if [ -t 1 ]; then
  474. progress="--progress=bar:noscroll"
  475. else
  476. progress="--progress=dot:giga"
  477. fi
  478. local msg="Downloading $desc"
  479. html "$msg..."
  480. domain=$(echo "$url" | awk -F/ '{print $3}')
  481. dots=$(echo "$domain" | tr -cd '.' | wc -c)
  482. (( dots > 1 )) && domain=$(expr "$domain" : '.*\.\(.*\..*\)')
  483. if [ -n "$domain" ] && [[ "${domain,,}" != *"microsoft.com" ]]; then
  484. msg="Downloading $desc from $domain"
  485. fi
  486. info "$msg..."
  487. /run/progress.sh "$iso" "$size" "$msg ([P])..." &
  488. { wget "$url" -O "$iso" -q --timeout=30 --show-progress "$progress"; rc=$?; } || :
  489. fKill "progress.sh"
  490. if (( rc == 0 )) && [ -f "$iso" ]; then
  491. total=$(stat -c%s "$iso")
  492. if [ "$total" -lt 100000000 ]; then
  493. error "Invalid download link: $url (is only $total bytes?). Please report this at $SUPPORT/issues." && return 1
  494. fi
  495. verifyFile "$iso" "$size" "$total" "$sum" || return 1
  496. html "Download finished successfully..." && return 0
  497. fi
  498. msg="Failed to download $url"
  499. (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
  500. (( rc == 4 )) && error "$msg , network failure!" && return 1
  501. (( rc == 8 )) && error "$msg , server issued an error response!" && return 1
  502. error "$msg , reason: $rc"
  503. return 1
  504. }
  505. downloadImage() {
  506. local iso="$1"
  507. local version="$2"
  508. local lang="$3"
  509. local tried="n"
  510. local url sum size base desc language
  511. if [[ "${version,,}" == "http"* ]]; then
  512. base=$(basename "$iso")
  513. desc=$(fromFile "$base")
  514. downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
  515. rm -f "$iso"
  516. return 1
  517. fi
  518. if ! validVersion "$version" "en"; then
  519. error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
  520. fi
  521. desc=$(printVersion "$version" "")
  522. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  523. language=$(getLanguage "$lang" "desc")
  524. if ! validVersion "$version" "$lang"; then
  525. desc=$(printEdition "$version" "$desc")
  526. error "The $language language version of $desc is not available, please switch to English." && return 1
  527. fi
  528. desc+=" in $language"
  529. fi
  530. if isMido "$version" "$lang"; then
  531. tried="y"
  532. if getWindows "$version" "$lang" "$desc"; then
  533. size=$(getMido "$version" "$lang" "size" )
  534. sum=$(getMido "$version" "$lang" "sum")
  535. downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
  536. rm -f "$iso"
  537. fi
  538. fi
  539. switchEdition "$version"
  540. if isESD "$version" "$lang"; then
  541. if [[ "$tried" != "n" ]]; then
  542. info "Failed to download $desc, will try a diferent method now..."
  543. fi
  544. tried="y"
  545. if getESD "$TMP/esd" "$version" "$lang" "$desc"; then
  546. ISO="${ISO%.*}.esd"
  547. downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
  548. rm -f "$ISO"
  549. ISO="$iso"
  550. fi
  551. fi
  552. for ((i=1;i<=MIRRORS;i++)); do
  553. url=$(getLink "$i" "$version" "$lang")
  554. if [ -n "$url" ]; then
  555. if [[ "$tried" != "n" ]]; then
  556. info "Failed to download $desc, will try another mirror now..."
  557. fi
  558. tried="y"
  559. size=$(getSize "$i" "$version" "$lang")
  560. sum=$(getHash "$i" "$version" "$lang")
  561. downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
  562. rm -f "$iso"
  563. fi
  564. done
  565. return 1
  566. }
  567. return 0