File Coverage

blib/lib/SBOM/CycloneDX/License.pm
Criterion Covered Total %
statement 70 84 83.3
branch 19 36 52.7
condition 8 14 57.1
subroutine 19 20 95.0
pod 1 2 50.0
total 117 156 75.0


line stmt bran cond sub pod time code
1             package SBOM::CycloneDX::License;
2              
3 16     16   349 use 5.010001;
  16         73  
4 16     16   105 use strict;
  16         39  
  16         468  
5 16     16   75 use warnings;
  16         35  
  16         1041  
6 16     16   128 use utf8;
  16         35  
  16         154  
7              
8 16     16   669 use SBOM::CycloneDX::BomRef;
  16         34  
  16         615  
9 16     16   84 use SBOM::CycloneDX::Enum;
  16         38  
  16         1056  
10 16     16   121 use SBOM::CycloneDX::List;
  16         33  
  16         512  
11 16     16   9051 use SBOM::CycloneDX::License::Licensing;
  16         80  
  16         738  
12              
13 16     16   421 use Carp;
  16         36  
  16         1707  
14 16     16   108 use List::Util qw(first);
  16         38  
  16         1128  
15 16     16   96 use Types::Standard qw(Str Enum InstanceOf);
  16         2461  
  16         675  
16 16     16   53655 use Types::TypeTiny qw(ArrayLike);
  16         39  
  16         78  
17              
18 16     16   8488 use Moo;
  16         34  
  16         92  
19 16     16   6930 use namespace::autoclean;
  16         36  
  16         125  
20              
21 16   50 16   1485 use constant DEBUG => $ENV{SBOM_DEBUG} || 0;
  16         50  
  16         19900  
