File Coverage

blib/lib/NcFTPd/Log/Parse/Xfer.pm
Criterion Covered Total %
statement 31 33 93.9
branch 7 10 70.0
condition 1 3 33.3
subroutine 6 6 100.0
pod n/a
total 45 52 86.5


line stmt bran cond sub pod time code
1             package NcFTPd::Log::Parse::Xfer;
2            
3 2     2   30393 use strict;
  2         5  
  2         76  
4 2     2   11 use warnings;
  2         3  
  2         62  
5 2     2   10 use base 'NcFTPd::Log::Parse::Base';
  2         17  
  2         760  
6            
7             # Field names
8             use constant {
9 2         2991 DESTINATION => 'destination',
10             DURATION => 'duration',
11             EMAIL => 'email',
12             HOST => 'host',
13             MODE => 'mode',
14             NOTES => 'notes',
15             OPERATION => 'operation',
16             PATHNAME => 'pathname',
17             PATTERN => 'pattern',
18             RATE => 'rate',
19             RECURSION => 'recursion',
20             RESERVED1 => 'reserved1',
21             RESERVED2 => 'reserved2',
22             RESERVED3 => 'reserved3',
23             SESSION_ID => 'session_id',
24             SIZE => 'size',
25             SOURCE => 'source',
26             START_OF_TRANSFER => 'start_of_transfer',
27             STARTING_OFFSET => 'starting_offset',
28             STARTING_SIZE => 'starting_size',
29             STATUS => 'status',
30             SUFFIX => 'suffix',
31             # Transfer type, binary or ascii
32             TYPE => 'type',
33             USER => 'user'
34 2     2   13 };
  2         6  
