From 6252a2ef62739a09a1d63aaca71097a1de1dbb41 Mon Sep 17 00:00:00 2001 From: Robert Strong Date: Tue, 5 Mar 2013 22:07:59 -0800 Subject: [PATCH] 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 | 7 +- browser/installer/windows/nsis/defines.nsi.in | 8 +- browser/installer/windows/nsis/stub.nsi | 651 ++++++-- .../nsis/Contrib/InetBgDL/InetBgDL.cpp | 1436 ++++++++--------- .../nsis/Contrib/InetBgDL/InetBgDL.h | 123 +- other-licenses/nsis/Plugins/InetBgDL.dll | Bin 44032 -> 37376 bytes 6 files changed, 1261 insertions(+), 964 deletions(-) diff --git a/browser/branding/official/branding.nsi b/browser/branding/official/branding.nsi index 684db0ffe9d..a817e0391c7 100644 --- a/browser/branding/official/branding.nsi +++ b/browser/branding/official/branding.nsi @@ -13,9 +13,10 @@ !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" diff --git a/browser/installer/windows/nsis/defines.nsi.in b/browser/installer/windows/nsis/defines.nsi.in index 54367764ffb..a976395555a 100644 --- a/browser/installer/windows/nsis/defines.nsi.in +++ b/browser/installer/windows/nsis/defines.nsi.in @@ -29,13 +29,19 @@ # 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 diff --git a/browser/installer/windows/nsis/stub.nsi b/browser/installer/windows/nsis/stub.nsi index 465c339988a..738206c989c 100644 --- a/browser/installer/windows/nsis/stub.nsi +++ b/browser/installer/windows/nsis/stub.nsi @@ -56,7 +56,7 @@ Var CanWriteToInstallDir Var HasRequiredSpaceAvailable Var IsDownloadFinished Var Initialized -Var DownloadSize +Var DownloadSizeBytes Var HalfOfDownload Var DownloadReset Var ExistingTopDir @@ -64,33 +64,111 @@ Var SpaceAvailableBytes Var InitialInstallDir Var HandleDownload Var CanSetAsDefault -Var TmpVal Var InstallCounterStep +Var TmpVal 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_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 -!define DownloadIntervalMS 200 ; Interval for the download timer -!define InstallIntervalMS 100 ; Interval for the install timer +/** + * 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 + +/** + * 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 @@ -99,10 +177,14 @@ Var CTL_RIGHT_PX ; 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 @@ -114,10 +196,7 @@ Var CTL_RIGHT_PX !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" @@ -134,9 +213,10 @@ Var CTL_RIGHT_PX !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}" @@ -202,7 +282,7 @@ 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. @@ -240,7 +320,7 @@ Function .onInit ; 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" @@ -318,9 +398,17 @@ Function .onInit 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" @@ -352,45 +440,6 @@ 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 @@ -405,6 +454,183 @@ Function .onUserAbort 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 @@ -422,12 +648,11 @@ Function createIntro !else StrCpy $CheckboxInstallMaintSvc "0" !endif + StrCpy $WasOptionsButtonClicked "0" nsDialogs::Create /NOUNLOAD 1018 Pop $Dialog - StrCpy $WasOptionsButtonClicked "" - GetFunctionAddress $0 OnBack nsDialogs::OnBack /NOUNLOAD $0 @@ -443,12 +668,20 @@ Function createIntro 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 " ")' @@ -461,7 +694,7 @@ Function createIntro ${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} "" @@ -482,6 +715,11 @@ Function createIntro 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 @@ -493,6 +731,14 @@ 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 @@ -522,11 +768,28 @@ Function leaveIntro 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} @@ -615,20 +878,20 @@ Function createOptions ${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 @@ -638,11 +901,11 @@ Function createOptions 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} @@ -650,7 +913,7 @@ Function createOptions ; 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 @@ -705,7 +968,9 @@ Function createOptions 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" @@ -713,12 +978,18 @@ Function createOptions ${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 @@ -742,6 +1013,13 @@ Function leaveOptions 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 @@ -849,7 +1127,8 @@ Function createInstall ${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 @@ -881,7 +1160,7 @@ Function createInstall 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 @@ -894,13 +1173,25 @@ Function createInstall 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" @@ -909,8 +1200,10 @@ Function createInstall StrCpy $ExistingProfile "0" ${EndIf} + StrCpy $DownloadServerIP "" + System::Call "kernel32::GetTickCount()l .s" - Pop $StartTickCount + Pop $StartDownloadPhaseTickCount ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS} @@ -924,14 +1217,10 @@ Function createInstall ${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}" @@ -948,37 +1237,90 @@ Function OnDownload # $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} @@ -991,12 +1333,31 @@ Function OnDownload ; 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} @@ -1020,7 +1381,7 @@ Function OnDownload 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} @@ -1031,14 +1392,17 @@ Function OnDownload 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 @@ -1107,15 +1471,40 @@ Function OnDownload 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 @@ -1127,11 +1516,11 @@ 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 @@ -1155,7 +1544,7 @@ Function CheckInstall Delete "$PLUGINSDIR\download.exe" Delete "$PLUGINSDIR\${CONFIG_INI}" System::Call "kernel32::GetTickCount()l .s" - Pop $InstallTickCount + Pop $EndInstallPhaseTickCount ${NSD_CreateTimer} FinishInstall ${InstallIntervalMS} ${EndUnless} ${EndIf} @@ -1212,14 +1601,11 @@ Function FinishInstall 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 @@ -1424,12 +1810,12 @@ 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" @@ -1458,11 +1844,10 @@ 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 @@ -1474,9 +1859,7 @@ Function DisplayDownloadError ${EndIf} ${EndIf} - SetAutoClose true - StrCpy $R9 "2" - Call RelativeGotoPage + Call SendPing FunctionEnd Function OpenManualDownloadURL diff --git a/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.cpp b/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.cpp index 3ed34798f59..e4f61d6c62b 100644 --- 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: - * ://:/ - * - * @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 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; +} diff --git a/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.h b/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.h index d2c1bdd2501..c7fabec2bbf 100644 --- 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 -#include -#include - -#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 +#include +#include + +#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, +}; diff --git a/other-licenses/nsis/Plugins/InetBgDL.dll b/other-licenses/nsis/Plugins/InetBgDL.dll index 3af9e998bb18784048587c15b5b4fa5ea2c27c6b..7d5b793cb5c1baa534dc524bcbea7425e9546f3a 100644 GIT binary patch delta 13040 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�#makIP|&RC$$8Nw?ZT1_c%q;2$(T}i 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