Windows stub installer code only - Bug 811573 - 'Add more data points to the metrics ping for the stub installer'. Also fixes bug 797998 - 'In the download phase of the stub installer without an internet connection, the stub installer should eventually time out and report an error' and bug 836044 - 'Aurora stub installer doesn't seem to be working'. r=bbondy
authorRobert Strong <robert.bugzilla@gmail.com>
Tue, 05 Mar 2013 22:07:59 -0800
changeset 123956 216ec69cc53160f617a5e6b317554e9cb0a03869
parent 123850 3d81fc67e6ed96566a1248538bc8d561aa071555
child 123957 635f2c7660c8abb1c814314447088b69c91b5d11
child 127420 222fe7d597a3cacd8cc5612a46e225bc383e0179
push id24156
push useremorley@mozilla.com
push dateWed, 06 Mar 2013 16:11:48 +0000
treeherdermozilla-inbound@310b85d5fa77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbondy
bugs811573, 797998, 836044
milestone22.0a1
first release with
nightly linux32
216ec69cc531 / 22.0a1 / 20130306031012 / files
nightly linux64
216ec69cc531 / 22.0a1 / 20130306031012 / files
nightly mac
216ec69cc531 / 22.0a1 / 20130306031012 / files
nightly win32
216ec69cc531 / 22.0a1 / 20130306031012 / files
nightly win64
216ec69cc531 / 22.0a1 / 20130306031012 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Windows stub installer code only - Bug 811573 - 'Add more data points to the metrics ping for the stub installer'. Also fixes bug 797998 - 'In the download phase of the stub installer without an internet connection, the stub installer should eventually time out and report an error' and bug 836044 - 'Aurora stub installer doesn't seem to be working'. r=bbondy
browser/branding/official/branding.nsi
browser/installer/windows/nsis/defines.nsi.in
browser/installer/windows/nsis/stub.nsi
other-licenses/nsis/Contrib/InetBgDL/InetBgDL.cpp
other-licenses/nsis/Contrib/InetBgDL/InetBgDL.h
other-licenses/nsis/Plugins/InetBgDL.dll
--- a/browser/branding/official/branding.nsi
+++ b/browser/branding/official/branding.nsi
@@ -8,19 +8,20 @@
 
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Mozilla Firefox"
 !define CompanyName           "Mozilla Corporation"
 !define URLInfoAbout          "http://www.mozilla.com/${AB_CD}/"
 !define URLUpdateInfo         "http://www.mozilla.com/${AB_CD}/firefox/"
 
-; Prevents the beta channel urls in stub.nsi from being used when not using
-; official branding
-!define Official
+; The OFFICIAL define is a workaround to support different urls for Release and
+; Beta since they share the same branding when building with other branches that
+; set the update channel to beta.
+!define OFFICIAL
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-latest&os=win&lang=${AB_CD}"
 !define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=release"
 !define Channel "release"
 
 # The installer's certificate name and issuer expected by the stub installer
 !define CertNameDownload   "Mozilla Corporation"
 !define CertIssuerDownload "Thawte Code Signing CA - G2"
 
--- a/browser/installer/windows/nsis/defines.nsi.in
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -24,23 +24,29 @@
 !define CERTIFICATE_ISSUER    "Thawte Code Signing CA - G2"
 
 # LSP_CATEGORIES is the permitted LSP categories for the application. Each LSP
 # category value is ANDed together to set multiple permitted categories.
 # See http://msdn.microsoft.com/en-us/library/ms742253%28VS.85%29.aspx
 # The value below removes all LSP categories previously set.
 !define LSP_CATEGORIES "0x00000000"
 
+!if "@MOZ_UPDATE_CHANNEL@" == ""
+!define UpdateChannel "Unknown"
+!else
+!define UpdateChannel "@MOZ_UPDATE_CHANNEL@"
+!endif
+
 # Due to official and beta using the same branding this is needed to
 # differentiante between the url used by the stub for downloading.
 !if "@MOZ_UPDATE_CHANNEL@" == "beta"
 !define BETA_UPDATE_CHANNEL
 !endif
 
-!define BaseURLStubPing "http://download-stats.mozilla.org/stub/v4/"
+!define BaseURLStubPing "http://download-stats.mozilla.org/stub"
 
 # NO_INSTDIR_FROM_REG is defined for pre-releases which have a PreReleaseSuffix
 # (e.g. Alpha X, Beta X, etc.) to prevent finding a non-default installation
 # directory in the registry and using that as the default. This prevents
 # Beta releases built with official branding from finding an existing install
 # of an official release and defaulting to its installation directory.
 !if "@PRE_RELEASE_SUFFIX@" != ""
 !define NO_INSTDIR_FROM_REG
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -51,97 +51,177 @@ Var FontNormal
 Var FontItalic
 Var FontBlurb
 
 Var WasOptionsButtonClicked
 Var CanWriteToInstallDir
 Var HasRequiredSpaceAvailable
 Var IsDownloadFinished
 Var Initialized
-Var DownloadSize
+Var DownloadSizeBytes
 Var HalfOfDownload
 Var DownloadReset
 Var ExistingTopDir
 Var SpaceAvailableBytes
 Var InitialInstallDir
 Var HandleDownload
 Var CanSetAsDefault
+Var InstallCounterStep
 Var TmpVal
-Var InstallCounterStep
 
 Var ExitCode
-Var StartTickCount
-Var DownloadTickCount
-Var InstallTickCount
-Var FinishTickCount
+Var FirefoxLaunchCode
+
+; The first three tick counts are for the start of a phase and equate equate to
+; the display of individual installer pages.
+Var StartIntroPhaseTickCount
+Var StartOptionsPhaseTickCount
+Var StartDownloadPhaseTickCount
+; Since the Intro and Options pages can be displayed multiple times the total
+; seconds spent on each of these pages is reported.
+Var IntroPhaseSeconds
+Var OptionsPhaseSeconds
+; The tick count for the last download
+Var StartLastDownloadTickCount
+; The number of seconds from the start of the download phase until the first
+; bytes are received. This is only recorded for first request so it is possible
+; to determine connection issues for the first request.
+Var DownloadFirstTransferSeconds
+; The last four tick counts are for the end of a phase in the installation page.
+; the options phase when it isn't entered.
+Var EndDownloadPhaseTickCount
+Var EndPreInstallPhaseTickCount
+Var EndInstallPhaseTickCount
+Var EndFinishPhaseTickCount
+
+Var IntroPageShownCount
+Var OptionsPageShownCount
+Var InitialInstallRequirementsCode
 Var ExistingProfile
-Var ExistingInstall
-Var DownloadedAmount
-Var FirefoxLaunch
+Var ExistingVersion
+Var ExistingBuildID
+Var DownloadedBytes
+Var DownloadRetryCount
+Var OpenedDownloadPage
+Var DownloadServerIP
 
-Var HEIGHT_PX
-Var CTL_RIGHT_PX
+Var ControlHeightPX
+Var ControlRightPX
+
+; Uncomment the following to prevent pinging the metrics server when testing
+; the stub installer
+;!define STUB_DEBUG
+
+!define StubURLVersion "v5"
+
+; Successful install exit code
+!define ERR_SUCCESS 0
 
-!define ERR_SUCCESS 0
-!define ERR_CANCEL_DOWNLOAD 10
-!define ERR_INVALID_HANDLE 11
-!define ERR_CERT_UNTRUSTED 12
-!define ERR_CERT_ATTRIBUTES 13
-!define ERR_CERT_UNTRUSTED_AND_ATTRIBUTES 14
-!define ERR_CHECK_INSTALL_TIMEOUT 15
-!define ERR_UNKNOWN 99
+/**
+ * The following errors prefixed with ERR_DOWNLOAD apply to the download phase.
+ */
+; The download was cancelled by the user
+!define ERR_DOWNLOAD_CANCEL 10
+
+; Too many attempts to download. The maximum attempts is defined in
+; DownloadMaxRetries.
+!define ERR_DOWNLOAD_TOO_MANY_RETRIES 11
+
+/**
+ * The following errors prefixed with ERR_PREINSTALL apply to the pre-install
+ * check phase.
+ */
+; Unable to acquire a file handle to the downloaded file
+!define ERR_PREINSTALL_INVALID_HANDLE 20
+
+; The downloaded file's certificate is not trusted by the certificate store.
+!define ERR_PREINSTALL_CERT_UNTRUSTED 21
+
+; The downloaded file's certificate attribute values were incorrect.
+!define ERR_PREINSTALL_CERT_ATTRIBUTES 22
+
+; The downloaded file's certificate is not trusted by the certificate store and
+; certificate attribute values were incorrect.
+!define ERR_PREINSTALL_CERT_UNTRUSTED_AND_ATTRIBUTES 23
 
-!define DownloadIntervalMS 200 ; Interval for the download timer
-!define InstallIntervalMS 100 ; Interval for the install timer
+/**
+ * The following errors prefixed with ERR_INSTALL apply to the install phase.
+ */
+; The installation timed out. The installation timeout is defined by the number
+; of progress steps defined in InstallProgresSteps and the install timer
+; interval defined in InstallIntervalMS
+!define ERR_INSTALL_TIMEOUT 30
+
+; Maximum times to retry the download before displaying an error
+!define DownloadMaxRetries 9
+
+; Minimum size expected to download in bytes
+!define DownloadMinSizeBytes 15728640 ; 15 MB
+
+; Maximum size expected to download in bytes
+!define DownloadMaxSizeBytes 36700160 ; 35 MB
+
+; Interval before retrying to download. 3 seconds is used along with 10
+; attempted downloads (the first attempt along with 9 retries) to give a
+; minimum of 30 seconds or retrying before giving up.
+!define DownloadRetryIntervalMS 3000
+
+; Interval for the download timer
+!define DownloadIntervalMS 200
+
+; Interval for the install timer
+!define InstallIntervalMS 100
 
 ; Number of steps for the install progress.
 ; This is 120 seconds with a 100 millisecond timer and a first step of 20 as
 ; defined by InstallProgressFirstStep. This might not be enough when installing
 ; on a slow network drive so it will fallback to downloading the full installer
 ; if it reaches this number. The size of the install progress step increases
 ; when the full installer finishes instead of waiting the entire 120 seconds.
 !define InstallProgresSteps 1220
+
 ; The first step for the install progress bar. By starting with a large step
 ; immediate feedback is given to the user.
 !define InstallProgressFirstStep 20
 
+; The interval in MS used for the progress bars set as marquee.
+!define ProgressbarMarqueeIntervalMS 10
+
 ; On Vista and above attempt to elevate Standard Users in addition to users that
 ; are a member of the Administrators group.
 !define NONADMIN_ELEVATE
 
 !define CONFIG_INI "config.ini"
 
 !define MAX_PATH 260
 
 !define FILE_SHARE_READ 1
 !define GENERIC_READ 0x80000000
 !define OPEN_EXISTING 3
-!define FILE_BEGIN 0
-!define FILE_END 2
 !define INVALID_HANDLE_VALUE -1
-!define INVALID_FILE_SIZE 0xffffffff
 
 !include "nsDialogs.nsh"
 !include "LogicLib.nsh"
 !include "FileFunc.nsh"
 !include "WinVer.nsh"
 !include "WordFunc.nsh"
 
 !insertmacro GetParameters
 !insertmacro GetOptions
 !insertmacro StrFilter
 
 !include "locales.nsi"
 !include "branding.nsi"
 
 !include "defines.nsi"
 
-; Workaround to support different urls for Official and Beta since they share
-; the same branding.
-!ifdef Official
+; The OFFICIAL define is a workaround to support different urls for Release and
+; Beta since they share the same branding when building with other branches that
+; set the update channel to beta.
+!ifdef OFFICIAL
 !ifdef BETA_UPDATE_CHANNEL
 !undef URLStubDownload
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-beta-latest&os=win&lang=${AB_CD}"
 !undef URLManualDownload
 !define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=beta"
 !undef Channel
 !define Channel "beta"
 !endif
@@ -197,17 +277,17 @@ ChangeUI all "nsisui.exe"
   !define INSTALL_BLURB3 "$(INSTALL_BLURB3)"
 !endif
 
 Caption "$(WIN_CAPTION)"
 
 Page custom createDummy ; Needed to enable the Intro page's back button
 Page custom createIntro leaveIntro ; Introduction page
 Page custom createOptions leaveOptions ; Options page
-Page custom createInstall leaveInstall ; Download / Installation page
+Page custom createInstall ; Download / Installation page
 
 Function .onInit
   ; Remove the current exe directory from the search order.
   ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
   System::Call 'kernel32::SetDllDirectoryW(w "")'
 
   StrCpy $LANGUAGE 0
   ; This macro is used to set the brand name variables but the ini file method
@@ -235,17 +315,17 @@ Function .onInit
   ${EndIf}
 
   ${If} $R8 == "1"
     ; XXX-rstrong - some systems failed the AtLeastWin2000 test that we
     ; used to use for an unknown reason and likely fail the AtMostWin2000
     ; and possibly the IsWinXP test as well. To work around this also
     ; check if the Windows NT registry Key exists and if it does if the
     ; first char in CurrentVersion is equal to 3 (Windows NT 3.5 and
-    ; 3.5.1), to 4 (Windows NT 4) or 5 (Windows 2000 and Windows XP).
+    ; 3.5.1), 4 (Windows NT 4), or 5 (Windows 2000 and Windows XP).
     StrCpy $R8 ""
     ClearErrors
     ReadRegStr $R8 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" "CurrentVersion"
     StrCpy $R8 "$R8" 1
     ${If} ${Errors}
     ${OrIf} "$R8" == "3"
     ${OrIf} "$R8" == "4"
     ${OrIf} "$R8" == "5"
@@ -313,19 +393,27 @@ Function .onInit
   ${OrIf} ${AtLeastWin8}
     StrCpy $CanSetAsDefault "false"
     StrCpy $CheckboxSetAsDefault "0"
   ${Else}
     DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
     StrCpy $CanSetAsDefault "true"
   ${EndIf}
 
+  ; Initialize the majority of variables except those that need to be reset
+  ; when a page is displayed.
+  StrCpy $IntroPhaseSeconds "0"
+  StrCpy $OptionsPhaseSeconds "0"
+  StrCpy $EndPreInstallPhaseTickCount "0"
+  StrCpy $EndInstallPhaseTickCount "0"
+  StrCpy $IntroPageShownCount "0"
+  StrCpy $OptionsPageShownCount "0"
+  StrCpy $InitialInstallRequirementsCode ""
   StrCpy $IsDownloadFinished ""
