File Coverage

blib/lib/STIX/Parser.pm
Criterion Covered Total %
statement 108 112 96.4
branch 38 44 86.3
condition 8 11 72.7
subroutine 13 13 100.0
pod 1 1 100.0
total 168 181 92.8


line stmt bran cond sub pod time code
1             package STIX::Parser;
2              
3 2     2   21245 use 5.010001;
  2         9  
4 2     2   13 use strict;
  2         4  
  2         90  
5 2     2   44 use warnings;
  2         4  
  2         130  
6 2     2   1158 use utf8;
  2         655  
  2         44  
7              
8 2     2   1308 use Cpanel::JSON::XS;
  2         8319  
  2         148  
9 2     2   1007 use STIX::Common::Hashes;
  2         10  
  2         122  
10 2     2   1429 use STIX::Common::Identifier;
  2         10  
  2         122  
11 2     2   1736 use STIX::Util qw(file_read);
  2         9  
  2         156  
12 2     2   1204 use STIX;
  2         10  
  2         217  
13              
14 2     2   22 use Moo;
  2         5  
  2         14  
15              
16 2   50 2   1055 use constant DEBUG => $ENV{STIX_DEBUG} || 0;
  2         5  
  2         5147  
17              
18             my %OBJECTS_MAPPING = (
19              
20             'bundle' => sub { STIX::bundle(@_) },
21              
22             'external-reference' => sub { STIX::external_reference(@_) },
23             'granular-marking' => sub { STIX::granular_marking(@_) },
24             'kill-chain-phase' => sub { STIX::kill_chain_phase(@_) },
25             'marking-definition' => sub { STIX::marking_definition(@_) },
26              
27             'attack-pattern' => sub { STIX::attack_pattern(@_) },
28             'campaign' => sub { STIX::campaign(@_) },
29             'course-of-action' => sub { STIX::course_of_action(@_) },
30             'grouping' => sub { STIX::grouping(@_) },
31             'identity' => sub { STIX::identity(@_) },
32             'incident' => sub { STIX::incident(@_) },
33             'indicator' => sub { STIX::indicator(@_) },
34             'infrastructure' => sub { STIX::infrastructure(@_) },
35             'intrusion-set' => sub { STIX::intrusion_set(@_) },
36             'location' => sub { STIX::location(@_) },
37             'malware' => sub { STIX::malware(@_) },
38             'malware-analysis' => sub { STIX::malware_analysis(@_) },
39             'note' => sub { STIX::note(@_) },
40             'observed-data' => sub { STIX::observed_data(@_) },
41             'opinion' => sub { STIX::opinion(@_) },
42             'report' => sub { STIX::report(@_) },
43             'threat-actor' => sub { STIX::threat_actor(@_) },
44             'tool' => sub { STIX::tool(@_) },
45             'vulnerability' => sub { STIX::vulnerability(@_) },
46              
47             'relationship' => sub { STIX::relationship(@_) },
48             'sighting' => sub { STIX::sighting(@_) },
49              
50             'artifact' => sub { STIX::artifact(@_) },
51             'autonomous-system' => sub { STIX::autonomous_system(@_) },
52             'directory' => sub { STIX::directory(@_) },
53             'domain-name' => sub { STIX::domain_name(@_) },
54             'email-addr' => sub { STIX::email_addr(@_) },
55             'email-message' => sub { STIX::email_message(@_) },
56             'file' => sub { STIX::file(@_) },
57             'ipv4-addr' => sub { STIX::ipv4_addr(@_) },
58             'ipv6-addr' => sub { STIX::ipv6_addr(@_) },
59             'mac-addr' => sub { STIX::mac_addr(@_) },
60             'mutex' => sub { STIX::mutex(@_) },
61             'network-traffic' => sub { STIX::network_traffic(@_) },
62             'process' => sub { STIX::process(@_) },
63             'software' => sub { STIX::software(@_) },
64             'url' => sub { STIX::url(@_) },
65             'user-account' => sub { STIX::user_account(@_) },
66             'windows-registry-key' => sub { STIX::windows_registry_key(@_) },
67             'x509-certificate' => sub { STIX::x509_certificate(@_) },
68              
69             );
70              
71             my %EXTENSIONS_MAPPING = (
72             'archive-ext' => sub { STIX::archive_ext(@_) },
73             'http-request-ext' => sub { STIX::http_request_ext(@_) },
74             'icmp-ext' => sub { STIX::icmp_ext(@_) },
75             'ntfs-ext' => sub { STIX::ntfs_ext(@_) },
76             'pdf-ext' => sub { STIX::pdf_ext(@_) },
77             'raster-image-ext' => sub { STIX::raster_image_ext(@_) },
78             'socket-ext' => sub { STIX::socket_ext(@_) },
79             'tcp-ext' => sub { STIX::tcp_ext(@_) },
80             'unix-account-ext' => sub { STIX::unix_account_ext(@_) },
81             'windows-process-ext' => sub { STIX::windows_process_ext(@_) },
82             'windows-service-ext' => sub { STIX::windows_service_ext(@_) },
83             );
84              
85             has file => (is => 'ro');
86             has content => (is => 'ro');
87              
88              
89             sub parse {
90              
91 133     133 1 1470275 my $self = shift;
92              
93 133 50 66     1439 if ($self->content || $self->file) {
94              
95 133         501 my $content = $self->content;
96              
97 133 100       673 if ($self->file) {
98 8 50       238 Carp::croak sprintf('File "%s" not found', $self->file) unless (-e $self->file);
99 8         109 $content = file_read($self->file);
100             }
101              
102 133 50       463 Carp::croak q{Empty 'content'} unless $content;
103              
104 133         3429 my $parsed = Cpanel::JSON::XS->new->filter_json_object(\&_filter_json_object)->decode($content);
105              
106 133 50       1214 Carp::croak "Failed to parse the STIX file: $@" if ($@);
107              
108 133         645 return $parsed;
109              
110             }
111              
112             }
113              
114             sub _filter_json_object {
115              
116 502     502   11847 my $custom_properties = {};
117              
118 502         953 foreach my $property (keys %{$_[0]}) {
  502         2511  
119              
120             #DEBUG and say STDERR "-- PROPERTY $property";
121              
122 3437         6549 my $value = $_[0]->{$property};
123              
124 3437 100       7159 if (ref($value) eq 'JSON::PP::Boolean') {
125 49 100       448 $value = !!1 if $value;
126 49 100       691 $value = !!0 if !$value;
127             }
128              
129 3437 100       8436 if ($property =~ /_ref$/) {
130 347         9060 $value = STIX::Common::Identifier->new($value);
131             }
132              
133 3437 100       9592 if ($property =~ /_refs$/) {
134              
135 22         80 my @refs = ();
136              
137 22         64 foreach my $ref (@{$value}) {
  22         92  
138 259         7777 push @refs, STIX::Common::Identifier->new($ref);
139             }
140              
141 22         272 $value = \@refs;
142             }
143              
144 3437 100       6748 if ($property eq 'hashes') {
145 15         28 $value = STIX::Common::Hashes->new(%{$value});
  15         582  
146             }
147              
148 3437 100       7237 if ($property eq 'body_multipart') {
149              
150 1         4 my @multiparts = ();
151              
152 1         3 foreach my $multipart (@{$value}) {
  1         6  
153 3         4355 push @multiparts, STIX::email_mime_part_type($multipart);
154             }
155              
156 1         127 $value = \@multiparts;
157             }
158              
159 3437 100       6170 if ($property eq 'x509_v3_extensions') {
160 1         3 $value = STIX::x509_v3_extensions_type(%{$value});
  1         14  
161             }
162              
163 3437 100       6601 if ($property eq 'kill_chain_phases') {
164              
165              
166 26         62 my @multiparts = ();
167              
168 26         41 foreach my $multipart (@{$value}) {
  26         50  
169 26         74 push @multiparts, STIX::kill_chain_phase($multipart);
170             }
171              
172 26         3200 $value = \@multiparts;
173              
174             }
175              
176 3437 100       6518 if ($property eq 'alternate_data_streams') {
177              
178 1         4 my @streams = ();
179              
180 1         3 foreach my $stream (@{$value}) {
  1         2  
181 1         6 push @streams, STIX::alternate_data_stream_type($stream);
182             }
183              
184 1         2410 $value = \@streams;
185              
186             }
187              
188 3437 100       5802 if ($property eq 'extensions') {
189              
190 12         49 my $extensions = {};
191              
192 12         31 foreach my $extension (keys %{$value}) {
  12         52  
193              
194 12         33 my $data = $value->{$extension};
195              
196 12 100       59 if (defined $EXTENSIONS_MAPPING{$extension}) {
197 11         24 $data = $EXTENSIONS_MAPPING{$extension}->(%{$value->{$extension}});
  11         96  
198             }
199              
200 12         33536 $extensions->{$extension} = $data;
201             }
202              
203 12         36 $value = $extensions;
204              
205             }
206              
207 3437 50       6577 if ($property =~ /^x_/) {
208 0         0 delete $_[0]->{$property};
209 0         0 $custom_properties->{$property} = $value;
210             }
211             else {
212 3437         10321 $_[0]->{$property} = $value;
213             }
214              
215             }
216              
217 502 100       1963 if ($_[0]->{source_name}) {
218 23         120 return STIX::external_reference(@_);
219             }
220              
221 479 50       828 if (%{$custom_properties}) {
  479         1298  
222 0         0 DEBUG and say '-- Append "custom_properties"';
223 0         0 $_[0]->{custom_properties} = $custom_properties;
224             }
225              
226 479 100 66     2937 if (defined $_[0]->{type} && defined $OBJECTS_MAPPING{$_[0]->{type}}) {
227              
228 397 100 100     1310 if ($_[0]->{type} eq 'windows-registry-key' && defined $_[0]->{values}) {
229              
230 1         4 my @values = ();
231              
232 1         4 foreach my $value (@{$_[0]->{values}}) {
  1         4  
233 2         5145 push @values, STIX::windows_registry_value_type($value);
234             }
235              
236 1         94 $_[0]->{values} = \@values;
237              
238             }
239              
240 397         1389 my $object = $OBJECTS_MAPPING{$_[0]->{type}}->(@_);
241              
242 397         45610 DEBUG and say sprintf('-- MAPPED "type = %s" => %s (%s)', $_[0]->{type}, ref($object), $object->id);
243              
244 397         6585 return $object;
245              
246             }
247              
248 82         652 return @_;
249              
250             }
251              
252              
253             1;
254              
255             =encoding utf-8
256              
257             =head1 NAME
258              
259             STIX::Parser - Parse a STIX JSON
260              
261             =head1 SYNOPSIS
262              
263             use STIX::Parser;
264              
265             # Parse a local JSON file
266             my $parser = STIX::Parser->new( file => './infrastructure.json');
267              
268             # Parse a JSON string
269             my $parser = STIX::Parser->new( content => $json );
270              
271             my $stix = $parser->parse;
272              
273             say $stix->type;
274              
275              
276             =head1 DESCRIPTION
277              
278             Parse a STIX JSON.
279              
280             =head2 METHODS
281              
282             =over
283              
284             =item STIX::Parser->new( [ file => $path | content => $string] )
285              
286             Create a new instance of L.
287              
288             =item $parser->parse
289              
290             Parse the provided JSON file (or string) and return a STIX object.
291              
292             =back
293              
294              
295             =head1 SUPPORT
296              
297             =head2 Bugs / Feature Requests
298              
299             Please report any bugs or feature requests through the issue tracker
300             at L.
301             You will be notified automatically of any progress on your issue.
302              
303             =head2 Source Code
304              
305             This is open source software. The code repository is available for
306             public review and contribution under the terms of the license.
307              
308             L
309              
310             git clone https://github.com/giterlizzi/perl-STIX.git
311              
312              
313             =head1 AUTHOR
314              
315             =over 4
316              
317             =item * Giuseppe Di Terlizzi
318              
319             =back
320              
321              
322             =head1 LICENSE AND COPYRIGHT
323              
324             This software is copyright (c) 2024 by Giuseppe Di Terlizzi.
325              
326             This is free software; you can redistribute it and/or modify it under
327             the same terms as the Perl 5 programming language system itself.
328              
329             =cut