File Coverage

blib/lib/App/Spoor/AccessEntryParser.pm
Criterion Covered Total %
statement 47 47 100.0
branch 12 12 100.0
condition 6 6 100.0
subroutine 8 8 100.0
pod 1 1 100.0
total 74 74 100.0


line stmt bran cond sub pod time code
1             package App::Spoor::AccessEntryParser;
2              
3 2     2   163617 use v5.10;
  2         10  
4 2     2   12 use strict;
  2         4  
  2         40  
5 2     2   10 use warnings;
  2         4  
  2         57  
6 2     2   11 use utf8;
  2         3  
  2         14  
7              
8             =head1 NAME
9              
10             App::Spoor::AccessEntryParser
11              
12             =head1 VERSION
13              
14             Version 0.02
15              
16             =cut
17              
18             our $VERSION = '0.02';
19              
20              
21             =head1 SYNOPSIS
22              
23             This package contains the necessary functionality to parse CPanel access log entries.
24              
25             =head1 SUBROUTINES/METHODS
26              
27             =head2 parse
28              
29             This subroutine accepts a single line from a CPanel access log (as a string) and returns a reference to a hash
30             representation of that entry.
31              
32             The hash representation contains the following elements when the entry could be successfully parsed:
33              
34             =over 2
35              
36             =item * type: This is hardcoded to 'access'
37              
38             =item * log_time: A DateTime instance representing the time of the log entry
39              
40             =item * event: A description of the event that the entry refers to - can be one of forward_added_partial_ip, forward_removed, unrecognised.
41              
42             =item * ip: The IP address listed in the entry
43              
44             =item * credential: The user performing the request
45              
46             =item * context: The context within which the operation is being performed can be either 'mailbox' or 'unrecognised'
47              
48             =item * status: The status of the request can be one of 'success' or 'failed'
49              
50             =back
51              
52             If the entry could not be successfully parsed, it will contain the following items:
53              
54             =over 2
55              
56             =item * type: This is hardcoded to 'access'
57              
58             =item * event: This is hardcoded to 'unrecognised'.
59              
60             =back
61              
62             =cut
63              
64             sub parse {
65 2     2   1367 use DateTime::Format::Strptime;
  2         1065028  
  2         11  
66 2     2   1307 use URI::Escape qw( uri_unescape );
  2         2819  
  2         341  
67              
68 22     22 1 5730 my $log_entry = shift;
69 22         97 my $level;
70             my $event;
71 22         0 my $status;
72 22         0 my $forward_recipient;
73 22         0 my %result;
74 22         74 my $date_parser = DateTime::Format::Strptime->new(pattern => '%m/%d/%Y:%H:%M:%S %z', on_error => 'croak');
75              
76 22 100       28462 if ($log_entry =~ /
77             \A
78             (?<ip>\S+)\s
79             -\s
80             (?<username>.+)\s
81             \[(?<timestamp>[^\]]+)\]\s
82             "(?<http_request>[^"]+)"\s
83             (?<response_code>\d{3})\s
84             /x) {
85 2     2   940 my $log_time = $date_parser->parse_datetime($+{timestamp})->epoch();
  2         839  
  2         635  
  20         105  
86 20         15187 my $credential = uri_unescape($+{username});
87 20         450 my $ip = $+{ip};
88 20         80 my $http_request = $+{http_request};
89 20         70 my $response_code = $+{response_code};
90              
91 20 100       68 if ($credential =~ /@/) {
92 16         29 $level = 'mailbox';
93             } else {
94 4         8 $level = 'unrecognised';
95             }
96              
97 20 100       45 if ($response_code eq '200') {
98 16         24 $status = 'success';
99             } else {
100 4         9 $status = 'failed';
101             }
102              
103 20 100 100     158 if ($credential =~ /@/ && $http_request =~ /\APOST.+doaddfwd.html/) {
    100 100        
104 4         15 $event = 'forward_added_partial_ip';
105             } elsif (
106             $credential =~ /@/ &&
107             $http_request =~ /\AGET.+dodelfwd.html\?.*emaildest=(?<forward_recipient>[^\s?]+)/
108             ) {
109 4         9 $event = 'forward_removed';
110 4         14 $forward_recipient = uri_unescape($+{forward_recipient});
111             } else {
112 12         20 $event = 'unrecognised';
113             }
114              
115 20         171 %result = (
116             type => 'access',
117             log_time => $log_time,
118             event => $event,
119             ip => $ip,
120             credential => $credential,
121             context => $level,
122             status => $status,
123             );
124              
125 20 100       60 $result{forward_recipient} = $forward_recipient if($forward_recipient);
126             } else {
127 2         8 %result = (
128             type => 'access',
129             event => 'unrecognised',
130             );
131             }
132              
133 22         870 \%result;
134             }
135              
136             =head1 AUTHOR
137              
138             Rory McKinley, C<< <rorymckinley at capefox.co> >>
139              
140             =head1 BUGS
141              
142             Please report any bugs or feature requests to C<bug-app-spoor at rt.cpan.org>, or through
143             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-Spoor>. I will be notified, and then you'll
144             automatically be notified of progress on your bug as I make changes.
145              
146             =head1 SUPPORT
147              
148             You can find documentation for this module with the perldoc command.
149              
150             perldoc App::Spoor::AccessEntryParser
151              
152             You can also look for information at:
153              
154             =over 4
155              
156             =item * RT: CPAN's request tracker (report bugs here)
157              
158             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Spoor>
159              
160             =item * AnnoCPAN: Annotated CPAN documentation
161              
162             L<http://annocpan.org/dist/App-Spoor>
163              
164             =item * CPAN Ratings
165              
166             L<https://cpanratings.perl.org/d/App-Spoor>
167              
168             =item * Search CPAN
169              
170             L<https://metacpan.org/release/App-Spoor>
171              
172             =back
173              
174             =head1 LICENSE AND COPYRIGHT
175              
176             Copyright 2019 Rory McKinley.
177              
178             This program is free software; you can redistribute it and/or modify it
179             under the terms of the the Artistic License (2.0). You may obtain a
180             copy of the full license at:
181              
182             L<http://www.perlfoundation.org/artistic_license_2_0>
183              
184             Any use, modification, and distribution of the Standard or Modified
185             Versions is governed by this Artistic License. By using, modifying or
186             distributing the Package, you accept this license. Do not use, modify,
187             or distribute the Package, if you do not accept this license.
188              
189             If your Modified Version has been derived from a Modified Version made
190             by someone other than you, you are nevertheless required to ensure that
191             your Modified Version complies with the requirements of this license.
192              
193             This license does not grant you the right to use any trademark, service
194             mark, tradename, or logo of the Copyright Holder.
195              
196             This license includes the non-exclusive, worldwide, free-of-charge
197             patent license to make, have made, use, offer to sell, sell, import and
198             otherwise transfer the Package with respect to any patent claims
199             licensable by the Copyright Holder that are necessarily infringed by the
200             Package. If you institute patent litigation (including a cross-claim or
201             counterclaim) against any party alleging that the Package constitutes
202             direct or contributory patent infringement, then this Artistic License
203             to you shall terminate on the date that such litigation is filed.
204              
205             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
206             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
207             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
208             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
209             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
210             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
211             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
212             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
213              
214              
215             =cut
216              
217             1; # End of App::Spoor::AccessEntryParser