-  StrCpy $FirefoxLaunch "0"
-  StrCpy $ExitCode "${ERR_UNKNOWN}"
+  StrCpy $FirefoxLaunchCode "0"
 
   CreateFont $FontBlurb "$(^Font)" "12" "500"
   CreateFont $FontNormal "$(^Font)" "11" "500"
   CreateFont $FontItalic "$(^Font)" "11" "500" /ITALIC
 
   InitPluginsDir
   File /oname=$PLUGINSDIR\bgintro.bmp "bgintro.bmp"
   File /oname=$PLUGINSDIR\bgplain.bmp "bgplain.bmp"
@@ -347,69 +435,207 @@ Function .onGUIInit
   ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LAYOUTRTL}
   ${RemoveExStyle} $HWNDPARENT ${WS_EX_RTLREADING}
   ${RemoveExStyle} $HWNDPARENT ${WS_EX_RIGHT}
   ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LEFT}|${WS_EX_LTRREADING}
 FunctionEnd
 !endif
 
 Function .onGUIEnd
-  ; Try to send a ping if a download was attempted
-  ${If} $IsDownloadFinished != ""
-  ${AndIf} $CheckboxSendPing == 1
-    ${If} $IsDownloadFinished == "false"
-      ; When the value of $IsDownloadFinished is false the download was started
-      ; but didn't finish and GetTickCount needs to be called to determine how
-      ; long the download was in progress.
-      System::Call "kernel32::GetTickCount()l .s"
-      Pop $DownloadTickCount
-      StrCpy $1 "0"
-      StrCpy $2 "0"
-
-      ; Cancel the download in progress
-      InetBgDL::Get /RESET /END
-    ${Else}
-      ; Get the tick count for when the installer closes.
-      System::Call "kernel32::GetTickCount()l .s"
-      Pop $FinishTickCount
-      ; Get the time from the end of the install to close the installer.
-      ${GetSecondsElapsed} "$InstallTickCount" "$FinishTickCount" $2
-
-      ; Get the time from the end of the download to the completion of the
-      ; installation.
-      ${GetSecondsElapsed} "$DownloadTickCount" "$InstallTickCount" $1
-    ${EndIf}
-
-    ; Get the time from the start of the download to the end of the download.
-    ${GetSecondsElapsed} "$StartTickCount" "$DownloadTickCount" $0
-
-    System::Int64Op $DownloadedAmount / 1024
-    Pop $DownloadedAmount
-
-    InetBgDL::Get "${BaseURLStubPing}${Channel}/${AB_CD}/$ExitCode/$FirefoxLaunch/$DownloadedAmount/$0/$1/$2/$ExistingProfile/$ExistingInstall/" \
-                  "$PLUGINSDIR\_temp" /END
-  ${ElseIf} $IsDownloadFinished == "false"
-    ; Cancel the download in progress
-    InetBgDL::Get /RESET /END
-  ${EndIf}
-
   ${UnloadUAC}
 FunctionEnd
 
 Function .onUserAbort
   ${NSD_KillTimer} StartDownload
   ${NSD_KillTimer} OnDownload
   ${NSD_KillTimer} StartInstall
   ${NSD_KillTimer} CheckInstall
   ${NSD_KillTimer} FinishInstall
   ${NSD_KillTimer} DisplayDownloadError
 
   Delete "$PLUGINSDIR\_temp"
   Delete "$PLUGINSDIR\download.exe"
   Delete "$PLUGINSDIR\${CONFIG_INI}"
+  ${If} "$IsDownloadFinished" == ""
+  ${OrIf} $CheckboxSendPing != 1
+    ; When not sending a ping cancel the download if it is in progress and exit
+    ; the installer.
+    ${If} "$IsDownloadFinished" == "false"
+      HideWindow
+      InetBgDL::Get /RESET /END
+    ${EndIf}
+  ${Else}
+    Call SendPing
+    ; Aborting the abort will allow SendPing to hide the installer window and
+    ; close the installer after it sends the metrics ping.
+    Abort 
+  ${EndIf}
+FunctionEnd
+
+Function SendPing
+  HideWindow
+  ; Try to send a ping if a download was attempted
+  ${If} $CheckboxSendPing == 1
+  ${AndIf} $IsDownloadFinished != ""
+    ; Get the tick count for the completion of all phases.
+    System::Call "kernel32::GetTickCount()l .s"
+    Pop $EndFinishPhaseTickCount
+
+    ; When the value of $IsDownloadFinished is false the download was started
+    ; but didn't finish. In this case the tick count stored in
+    ; $EndFinishPhaseTickCount is used to determine how long the download was
+    ; in progress.
+    ${If} "$IsDownloadFinished" == "false"
+      StrCpy $EndDownloadPhaseTickCount "$EndFinishPhaseTickCount"
+      ; Cancel the download in progress
+      InetBgDL::Get /RESET /END
+    ${EndIf}
+
+
+    ; When $DownloadFirstTransferSeconds equals an empty string the download
+    ; never successfully started so set the value to 0. It will be possible to
+    ; determine that the download didn't successfully start from the seconds for
+    ; the last download.
+    ${If} "$DownloadFirstTransferSeconds" == ""
+      StrCpy $DownloadFirstTransferSeconds "0"
+    ${EndIf}
+
+    ; When $StartLastDownloadTickCount equals 0 the download never successfully
+    ; started so set the value to $EndDownloadPhaseTickCount to compute the
+    ; correct value.
+    ${If} $StartLastDownloadTickCount == "0"
+      ; This could happen if the download never successfully starts
+      StrCpy $StartLastDownloadTickCount "$EndDownloadPhaseTickCount"
+    ${EndIf}
+
+    ; When $EndPreInstallPhaseTickCount equals 0 the installation phase was
+    ; never completed so set its value to $EndFinishPhaseTickCount to compute
+    ; the correct value.
+    ${If} "$EndPreInstallPhaseTickCount" == "0"
+      StrCpy $EndPreInstallPhaseTickCount "$EndFinishPhaseTickCount"
+    ${EndIf}
+
+    ; When $EndInstallPhaseTickCount equals 0 the installation phase was never
+    ; completed so set its value to $EndFinishPhaseTickCount to compute the
+    ; correct value.
+    ${If} "$EndInstallPhaseTickCount" == "0"
+      StrCpy $EndInstallPhaseTickCount "$EndFinishPhaseTickCount"
+    ${EndIf}
+
+    ; Get the seconds elapsed from the start of the download phase to the end of
+    ; the download phase.
+    ${GetSecondsElapsed} "$StartDownloadPhaseTickCount" "$EndDownloadPhaseTickCount" $0
+
+    ; Get the seconds elapsed from the start of the last download to the end of
+    ; the last download.
+    ${GetSecondsElapsed} "$StartLastDownloadTickCount" "$EndDownloadPhaseTickCount" $1
+
+    ; Get the seconds elapsed from the end of the download phase to the
+    ; completion of the pre-installation check phase.
+    ${GetSecondsElapsed} "$EndDownloadPhaseTickCount" "$EndPreInstallPhaseTickCount" $2
+
+    ; Get the seconds elapsed from the end of the pre-installation check phase
+    ; to the completion of the installation phase.
+    ${GetSecondsElapsed} "$EndPreInstallPhaseTickCount" "$EndInstallPhaseTickCount" $3
+
+    ; Get the seconds elapsed from the end of the installation phase to the
+    ; completion of all phases.
+    ${GetSecondsElapsed} "$EndInstallPhaseTickCount" "$EndFinishPhaseTickCount" $4
+
+!ifdef HAVE_64BIT_OS
+    StrCpy $R0 "1"
+!else
+    StrCpy $R0 "0"
+!endif
+
+    ${If} ${RunningX64}
+      StrCpy $R1 "1"
+    ${Else}
+      StrCpy $R1 "0"
+    ${EndIf}
+
+    ${WinVerGetMajor} $R2
+    ${WinVerGetMinor} $R3
+    ${WinVerGetBuild} $R4
+
+    ${If} "$ExitCode" == "${ERR_SUCCESS}"
+      ReadINIStr $R5 "$INSTDIR\application.ini" "App" "Version"
+      ReadINIStr $R6 "$INSTDIR\application.ini" "App" "BuildID"
+    ${Else}
+      StrCpy $R5 "0"
+      StrCpy $R6 "0"
+    ${EndIf}
+
+    ; Whether installed into the default installation directory
+    ${GetLongPath} "$INSTDIR" $R7
+    ${GetLongPath} "$InitialInstallDir" $R8
+    ${If} "$R7" == "$R8"
+      StrCpy $R7 "1"
+    ${Else}
+      StrCpy $R7 "0"
+    ${EndIf}
+
+    ClearErrors
+    WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
+                     "Write Test"
+    ${If} ${Errors}
+      StrCpy $R8 "0"
+    ${Else}
+      DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+      StrCpy $R8 "1"
+    ${EndIf}
+
+    ${If} "$DownloadServerIP" == ""
+      StrCpy $DownloadServerIP "Unknown"
+    ${EndIf}
+
+!ifdef STUB_DEBUG
+    MessageBox MB_OK "${BaseURLStubPing} \
+                      $\nStub URL Version = ${StubURLVersion} \
+                      $\nBuild Channel = ${Channel} \
+                      $\nUpdate Channel = ${UpdateChannel} \
+                      $\nLocale = ${AB_CD} \
+                      $\nFirefox x64 = $R0 \
+                      $\nRunning x64 Windows = $R1 \
+                      $\nMajor = $R2 \
+                      $\nMinor = $R3 \
+                      $\nBuild = $R4 \
+                      $\nExit Code = $ExitCode \
+                      $\nFirefox Launch Code = $FirefoxLaunchCode \
+                      $\nDownload Retry Count = $DownloadRetryCount \
+                      $\nDownloaded Bytes = $DownloadedBytes \
+                      $\nIntroduction Phase Seconds = $IntroPhaseSeconds \
+                      $\nOptions Phase Seconds = $OptionsPhaseSeconds \
+                      $\nDownload Phase Seconds = $0 \
+                      $\nDownload First Transfer Seconds = $DownloadFirstTransferSeconds \
+                      $\nLast Download Seconds = $1 \
+                      $\nPreinstall Phase Seconds = $2 \
+                      $\nInstall Phase Seconds = $3 \
+                      $\nFinish Phase Seconds = $4 \
+                      $\nIntro Page Shown Count = $IntroPageShownCount \
+                      $\nOptions Page Shown Count = $OptionsPageShownCount \
+                      $\nInitial Install Requirements Code = $InitialInstallRequirementsCode \
+                      $\nOpened Download Page = $OpenedDownloadPage \
+                      $\nExisting Profile = $ExistingProfile \
+                      $\nExisting Version = $ExistingVersion \
+                      $\nExisting Build ID = $ExistingBuildID \
+                      $\nNew Version = $R5 \
+                      $\nNew Build ID = $R6 \
+                      $\nDefault Install Dir = $R7 \
+                      $\nHas Admin = $R8 \
+                      $\nDownload Server IP = $DownloadServerIP"
+!else
+    ${NSD_CreateTimer} OnPing ${DownloadIntervalMS}
+    InetBgDL::Get "${BaseURLStubPing}/${StubURLVersion}/${Channel}/${UpdateChannel}/${AB_CD}/$R0/$R1/$R2/$R3/$R4/$ExitCode/$FirefoxLaunchCode/$DownloadRetryCount/$DownloadedBytes/$IntroPhaseSeconds/$OptionsPhaseSeconds/$0/$1/$DownloadFirstTransferSeconds/$2/$3/$4/$IntroPageShownCount/$OptionsPageShownCount/$InitialInstallRequirementsCode/$OpenedDownloadPage/$ExistingProfile/$ExistingVersion/$ExistingBuildID/$R5/$R6/$R7/$R8/$DownloadServerIP" \
+                  "$PLUGINSDIR\_temp" /END
+!endif
+  ${ElseIf} "$IsDownloadFinished" == "false"
+    ; Cancel the download in progress
+    InetBgDL::Get /RESET /END
+  ${EndIf}
 FunctionEnd
 
 Function createDummy
 FunctionEnd
 
 Function createIntro
   ; If Back is clicked on the options page reset variables
   StrCpy $INSTDIR "$InitialInstallDir"
@@ -417,56 +643,63 @@ Function createIntro
   StrCpy $CheckboxShortcutInStartMenu "1"
   StrCpy $CheckboxShortcutOnDesktop "1"
   StrCpy $CheckboxSendPing "1"
 !ifdef MOZ_MAINTENANCE_SERVICE
   StrCpy $CheckboxInstallMaintSvc "1"
 !else
   StrCpy $CheckboxInstallMaintSvc "0"
 !endif
+  StrCpy $WasOptionsButtonClicked "0"
 
   nsDialogs::Create /NOUNLOAD 1018
   Pop $Dialog
 
-  StrCpy $WasOptionsButtonClicked ""
-
   GetFunctionAddress $0 OnBack
   nsDialogs::OnBack /NOUNLOAD $0
 
 !ifdef ${AB_CD}_rtl
   ; For RTL align the text with the top of the F in the Firefox bitmap
   StrCpy $0 "${INTRO_BLURB_RTL_TOP_DU}"
 !else
   ; For LTR align the text with the top of the x in the Firefox bitmap
   StrCpy $0 "${INTRO_BLURB_LTR_TOP_DU}"
 !endif
   ${NSD_CreateLabel} ${INTRO_BLURB_EDGE_DU} $0 ${INTRO_BLURB_WIDTH_DU} 76u "${INTRO_BLURB}"
   Pop $0
   SendMessage $0 ${WM_SETFONT} $FontBlurb 0
   SetCtlColors $0 ${INTRO_BLURB_TEXT_COLOR} transparent
 
-  ${Unless} $Initialized == "true"
+  ${If} "$Initialized" == "true"
+    ; When the user clicked back from the options page.
+    System::Call "kernel32::GetTickCount()l .s"
+    Pop $0
+    ${GetSecondsElapsed} "$StartOptionsPhaseTickCount" "$0" $1
+    ; This is added to the previous value of $OptionsPhaseSeconds because the
+    ; options page can be displayed multiple times.
+    IntOp $OptionsPhaseSeconds $OptionsPhaseSeconds + $1
+  ${Else}
     SetCtlColors $HWNDPARENT ${FOOTER_CONTROL_TEXT_COLOR_NORMAL} ${FOOTER_BKGRD_COLOR}
     GetDlgItem $0 $HWNDPARENT 10 ; Default browser checkbox
     ; Set as default is not supported in the installer for Win8 and above so
     ; only display it on Windows 7 and below
-    ${If} $CanSetAsDefault == "true"
+    ${If} "$CanSetAsDefault" == "true"
       ; The uxtheme must be disabled on checkboxes in order to override the
       ; system font color.
       System::Call 'uxtheme::SetWindowTheme(i $0 , w " ", w " ")'
       SendMessage $0 ${WM_SETFONT} $FontNormal 0
       SendMessage $0 ${WM_SETTEXT} 0 "STR:$(MAKE_DEFAULT)"
       SendMessage $0 ${BM_SETCHECK} 1 0
       SetCtlColors $0 ${FOOTER_CONTROL_TEXT_COLOR_NORMAL} ${FOOTER_BKGRD_COLOR}
     ${Else}
       ShowWindow $0 ${SW_HIDE}
     ${EndIf}
     GetDlgItem $0 $HWNDPARENT 11
     ShowWindow $0 ${SW_HIDE}
