Skip to content

Commit

Permalink
Test CI on the uninstaller fixes (#696)
Browse files Browse the repository at this point in the history
* Merge pull request from GHSA-v8h5-w775-5rrw

* Only run uninstaller on directory with _conda.exe

* Do not run uninstaller in system-critical directories

* Add loop to check for minimum conda files

* Add missing goto after passing all checks

* Add push/pop operations to un.onInit

* Fix typo

* Use WordFind instead of UnStrTok

* Start WordFind loop with 1

* Add additional files to check for conda

* Add confirmation dialog before uninstalling

* Resolve INSTDIR

* Add exceptions to fill_template

* Do not replace NSIS predefines

* Add example template

* Update constructor/nsis/main.nsi.tmpl

Co-authored-by: jaimergp <jaimergp@users.noreply.github.com>

* Add check for GetFullPathName

* Add additional directory checks for uninstaller

* Add quotes to INSTDIR in uninstaller message

* Add check to GetFullPathName

* Add additional directory checks for uninstaller

* Add check to GetFullPathName for uninstaller

* Remove duplicate comment

* Replace missing global variable with local variable

* Fix INSTDIR path resolution command

* Fix GetFullPathName logic

* Replace @name@ with ${NAME}

---------

Co-authored-by: jaimergp <jaimergp@users.noreply.github.com>

* add news

* Remove Scripts\activate.bat from required files on $INSTDIR (some installers do not ship conda)

---------

Co-authored-by: Marco Esters <mesters@anaconda.com>
  • Loading branch information
jaimergp and marcoesters committed Jul 13, 2023
1 parent 0c2bd78 commit bc0d653
Show file tree
Hide file tree
Showing 7 changed files with 877 additions and 5 deletions.
84 changes: 82 additions & 2 deletions constructor/nsis/main.nsi.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Page Custom mui_AnaCustomOptions_Show
!insertmacro MUI_PAGE_FINISH

!insertmacro MUI_UNPAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.OnDirectoryLeave
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
Expand Down Expand Up @@ -490,9 +491,9 @@ Function .onInit
# If we're a 64-bit installer, make sure it's 64-bit Windows
${IfNot} ${RunningX64}
MessageBox MB_OK|MB_ICONEXCLAMATION \
"This installer is for a 64-bit version for @NAME@$\n\
"This installer is for a 64-bit version for ${NAME}$\n\
but your system is 32-bit. Please use the 32-bit Windows$\n\
@NAME@ installer." \
${NAME} installer." \
/SD IDOK
Abort
${EndIf}
Expand Down Expand Up @@ -671,6 +672,60 @@ Function .onInit
FunctionEnd

Function un.onInit
Push $0
Push $1
Push $2
Push $3

# Resolve INSTDIR
GetFullPathName $0 $INSTDIR
# If the directory does not exist or cannot be resolved, $0 will be empty
StrCmp $0 "" invalid_dir
StrCpy $INSTDIR $0

# Never run the uninstaller when $INSTDIR points at system-critical directories

StrLen $InstDirLen $INSTDIR
# INSTDIR is a full path and has no trailing backslash,
# so if its length is 2, it is pointed at a system root
StrCmp $InstdirLen 2 invalid_dir

# Never delete anything inside Windows
StrCpy $0 $INSTDIR 7 3
StrCmp $0 "Windows" invalid_dir

StrCpy $0 "ALLUSERSPROFILE APPDATA LOCALAPPDATA PROGRAMDATA PROGRAMFILES PROGRAMFILES(x86) PUBLIC SYSTEMDRIVE SYSTEMROOT USERPROFILE"
StrCpy $1 1
loop_critical:
${WordFind} $0 " " "E+$1" $2
IfErrors endloop_critical
ReadEnvStr $3 $2
StrCmp $3 $INSTDIR invalid_dir
IntOp $1 $1 + 1
goto loop_critical
endloop_critical:

# Primitive check to see that $INSTDIR points to a conda directory
StrCpy $0 "_conda.exe conda-meta\history"
StrCpy $1 1
loop_conda:
${WordFind} $0 " " "E+$1" $2
IfErrors endloop_conda
IfFileExists $INSTDIR\$2 0 invalid_dir
IntOp $1 $1 + 1
goto loop_conda
endloop_conda:

# All checks have passed
goto valid_dir

invalid_dir:
MessageBox MB_OK|MB_ICONSTOP \
"Error: $INSTDIR is not a valid conda directory. Please run the uninstaller from a conda directory." \
/SD IDABORT
abort
valid_dir:

# Select the correct registry to look at, depending
# on whether it's a 32-bit or 64-bit installer
SetRegView @BITS@
Expand All @@ -691,6 +746,11 @@ Function un.onInit
${Else}
SetShellVarContext All
${EndIf}

Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd

# http://nsis.sourceforge.net/Check_for_spaces_in_a_directory_path
Expand Down Expand Up @@ -940,6 +1000,17 @@ Function .onVerifyInstDir
PathGood:
FunctionEnd

Function un.OnDirectoryLeave
MessageBox MB_YESNO \
"Are you sure you want to remove '$INSTDIR' and all of its contents?" \
/SD IDYES \
IDYES confirmed_yes IDNO confirmed_no
confirmed_no:
MessageBox MB_OK|MB_ICONSTOP "Uninstallation aborted by user." /SD IDOK
Quit
confirmed_yes:
FunctionEnd

# Make function available for both installer and uninstaller
# Uninstaller functions need an `un.` prefix, so we use a macro to do both
# see https://nsis.sourceforge.io/Sharing_functions_between_Installer_and_Uninstaller
Expand Down Expand Up @@ -996,6 +1067,15 @@ Section "Install"
File "@NSIS_DIR@\_nsis.py"
File "@NSIS_DIR@\_system_path.py"

# Resolve INSTDIR so that paths and registry keys do not contain '..' or similar strings.
# $0 is empty if the directory doesn't exist, but the File commands should have created it already.
GetFullPathName $0 $INSTDIR
${If} $0 == ""
MessageBox MB_ICONSTOP "Error resolving installation directory." /SD IDABORT
Quit
${EndIf}
StrCpy $INSTDIR $0

ReadEnvStr $0 SystemRoot
# set PATH for the installer process, so that MSVC runtimes get found OK
# This is also isolating PATH to be just us and Windows core stuff, which hopefully avoids
Expand Down
4 changes: 2 additions & 2 deletions constructor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def filename_dist(dist):
return dist


def fill_template(data, d):
def fill_template(data, d, exceptions=[]):
pat = re.compile(r'__(\w+)__')

def replace(match):
key = match.group(1)
return d[key]
return key if key in exceptions else d[key]

return pat.sub(replace, data)

Expand Down
21 changes: 20 additions & 1 deletion constructor/winexe.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,25 @@ def make_nsi(info, dir_path, extra_files=None, temp_extra_files=None):
'INDEX_CACHE': '@cache',
'REPODATA_RECORD': '@repodata_record.json',
}