35            
36             my %TRANSFER_NOTES = (
37             Df => 'FTP default data connection was used',
38             Po => 'PORT connection',
39             Ps => 'PASV connection',
40             Mm => 'Used memory mapped I/O',
41             Bl => 'Used block transfer mode',
42             Sf => 'Used sendfile',
43             # These are not documented but they show up in store/retrieve entries
44             Ap => 'Unknown',
45             Rz => 'Unknown'
46             );
47            
48             my @TRANSFER_STATUSES = __PACKAGE__->_transfer_statuses;
49             my %COMMON_REGEX = __PACKAGE__->_common_regex;
50            
51             $COMMON_REGEX{optdigit} = '-1|\d+';
52             $COMMON_REGEX{notes} = join '|', keys %TRANSFER_NOTES;
53            
54             # Log entry definitions
55             my $CHMOD = {
56             name => 'chmod',
57             fields => [ PATHNAME, MODE, RESERVED1, RESERVED2, USER, EMAIL, HOST, SESSION_ID ],
58             regex => qr{
59             (.+),
60             (\d{3}), # Permissions
61             (.*?), # Reserved
62             (.*?), # Reserved
63             (.+), # User
64             (.*?), # "Email" (anonymous login password)
65             (.+), # "Host"
66             ($COMMON_REGEX{session}),
67             }x
68             };
69            
70             my $DELETE = {
71             name => 'delete',
72             fields => [ PATHNAME, RESERVED1, RESERVED2, RESERVED3, USER, EMAIL, HOST, SESSION_ID ],
73             regex => qr{
74             (.+), # Path of target
75             (.*?), # Reserved
76             (.*?), # Reserved
77             (.*?), # Reserved
78             (.+), # User
79             (.*?), # "Email" (anonymous login password)
80             (.+), # Host
81             ($COMMON_REGEX{session}), # Session
82             }x
83             };
84            
85             my $LINK = {
86             name => 'link',
87             fields => [ SOURCE, RESERVED1, DESTINATION, RESERVED2, USER, EMAIL, HOST, SESSION_ID ],
88             regex => qr{
89             (.+), # Path of existing file
90             (to), # Reserved, hardcoded to "to"
91             (.+), # Path of linked file
92             (.*?), # Reserved
93             (.+), # User
94             (.*?), # "Email" (anonymous login password)
95             (.+), # Host
96             ($COMMON_REGEX{session}),
97             }x
98             };
99            
100             my $LIST = {
101             name => 'listing',
102             fields => [ PATHNAME, STATUS, PATTERN, RECURSION, USER, EMAIL, HOST, SESSION_ID ],
103             regex => qr{
104             (.+), # Path
105             ($COMMON_REGEX{status}), # Transfer status
106             (.*?), # Filter pattern
107             ((?:RECURSIVE)?), # Recursive directory transversial
108             (.+), # User
109             (.*?), # "Email" (anonymous login password)
110             (.+), # Host
111             ($COMMON_REGEX{session}),
112             }x
113             };
114            
115             my $STORE = {
116             name => 'store',
117             fields => [ PATHNAME, SIZE, DURATION, RATE, USER, EMAIL, HOST, SUFFIX, STATUS,
118             TYPE, NOTES, START_OF_TRANSFER, SESSION_ID, STARTING_SIZE, STARTING_OFFSET ],
119             regex => qr{
120             (.+), # Path
121             (\d+), # Size
122             ($COMMON_REGEX{decimal}), # Durtaion
123             ($COMMON_REGEX{decimal}), # Transfer rate
124             (.+), # User
125             (.*?), # Email
126             (.+), # Peer
127             ((?:\.\w+)?), # Content "translation" (file extention)
128             ($COMMON_REGEX{status}), # Transfer status
129             (A|I), # FTP transfer mode
130             ((?:$COMMON_REGEX{notes})*?), # Notes about the transfer
131             (\d+) # Start of transfer
132             #optional, added in later version
133             (?:,
134             ($COMMON_REGEX{session}),
135             ($COMMON_REGEX{optdigit}), # File size at start of the transfer
136             ($COMMON_REGEX{optdigit}), # Position of file start of the transfer
137             )?
138             }x
139             };
140            
141             my $MKDIR = {
142             name => 'mkdir',
143             fields => $DELETE->{fields},
144             regex => $DELETE->{regex}
145             };
146            
147             my $RENAME = {
148             name => 'rename',
149             fields => $LINK->{fields},
150             regex => $LINK->{regex}
151             };
152            
153             my $RETRIEVE = {
154             name => 'retrieve',
155             fields => $STORE->{fields},
156             regex => $STORE->{regex}
157             };
158            
159             my %LOG_ENTRIES = (
160             C => $CHMOD,
161             D => $DELETE,
162             L => $LINK,
163             M => $MKDIR,
164             N => $RENAME,
165             R => $RETRIEVE,
166             S => $STORE,
167             T => $LIST
168             );
169            
170             sub _expand_field
171             {
172 91     91   149 my ($self, $name, $value) = @_;
173            
174 91 100       226 if($name eq OPERATION) {
    100          
175 7         16 $value = $LOG_ENTRIES{$value}->{name};
176             }
177             elsif($name eq NOTES) {
178 2         89 my @notes = grep length, split /($COMMON_REGEX{notes})/, $value;
179 2         15 $value = [ map $TRANSFER_NOTES{$_}, @notes ];
180             }
181            
182 91         371 $value;
183             }
184            
185             sub _parse_entry
186             {
187 21     21   45 my ($self, $fields) = @_;
188            
189 21 50 33     131 return unless $fields and $fields =~ /^(\w),(.+)/;
190            
191 21         31 my $op = $1;
192 21         53 my $details = $2;
193 21         24 my $entry;
194            
195             #TODO: Provide line number on error
196 21 50       52 if(!defined $LOG_ENTRIES{$op}) {
197 0         0 $self->{error} = "Unknown operation '$op'";
198             }
199             else {
200 21         19 my @keys = @{$LOG_ENTRIES{$op}->{fields}};
  21         102  
201 21         1140 my @values = $details =~ $LOG_ENTRIES{$op}->{regex};
202            
203             #print "R: $LOG_ENTRIES{$op}->{regex}\n $details\n";
204            
205 21 50       52 if(@values) {
206 21         77 $entry->{&OPERATION} = $op;
207 21         365 @$entry{@keys} = @values;
208             }
209             else {
210 0         0 $self->{error} = "Unrecognized format for line: $fields";
211             }
212             }
213            
214 21         77 $entry;
215             }
216            
217             1;
218            
219            
220             __END__