-  ${EndUnless}
+  ${EndIf}
 
   ${NSD_CreateBitmap} ${APPNAME_BMP_EDGE_DU} ${APPNAME_BMP_TOP_DU} \
                       ${APPNAME_BMP_WIDTH_DU} ${APPNAME_BMP_HEIGHT_DU} ""
   Pop $2
   ${SetStretchedTransparentImage} $2 $PLUGINSDIR\appname.bmp $0
 
   ${NSD_CreateBitmap} 0 0 100% 100% ""
   Pop $2
@@ -477,27 +710,40 @@ Function createIntro
   ${NSD_SetFocus} $0
 
   GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
   SendMessage $0 ${WM_SETTEXT} 0 "STR:$(CANCEL_BUTTON)"
 
   GetDlgItem $0 $HWNDPARENT 3 ; Back and Options button
   SendMessage $0 ${WM_SETTEXT} 0 "STR:$(OPTIONS_BUTTON)"
 
+  IntOp $IntroPageShownCount $IntroPageShownCount + 1
+
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $StartIntroPhaseTickCount
+
   LockWindow off
   nsDialogs::Show
 
   ${NSD_FreeImage} $0
   ${NSD_FreeImage} $1
 
   StrCpy $Initialized "true"
 FunctionEnd
 
 Function leaveIntro
   LockWindow on
+
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $0
+  ${GetSecondsElapsed} "$StartIntroPhaseTickCount" "$0" $1
+  ; This is added to the previous value of $IntroPhaseSeconds because the
+  ; introduction page can be displayed multiple times.
+  IntOp $IntroPhaseSeconds $IntroPhaseSeconds + $1
+
   SetShellVarContext all ; Set SHCTX to All Users
   ; If the user doesn't have write access to the installation directory set
   ; the installation directory to a subdirectory of the All Users application
   ; directory and if the user can't write to that location set the installation
   ; directory to a subdirectory of the users local application directory
   ; (e.g. non-roaming).
   Call CanWrite
   ${If} "$CanWriteToInstallDir" == "false"
@@ -517,21 +763,38 @@ Function leaveIntro
   ${If} ${FileExists} "$INSTDIR"
     ; Always display the long path if the path exists.
     ${GetLongPath} "$INSTDIR" $INSTDIR
   ${EndIf}
 
 FunctionEnd
 
 Function createOptions
-  ; Skip the options page unless the Options button was clicked
-  ${If} "$WasOptionsButtonClicked" != "true"
+  ; Check whether the requirements to install are satisfied the first time the
+  ; options page is displayed for metrics.
+  ${If} "$InitialInstallRequirementsCode" == ""
+    ${If} "$CanWriteToInstallDir" != "true"
+    ${AndIf} "$HasRequiredSpaceAvailable" != "true"
+      StrCpy $InitialInstallRequirementsCode "1"
+    ${ElseIf} "$CanWriteToInstallDir" != "true"
+      StrCpy $InitialInstallRequirementsCode "2"
+    ${ElseIf} "$HasRequiredSpaceAvailable" != "true"
+      StrCpy $InitialInstallRequirementsCode "3"
+    ${Else}
+      StrCpy $InitialInstallRequirementsCode "0"
+    ${EndIf}
+  ${EndIf}
+
+  ; Skip the options page unless the Options button was clicked as long as the
+  ; installation directory can be written to and there is the minimum required
+  ; space available.
+  ${If} "$WasOptionsButtonClicked" != "1"
     ${If} "$CanWriteToInstallDir" == "true"
     ${AndIf} "$HasRequiredSpaceAvailable" == "true"
-      Abort
+      Abort ; Skip the options page
     ${EndIf}
   ${EndIf}
 
   StrCpy $ExistingTopDir ""
 
   nsDialogs::Create /NOUNLOAD 1018
   Pop $Dialog
   ; Since the text color for controls is set in this Dialog the foreground and
@@ -610,52 +873,52 @@ Function createOptions
   ; the "Space Required:" and "Space Available:" labels prior to setting RTL so
   ; the correct position of the controls can be set by NSIS for RTL locales.
 
   ; Get the width and height of both labels and use the tallest for the height
   ; and the widest to calculate where to place the labels after these labels.
   ${GetTextExtent} "$(SPACE_REQUIRED)" $FontItalic $0 $1
   ${GetTextExtent} "$(SPACE_AVAILABLE)" $FontItalic $2 $3
   ${If} $1 > $3
-    StrCpy $HEIGHT_PX "$1"
+    StrCpy $ControlHeightPX "$1"
   ${Else}
-    StrCpy $HEIGHT_PX "$3"
+    StrCpy $ControlHeightPX "$3"
   ${EndIf}
 
   IntOp $0 $0 + 8 ; Add padding to the control's width
   ; Make both controls the same width as the widest control
-  ${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 134u $0 $HEIGHT_PX "$(SPACE_REQUIRED)"
+  ${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 134u $0 $ControlHeightPX "$(SPACE_REQUIRED)"
   Pop $5
   SetCtlColors $5 ${OPTIONS_TEXT_COLOR_FADED} ${OPTIONS_BKGRD_COLOR}
   SendMessage $5 ${WM_SETFONT} $FontItalic 0
 
   IntOp $2 $2 + 8 ; Add padding to the control's width
-  ${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 145u $2 $HEIGHT_PX "$(SPACE_AVAILABLE)"
+  ${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 145u $2 $ControlHeightPX "$(SPACE_AVAILABLE)"
   Pop $6
   SetCtlColors $6 ${OPTIONS_TEXT_COLOR_FADED} ${OPTIONS_BKGRD_COLOR}
   SendMessage $6 ${WM_SETFONT} $FontItalic 0
 
   ; Use the widest label for aligning the labels next to them
   ${If} $0 > $2
     StrCpy $6 "$5"
   ${EndIf}
   FindWindow $1 "#32770" "" $HWNDPARENT
-  ${GetDlgItemEndPX} $6 $CTL_RIGHT_PX
+  ${GetDlgItemEndPX} $6 $ControlRightPX
 
-  IntOp $CTL_RIGHT_PX $CTL_RIGHT_PX + 6
+  IntOp $ControlRightPX $ControlRightPX + 6
 
-  ${NSD_CreateLabel} $CTL_RIGHT_PX 134u 100% $HEIGHT_PX \
+  ${NSD_CreateLabel} $ControlRightPX 134u 100% $ControlHeightPX \
                      "${APPROXIMATE_REQUIRED_SPACE_MB} $(MEGA)$(BYTE)"
   Pop $7
   SetCtlColors $7 ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
   SendMessage $7 ${WM_SETFONT} $FontNormal 0
 
   ; Create the free space label with an empty string and update it by calling
   ; UpdateFreeSpaceLabel
-  ${NSD_CreateLabel} $CTL_RIGHT_PX 145u 100% $HEIGHT_PX " "
+  ${NSD_CreateLabel} $ControlRightPX 145u 100% $ControlHeightPX " "
   Pop $LabelFreeSpace
   SetCtlColors $LabelFreeSpace ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
   SendMessage $LabelFreeSpace ${WM_SETFONT} $FontNormal 0
 
   Call UpdateFreeSpaceLabel
 
   ${NSD_CreateCheckbox} ${OPTIONS_ITEM_EDGE_DU} 168u ${OPTIONS_SUBITEM_WIDTH_DU} \
                         12u "$(SEND_PING)"
@@ -700,30 +963,38 @@ Function createOptions
   ${NSD_SetFocus} $0
 
   GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
   SendMessage $0 ${WM_SETTEXT} 0 "STR:$(CANCEL_BUTTON)"
 
   GetDlgItem $0 $HWNDPARENT 3 ; Back and Options button
   SendMessage $0 ${WM_SETTEXT} 0 "STR:$(BACK_BUTTON)"
 
-  ${If} "$WasOptionsButtonClicked" != "true"
+  ; If the option button was not clicked display the reason for what needs to be
+  ; resolved to continue the installation.
+  ${If} "$WasOptionsButtonClicked" != "1"
     ${If} "$CanWriteToInstallDir" == "false"
       MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_WRITE_ACCESS)"
     ${ElseIf} "$HasRequiredSpaceAvailable" == "false"
       MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_DISK_SPACE)"
     ${EndIf}
   ${EndIf}
 
+  IntOp $OptionsPageShownCount $OptionsPageShownCount + 1
+
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $StartOptionsPhaseTickCount
+
   LockWindow off
   nsDialogs::Show
 FunctionEnd
 
 Function leaveOptions
   LockWindow on
+
   ${GetRoot} "$INSTDIR" $0
   ${GetLongPath} "$INSTDIR" $INSTDIR
   ${GetLongPath} "$0" $0
   ${If} "$INSTDIR" == "$0"
     LockWindow off
     MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_ROOT_INSTALL)"
     Abort ; Stay on the page
   ${EndIf}
@@ -737,16 +1008,23 @@ Function leaveOptions
 
   Call CheckSpace
   ${If} "$HasRequiredSpaceAvailable" == "false"
     LockWindow off
     MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_DISK_SPACE)"
     Abort ; Stay on the page
   ${EndIf}
 
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $0
+  ${GetSecondsElapsed} "$StartOptionsPhaseTickCount" "$0" $1
+  ; This is added to the previous value of $OptionsPhaseSeconds because the
+  ; options page can be displayed multiple times.
+  IntOp $OptionsPhaseSeconds $OptionsPhaseSeconds + $1
+
   ${NSD_GetState} $CheckboxShortcutOnBar $CheckboxShortcutOnBar
   ${NSD_GetState} $CheckboxShortcutInStartMenu $CheckboxShortcutInStartMenu
   ${NSD_GetState} $CheckboxShortcutOnDesktop $CheckboxShortcutOnDesktop
   ${NSD_GetState} $CheckboxSendPing $CheckboxSendPing
 !ifdef MOZ_MAINTENANCE_SERVICE
   ${NSD_GetState} $CheckboxInstallMaintSvc $CheckboxInstallMaintSvc
 !endif
 
@@ -844,17 +1122,18 @@ Function createInstall
   Pop $LabelInstallingInProgress
   SendMessage $LabelInstallingInProgress ${WM_SETFONT} $FontNormal 0
   SetCtlColors $LabelInstallingInProgress ${INSTALL_PROGRESS_TEXT_COLOR_NORMAL} transparent
   ShowWindow $LabelInstallingInProgress ${SW_HIDE}
 
   ${NSD_CreateProgressBar} 103u 166u 157u 9u ""
   Pop $ProgressbarDownload
   ${NSD_AddStyle} $ProgressbarDownload ${PBS_MARQUEE}
-  SendMessage $ProgressbarDownload ${PBM_SETMARQUEE} 1 10 ; start=1|stop=0 interval(ms)=+N
+  SendMessage $ProgressbarDownload ${PBM_SETMARQUEE} 1 \
+              ${ProgressbarMarqueeIntervalMS} ; start=1|stop=0 interval(ms)=+N
 
   ${NSD_CreateProgressBar} 260u 166u 84u 9u ""
   Pop $ProgressbarInstall
   SendMessage $ProgressbarInstall ${PBM_SETRANGE32} 0 ${InstallProgresSteps}
 
   ${NSD_CreateBitmap} ${APPNAME_BMP_EDGE_DU} ${APPNAME_BMP_TOP_DU} \
                       ${APPNAME_BMP_WIDTH_DU} ${APPNAME_BMP_HEIGHT_DU} ""
   Pop $2
@@ -876,132 +1155,214 @@ Function createInstall
   SendMessage $0 ${WM_SETTEXT} 0 "STR:$(CANCEL_BUTTON)"
   ; Focus the Cancel button otherwise it isn't possible to tab to it since it is
   ; the only control that can be tabbed to.
   ${NSD_SetFocus} $0
   ; Kill the Cancel button's focus so pressing enter won't cancel the install.
   SendMessage $0 ${WM_KILLFOCUS} 0 0
 
   ; Set as default is not supported in the installer for Win8 and above
-  ${If} $CanSetAsDefault == "true"
+  ${If} "$CanSetAsDefault" == "true"
     GetDlgItem $0 $HWNDPARENT 10 ; Default browser checkbox
     SendMessage $0 ${BM_GETCHECK} 0 0 $CheckboxSetAsDefault
     EnableWindow $0 0
     ShowWindow $0 ${SW_HIDE}
   ${EndIf}
 
   GetDlgItem $0 $HWNDPARENT 11
   SendMessage $0 ${WM_SETTEXT} 0 "STR:$(ONE_MOMENT)"
   SendMessage $0 ${WM_SETFONT} $FontNormal 0
   SetCtlColors $0 ${FOOTER_CONTROL_TEXT_COLOR_FADED} ${FOOTER_BKGRD_COLOR}
   ShowWindow $0 ${SW_SHOW}
 
+  ; Set $DownloadReset to true so the first download tick count is measured.
+  StrCpy $DownloadReset "true"
   StrCpy $IsDownloadFinished "false"
-  StrCpy $DownloadReset "false"
-  StrCpy $ExitCode "${ERR_CANCEL_DOWNLOAD}"
-  ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
-    StrCpy $ExistingInstall "1"
-  ${Else}
-    StrCpy $ExistingInstall "0"
+  StrCpy $DownloadRetryCount "0"
+  StrCpy $StartLastDownloadTickCount "0"
+  StrCpy $DownloadFirstTransferSeconds ""
+  StrCpy $ExitCode "${ERR_DOWNLOAD_CANCEL}"
+  StrCpy $OpenedDownloadPage "0"
+
+  ClearErrors
+  ReadINIStr $ExistingVersion "$INSTDIR\application.ini" "App" "Version"
+  ${If} ${Errors}
+    StrCpy $ExistingVersion "0"
+  ${EndIf}
+
+  ClearErrors
+  ReadINIStr $ExistingBuildID "$INSTDIR\application.ini" "App" "BuildID"
+  ${If} ${Errors}
+    StrCpy $ExistingBuildID "0"
   ${EndIf}
 
   ${If} ${FileExists} "$LOCALAPPDATA\Mozilla\Firefox"
     StrCpy $ExistingProfile "1"
   ${Else}
     StrCpy $ExistingProfile "0"
   ${EndIf}
 
+  StrCpy $DownloadServerIP ""
+
   System::Call "kernel32::GetTickCount()l .s"
