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   343 use 5.010001;
  16         54  
4 16     16   74 use strict;
  16         27  
  16         309  
5 16     16   51 use warnings;
  16         29  
  16         772  
6 16     16   97 use utf8;
  16         59  
  16         118  
7              
8 16     16   483 use SBOM::CycloneDX::BomRef;
  16         26  
  16         470  
9 16     16   58 use SBOM::CycloneDX::Enum;
  16         27  
  16         803  
10 16     16   76 use SBOM::CycloneDX::List;
  16         25  
  16         309  
11 16     16   6897 use SBOM::CycloneDX::License::Licensing;
  16         66  
  16         617  
12              
13 16     16   317 use Carp;
  16         30  
  16         1296  
14 16     16   319 use List::Util qw(first);
  16         32  
  16         956  
15 16     16   70 use Types::Standard qw(Str Enum InstanceOf);
  16         1457  
  16         723  
16 16     16   39510 use Types::TypeTiny qw(ArrayLike);
  16         29  
  16         102  
17              
18 16     16   6494 use Moo;
  16         160  
  16         93  
19 16     16   5432 use namespace::autoclean;
  16         24  
  16         102  
20              
21 16   50 16   1299 use constant DEBUG => $ENV{SBOM_DEBUG} || 0;
  16         28  
  16         15955  
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 303 my ($self, $args) = @_;
50 22 50 66     341 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   624 my ($self) = @_;
88              
89 18 50 33     259 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     661 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 53     53 1 80 my $self = shift;
111              
112 53         74 my $json = {};
113              
114 53 50       681 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 53         840 my $spdx_license = $self->id;
124 53         779 my $license_name = $self->name;
125              
126 53 100 100 18619   404 if (defined $spdx_license and not first { $_ eq $spdx_license } @{SBOM::CycloneDX::Enum->SPDX_LICENSES()}) {
  18619         16099  
  43         688  
127 22         28 DEBUG and say STDERR "-- SPDX license not found ($spdx_license)";
128             }
129              
130 53         205 $json->{license} = {};
131              
132 53 100       130 $json->{license}->{id} = $spdx_license if $spdx_license;
133 53 100       98 $json->{license}->{name} = $license_name if $license_name;
134              
135 53 100       753 $json->{license}->{'bom-ref'} = $self->bom_ref if $self->bom_ref;
136 53 100       816 $json->{license}->{acknowledgement} = $self->acknowledgement if $self->acknowledgement;
137 53 100       890 $json->{license}->{text} = $self->text if $self->text;
138 53 50       766 $json->{license}->{url} = $self->url if $self->url;
139 53 50       230 $json->{license}->{properties} = $self->properties if @{$self->properties};
  53         592  
140 53 50       84 $json->{license}->{licensing} = $self->licensing if %{$self->licensing->TO_JSON};
  53         600  
141              
142             }
143              
144 53         950 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