1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5This file is part of cpe package.
6
7This module is used to the treatment of identifiers
8of IT platforms (hardware, operating systems or applications of system)
9in accordance with binding style URI of version 2.3 of CPE
10(Common Platform Enumeration) specification.
11
12Copyright (C) 2013  Alejandro Galindo García, Roberto Abdelkader Martínez Pérez
13
14This program is free software: you can redistribute it and/or modify
15it under the terms of the GNU Lesser General Public License as published by
16the Free Software Foundation, either version 3 of the License, or
17(at your option) any later version.
18
19This program is distributed in the hope that it will be useful,
20but WITHOUT ANY WARRANTY; without even the implied warranty of
21MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22GNU Lesser General Public License for more details.
23
24You should have received a copy of the GNU Lesser General Public License
25along with this program. If not, see <http://www.gnu.org/licenses/>.
26
27For any problems using the cpe package, or general questions and
28feedback about it, please contact:
29
30- Alejandro Galindo García: galindo.garcia.alejandro@gmail.com
31- Roberto Abdelkader Martínez Pérez: robertomartinezp@gmail.com
32"""
33
34from .cpe import CPE
35from .cpe2_3 import CPE2_3
36from .cpe2_3_wfn import CPE2_3_WFN
37from .comp.cpecomp import CPEComponent
38from .comp.cpecomp_logical import CPEComponentLogical
39from .comp.cpecomp2_3_uri import CPEComponent2_3_URI
40from .comp.cpecomp2_3_wfn import CPEComponent2_3_WFN
41from .comp.cpecomp2_3_uri_edpacked import CPEComponent2_3_URI_edpacked
42from .comp.cpecomp_anyvalue import CPEComponentAnyValue
43from .comp.cpecomp_empty import CPEComponentEmpty
44from .comp.cpecomp_undefined import CPEComponentUndefined
45from .comp.cpecomp_notapplicable import CPEComponentNotApplicable
46
47import re
48
49
50class CPE2_3_URI(CPE2_3):
51    """
52    Implementation of binding style URI of version 2.3 of CPE specification.
53
54    A CPE Name is a percent-encoded URI with each name
55    starting with the prefix (the URI scheme name) 'cpe:'.
56
57    Each platform can be broken down into many distinct parts.
58    A CPE Name specifies a simple part and is used to identify
59    any platform that matches the description of that part.
60
61    The distinct parts are:
62
63    - Hardware part: the physical platform supporting the IT system.
64    - Operating system part: the operating system controls and manages
65      the IT hardware.
66    - Application part: software systems, services, servers, and packages
67      installed on the system.
68
69    CPE Name syntax:
70
71        cpe:/{part}:{vendor}:{product}:{version}:{update}:{edition}:{language}
72    """
73
74    ###############
75    #  CONSTANTS  #
76    ###############
77
78    #: Style of CPE Name
79    STYLE = CPE2_3.STYLE_URI
80
81    ###############
82    #  VARIABLES  #
83    ###############
84
85    # Compilation of regular expression associated with parts of CPE Name
86    _typesys = "?P<{0}>(h|o|a)".format(CPEComponent.ATT_PART)
87    _vendor = "?P<{0}>[^:]+".format(CPEComponent.ATT_VENDOR)
88    _product = "?P<{0}>[^:]+".format(CPEComponent.ATT_PRODUCT)
89    _version = "?P<{0}>[^:]+".format(CPEComponent.ATT_VERSION)
90    _update = "?P<{0}>[^:]+".format(CPEComponent.ATT_UPDATE)
91    _edition = "?P<{0}>[^:]+".format(CPEComponent.ATT_EDITION)
92    _language = "?P<{0}>[^:]+".format(CPEComponent.ATT_LANGUAGE)
93
94    _parts_pattern = "^cpe:/({0})?(:({1})?)?(:({2})?)?(:({3})?)?(:({4})?)?(:({5})?)?(:({6})?)?$".format(
95        _typesys, _vendor, _product, _version, _update, _edition, _language)
96
97    _parts_rxc = re.compile(_parts_pattern, re.IGNORECASE)
98
99    ###################
100    #  CLASS METHODS  #
101    ###################
102
103    @classmethod
104    def _create_component(cls, att, value):
105        """
106        Returns a component with value "value".
107
108        :param string att: Attribute name
109        :param string value: Attribute value
110        :returns: Component object created
111        :rtype: CPEComponent
112        :exception: ValueError - invalid value of attribute
113        """
114
115        if value == CPEComponent2_3_URI.VALUE_UNDEFINED:
116            comp = CPEComponentUndefined()
117        elif (value == CPEComponent2_3_URI.VALUE_ANY or
118              value == CPEComponent2_3_URI.VALUE_EMPTY):
119            comp = CPEComponentAnyValue()
120        elif (value == CPEComponent2_3_URI.VALUE_NA):
121            comp = CPEComponentNotApplicable()
122        else:
123            comp = CPEComponentNotApplicable()
124            try:
125                comp = CPEComponent2_3_URI(value, att)
126            except ValueError:
127                errmsg = "Invalid value of attribute '{0}': {1} ".format(att,
128                                                                         value)
129                raise ValueError(errmsg)
130
131        return comp
132
133    @classmethod
134    def _unpack_edition(cls, value):
135        """
136        Unpack its elements and set the attributes in wfn accordingly.
137        Parse out the five elements:
138
139        ~ edition ~ software edition ~ target sw ~ target hw ~ other
140
141        :param string value: Value of edition attribute
142        :returns: Dictionary with parts of edition attribute
143        :exception: ValueError - invalid value of edition attribute
144        """
145
146        components = value.split(CPEComponent2_3_URI.SEPARATOR_PACKED_EDITION)
147        d = dict()
148
149        ed = components[1]
150        sw_ed = components[2]
151        t_sw = components[3]
152        t_hw = components[4]
153        oth = components[5]
154
155        ck = CPEComponent.ATT_EDITION
156        d[ck] = CPE2_3_URI._create_component(ck, ed)
157        ck = CPEComponent.ATT_SW_EDITION
158        d[ck] = CPE2_3_URI._create_component(ck, sw_ed)
159        ck = CPEComponent.ATT_TARGET_SW
160        d[ck] = CPE2_3_URI._create_component(ck, t_sw)
161        ck = CPEComponent.ATT_TARGET_HW
162        d[ck] = CPE2_3_URI._create_component(ck, t_hw)
163        ck = CPEComponent.ATT_OTHER
164        d[ck] = CPE2_3_URI._create_component(ck, oth)
165
166        return d
167
168    ####################
169    #  OBJECT METHODS  #
170    ####################
171
172    def __getitem__(self, i):
173        """
174        Returns the i'th component name of CPE Name.
175
176        :param int i: component index to find
177        :returns: component string found
178        :rtype: CPEComponent
179        :exception: IndexError - index not found in CPE Name
180        """
181
182        count = 0
183        errmsg = "Component index of CPE Name out of range"
184
185        packed_ed = self._pack_edition()
186
187        for pk in CPE.CPE_PART_KEYS:
188            elements = self.get(pk)
189            for elem in elements:
190                for ck in CPEComponent.CPE_COMP_KEYS:
191                    if (count == i):
192                        if ck == CPEComponent.ATT_EDITION:
193                            empty_ed = elem.get(ck) == CPEComponentUndefined()
194                            k = CPEComponent.ATT_SW_EDITION
195                            empty_sw_ed = elem.get(k) == CPEComponentUndefined()
196                            k = CPEComponent.ATT_TARGET_SW
197                            empty_tg_sw = elem.get(k) == CPEComponentUndefined()
198                            k = CPEComponent.ATT_TARGET_HW
199                            empty_tg_hw = elem.get(k) == CPEComponentUndefined()
200                            k = CPEComponent.ATT_OTHER
201                            empty_oth = elem.get(k) == CPEComponentUndefined()
202
203                            if (empty_ed and empty_sw_ed and empty_tg_sw and
204                               empty_tg_hw and empty_oth):
205
206                                # Edition component undefined
207                                raise IndexError(errmsg)
208                            else:
209                                # Some part of edition component defined.
210                                # Pack the edition component
211                                return CPEComponent2_3_URI_edpacked(packed_ed)
212                        else:
213                            comp = elem.get(ck)
214
215                            if not isinstance(comp, CPEComponentUndefined):
216                                return comp
217                            else:
218                                raise IndexError(errmsg)
219                    else:
220                        count += 1
221
222        raise IndexError(errmsg)
223
224    def __len__(self):
225        """
226        Returns the number of components of CPE Name.
227
228        :returns: count of components of CPE Name
229        :rtype: int
230        """
231
232        prefix = "cpe:/"
233        data = self.cpe_str[len(prefix):]
234
235        if data == "":
236            return 0
237
238        count = data.count(CPEComponent2_3_URI.SEPARATOR_COMP)
239
240        return count + 1
241
242    def __new__(cls, cpe_str, *args, **kwargs):
243        """
244        Create a new CPE Name of version 2.3 with URI style.
245
246        :param string cpe_str: CPE Name string
247        :returns: CPE object of version 2.3 of CPE specification with
248            URI style.
249        :rtype: CPE2_3_URI
250        """
251
252        return dict.__new__(cls)
253
254    def _parse(self):
255        """
256        Checks if the CPE Name is valid.
257
258        :returns: None
259        :exception: ValueError - bad-formed CPE Name
260        """
261
262        # CPE Name must not have whitespaces
263        if (self._str.find(" ") != -1):
264            msg = "Bad-formed CPE Name: it must not have whitespaces"
265            raise ValueError(msg)
266
267        # Partitioning of CPE Name
268        parts_match = CPE2_3_URI._parts_rxc.match(self._str)
269
270        # Validation of CPE Name parts
271        if (parts_match is None):
272            msg = "Bad-formed CPE Name: validation of parts failed"
273            raise ValueError(msg)
274
275        components = dict()
276        edition_parts = dict()
277
278        for ck in CPEComponent.CPE_COMP_KEYS:
279            value = parts_match.group(ck)
280
281            try:
282                if (ck == CPEComponent.ATT_EDITION and value is not None):
283                    if value[0] == CPEComponent2_3_URI.SEPARATOR_PACKED_EDITION:
284                        # Unpack the edition part
285                        edition_parts = CPE2_3_URI._unpack_edition(value)
286                    else:
287                        comp = CPE2_3_URI._create_component(ck, value)
288                else:
289                    comp = CPE2_3_URI._create_component(ck, value)
290            except ValueError:
291                errmsg = "Bad-formed CPE Name: not correct value '{0}'".format(
292                    value)
293                raise ValueError(errmsg)
294            else:
295                components[ck] = comp
296
297        components = dict(components, **edition_parts)
298
299        # Adds the components of version 2.3 of CPE not defined in version 2.2
300        for ck2 in CPEComponent.CPE_COMP_KEYS_EXTENDED:
301            if ck2 not in components.keys():
302                components[ck2] = CPEComponentUndefined()
303
304        # Exchange the undefined values in middle attributes of CPE Name for
305        # logical value ANY
306        check_change = True
307
308        # Start in the last attribute specififed in CPE Name
309        for ck in CPEComponent.CPE_COMP_KEYS[::-1]:
310            if ck in components:
311                comp = components[ck]
312                if check_change:
313                    check_change = ((ck != CPEComponent.ATT_EDITION) and
314                                   (comp == CPEComponentUndefined()) or
315                                   (ck == CPEComponent.ATT_EDITION and
316                                   (len(edition_parts) == 0)))
317                elif comp == CPEComponentUndefined():
318                    comp = CPEComponentAnyValue()
319
320                components[ck] = comp
321
322        #  Storage of CPE Name
323        part_comp = components[CPEComponent.ATT_PART]
324        if isinstance(part_comp, CPEComponentLogical):
325            elements = []
326            elements.append(components)
327            self[CPE.KEY_UNDEFINED] = elements
328        else:
329            # Create internal structure of CPE Name in parts:
330            # one of them is filled with identified components,
331            # the rest are empty
332            system = parts_match.group(CPEComponent.ATT_PART)
333            if system in CPEComponent.SYSTEM_VALUES:
334                self._create_cpe_parts(system, components)
335            else:
336                self._create_cpe_parts(CPEComponent.VALUE_PART_UNDEFINED,
337                                       components)
338
339        # Fills the empty parts of internal structure of CPE Name
340        for pk in CPE.CPE_PART_KEYS:
341            if pk not in self.keys():
342                # Empty part
343                self[pk] = []
344
345    def as_wfn(self):
346        """
347        Returns the CPE Name as Well-Formed Name string of version 2.3.
348        If edition component is not packed, only shows the first seven
349        components, otherwise shows all.
350
351        :return: CPE Name as WFN string
352        :rtype: string
353        :exception: TypeError - incompatible version
354        """
355
356        if self._str.find(CPEComponent2_3_URI.SEPARATOR_PACKED_EDITION) == -1:
357            # Edition unpacked, only show the first seven components
358
359            wfn = []
360            wfn.append(CPE2_3_WFN.CPE_PREFIX)
361
362            for ck in CPEComponent.CPE_COMP_KEYS:
363                lc = self._get_attribute_components(ck)
364
365                if len(lc) > 1:
366                    # Incompatible version 1.1, there are two or more elements
367                    # in CPE Name
368                    errmsg = "Incompatible version {0} with WFN".format(
369                        self.VERSION)
370                    raise TypeError(errmsg)
371
372                else:
373                    comp = lc[0]
374
375                    v = []
376                    v.append(ck)
377                    v.append("=")
378
379                    if (isinstance(comp, CPEComponentUndefined) or
380                       isinstance(comp, CPEComponentEmpty)):
381
382                        # Do not set the attribute
383                        continue
384
385                    elif isinstance(comp, CPEComponentAnyValue):
386
387                        # Logical value any
388                        v.append(CPEComponent2_3_WFN.VALUE_ANY)
389
390                    elif isinstance(comp, CPEComponentNotApplicable):
391
392                        # Logical value not applicable
393                        v.append(CPEComponent2_3_WFN.VALUE_NA)
394
395                    else:
396                        # Get the value of WFN of component
397                        v.append('"')
398                        v.append(comp.as_wfn())
399                        v.append('"')
400
401                    # Append v to the WFN and add a separator
402                    wfn.append("".join(v))
403                    wfn.append(CPEComponent2_3_WFN.SEPARATOR_COMP)
404
405            # Del the last separator
406            wfn = wfn[:-1]
407
408            # Return the WFN string
409            wfn.append(CPE2_3_WFN.CPE_SUFFIX)
410
411            return "".join(wfn)
412
413        else:
414            # Shows all components
415            return super(CPE2_3_URI, self).as_wfn()
416
417    def get_attribute_values(self, att_name):
418        """
419        Returns the values of attribute "att_name" of CPE Name.
420        By default a only element in each part.
421
422        :param string att_name: Attribute name to get
423        :returns: List of attribute values
424        :rtype: list
425        :exception: ValueError - invalid attribute name
426        """
427
428        lc = []
429
430        if not CPEComponent.is_valid_attribute(att_name):
431            errmsg = "Invalid attribute name '{0}'".format(att_name)
432            raise ValueError(errmsg)
433
434        for pk in CPE.CPE_PART_KEYS:
435            elements = self.get(pk)
436            for elem in elements:
437                comp = elem.get(att_name)
438
439                if (isinstance(comp, CPEComponentAnyValue) or
440                   isinstance(comp, CPEComponentUndefined)):
441                    value = CPEComponent2_3_URI.VALUE_ANY
442                elif isinstance(comp, CPEComponentNotApplicable):
443                    value = CPEComponent2_3_URI.VALUE_NA
444                else:
445                    value = comp.get_value()
446
447                lc.append(value)
448        return lc
449
450if __name__ == "__main__":
451    import doctest
452    doctest.testmod()
453    doctest.testfile("tests/testfile_cpe2_3_uri.txt")
454