1Parser(3) User Contributed Perl Documentation Parser(3)
2
3
4
6 Nmap::Parser - parse nmap scan data with perl
7
9 use Nmap::Parser;
10 my $np = new Nmap::Parser;
11
12 $np->parsescan($nmap_path, $nmap_args, @ips);
13 #or
14 $np->parsefile($file_xml);
15
16 my $session = $np->get_session();
17 #a Nmap::Parser::Session object
18
19 my $host = $np->get_host($ip_addr);
20 #a Nmap::Parser::Host object
21
22 my $service = $host->tcp_service(80);
23 #a Nmap::Parser::Host::Service object
24
25 my $os = $host->os_sig();
26 #a Nmap::Parser::Host::OS object
27
28 #---------------------------------------
29
30 my $np2 = new Nmap::Parser;
31
32 $np2->callback(\&my_callback);
33
34 $np2->parsefile($file_xml);
35 #or
36 $np2->parsescan($nmap_path, $nmap_args, @ips);
37
38 sub my_callback {
39
40 my $host = shift;
41 #Nmap::Parser::Host object
42 #.. see documentation for all methods ...
43
44 }
45
46 For a full listing of methods see the documentation corresponding to
47 each object. You can also visit the website
48 <https://github.com/modernistik/Nmap-Parser> for additional
49 installation instructions.
50
52 This module implements a interface to the information contained in an
53 nmap scan. It is implemented by parsing the xml scan data that is
54 generated by nmap. This will enable anyone who utilizes nmap to quickly
55 create fast and robust security scripts that utilize the powerful port
56 scanning abilities of nmap.
57
58 The latest version of this module can be found on here
59 <https://github.com/modernistik/Nmap-Parser>
60
62 This module has an internal framework to make it easy to retrieve the
63 desired information of a scan. Every nmap scan is based on two main
64 sections of informations: the scan session, and the scan information of
65 all hosts. The session information will be stored as a
66 Nmap::Parser::Session object. This object will contain its own methods
67 to obtain the desired information. The same is true for any hosts that
68 were scanned using the Nmap::Parser::Host object. There are two sub
69 objects under Nmap::Parser::Host. One is the
70 Nmap::Parser::Host::Service object which will be used to obtain
71 information of a given service running on a given port. The second is
72 the Nmap::Parser::Host::OS object which contains the operating system
73 signature information (OS guessed names, classes, osfamily..etc).
74
75 Nmap::Parser -- Core parser
76 |
77 +--Nmap::Parser::Session -- Nmap scan session information
78 |
79 +--Nmap::Parser::Host -- General host information
80 | |
81 | |-Nmap::Parser::Host::Service -- Port service information
82 | |
83 | |-Nmap::Parser::Host::OS -- Operating system signature information
84
86 Nmap::Parser
87 The main idea behind the core module is, you will first parse the
88 information and then extract data. Therefore, all parse*() methods
89 should be executed before any get_*() methods.
90
91 parse($string)
92 parse($filehandle)
93 Parses the nmap scan information in $string. Note that is usually
94 only used if you have the whole xml scan information in $string or
95 if you are piping the scan information.
96
97 parsefile($xml_file)
98 Parses the nmap scan data in $xml_file. This file can be generated
99 from an nmap scan by using the '-oX filename.xml' option with nmap.
100 If you get an error or your program dies due to parsing, please
101 check that the xml information is compliant. The file is closed no
102 matter how "parsefile()" returns.
103
104 parsescan($nmap,$args,@ips)
105 This method runs an nmap scan where $nmap is the path to the nmap
106 executable or binary, $args are the nmap command line parameters,
107 and @ips are the list of IP addresses to scan. parsescan() will
108 automagically run the nmap scan and parse the information.
109
110 If you wish to save the xml output from parsescan(), you must call
111 cache_scan() method BEFORE you start the parsescan() process. This
112 is done to conserve memory while parsing. cache_scan() will let
113 Nmap::Parser know to save the output before parsing the xml since
114 Nmap::Parser purges everything that has been parsed by the script
115 to conserve memory and increase speed.
116
117 See section EXAMPLES for a short tutorial
118
119 Note: You cannot have one of the nmap options to be '-oX', '-oN' or
120 '-oG'. Your program will die if you try and pass any of these
121 options because it decides the type of output nmap will generate.
122 The IP addresses can be nmap-formatted addresses see nmap(1)
123
124 If you get an error or your program dies due to parsing, please
125 check that the xml information is compliant. If you are using
126 parsescan() or an open filehandle , make sure that the nmap scan
127 that you are performing is successful in returning xml information.
128 (Sometimes using loopback addresses causes nmap to fail).
129
130 cache_scan($filename)
131 This function allows you to save the output of a parsescan() (or
132 nmap scan) to the disk. $filename is the name of the file you wish
133 to save the nmap scan information to. It defaults to
134 nmap-parser-cache.xml It returns the name of the file to be used as
135 the cache.
136
137 #Must be called before parsescan().
138 $np->cache_scan($filename); #output set to nmap-parser-cache.xml
139
140 #.. do other stuff to prepare for parsescan(), ex. setup callbacks
141
142 $np->parsescan('/usr/bin/nmap',$args,@IPS);
143
144 purge()
145 Cleans the xml scan data from memory. This is useful if you have a
146 program where you are parsing lots of nmap scan data files with
147 persistent variables.
148
149 callback(\&code_ref)
150 Sets the parsing mode to be done using the callback function. It
151 takes the parameter of a code reference or a reference to a
152 function. If no code reference is given, it resets the mode to
153 normal (no callback).
154
155 $np->callback(\&my_function); #sets callback, my_function() will be called
156 $np->callback(); #resets it, no callback function called. Back to normal.
157
158 get_session()
159 Obtains the Nmap::Parser::Session object which contains the session
160 scan information.
161
162 get_host($ip_addr)
163 Obtains the Nmap::Parser::Host object for the given $ip_addr.
164
165 del_host($ip_addr)
166 Deletes the stored Nmap::Parser::Host object whose IP is $ip_addr.
167
168 all_hosts()
169 all_hosts($status)
170 Returns an array of all the Nmap::Parser::Host objects for the
171 scan. If the optional status is given, it will only return those
172 hosts that match that status. The status can be any of the
173 following: "(up|down|unknown|skipped)"
174
175 get_ips()
176 get_ips($status)
177 Returns the list of IP addresses that were scanned in this nmap
178 session. They are sorted using addr_sort. If the optional status is
179 given, it will only return those IP addresses that match that
180 status. The status can be any of the following:
181 "(up|down|unknown|skipped)"
182
183 addr_sort(@ips)
184 This function takes a list of IP addresses and returns the
185 correctly sorted version of the list.
186
187 Nmap::Parser::Session
188 This object contains the scan session information of the nmap scan.
189
190 finish_time()
191 Returns the numeric time that the nmap scan finished.
192
193 nmap_version()
194 Returns the version of nmap used for the scan.
195
196 numservices()
197 numservices($type)
198 If numservices is called without argument, it returns the total
199 number of services that were scanned for all types. If $type is
200 given, it returns the number of services for that given scan type.
201 See scan_types() for more info.
202
203 scan_args()
204 Returns a string which contains the nmap executed command line used
205 to run the scan.
206
207 scan_type_proto($type)
208 Returns the protocol type of the given scan type (provided by
209 $type). See scan_types() for more info.
210
211 scan_types()
212 Returns the list of scan types that were performed. It can be any
213 of the following:
214 "(syn|ack|bounce|connect|null|xmas|window|maimon|fin|udp|ipproto)".
215
216 start_str()
217 Returns the human readable format of the start time.
218
219 start_time()
220 Returns the numeric form of the time the nmap scan started.
221
222 time_str()
223 Returns the human readable format of the finish time.
224
225 xml_version()
226 Returns the version of nmap xml file.
227
228 prescripts()
229 prescripts($name)
230 A basic call to prescripts() returns a list of the names of the NSE
231 scripts run in the pre-scanning phase. If $name is given, it
232 returns the text output of the a reference to a hash with "output"
233 and "contents" keys for the script with that name, or undef if that
234 script was not run. The value of the "output" key is the text
235 output of the script. The value of the "contents" key is a data
236 structure based on the XML output of the NSE script.
237
238 postscripts()
239 postscripts($name)
240 A basic call to postscripts() returns a list of the names of the
241 NSE scripts run in the post-scaning phase. If $name is given, it
242 returns the text output of the a reference to a hash with "output"
243 and "contents" keys for the script with that name, or undef if that
244 script was not run. The value of the "output" key is the text
245 output of the script. The value of the "contents" key is a data
246 structure based on the XML output of the NSE script.
247
248 Nmap::Parser::Host
249 This object represents the information collected from a scanned host.
250
251 status()
252 Returns the state of the host. It is usually one of these
253 "(up|down|unknown|skipped)".
254
255 addr()
256 Returns the main IP address of the host. This is usually the IPv4
257 address. If there is no IPv4 address, the IPv6 is returned
258 (hopefully there is one).
259
260 addrtype()
261 Returns the address type of the address given by addr() .
262
263 all_hostnames()
264 Returns a list of all hostnames found for the given host.
265
266 extraports_count()
267 Returns the number of extraports found.
268
269 extraports_state()
270 Returns the state of all the extraports found.
271
272 hostname()
273 hostname($index)
274 As a basic call, hostname() returns the first hostname obtained for
275 the given host. If there exists more than one hostname, you can
276 provide a number, which is used as the location in the array. The
277 index starts at 0;
278
279 #in the case that there are only 2 hostnames
280 hostname() eq hostname(0);
281 hostname(1); #second hostname found
282 hostname(400) eq hostname(1) #nothing at 400; return the name at the last index
283
284 ipv4_addr()
285 Explicitly return the IPv4 address.
286
287 ipv6_addr()
288 Explicitly return the IPv6 address.
289
290 mac_addr()
291 Explicitly return the MAC address.
292
293 mac_vendor()
294 Return the vendor information of the MAC.
295
296 distance()
297 Return the distance (in hops) of the target machine from the
298 machine that performed the scan.
299
300 trace_error()
301 Returns a true value (usually a meaningful error message) if the
302 traceroute was performed but could not reach the destination. In
303 this case "all_trace_hops()" contains only the part of the path
304 that could be determined.
305
306 all_trace_hops()
307 Returns an array of Nmap::Parser::Host::TraceHop objects
308 representing the path to the target host. This array may be empty
309 if Nmap did not perform the traceroute for some reason (same
310 network, for example).
311
312 Some hops may be missing if Nmap could not figure out information
313 about them. In this case there is a gap between the "ttl()" values
314 of consecutive returned hops. See also "trace_error()".
315
316 trace_proto()
317 Returns the name of the protocol used to perform the traceroute.
318
319 trace_port()
320 Returns the port used to perform the traceroute.
321
322 os_sig()
323 Returns an Nmap::Parser::Host::OS object that can be used to obtain
324 all the Operating System signature (fingerprint) information. See
325 Nmap::Parser::Host::OS for more details.
326
327 $os = $host->os_sig;
328 $os->name;
329 $os->osfamily;
330
331 tcpsequence_class()
332 tcpsequence_index()
333 tcpsequence_values()
334 Returns the class, index and values information respectively of the
335 tcp sequence.
336
337 ipidsequence_class()
338 ipidsequence_values()
339 Returns the class and values information respectively of the ipid
340 sequence.
341
342 tcptssequence_class()
343 tcptssequence_values()
344 Returns the class and values information respectively of the tcpts
345 sequence.
346
347 uptime_lastboot()
348 Returns the human readable format of the timestamp of when the host
349 had last rebooted.
350
351 uptime_seconds()
352 Returns the number of seconds that have passed since the host's
353 last boot from when the scan was performed.
354
355 hostscripts()
356 hostscripts($name)
357 A basic call to hostscripts() returns a list of the names of the
358 host scripts run. If $name is given, it returns the text output of
359 the a reference to a hash with "output" and "contents" keys for the
360 script with that name, or undef if that script was not run. The
361 value of the "output" key is the text output of the script. The
362 value of the "contents" key is a data structure based on the XML
363 output of the NSE script.
364
365 tcp_ports()
366 udp_ports()
367 Returns the sorted list of TCP|UDP ports respectively that were
368 scanned on this host. Optionally a string argument can be given to
369 these functions to filter the list.
370
371 $host->tcp_ports('open') #returns all only 'open' ports (even 'open|filtered')
372 $host->udp_ports('open|filtered'); #matches exactly ports with 'open|filtered'
373
374 Note that if a port state is set to 'open|filtered' (or any
375 combination), it will be counted as an 'open' port as well as a
376 'filtered' one.
377
378 tcp_port_count()
379 udp_port_count()
380 Returns the total of TCP|UDP ports scanned respectively.
381
382 tcp_port_state_ttl()
383 Returns the 'reason_ttl' value present in nmap xml result.
384
385 tcp_del_ports($portid, [$portid, ...])
386 udp_del_ports($portid, [ $portid, ...])
387 Deletes the current $portid from the list of ports for given
388 protocol.
389
390 tcp_port_state($portid)
391 udp_port_state($portid)
392 Returns the state of the given port, provided by the port number in
393 $portid.
394
395 tcp_open_ports()
396 udp_open_ports()
397 Returns the list of open TCP|UDP ports respectively. Note that if a
398 port state is for example, 'open|filtered', it will appear on this
399 list as well.
400
401 tcp_filtered_ports()
402 udp_filtered_ports()
403 Returns the list of filtered TCP|UDP ports respectively. Note that
404 if a port state is for example, 'open|filtered', it will appear on
405 this list as well.
406
407 tcp_closed_ports()
408 udp_closed_ports()
409 Returns the list of closed TCP|UDP ports respectively. Note that if
410 a port state is for example, 'closed|filtered', it will appear on
411 this list as well.
412
413 tcp_service($portid)
414 udp_service($portid)
415 Returns the Nmap::Parser::Host::Service object of a given service
416 running on port, provided by $portid. See
417 Nmap::Parser::Host::Service for more info.
418
419 $svc = $host->tcp_service(80);
420 $svc->name;
421 $svc->proto;
422
423 Nmap::Parser::Host::Service
424
425 This object represents the service running on a given port in a given
426 host. This object is obtained by using the tcp_service($portid) or
427 udp_service($portid) method from the Nmap::Parser::Host object. If a
428 portid is given that does not exist on the given host, these functions
429 will still return an object (so your script doesn't die). Its good to
430 use tcp_ports() or udp_ports() to see what ports were collected.
431
432 confidence()
433 Returns the confidence level in service detection.
434
435 extrainfo()
436 Returns any additional information nmap knows about the service.
437
438 method()
439 Returns the detection method.
440
441 name()
442 Returns the service name.
443
444 owner()
445 Returns the process owner of the given service. (If available)
446
447 port()
448 Returns the port number where the service is running on.
449
450 product()
451 Returns the product information of the service.
452
453 proto()
454 Returns the protocol type of the service.
455
456 rpcnum()
457 Returns the RPC number.
458
459 tunnel()
460 Returns the tunnel value. (If available)
461
462 fingerprint()
463 Returns the service fingerprint. (If available)
464
465 version()
466 Returns the version of the given product of the running service.
467
468 scripts()
469 scripts($name)
470 A basic call to scripts() returns a list of the names of the NSE
471 scripts run for this port. If $name is given, it returns a
472 reference to a hash with "output" and "contents" keys for the
473 script with that name, or undef if that script was not run. The
474 value of the "output" key is the text output of the script. The
475 value of the "contents" key is a data structure based on the XML
476 output of the NSE script.
477
478 Nmap::Parser::Host::OS
479
480 This object represents the Operating System signature (fingerprint)
481 information of the given host. This object is obtained from an
482 Nmap::Parser::Host object using the "os_sig()" method. One important
483 thing to note is that the order of OS names and classes are sorted by
484 DECREASING ACCURACY. This is more important than alphabetical ordering.
485 Therefore, a basic call to any of these functions will return the
486 record with the highest accuracy. (Which is probably the one you want
487 anyways).
488
489 all_names()
490 Returns the list of all the guessed OS names for the given host.
491
492 class_accuracy()
493 class_accuracy($index)
494 A basic call to class_accuracy() returns the osclass accuracy of
495 the first record. If $index is given, it returns the osclass
496 accuracy for the given record. The index starts at 0.
497
498 class_count()
499 Returns the total number of OS class records obtained from the nmap
500 scan.
501
502 name()
503 name($index)
504 names()
505 names($index)
506 A basic call to name() returns the OS name of the first record
507 which is the name with the highest accuracy. If $index is given, it
508 returns the name for the given record. The index starts at 0.
509
510 name_accuracy()
511 name_accuracy($index)
512 A basic call to name_accuracy() returns the OS name accuracy of the
513 first record. If $index is given, it returns the name for the given
514 record. The index starts at 0.
515
516 name_count()
517 Returns the total number of OS names (records) for the given host.
518
519 osfamily()
520 osfamily($index)
521 A basic call to osfamily() returns the OS family information of the
522 first record. If $index is given, it returns the OS family
523 information for the given record. The index starts at 0.
524
525 osgen()
526 osgen($index)
527 A basic call to osgen() returns the OS generation information of
528 the first record. If $index is given, it returns the OS generation
529 information for the given record. The index starts at 0.
530
531 portused_closed()
532 Returns the closed port number used to help identify the OS
533 signatures. This might not be available for all hosts.
534
535 portused_open()
536 Returns the open port number used to help identify the OS
537 signatures. This might not be available for all hosts.
538
539 os_fingerprint()
540 Returns the OS fingerprint used to help identify the OS signatures.
541 This might not be available for all hosts.
542
543 type()
544 type($index)
545 A basic call to type() returns the OS type information of the first
546 record. If $index is given, it returns the OS type information for
547 the given record. The index starts at 0.
548
549 vendor()
550 vendor($index)
551 A basic call to vendor() returns the OS vendor information of the
552 first record. If $index is given, it returns the OS vendor
553 information for the given record. The index starts at 0.
554
555 Nmap::Parser::Host::TraceHop
556
557 This object represents a router on the IP path towards the destination
558 or the destination itself. This is similar to what the "traceroute"
559 command outputs.
560
561 Nmap::Parser::Host::TraceHop objects are obtained through the
562 "all_trace_hops()" and "trace_hop()" Nmap::Parser::Host methods.
563
564 ttl()
565 The Time To Live is the network distance of this hop.
566
567 rtt()
568 The Round Trip Time is roughly equivalent to the "ping" time
569 towards this hop. It is not always available (in which case it
570 will be undef).
571
572 ipaddr()
573 The known IP address of this hop.
574
575 host()
576 The host name of this hop, if known.
577
579 I think some of us best learn from examples. These are a couple of
580 examples to help create custom security audit tools using some of the
581 nice features of the Nmap::Parser module. Hopefully this can double as
582 a tutorial. More tutorials (articles) can be found at
583 <https://github.com/modernistik/Nmap-Parser>
584
585 Real-Time Scanning
586 You can run a nmap scan and have the parser parse the information
587 automagically. The only constraint is that you cannot use '-oX',
588 '-oN', or '-oG' as one of your arguments for nmap command line
589 parameters passed to parsescan().
590
591 use Nmap::Parser;
592
593 my $np = new Nmap::Parser;
594 my @hosts = @ARGV; #get hosts from cmd line
595
596 #runs the nmap command with hosts and parses it automagically
597 $np->parsescan('/usr/bin/nmap','-sS O -p 1-1023',@hosts);
598
599 for my $host ($np->all_hosts()){
600 print $host->hostname."\n";
601 #do mor stuff...
602 }
603
604 If you would like to run the scan using parsescan() but also save the
605 scan xml output, you can use cache_scan(). You must call cache_scan()
606 BEFORE you initiate the parsescan() method.
607
608 use Nmap::Parser;
609 my $np = new Nmap::Parser;
610
611 #telling np to save output
612 $np->cache_scan('nmap.localhost.xml');
613 $np->parsescan('/usr/bin/nmap','-F','localhost');
614 #do other stuff...
615
616 Callbacks
617 This is probably the easiest way to write a script with using
618 Nmap::Parser, if you don't need the general scan session information.
619 During the parsing process, the parser will obtain information of every
620 host. The callback function (in this case 'booyah()') is called after
621 the parsing of every host (sequentially). When the callback returns,
622 the parser will delete all information of the host it had sent to the
623 callback. This callback function is called for every host that the
624 parser encounters. The callback function must be setup before parsing
625
626 use Nmap::Parser;
627 my $np = new Nmap::Parser;
628
629
630 $np->callback( \&booyah );
631
632 $np->parsefile('nmap_results.xml');
633 # or use parsescan()
634
635 sub booyah {
636 my $host = shift; #Nmap::Parser::Host object, just parsed
637 print 'IP: ',$host->addr,"\n";
638 # ... do more stuff with $host ...
639
640 #when it returns, host object will be deleted from memory
641 #(good for processing VERY LARGE files or scans)
642 }
643
644 Multiple Instances - ("no less 'of'; my $self")
645 Using multiple instances of Nmap::Parser is extremely useful in helping
646 audit/monitor the network Policy (ohh noo! its that 'P' word!). In
647 this example, we have a set of hosts that had been scanned previously
648 for tcp services where the image was saved in base_image.xml. We now
649 will scan the same hosts, and compare if any new tcp have been open
650 since then (good way to look for suspicious new services). Easy
651 security Compliance detection. (ooh noo! The 'C' word too!).
652
653 use Nmap::Parser;
654 use vars qw($nmap_exe $nmap_args @ips);
655 my $base = new Nmap::Parser;
656 my $curr = new Nmap::Parser;
657
658
659 $base->parsefile('base_image.xml'); #load previous state
660 $curr->parsescan($nmap_exe, $nmap_args, @ips); #scan current hosts
661
662 for my $ip ($curr->get_ips )
663 {
664 #assume that IPs in base == IPs in curr scan
665 my $ip_base = $base->get_host($ip);
666 my $ip_curr = $curr->get_host($ip);
667 my %port = ();
668
669 #find ports that are open that were not open before
670 #by finding the difference in port lists
671 my @diff = grep { $port{$_} < 2}
672 (map {$port{$_}++; $_}
673 ( $ip_curr->tcp_open_ports , $ip_base->tcp_open_ports ));
674
675 print "$ip has these new ports open: ".join(',',@diff) if(scalar @diff);
676
677 for (@diff){print "$_ seems to be ",$ip_curr->tcp_service($_)->name,"\n";}
678
679 }
680
682 Discussion Forum
683 If you have questions about how to use the module, or any of its
684 features, you can post messages to the Nmap::Parser module forum on
685 CPAN::Forum. <https://github.com/modernistik/Nmap-Parser/issues>
686
687 Bug Reports, Enhancements, Merge Requests
688 Please submit any bugs or feature requests to:
689 <https://github.com/modernistik/Nmap-Parser/issues>
690
691 Please make sure that you submit the xml-output file of the scan which
692 you are having trouble with. This can be done by running your scan with
693 the -oX filename.xml nmap switch. Please remove any important IP
694 addresses for security reasons. It saves time in reproducing issues.
695
697 nmap, XML::Twig
698
699 The Nmap::Parser page can be found at:
700 <https://github.com/modernistik/Nmap-Parser>. It contains the latest
701 developments on the module. The nmap security scanner homepage can be
702 found at: <http://www.insecure.org/nmap/>.
703
705 Origiinal author, Anthony Persaud <https://www.modernistik.com>.
706 However, special thanks to: Daniel Miller
707 <https://github.com/bonsaiviking> and Robin Bowes
708 <http://robinbowes.com>. Please see Changes.md file for a list of
709 other great contributors.
710
712 <https://www.modernistik.com>
713 MIT License
714
715 Permission is hereby granted, free of charge, to any person obtaining a
716 copy of this software and associated documentation files (the
717 "Software"), to deal in the Software without restriction, including
718 without limitation the rights to use, copy, modify, merge, publish,
719 distribute, sublicense, and/or sell copies of the Software, and to
720 permit persons to whom the Software is furnished to do so, subject to
721 the following conditions:
722
723 The above copyright notice and this permission notice shall be included
724 in all copies or substantial portions of the Software.
725
726 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
727 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
728 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
729 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
730 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
731 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
732 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
733
734
735
736perl v5.34.0 2022-01-21 Parser(3)