# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. import os from glob import glob from html.parser import HTMLParser from mozlint import result from mozlint.pathutils import expand_exclusions here = os.path.abspath(os.path.dirname(__file__)) topsrcdir = os.path.join(here, "..", "..", "..") # Official source: https://www.mozilla.org/en-US/MPL/headers/ TEMPLATES = { "mpl2_license": """ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. """.strip().splitlines(), "public_domain_license": """ Any copyright is dedicated to the public domain. https://creativecommons.org/publicdomain/zero/1.0/ """.strip().splitlines(), } license_list = os.path.join(here, "valid-licenses.txt") def load_valid_license(): """ Load the list of license patterns """ with open(license_list) as f: l = f.readlines() # Remove the empty lines return list(filter(bool, [x.replace("\n", "") for x in l])) def is_valid_license(licenses, filename): """ From a given file, check if we can find the license patterns in the X first lines of the file """ with open(filename, "r", errors="replace") as myfile: contents = myfile.read() # Empty files don't need a license. if not contents: return True for l in licenses: if l.lower().strip() in contents.lower(): return True return False def add_header(log, filename, header): """ Add the header to the top of the file """ header.append("\n") with open(filename, "r+") as f: # lines in list format try: lines = f.readlines() except UnicodeDecodeError as e: log.debug("Could not read file '{}'".format(f)) log.debug("Error: {}".format(e)) return i = 0 if lines: # if the file isn't empty (__init__.py can be empty files) if lines[0].startswith("#!") or lines[0].startswith(" end = " -->" license.append(start + l.strip() + end) if ext != ".svg" or not end: # When dealing with an svg, we should not have a space between # the license and the content license.append("\n") add_header(log, filename, license) return True # In case we don't know how to handle a specific format. return False class HTMLParseError(Exception): def __init__(self, msg, pos): super().__init__(msg, *pos) class LicenseHTMLParser(HTMLParser): def __init__(self): super().__init__() self.in_code = False self.invalid_paths = [] def handle_starttag(self, tag, attrs): if tag == "code": if self.in_code: raise HTMLParseError("nested code tag", self.getpos()) self.in_code = True def handle_endtag(self, tag): if tag == "code": if not self.in_code: raise HTMLParseError("not started code tag", self.getpos()) self.in_code = False def handle_data(self, data): if self.in_code: path = data.strip() abspath = os.path.join(topsrcdir, path) if not glob(abspath): self.invalid_paths.append((path, self.getpos())) def lint_license_html(path): parser = LicenseHTMLParser() with open(path) as fd: content = fd.read() parser.feed(content) return parser.invalid_paths def is_html_licence_summary(path): license_html = os.path.join(topsrcdir, "toolkit", "content", "license.html") return os.path.samefile(path, license_html) def lint(paths, config, fix=None, **lintargs): results = [] log = lintargs["log"] files = list(expand_exclusions(paths, config, lintargs["root"])) fixed = 0 licenses = load_valid_license() for f in files: if is_test(f): # For now, do not do anything with test (too many) continue if not is_valid_license(licenses, f): if fix and fix_me(log, f): fixed += 1 else: res = { "path": f, "message": "No matching license strings found in tools/lint/license/valid-licenses.txt", # noqa "level": "error", } results.append(result.from_config(config, **res)) if is_html_licence_summary(f): try: for invalid_path, (lineno, column) in lint_license_html(f): res = { "path": f, "message": "references unknown path {}".format(invalid_path), "level": "error", "lineno": lineno, "column": column, } results.append(result.from_config(config, **res)) except HTMLParseError as err: res = { "path": f, "message": err.args[0], "level": "error", "lineno": err.args[1], "column": err.args[2], } results.append(result.from_config(config, **res)) return {"results": results, "fixed": fixed}