|
1 | 1 | # Copyright (C) 2021 Intel Corporation |
2 | 2 | # SPDX-License-Identifier: GPL-3.0-or-later |
3 | 3 |
|
| 4 | +import json |
4 | 5 | import os |
5 | 6 | import subprocess |
6 | 7 | import sys |
7 | 8 | from re import MULTILINE, compile, search |
| 9 | +from typing import List |
8 | 10 |
|
9 | 11 | import defusedxml.ElementTree as ET |
10 | 12 |
|
@@ -122,6 +124,7 @@ def is_executable(self, filename): |
122 | 124 | and ("PKG-INFO: " not in output) |
123 | 125 | and ("METADATA: " not in output) |
124 | 126 | and ("pom.xml" not in output) |
| 127 | + and ("package-lock.json" not in output) |
125 | 128 | ): |
126 | 129 | return False, None |
127 | 130 | # otherwise use python implementation of file |
@@ -169,8 +172,11 @@ def scan_file(self, filename): |
169 | 172 |
|
170 | 173 | # Check for Java package |
171 | 174 | if output and "pom.xml" in output: |
172 | | - java_lines = "\n".join(lines.splitlines()) |
173 | | - yield from self.run_java_checker(filename, java_lines) |
| 175 | + yield from self.run_java_checker(filename) |
| 176 | + |
| 177 | + # Javascript checker |
| 178 | + if output and "package-lock.json" in output: |
| 179 | + yield from self.run_js_checker(filename) |
174 | 180 |
|
175 | 181 | # If python package then strip the lines to avoid detecting other product strings |
176 | 182 | if output and ("PKG-INFO: " in output or "METADATA: " in output): |
@@ -199,7 +205,7 @@ def find_java_vendor(self, product, version): |
199 | 205 | return ProductInfo(vendor, product, version), file_path |
200 | 206 | return None, None |
201 | 207 |
|
202 | | - def run_java_checker(self, filename, lines): |
| 208 | + def run_java_checker(self, filename: str) -> None: |
203 | 209 | """Process maven pom.xml file and extract product and dependency details""" |
204 | 210 | tree = ET.parse(filename) |
205 | 211 | # Find root element |
@@ -247,6 +253,65 @@ def run_java_checker(self, filename, lines): |
247 | 253 |
|
248 | 254 | self.logger.debug(f"Done scanning file: {filename}") |
249 | 255 |
|
| 256 | + def find_js_vendor(self, product: str, version: str) -> List[List[str]]: |
| 257 | + """Find vendor for Javascript product""" |
| 258 | + if version == "*": |
| 259 | + return None |
| 260 | + vendor_package_pair = self.cve_db.get_vendor_product_pairs(product) |
| 261 | + vendorlist: List[List[str]] = [] |
| 262 | + if vendor_package_pair != []: |
| 263 | + # To handle multiple vendors, return all combinations of product/vendor mappings |
| 264 | + for v in vendor_package_pair: |
| 265 | + vendor = v["vendor"] |
| 266 | + file_path = "".join(self.file_stack) |
| 267 | + # Tidy up version string |
| 268 | + if "^" in version: |
| 269 | + version = version[1:] |
| 270 | + self.logger.debug(f"{file_path} {product} {version} by {vendor}") |
| 271 | + vendorlist.append([ProductInfo(vendor, product, version), file_path]) |
| 272 | + return vendorlist if len(vendorlist) > 0 else None |
| 273 | + return None |
| 274 | + |
| 275 | + def run_js_checker(self, filename: str) -> None: |
| 276 | + """Process package-lock.json file and extract product and dependency details""" |
| 277 | + fh = open(filename) |
| 278 | + data = json.load(fh) |
| 279 | + product = data["name"] |
| 280 | + version = data["version"] |
| 281 | + vendor = self.find_js_vendor(product, version) |
| 282 | + if vendor is not None: |
| 283 | + for v in vendor: |
| 284 | + yield v[0], v[1] # product_info, file_path |
| 285 | + # Now process dependencies |
| 286 | + for i in data["dependencies"]: |
| 287 | + # To handle @actions/<product>: lines, extract product name from line |
| 288 | + product = i.split("/")[1] if "/" in i else i |
| 289 | + # Handle different formats. Either <product> : <version> or |
| 290 | + # <product>: { |
| 291 | + # ... |
| 292 | + # "version" : <version> |
| 293 | + # ... |
| 294 | + # } |
| 295 | + try: |
| 296 | + version = data["dependencies"][i]["version"] |
| 297 | + except Exception: |
| 298 | + # Cater for case when version field not present |
| 299 | + version = data["dependencies"][i] |
| 300 | + vendor = self.find_js_vendor(product, version) |
| 301 | + if vendor is not None: |
| 302 | + for v in vendor: |
| 303 | + yield v[0], v[1] # product_info, file_path |
| 304 | + if "requires" in data["dependencies"][i]: |
| 305 | + for r in data["dependencies"][i]["requires"]: |
| 306 | + # To handle @actions/<product>: lines, extract product name from line |
| 307 | + product = r.split("/")[1] if "/" in r else r |
| 308 | + version = data["dependencies"][i]["requires"][r] |
| 309 | + vendor = self.find_js_vendor(product, version) |
| 310 | + if vendor is not None: |
| 311 | + for v in vendor: |
| 312 | + yield v[0], v[1] # product_info, file_path |
| 313 | + self.logger.debug(f"Done scanning file: {filename}") |
| 314 | + |
250 | 315 | def run_python_package_checkers(self, filename, lines): |
251 | 316 | """ |
252 | 317 | This generator runs only for python packages. |
|
0 commit comments