File Coverage

blib/lib/Net/IMP/Filter.pm
Criterion Covered Total %
statement 98 170 57.6
branch 51 114 44.7
condition 11 44 25.0
subroutine 10 15 66.6
pod 7 7 100.0
total 177 350 50.5


line stmt bran cond sub pod time code
1 3     3   27793 use strict;
  3         3  
  3         99  
2 3     3   9 use warnings;
  3         3  
  3         88  
3 3     3   9 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  3         3  
  3         19  
4              
5             package Net::IMP::Filter;
6 3     3   157 use Net::IMP;
  3         6  
  3         212  
7 3     3   14 use Net::IMP::Debug;
  3         2  
  3         16  
8 3     3   923 use Hash::Util 'lock_ref_keys';
  3         3721  
  3         16  
9 3     3   173 use Scalar::Util 'weaken';
  3         4  
  3         4083  
10              
11              
12             ############################################################################
13             # these need to be redefined in subclass
14             ############################################################################
15             # analyzed data output
16             sub out {
17 0     0 1 0 my ($self,$dir,$data) = @_;
18 0         0 return;
19             }
20              
21             sub deny {
22 0     0 1 0 my ($self,$msg,$dir,@extmsg) = @_;
23 0         0 while (@extmsg) {
24 0         0 my ($k,$v) = splice(@extmsg,0,2);
25 0         0 $msg .= " $k:$v";
26             }
27 0 0       0 $DEBUG && debug("deny $msg");
28 0         0 return;
29             }
30              
31             sub fatal {
32 0     0 1 0 my ($self,$msg) = @_;
33 0 0       0 $DEBUG && debug("fatal $msg");
34 0         0 return;
35             }
36              
37             sub log {
38 0     0 1 0 my ($self,$level,$msg,$dir,$offset,$len,@extmsg) = @_;
39 0         0 while (@extmsg) {
40 0         0 my ($k,$v) = splice(@extmsg,0,2);
41 0         0 $msg .= " $k:$v";
42             }
43 0 0       0 $DEBUG && debug("log [$level] $msg");
44 0         0 return;
45             }
46              
47             sub acctfld {
48 0     0 1 0 my ($self,$key,$value) = @_;
49 0 0       0 $DEBUG && debug("acctfld $key=$value");
50 0         0 return;
51             }
52              
53             ############################################################################
54             # Implementation
55             ############################################################################
56             sub new {
57 10     10 1 15 my ($class,$imp,%args) = @_;
58 10   33     96 my $self = lock_ref_keys( bless {
59             %args,
60             imp => $imp, # analyzer object
61             buf => [
62             # list of buffered data [ offset,buf,type ] per dir
63             # buffers for same streaming type will be concatenated
64             [ [0,'',0] ],
65             [ [0,'',0] ],
66             ],
67             pass => [0,0], # may pass up to this offset
68             prepass => [0,0], # may prepass up to this offset
69             skipped => [0,0], # flag if last data got not send to analyzer
70             # because of pass into future
71             eof => [0,0], # flag if eof received
72             dead => 0, # set if deny|fatal received
73             },(ref $class || $class) );
74              
75 10 50       61 if ($imp) {
76 10         17 weaken( my $weak = $self );
77 10         24 $imp->set_callback(\&_imp_cb,$weak);
78             }
79 10         16 return $self;
80             }
81              
82             # data into analyzer
83             sub in {
84 25     25 1 106 my ($self,$dir,$data,$type) = @_;
85 25 50       38 $self->{dead} and return;
86              
87 25   100     86 $type ||= IMP_DATA_STREAM;
88 25 50       33 $DEBUG && debug("in($dir,$type) %d bytes",length($data));
89              
90 25 100       47 $self->{eof}[$dir] = 1 if $data eq '';
91 25 50       36 return $self->out($dir,$data,$type) if ! $self->{imp};
92              
93 25         25 my $buf = $self->{buf}[$dir];
94              
95             # (pre)pass as much as possible
96 25         28 for my $w (qw(pass prepass)) {
97 50 50       97 my $maxoff = $self->{$w}[$dir] or next;
98 0 0 0     0 @$buf == 1 and ! $buf->[0][2] or die "buf should be empty";
99 0 0 0     0 if ( $maxoff == IMP_MAXOFFSET
100             or $maxoff > $buf->[-1][0] + length($data) ) {
101 0 0       0 $DEBUG && debug("can $w everything");
102 0   0     0 my $lastoff = $self->{skipped}[$dir] && $buf->[0][0];
103 0         0 $buf->[0][0] += length($data);
104 0         0 $self->out($dir,$data,$type);
105 0 0 0     0 if ($w eq 'prepass') {
    0          
106 0         0 $self->{imp}->data($dir,$data,$lastoff,$type);
107 0         0 $self->{skipped}[$dir] = 0;
108             } elsif ( $data eq '' and $maxoff != IMP_MAXOFFSET ) {
109 0         0 $self->{imp}->data($dir,$data,$lastoff,$type);
110 0         0 $self->{skipped}[$dir] = 0;
111             } else {
112 0         0 $self->{skipped}[$dir] = 1;
113             }
114 0         0 return;
115             }
116              
117 0         0 my $canfw = $maxoff - $buf->[-1][0];
118 0 0 0     0 if ( $type > 0 and $canfw != length($data)) {
119             # packet types need to be handled as a single piece
120 0         0 debug("partial $w for $type ignored");
121 0         0 next;
122             }
123              
124 0 0       0 $DEBUG && debug("can $w %d bytes of %d", $canfw, length($data));
125 0         0 my $fwd = substr($data,0,$canfw,'');
126 0   0     0 my $lastoff = $self->{skipped}[$dir] && $buf->[0][0];
127 0         0 $buf->[0][0] += length($fwd);
128 0         0 $self->{$w}[$dir] = 0; # no more (pre)pass
129 0         0 $self->out($dir,$fwd,$type);
130 0 0       0 if ($w eq 'prepass') {
131 0         0 $self->{imp}->data($dir,$fwd,$lastoff,$type);
132 0         0 $self->{skipped}[$dir] = 0;
133             } else {
134 0         0 $self->{skipped}[$dir] = 1;
135             }
136             }
137              
138             # data left which need to be forwarded to analyzer
139 25 100 66     75 if ( ! $buf->[-1][2] ) {
    100          
140             # replace empty (untyped) buffer with new data
141 12         11 $buf->[-1][1] = $data;
142 12         15 $buf->[-1][2] = $type;
143             } elsif ( $type < 0 and $buf->[-1][2] == $type ) {
144             # streaming data of same type can be added to current buffer
145 12         13 $buf->[-1][1] .= $data;
146             } else {
147             # need new buffer
148 1         2 push @$buf,[
149             $buf->[-1][0] + length($buf->[-1][1]), # base = end of last
150             $data,
151             $type
152             ];
153             }
154              
155 25 50       40 $DEBUG && debug("buffer and analyze %d bytes of data", length($data));
156 25   33     41 my $lastoff = $self->{skipped}[$dir] && $buf->[0][0];
157 25         59 $self->{imp}->data($dir,$data,$lastoff,$type);
158 25         96 $self->{skipped}[$dir] = 0;
159             }
160              
161             # callback from analyzer
162             sub _imp_cb {
163 68     68   49 my $self = shift;
164 68 50       105 $self->{dead} and return;
165              
166 68         50 my @fwd;
167 68         68 for my $rv (@_) {
168 71         66 my $rtype = shift(@$rv);
169 71 50       88 $DEBUG && debug("$rtype ".join(" ",map { "'$_'" } @$rv));
  0         0  
170              
171 71 50       252 if ( $rtype == IMP_DENY ) {
    50          
    50          
    50          
    100          
    50          
    0          
172 0         0 my ($dir,$msg,@extmsg) = @$rv;
173 0         0 $self->deny($msg,$dir,@extmsg);
174 0         0 $self->{dead} = 1;
175 0         0 return;
176             } elsif ( $rtype == IMP_FATAL ) {
177 0         0 my $reason = shift;
178 0         0 $self->fatal($reason);
179 0         0 $self->{dead} = 1;
180 0         0 return;
181              
182             } elsif ( $rtype == IMP_LOG ) {
183 0         0 my ($dir,$offset,$len,$level,$msg,@extmsg) = @$rv;
184 0         0 $self->log($level,$msg,$dir,$offset,$len,@extmsg);
185              
186             } elsif ( $rtype == IMP_ACCTFIELD ) {
187 0         0 my ($key,$value) = @$rv;
188 0         0 $self->acctfld($key,$value);
189              
190             } elsif ( $rtype ~~ [ IMP_PASS, IMP_PREPASS ] ) {
191 40         55 my ($dir,$offset) = @$rv;
192 40 50       56 $DEBUG && debug("got %s %d|%d", $rtype,$dir,$offset);
193              
194 40 50 0     63 if ( $self->{pass}[$dir] == IMP_MAXOFFSET ) {
    50 0        
    0          
195 0         0 next; # cannot get better than previous pass
196             } elsif ( $rtype == IMP_PASS ) {
197 40 100       63 if ( $offset == IMP_MAXOFFSET ) {
    50          
198 9         7 $self->{pass}[$dir] = $offset;
199 9         9 $self->{prepass}[$dir] = 0;
200             } elsif ( $offset > $self->{pass}[$dir] ) {
201 31         25 $self->{pass}[$dir] = $offset;
202             $self->{prepass}[$dir] = 0
203 31 50       53 if $offset >= $self->{prepass}[$dir];
204             } else {
205 0         0 next; # not better than previous pass
206             }
207              
208             # IMP_PREPASS
209             } elsif ( $offset == IMP_MAXOFFSET or (
210             $offset > $self->{pass}[$dir] and
211             $offset > $self->{prepass}[$dir] )) {
212             # update for prepass
213 0         0 $self->{prepass}[$dir] = $offset
214             } else {
215             # next; # no better than previous prepass
216             }
217              
218 40         35 my $buf = $self->{buf}[$dir];
219 40         22 my $end;
220              
221 40         63 while ($buf->[0][2]) {
222 32         29 my $buf0 = shift(@$buf);
223 32         31 $end = $buf0->[0] + length($buf0->[1]);
224 32 100 66     119 if ( $offset == IMP_MAXOFFSET
    50          
    100          
    50          
225             or $offset >= $end ) {
226 10 50       14 $DEBUG && debug("pass complete buf");
227 10         16 push @fwd, [ $dir, $buf0->[1] ];
228             # keep dummy in buf
229 10 100       18 if ( ! @$buf ) {
230 9         13 unshift @$buf,[ $buf0->[0] + length($buf0->[1]),'',0 ];
231 9 50       21 push @fwd,[$dir,''] if $self->{eof}[$dir]; # fwd eof
232 9         11 last;
233             }
234             } elsif ( $offset < $buf0->[0] ) {
235 0 0       0 $DEBUG && debug("duplicate $rtype $offset ($buf0->[0])");
236 0         0 unshift @$buf,$buf0;
237 0         0 last;
238             } elsif ( $offset == $buf0->[0] ) {
239             # at border, e.g. forward 0 bytes
240 1         1 unshift @$buf,$buf0;
241 1         1 last;
242             } elsif ( $buf0->[2] < 0 ) {
243             # streaming type, can pass part of buf
244 21 50       27 $DEBUG && debug("pass part of buf");
245 21         49 push @fwd, [
246             $dir,
247             substr($buf0->[1],0,$offset - $end,'')
248             ];
249             # put back with adjusted offset
250 21         18 $buf0->[0] = $offset;
251 21         25 unshift @$buf, $buf0;
252 21         21 last;
253             } else {
254 0 0       0 $DEBUG && debug(
255             "ignore partial $rtype for $buf0->[2] (offset=$offset,pos=$buf0->[0])");
256 0         0 unshift @$buf, $buf0; # put back
257 0         0 last;
258             }
259             }
260              
261 40 100 66     105 if ( $offset != IMP_MAXOFFSET and $offset <= $end ) {
262             # limit reached, reset (pre)pass
263 31 50       81 $self->{ $rtype == IMP_PASS ? 'pass':'prepass' }[$dir] = 0;
264             }
265              
266             } elsif ( $rtype == IMP_REPLACE ) {
267 31         46 my ($dir,$offset,$newdata) = @$rv;
268 31 50       41 $DEBUG && debug("got %s %d|%d", $rtype,$dir,$offset);
269              
270 31 50 33     80 if ( $self->{pass}[$dir] or $self->{prepass}[$dir] ) {
271             # we are allowed to (pre)pass in future, so we cannot replace
272 0         0 die "cannot replace already passed data";
273             }
274              
275 31         27 my $buf = $self->{buf}[$dir];
276 31         26 my $buf0 = $buf->[0];
277 31         29 my $eob = $buf0->[0] + length($buf0->[1]);
278 31 50       46 if ( $eob < $offset ) {
    100          
279 0         0 die "replacement cannot span different types or packets";
280             } elsif ( $eob == $offset ) {
281             # full replace
282 3 50       5 $DEBUG && debug("full replace");
283 3         8 push @fwd,[ $dir,$newdata ];
284 3         2 shift(@$buf);
285 3 50       12 push @$buf, [ $eob,'',0 ] if ! @$buf;
286             } else {
287 28 50       37 die "no partial replacement for packet types allowed"
288             if $buf0->[2]>0;
289 28 50       35 $DEBUG && debug("partial replace");
290 28         34 push @fwd,[ $dir,$newdata ];
291 28         36 substr( $buf0->[1],0,$offset - $buf0->[0],'');
292 28         43 $buf0->[0] = $offset;
293             }
294              
295             } elsif ( $rtype ~~ [ IMP_PAUSE, IMP_CONTINUE ] ) {
296             # ignore
297             } else {
298 0         0 die "cannot handle Net::IMP rtype $rtype";
299             }
300             }
301 68         165 $self->out(@$_) for (@fwd);
302             }
303              
304              
305             1;
306             __END__