22              
23             # TODO Incomplete
24              
25             around BUILDARGS => sub {
26              
27             my ($orig, $class, @args) = @_;
28              
29             if (@args == 1 && defined $args[0] && !ref $args[0]) {
30              
31             my $license = $args[0];
32             $license =~ s/^\s+|\s+$//g;
33              
34             if ($license =~ /(\bWITH\b|\bOR\b|\bAND\b)/i) {
35             return {expression => $license};
36             }
37             else {
38             return {id => $license};
39             }
40             }
41              
42             return $class->$orig(@args);
43              
44             };
45              
46             extends 'SBOM::CycloneDX::Base';
47              
48             sub BUILD {
49 22     22 0 386 my ($self, $args) = @_;
50 22 50 66     454 Carp::croak('"id" and "name" cannot be used at the same time') if exists $args->{id} && exists $args->{name};
51             }
52              
53             has bom_ref => (
54             is => 'rw',
55             isa => InstanceOf ['SBOM::CycloneDX::BomRef'],
56             coerce => sub { ref($_[0]) ? $_[0] : SBOM::CycloneDX::BomRef->new($_[0]) }
57             );
58              
59             has id => (is => 'rw', isa => Str, trigger => 1);
60             has name => (is => 'rw', isa => Str);
61             has acknowledgement => (is => 'rw', isa => Enum [qw(declared concluded)]);
62             has text => (is => 'rw', isa => InstanceOf ['SBOM::CycloneDX::Attachment']);
63             has url => (is => 'rw', isa => Str, trigger => 1);
64             has expression => (is => 'rw', isa => Str); # TODO check SPDX expression
65              
66             has expression_details => (
67             is => 'rw',
68             isa => ArrayLike [InstanceOf ['SBOM::CycloneDX::License::ExpressionDetail']],
69             default => sub { SBOM::CycloneDX::List->new }
70             );
71              
72             has licensing => (
73             is => 'rw',
74             isa => InstanceOf ['SBOM::CycloneDX::License::Licensing'],
75             default => sub { SBOM::CycloneDX::License::Licensing->new }
76             );
77              
78             has properties => (
79             is => 'rw',
80             isa => ArrayLike [InstanceOf ['SBOM::CycloneDX::Property']],
81             default => sub { SBOM::CycloneDX::List->new }
82             );
83              
84              
85             sub _trigger_id {
86              
87 18     18   784 my ($self) = @_;
88              
89 18 50 33     347 if ($self->id && $self->id =~ /(\bWITH\b|\bOR\b|\bAND\b)/i) {
90 0         0 DEBUG and say STDERR '-- Detected SPDX expression';
91 0         0 $self->expression($self->id);
92 0         0 $self->{id} = undef;
93             }
94              
95 18 50 33     791 if ($self->id && $self->id =~ /^(NOASSERTION|NONE)$/) {
96 0         0 DEBUG and say STDERR "-- Detected $1 license identifier (unset license identifier)";
97 0         0 $self->{id} = undef;
98             }
99              
100             }
101              
102              
103             sub _trigger_url {
104 0     0   0 my ($self) = @_;
105 0 0       0 $self->url('https://opensource.org/license/' . $self->id) if $self->url eq '1';
106             }
107              
108             sub TO_JSON {
109              
110 301     301 1 1018 my $self = shift;
111              
112 301         509 my $json = {};
113              
114 301 50       6641 if ($self->expression) {
115 0         0 $json->{expression} = $self->expression;
116 0 0       0 $json->{expressionDetails} = $self->expression_details if @{$self->expression_details};
  0         0  
117 0 0       0 $json->{acknowledgement} = $self->acknowledgement if $self->acknowledgement;
118 0 0       0 $json->{'bom-ref'} = $self->bom_ref if $self->bom_ref;
119 0 0       0 $json->{licensing} = $self->licensing if %{$self->licensing->TO_JSON};
  0         0  
120             }
121             else {
122              
123 301         6913 my $spdx_license = $self->id;
124 301         6164 my $license_name = $self->name;
125              
126 301 100 100 118609   2895 if (defined $spdx_license and not first { $_ eq $spdx_license } @{SBOM::CycloneDX::Enum->SPDX_LICENSES()}) {
  118609         136249  
  247         3284  
127 148         238 DEBUG and say STDERR "-- SPDX license not found ($spdx_license)";
128             }
129              
130 301         1720 $json->{license} = {};
131              
132 301 100       1010 $json->{license}->{id} = $spdx_license if $spdx_license;
133 301 100       652 $json->{license}->{name} = $license_name if $license_name;
134              
135 301 100       7619 $json->{license}->{'bom-ref'} = $self->bom_ref if $self->bom_ref;
136 301 100       7209 $json->{license}->{acknowledgement} = $self->acknowledgement if $self->acknowledgement;
137 301 100       6719 $json->{license}->{text} = $self->text if $self->text;
138 301 50       6227 $json->{license}->{url} = $self->url if $self->url;
139 301 50       1707 $json->{license}->{properties} = $self->properties if @{$self->properties};
  301         5246  
140 301 50       528 $json->{license}->{licensing} = $self->licensing if %{$self->licensing->TO_JSON};
  301         5341  
141              
142             }
143              
144 301         5363 return $json;
145              
146             }
147              
148             1;
149              
150             =encoding utf-8
151              
152             =head1 NAME
153              
154             SBOM::CycloneDX::License - Specifies the details and attributes related to a software license
155              
156             =head1 SYNOPSIS
157              
158             # SPDX license
159              
160             $license = SBOM::CycloneDX::License->new(
161             id => 'Apache-2.0'
162             );
163              
164             # or
165              
166             $license = SBOM::CycloneDX::License->new('MIT');
167              
168             # SPDX license expression
169              
170             $license = SBOM::CycloneDX::License->new(
171             expression => 'MIT AND (LGPL-2.1-or-later OR BSD-3-Clause)'
172             );
173              
174             # or
175              
176             $license = SBOM::CycloneDX::License->new('MIT AND (LGPL-2.1-or-later OR BSD-3-Clause)');
177              
178             # Non-SPDX license
179              
180             $license = SBOM::CycloneDX::License->new(
181             name => 'Acme Software License'
182             );
183              
184              
185             =head1 DESCRIPTION
186              
187             L specifies the details and attributes related to a software
188             license.
189              
190             It can either include a valid SPDX license identifier or a named license,
191             along with additional properties such as license acknowledgment, comprehensive
192             commercial licensing information, and the full text of the license.
193              
194             =head2 METHODS
195              
196             L inherits all methods from L
197             and implements the following new ones.
198              
199             =over
200              
201             =item SBOM::CycloneDX::License->new( $id | $expression | %PARAMS )
202              
203             Properties:
204              
205             =over
206              
207             =item * C, An identifier which can be used to reference the license
208             elsewhere in the BOM. Every bom-ref must be unique within the BOM.
209              
210             Value SHOULD not start with the BOM-Link intro C to avoid conflicts with BOM-Links.
211              
212             =item * C, A valid SPDX license identifier. If specified, this value must be
213             one of the enumeration of valid SPDX license identifiers defined in L C.
214              
215             =item * C, A tuple of exactly one SPDX License Expression.
216              
217             Refer to L for syntax requirements.
218              
219             =item * C, Details for parts of the C.
220              
221             See L
222              
223             =item * C, The name of the license. This may include the name of a commercial
224             or proprietary license or an open source license that may not be defined by SPDX.
225              
226             =item * C,
227              
228             =item * C, An way to include the textual content of a license.
229             See L
230              
231             =item * C, The URL to the license file. If C<1> is provided, the license URL is
232             automatically generated.
233              
234             =item * C, Declared licenses and concluded licenses represent two different
235             stages in the licensing process within software development. Declared licenses refer
236             to the initial intention of the software authors regarding the licensing terms
237             under which their code is released. On the other hand, concluded licenses are the
238             result of a comprehensive analysis of the project's codebase to identify and confirm
239             the actual licenses of the components used, which may differ from the initially
240             declared licenses. While declared licenses provide an upfront indication of the
241             licensing intentions, concluded licenses offer a more thorough understanding of
242             the actual licensing within a project, facilitating proper compliance and risk
243             management. Observed licenses are defined in C<$bom->evidence->licenses>. Observed
244             licenses form the evidence necessary to substantiate a concluded license.
245              
246             See L
247              
248              
249             =item * C, Provides the ability to document properties in a name-value
250             store. This provides flexibility to include data not officially supported in the
251             standard without having to use additional namespaces or create extensions.
252             Unlike key-value stores, properties support duplicate names, each potentially
253             having different values. Property names of interest to the general public are
254             encouraged to be registered in the CycloneDX Property Taxonomy. Formal
255             registration is optional. See L
256              
257             =back
258              
259             =item $license->bom_ref
260              
261             =item $license->id
262              
263             # SPDX license
264              
265             $license = SBOM::CycloneDX::License->new(
266             id => 'Apache-2.0'
267             );
268              
269             # or
270              
271             $license = SBOM::CycloneDX::License->new('MIT');
272              
273             =item $license->name
274              
275             =item $license->acknowledgement
276              
277             =item $license->text
278              
279             $license->text(SBOM::CycloneDX::Attachment(file => '/path/LICENSE.md'));
280              
281             =item $license->url
282              
283             =item $license->expression
284              
285             # SPDX license expression
286              
287             $license = SBOM::CycloneDX::License->new(
288             expression => 'MIT AND (LGPL-2.1-or-later OR BSD-3-Clause)'
289             );
290              
291             # or
292              
293             $license = SBOM::CycloneDX::License->new('MIT AND (LGPL-2.1-or-later OR BSD-3-Clause)');
294              
295             =item $license->expression_details
296              
297             =item $license->licensing
298              
299             $license->licensing->alt_ids(['acme', 'acme-license']);
300              
301              
302             $licensing = SBOM::CycloneDX::License::Licensing->new(
303             alt_ids => ['acme', 'acme-license'],
304             purchase_order => 'PO-12345',
305             license_types => ['appliance'],
306             );
307              
308             $license->licensing($licensing);
309              
310             =item $license->properties
311              
312             $license->properties->add(SBOM::CycloneDX::Property->new(name => 'foo', value => 'bar'));
313              
314             =back
315              
316             =head1 SUPPORT
317              
318             =head2 Bugs / Feature Requests
319              
320             Please report any bugs or feature requests through the issue tracker
321             at L.
322             You will be notified automatically of any progress on your issue.
323              
324             =head2 Source Code
325              
326             This is open source software. The code repository is available for
327             public review and contribution under the terms of the license.
328              
329             L
330              
331             git clone https://github.com/giterlizzi/perl-SBOM-CycloneDX.git
332              
333              
334             =head1 AUTHOR
335              
336             =over 4
337              
338             =item * Giuseppe Di Terlizzi
339              
340             =back
341              
342              
343             =head1 LICENSE AND COPYRIGHT
344              
345             This software is copyright (c) 2025-2026 by Giuseppe Di Terlizzi.
346              
347             This is free software; you can redistribute it and/or modify it under
348             the same terms as the Perl 5 programming language system itself.
349              
350             =cut