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