File Coverage

blib/lib/App/HTTP_Proxy_IMP/IMP/FakeResponse.pm
Criterion Covered Total %
statement 40 117 34.1
branch 4 54 7.4
condition 3 20 15.0
subroutine 12 18 66.6
pod 7 8 87.5
total 66 217 30.4


line stmt bran cond sub pod time code
1              
2 1     1   6 use strict;
  1         2  
  1         32  
3 1     1   5 use warnings;
  1         3  
  1         35  
4             package App::HTTP_Proxy_IMP::IMP::FakeResponse;
5 1     1   6 use base 'Net::IMP::HTTP::Request';
  1         2  
  1         148  
6 1     1   7 use fields qw(root file response);
  1         2  
  1         5  
7 1     1   81 no warnings 'experimental'; # smartmatch
  1         1  
  1         41  
8              
9 1     1   5 use Net::IMP;
  1         1  
  1         95  
10 1     1   7 use Net::IMP::Debug;
  1         2  
  1         6  
11 1     1   112 use Carp;
  1         2  
  1         74  
12 1     1   15 use Digest::MD5;
  1         3  
  1         1758  
13              
14 1     1 0 15 sub RTYPES { ( IMP_PASS,IMP_REPLACE,IMP_DENY,IMP_ACCTFIELD ) }
15              
16             sub new_factory {
17 1     1 1 5 my ($class,%args) = @_;
18 1 50       3 my $dir = $args{root} or croak("no root directory given");
19 1 50 33     25 -d $dir && -r _ && -x _ or croak("cannot use base dir $dir: $!");
      33        
20 1         11 my $obj = $class->SUPER::new_factory(%args);
21 1         237 $obj->{root} = $dir;
22 1         6 return $obj;
23             }
24              
25             sub validate_cfg {
26 1     1 1 3 my ($class,%args) = @_;
27 1         3 my $dir = delete $args{root};
28 1         2 delete $args{ignore_parameters};
29 1         7 my @err = $class->SUPER::validate_cfg(%args);
30 1 50 33     41 if ( ! $dir ) {
    50          
31 0         0 push @err, "no 'root' given";
32             } elsif ( ! -d $dir || ! -r _ || ! -x _ ) {
33 0         0 push @err, "cannot access root dir $dir";
34             }
35 1         6 return @err;
36             }
37              
38             sub request_hdr {
39 0     0 1   my ($self,$hdr) = @_;
40              
41 0           my ($method,$proto,$host,$path) = $hdr =~m{\A([A-Z]+) +(?:(\w+)://([^/]+))?(\S+)};
42 0 0         $host = $1 if $hdr =~m{\nHost: *(\S+)}i;
43 0 0         if ( ! $host ) {
44 0           $self->run_callback([IMP_DENY,0,'cannot determine host']);
45 0           return;
46             }
47 0   0       $proto ||= 'http';
48 0           $host = lc($host);
49 0 0         my $port =
50 0 0         $host=~s{^(?:\[(\w._\-:)+\]|(\w._\-))(?::(\d+))?$}{ $1 || $2 }e ?
51             $3:80;
52              
53 0           my $dir = $self->{factory_args}{root}."/$host:$port";
54 0 0         goto IGNORE if ! -d $dir;
55              
56 0           my $uri = "$proto://$host:$port$path";
57 0 0         my $qstring = $path =~s{\?(.+)}{} ? $1 : undef;
58             # collect information to determine filename
59 0           my %file = (
60             uri => $uri,
61             dir => $dir,
62             method => $method,
63             md5path => Digest::MD5->new->add($path)->hexdigest,
64             md5data => undef,
65             );
66              
67 0           my $fname = "$dir/".lc($method)."-".$file{md5path};
68 0 0         goto TRY_FNAME if $self->{factory_args}{ignore_parameters};
69              
70 0 0         ( $file{md5data} = Digest::MD5->new )->add("\000$qstring\001")
71             if defined $qstring;
72 0 0         if ( $method ~~ [ 'GET','HEAD' ] ) {
73 0 0         $fname .= "-".$file{md5data}->hexdigest if $file{md5data};
74 0           goto TRY_FNAME;
75             }
76              
77             # ignore if there will not be a matching filename, no matter
78             # what md5data will be
79 0 0 0       goto IGNORE if ! -f $fname and ! glob("$fname-*");
80              
81             # don't pass yet, continue in request body
82 0           $file{rqhdr} = $hdr;
83 0           $self->{file} = \%file;
84 0           return;
85              
86             TRY_FNAME:
87 0 0         if ( $self->{response} = _extract_response($fname)) {
88 0           $hdr =~s{(\A\w+\s+)}{$1internal://};
89 0           debug("hijack http://$host:$port$path");
90 0           $self->run_callback(
91             [ IMP_ACCTFIELD,'orig_uri',$uri ],
92             [ IMP_REPLACE,0,$self->offset(0),$hdr ],
93             [ IMP_PASS,0,IMP_MAXOFFSET ]
94             );
95 0           return;
96             }
97              
98             IGNORE:
99 0           $self->run_callback(
100             [ IMP_PASS,0,IMP_MAXOFFSET ],
101             [ IMP_PASS,1,IMP_MAXOFFSET ],
102             );
103             }
104              
105             sub request_body {
106 0     0 1   my ($self,$data) = @_;
107 0 0         my $f = $self->{file} or return;
108 0           my $md = $f->{md5data};
109 0 0         if ( $data ne '' ) {
110 0   0       $md ||= $f->{md5data} = Digest::MD5->new;
111 0           $md->add($data);
112 0           return;
113             }
114              
115             # eof of request body - determine final filename
116 0           $self->{file} = undef;
117             my $fname = $f->{dir}.'/'.join('-',
118             lc($f->{method}),
119             $f->{md5path},
120 0 0         $f->{md5data} ? ($f->{md5data}->hexdigest):()
121             );
122              
123             # setup response if file is found
124 0 0         if ( $self->{response} = _extract_response($fname)) {
125 0           ( my $hdr = $f->{rqhdr})=~s{(\A\w+\s+)}{$1internal://};
126 0           debug("hijack $f->{uri}");
127             $self->run_callback(
128             [ IMP_ACCTFIELD,'orig_uri',$f->{uri} ],
129 0           [ IMP_REPLACE,0,length($f->{rqhdr}),$hdr ],
130             [ IMP_PASS,0,IMP_MAXOFFSET ]
131             );
132 0           return;
133             }
134              
135             # otherwise pass everything through
136             $self->run_callback(
137 0           [ IMP_PASS,0,IMP_MAXOFFSET ],
138             [ IMP_PASS,1,IMP_MAXOFFSET ],
139             );
140             }
141              
142              
143             sub response_hdr {
144 0     0 1   my ($self,$hdr) = @_;
145 0 0 0       my $rphdr = $self->{response} && $self->{response}[0] or return;
146 0           $rphdr =~s{\r?\n}{\r\n}g;
147 0           my $clen = length($self->{response}[1]);
148 0 0         $rphdr =~s{(\nContent-length:[ \t]*)\d+}{$1$clen} or
149             $rphdr =~s{\n}{\nContent-length: $clen\r\n};
150 0           warn "XXXX offset=".$self->offset(1)." len=".length($hdr);
151 0           $self->run_callback([ IMP_REPLACE,1,$self->offset(1),$rphdr ]);
152             }
153              
154             sub response_body {
155 0     0 1   my ($self,$data) = @_;
156 0 0         my $rp = $self->{response} or return;
157 0           $self->{response} = undef;
158 0           warn "XXXX offset=".$self->offset(1)." len=".length($data);
159 0           $self->run_callback(
160             [ IMP_REPLACE,1,$self->offset(1),$rp->[1] ],
161             [ IMP_PASS,1,IMP_MAXOFFSET ],
162             );
163             }
164              
165             sub any_data {
166 0     0 1   my $self = shift;
167             # ignore
168 0 0         $self->{file} or return;
169 0           $self->{file} = undef;
170 0           $self->run_callback(
171             [ IMP_PASS,0,IMP_MAXOFFSET ],
172             [ IMP_PASS,1,IMP_MAXOFFSET ],
173             );
174             }
175              
176             sub _extract_response {
177 0     0     my $fname = shift;
178 0 0         open( my $fh,'<',$fname) or return;
179 0           my $data = do { local $/; <$fh> };
  0            
  0            
180 0 0         if ( $data =~s{\A(HTTP/1\.[01] .*?(\r?\n)\2)}{}s ) {
181             # only response header + body
182 0           return [ $1,$data ];
183             } else {
184 0           my @size = unpack("NNNN",substr($data,-16));
185 0 0         if ( $size[0]+$size[1]+$size[2]+$size[3] + 16 == length($data)) {
186             # format used by Net::IMP::HTTP::SaveResponse
187 0           my $rq = $size[0]+$size[1]; # skip request
188             return [
189 0           substr($data,$rq,$size[2]), # response header
190             substr($data,$rq+$size[2],$size[3]) # response body
191             ],
192             }
193             }
194 0           debug("unknown format in $fname");
195 0           return;
196             }
197              
198             1;
199              
200             __END__