X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=fdroidserver%2Flint.py;h=b0a5cad76f93f52fb33752519bd98602e5605cf9;hb=498ea5d6095784841538e61892a8cfbfe6eecc48;hp=c805288f2112232ed42371c58e32478314edb75e;hpb=3e88b7444d31e894ac3b182f4b5a868f082d08c5;p=fdroidserver.git diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py index c805288f..b0a5cad7 100644 --- a/fdroidserver/lint.py +++ b/fdroidserver/lint.py @@ -17,10 +17,12 @@ # along with this program. If not, see . from argparse import ArgumentParser +import glob import os import re import sys +from . import _ from . import common from . import metadata from . import rewritemeta @@ -33,93 +35,149 @@ def enforce_https(domain): return (re.compile(r'.*[^sS]://[^/]*' + re.escape(domain) + r'(/.*)?'), domain + " URLs should always use https://") + https_enforcings = [ enforce_https('github.com'), enforce_https('gitlab.com'), enforce_https('bitbucket.org'), enforce_https('apache.org'), enforce_https('google.com'), + enforce_https('git.code.sf.net'), enforce_https('svn.code.sf.net'), + enforce_https('anongit.kde.org'), + enforce_https('savannah.nongnu.org'), + enforce_https('git.savannah.nongnu.org'), + enforce_https('download.savannah.nongnu.org'), + enforce_https('savannah.gnu.org'), + enforce_https('git.savannah.gnu.org'), + enforce_https('download.savannah.gnu.org'), ] def forbid_shortener(domain): return (re.compile(r'https?://[^/]*' + re.escape(domain) + r'/.*'), - "URL shorteners should not be used") + _("URL shorteners should not be used")) + http_url_shorteners = [ + forbid_shortener('1url.com'), + forbid_shortener('adf.ly'), + forbid_shortener('bc.vc'), + forbid_shortener('bit.do'), + forbid_shortener('bit.ly'), + forbid_shortener('bitly.com'), + forbid_shortener('budurl.com'), + forbid_shortener('buzurl.com'), + forbid_shortener('cli.gs'), + forbid_shortener('cur.lv'), + forbid_shortener('cutt.us'), + forbid_shortener('db.tt'), + forbid_shortener('filoops.info'), forbid_shortener('goo.gl'), + forbid_shortener('is.gd'), + forbid_shortener('ity.im'), + forbid_shortener('j.mp'), + forbid_shortener('l.gg'), + forbid_shortener('lnkd.in'), + forbid_shortener('moourl.com'), + forbid_shortener('ow.ly'), + forbid_shortener('para.pt'), + forbid_shortener('po.st'), + forbid_shortener('q.gs'), + forbid_shortener('qr.ae'), + forbid_shortener('qr.net'), + forbid_shortener('rdlnk.com'), + forbid_shortener('scrnch.me'), + forbid_shortener('short.nr'), + forbid_shortener('sn.im'), + forbid_shortener('snipurl.com'), + forbid_shortener('su.pr'), forbid_shortener('t.co'), + forbid_shortener('tiny.cc'), + forbid_shortener('tinyarrows.com'), + forbid_shortener('tinyurl.com'), + forbid_shortener('tr.im'), + forbid_shortener('tweez.me'), + forbid_shortener('twitthis.com'), + forbid_shortener('twurl.nl'), + forbid_shortener('tyn.ee'), + forbid_shortener('u.bb'), + forbid_shortener('u.to'), forbid_shortener('ur1.ca'), + forbid_shortener('urlof.site'), + forbid_shortener('v.gd'), + forbid_shortener('vzturl.com'), + forbid_shortener('x.co'), + forbid_shortener('xrl.us'), + forbid_shortener('yourls.org'), + forbid_shortener('zip.net'), + forbid_shortener('✩.ws'), + forbid_shortener('➡.ws'), ] http_checks = https_enforcings + http_url_shorteners + [ (re.compile(r'.*github\.com/[^/]+/[^/]+\.git'), - "Appending .git is not necessary"), + _("Appending .git is not necessary")), (re.compile(r'.*://[^/]*(github|gitlab|bitbucket|rawgit)[^/]*/([^/]+/){1,3}master'), - "Use /HEAD instead of /master to point at a file in the default branch"), + _("Use /HEAD instead of /master to point at a file in the default branch")), ] regex_checks = { - 'Web Site': http_checks, - 'Source Code': http_checks, + 'WebSite': http_checks, + 'SourceCode': http_checks, 'Repo': https_enforcings, - 'Issue Tracker': http_checks + [ + 'IssueTracker': http_checks + [ (re.compile(r'.*github\.com/[^/]+/[^/]+/*$'), - "/issues is missing"), + _("/issues is missing")), (re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'), - "/issues is missing"), + _("/issues is missing")), ], 'Donate': http_checks + [ (re.compile(r'.*flattr\.com'), - "Flattr donation methods belong in the FlattrID flag"), + _("Flattr donation methods belong in the FlattrID flag")), + (re.compile(r'.*liberapay\.com'), + _("Liberapay donation methods belong in the LiberapayID flag")), ], 'Changelog': http_checks, 'Author Name': [ (re.compile(r'^\s'), - "Unnecessary leading space"), + _("Unnecessary leading space")), (re.compile(r'.*\s$'), - "Unnecessary trailing space"), - ], - 'License': [ - (re.compile(r'^(|None|Unknown)$'), - "No license specified"), + _("Unnecessary trailing space")), ], 'Summary': [ - (re.compile(r'^$'), - "Summary yet to be filled"), (re.compile(r'.*\b(free software|open source)\b.*', re.IGNORECASE), - "No need to specify that the app is Free Software"), + _("No need to specify that the app is Free Software")), (re.compile(r'.*((your|for).*android|android.*(app|device|client|port|version))', re.IGNORECASE), - "No need to specify that the app is for Android"), + _("No need to specify that the app is for Android")), (re.compile(r'.*[a-z0-9][.!?]( |$)'), - "Punctuation should be avoided"), + _("Punctuation should be avoided")), (re.compile(r'^\s'), - "Unnecessary leading space"), + _("Unnecessary leading space")), (re.compile(r'.*\s$'), - "Unnecessary trailing space"), + _("Unnecessary trailing space")), ], - 'Description': [ - (re.compile(r'^No description available$'), - "Description yet to be filled"), + 'Description': https_enforcings + http_url_shorteners + [ (re.compile(r'\s*[*#][^ .]'), - "Invalid bulleted list"), + _("Invalid bulleted list")), (re.compile(r'^\s'), - "Unnecessary leading space"), + _("Unnecessary leading space")), (re.compile(r'.*\s$'), - "Unnecessary trailing space"), - (re.compile(r'.*([^[]|^)\[[^:[\]]+( |\]|$)'), - "Invalid link - use [http://foo.bar Link title] or [http://foo.bar]"), - (re.compile(r'(^|.* )https?://[^ ]+'), - "Unlinkified link - use [http://foo.bar Link title] or [http://foo.bar]"), + _("Unnecessary trailing space")), + (re.compile(r'.*<(applet|base|body|button|embed|form|head|html|iframe|img|input|link|object|picture|script|source|style|svg|video).*', re.IGNORECASE), + _("Forbidden HTML tags")), + (re.compile(r'''.*\s+src=["']javascript:.*'''), + _("Javascript in HTML src attributes")), ], } +locale_pattern = re.compile(r'^[a-z]{2,3}(-[A-Z][A-Z])?$') + def check_regexes(app): for f, checks in regex_checks.items(): for m, r in checks: - v = app.get_field(f) + v = app.get(f) t = metadata.fieldtype(f) if t == metadata.TYPE_MULTILINE: for l in v.splitlines(): @@ -137,10 +195,10 @@ def get_lastbuild(builds): lastbuild = None for build in builds: if not build.disable: - vercode = int(build.vercode) + vercode = int(build.versionCode) if lowest_vercode == -1 or vercode < lowest_vercode: lowest_vercode = vercode - if not lastbuild or int(build.vercode) > int(lastbuild.vercode): + if not lastbuild or int(build.versionCode) > int(lastbuild.versionCode): lastbuild = build return lastbuild @@ -151,23 +209,23 @@ def check_ucm_tags(app): and lastbuild.commit and app.UpdateCheckMode == 'RepoManifest' and not lastbuild.commit.startswith('unknown') - and lastbuild.vercode == app.CurrentVersionCode + and lastbuild.versionCode == app.CurrentVersionCode and not lastbuild.forcevercode and any(s in lastbuild.commit for s in '.,_-/')): - yield "Last used commit '%s' looks like a tag, but Update Check Mode is '%s'" % ( - lastbuild.commit, app.UpdateCheckMode) + yield _("Last used commit '{commit}' looks like a tag, but Update Check Mode is '{ucm}'")\ + .format(commit=lastbuild.commit, ucm=app.UpdateCheckMode) def check_char_limits(app): limits = config['char_limits'] - if len(app.Summary) > limits['Summary']: - yield "Summary of length %s is over the %i char limit" % ( - len(app.Summary), limits['Summary']) + if len(app.Summary) > limits['summary']: + yield _("Summary of length {length} is over the {limit} char limit")\ + .format(length=len(app.Summary), limit=limits['summary']) - if len(app.Description) > limits['Description']: - yield "Description of length %s is over the %i char limit" % ( - len(app.Description), limits['Description']) + if len(app.Description) > limits['description']: + yield _("Description of length {length} is over the {limit} char limit")\ + .format(length=len(app.Description), limit=limits['description']) def check_old_links(app): @@ -181,15 +239,17 @@ def check_old_links(app): 'code.google.com', ] if any(s in app.Repo for s in usual_sites): - for f in ['Web Site', 'Source Code', 'Issue Tracker', 'Changelog']: - v = app.get_field(f) + for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']: + v = app.get(f) if any(s in v for s in old_sites): - yield "App is in '%s' but has a link to '%s'" % (app.Repo, v) + yield _("App is in '{repo}' but has a link to {url}")\ + .format(repo=app.Repo, url=v) def check_useless_fields(app): if app.UpdateCheckName == app.id: - yield "Update Check Name is set to the known app id - it can be removed" + yield _("Update Check Name is set to the known app id - it can be removed") + filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)') @@ -197,12 +257,13 @@ filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)') def check_checkupdates_ran(app): if filling_ucms.match(app.UpdateCheckMode): if not app.AutoName and not app.CurrentVersion and app.CurrentVersionCode == '0': - yield "UCM is set but it looks like checkupdates hasn't been run yet" + yield _("UCM is set but it looks like checkupdates hasn't been run yet") def check_empty_fields(app): if not app.Categories: - yield "Categories are not set" + yield _("Categories are not set") + all_categories = set([ "Connectivity", @@ -228,39 +289,39 @@ all_categories = set([ def check_categories(app): for categ in app.Categories: if categ not in all_categories: - yield "Category '%s' is not valid" % categ + yield _("Category '%s' is not valid" % categ) def check_duplicates(app): if app.Name and app.Name == app.AutoName: - yield "Name '%s' is just the auto name - remove it" % app.Name + yield _("Name '%s' is just the auto name - remove it") % app.Name links_seen = set() for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']: - v = app.get_field(f) + v = app.get(f) if not v: continue v = v.lower() if v in links_seen: - yield "Duplicate link in '%s': %s" % (f, v) + yield _("Duplicate link in '{field}': {url}").format(field=f, url=v) else: links_seen.add(v) name = app.Name or app.AutoName if app.Summary and name: if app.Summary.lower() == name.lower(): - yield "Summary '%s' is just the app's name" % app.Summary + yield _("Summary '%s' is just the app's name") % app.Summary if app.Summary and app.Description and len(app.Description) == 1: if app.Summary.lower() == app.Description[0].lower(): - yield "Description '%s' is just the app's summary" % app.Summary + yield _("Description '%s' is just the app's summary") % app.Summary seenlines = set() for l in app.Description.splitlines(): if len(l) < 1: continue if l in seenlines: - yield "Description has a duplicate line" + yield _("Description has a duplicate line") seenlines.add(l) @@ -273,7 +334,7 @@ def check_mediawiki_links(app): url = um.group(1) for m, r in http_checks: if m.match(url): - yield "URL '%s' in Description: %s" % (url, r) + yield _("URL {url} in Description: {error}").format(url=url, error=r) def check_bulleted_lists(app): @@ -288,7 +349,7 @@ def check_bulleted_lists(app): if l[0] == lchar and l[1] == ' ': lcount += 1 if lcount > 2 and lchar not in validchars: - yield "Description has a list (%s) but it isn't bulleted (*) nor numbered (#)" % lchar + yield _("Description has a list (%s) but it isn't bulleted (*) nor numbered (#)") % lchar break else: lchar = l[0] @@ -296,18 +357,28 @@ def check_bulleted_lists(app): def check_builds(app): + supported_flags = set(metadata.build_flags) + # needed for YAML and JSON for build in app.builds: if build.disable: if build.disable.startswith('Generated by import.py'): - yield "Build generated by `fdroid import` - remove disable line once ready" + yield _("Build generated by `fdroid import` - remove disable line once ready") continue for s in ['master', 'origin', 'HEAD', 'default', 'trunk']: if build.commit and build.commit.startswith(s): - yield "Branch '%s' used as commit in build '%s'" % (s, build.version) + yield _("Branch '{branch}' used as commit in build '{versionName}'")\ + .format(branch=s, versionName=build.versionName) for srclib in build.srclibs: - ref = srclib.split('@')[1].split('/')[0] - if ref.startswith(s): - yield "Branch '%s' used as commit in srclib '%s'" % (s, srclib) + if '@' in srclib: + ref = srclib.split('@')[1].split('/')[0] + if ref.startswith(s): + yield _("Branch '{branch}' used as commit in srclib '{srclib}'")\ + .format(branch=s, srclib=srclib) + else: + yield _('srclibs missing name and/or @') + ' (srclibs: ' + srclib + ')' + for key in build.keys(): + if key not in supported_flags: + yield _('%s is not an accepted build field') % key def check_files_dir(app): @@ -317,45 +388,56 @@ def check_files_dir(app): files = set() for name in os.listdir(dir_path): path = os.path.join(dir_path, name) - if not os.path.isfile(path): - yield "Found non-file at %s" % path + if not (os.path.isfile(path) or name == 'signatures' or locale_pattern.match(name)): + yield _("Found non-file at %s") % path continue files.add(name) - used = set() + used = {'signatures', } for build in app.builds: for fname in build.patch: if fname not in files: - yield "Unknown file %s in build '%s'" % (fname, build.version) + yield _("Unknown file '{filename}' in build '{versionName}'")\ + .format(filename=fname, versionName=build.versionName) else: used.add(fname) for name in files.difference(used): - yield "Unused file at %s" % os.path.join(dir_path, name) + if locale_pattern.match(name): + continue + yield _("Unused file at %s") % os.path.join(dir_path, name) def check_format(app): if options.format and not rewritemeta.proper_format(app): - yield "Run rewritemeta to fix formatting" + yield _("Run rewritemeta to fix formatting") + + +def check_license_tag(app): + '''Ensure all license tags are in https://spdx.org/license-list''' + if app.License.rstrip('+') not in SPDX: + yield _('Invalid license tag "%s"! Use only tags from https://spdx.org/license-list') \ + % (app.License) def check_extlib_dir(apps): dir_path = os.path.join('build', 'extlib') - files = set() - for root, dirs, names in os.walk(dir_path): - for name in names: - files.add(os.path.join(root, name)[len(dir_path) + 1:]) + unused_extlib_files = set() + for root, dirs, files in os.walk(dir_path): + for name in files: + unused_extlib_files.add(os.path.join(root, name)[len(dir_path) + 1:]) used = set() for app in apps: for build in app.builds: for path in build.extlibs: - if path not in files: - yield "%s: Unknown extlib %s in build '%s'" % (app.id, path, build.version) + if path not in unused_extlib_files: + yield _("{appid}: Unknown extlib {path} in build '{versionName}'")\ + .format(appid=app.id, path=path, versionName=build.versionName) else: used.add(path) - for path in files.difference(used): + for path in unused_extlib_files.difference(used): if any(path.endswith(s) for s in [ '.gitignore', 'source.txt', 'origin.txt', 'md5.txt', @@ -364,7 +446,30 @@ def check_extlib_dir(apps): 'NOTICE', 'NOTICE.txt', ]): continue - yield "Unused extlib at %s" % os.path.join(dir_path, path) + yield _("Unused extlib at %s") % os.path.join(dir_path, path) + + +def check_for_unsupported_metadata_files(basedir=""): + """Checks whether any non-metadata files are in metadata/""" + + global config + + return_value = False + formats = config['accepted_formats'] + for f in glob.glob(basedir + 'metadata/*') + glob.glob(basedir + 'metadata/.*'): + if os.path.isdir(f): + exists = False + for t in formats: + exists = exists or os.path.exists(f + '.' + t) + if not exists: + print(_('"%s/" has no matching metadata file!') % f) + return_value = True + elif not os.path.splitext(f)[1][1:] in formats: + print('"' + f.replace(basedir, '') + + '" is not a supported file format: (' + ','.join(formats) + ')') + return_value = True + + return return_value def main(): @@ -375,9 +480,11 @@ def main(): parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") common.setup_global_opts(parser) parser.add_argument("-f", "--format", action="store_true", default=False, - help="Also warn about formatting issues, like rewritemeta -l") - parser.add_argument("appid", nargs='*', help="app-id in the form APPID") + help=_("Also warn about formatting issues, like rewritemeta -l")) + parser.add_argument("appid", nargs='*', help=_("applicationId in the form APPID")) + metadata.add_metadata_arguments(parser) options = parser.parse_args() + metadata.warnings_action = options.W config = common.read_config(options) @@ -385,7 +492,7 @@ def main(): allapps = metadata.read_metadata(xref=True) apps = common.read_app_args(options.appid, allapps, False) - anywarns = False + anywarns = check_for_unsupported_metadata_files() apps_check_funcs = [] if len(options.appid) == 0: @@ -415,6 +522,7 @@ def main(): check_builds, check_files_dir, check_format, + check_license_tag, ] for check_func in app_check_funcs: @@ -426,5 +534,341 @@ def main(): sys.exit(1) +# A compiled, public domain list of official SPDX license tags from: +# https://github.com/sindresorhus/spdx-license-list/blob/v3.0.1/spdx-simple.json +# The deprecated license tags have been removed from the list, they are at the +# bottom, starting after the last license tags that start with Z. +# This is at the bottom, since its a long list of data +SPDX = [ + "PublicDomain", # an F-Droid addition, until we can enforce a better option + "Glide", + "Abstyles", + "AFL-1.1", + "AFL-1.2", + "AFL-2.0", + "AFL-2.1", + "AFL-3.0", + "AMPAS", + "APL-1.0", + "Adobe-Glyph", + "APAFML", + "Adobe-2006", + "AGPL-1.0", + "Afmparse", + "Aladdin", + "ADSL", + "AMDPLPA", + "ANTLR-PD", + "Apache-1.0", + "Apache-1.1", + "Apache-2.0", + "AML", + "APSL-1.0", + "APSL-1.1", + "APSL-1.2", + "APSL-2.0", + "Artistic-1.0", + "Artistic-1.0-Perl", + "Artistic-1.0-cl8", + "Artistic-2.0", + "AAL", + "Bahyph", + "Barr", + "Beerware", + "BitTorrent-1.0", + "BitTorrent-1.1", + "BSL-1.0", + "Borceux", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-2-Clause-NetBSD", + "BSD-3-Clause", + "BSD-3-Clause-Clear", + "BSD-3-Clause-No-Nuclear-License", + "BSD-3-Clause-No-Nuclear-License-2014", + "BSD-3-Clause-No-Nuclear-Warranty", + "BSD-4-Clause", + "BSD-Protection", + "BSD-Source-Code", + "BSD-3-Clause-Attribution", + "0BSD", + "BSD-4-Clause-UC", + "bzip2-1.0.5", + "bzip2-1.0.6", + "Caldera", + "CECILL-1.0", + "CECILL-1.1", + "CECILL-2.0", + "CECILL-2.1", + "CECILL-B", + "CECILL-C", + "ClArtistic", + "MIT-CMU", + "CNRI-Jython", + "CNRI-Python", + "CNRI-Python-GPL-Compatible", + "CPOL-1.02", + "CDDL-1.0", + "CDDL-1.1", + "CPAL-1.0", + "CPL-1.0", + "CATOSL-1.1", + "Condor-1.1", + "CC-BY-1.0", + "CC-BY-2.0", + "CC-BY-2.5", + "CC-BY-3.0", + "CC-BY-4.0", + "CC-BY-ND-1.0", + "CC-BY-ND-2.0", + "CC-BY-ND-2.5", + "CC-BY-ND-3.0", + "CC-BY-ND-4.0", + "CC-BY-NC-1.0", + "CC-BY-NC-2.0", + "CC-BY-NC-2.5", + "CC-BY-NC-3.0", + "CC-BY-NC-4.0", + "CC-BY-NC-ND-1.0", + "CC-BY-NC-ND-2.0", + "CC-BY-NC-ND-2.5", + "CC-BY-NC-ND-3.0", + "CC-BY-NC-ND-4.0", + "CC-BY-NC-SA-1.0", + "CC-BY-NC-SA-2.0", + "CC-BY-NC-SA-2.5", + "CC-BY-NC-SA-3.0", + "CC-BY-NC-SA-4.0", + "CC-BY-SA-1.0", + "CC-BY-SA-2.0", + "CC-BY-SA-2.5", + "CC-BY-SA-3.0", + "CC-BY-SA-4.0", + "CC0-1.0", + "Crossword", + "CrystalStacker", + "CUA-OPL-1.0", + "Cube", + "curl", + "D-FSL-1.0", + "diffmark", + "WTFPL", + "DOC", + "Dotseqn", + "DSDP", + "dvipdfm", + "EPL-1.0", + "ECL-1.0", + "ECL-2.0", + "eGenix", + "EFL-1.0", + "EFL-2.0", + "MIT-advertising", + "MIT-enna", + "Entessa", + "ErlPL-1.1", + "EUDatagrid", + "EUPL-1.0", + "EUPL-1.1", + "Eurosym", + "Fair", + "MIT-feh", + "Frameworx-1.0", + "FreeImage", + "FTL", + "FSFAP", + "FSFUL", + "FSFULLR", + "Giftware", + "GL2PS", + "Glulxe", + "AGPL-3.0", + "GFDL-1.1", + "GFDL-1.2", + "GFDL-1.3", + "GPL-1.0", + "GPL-2.0", + "GPL-3.0", + "LGPL-2.1", + "LGPL-3.0", + "LGPL-2.0", + "gnuplot", + "gSOAP-1.3b", + "HaskellReport", + "HPND", + "IBM-pibs", + "IPL-1.0", + "ICU", + "ImageMagick", + "iMatix", + "Imlib2", + "IJG", + "Info-ZIP", + "Intel-ACPI", + "Intel", + "Interbase-1.0", + "IPA", + "ISC", + "JasPer-2.0", + "JSON", + "LPPL-1.0", + "LPPL-1.1", + "LPPL-1.2", + "LPPL-1.3a", + "LPPL-1.3c", + "Latex2e", + "BSD-3-Clause-LBNL", + "Leptonica", + "LGPLLR", + "Libpng", + "libtiff", + "LAL-1.2", + "LAL-1.3", + "LiLiQ-P-1.1", + "LiLiQ-Rplus-1.1", + "LiLiQ-R-1.1", + "LPL-1.02", + "LPL-1.0", + "MakeIndex", + "MTLL", + "MS-PL", + "MS-RL", + "MirOS", + "MITNFA", + "MIT", + "Motosoto", + "MPL-1.0", + "MPL-1.1", + "MPL-2.0", + "MPL-2.0-no-copyleft-exception", + "mpich2", + "Multics", + "Mup", + "NASA-1.3", + "Naumen", + "NBPL-1.0", + "Net-SNMP", + "NetCDF", + "NGPL", + "NOSL", + "NPL-1.0", + "NPL-1.1", + "Newsletr", + "NLPL", + "Nokia", + "NPOSL-3.0", + "NLOD-1.0", + "Noweb", + "NRL", + "NTP", + "Nunit", + "OCLC-2.0", + "ODbL-1.0", + "PDDL-1.0", + "OCCT-PL", + "OGTSL", + "OLDAP-2.2.2", + "OLDAP-1.1", + "OLDAP-1.2", + "OLDAP-1.3", + "OLDAP-1.4", + "OLDAP-2.0", + "OLDAP-2.0.1", + "OLDAP-2.1", + "OLDAP-2.2", + "OLDAP-2.2.1", + "OLDAP-2.3", + "OLDAP-2.4", + "OLDAP-2.5", + "OLDAP-2.6", + "OLDAP-2.7", + "OLDAP-2.8", + "OML", + "OPL-1.0", + "OSL-1.0", + "OSL-1.1", + "OSL-2.0", + "OSL-2.1", + "OSL-3.0", + "OpenSSL", + "OSET-PL-2.1", + "PHP-3.0", + "PHP-3.01", + "Plexus", + "PostgreSQL", + "psfrag", + "psutils", + "Python-2.0", + "QPL-1.0", + "Qhull", + "Rdisc", + "RPSL-1.0", + "RPL-1.1", + "RPL-1.5", + "RHeCos-1.1", + "RSCPL", + "RSA-MD", + "Ruby", + "SAX-PD", + "Saxpath", + "SCEA", + "SWL", + "SMPPL", + "Sendmail", + "SGI-B-1.0", + "SGI-B-1.1", + "SGI-B-2.0", + "OFL-1.0", + "OFL-1.1", + "SimPL-2.0", + "Sleepycat", + "SNIA", + "Spencer-86", + "Spencer-94", + "Spencer-99", + "SMLNJ", + "SugarCRM-1.1.3", + "SISSL", + "SISSL-1.2", + "SPL-1.0", + "Watcom-1.0", + "TCL", + "TCP-wrappers", + "Unlicense", + "TMate", + "TORQUE-1.1", + "TOSL", + "Unicode-DFS-2015", + "Unicode-DFS-2016", + "Unicode-TOU", + "UPL-1.0", + "NCSA", + "Vim", + "VOSTROM", + "VSL-1.0", + "W3C-20150513", + "W3C-19980720", + "W3C", + "Wsuipa", + "Xnet", + "X11", + "Xerox", + "XFree86-1.1", + "xinetd", + "xpp", + "XSkat", + "YPL-1.0", + "YPL-1.1", + "Zed", + "Zend-2.0", + "Zimbra-1.3", + "Zimbra-1.4", + "Zlib", + "zlib-acknowledgement", + "ZPL-1.1", + "ZPL-2.0", + "ZPL-2.1", +] + if __name__ == "__main__": main()