Completed
Push — master ( d58222...cd4f3f )
by Juan José
16s queued 12s
created

ospd.xml.XmlStringHelper.add_attr()   A

Complexity

Conditions 4

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nop 4
dl 0
loc 19
rs 10
c 0
b 0
f 0
1
# Copyright (C) 2014-2020 Greenbone Networks GmbH
2
#
3
# SPDX-License-Identifier: GPL-2.0-or-later
4
#
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
19
""" OSP XML utils class.
20
"""
21
22
from typing import List, Dict, Any, Union
23
24
from xml.etree.ElementTree import tostring, Element
25
26
from ospd.misc import ResultType
27
28
29
def get_result_xml(result):
30
    """ Formats a scan result to XML format.
31
32
    Arguments:
33
        result (dict): Dictionary with a scan result.
34
35
    Return:
36
        Result as xml element object.
37
    """
38
    result_xml = Element('result')
39
    for name, value in [
40
        ('name', result['name']),
41
        ('type', ResultType.get_str(result['type'])),
42
        ('severity', result['severity']),
43
        ('host', result['host']),
44
        ('hostname', result['hostname']),
45
        ('test_id', result['test_id']),
46
        ('port', result['port']),
47
        ('qod', result['qod']),
48
    ]:
49
        result_xml.set(name, str(value))
50
    result_xml.text = result['value']
51
    return result_xml
52
53
54
def simple_response_str(
55
    command: str,
56
    status: int,
57
    status_text: str,
58
    content: Union[str, Element, List[str], List[Element]] = "",
59
) -> bytes:
60
    """ Creates an OSP response XML string.
61
62
    Arguments:
63
        command (str): OSP Command to respond to.
64
        status (int): Status of the response.
65
        status_text (str): Status text of the response.
66
        content (str): Text part of the response XML element.
67
68
    Return:
69
        String of response in xml format.
70
    """
71
    response = Element('%s_response' % command)
72
73
    for name, value in [('status', str(status)), ('status_text', status_text)]:
74
        response.set(name, str(value))
75
76
    if isinstance(content, list):
77
        for elem in content:
78
            if isinstance(elem, Element):
79
                response.append(elem)
80
    elif isinstance(content, Element):
81
        response.append(content)
82
    else:
83
        response.text = content
84
85
    return tostring(response)
86
87
88
def get_elements_from_dict(data: Dict[str, Any]) -> List[Element]:
89
    """ Creates a list of etree elements from a dictionary
90
91
    Args:
92
        Dictionary of tags and their elements.
93
94
    Return:
95
        List of xml elements.
96
    """
97
98
    responses = []
99
100
    for tag, value in data.items():
101
        elem = Element(tag)
102
103
        if isinstance(value, dict):
104
            for val in get_elements_from_dict(value):
105
                elem.append(val)
106
        elif isinstance(value, list):
107
            elem.text = ', '.join(value)
108
        else:
109
            elem.text = value
110
111
        responses.append(elem)
112
113
    return responses
114
115
116
def elements_as_text(
117
    elements: Dict[str, Union[str, Dict]], indent: int = 2
118
) -> str:
119
    """ Returns the elements dictionary as formatted plain text. """
120
121
    text = ""
122
    for elename, eledesc in elements.items():
123
        if isinstance(eledesc, dict):
124
            desc_txt = elements_as_text(eledesc, indent + 2)
125
            desc_txt = ''.join(['\n', desc_txt])
126
        elif isinstance(eledesc, str):
127
            desc_txt = ''.join([eledesc, '\n'])
128
        else:
129
            assert False, "Only string or dictionary"
130
131
        ele_txt = "\t{0}{1: <22} {2}".format(' ' * indent, elename, desc_txt)
0 ignored issues
show
introduced by
The variable desc_txt does not seem to be defined for all execution paths.
Loading history...
132
133
        text = ''.join([text, ele_txt])
134
135
    return text
136
137
138
class XmlStringHelper:
139
    """ Class with methods to help the creation of a xml object in
140
    string format.
141
    """
142
143
    def create_element(self, elem_name: str, end: bool = False) -> bytes:
144
        """ Get a name and create the open element of an entity.
145
146
        Arguments:
147
            elem_name (str): The name of the tag element.
148
            end (bool): Create a initial tag if False, otherwise the end tag.
149
150
        Return:
151
            Encoded string representing a part of an xml element.
152
        """
153
        if end:
154
            ret = "</%s>" % elem_name
155
        else:
156
            ret = "<%s>" % elem_name
157
158
        return ret.encode()
159
160
    def create_response(self, command: str, end: bool = False) -> bytes:
161
        """ Create or end an xml response.
162
163
        Arguments:
164
            command (str): The name of the command for the response element.
165
            end (bool): Create a initial tag if False, otherwise the end tag.
166
167
        Return:
168
            Encoded string representing a part of an xml element.
169
        """
170
        if not command:
171
            return
172
173
        if end:
174
            return ('</%s_response>' % command).encode()
175
176
        return (
177
            '<%s_response status="200" status_text="OK">' % command
178
        ).encode()
179
180
    def add_element(
181
        self,
182
        content: Union[Element, str, list],
183
        xml_str: bytes = None,
184
        end: bool = False,
185
    ) -> bytes:
186
        """Create the initial or ending tag for a subelement, or add
187
        one or many xml elements
188
189
        Arguments:
190
            content (Element, str, list): Content to add.
191
            xml_str (bytes): Initial string where content to be added to.
192
            end (bool): Create a initial tag if False, otherwise the end tag.
193
                        It will be added to the xml_str.
194
195
        Return:
196
            Encoded string representing a part of an xml element.
197
        """
198
199
        if not xml_str:
200
            xml_str = b''
201
202
        if content:
203
            if isinstance(content, list):
204
                for elem in content:
205
                    xml_str = xml_str + tostring(elem)
206
            elif isinstance(content, Element):
207
                xml_str = xml_str + tostring(content)
208
            else:
209
                if end:
210
                    xml_str = xml_str + self.create_element(content, False)
211
                else:
212
                    xml_str = xml_str + self.create_element(content)
213
214
        return xml_str
215
216
    def add_attr(self, tag: bytes, attribute: str, value: str = None) -> bytes:
217
        """ Add an attribute to the beginnig tag of an xml element.
218
        Arguments:
219
            tag (bytes): Tag to add the attrubute to.
220
            attribute (str): Attribute name
221
            value (str): Attribute value
222
        Return:
223
            Tag in encoded string format with the given attribute
224
        """
225
        if not tag:
226
            return None
227
228
        if not attribute:
229
            return tag
230
231
        if not value:
232
            value = ''
233
234
        return tag[:-1] + (" %s=\'%s\'>" % (attribute, value)).encode()
235