#!/usr/bin/env python3
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
"""Makes sure that all files contain proper licensing information."""


import optparse
import os.path
import subprocess
import sys


def PrintUsage():
  print("""Usage: python checklicenses.py [--root <root>] [tocheck]
  --root   Specifies the repository root. This defaults to ".." relative
           to the script file. This will be correct given the normal location
           of the script in "<root>/tools".

  --ignore-suppressions  Ignores path-specific allowed license. Useful when
                         trying to remove a suppression/allowed entry.

  --list-allowed  Print a list of allowed licenses and exit.

  tocheck  Specifies the directory, relative to root, to check. This defaults
           to "." so it checks everything.

Examples:
  python checklicenses.py
  python checklicenses.py --root ~/chromium/src third_party""")


ALLOWED_LICENSES = [
    'BSD (1 clause)',
    'BSD (2 clause)',
    'BSD (2 clause) GPL (v2 or later)',
    'BSD (3 clause)',
    'BSD (4 clause (University of California-Specific))',
    'GPL (v2 or later)',
    'ISC',
    'ISC GPL (v2 or later)',
    'LGPL (v2 or later)',
    'LGPL (v2.1 or later)',
    'MIT/X11 (BSD like)',
    'Public domain',
    'Public domain GPL (v2 or later)',
    'Public domain MIT/X11 (BSD like)',
    'zlib/libpng',
    'zlib/libpng GPL (v2 or later)',
]


PATH_SPECIFIC_ALLOWED_LICENSES = {
    # Some of the libpcap include files (including pcap.h) have the
    # 4-clause BSD license with an advertising clause for the Computer
    # Systems Engineering Group at Lawrence Berkeley Laboratory.
    # We have always distributed packages including the headers,
    # so if this is a problem it's one even if these files aren't
    # copied into our repository.
    'libpcap/pcap': [
        'BSD (4 clause)',
    ],
    'wsutil/strnatcmp.c': [
        'Zlib',
    ],
    'wsutil/strnatcmp.h': [
        'Zlib',
    ],
    'resources/protocols/dtds': [
        'UNKNOWN',
    ],
    'resources/protocols/diameter/dictionary.dtd': [
        'UNKNOWN',
    ],
    'resources/protocols/wimaxasncp/dictionary.dtd': [
        'UNKNOWN',
    ],
    'doc/': [
        'UNKNOWN',
    ],
    'doc/custom_layer_chm.xsl': [
        'UNKNOWN',
    ],
    'doc/custom_layer_single_html.xsl': [
        'UNKNOWN',
    ],
    'fix': [
        'UNKNOWN',
    ],
    'wsutil/g711.c': [
        'UNKNOWN',
    ],
    'packaging/macosx': [
        'UNKNOWN',
    ],
    'epan/except.c': [
        'UNKNOWN',
    ],
    'epan/except.h': [
        'UNKNOWN',
    ],
    # Generated header files by lex/lemon/whatever
    'epan/dtd_grammar.h': [
        'UNKNOWN',
    ],
    'epan/dfilter/grammar.h': [
        'UNKNOWN',
    ],
    'epan/dfilter/grammar.c': [
        'UNKNOWN',
    ],
    'epan/dissectors/packet-ieee80211-radiotap-iter.': [ # Using ISC license only
         'ISC GPL (v2)'
    ],
     # Mentions BSD-3-clause twice due to embedding of code:
    'epan/dissectors/packet-communityid.c': [
         'BSD (3 clause) BSD (3 clause)',
    ],
    'plugins/mate/mate_grammar.h': [
        'UNKNOWN',
    ],
    'vcs_version.h': [
        'UNKNOWN',
    ],
    # Special IDL license that appears to be compatible as far as I (not a
    # lawyer) can tell. See
    # https://lists.wireshark.org/archives/wireshark-dev/201310/msg00234.html
    'epan/dissectors/pidl/idl_types.h': [
        'UNKNOWN',
    ],
    # The following tools are under incompatible licenses (mostly GPLv3 or
    # GPLv3+), but this is OK since they are not actually linked into Wireshark
    'tools/pidl': [
        'UNKNOWN',
    ],
    'tools/lemon': [
        'UNKNOWN',
    ],
    'tools/licensecheck.pl': [
        'GPL (v2)'
    ],
    '.gitlab/': [
        'UNKNOWN',
    ],
    'wsutil/dtoa.c': [
        'dtoa',
    ],
    'wsutil/dtoa.h': [
        'dtoa',
    ],
    'wsutil/safe-math.h': [ # Public domain (CC0)
        'UNKNOWN',
    ],
}

