From dd9c5052a2d6d6d1bc2f81870fb4439f9e48c553 Mon Sep 17 00:00:00 2001 From: mariano scasso Date: Wed, 8 Apr 2026 15:33:58 +0200 Subject: [PATCH 1/5] change license report behavior acording to SP-4247 --- inc/scanoss.h | 3 ++- src/help.c | 1 + src/license.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/main.c | 2 ++ src/match.c | 6 ++++-- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/inc/scanoss.h b/inc/scanoss.h index 23ca700..7642f2c 100644 --- a/inc/scanoss.h +++ b/inc/scanoss.h @@ -33,7 +33,7 @@ #define WFP_LN 4 #define WFP_REC_LN 18 -#define SCANOSS_VERSION "5.4.23" +#define SCANOSS_VERSION "5.4.24" /* Log files */ #define SCAN_LOG "/tmp/scanoss_scan.log" @@ -59,6 +59,7 @@ #define DISABLE_SERVER_INFO 4096 #define DISABLE_HEALTH 8192 #define ENABLE_HIGH_ACCURACY 16384 +#define ENABLE_LICENSE_FULL_REPORT 32768 #define MAX_SBOM_ITEMS 2000 #define SHORTEST_PATHS_QTY 4000 // number of shortest path to evaluate diff --git a/src/help.c b/src/help.c index 075f9fb..441656a 100644 --- a/src/help.c +++ b/src/help.c @@ -102,6 +102,7 @@ These settings can also be specified in %s\n\ | 4096 | Disable extended server stats (default: enabled) |\n\ | 8192 | Disable health layer (default: enabled) |\n\ | 16384 | Enable high accuracy, slower scan (default: disabled) |\n\ +| 32768 | Enable full license report (default: disabled) |\n\ +-------+-------------------------------------------------------+\n\ Examples:\n\ scanoss -F 12 DIRECTORY Scan DIRECTORY without license and dependency data\n\ diff --git a/src/license.c b/src/license.c index 74a2b22..43bb26b 100644 --- a/src/license.c +++ b/src/license.c @@ -291,7 +291,7 @@ static char *json_from_license(uint32_t *crclist, char *buffer, char *license, i if (!license_source_id) return buffer; //skip scancode licenses starting with "LicenseRef" - if (!strncmp(license_source_id, "scancode", 8) && !strncmp(license, "LicenseRef", 10)) + if (!strncmp(license_source_id, "scancode", 8) && strstr(license, "LicenseRef")) return buffer; /* Calculate CRC to avoid duplicates */ uint32_t CRC = string_crc32c(license); @@ -313,8 +313,49 @@ static char *json_from_license(uint32_t *crclist, char *buffer, char *license, i len += sprintf(buffer + len, "\"name\": \"%s\",", license); len += osadl_print_license(buffer + len, license, true); len += sprintf(buffer + len, "\"source\": \"%s\"", license_source_id); - if (!strstr(license, "LicenseRef")) + + /* Check if license contains AND/OR operators */ + if (strstr(license, " AND ") || strstr(license, " OR ")) + { + /* Build "urls" object with each individual license mapped to its URL */ + len += sprintf(buffer + len, ",\"urls\": {"); + char lic_copy[MAX_FIELD_LN]; + strncpy(lic_copy, license, MAX_FIELD_LN - 1); + lic_copy[MAX_FIELD_LN - 1] = '\0'; + + char first_license[MAX_FIELD_LN] = "\0"; + bool first_entry = true; + char *saveptr = NULL; + char *token = strtok_r(lic_copy, " ", &saveptr); + + while (token) + { + /* Skip AND/OR operators */ + if (strcmp(token, "AND") == 0 || strcmp(token, "OR") == 0) + { + token = strtok_r(NULL, " ", &saveptr); + continue; + } + if (!first_entry) + len += sprintf(buffer + len, ","); + else + { + strncpy(first_license, token, MAX_FIELD_LN - 1); + first_entry = false; + } + len += sprintf(buffer + len, "\"%s\": \"https://spdx.org/licenses/%s.html\"", token, token); + token = strtok_r(NULL, " ", &saveptr); + } + len += sprintf(buffer + len, "}"); + + /* "url" points to the first license only */ + len += sprintf(buffer + len, ",\"url\": \"https://spdx.org/licenses/%s.html\"", first_license); + } + else + { len += sprintf(buffer + len, ",\"url\": \"https://spdx.org/licenses/%s.html\"", license); + } + len += sprintf(buffer + len, "}"); return (buffer + len); } @@ -507,7 +548,7 @@ void print_licenses(component_data_t *comp) len += sprintf(result + len, "\"licenses\": ["); buffer = result + len; bool first = true; - + bool component_license = false; /* Sort licenses by id (ascending) */ if (licenses_by_type.count > 1) qsort(licenses_by_type.licenses, licenses_by_type.count, sizeof(struct license_type), license_compare_by_id); @@ -515,6 +556,12 @@ void print_licenses(component_data_t *comp) for (int i = 0; i < licenses_by_type.count; i++) { buffer = license_to_json(crclist, buffer, licenses_by_type.licenses[i].text, licenses_by_type.licenses[i].id, &first); + //just report component license if available + if (licenses_by_type.licenses[i].id == 0 && !first) + component_license = true; + + if (i > 0 && component_license && !full_license_report) + break; } len = buffer - result; diff --git a/src/main.c b/src/main.c index b732d42..75d0ed0 100644 --- a/src/main.c +++ b/src/main.c @@ -384,6 +384,8 @@ int main(int argc, char **argv) case 'F': engine_flags_cmd_line = atol(optarg); engine_flags |= engine_flags_cmd_line; + if (engine_flags & ENABLE_LICENSE_FULL_REPORT) + full_license_report = true; break; case 'l': diff --git a/src/match.c b/src/match.c index d02a780..c20d927 100644 --- a/src/match.c +++ b/src/match.c @@ -965,8 +965,10 @@ void match_select_best(scan_data_t *scan) } //If the best match is not good or is not identified be prefer the candidate. - if ((!best_match_component->identified && match_component->identified) || - (path_is_third_party(best_match_component) < path_is_third_party(match_component))) + if (best_match_component->identified > match_component->identified) + continue; + + if (path_is_third_party(best_match_component) < path_is_third_party(match_component)) { scanlog("Replacing best match for a prefered component\n"); scan->matches_list_array[i]->best_match = item->match; From b0276d916ee09bcb86080762b2e3a5464b201ed9 Mon Sep 17 00:00:00 2001 From: mariano scasso Date: Thu, 9 Apr 2026 12:55:08 +0200 Subject: [PATCH 2/5] limit file source licenses to 3 --- src/license.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/license.c b/src/license.c index 43bb26b..e896e26 100644 --- a/src/license.c +++ b/src/license.c @@ -549,12 +549,29 @@ void print_licenses(component_data_t *comp) buffer = result + len; bool first = true; bool component_license = false; + int file_header_filter = 0; + int scancode_file_filter = 0; /* Sort licenses by id (ascending) */ if (licenses_by_type.count > 1) qsort(licenses_by_type.licenses, licenses_by_type.count, sizeof(struct license_type), license_compare_by_id); for (int i = 0; i < licenses_by_type.count; i++) { + //file header license and scancode_file liceses are limited to a maximum of 3. + if (licenses_by_type.licenses[i].id == 2) + { + file_header_filter++; + if (file_header_filter >=3 && !full_license_report) + continue; + } + + if (licenses_by_type.licenses[i].id == 4) + { + scancode_file_filter++; + if (scancode_file_filter >=3 && !full_license_report) + continue; + } + buffer = license_to_json(crclist, buffer, licenses_by_type.licenses[i].text, licenses_by_type.licenses[i].id, &first); //just report component license if available if (licenses_by_type.licenses[i].id == 0 && !first) From 4c1fab5bba15e51969718e397f7172f352bc68f4 Mon Sep 17 00:00:00 2001 From: mariano scasso Date: Thu, 9 Apr 2026 14:53:25 +0200 Subject: [PATCH 3/5] add WITH case for license split --- src/license.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/license.c b/src/license.c index e896e26..875f695 100644 --- a/src/license.c +++ b/src/license.c @@ -314,8 +314,8 @@ static char *json_from_license(uint32_t *crclist, char *buffer, char *license, i len += osadl_print_license(buffer + len, license, true); len += sprintf(buffer + len, "\"source\": \"%s\"", license_source_id); - /* Check if license contains AND/OR operators */ - if (strstr(license, " AND ") || strstr(license, " OR ")) + /* Check if license contains AND/OR/WITH operators */ + if (strstr(license, " AND ") || strstr(license, " OR ") || strstr(license, " WITH ")) { /* Build "urls" object with each individual license mapped to its URL */ len += sprintf(buffer + len, ",\"urls\": {"); @@ -326,14 +326,14 @@ static char *json_from_license(uint32_t *crclist, char *buffer, char *license, i char first_license[MAX_FIELD_LN] = "\0"; bool first_entry = true; char *saveptr = NULL; - char *token = strtok_r(lic_copy, " ", &saveptr); + char *token = strtok_r(lic_copy, " ()", &saveptr); while (token) { - /* Skip AND/OR operators */ - if (strcmp(token, "AND") == 0 || strcmp(token, "OR") == 0) + /* Skip AND/OR/WITH operators */ + if (strcmp(token, "AND") == 0 || strcmp(token, "OR") == 0 || strcmp(token, "WITH") == 0) { - token = strtok_r(NULL, " ", &saveptr); + token = strtok_r(NULL, " ()", &saveptr); continue; } if (!first_entry) @@ -344,7 +344,7 @@ static char *json_from_license(uint32_t *crclist, char *buffer, char *license, i first_entry = false; } len += sprintf(buffer + len, "\"%s\": \"https://spdx.org/licenses/%s.html\"", token, token); - token = strtok_r(NULL, " ", &saveptr); + token = strtok_r(NULL, " ()", &saveptr); } len += sprintf(buffer + len, "}"); From 6cf3fe3491426cdd47b721364e0f53cf8b8a3db3 Mon Sep 17 00:00:00 2001 From: mariano scasso Date: Thu, 9 Apr 2026 18:42:02 +0200 Subject: [PATCH 4/5] use scancode component liceses as fallback --- src/license.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/license.c b/src/license.c index 875f695..91dd783 100644 --- a/src/license.c +++ b/src/license.c @@ -549,6 +549,7 @@ void print_licenses(component_data_t *comp) buffer = result + len; bool first = true; bool component_license = false; + bool scanoss_license = false; int file_header_filter = 0; int scancode_file_filter = 0; /* Sort licenses by id (ascending) */ @@ -572,10 +573,16 @@ void print_licenses(component_data_t *comp) continue; } + if (licenses_by_type.licenses[i].id == 5 && scanoss_license && !full_license_report) + continue; + buffer = license_to_json(crclist, buffer, licenses_by_type.licenses[i].text, licenses_by_type.licenses[i].id, &first); //just report component license if available if (licenses_by_type.licenses[i].id == 0 && !first) component_license = true; + + else if (licenses_by_type.licenses[i].id > 0 && !first) + scanoss_license = true; if (i > 0 && component_license && !full_license_report) break; From 692cae730bd0eab0448c2be9ae847b47c6966314 Mon Sep 17 00:00:00 2001 From: mariano scasso Date: Fri, 10 Apr 2026 04:23:53 +0200 Subject: [PATCH 5/5] adjust license logic to license service --- src/license.c | 56 +++++++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/license.c b/src/license.c index 91dd783..553cfa9 100644 --- a/src/license.c +++ b/src/license.c @@ -133,19 +133,28 @@ void license_free_list(struct license_list * ptr) ptr->count = 0; } +static int license_priority(int id) +{ + static const int priority[] = {0, 31, 32, 33, 35, 3, 1, 2, 5}; + static const int n = sizeof(priority) / sizeof(priority[0]); + + for (int i = 0; i < n; i++) + if (priority[i] == id) + return i; + + return n; /* unknown IDs go last */ +} + static int license_compare_by_id(const void *a, const void *b) { const struct license_type *la = a; const struct license_type *lb = b; - /* IDs 5 and 6 should go to the end */ - bool a_is_last = (la->id == 5 || la->id == 6); - bool b_is_last = (lb->id == 5 || lb->id == 6); + int pa = license_priority(la->id); + int pb = license_priority(lb->id); - if (a_is_last && !b_is_last) - return 1; - if (!a_is_last && b_is_last) - return -1; + if (pa != pb) + return pa - pb; return la->id - lb->id; } @@ -548,44 +557,17 @@ void print_licenses(component_data_t *comp) len += sprintf(result + len, "\"licenses\": ["); buffer = result + len; bool first = true; - bool component_license = false; - bool scanoss_license = false; - int file_header_filter = 0; - int scancode_file_filter = 0; + int last_id = -1; /* Sort licenses by id (ascending) */ if (licenses_by_type.count > 1) qsort(licenses_by_type.licenses, licenses_by_type.count, sizeof(struct license_type), license_compare_by_id); for (int i = 0; i < licenses_by_type.count; i++) { - //file header license and scancode_file liceses are limited to a maximum of 3. - if (licenses_by_type.licenses[i].id == 2) - { - file_header_filter++; - if (file_header_filter >=3 && !full_license_report) - continue; - } - - if (licenses_by_type.licenses[i].id == 4) - { - scancode_file_filter++; - if (scancode_file_filter >=3 && !full_license_report) - continue; - } - - if (licenses_by_type.licenses[i].id == 5 && scanoss_license && !full_license_report) - continue; - buffer = license_to_json(crclist, buffer, licenses_by_type.licenses[i].text, licenses_by_type.licenses[i].id, &first); - //just report component license if available - if (licenses_by_type.licenses[i].id == 0 && !first) - component_license = true; - - else if (licenses_by_type.licenses[i].id > 0 && !first) - scanoss_license = true; - - if (i > 0 && component_license && !full_license_report) + if (last_id >= 0 && last_id != licenses_by_type.licenses[i].id && !first && !full_license_report) break; + last_id = licenses_by_type.licenses[i].id; } len = buffer - result;