diff --git a/dissect/hypervisor/descriptor/ovf.py b/dissect/hypervisor/descriptor/ovf.py index 32d3e41..0040918 100644 --- a/dissect/hypervisor/descriptor/ovf.py +++ b/dissect/hypervisor/descriptor/ovf.py @@ -1,4 +1,7 @@ -from xml.etree import ElementTree +from typing import Iterator, TextIO +from xml.etree.ElementTree import Element + +from defusedxml import ElementTree class OVF: @@ -11,9 +14,9 @@ class OVF: DISK_XPATH = "ovf:DiskSection/ovf:Disk" DISK_DRIVE_XPATH = 'ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item/[rasd:ResourceType="17"]' - def __init__(self, fh): + def __init__(self, fh: TextIO): self.fh = fh - self.xml = ElementTree.fromstring(fh.read()) + self.xml: Element = ElementTree.fromstring(fh.read()) self.references = {} for file_ in self.xml.findall(self.FILE_XPATH, self.NS): @@ -27,12 +30,18 @@ def __init__(self, fh): file_ref = disk.get("{{{ovf}}}fileRef".format(**self.NS)) self._disks[disk_id] = self.references[file_ref] - def disks(self): + def disks(self) -> Iterator[str]: for disk in self.xml.findall(self.DISK_DRIVE_XPATH, self.NS): resource = disk.find("{{{rasd}}}HostResource".format(**self.NS)) - _, _, xpath = resource.text.partition(":") + xpath = resource.text + if xpath.startswith("ovf:"): + xpath = xpath[4:] + if xpath.startswith("/disk/"): disk_ref = xpath.split("/")[-1] yield self._disks[disk_ref] + elif xpath.startswith("/file/"): + file_ref = xpath.split("/")[-1] + yield self.references[file_ref] else: raise NotImplementedError(resource) diff --git a/pyproject.toml b/pyproject.toml index 03b6209..035ed61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ classifiers = [ "Topic :: Utilities", ] dependencies = [ + "defusedxml", "dissect.cstruct>=3.0.dev,<4.0.dev", "dissect.util>=3.0.dev,<4.0.dev", ] diff --git a/tests/test_ovf.py b/tests/test_ovf.py new file mode 100644 index 0000000..bd3b13a --- /dev/null +++ b/tests/test_ovf.py @@ -0,0 +1,168 @@ +from io import StringIO + +from dissect.hypervisor.descriptor.ovf import OVF + +# Handcrafted to include some test cases +TEST_OVF = """ + + + + + + + + List of the virtual disks used in the package + + + + + Logical networks used in the package + + Logical network used by this appliance. + + + + A virtual machine + + The kind of installed guest operating system + Linux26_64 + Linux26_64 + + + Virtual hardware requirements for a virtual machine + + Virtual Hardware Family + 0 + Test OVF + virtualbox-2.2 + + + 1 virtual CPU + Number of virtual CPUs + 1 virtual CPU + 1 + 3 + 1 + + + MegaBytes + 1536 MB of memory + Memory Size + 1536 MB of memory + 2 + 4 + 1536 + + + 0 + sataController0 + SATA Controller + sataController0 + 3 + AHCI + 20 + + + 0 + usb + USB Controller + usb + 4 + 23 + + + 3 + false + sound + Sound Card + sound + 5 + ensoniq1371 + 35 + + + 0 + disk1 + Disk Image + disk1 + /disk/vmdisk1 + 6 + 3 + 17 + + + 1 + disk2 + Disk Image + disk2 + ovf:/disk/vmdisk2 + 8 + 3 + 17 + + + 2 + disk3 + Disk Image + disk3 + ovf:/file/file3 + 9 + 3 + 17 + + + true + Ethernet adapter on 'NAT' + NAT + Ethernet adapter on 'NAT' + 7 + 10 + + + + Complete VirtualBox machine configuration in VirtualBox format + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" # noqa + + +def test_ovf(): + ovf = OVF(StringIO(TEST_OVF)) + assert list(ovf.disks()) == ["disk1.vmdk", "disk2.vmdk", "disk3.vmdk"]