def check_licenses(options, args):
  if options.list_allowed:
    print('\n'.join(ALLOWED_LICENSES))
    sys.exit(0)

  # Figure out which directory we have to check.
  if len(args) == 0:
    # No directory to check specified, use the repository root.
    start_dir = options.base_directory
  elif len(args) == 1:
    # Directory specified. Start here. It's supposed to be relative to the
    # base directory.
    start_dir = os.path.abspath(os.path.join(options.base_directory, args[0]))
  else:
    # More than one argument, we don't handle this.
    PrintUsage()
    return 1

  print("Using base directory: %s" % options.base_directory)
  print("Checking: %s" % start_dir)
  print("")

  licensecheck_path = os.path.abspath(os.path.join(options.base_directory,
                                                    'tools',
                                                    'licensecheck.pl'))

  licensecheck = subprocess.Popen([licensecheck_path,
                                   '-l', '160',
                                   '-r', start_dir],
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE)
  stdout, stderr = licensecheck.communicate()
  stdout = stdout.decode('utf-8')
  stderr = stderr.decode('utf-8')
  if options.verbose:
    print('----------- licensecheck stdout -----------')
    print(stdout)
    print('--------- end licensecheck stdout ---------')
  if licensecheck.returncode != 0 or stderr:
    print('----------- licensecheck stderr -----------')
    print(stderr)
    print('--------- end licensecheck stderr ---------')
    print("\nFAILED\n")
    return 1

  success = True
  exit_status = 0
  for line in stdout.splitlines():
    filename, license = line.split(':', 1)
    filename = os.path.relpath(filename.strip(), options.base_directory)

    # All files in the build output directory are generated one way or another.
    # There's no need to check them.
    if os.path.dirname(filename).startswith('build'):
      continue

    # For now we're just interested in the license.
    license = license.replace('*No copyright*', '').strip()

    # Skip generated files.
    if 'GENERATED FILE' in license:
      continue

    # Support files which provide a choice between licenses.
    if any(item in ALLOWED_LICENSES for item in license.split(';')):
      continue

    if not options.ignore_suppressions:
      found_path_specific = False
      for prefix in PATH_SPECIFIC_ALLOWED_LICENSES:
        if (filename.startswith(prefix) and
            license in PATH_SPECIFIC_ALLOWED_LICENSES[prefix]):
          found_path_specific = True
          break
      if found_path_specific:
        continue

    reason = "License '%s' for '%s' is not allowed." % (license, filename)
    success = False
    print(reason)
    exit_status = 1

  if success:
    print("\nSUCCESS\n")
    return 0
  else:
    print("\nFAILED\n")
    return exit_status


def main():
  default_root = os.path.abspath(
      os.path.join(os.path.dirname(__file__), '..'))
  option_parser = optparse.OptionParser()
  option_parser.add_option('--root', default=default_root,
                           dest='base_directory',
                           help='Specifies the repository root. This defaults '
                           'to "../.." relative to the script file, which '
                           'will normally be the repository root.')
  option_parser.add_option('-v', '--verbose', action='store_true',
                           default=False, help='Print debug logging')
  option_parser.add_option('--list-allowed',
                           action='store_true',
                           default=False,
                           help='Print a list of allowed licenses and exit.')
  option_parser.add_option('--ignore-suppressions',
                           action='store_true',
                           default=False,
                           help='Ignore path-specific allowed license.')
  options, args = option_parser.parse_args()
  return check_licenses(options, args)


if '__main__' == __name__:
  sys.exit(main())
