File: xml.py

package info (click to toggle)
ospd-openvas 22.10.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,664 kB
  • sloc: python: 14,268; xml: 1,913; makefile: 45; sh: 29
file content (300 lines) | stat: -rw-r--r-- 8,642 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: 2014-2023 Greenbone AG
#
# SPDX-License-Identifier: AGPL-3.0-or-later

"""OSP XML utils class."""

import re

from typing import List, Dict, Any, Union

from xml.sax.saxutils import escape, quoteattr
from xml.etree.ElementTree import Element, tostring

from ospd.misc import ResultType


r = re.compile(  # pylint: disable=invalid-name
    r'(.*?)(?:([^\x09\x0A\x0D\x20-\x7E\x85\xA0-\xFF'
    + r'\u0100-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD])|([\n])|$)'
)


def split_invalid_xml(result_text: str) -> Union[List[Union[str, int]], str]:
    """Search for occurrence of non printable chars and replace them
    with the integer representation the Unicode code. The original string
    is splitted where a non printable char is found.
    """
    splitted_string = []

    def replacer(match):
        regex_g1 = match.group(1)
        if len(regex_g1) > 0:
            splitted_string.append(regex_g1)
        regex_g2 = match.group(2)
        if regex_g2 is not None:
            splitted_string.append(ord(regex_g2))
        regex_g3 = match.group(3)
        if regex_g3 is not None:
            splitted_string.append(regex_g3)
        return ""

    re.sub(r, replacer, result_text)
    return splitted_string


def escape_ctrl_chars(result_text):
    """Replace non printable chars in result_text with an hexa code
    in string format.
    """
    escaped_str = ''
    for fragment in split_invalid_xml(result_text):
        if isinstance(fragment, int):
            escaped_str += f'\\x{fragment:00004X}'
        else:
            escaped_str += fragment

    return escaped_str


def get_result_xml(result):
    """Formats a scan result to XML format.

    Arguments:
        result (dict): Dictionary with a scan result.

    Return:
        Result as xml element object.
    """

    result_xml = Element('result')
    for name, value in [
        ('name', result['name']),
        ('type', ResultType.get_str(result['type'])),
        ('severity', result['severity']),
        ('host', result['host']),
        ('hostname', result['hostname']),
        ('test_id', result['test_id']),
        ('port', result['port']),
        ('qod', result['qod']),
        ('uri', result['uri']),
    ]:
        result_xml.set(name, escape(str(value)))
    if result['value'] is not None:
        result_xml.text = escape_ctrl_chars(result['value'])

    return result_xml


def get_progress_xml(progress: Dict[str, int]):
    """Formats a scan progress to XML format.

    Arguments:
        progress (dict): Dictionary with a scan progress.

    Return:
        Progress as xml element object.
    """

    progress_xml = Element('progress')
    for progress_item, value in progress.items():
        elem = None
        if progress_item == 'current_hosts':
            for host, h_progress in value.items():
                elem = Element('host')
                elem.set('name', host)
                elem.text = str(h_progress)
                progress_xml.append(elem)
        else:
            elem = Element(progress_item)
            elem.text = str(value)
            progress_xml.append(elem)
    return progress_xml


def simple_response_str(
    command: str,
    status: int,
    status_text: str,
    content: Union[str, Element, List[str], List[Element]] = "",
) -> bytes:
    """Creates an OSP response XML string.

    Arguments:
        command (str): OSP Command to respond to.
        status (int): Status of the response.
        status_text (str): Status text of the response.
        content (str): Text part of the response XML element.

    Return:
        String of response in xml format.
    """
    response = Element(f'{command}_response')

    for name, value in [('status', str(status)), ('status_text', status_text)]:
        response.set(name, escape(str(value)))

    if isinstance(content, list):
        for elem in content:
            if isinstance(elem, Element):
                response.append(elem)
    elif isinstance(content, Element):
        response.append(content)
    elif content is not None:
        response.text = escape_ctrl_chars(content)

    return tostring(response, encoding='utf-8')