-  Pop $StartTickCount
+  Pop $StartDownloadPhaseTickCount
 
   ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
 
   LockWindow off
   nsDialogs::Show
 
   ${NSD_FreeImage} $0
   ${NSD_FreeImage} $1
   ${NSD_FreeImage} $HwndBitmapBlurb1
   ${NSD_FreeImage} $HwndBitmapBlurb2
   ${NSD_FreeImage} $HWndBitmapBlurb3
 FunctionEnd
 
-Function leaveInstall
-# Need a ping?
-FunctionEnd
-
 Function StartDownload
   ${NSD_KillTimer} StartDownload
   InetBgDL::Get "${URLStubDownload}" "$PLUGINSDIR\download.exe" \
-                /RANGEREQUEST /CONNECTTIMEOUT 120 /RECEIVETIMEOUT 120 /END
+                /CONNECTTIMEOUT 120 /RECEIVETIMEOUT 120 /END
   StrCpy $4 ""
   ${NSD_CreateTimer} OnDownload ${DownloadIntervalMS}
   ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
     RmDir /r "$INSTDIR\${TO_BE_DELETED}"
   ${EndIf}
 FunctionEnd
 
 Function OnDownload
   InetBgDL::GetStats
   # $0 = HTTP status code, 0=Completed
   # $1 = Completed files
   # $2 = Remaining files
   # $3 = Number of downloaded bytes for the current file
   # $4 = Size of current file (Empty string if the size is unknown)
   # /RESET must be used if status $0 > 299 (e.g. failure)
   # When status is $0 =< 299 it is handled by InetBgDL
+  StrCpy $DownloadServerIP "$5"
   ${If} $0 > 299
     ${NSD_KillTimer} OnDownload
+    IntOp $DownloadRetryCount $DownloadRetryCount + 1
     ${If} "$DownloadReset" != "true"
-      StrCpy $DownloadedAmount "0"
+      StrCpy $DownloadedBytes "0"
       ${NSD_AddStyle} $ProgressbarDownload ${PBS_MARQUEE}
-      SendMessage $ProgressbarDownload ${PBM_SETMARQUEE} 1 10 ; start=1|stop=0 interval(ms)=+N
+      SendMessage $ProgressbarDownload ${PBM_SETMARQUEE} 1 \
+                  ${ProgressbarMarqueeIntervalMS} ; start=1|stop=0 interval(ms)=+N
     ${EndIf}
     InetBgDL::Get /RESET /END
-    ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+    StrCpy $DownloadSizeBytes ""
     StrCpy $DownloadReset "true"
-    StrCpy $DownloadSize ""
+
+    ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+      StrCpy $ExitCode "${ERR_DOWNLOAD_TOO_MANY_RETRIES}"
+      ; Use a timer so the UI has a chance to update
+      ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+    ${Else}
+      ${NSD_CreateTimer} StartDownload ${DownloadRetryIntervalMS}
+    ${EndIf}
     Return
   ${EndIf}
 
   ${If} "$DownloadReset" == "true"
+    System::Call "kernel32::GetTickCount()l .s"
+    Pop $StartLastDownloadTickCount
     StrCpy $DownloadReset "false"
+    ; The seconds elapsed from the start of the download phase until the first
+    ; bytes are received are only recorded for the first request so it is
+    ; possible to determine connection issues for the first request.
+    ${If} "$DownloadFirstTransferSeconds" == ""
+      ; Get the seconds elapsed from the start of the download phase until the
+      ; first bytes are received.
+      ${GetSecondsElapsed} "$StartDownloadPhaseTickCount" "$StartLastDownloadTickCount" $DownloadFirstTransferSeconds
+    ${EndIf}
   ${EndIf}
 
-  ${If} $DownloadSize == ""
-  ${AndIf} $4 != ""
-    StrCpy $DownloadSize "$4"
+  ${If} "$DownloadSizeBytes" == ""
+  ${AndIf} "$4" != ""
+    ; Handle the case where the size of the file to be downloaded is less than
+    ; the minimum expected size or greater than the maximum expected size at the
+    ; beginning of the download.
+    ${If} $4 < ${DownloadMinSizeBytes}
+    ${OrIf} $4 > ${DownloadMaxSizeBytes}
+      ${NSD_KillTimer} OnDownload
+      InetBgDL::Get /RESET /END
+      StrCpy $DownloadReset "true"
+
+      ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+        ; Use a timer so the UI has a chance to update
+        ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+      ${Else}
+        ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+      ${EndIf}
+      Return
+    ${EndIf}
+
+    StrCpy $DownloadSizeBytes "$4"
     System::Int64Op $4 / 2
     Pop $HalfOfDownload
     SendMessage $ProgressbarDownload ${PBM_SETMARQUEE} 0 0 ; start=1|stop=0 interval(ms)=+N
     ${RemoveStyle} $ProgressbarDownload ${PBS_MARQUEE}
-    SendMessage $ProgressbarDownload ${PBM_SETRANGE32} 0 $DownloadSize
+    SendMessage $ProgressbarDownload ${PBM_SETRANGE32} 0 $DownloadSizeBytes
   ${EndIf}
 
   ; Don't update the status until after the download starts
   ${If} $2 != 0
-  ${AndIf} $4 == ""
+  ${AndIf} "$4" == ""
+    Return
+  ${EndIf}
+
+  ; Handle the case where the downloaded size is greater than the maximum
+  ; expected size during the download.
+  ${If} $DownloadedBytes > ${DownloadMaxSizeBytes}
+    InetBgDL::Get /RESET /END
+    StrCpy $DownloadReset "true"
+
+    ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+      ; Use a timer so the UI has a chance to update
+      ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+    ${Else}
+      ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+    ${EndIf}
     Return
   ${EndIf}
 
   ${If} $IsDownloadFinished != "true"
     ${If} $2 == 0
       ${NSD_KillTimer} OnDownload
       StrCpy $IsDownloadFinished "true"
       ; The first step of the install progress bar is determined by the
       ; InstallProgressFirstStep define and provides the user with immediate
       ; feedback.
       StrCpy $InstallCounterStep "${InstallProgressFirstStep}"
       System::Call "kernel32::GetTickCount()l .s"
-      Pop $DownloadTickCount
-      StrCpy $DownloadedAmount "$DownloadSize"
+      Pop $EndDownloadPhaseTickCount
+
+      StrCpy $DownloadedBytes "$DownloadSizeBytes"
+
+      ; When a download has finished handle the case where the  downloaded size
+      ; is less than the minimum expected size or greater than the maximum
+      ; expected size during the download.
+      ${If} $DownloadedBytes < ${DownloadMinSizeBytes}
+      ${OrIf} $DownloadedBytes > ${DownloadMaxSizeBytes}
+        InetBgDL::Get /RESET /END
+        StrCpy $DownloadReset "true"
+
+        ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+          ; Use a timer so the UI has a chance to update
+          ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+        ${Else}
+          ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+        ${EndIf}
+        Return
+      ${EndIf}
+
       LockWindow on
       ; Update the progress bars first in the UI change so they take affect
       ; before other UI changes.
-      SendMessage $ProgressbarDownload ${PBM_SETPOS} $DownloadSize 0
+      SendMessage $ProgressbarDownload ${PBM_SETPOS} $DownloadSizeBytes 0
       SendMessage $ProgressbarInstall ${PBM_SETPOS} $InstallCounterStep 0
       ShowWindow $LabelDownloadingInProgress ${SW_HIDE}
       ShowWindow $LabelInstallingToBeDone ${SW_HIDE}
       ShowWindow $LabelInstallingInProgress ${SW_SHOW}
       ShowWindow $LabelDownloadingDown ${SW_SHOW}
       ShowWindow $LabelBlurb2 ${SW_HIDE}
       ShowWindow $BitmapBlurb2 ${SW_HIDE}
       ShowWindow $LabelBlurb3 ${SW_SHOW}
@@ -1015,35 +1376,38 @@ Function OnDownload
       StrCpy $R9 "${INVALID_HANDLE_VALUE}"
       System::Call 'kernel32::CreateFileW(w "$PLUGINSDIR\download.exe", \
                                           i ${GENERIC_READ}, \
                                           i ${FILE_SHARE_READ}, i 0, \
                                           i ${OPEN_EXISTING}, i 0, i 0) i .R9'
       StrCpy $HandleDownload "$R9"
 
       ${If} $HandleDownload == ${INVALID_HANDLE_VALUE}
-        StrCpy $ExitCode "${ERR_INVALID_HANDLE}"
+        StrCpy $ExitCode "${ERR_PREINSTALL_INVALID_HANDLE}"
         StrCpy $0 "0"
         StrCpy $1 "0"
       ${Else}
         CertCheck::VerifyCertTrust "$PLUGINSDIR\download.exe"
         Pop $0
         CertCheck::VerifyCertNameIssuer "$PLUGINSDIR\download.exe" \
                                         "${CertNameDownload}" "${CertIssuerDownload}"
         Pop $1
         ${If} $0 == 0
         ${AndIf} $1 == 0
-          StrCpy $ExitCode "${ERR_CERT_UNTRUSTED_AND_ATTRIBUTES}"
+          StrCpy $ExitCode "${ERR_PREINSTALL_CERT_UNTRUSTED_AND_ATTRIBUTES}"
         ${ElseIf} $0 == 0
-          StrCpy $ExitCode "${ERR_CERT_UNTRUSTED}"
+          StrCpy $ExitCode "${ERR_PREINSTALL_CERT_UNTRUSTED}"
         ${ElseIf}  $1 == 0
-          StrCpy $ExitCode "${ERR_CERT_ATTRIBUTES}"
+          StrCpy $ExitCode "${ERR_PREINSTALL_CERT_ATTRIBUTES}"
         ${EndIf}
       ${EndIf}
 
+      System::Call "kernel32::GetTickCount()l .s"
+      Pop $EndPreInstallPhaseTickCount
+
       ${If} $0 == 0
       ${OrIf} $1 == 0
         ; Use a timer so the UI has a chance to update
         ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
         Return
       ${EndIf}
 
       ; Instead of extracting the files we use the downloaded installer to
@@ -1102,41 +1466,66 @@ Function OnDownload
         StrCpy $HalfOfDownload "true"
         LockWindow on
         ShowWindow $LabelBlurb1 ${SW_HIDE}
         ShowWindow $BitmapBlurb1 ${SW_HIDE}
         ShowWindow $LabelBlurb2 ${SW_SHOW}
         ShowWindow $BitmapBlurb2 ${SW_SHOW}
         LockWindow off
       ${EndIf}
-      StrCpy $DownloadedAmount "$3"
+      StrCpy $DownloadedBytes "$3"
       SendMessage $ProgressbarDownload ${PBM_SETPOS} $3 0
     ${EndIf}
   ${EndIf}
 FunctionEnd
 
+Function OnPing
+  InetBgDL::GetStats
+  # $0 = HTTP status code, 0=Completed
+  # $1 = Completed files
+  # $2 = Remaining files
+  # $3 = Number of downloaded bytes for the current file
+  # $4 = Size of current file (Empty string if the size is unknown)
+  # /RESET must be used if status $0 > 299 (e.g. failure)
+  # When status is $0 =< 299 it is handled by InetBgDL
+  ${If} $2 == 0
+  ${OrIf} $0 > 299
+    ${NSD_KillTimer} OnPing
+    ${If} $0 > 299
+      InetBgDL::Get /RESET /END
+    ${EndIf}
+    ; The following will exit the installer
+    SetAutoClose true
+    StrCpy $R9 "2"
+    Call RelativeGotoPage
+  ${EndIf}
+FunctionEnd
+
 Function StartInstall
   ${NSD_KillTimer} StartInstall
 
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $EndPreInstallPhaseTickCount
+
   IntOp $InstallCounterStep $InstallCounterStep + 1
   LockWindow on
   SendMessage $ProgressbarInstall ${PBM_SETPOS} $InstallCounterStep 0
   LockWindow off
 
   Exec "$\"$PLUGINSDIR\download.exe$\" /INI=$PLUGINSDIR\${CONFIG_INI}"
   ${NSD_CreateTimer} CheckInstall ${InstallIntervalMS}
 FunctionEnd
 
 Function CheckInstall
   IntOp $InstallCounterStep $InstallCounterStep + 1
-  ${If} $InstallCounterStep >= ${InstallProgresSteps} 
+  ${If} $InstallCounterStep >= ${InstallProgresSteps}
     ${NSD_KillTimer} CheckInstall
     ; Close the handle that prevents modification of the full installer
     System::Call 'kernel32::CloseHandle(i $HandleDownload)'
-    StrCpy $ExitCode "${ERR_CHECK_INSTALL_TIMEOUT}"
+    StrCpy $ExitCode "${ERR_INSTALL_TIMEOUT}"
     ; Use a timer so the UI has a chance to update
     ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
     Return
   ${EndIf}
 
   SendMessage $ProgressbarInstall ${PBM_SETPOS} $InstallCounterStep 0
 
   ${If} ${FileExists} "$INSTDIR\install.log"
@@ -1150,17 +1539,17 @@ Function CheckInstall
     ${Unless} ${Errors}
       ${NSD_KillTimer} CheckInstall
       ; Close the handle that prevents modification of the full installer
       System::Call 'kernel32::CloseHandle(i $HandleDownload)'
       Rename "$INSTDIR\install.tmp" "$INSTDIR\install.log"
       Delete "$PLUGINSDIR\download.exe"
       Delete "$PLUGINSDIR\${CONFIG_INI}"
       System::Call "kernel32::GetTickCount()l .s"
-      Pop $InstallTickCount
+      Pop $EndInstallPhaseTickCount
       ${NSD_CreateTimer} FinishInstall ${InstallIntervalMS}
     ${EndUnless}
   ${EndIf}
 FunctionEnd
 
 Function FinishInstall
   ; The full installer has complete but we still need to finish the progress
   ; bar so increase the size of the step
@@ -1207,24 +1596,21 @@ Function FinishInstall
     Delete "$INSTDIR\${FileMainEXE}"
     Rename "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
   ${EndIf}
 
   StrCpy $ExitCode "${ERR_SUCCESS}"
 
   Call LaunchApp
 
-  ; The following will exit the installer
-  SetAutoClose true
-  StrCpy $R9 "2"
-  Call RelativeGotoPage
+  Call SendPing
 FunctionEnd
 
 Function OnBack
-  StrCpy $WasOptionsButtonClicked "true"
+  StrCpy $WasOptionsButtonClicked "1"
   StrCpy $R9 "1" ; Goto the next page
   Call RelativeGotoPage
   ; The call to Abort prevents NSIS from trying to move to the previous or the
   ; next page.
   Abort
 FunctionEnd
 
 Function RelativeGotoPage
@@ -1419,22 +1805,22 @@ Function ExecSetAsDefaultAppUser
   ; Using the helper.exe lessens the stub installer size.
   ; This could ask for elevatation when the user doesn't install as admin.
   Exec "$\"$INSTDIR\uninstall\helper.exe$\" /SetAsDefaultAppUser"
 FunctionEnd
 
 Function LaunchApp
   FindWindow $0 "${WindowClass}"
   ${If} $0 <> 0 ; integer comparison
