Skip to content

Commit

Permalink
powershell - fix quoting values (ansible#71411)
Browse files Browse the repository at this point in the history
* powershell - fix quoting values

* Add ignore for smart quote skip

(cherry picked from commit 72a7cb4)
  • Loading branch information
jborean93 committed Aug 25, 2020
1 parent 3be5974 commit 56d54ab
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 26 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/powershell-fix-quoting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bugfixes:
- powershell - fix escaping of strings that broken modules like fetch when dealing with special chars - https://github.com/ansible/ansible/issues/62781
2 changes: 1 addition & 1 deletion lib/ansible/plugins/connection/winrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ def fetch_file(self, in_path, out_path):
while True:
try:
script = '''
$path = "%(path)s"
$path = '%(path)s'
If (Test-Path -Path $path -PathType Leaf)
{
$buffer_size = %(buffer_size)d
Expand Down
39 changes: 14 additions & 25 deletions lib/ansible/plugins/shell/powershell.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ def set_user_facl(self, paths, user, mode):
def remove(self, path, recurse=False):
path = self._escape(self._unquote(path))
if recurse:
return self._encode_script('''Remove-Item "%s" -Force -Recurse;''' % path)
return self._encode_script('''Remove-Item '%s' -Force -Recurse;''' % path)
else:
return self._encode_script('''Remove-Item "%s" -Force;''' % path)
return self._encode_script('''Remove-Item '%s' -Force;''' % path)

def mkdtemp(self, basefile=None, system=False, mode=None, tmpdir=None):
# Windows does not have an equivalent for the system temp files, so
Expand All @@ -135,38 +135,38 @@ def expand_user(self, user_home_path, username=''):
if user_home_path == '~':
script = 'Write-Output (Get-Location).Path'
elif user_home_path.startswith('~\\'):
script = 'Write-Output ((Get-Location).Path + "%s")' % self._escape(user_home_path[1:])
script = "Write-Output ((Get-Location).Path + '%s')" % self._escape(user_home_path[1:])
else:
script = 'Write-Output "%s"' % self._escape(user_home_path)
script = "Write-Output '%s'" % self._escape(user_home_path)
return self._encode_script(script)

def exists(self, path):
path = self._escape(self._unquote(path))
script = '''
If (Test-Path "%s")
If (Test-Path '%s')
{
$res = 0;
}
Else
{
$res = 1;
}
Write-Output "$res";
Write-Output '$res';
Exit $res;
''' % path
return self._encode_script(script)

def checksum(self, path, *args, **kwargs):
path = self._escape(self._unquote(path))
script = '''
If (Test-Path -PathType Leaf "%(path)s")
If (Test-Path -PathType Leaf '%(path)s')
{
$sp = new-object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider;
$fp = [System.IO.File]::Open("%(path)s", [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read);
$fp = [System.IO.File]::Open('%(path)s', [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read);
[System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower();
$fp.Dispose();
}
ElseIf (Test-Path -PathType Container "%(path)s")
ElseIf (Test-Path -PathType Container '%(path)s')
{
Write-Output "3";
}
Expand Down Expand Up @@ -252,22 +252,11 @@ def _unquote(self, value):
return m.group(1)
return value

def _escape(self, value, include_vars=False):
'''Return value escaped for use in PowerShell command.'''
# http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences
# http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python
subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'),
('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'),
('\'', '`\''), ('`', '``'), ('\x00', '`0')]
if include_vars:
subs.append(('$', '`$'))
pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs)
substs = [s for p, s in subs]

def replace(m):
return substs[m.lastindex - 1]

return re.sub(pattern, replace, value)
def _escape(self, value):
'''Return value escaped for use in PowerShell single quotes.'''
# There are 5 chars that need to be escaped in a single quote.
# https://github.com/PowerShell/PowerShell/blob/b7cb335f03fe2992d0cbd61699de9d9aafa1d7c1/src/System.Management.Automation/engine/parser/CharTraits.cs#L265-L272
return re.compile(u"(['\u2018\u2019\u201a\u201b])").sub(u'\\1\\1', value)

def _encode_script(self, script, as_list=False, strict_mode=True, preserve_rc=True):
'''Convert a PowerShell script to a single base64-encoded command.'''
Expand Down
2 changes: 2 additions & 0 deletions test/integration/targets/win_fetch/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dependencies:
- setup_remote_tmp_dir
23 changes: 23 additions & 0 deletions test/integration/targets/win_fetch/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,26 @@
# Doesn't fail anymore, only returns a message.
- "fetch_dir is not changed"
- "fetch_dir.msg"

- name: create file with special characters
raw: Set-Content -LiteralPath '{{ remote_tmp_dir }}\abc$not var''quote‘‘' -Value 'abc'

- name: fetch file with special characters
fetch:
src: '{{ remote_tmp_dir }}\abc$not var''quote‘'
dest: '{{ host_output_dir }}/'
flat: yes
register: fetch_special_file

- name: get content of fetched file
command: cat {{ (host_output_dir ~ "/abc$not var'quote‘") | quote }}
register: fetch_special_file_actual
delegate_to: localhost

- name: assert fetch file with special characters
assert:
that:
- fetch_special_file is changed
- fetch_special_file.checksum == '34d4150adc3347f1dd8ce19fdf65b74d971ab602'
- fetch_special_file.dest == host_output_dir + "/abc$not var'quote‘"
- fetch_special_file_actual.stdout == 'abc'
1 change: 1 addition & 0 deletions test/sanity/ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ test/integration/targets/want_json_modules_posix/library/helloworld.py future-im
test/integration/targets/want_json_modules_posix/library/helloworld.py metaclass-boilerplate
test/integration/targets/win_exec_wrapper/library/test_fail.ps1 pslint:PSCustomUseLiteralPath
test/integration/targets/win_exec_wrapper/tasks/main.yml no-smart-quotes # We are explicitly testing smart quote support for env vars
test/integration/targets/win_fetch/tasks/main.yml no-smart-quotes # We are explictly testing smart quotes in the file name to fetch
test/integration/targets/win_module_utils/library/legacy_only_new_way_win_line_ending.ps1 line-endings # Explicitly tests that we still work with Windows line endings
test/integration/targets/win_module_utils/library/legacy_only_old_way_win_line_ending.ps1 line-endings # Explicitly tests that we still work with Windows line endings
test/integration/targets/win_script/files/test_script.ps1 pslint:PSAvoidUsingWriteHost # Keep
Expand Down

0 comments on commit 56d54ab

Please sign in to comment.