license-checker icon indicating copy to clipboard operation
license-checker copied to clipboard

highcharts license incorrectly reported using highcharts-server license URL

Open loop-evgeny opened this issue 3 years ago • 2 comments

package.json file:

{
  "dependencies": {
    "react-highcharts": "^16.1.0"
  }
}

Run

npm i
license-checker --packages "[email protected]"

Output:

└─ [email protected]
   ├─ licenses: Custom: https://www.npmjs.com/package/highcharts-server
...

However, node_modules/highcharts/package.json has this:

"license": "https://www.highcharts.com/license"

The difference is significant, because highcharts-server is MIT-licensed, while highcharts has a commercial license.

loop-evgeny avatar Oct 14 '21 08:10 loop-evgeny

I've been investigating this, and found some interesting tidbits:

  • license-checker uses read-installed to traverse the tree of installed packages. read-installed returns a data structure that has a few properties, one of which is readme, another is license.
  • If there is no readme, license-checker will read the README.md file of the package and put that string in the readme property of the data returned from read-installed.
  • If there is no license or licenses property in the data returned from read-installed, license-checker will use the readme property and look for licenses in there instead, using a bunch of regexes, one of which is an URL regex.
  • The README.md file for [email protected] contains a link to https://www.npmjs.com/package/highcharts-server early on.
  • My guess is that for some reason, read-installed does not properly return the license property of this package, causing license-checker to grab the incorrect URL from the README.md instead.
  • Remaining question is why read-installed does not return the license property of [email protected], even though it clearly has such a property in its package.json

joelwkall avatar Oct 21 '21 11:10 joelwkall

Not sure if anyone will ever look into this discussion again, but I just stumbled across this issue in combination with a custom license that was used in one of my packages.

I think I was able to track it down to this portion of code from index.js of license-checker (Lines 151 to 172 in Version: 25.0.1)

if (json.path && fs.existsSync(json.path)) {
        dirFiles = fs.readdirSync(json.path);
        files = licenseFiles(dirFiles);

        noticeFiles = dirFiles.filter(function(filename) {
            filename = filename.toUpperCase();
            var name = path.basename(filename).replace(path.extname(filename), '');
            return name === 'NOTICE';
        });
    }

    files.forEach(function(filename, index) {
        licenseFile = path.join(json.path, filename);
        // Checking that the file is in fact a normal file and not a directory for example.
        /*istanbul ignore else*/
        if (fs.lstatSync(licenseFile).isFile()) {
            var content;
            if (!moduleInfo.licenses || moduleInfo.licenses.indexOf(UNKNOWN) > -1 || moduleInfo.licenses.indexOf('Custom:') === 0) {
                //Only re-check the license if we didn't get it from elsewhere
                content = fs.readFileSync(licenseFile, { encoding: 'utf8' });
                moduleInfo.licenses = license(content);
            }

The "files" array is populated with all the files that are assumed to be related to a license by the licenseFiles function - basically checking if the file is named LICENSE, LICENSE.MD, LICENCE, LICENCE.MD or ... README[.MD]

Now if the currently found moduleInfo.licenses starts with "Custom:", which is the case if the the license-field of the package.json does not carry a valid spdx-Expression, it will replace that moduleInfo.licenses with something found in one of the files above.

If the package happens to contain a readme.md file and that readme.md file happens to contains a URL, the license function would actually match that URL with the IS_URL-Regex and return "Custom: <FOUND_URL>" here:

match = IS_URL.exec(str) || IS_FILE_REFERENCE.exec(str);
        if(match) {
            return 'Custom: ' + match[1];
        } else {
            return null;
        }

strobocopter avatar Aug 14 '24 15:08 strobocopter