-    StrCpy $FirefoxLaunch "1"
+    StrCpy $FirefoxLaunchCode "1"
     MessageBox MB_OK|MB_ICONQUESTION "$(WARN_MANUALLY_CLOSE_APP_LAUNCH)"
     Return
   ${EndIf}
 
-  StrCpy $FirefoxLaunch "2"
+  StrCpy $FirefoxLaunchCode "2"
 
   ; Set the current working directory to the installation directory
   SetOutPath "$INSTDIR"
   ClearErrors
   ${GetParameters} $0
   ${GetOptions} "$0" "/UAC:" $1
   ${If} ${Errors}
     Exec "$\"$INSTDIR\${FileMainEXE}$\""
@@ -1453,35 +1839,32 @@ Function LaunchAppFromElevatedProcess
   ; Set the current working directory to the installation directory
   ${GetParent} "$0" $1
   SetOutPath "$1"
   Exec "$\"$0$\""
 FunctionEnd
 
 Function DisplayDownloadError
   ${NSD_KillTimer} DisplayDownloadError
-  StrCpy $R9 "false"
-  MessageBox MB_OKCANCEL|MB_ICONSTOP "$(ERROR_DOWNLOAD)" IDCANCEL +2 IDOK 0
-  StrCpy $R9 "true"
+  MessageBox MB_OKCANCEL|MB_ICONSTOP "$(ERROR_DOWNLOAD)" IDCANCEL +2 IDOK +1
+  StrCpy $OpenedDownloadPage "1" ; Already initialized to 0
 
-  ${If} "$R9" == "true"
+  ${If} "$OpenedDownloadPage" == "1"
     ClearErrors
     ${GetParameters} $0
     ${GetOptions} "$0" "/UAC:" $1
     ${If} ${Errors}
       Call OpenManualDownloadURL
     ${Else}
       GetFunctionAddress $0 OpenManualDownloadURL
       UAC::ExecCodeSegment $0
     ${EndIf}
   ${EndIf}
 
-  SetAutoClose true
-  StrCpy $R9 "2"
-  Call RelativeGotoPage
+  Call SendPing
 FunctionEnd
 
 Function OpenManualDownloadURL
   ExecShell "open" "${URLManualDownload}"
 FunctionEnd
 
 Section
 SectionEnd