# These are NSIS predefines and must not be replaced
# https://nsis.sourceforge.io/Docs/Chapter5.html#precounter
nsis_predefines = [
"COUNTER",
"DATE",
"FILE",
"FILEDIR",
"FUNCTION",
"GLOBAL",
"LINE",
"MACRO",
"PAGEEX",
"SECTION",
"TIME",
"TIMESTAMP",
"UNINSTALL",
]

conclusion_text = info.get("conclusion_text", "")
if conclusion_text:
conclusion_lines = conclusion_text.strip().splitlines()
Expand Down Expand Up @@ -277,7 +296,7 @@ def make_nsi(info, dir_path, extra_files=None, temp_extra_files=None):
ppd["custom_welcome"] = info.get("welcome_file", "").endswith(".nsi")
ppd["custom_conclusion"] = info.get("conclusion_file", "").endswith(".nsi")
data = preprocess(data, ppd)
data = fill_template(data, replace)
data = fill_template(data, replace, exceptions=nsis_predefines)
if info['_platform'].startswith("win") and sys.platform != 'win32':
# Branding /TRIM commannd is unsupported on non win platform
data_lines = data.split("\n")
Expand Down
28 changes: 28 additions & 0 deletions examples/custom_nsis_template/EULA.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Copyright (c) 2016, Example, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Example, Inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL EXAMPLE, INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The License has to be read correctly so that dollar signs don't lead to an unset
parameter error:
You have to pay US $8, if you can read this.
12 changes: 12 additions & 0 deletions examples/custom_nsis_template/construct.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: custom
version: X
ignore_duplicate_files: True
installer_filename: {{ name }}-installer.exe
installer_type: exe
license_file: EULA.txt

nsis_template: custom.nsi.tmpl

# This is required, even if we include no specs in this installer.
channels:
- https://repo.anaconda.com/pkgs/main
Loading

0 comments on commit bc0d653

Please sign in to comment.