author | Robert 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 id | 24156 |
push user | emorley@mozilla.com |
push date | Wed, 06 Mar 2013 16:11:48 +0000 |
treeherder | mozilla-inbound@310b85d5fa77 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bbondy |
bugs | 811573, 797998, 836044 |
milestone | 22.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
22.0a1
/
20130306031012
/
pushlog to previous
nightly linux64
22.0a1
/
20130306031012
/
pushlog to previous
nightly mac
22.0a1
/
20130306031012
/
pushlog to previous
nightly win32
22.0a1
/
20130306031012
/
pushlog to previous
nightly win64
22.0a1
/
20130306031012
/
pushlog to previous
|
--- 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