--- a/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.cpp
+++ b/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.cpp
@@ -1,768 +1,668 @@
-//
-// Copyright (C) Anders Kjersem. Licensed under the zlib/libpng license
-//
-
-#include "InetBgDL.h"
-
-#define USERAGENT _T("NSIS InetBgDL (Mozilla)")
-
-#define STATUS_COMPLETEDALL 0
-#define STATUS_INITIAL 202
-#define STATUS_CONNECTING STATUS_INITIAL //102
-#define STATUS_DOWNLOADING STATUS_INITIAL
-#define STATUS_ERR_GETLASTERROR 418 //HTTP: I'm a teapot: Win32 error code in $3
-#define STATUS_ERR_LOCALFILEWRITEERROR 450 //HTTP: MS parental control extension
-#define STATUS_ERR_CANCELLED 499
-
-typedef DWORD FILESIZE_T; // Limit to 4GB for now...
-#define FILESIZE_UNKNOWN (-1)
-
-HINSTANCE g_hInst;
-NSIS::stack_t*g_pLocations = NULL;
-HANDLE g_hThread = NULL;
-HANDLE g_hGETStartedEvent = NULL;
-volatile UINT g_FilesTotal = 0;
-volatile UINT g_FilesCompleted = 0;
-volatile UINT g_Status = STATUS_INITIAL;
-volatile FILESIZE_T g_cbCurrXF;
-volatile FILESIZE_T g_cbCurrTot = FILESIZE_UNKNOWN;
-CRITICAL_SECTION g_CritLock;
-UINT g_N_CCH;
-PTSTR g_N_Vars;
-
-DWORD g_ConnectTimeout = 0;
-DWORD g_ReceiveTimeout = 0;
-bool g_WantRangeRequest = false;
-
-#define NSISPI_INITGLOBALS(N_CCH, N_Vars) do { \
-  g_N_CCH = N_CCH; \
-  g_N_Vars = N_Vars; \
-  } while(0)
-
-#define ONELOCKTORULETHEMALL
-#ifdef ONELOCKTORULETHEMALL
-#define TaskLock_AcquireExclusive() EnterCriticalSection(&g_CritLock)
-#define TaskLock_ReleaseExclusive() LeaveCriticalSection(&g_CritLock)
-#define StatsLock_AcquireExclusive() TaskLock_AcquireExclusive()
-#define StatsLock_ReleaseExclusive() TaskLock_ReleaseExclusive()
-#define StatsLock_AcquireShared() StatsLock_AcquireExclusive()
-#define StatsLock_ReleaseShared() StatsLock_ReleaseExclusive()
-#endif
-
-PTSTR NSIS_SetRegStr(UINT Reg, LPCTSTR Value)
-{
-  PTSTR s = g_N_Vars + (Reg * g_N_CCH);
-  lstrcpy(s, Value);
-  return s;
-}
-#define NSIS_SetRegStrEmpty(r) NSIS_SetRegStr(r, _T(""))
-void NSIS_SetRegUINT(UINT Reg, UINT Value)
-{
-  TCHAR buf[32];
-  wsprintf(buf, _T("%u"), Value);
-  NSIS_SetRegStr(Reg, buf);
-}
-#define StackFreeItem(pI) GlobalFree(pI)
-NSIS::stack_t* StackPopItem(NSIS::stack_t**ppST)
-{
-  if (*ppST)
-  {
-    NSIS::stack_t*pItem = *ppST;
-    *ppST = pItem->next;
-    return pItem;
-  }
-  return NULL;
-}
-
-void Reset()
-{
-  // The g_hGETStartedEvent event is used to make sure that the Get() call will
-  // acquire the lock before the Reset() call acquires the lock.
-  if (g_hGETStartedEvent) {
-    WaitForSingleObject(g_hGETStartedEvent, INFINITE);
-    CloseHandle(g_hGETStartedEvent);
-    g_hGETStartedEvent = NULL;
-  }
-
-  TaskLock_AcquireExclusive();
-#ifndef ONELOCKTORULETHEMALL
-  StatsLock_AcquireExclusive();
-#endif
-  g_FilesTotal = 0; // This causes the Task thread to exit the transfer loop
-  if (g_hThread)
-  {
-    if (WAIT_OBJECT_0 != WaitForSingleObject(g_hThread, 10 * 1000))
-    {
-      TerminateThread(g_hThread, ERROR_OPERATION_ABORTED);
-    }
-    CloseHandle(g_hThread);
-    g_hThread = NULL;
-  }
-  g_FilesTotal = 0;
-  g_FilesCompleted = 0;
-  g_Status = STATUS_INITIAL;
-#ifndef ONELOCKTORULETHEMALL
-  StatsLock_ReleaseExclusive();
-#endif
-  for (NSIS::stack_t*pTmpTast,*pTask = g_pLocations; pTask ;)
-  {
-    pTmpTast = pTask;
-    pTask = pTask->next;
-    StackFreeItem(pTmpTast);
-  }
-  g_pLocations = NULL;
-  TaskLock_ReleaseExclusive();
-}
-
-UINT_PTR __cdecl NSISPluginCallback(UINT Event)
-{
-  switch(Event)
-  {
-  case NSPIM_UNLOAD:
-    Reset();
-    break;
-  }
-  return NULL;
-}
-
-/* ParseURL is a quickly thrown together simple way to parse a URL into parts.
- * It is only designed to support simple URLs and doesn't support things like
- * authorization information in the URL.
- * The format of the URL is assumed to be:
- * <protocol>://<server>:<port>/<path>
- *
- * @param url      The input URL to parse
- * @param protocol Out variable which will store the protocol of the passed url
- * @param server Out variable which will store the server of the passed url
- * @param port Out variable which will store the port of the passed url
- * @param path Out variable which will store the path of the passed url
- * @return true if successful
-*/
-template<size_t A, size_t B, size_t C> static bool
-BasicParseURL(LPCWSTR url, wchar_t (*protocol)[A], INTERNET_PORT *port,
-              wchar_t (*server)[B], wchar_t (*path)[C])
-{
-  ZeroMemory(*protocol, A * sizeof(wchar_t));
-  ZeroMemory(*server, B * sizeof(wchar_t));
-  ZeroMemory(*path, C * sizeof(wchar_t));
-
-  const WCHAR *p = url;
-  // Skip past the protocol
-  int pos = 0;
-  while (*p != L'\0' && *p != L'/' && *p != L':')
-  {
-    if (pos + 1 >= A)
-      return false;
-    (*protocol)[pos++] = *p++;
-  }
-
-  // Skip past the ://
-  p += 3;
-
-  *port = INTERNET_DEFAULT_HTTP_PORT;
-  if (!wcsicmp(*protocol, L"https"))
-  {
-    *port = INTERNET_DEFAULT_HTTPS_PORT;
-  }
-
-  // Get the server
-  pos = 0;
-  while (*p != L'\0' && *p != L'/' && *p != L':')
-  {
-    if (pos + 1 >= B)
-      return false;
-    (*server)[pos++] = *p++;
-  }
-
-  // Get the port if specified
-  if (*p == L':')
-  {
-    WCHAR portStr[16];
-    p++;
-    pos = 0;
-    while (*p != L'\0' && *p != L'/')
-    {
-      if (pos + 1 >= 16)
-        return false;
-      portStr[pos++] = *p++;
-    }
-    portStr[pos] = '\0';
-    *port = (INTERNET_PORT)_wtoi(portStr);
-  }
-  else
-  {
-    // Skip the slash after the server
-    while (*p != L'\0' && *p != L'/')
-    {
-      p++;
-    }
-  }
-
-  // Get the rest as the path
-  pos = 0;
-  while (*p != L'\0')
-  {
-    if (pos + 1 >= C)
-      return false;
-    (*path)[pos++] = *p++;
-  }
-
-  return true;
-}
-
-DWORD CALLBACK TaskThreadProc(LPVOID ThreadParam)
-{
-  NSIS::stack_t *pURL,*pFile;
-  HINTERNET hInetSes = NULL, hInetCon = NULL;
-  HANDLE hLocalFile;
-  bool completedFile = false;
-startnexttask:
-  hLocalFile = INVALID_HANDLE_VALUE;
-  pFile = NULL;
-  TaskLock_AcquireExclusive();
-  // Now that we've acquired the lock, we can set the event to indicate this.
-  // SetEvent will likely never fail, but if it does we should set it to NULL
-  // to avoid anyone waiting on it.
-  if (!SetEvent(g_hGETStartedEvent)) {
-    CloseHandle(g_hGETStartedEvent);
-    g_hGETStartedEvent = NULL;
-  }
-  pURL = g_pLocations;
-  if (pURL)
-  {
-    pFile = pURL->next;
-    g_pLocations = pFile->next;
-  }
-#ifndef ONELOCKTORULETHEMALL
-  StatsLock_AcquireExclusive();
-#endif
-  if (completedFile)
-  {
-    ++g_FilesCompleted;
-  }
-  completedFile = false;
-  g_cbCurrXF = 0;
-  g_cbCurrTot = FILESIZE_UNKNOWN;
-  if (!pURL)
-  {
-    if (g_FilesTotal)
-    {
-      if (g_FilesTotal == g_FilesCompleted)
-      {
-        g_Status = STATUS_COMPLETEDALL;
-      }
-    }
-    g_hThread = NULL;
-  }
-#ifndef ONELOCKTORULETHEMALL
-  StatsLock_ReleaseExclusive();
-#endif
-  TaskLock_ReleaseExclusive();
-
-  if (!pURL)
-  {
-    if (0)
-    {
-diegle:
-      DWORD gle = GetLastError();
-      //TODO? if (ERROR_INTERNET_EXTENDED_ERROR==gle) InternetGetLastResponseInfo(...)
-      g_Status = STATUS_ERR_GETLASTERROR;
-    }
-    if (hInetSes)
-    {
-      InternetCloseHandle(hInetSes);
-    }
-    if (hInetCon)
-    {
-      InternetCloseHandle(hInetCon);
-    }
-    if (INVALID_HANDLE_VALUE != hLocalFile)
-    {
-      CloseHandle(hLocalFile);
-    }
-    StackFreeItem(pURL);
-    StackFreeItem(pFile);
-    return 0;
-  }
-
-  if (!hInetSes)
-  {
-    hInetSes = InternetOpen(USERAGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
-    if (!hInetSes)
-    {
-      goto diegle;
-    }
-
-    //msdn.microsoft.com/library/default.asp?url=/workshop/components/offline/offline.asp#Supporting Offline Browsing in Applications and Components
-    ULONG longOpt;
-    DWORD cbio = sizeof(ULONG);
-    if (InternetQueryOption(hInetSes, INTERNET_OPTION_CONNECTED_STATE, &longOpt, &cbio))
-    {
-      if (INTERNET_STATE_DISCONNECTED_BY_USER&longOpt)
-      {
-        INTERNET_CONNECTED_INFO ci = {INTERNET_STATE_CONNECTED, 0};
-        InternetSetOption(hInetSes, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
-      }
-    }
-
-    if(g_ConnectTimeout > 0)
-    {
-      InternetSetOption(hInetSes, INTERNET_OPTION_CONNECT_TIMEOUT,
-                        &g_ConnectTimeout, sizeof(g_ConnectTimeout));
-    }
-  }
-
-  DWORD ec = ERROR_SUCCESS;
-  hLocalFile = CreateFile(pFile->text,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_DELETE,NULL,CREATE_ALWAYS,0,NULL);
-  if (INVALID_HANDLE_VALUE == hLocalFile)
-  {
-    goto diegle;
-  }
-
-  const DWORD IOURedirFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
-                              INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
-  const DWORD IOUCacheFlags = INTERNET_FLAG_RESYNCHRONIZE |
-                              INTERNET_FLAG_NO_CACHE_WRITE |
-                              INTERNET_FLAG_PRAGMA_NOCACHE |
-                              INTERNET_FLAG_RELOAD;
-  const DWORD IOUCookieFlags = INTERNET_FLAG_NO_COOKIES;
-  DWORD IOUFlags = IOURedirFlags | IOUCacheFlags | IOUCookieFlags |
-                   INTERNET_FLAG_NO_UI | INTERNET_FLAG_EXISTING_CONNECT;
-
-  WCHAR protocol[16];
-  WCHAR server[128];
-  WCHAR path[1024];
-  INTERNET_PORT port;
-  // Good old VC6 cannot deduce the size of these params :'(
-  if (!BasicParseURL<16, 128, 1024>(pURL->text, &protocol, &port, &server, &path))
-  {
-    // Insufficient buffer or bad URL passed in
-    goto diegle;
-  }
-
-  DWORD context;
-  hInetCon = InternetConnect(hInetSes, server, port, NULL, NULL,
-                             INTERNET_SERVICE_HTTP, IOUFlags,
-                             (unsigned long)&context);
-  if (!hInetCon)
-  {
-    goto diegle;
-  }
-
-  // Setup a buffer of size 256KiB to store the downloaded data.
-  // Get at most 4MiB at a time from the partial HTTP Range requests.
-  // Biffer buffers will be faster.
-  // cbRangeReadBufXF should be a multiple of cbBufXF.
-  const UINT cbBufXF = 262144;
-  const UINT cbRangeReadBufXF = 4194304;
-  BYTE bufXF[cbBufXF];
-
-  // Up the default internal buffer size from 4096 to internalReadBufferSize.
-  DWORD internalReadBufferSize = cbRangeReadBufXF;
-  if (!InternetSetOption(hInetCon, INTERNET_OPTION_READ_BUFFER_SIZE,
-                         &internalReadBufferSize, sizeof(DWORD)))
-  {
-    // Maybe it's too big, try half of the optimal value.  If that fails just
-    // use the default.
-    internalReadBufferSize /= 2;
-    InternetSetOption(hInetCon, INTERNET_OPTION_READ_BUFFER_SIZE,
-                      &internalReadBufferSize, sizeof(DWORD));
-  }
-
-  // Change the default timeout of 30 seconds to the specified value.
-  // Is case a proxy in between caches the results, it could in theory
-  // take longer to get the first chunk, so it is good to set this high.
-  if (g_ReceiveTimeout)
-  {
-    InternetSetOption(hInetCon, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT,
-                      &g_ReceiveTimeout, sizeof(DWORD));
-  }
-
-  HINTERNET hInetFile;
-  DWORD cbio = sizeof(DWORD);
-
-  // Keep looping until we don't have a redirect anymore
-  int redirectCount = 0;
-  for (;;) {
-    // Make sure we aren't stuck in some kind of infinite redirect loop.
-    if (redirectCount > 15) {
-      goto diegle;
-    }
-
-    // If a range request was specified, first do a HEAD request
-    hInetFile = HttpOpenRequest(hInetCon, g_WantRangeRequest ? L"HEAD" : L"GET",
-                                path, NULL, NULL, NULL,
-                                INTERNET_FLAG_NO_AUTO_REDIRECT |
-                                INTERNET_FLAG_PRAGMA_NOCACHE |
-                                INTERNET_FLAG_RELOAD |
-                                (port == INTERNET_DEFAULT_HTTPS_PORT ?
-                                         INTERNET_FLAG_SECURE : 0), 0);
-    if (!hInetFile)
-    {
-      goto diegle;
-    }
-
-    if (!HttpSendRequest(hInetFile, NULL, 0, NULL, 0))
-    {
-      goto diegle;
-    }
-
-    WCHAR responseText[256];
-    cbio = sizeof(responseText);
-    if (!HttpQueryInfo(hInetFile,
-                       HTTP_QUERY_STATUS_CODE,
-                       responseText, &cbio, NULL))
-    {
-      goto diegle;
-    }
-
-    int statusCode = _wtoi(responseText);
-    if (statusCode == HTTP_STATUS_REDIRECT ||
-        statusCode == HTTP_STATUS_MOVED) {
-      redirectCount++;
-      WCHAR URLBuffer[2048];
-      cbio = sizeof(URLBuffer);
-      if (!HttpQueryInfo(hInetFile,
-                         HTTP_QUERY_LOCATION,
-                         (DWORD*)URLBuffer, &cbio, NULL))
-      {
-        goto diegle;
-      }
-
-      WCHAR protocol2[16];
-      WCHAR server2[128];
-      WCHAR path2[1024];
-      INTERNET_PORT port2;
-      BasicParseURL<16, 128, 1024>(URLBuffer, &protocol2, &port2, &server2, &path2);
-      // Check if we need to reconnect to a new server
-      if (wcscmp(protocol, protocol2) || wcscmp(server, server2) ||
-          port != port2) {
-        wcscpy(server, server2);
-        port = port2;
-        InternetCloseHandle(hInetCon);
-        hInetCon = InternetConnect(hInetSes, server, port, NULL, NULL,
-                                   INTERNET_SERVICE_HTTP, IOUFlags,
-                                   (unsigned long)&context);
-        if (!hInetCon)
-        {
-          goto diegle;
-        }
-      }
-      wcscpy(path, path2);
-
-      // Close the existing handle because we'll be issuing a new request
-      // with the new request path.
-      InternetCloseHandle(hInetFile);
-      continue;
-    }
-    break;
-  }
-
-  // Get the file length via the Content-Length header
-  FILESIZE_T cbThisFile;
-  cbio = sizeof(cbThisFile);
-  if (!HttpQueryInfo(hInetFile,
-                     HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
-                     &cbThisFile, &cbio, NULL))
-  {
-    cbThisFile = FILESIZE_UNKNOWN;
-  }
-
-  // Determine if we should use byte range requests. We want to use it if:
-  // 1. Server accepts byte range requests
-  // 2. The size of the file is known and more than our Range buffer size.
-  bool shouldUseRangeRequest = true;
-  WCHAR rangeRequestAccepted[64] = { '\0' };
-  cbio = sizeof(rangeRequestAccepted);
-  if (cbThisFile != FILESIZE_UNKNOWN && cbThisFile <= cbRangeReadBufXF ||
-      !HttpQueryInfo(hInetFile,
-                     HTTP_QUERY_ACCEPT_RANGES,
-                     (LPDWORD)rangeRequestAccepted, &cbio, NULL))
-  {
-    shouldUseRangeRequest = false;
-  }
-  else
-  {
-    shouldUseRangeRequest = wcsstr(rangeRequestAccepted, L"bytes") != 0 &&
-                            cbThisFile != FILESIZE_UNKNOWN;
-  }
-
-  // If the server doesn't have range request support or doesn't have a
-  // Content-Length header, then get everything all at once.
-  // If the user didn't want a range request, then we already issued the GET
-  // request earlier.  If the user did want a range request, then we issued only
-  // a HEAD so far.
-  if (g_WantRangeRequest && !shouldUseRangeRequest)
-  {
-    InternetCloseHandle(hInetFile);
-    InternetCloseHandle(hInetCon);
-    hInetFile = InternetOpenUrl(hInetSes, pURL->text,
-                                NULL, 0, IOUFlags |
-                                (!wcsicmp(protocol, L"https") ?
-                                 INTERNET_FLAG_SECURE : 0),
-                                NULL);
-    if (!hInetFile)
-    {
-      goto diegle;
-    }
-
-    // For some reason this also needs to be set on the hInetFile and
-    // not just the connection.
-    if (g_ReceiveTimeout > 0)
-    {
-      InternetSetOption(hInetCon, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT,
-                        &g_ReceiveTimeout, sizeof(DWORD));
-    }
-  }
-
-  for(;;)
-  {
-    DWORD cbio = 0,cbXF = 0;
-    // If we know the file size, download it in chunks
-    if (g_WantRangeRequest && shouldUseRangeRequest && cbThisFile != g_cbCurrXF)
-    {
-      // Close the previous request, but not the connection
-      InternetCloseHandle(hInetFile);
-      hInetFile = HttpOpenRequest(hInetCon, L"GET", path,
-                                  NULL, NULL, NULL,
-                                  INTERNET_FLAG_PRAGMA_NOCACHE |
-                                  INTERNET_FLAG_RELOAD |
-                                  (port == INTERNET_DEFAULT_HTTPS_PORT ?
-                                           INTERNET_FLAG_SECURE : 0), 0);
-      if (!hInetFile)
-      {
-        // TODO: we could add retry here to be more tolerant
-        goto diegle;
-      }
-
-      // For some reason this also needs to be set on the hInetFile and
-      // not just the connection.
-      if (g_ReceiveTimeout > 0)
-      {
-        InternetSetOption(hInetCon, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT,
-                          &g_ReceiveTimeout, sizeof(DWORD));
-      }
-
-      WCHAR range[32];
-      swprintf(range, L"Range: bytes=%d-%d", g_cbCurrXF,
-               min(g_cbCurrXF + cbRangeReadBufXF, cbThisFile));
-      if (!HttpSendRequest(hInetFile, range, wcslen(range), NULL, 0))
-      {
-        // TODO: we could add retry here to be more tolerant
-        goto diegle;
-      }
-    }
-
-    // Read the chunk (or full file if we don't know the size) we downloaded
-    BOOL retXF;
-    for (;;)
-    {
-      DWORD cbioThisIteration = 0;
-      retXF = InternetReadFile(hInetFile, bufXF, cbBufXF, &cbioThisIteration);
-      if (!retXF)
-      {
-        ec = GetLastError();
-        TRACE1(_T("InternetReadFile failed, gle=%u\n"), ec);
-        // TODO: we could add retry here to be more tolerant
-        goto diegle;
-      }
-
-      // Check if we're done reading
-      if (cbioThisIteration == 0)
-      {
-        break;
-      }
-
-      // Write what we found
-      cbXF = cbioThisIteration;
-      retXF = WriteFile(hLocalFile, bufXF, cbXF, &cbioThisIteration, NULL);
-      if (!retXF || cbXF != cbioThisIteration)
-      {
-        cbio += cbioThisIteration;
-        ec = GetLastError();
-        break;
-      }
-
-      cbio += cbioThisIteration;
-      StatsLock_AcquireExclusive();
-      if (FILESIZE_UNKNOWN != cbThisFile)
-      {
-        g_cbCurrTot = cbThisFile;
-      }
-      g_cbCurrXF += cbXF;
-      StatsLock_ReleaseExclusive();
-
-      // Avoid an extra call to InternetReadFile if we already read everything
-      // in the current request
-      if (cbio == cbRangeReadBufXF && shouldUseRangeRequest)
-      {
-        break;
-      }
-    }
-
-    // Check if we're done transferring the file successfully
-    if (0 == cbio &&
-        (cbThisFile == FILESIZE_UNKNOWN || cbThisFile == g_cbCurrXF))
-    {
-      ASSERT(ERROR_SUCCESS == ec);
-      TRACE2(_T("InternetReadFile true with 0 cbio, cbThisFile=%d gle=%u\n"), cbThisFile, GetLastError());
-      break;
-    }
-
-    // Check if we canceled the download
-    if (0 == g_FilesTotal)
-    {
-      TRACEA("0==g_FilesTotal, aborting transfer loop...\n");
-      ec = ERROR_CANCELLED;
-      break;
-    }
-  }
-
-  TRACE2(_T("TaskThreadProc completed %s, ec=%u\n"), pURL->text, ec);
-  InternetCloseHandle(hInetFile);
-  if (ERROR_SUCCESS == ec)
-  {
-    if (INVALID_HANDLE_VALUE != hLocalFile)
-    {
-      CloseHandle(hLocalFile);
-      hLocalFile = INVALID_HANDLE_VALUE;
-    }
-    StackFreeItem(pURL);
-    StackFreeItem(pFile);
-    ++completedFile;
-  }
-  else
-  {
-    SetLastError(ec);
-    goto diegle;
-  }
-  goto startnexttask;
-}
-
-NSISPIEXPORTFUNC Get(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
-{
-  pX->RegisterPluginCallback(g_hInst, NSISPluginCallback);
-  g_WantRangeRequest = false;
-  for (;;)
-  {
-    NSIS::stack_t*pURL = StackPopItem(ppST);
-    if (!pURL)
-    {
-      break;
-    }
-
-    if (lstrcmpi(pURL->text, _T("/rangerequest")) == 0)
-    {
-      g_WantRangeRequest = true;
-      continue;
-    }
-    else if (lstrcmpi(pURL->text, _T("/connecttimeout")) == 0)
-    {
-      NSIS::stack_t*pConnectTimeout = StackPopItem(ppST);
-      g_ConnectTimeout = _tcstol(pConnectTimeout->text, NULL, 10) * 1000;
-      continue;
-    }
-    else if (lstrcmpi(pURL->text, _T("/receivetimeout")) == 0)
-    {
-      NSIS::stack_t*pReceiveTimeout = StackPopItem(ppST);
-      g_ReceiveTimeout = _tcstol(pReceiveTimeout->text, NULL, 10) * 1000;
-      continue;
-    }
-    else if (lstrcmpi(pURL->text, _T("/reset")) == 0)
-    {
-      StackFreeItem(pURL);
-      Reset();
-      continue;
-    }
-    else if (lstrcmpi(pURL->text, _T("/end")) == 0)
-    {
-freeurlandexit:
-      StackFreeItem(pURL);
-      break;
-    }
-
-    NSIS::stack_t*pFile = StackPopItem(ppST);
-    if (!pFile)
-    {
-      goto freeurlandexit;
-    }
-
-    TaskLock_AcquireExclusive();
-
-    pFile->next = NULL;
-    pURL->next = pFile;
-    NSIS::stack_t*pTasksTail = g_pLocations;
-    while(pTasksTail && pTasksTail->next) pTasksTail = pTasksTail->next;
-    if (pTasksTail)
-    {
-      pTasksTail->next = pURL;
-    }
-    else
-    {
-      g_pLocations = pURL;
-    }
-
-    if (!g_hThread)
-    {
-      DWORD tid;
-      if (g_hGETStartedEvent) {
-        CloseHandle(g_hGETStartedEvent);
-      }
-      g_hGETStartedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-      g_hThread = CreateThread(NULL, 0, TaskThreadProc, NULL, 0, &tid);
-    }
-
-    if (!g_hThread)
-    {
-      goto freeurlandexit;
-    }
-
-#ifndef ONELOCKTORULETHEMALL
-    StatsLock_AcquireExclusive();
-#endif
-    ++g_FilesTotal;
-#ifndef ONELOCKTORULETHEMALL
-    StatsLock_ReleaseExclusive();
-#endif
-    TaskLock_ReleaseExclusive();
-  }
-}
-
-NSISPIEXPORTFUNC GetStats(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
-{
-  NSISPI_INITGLOBALS(N_CCH, N_Vars);
-  StatsLock_AcquireShared();
-  NSIS_SetRegUINT(0, g_Status);
-  NSIS_SetRegUINT(1, g_FilesCompleted);
-  NSIS_SetRegUINT(2, g_FilesTotal - g_FilesCompleted);
-  NSIS_SetRegUINT(3, g_cbCurrXF);
-  NSIS_SetRegStrEmpty(4);
-  if (FILESIZE_UNKNOWN != g_cbCurrTot)
-  {
-    NSIS_SetRegUINT(4, g_cbCurrTot);
-  }
-  StatsLock_ReleaseShared();
-}
-
-EXTERN_C BOOL WINAPI _DllMainCRTStartup(HMODULE hInst, UINT Reason, LPVOID pCtx)
-{
-  if (DLL_PROCESS_ATTACH==Reason)
-  {
-    g_hInst=hInst;
-    InitializeCriticalSection(&g_CritLock);
-  }
-  return TRUE;
-}
-
-BOOL WINAPI DllMain(HINSTANCE hInst, ULONG Reason, LPVOID pCtx)
-{
-  return _DllMainCRTStartup(hInst, Reason, pCtx);
-}
-
-// For some reason VC6++ doesn't like wcsicmp and swprintf.
-// If you use them, you get a linking error about _main
-// as an unresolved external.
-int main(int argc, char**argv)
-{
-  return 0;
-}
+//
+// Copyright (C) Anders Kjersem. Licensed under the zlib/libpng license
+//
+
+#include "InetBgDL.h"
+
+#define USERAGENT _T("NSIS InetBgDL (Mozilla)")
+
+#define STATUS_COMPLETEDALL 0
+#define STATUS_INITIAL 202
+#define STATUS_CONNECTING STATUS_INITIAL //102
+#define STATUS_DOWNLOADING STATUS_INITIAL
+#define STATUS_ERR_GETLASTERROR 418 //HTTP: I'm a teapot: Win32 error code in $3
+#define STATUS_ERR_LOCALFILEWRITEERROR 450 //HTTP: MS parental control extension
+#define STATUS_ERR_CANCELLED 499
+
+typedef DWORD FILESIZE_T; // Limit to 4GB for now...
+#define FILESIZE_UNKNOWN (-1)
+
+#define MAX_STRLEN 1024
+
+HINSTANCE g_hInst;
+NSIS::stack_t*g_pLocations = NULL;
+HANDLE g_hThread = NULL;
+HANDLE g_hGETStartedEvent = NULL;
+volatile UINT g_FilesTotal = 0;
+volatile UINT g_FilesCompleted = 0;
+volatile UINT g_Status = STATUS_INITIAL;
+volatile FILESIZE_T g_cbCurrXF;
+volatile FILESIZE_T g_cbCurrTot = FILESIZE_UNKNOWN;
+CRITICAL_SECTION g_CritLock;
+UINT g_N_CCH;
+PTSTR g_N_Vars;
+TCHAR g_ServerIP[128] = { _T('\0') };
+
+DWORD g_ConnectTimeout = 0;
+DWORD g_ReceiveTimeout = 0;
+
+#define NSISPI_INITGLOBALS(N_CCH, N_Vars) do { \
+  g_N_CCH = N_CCH; \
+  g_N_Vars = N_Vars; \
+  } while(0)
+
+#define ONELOCKTORULETHEMALL
+#ifdef ONELOCKTORULETHEMALL
+#define TaskLock_AcquireExclusive() EnterCriticalSection(&g_CritLock)
+#define TaskLock_ReleaseExclusive() LeaveCriticalSection(&g_CritLock)
+#define StatsLock_AcquireExclusive() TaskLock_AcquireExclusive()
+#define StatsLock_ReleaseExclusive() TaskLock_ReleaseExclusive()
+#define StatsLock_AcquireShared() StatsLock_AcquireExclusive()
+#define StatsLock_ReleaseShared() StatsLock_ReleaseExclusive()
+#endif
+
+PTSTR NSIS_SetRegStr(UINT Reg, LPCTSTR Value)
+{
+  PTSTR s = g_N_Vars + (Reg * g_N_CCH);
+  lstrcpy(s, Value);
+  return s;
+}
+#define NSIS_SetRegStrEmpty(r) NSIS_SetRegStr(r, _T(""))
+void NSIS_SetRegUINT(UINT Reg, UINT Value)
+{
+  TCHAR buf[32];
+  wsprintf(buf, _T("%u"), Value);
+  NSIS_SetRegStr(Reg, buf);
+}
+#define StackFreeItem(pI) GlobalFree(pI)
+NSIS::stack_t* StackPopItem(NSIS::stack_t**ppST)
+{
+  if (*ppST)
+  {
+    NSIS::stack_t*pItem = *ppST;
+    *ppST = pItem->next;
+    return pItem;
+  }
+  return NULL;
+}
+
+void Reset()
+{
+  // The g_hGETStartedEvent event is used to make sure that the Get() call will
+  // acquire the lock before the Reset() call acquires the lock.
+  if (g_hGETStartedEvent) {
+    TRACE(_T("InetBgDl: waiting on g_hGETStartedEvent\n"));
+    WaitForSingleObject(g_hGETStartedEvent, INFINITE);
+    CloseHandle(g_hGETStartedEvent);
+    g_hGETStartedEvent = NULL;
+  }
+
+  TaskLock_AcquireExclusive();
+#ifndef ONELOCKTORULETHEMALL
+  StatsLock_AcquireExclusive();
+#endif
+  g_FilesTotal = 0; // This causes the Task thread to exit the transfer loop
+  if (g_hThread)
+  {
+    TRACE(_T("InetBgDl: waiting on g_hThread\n"));
+    if (WAIT_OBJECT_0 != WaitForSingleObject(g_hThread, 10 * 1000))
+    {
+      TRACE(_T("InetBgDl: terminating g_hThread\n"));
+      TerminateThread(g_hThread, ERROR_OPERATION_ABORTED);
+    }
+    CloseHandle(g_hThread);
+    g_hThread = NULL;
+  }
+  g_FilesTotal = 0;
+  g_FilesCompleted = 0;
+  g_Status = STATUS_INITIAL;
+#ifndef ONELOCKTORULETHEMALL
+  StatsLock_ReleaseExclusive();
+#endif
+  for (NSIS::stack_t*pTmpTast,*pTask = g_pLocations; pTask ;)
+  {
+    pTmpTast = pTask;
+    pTask = pTask->next;
+    StackFreeItem(pTmpTast);
+  }
+  g_pLocations = NULL;
+  TaskLock_ReleaseExclusive();
+}
+
+UINT_PTR __cdecl NSISPluginCallback(UINT Event)
+{
+  switch(Event)
+  {
+  case NSPIM_UNLOAD:
+    Reset();
+    break;
+  }
+  return NULL;
+}
+
+void __stdcall InetStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext,
+                                  DWORD dwInternetStatus,
+                                  LPVOID lpvStatusInformation,
+                                  DWORD dwStatusInformationLength)
+{
+  if (dwInternetStatus == INTERNET_STATUS_NAME_RESOLVED) {
+    // The documentation states the IP address is a PCTSTR but it is actually a
+    // PCSTR.
+    StatsLock_AcquireExclusive();
+    wsprintf(g_ServerIP, _T("%S"), lpvStatusInformation);
+    StatsLock_ReleaseExclusive();
+  }
+
+#if defined(PLUGIN_DEBUG)
+  switch (dwInternetStatus)
+  {
+    case INTERNET_STATUS_RESOLVING_NAME:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_RESOLVING_NAME (%d), name=%s\n"),
+            dwStatusInformationLength, lpvStatusInformation);
+      break;
+    case INTERNET_STATUS_NAME_RESOLVED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_NAME_RESOLVED (%d), resolved name=%s\n"),
+            dwStatusInformationLength, g_ServerIP);
+      break;
+    case INTERNET_STATUS_CONNECTING_TO_SERVER:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTING_TO_SERVER (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_CONNECTED_TO_SERVER:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTED_TO_SERVER (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_SENDING_REQUEST:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_SENDING_REQUEST (%d)\n"),
+               dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_REQUEST_SENT:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_REQUEST_SENT (%d), bytes sent=%d\n"),
+             dwStatusInformationLength, lpvStatusInformation);
+      break;
+    case INTERNET_STATUS_RECEIVING_RESPONSE:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_RECEIVING_RESPONSE (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_RESPONSE_RECEIVED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_RESPONSE_RECEIVED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_CTL_RESPONSE_RECEIVED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_CTL_RESPONSE_RECEIVED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_PREFETCH:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_PREFETCH (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_CLOSING_CONNECTION:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_CLOSING_CONNECTION (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_CONNECTION_CLOSED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTION_CLOSED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_HANDLE_CREATED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_HANDLE_CREATED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_HANDLE_CLOSING:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_HANDLE_CLOSING (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_DETECTING_PROXY:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_DETECTING_PROXY (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_REQUEST_COMPLETE:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_REQUEST_COMPLETE (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_REDIRECT:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_REDIRECT (%d), new url=%s\n"),
+            dwStatusInformationLength, lpvStatusInformation);
+      break;
+    case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_INTERMEDIATE_RESPONSE (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_USER_INPUT_REQUIRED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_USER_INPUT_REQUIRED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_STATE_CHANGE:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_STATE_CHANGE (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_COOKIE_SENT:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_SENT (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_COOKIE_RECEIVED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_RECEIVED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_PRIVACY_IMPACTED:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_PRIVACY_IMPACTED (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_P3P_HEADER:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_P3P_HEADER (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_P3P_POLICYREF:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_P3P_POLICYREF (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    case INTERNET_STATUS_COOKIE_HISTORY:
+      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_HISTORY (%d)\n"),
+            dwStatusInformationLength);
+      break;
+    default:
+      TRACE(_T("InetBgDl: Unknown Status %d\n"), dwInternetStatus);
+      break;
+  }
+#endif
+}
+
+DWORD CALLBACK TaskThreadProc(LPVOID ThreadParam)
+{
+  NSIS::stack_t *pURL,*pFile;
+  HINTERNET hInetSes = NULL, hInetFile = NULL;
+  DWORD cbio = sizeof(DWORD);
+  HANDLE hLocalFile;
+  bool completedFile = false;
+startnexttask:
+  hLocalFile = INVALID_HANDLE_VALUE;
+  pFile = NULL;
+  TaskLock_AcquireExclusive();
+  // Now that we've acquired the lock, we can set the event to indicate this.
+  // SetEvent will likely never fail, but if it does we should set it to NULL
+  // to avoid anyone waiting on it.
+  if (!SetEvent(g_hGETStartedEvent)) {
+    CloseHandle(g_hGETStartedEvent);
+    g_hGETStartedEvent = NULL;
+  }
+  pURL = g_pLocations;
+  if (pURL)
+  {
+    pFile = pURL->next;
+    g_pLocations = pFile->next;
+  }
+#ifndef ONELOCKTORULETHEMALL
+  StatsLock_AcquireExclusive();
+#endif
+  if (completedFile)
+  {
+    ++g_FilesCompleted;
+  }
+  completedFile = false;
+  g_cbCurrXF = 0;
+  g_cbCurrTot = FILESIZE_UNKNOWN;
+  if (!pURL)
+  {
+    if (g_FilesTotal)
+    {
+      if (g_FilesTotal == g_FilesCompleted)
+      {
+        g_Status = STATUS_COMPLETEDALL;
+      }
+    }
+    g_hThread = NULL;
+  }
+#ifndef ONELOCKTORULETHEMALL
+  StatsLock_ReleaseExclusive();
+#endif
+  TaskLock_ReleaseExclusive();
+
+  if (!pURL)
+  {
+    if (0)
+    {
+diegle:
+      DWORD gle = GetLastError();
+      //TODO? if (ERROR_INTERNET_EXTENDED_ERROR==gle) InternetGetLastResponseInfo(...)
+      g_Status = STATUS_ERR_GETLASTERROR;
+    }
+    if (hInetSes)
+    {
+      InternetCloseHandle(hInetSes);
+    }
+    if (INVALID_HANDLE_VALUE != hLocalFile)
+    {
+      CloseHandle(hLocalFile);
+    }
+    StackFreeItem(pURL);
+    StackFreeItem(pFile);
+    return 0;
+  }
+
+  if (!hInetSes)
+  {
+    hInetSes = InternetOpen(USERAGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
+    if (!hInetSes)
+    {
+      TRACE(_T("InetBgDl: InternetOpen failed with gle=%u\n"),
+            GetLastError());
+      goto diegle;
+    }
+    InternetSetStatusCallback(hInetSes, (INTERNET_STATUS_CALLBACK)InetStatusCallback);
+
+    //msdn.microsoft.com/library/default.asp?url=/workshop/components/offline/offline.asp#Supporting Offline Browsing in Applications and Components
+    ULONG longOpt;
+    DWORD cbio = sizeof(ULONG);
+    if (InternetQueryOption(hInetSes, INTERNET_OPTION_CONNECTED_STATE, &longOpt, &cbio))
+    {
+      if (INTERNET_STATE_DISCONNECTED_BY_USER&longOpt)
+      {
+        INTERNET_CONNECTED_INFO ci = {INTERNET_STATE_CONNECTED, 0};
+        InternetSetOption(hInetSes, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
+      }
+    }
+
+    // Change the default connect timeout if specified.
+    if(g_ConnectTimeout > 0)
+    {
+      InternetSetOption(hInetSes, INTERNET_OPTION_CONNECT_TIMEOUT,
+                        &g_ConnectTimeout, sizeof(g_ConnectTimeout));
+    }
+
+    // Change the default receive timeout if specified.
+    if (g_ReceiveTimeout)
+    {
+      InternetSetOption(hInetSes, INTERNET_OPTION_RECEIVE_TIMEOUT,
+                        &g_ReceiveTimeout, sizeof(DWORD));
+    }
+  }
+
+  DWORD ec = ERROR_SUCCESS;
+  hLocalFile = CreateFile(pFile->text, GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_DELETE,NULL,CREATE_ALWAYS, 0, NULL);
+  if (INVALID_HANDLE_VALUE == hLocalFile)
+  {
+    TRACE(_T("InetBgDl: CreateFile file handle invalid\n"));
+    goto diegle;
+  }
+
+  const DWORD IOURedirFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
+                              INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
+  const DWORD IOUCacheFlags = INTERNET_FLAG_RESYNCHRONIZE |
+                              INTERNET_FLAG_NO_CACHE_WRITE |
+                              INTERNET_FLAG_PRAGMA_NOCACHE |
+                              INTERNET_FLAG_RELOAD;
+  const DWORD IOUCookieFlags = INTERNET_FLAG_NO_COOKIES;
+  DWORD IOUFlags = IOURedirFlags | IOUCacheFlags | IOUCookieFlags |
+                   INTERNET_FLAG_NO_UI | INTERNET_FLAG_EXISTING_CONNECT;
+
+  TCHAR *hostname = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR)),
+        *urlpath = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR)),
+        *extrainfo = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR));
+
+  URL_COMPONENTS uc = { sizeof(URL_COMPONENTS), NULL, 0, (INTERNET_SCHEME)0,
+                        hostname, MAX_STRLEN, (INTERNET_PORT)0, NULL, 0,
+                        NULL, 0, urlpath, MAX_STRLEN, extrainfo, MAX_STRLEN};
+  uc.dwHostNameLength = uc.dwUrlPathLength = uc.dwExtraInfoLength = MAX_STRLEN;
+
+  if (!InternetCrackUrl(pURL->text, 0, ICU_ESCAPE, &uc))
+  {
+    // Bad url or param passed in
+    TRACE(_T("InetBgDl: InternetCrackUrl false with url=%s, gle=%u\n"),
+          pURL->text, GetLastError());
+    goto diegle;
+  }
+
+  TRACE(_T("InetBgDl: scheme_id=%d, hostname=%s, port=%d, urlpath=%s, extrainfo=%s\n"),
+        uc.nScheme, hostname, uc.nPort, urlpath, extrainfo);
+
+  // Only http and https are supported
+  if (uc.nScheme != INTERNET_SCHEME_HTTP &&
+      uc.nScheme != INTERNET_SCHEME_HTTPS)
+  {
+    TRACE(_T("InetBgDl: only http and https is supported, aborting...\n"));
+    goto diegle;
+  }
+
+  TRACE(_T("InetBgDl: calling InternetOpenUrl with url=%s\n"), pURL->text);
+  hInetFile = InternetOpenUrl(hInetSes, pURL->text,
+                              NULL, 0, IOUFlags |
+                              (uc.nScheme == INTERNET_SCHEME_HTTPS ?
+                               INTERNET_FLAG_SECURE : 0), 1);
+  if (!hInetFile)
+  {
+    TRACE(_T("InetBgDl: InternetOpenUrl failed with gle=%u\n"),
+          GetLastError());
+    goto diegle;
+  }
+
+  // Get the file length via the Content-Length header
+  FILESIZE_T cbThisFile;
+  cbio = sizeof(cbThisFile);
+  if (!HttpQueryInfo(hInetFile,
+                     HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
+                     &cbThisFile, &cbio, NULL))
+  {
+    cbThisFile = FILESIZE_UNKNOWN;
+  }
+  TRACE(_T("InetBgDl: file size=%d bytes\n"), cbThisFile);
+
+  // Setup a buffer of size 256KiB to store the downloaded data.
+  const UINT cbBufXF = 262144;
+  // Use a 4MiB read buffer for the connection.
+  // Bigger buffers will be faster.
+  // cbReadBufXF should be a multiple of cbBufXF.
+  const UINT cbReadBufXF = 4194304;
+  BYTE bufXF[cbBufXF];
+
+  // Up the default internal buffer size from 4096 to internalReadBufferSize.
+  DWORD internalReadBufferSize = cbReadBufXF;
+  if (!InternetSetOption(hInetFile, INTERNET_OPTION_READ_BUFFER_SIZE,
+                         &internalReadBufferSize, sizeof(DWORD)))
+  {
+    TRACE(_T("InetBgDl: InternetSetOption failed to set read buffer size to %u bytes, gle=%u\n"),
+          internalReadBufferSize, GetLastError());
+
+    // Maybe it's too big, try half of the optimal value.  If that fails just
+    // use the default.
+    internalReadBufferSize /= 2;
+    if (!InternetSetOption(hInetFile, INTERNET_OPTION_READ_BUFFER_SIZE,
+                           &internalReadBufferSize, sizeof(DWORD)))
+    {
+      TRACE(_T("InetBgDl: InternetSetOption failed to set read buffer size ") \
+            _T("to %u bytes (using default read buffer size), gle=%u\n"),
+            internalReadBufferSize, GetLastError());
+    }
+  }
+
+  for(;;)
+  {
+    DWORD cbio = 0, cbXF = 0;
+    BOOL retXF = InternetReadFile(hInetFile, bufXF, cbBufXF, &cbio);
+    if (!retXF)
+    {
+      ec = GetLastError();
+      TRACE(_T("InetBgDl: InternetReadFile failed, gle=%u\n"), ec);
+      break;
+    }
+
+    if (0 == cbio)
+    {
+      ASSERT(ERROR_SUCCESS == ec);
+      // EOF or broken connection?
+      // TODO: Can InternetQueryDataAvailable detect this?
+
+      TRACE(_T("InetBgDl: InternetReadFile true with 0 cbio, cbThisFile=%d, gle=%u\n"),
+            cbThisFile, GetLastError());
+      // If we haven't transferred all of the file, and we know how big the file
+      // is, and we have no more data to read from the HTTP request, then set a
+      // broken pipe error. Reading without StatsLock is ok in this thread.
+      if (FILESIZE_UNKNOWN != cbThisFile && g_cbCurrXF != cbThisFile)
+      {
+        TRACE(_T("InetBgDl: downloaded file size of %d bytes doesn't equal ") \
+              _T("expected file size of %d bytes\n"), g_cbCurrXF, cbThisFile);
+        ec = ERROR_BROKEN_PIPE;
+      }
+      break;
+    }
+
+    // Check if we canceled the download
+    if (0 == g_FilesTotal)
+    {
+      TRACE(_T("InetBgDl: 0 == g_FilesTotal, aborting transfer loop...\n"));
+      ec = ERROR_CANCELLED;
+      break;
+    }
+
+    cbXF = cbio;
+    if (cbXF)
+    {
+      retXF = WriteFile(hLocalFile, bufXF, cbXF, &cbio, NULL);
+      if (!retXF || cbXF != cbio)
+      {
+        ec = GetLastError();
+        break;
+      }
+
+      StatsLock_AcquireExclusive();
+      if (FILESIZE_UNKNOWN != cbThisFile) {
+        g_cbCurrTot = cbThisFile;
+      }
+      g_cbCurrXF += cbXF;
+      StatsLock_ReleaseExclusive();
+    }
+  }
+
+  TRACE(_T("InetBgDl: TaskThreadProc completed %s, ec=%u\n"), pURL->text, ec);
+  InternetCloseHandle(hInetFile);
+  if (ERROR_SUCCESS == ec)
+  {
+    if (INVALID_HANDLE_VALUE != hLocalFile)
+    {
+      CloseHandle(hLocalFile);
+      hLocalFile = INVALID_HANDLE_VALUE;
+    }
+    StackFreeItem(pURL);
+    StackFreeItem(pFile);
+    ++completedFile;
+  }
+  else
+  {
+    TRACE(_T("InetBgDl: failed with ec=%u\n"), ec);
+    SetLastError(ec);
+    goto diegle;
+  }
+  goto startnexttask;
+}
+
+NSISPIEXPORTFUNC Get(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
+{
+  pX->RegisterPluginCallback(g_hInst, NSISPluginCallback);
+  for (;;)
+  {
+    NSIS::stack_t*pURL = StackPopItem(ppST);
+    if (!pURL)
+    {
+      break;
+    }
+
+    if (lstrcmpi(pURL->text, _T("/connecttimeout")) == 0)
+    {
+      NSIS::stack_t*pConnectTimeout = StackPopItem(ppST);
+      g_ConnectTimeout = _tcstol(pConnectTimeout->text, NULL, 10) * 1000;
+      continue;
+    }
+    else if (lstrcmpi(pURL->text, _T("/receivetimeout")) == 0)
+    {
+      NSIS::stack_t*pReceiveTimeout = StackPopItem(ppST);
+      g_ReceiveTimeout = _tcstol(pReceiveTimeout->text, NULL, 10) * 1000;
+      continue;
+    }
+    else if (lstrcmpi(pURL->text, _T("/reset")) == 0)
+    {
+      StackFreeItem(pURL);
+      Reset();
+      continue;
+    }
+    else if (lstrcmpi(pURL->text, _T("/end")) == 0)
+    {
+freeurlandexit:
+      StackFreeItem(pURL);
+      break;
+    }
+
+    NSIS::stack_t*pFile = StackPopItem(ppST);
+    if (!pFile)
+    {
+      goto freeurlandexit;
+    }
+
+    TaskLock_AcquireExclusive();
+
+    pFile->next = NULL;
+    pURL->next = pFile;
+    NSIS::stack_t*pTasksTail = g_pLocations;
+    while(pTasksTail && pTasksTail->next) pTasksTail = pTasksTail->next;
+    if (pTasksTail)
+    {
+      pTasksTail->next = pURL;
+    }
+    else
+    {
+      g_pLocations = pURL;
+    }
+
+    if (!g_hThread)
+    {
+      DWORD tid;
+      if (g_hGETStartedEvent) {
+        CloseHandle(g_hGETStartedEvent);
+      }
+      g_hGETStartedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+      g_hThread = CreateThread(NULL, 0, TaskThreadProc, NULL, 0, &tid);
+    }
+
+    if (!g_hThread)
+    {
+      goto freeurlandexit;
+    }
+
+#ifndef ONELOCKTORULETHEMALL
+    StatsLock_AcquireExclusive();
+#endif
+    ++g_FilesTotal;
+#ifndef ONELOCKTORULETHEMALL
+    StatsLock_ReleaseExclusive();
+#endif
+    TaskLock_ReleaseExclusive();
+  }
+}
+
+NSISPIEXPORTFUNC GetStats(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
+{
+  NSISPI_INITGLOBALS(N_CCH, N_Vars);
+  StatsLock_AcquireShared();
+  NSIS_SetRegUINT(0, g_Status);
+  NSIS_SetRegUINT(1, g_FilesCompleted);
+  NSIS_SetRegUINT(2, g_FilesTotal - g_FilesCompleted);
+  NSIS_SetRegUINT(3, g_cbCurrXF);
+  NSIS_SetRegStrEmpty(4);
+  if (FILESIZE_UNKNOWN != g_cbCurrTot)
+  {
+    NSIS_SetRegUINT(4, g_cbCurrTot);
+  }
+  NSIS_SetRegStr(5, g_ServerIP);
+  StatsLock_ReleaseShared();
+}
+
+EXTERN_C BOOL WINAPI _DllMainCRTStartup(HMODULE hInst, UINT Reason, LPVOID pCtx)
+{
+  if (DLL_PROCESS_ATTACH==Reason)
+  {
+    g_hInst=hInst;
+    InitializeCriticalSection(&g_CritLock);
+  }
+  return TRUE;
+}
+
+BOOL WINAPI DllMain(HINSTANCE hInst, ULONG Reason, LPVOID pCtx)
+{
+  return _DllMainCRTStartup(hInst, Reason, pCtx);
+}
+
+// For some reason VC6++ doesn't like wcsicmp and swprintf.
+// If you use them, you get a linking error about _main
+// as an unresolved external.
+int main(int argc, char**argv)
+{
+  return 0;
+}
--- a/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.h
+++ b/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.h
@@ -1,58 +1,65 @@
-//
-// Copyright (C) Anders Kjersem. Licensed under the zlib/libpng license
-//
-
-#if (defined(_MSC_VER) && !defined(_DEBUG))
-  #pragma comment(linker,"/opt:nowin98")
-  #pragma comment(linker,"/ignore:4078")
-  #pragma comment(linker,"/merge:.rdata=.text")
-#endif
-
-#ifdef UNICODE
-# ifndef _UNICODE
-#    define _UNICODE
-#  endif
-#endif
-
-#define _WIN32_WINNT 0x0400
-#include <windows.h>
-#include <tchar.h>
-#include <wininet.h>
-
-#if defined(_DEBUG) || 0
-#  define PLUGIN_DEBUG 1
-#  define TRACE2(fmt,p1,p2) do {TCHAR b[666];wsprintf(b,fmt,p1,p2);OutputDebugString(b);}while(0)
-#  define TRACEA OutputDebugStringA
-#else
-#  define TRACE2(fmt,p1,p2)
-#  define TRACEA(fmt)
-#endif
-#define TRACE1(fmt,p1) TRACE2(fmt,p1,0)
-
-#ifndef ASSERT
-#  define ASSERT(x)
-#endif
-
-#define NSISPIEXPORTFUNC EXTERN_C void __declspec(dllexport) __cdecl
-
-namespace NSIS {
-
-#define NSISCALL __stdcall
-typedef struct _xparams_t {
-  LPVOID xx1;//exec_flags_type *exec_flags;
-  int (NSISCALL *ExecuteCodeSegment)(int, HWND);
-  void (NSISCALL *validate_filename)(TCHAR*);
-  int (NSISCALL *RegisterPluginCallback)(HMODULE,LPVOID);
-} xparams_t;
-typedef struct _stack_t {
-  struct _stack_t *next;
-  TCHAR text[1];
-} stack_t;
-
-} // namespace NSIS
-
-enum NSPIM 
-{
-  NSPIM_UNLOAD,
-  NSPIM_GUIUNLOAD,
-};
+//
+// Copyright (C) Anders Kjersem. Licensed under the zlib/libpng license
+//
+
+#if (defined(_MSC_VER) && !defined(_DEBUG))
+  #pragma comment(linker,"/opt:nowin98")
+  #pragma comment(linker,"/ignore:4078")
+  #pragma comment(linker,"/merge:.rdata=.text")
+#endif
+
+#ifdef UNICODE
+# ifndef _UNICODE
+#    define _UNICODE
+#  endif
+#endif
+
+#define _WIN32_WINNT 0x0400
+#include <windows.h>
+#include <tchar.h>
+#include <wininet.h>
+
+#if defined(_DEBUG) || 0
+#  define PLUGIN_DEBUG 1
+void MYTRACE(LPCTSTR fmt, ...)
+{
+  va_list argptr;
+  va_start(argptr, fmt);
+  TCHAR buffer[2048] = { _T('\0') };
+  wvsprintf(buffer, fmt, argptr);
+  buffer[(sizeof(buffer)/sizeof(*buffer)) - 1] = _T('\0');
+  OutputDebugString(buffer);
+  va_end(argptr);
+}
+#else
+void MYTRACE(...) { }
+#endif
+#  define TRACE MYTRACE
+
+#ifndef ASSERT
+#  define ASSERT(x)
+#endif
+
+#define NSISPIEXPORTFUNC EXTERN_C void __declspec(dllexport) __cdecl
+
+namespace NSIS {
+
+#define NSISCALL __stdcall
+typedef struct _xparams_t {
+  LPVOID xx1;//exec_flags_type *exec_flags;
+  int (NSISCALL *ExecuteCodeSegment)(int, HWND);
+  void (NSISCALL *validate_filename)(TCHAR*);
+  int (NSISCALL *RegisterPluginCallback)(HMODULE,LPVOID);
+} xparams_t;
+typedef struct _stack_t {
+  struct _stack_t *next;
+  TCHAR text[1];
+} stack_t;
+
+} // namespace NSIS
+
+enum NSPIM 
+{
+  NSPIM_UNLOAD,
+  NSPIM_GUIUNLOAD,
+};
index 3af9e998bb18784048587c15b5b4fa5ea2c27c6b..7d5b793cb5c1baa534dc524bcbea7425e9546f3a
GIT binary patch
literal 37376
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
G;r{{_pLwYO