def get_elements_from_dict(data: Dict[str, Any]) -> List[Element]:
    """Creates a list of etree elements from a dictionary

    Args:
        Dictionary of tags and their elements.

    Return:
        List of xml elements.
    """

    responses = []

    for tag, value in data.items():
        elem = Element(tag)

        if isinstance(value, dict):
            for val in get_elements_from_dict(value):
                elem.append(val)
        elif isinstance(value, list):
            elem.text = ', '.join(value)
        elif value is not None:
            elem.text = escape_ctrl_chars(value)

        responses.append(elem)

    return responses


def elements_as_text(
    elements: Dict[str, Union[str, Dict]], indent: int = 2
) -> str:
    """Returns the elements dictionary as formatted plain text."""

    text = ""
    for elename, eledesc in elements.items():
        if isinstance(eledesc, dict):
            desc_txt = elements_as_text(eledesc, indent + 2)
            desc_txt = ''.join(['\n', desc_txt])
        elif isinstance(eledesc, str):
            desc_txt = ''.join([eledesc, '\n'])
        else:
            assert False, "Only string or dictionary"

        ele_txt = f"\t{' ' * indent}{elename: <22} {desc_txt}"

        text = ''.join([text, ele_txt])

    return text


class XmlStringHelper:
    """Class with methods to help the creation of a xml object in
    string format.
    """

    def create_element(self, elem_name: str, end: bool = False) -> bytes:
        """Get a name and create the open element of an entity.

        Arguments:
            elem_name (str): The name of the tag element.
            end (bool): Create a initial tag if False, otherwise the end tag.

        Return:
            Encoded string representing a part of an xml element.
        """
        if end:
            ret = f"</{elem_name}>"
        else:
            ret = f"<{elem_name}>"

        return ret.encode('utf-8')

    def create_response(self, command: str, end: bool = False) -> bytes:
        """Create or end an xml response.

        Arguments:
            command (str): The name of the command for the response element.
            end (bool): Create a initial tag if False, otherwise the end tag.

        Return:
            Encoded string representing a part of an xml element.
        """
        if not command:
            return

        if end:
            return (f'</{command}_response>').encode('utf-8')

        return (f'<{command}_response status="200" status_text="OK">').encode(
            'utf-8'
        )

    def add_element(
        self,
        content: Union[Element, str, list],
        xml_str: bytes = None,
        end: bool = False,
    ) -> bytes:
        """Create the initial or ending tag for a subelement, or add
        one or many xml elements

        Arguments:
            content (Element, str, list): Content to add.
            xml_str (bytes): Initial string where content to be added to.
            end (bool): Create a initial tag if False, otherwise the end tag.
                        It will be added to the xml_str.

        Return:
            Encoded string representing a part of an xml element.
        """

        if not xml_str:
            xml_str = b''

        if content:
            if isinstance(content, list):
                for elem in content:
                    xml_str = xml_str + tostring(elem, encoding='utf-8')
            elif isinstance(content, Element):
                xml_str = xml_str + tostring(content, encoding='utf-8')
            else:
                if end:
                    xml_str = xml_str + self.create_element(content, False)
                else:
                    xml_str = xml_str + self.create_element(content)

        return xml_str

    def add_attr(
        self, tag: bytes, attribute: str, value: Union[str, int] = None
    ) -> bytes:
        """Add an attribute to the beginning tag of an xml element.
        Arguments:
            tag (bytes): Tag to add the attribute to.
            attribute (str): Attribute name
            value (str): Attribute value
        Return:
            Tag in encoded string format with the given attribute
        """
        if not tag:
            return None

        if not attribute:
            return tag

        if not value:
            value = ''

        return tag[:-1] + (f" {attribute}={quoteattr(str(value))}>").encode(
            'utf-8'
        )