Passed
Pull Request — master (#227)
by Juan José
01:53 queued 34s
created

ospd.xml   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 131
dl 0
loc 288
rs 8.96
c 0
b 0
f 0
wmc 43

6 Functions

Rating   Name   Duplication   Size   Complexity  
A escape_ctrl_chars() 0 12 3
A split_invalid_xml() 0 21 4
A elements_as_text() 0 20 4
A get_result_xml() 0 26 3
B get_elements_from_dict() 0 26 6
B simple_response_str() 0 32 7

4 Methods

Rating   Name   Duplication   Size   Complexity  
A XmlStringHelper.create_response() 0 18 3
B XmlStringHelper.add_element() 0 35 7
A XmlStringHelper.create_element() 0 16 2
A XmlStringHelper.add_attr() 0 23 4

How to fix   Complexity   

Complexity

Complex classes like ospd.xml often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
import re
23
24
from typing import List, Dict, Any, Union
25
26
from xml.sax.saxutils import escape, quoteattr
27
from xml.etree.ElementTree import tostring, Element
28
29
from ospd.misc import ResultType
30
31
32
r = re.compile(
33
    r'(.*?)(?:([^\x09\x0A\x0D\x20-\x7E\x85\xA0-\xFF'
34
    + r'\u0100-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD])|([\n])|$)'
35
)
36
37
38
def split_invalid_xml(result_text: str) -> Union[List[Union[str, int]], str]:
39
    """ Search for occurence of non printable chars and replace them
40
    with the integer representation the Unicode code. The original string
41
    is splitted where a non printable char is found.
42
    """
43
    splitted_string = []
44
45
    def replacer(match):
46
        regex_g1 = match.group(1)
47
        if len(regex_g1) > 0:
48
            splitted_string.append(regex_g1)
49
        regex_g2 = match.group(2)
50
        if regex_g2 is not None:
51
            splitted_string.append(ord(regex_g2))
52
        regex_g3 = match.group(3)
53
        if regex_g3 is not None:
54
            splitted_string.append(regex_g3)
55
        return ""
56
57
    re.sub(r, replacer, result_text)
58
    return splitted_string
59
60
61
def escape_ctrl_chars(result_text):
62
    """ Replace non printable chars in result_text with an hexa code
63
    in string format.
64
    """
65
    escaped_str = ''
66
    for fragment in split_invalid_xml(result_text):
67
        if isinstance(fragment, int):
68
            escaped_str += '\\x%04X' % fragment
69
        else:
70
            escaped_str += fragment
71
72
    return escaped_str
73
74
75
def get_result_xml(result):
76
    """ Formats a scan result to XML format.
77
78
    Arguments:
79
        result (dict): Dictionary with a scan result.
80
81
    Return:
82
        Result as xml element object.
83
    """
84
85
    result_xml = Element('result')
86
    for name, value in [
87
        ('name', result['name']),
88
        ('type', ResultType.get_str(result['type'])),
89
        ('severity', result['severity']),
90
        ('host', result['host']),
91
        ('hostname', result['hostname']),
92
        ('test_id', result['test_id']),
93
        ('port', result['port']),
94
        ('qod', result['qod']),
95
    ]:
96
        result_xml.set(name, escape(str(value)))
97
    if result['value'] is not None:
98
        result_xml.text = escape_ctrl_chars(result['value'])
99
100
    return result_xml
101
102
103
def simple_response_str(
104
    command: str,
105
    status: int,
106
    status_text: str,
107
    content: Union[str, Element, List[str], List[Element]] = "",
108
) -> bytes:
109
    """ Creates an OSP response XML string.
110
111
    Arguments:
112
        command (str): OSP Command to respond to.
113
        status (int): Status of the response.
114
        status_text (str): Status text of the response.
115
        content (str): Text part of the response XML element.
116
117
    Return:
118
        String of response in xml format.
119
    """
120
    response = Element('%s_response' % command)
121
122
    for name, value in [('status', str(status)), ('status_text', status_text)]:
123
        response.set(name, escape(str(value)))
124
125
    if isinstance(content, list):
126
        for elem in content:
127
            if isinstance(elem, Element):
128
                response.append(elem)
129
    elif isinstance(content, Element):
130
        response.append(content)
131
    elif content is not None:
132
        response.text = escape_ctrl_chars(content)
133
134
    return tostring(response, encoding='utf-8')
135
136
137
def get_elements_from_dict(data: Dict[str, Any]) -> List[Element]:
138
    """ Creates a list of etree elements from a dictionary
139
140
    Args:
141
        Dictionary of tags and their elements.
142
143
    Return:
144
        List of xml elements.
145
    """
146
147
    responses = []
148
149
    for tag, value in data.items():
150
        elem = Element(tag)
151
152
        if isinstance(value, dict):
153
            for val in get_elements_from_dict(value):
154
                elem.append(val)
155
        elif isinstance(value, list):
156
            elem.text = ', '.join(value)
157
        elif value is not None:
158
            elem.text = escape_ctrl_chars(value)
159
160
        responses.append(elem)
161
162
    return responses
163
164
165
def elements_as_text(
166
    elements: Dict[str, Union[str, Dict]], indent: int = 2
167
) -> str:
168
    """ Returns the elements dictionary as formatted plain text. """
169
170
    text = ""
171
    for elename, eledesc in elements.items():
172
        if isinstance(eledesc, dict):
173
            desc_txt = elements_as_text(eledesc, indent + 2)
174
            desc_txt = ''.join(['\n', desc_txt])
175
        elif isinstance(eledesc, str):
176
            desc_txt = ''.join([eledesc, '\n'])
177
        else:
178
            assert False, "Only string or dictionary"
179
180
        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...
181
182
        text = ''.join([text, ele_txt])
183
184
    return text
185
186
187
class XmlStringHelper:
188
    """ Class with methods to help the creation of a xml object in
189
    string format.
190
    """
191
192
    def create_element(self, elem_name: str, end: bool = False) -> bytes:
193
        """ Get a name and create the open element of an entity.
194
195
        Arguments:
196
            elem_name (str): The name of the tag element.
197
            end (bool): Create a initial tag if False, otherwise the end tag.
198
199
        Return:
200
            Encoded string representing a part of an xml element.
201
        """
202
        if end:
203
            ret = "</%s>" % elem_name
204
        else:
205
            ret = "<%s>" % elem_name
206
207
        return ret.encode('utf-8')
208
209
    def create_response(self, command: str, end: bool = False) -> bytes:
210
        """ Create or end an xml response.
211
212
        Arguments:
213
            command (str): The name of the command for the response element.
214
            end (bool): Create a initial tag if False, otherwise the end tag.
215
216
        Return:
217
            Encoded string representing a part of an xml element.
218
        """
219
        if not command:
220
            return
221
222
        if end:
223
            return ('</%s_response>' % command).encode('utf-8')
224
225
        return ('<%s_response status="200" status_text="OK">' % command).encode(
226
            'utf-8'
227
        )
228
229
    def add_element(
230
        self,
231
        content: Union[Element, str, list],
232
        xml_str: bytes = None,
233
        end: bool = False,
234
    ) -> bytes:
235
        """Create the initial or ending tag for a subelement, or add
236
        one or many xml elements
237
238
        Arguments:
239
            content (Element, str, list): Content to add.
240
            xml_str (bytes): Initial string where content to be added to.
241
            end (bool): Create a initial tag if False, otherwise the end tag.
242
                        It will be added to the xml_str.
243
244
        Return:
245
            Encoded string representing a part of an xml element.
246
        """
247
248
        if not xml_str:
249
            xml_str = b''
250
251
        if content:
252
            if isinstance(content, list):
253
                for elem in content:
254
                    xml_str = xml_str + tostring(elem, encoding='utf-8')
255
            elif isinstance(content, Element):
256
                xml_str = xml_str + tostring(content, encoding='utf-8')
257
            else:
258
                if end:
259
                    xml_str = xml_str + self.create_element(content, False)
260
                else:
261
                    xml_str = xml_str + self.create_element(content)
262
263
        return xml_str
264
265
    def add_attr(
266
        self, tag: bytes, attribute: str, value: Union[str, int] = None
267
    ) -> bytes:
268
        """ Add an attribute to the beginnig tag of an xml element.
269
        Arguments:
270
            tag (bytes): Tag to add the attrubute to.
271
            attribute (str): Attribute name
272
            value (str): Attribute value
273
        Return:
274
            Tag in encoded string format with the given attribute
275
        """
276
        if not tag:
277
            return None
278
279
        if not attribute:
280
            return tag
281
282
        if not value:
283
            value = ''
284
285
        return tag[:-1] + (
286
            " %s=%s>" % (attribute, quoteattr(str(value)))
287
        ).encode('utf-8')
288