powershell - fix nested CLIXML parser (#71412)

pull/71467/head
Jordan Borean 4 years ago committed by GitHub
parent 3c8744f0c1
commit 8897d7e2ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- powershell - fix the CLIXML parser when it contains nested CLIXML objects - https://github.com/ansible/ansible/issues/69550

@ -43,12 +43,24 @@ def _parse_clixml(data, stream="Error"):
message encoded in the XML data. CLIXML is used by PowerShell to encode message encoded in the XML data. CLIXML is used by PowerShell to encode
multiple objects in stderr. multiple objects in stderr.
""" """
clixml = ET.fromstring(data.split(b"\r\n", 1)[-1]) lines = []
namespace_match = re.match(r'{(.*)}', clixml.tag)
namespace = "{%s}" % namespace_match.group(1) if namespace_match else "" # There are some scenarios where the stderr contains a nested CLIXML element like
# '<# CLIXML\r\n<# CLIXML\r\n<Objs>...</Objs><Objs>...</Objs>'.
# Parse each individual <Objs> element and add the error strings to our stderr list.
# https://github.com/ansible/ansible/issues/69550
while data:
end_idx = data.find(b"</Objs>") + 7
current_element = data[data.find(b"<Objs "):end_idx]
data = data[end_idx:]
clixml = ET.fromstring(current_element)
namespace_match = re.match(r'{(.*)}', clixml.tag)
namespace = "{%s}" % namespace_match.group(1) if namespace_match else ""
strings = clixml.findall("./%sS" % namespace)
lines.extend([e.text.replace('_x000D__x000A_', '') for e in strings if e.attrib.get('S') == stream])
strings = clixml.findall("./%sS" % namespace)
lines = [e.text.replace('_x000D__x000A_', '') for e in strings if e.attrib.get('S') == stream]
return to_bytes('\r\n'.join(lines)) return to_bytes('\r\n'.join(lines))

@ -56,6 +56,25 @@ def test_parse_clixml_multiple_streams():
assert actual == expected assert actual == expected
def test_parse_clixml_multiple_elements():
multiple_elements = b'#< CLIXML\r\n#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">' \
b'<Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS>' \
b'<I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj>' \
b'<S S="Error">Error 1</S></Objs>' \
b'<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0">' \
b'<TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS>' \
b'<I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj>' \
b'<Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">2</I64>' \
b'<PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj>' \
b'<S S="Error">Error 2</S></Objs>'
expected = b"Error 1\r\nError 2"
actual = _parse_clixml(multiple_elements)
assert actual == expected
def test_join_path_unc(): def test_join_path_unc():
pwsh = ShellModule() pwsh = ShellModule()
unc_path_parts = ['\\\\host\\share\\dir1\\\\dir2\\', '\\dir3/dir4', 'dir5', 'dir6\\'] unc_path_parts = ['\\\\host\\share\\dir1\\\\dir2\\', '\\dir3/dir4', 'dir5', 'dir6\\']

Loading…
Cancel
Save