From c0942d686574c1265abebca8daf90674f5a7a26d Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Thu, 17 Sep 2026 22:04:47 -0400 Subject: [PATCH] fix: identify GPL-2.0, not LGPL-2.0, when the GPLv2 preamble names the LGPL The license text matcher treated the mixed-case phrase "Library General Public License" as an LGPL-2.0 signal. That phrase appears in the GPLv2 preamble itself ("...covered by the GNU Library General Public License instead."), so any real GPL-2.0 license file set library=true and was emitted as LGPL-2.0 -- and because the version-2 branch prefers the library case, GPL-2.0 was dropped entirely. GPL and LGPL carry materially different obligations, so this is a compliance-relevant misclassification. GPL-2.0 and LGPL-2.0 share the "Version 2, June 1991" date line, so the only reliable discriminator is the document title. Match `library` on the all-caps LGPL-2.0 title "GNU LIBRARY GENERAL PUBLIC LICENSE" only, never the mixed-case preamble mention. A genuine GPL-2.0 file now reports GPL-2.0; a real LGPL-2.0 file (caps title) still reports LGPL-2.0. Add regression tests: a GPLv2 fixture that includes the preamble's LGPL mention (must be GPL-2.0) and an LGPL-2.0 fixture with the caps title plus a preamble (must stay LGPL-2.0). --- src/license.cpp | 10 ++++++++-- tests/unit/unit_tests.cpp | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/license.cpp b/src/license.cpp index f642343..7a2c735 100644 --- a/src/license.cpp +++ b/src/license.cpp @@ -71,12 +71,18 @@ std::vector identify_license_text(std::span data) { }; // GNU family — disambiguate Affero/Lesser/Library and version by the dated - // title line. LGPL 2.0 (1991) is titled "Library General Public License"; + // title line. LGPL 2.0 (1991) is titled "GNU LIBRARY GENERAL PUBLIC LICENSE"; // the 2.1 (1999) rename to "Lesser" is why v2 needs its own marker (without // it an LGPL-2.0 COPYING is mis-identified as GPL-2.0). + // + // `library` matches only the all-caps *title*, never the mixed-case phrase + // "GNU Library General Public License": the latter appears in the GPLv2 + // *preamble* ("...covered by the GNU Library General Public License + // instead.)"), so matching it flipped a genuine GPL-2.0 file to LGPL-2.0 and + // dropped GPL-2.0 entirely. bool affero = has("GNU AFFERO GENERAL PUBLIC LICENSE") || has("GNU Affero"); bool lesser = has("GNU LESSER GENERAL PUBLIC LICENSE") || has("Lesser General Public"); - bool library = has("GNU LIBRARY GENERAL PUBLIC LICENSE") || has("Library General Public License"); + bool library = has("GNU LIBRARY GENERAL PUBLIC LICENSE"); if (has("Version 3, 29 June 2007")) { if (affero) add("AGPL-3.0"); else if (lesser) add("LGPL-3.0"); diff --git a/tests/unit/unit_tests.cpp b/tests/unit/unit_tests.cpp index 13d3424..e3f803e 100644 --- a/tests/unit/unit_tests.cpp +++ b/tests/unit/unit_tests.cpp @@ -1399,6 +1399,20 @@ static void test_license() { // and plain GPL-2.0 is still GPL-2.0 (no false LGPL). CHECK(lic_text("GNU GENERAL PUBLIC LICENSE\n Version 2, June 1991\n") == std::vector{"GPL-2.0"}); + // Regression: the real GPLv2 *preamble* names the LGPL ("...covered by the + // GNU Library General Public License instead."). That mixed-case mention must + // NOT flip a genuine GPL-2.0 file to LGPL-2.0, nor drop GPL-2.0. Only the + // all-caps LGPL *title* means LGPL-2.0. + auto gpl2_preamble = lic_text( + "GNU GENERAL PUBLIC LICENSE\n Version 2, June 1991\n\n" + " (Some other Free Software Foundation software is covered by the GNU\n" + "Library General Public License instead.) You can apply it too.\n"); + CHECK(gpl2_preamble.size() == 1 && gpl2_preamble[0] == "GPL-2.0"); + // and a real LGPL-2.0 (caps title) is still LGPL-2.0, even with a preamble. + auto lgpl2_full = lic_text( + "GNU LIBRARY GENERAL PUBLIC LICENSE\n Version 2, June 1991\n\n" + " This library is free software; you can redistribute it ...\n"); + CHECK(lgpl2_full.size() == 1 && lgpl2_full[0] == "LGPL-2.0"); // New markers. CHECK(lic_text("Boost Software License - Version 1.0") == std::vector{"BSL-1.0"});