File Coverage

lib/HTTP/Server/Connection.pm
Criterion Covered Total %
statement 66 402 16.4
branch 0 164 0.0
condition 0 50 0.0
subroutine 22 61 36.0
pod 1 22 4.5
total 89 699 12.7


line stmt bran cond sub pod time code
1             # Copyrights 2008 by Mark Overmeer.
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 1.05.
5 1     1   5 use strict;
  1         1  
  1         27  
6 1     1   3 use warnings;
  1         2  
  1         36  
7              
8             package HTTP::Server::Connection;
9 1     1   18 use vars '$VERSION';
  1         1  
  1         52  
10             $VERSION = '0.11';
11              
12              
13 1     1   7 use HTTP::Server::Multiplex;
  1         1  
  1         23  
14 1     1   351 use HTTP::Server::Session;
  1         2  
  1         23  
15              
16 1     1   890 use HTTP::Request ();
  1         21009  
  1         25  
17 1     1   806 use HTTP::Response ();
  1         1959  
  1         21  
18 1     1   6 use HTTP::Status;
  1         2  
  1         333  
19 1     1   760 use HTTP::Date qw(time2str str2time);
  1         4121  
  1         70  
20 1     1   7 use URI ();
  1         3  
  1         20  
21 1     1   954 use LWP::MediaTypes qw(guess_media_type);
  1         13587  
  1         119  
22 1     1   11 use Fcntl qw(O_RDONLY);
  1         3  
  1         53  
23 1     1   7 use Scalar::Util qw(weaken);
  1         3  
  1         55  
24 1     1   6 use Socket qw(unpack_sockaddr_in inet_ntoa);
  1         3  
  1         310  
25 1     1   8 use Storable qw(freeze thaw);
  1         2  
  1         98  
26 1     1   5 use Fcntl qw(:mode);
  1         3  
  1         404  
27 1     1   6 use POSIX qw(strftime);
  1         2  
  1         12  
28              
29 1     1   77 use Log::Report 'httpd-multiplex', syntax => 'SHORT';
  1         2  
  1         13  
30              
31             use constant
32 1         5267 { HTTP_0_9 => 'HTTP/0.9'
33             , HTTP_1_0 => 'HTTP/1.0'
34             , HTTP_1_1 => 'HTTP/1.1'
35 1     1   361 };
  1         3  
36              
37             my @stat_fields =
38             qw/dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks/;
39              
40             my @default_headers;
41 0     0 0   sub setDefaultHeaders(@) {my $class = shift; push @default_headers, @_};
  0            
42              
43             # oops, dirty hack
44 0     0 0   sub HTTP::Request::id() { shift->{HSC_id} }
45              
46              
47             my $conn_id = 'C0000000';
48              
49             sub new($$$$)
50 0     0 0   { my ($class, $mux, $fh, $daemon) = @_;
51 0           my $self = bless {}, $class;
52 0           $self->{HSC_requests} = [];
53 0           $self->{HSC_mux} = $mux;
54 0           $self->{HSC_fh} = $fh;
55 0           $self->{HSC_session} = HTTP::Server::Session->new; # will change
56              
57 0           $self->{HSC_daemon} = $daemon;
58 0           weaken $self->{HSC_daemon};
59              
60 0           $self->{HSC_connect} = time;
61 0           $self->{HSC_conn_id} = ++$conn_id;
62 0           $self->{HSC_reqcount} = 0;
63              
64 0           my $peername = $fh->peername;
65 0           my ($port, $addr) = unpack_sockaddr_in $peername;
66 0           my $ip = inet_ntoa $addr;
67 0           info "$self->{HSC_conn_id} contacted by $ip:$port";
68              
69 0           my %client = (port => $port, ip => $ip, host => undef);
70 0           $daemon->dnslookup($self, $ip, \$client{host});
71 0           $self->{HSC_client} = \%client;
72              
73 0           $self;
74             }
75              
76 0     0 0   sub client() {shift->{HSC_client}}
77 0     0 0   sub session() {shift->{HSC_session}}
78 0     0 0   sub id() {shift->{HSC_conn_id}}
79              
80             # new text was received. Collect it into an HTTP::Request
81             sub mux_input($$$)
82 0     0 0   { my ($self, $mux, $fh, $refdata) = @_;
83 0           my $req = $self->{HSC_next};
84              
85             # ignore input for closing, connection can still be writing
86 0 0 0       if(!$req && $self->{HSC_no_more})
87 0           { $$refdata = '';
88 0           return;
89             }
90              
91 0           my $headers;
92 0 0         if($req)
93 0           { $headers = $req->headers;
94             }
95             else
96 0           { $$refdata =~ s/^\s+//s; # strip leading blanks
97 0 0         $$refdata =~ s/(.*?)\r\n\r\n//s or return; # not whole header yet
98 0           $req = $self->{HSC_next} = HTTP::Request->parse($1);
99 0           $req->{HSC_id}
100             = $self->{HSC_conn_id} . sprintf('-%02d', $self->{HSC_reqcount}++);
101              
102 0           my $proto = $req->protocol;
103 0 0         $req->protocol($proto = HTTP_0_9)
104             unless $proto;
105              
106 0           $headers = $req->headers;
107 0 0 0       $self->{HSC_no_more}++
      0        
108             if $req->protocol lt HTTP_1_1
109             || lc($headers->header('Connection') || '') ne 'keep-alive';
110              
111 0 0         if($proto lt HTTP_1_0)
112 0           { $self->{take_all}++;
113 0           return;
114             }
115            
116 0 0         if(my $expect = $headers->header('Expect'))
117 0 0         { if(lc $expect ne '100-continue')
118 0           { my $resp = $self->sendStatus($req, RC_EXPECTATION_FAILED);
119 0           trace "Unsupported Expect value '$expect'";
120 0           $self->cancelConnection;
121 0           return $resp;
122             }
123 0           $self->sendStatus($req, RC_CONTINUE);
124             }
125             }
126              
127 0   0       my $te = lc($headers->header('Transfer-Encoding') || '');
128 0   0       my $cl = $headers->header('Content-Length') || 0;
129              
130 0 0 0       if($te eq 'chunked')
    0          
    0          
    0          
131 0 0         { my ($starter, $len) = $$refdata =~ m/^((\S+)\r?\n)/ or return;
132 0 0         if($len !~ m/^[0-9a-fA-F]+$/)
133 0           { my $resp = $self->sendStatus($req, RC_BAD_REQUEST);
134 0           trace "Bad chunk header $len";
135 0           $self->cancelConnection;
136 0           return $resp;
137             }
138 0           my $need = hex $len;
139              
140 0           my $chunk_length = length($starter) + $need + 2;
141 0 0         return if length($$refdata) < $chunk_length;
142            
143 0 0         if($need!=0)
144 0           { $req->add_content(substr $$refdata, length($starter), $need);
145 0           substr($$refdata, 0, $chunk_length) = '';
146 0           return; # get more chunks
147             }
148              
149 0 0         return if $$refdata !~ m/\n\r?\n/; # need footer
150 0           my ($footer) = $$refdata =~ s/^0+\r?\n(.*?\r?\n)\r?\n//;
151 0           my $header = $req->headers;
152             HTTP::Message->parse($footer)->headers
153 0     0     ->scan(sub { $header->push_header(@_)} );
  0            
154              
155 0           $header->_header('Content-Length' => length ${$req->content_ref});
  0            
156 0           $header->remove_header('Transfer-Encoding');
157             }
158             elsif($te ne '')
159 0           { my $resp = $self->sendStatus($req, RC_NOT_IMPLEMENTED);
160 0           trace "Unsupported transfer encoding $te";
161 0           $self->cancelConnection;
162 0           return $resp;
163             }
164             elsif(defined $cl)
165 0 0 0       { return if defined $cl && length($$refdata) < $cl;
166 0           $req->content(substr $$refdata, 0, $cl, '');
167             }
168             elsif(($headers->header('Content-Type') || '')
169             =~ m/^multipart\/\w+\s*;.*boundary\s*=(["']?)\s*(\w+)\1/i)
170 0 0         { return unless $$refdata =~ s/(.*?\r?\n--\Q$2\E--\r?\n)//;
171 0           $req->content($1);
172             }
173             else
174 0           { $self->closeConnection;
175 0           $self->{take_all}++;
176             # collect till eof
177             }
178              
179 0 0         $mux->shutdown($fh, 0)
180             if $self->{HSC_no_more};
181              
182 0           info $req->id.' '.$req->protocol.' '.$req->method.' '.$req->uri;
183 0 0         if($self->{HSC_reqcount}==1)
184 0           { my $ua = $req->headers->header('User-Agent');
185 0 0         info $req->id.' UA='.$ua if $ua;
186             }
187              
188 0           $self->addRequest(delete $self->{HSC_next});
189             }
190              
191             sub mux_eof($$$)
192 0     0 0   { my ($self, $mux, $fh, $refdata) = @_;
193              
194 0           my $req = delete $self->{HSC_next};
195 0 0 0       if($req && length($$refdata) && $self->{take_all})
    0 0        
196 0           { $req->content_ref($refdata);
197 0           $self->addRequest($req);
198             }
199             elsif($$refdata =~ m/\S/)
200 0           { trace "trailing data in request (".length($$refdata)." bytes) ignored";
201             }
202              
203 0           $mux->shutdown($fh, 1);
204             }
205              
206             # This is the most tricky part: each connection may have multiple
207             # requests queued. If the handler returns a response object, the
208             # the response succeeded. Otherwise, other IO will need to be performed:
209             # we simply stop. When the other IO has completed, it will call this
210             # function again, to resolve the other requests.
211              
212             sub addRequest($)
213 0     0 0   { my ($self, $req) = @_;
214 0           my $queue = $self->{HSC_requests};
215 0           push @$queue, $req;
216              
217             # handler initiated by first request in queue, then auto-continues
218 0 0         $self->handleRequests
219             if @$queue==1;
220             }
221              
222             sub handleRequests()
223 0     0 0   { my ($self) = @_;
224 0           my $queue = $self->{HSC_requests};
225              
226             REQUEST:
227 0           while(@$queue)
228 0           { my $req = shift @$queue;
229 0           my $vhostn = $req->header('Host');
230 0           $vhostn =~ s/\:(\d+)$//; # strip optional port; ignored for now
231              
232 0 0         if(!defined $vhostn)
233 0 0         { if($req->protocol gt HTTP_1_1)
234 0           { $self->sendStatus($req, RC_MULTIPLE_CHOICES,
235             "explicit virtual host required in protocol ".$req->protocol);
236 0           next REQUEST;
237             }
238 0           $vhostn = 'default';
239             }
240              
241 0           my $vhost = $self->{HSC_daemon}->virtualHost($vhostn);
242 0 0         unless(defined $vhost)
243 0           { $self->sendStatus($req, RC_NOT_FOUND, "no virtual host $vhostn");
244 0           next REQUEST;
245             }
246              
247 0           my $resp = $vhost->handleRequest($self, $req);
248 0 0         defined $resp
249             or last REQUEST; # no answer==waiting in MUX
250             }
251             }
252              
253              
254             sub sendResponse($$$;$)
255 0     0 0   { my ($self, $req, $status, $header, $content) = @_;
256 0           my $protocol = $req->protocol;
257 0 0         defined $content or $content = '';
258              
259 0 0         if($protocol ge HTTP_1_0)
260 0 0         { push @$header
261             , Date => time2str(time)
262             , Connection => ($self->{HSC_no_more} ? 'close' : 'keep-alive')
263             , @default_headers;
264              
265 0 0         push @$header
266             , ref $content eq 'CODE'
267             ? ('Transfer-Encoding' => 'chunked')
268             : ('Content-Length' => length $content);
269             }
270             else
271 0           { undef $header;
272             }
273              
274 0           my $resp = HTTP::Response->new($status, status_message($status),$header);
275 0           $resp->request($req);
276 0           $resp->protocol($protocol);
277              
278 0           my ($mux, $fh) = @$self{'HSC_mux', 'HSC_fh'};
279 0           my $headtxt = $resp->as_string("\r\n");
280 0           my $size = length $headtxt;
281 0 0         if($req->method eq 'HEAD')
    0          
282 0           { $mux->write($fh, $headtxt);
283             }
284             elsif(ref $content eq 'CODE')
285             { # create chunked
286 0           $mux->write($fh, $headtxt);
287 0           $size = 0;
288 0           while(1)
289 0           { my $chunk = $content->();
290 0 0         defined $chunk or last;
291 0 0         length $chunk or next;
292 0           my $hexlen = sprintf "%x", length $chunk;
293 0           $mux->write($fh, "$hexlen\r\n$chunk\r\n");
294 0           $size += length($hexlen) + length($chunk) + 4;
295             }
296 0           $mux->write($fh, "0\r\n\r\n"); # end chunks and no footer
297 0           $size += 5;
298             }
299             else
300 0           { $resp->content_ref(\$content);
301 0           $mux->write($fh, $headtxt.$content);
302             }
303              
304 0           info $req->id." $status ${size}b";
305 0           $resp;
306             }
307              
308              
309             sub sendStatus($$;$)
310 0     0 0   { my ($self, $req, $status, $text) = @_;
311 0 0 0       my $descr = defined $text && length $text ? "\n

$text

" : '';
312 0           my @headers = ('Content-Type' => 'text/html');
313 0           my $message = status_message $status;
314              
315 0           $self->sendResponse($req, $status, \@headers, <<__CONTENT);
316             $status $message
317            

$status $message

$descr
318            
319             __CONTENT
320             }
321              
322              
323             sub sendRedirect($$$;$)
324 0     0 0   { my ($self, $req, $status, $location, $content) = @_;
325 0 0         is_redirect $status
326             or panic "Status '$status' is not redirect";
327              
328 0           my @headers = (Location => $location);
329 0 0 0       if(defined $content && length $content)
330 0 0         { my $ct = $content =~ m/^\s*\
331 0           push @headers, 'Content-Type' => $ct;
332             }
333              
334 0           $self->sendResponse($req, $status, \@headers, $content);
335             }
336              
337              
338             sub sendFile($$;$$)
339 0     0 0   { my ($self, $req, $file, $headers, $user_callback) = @_;
340 0   0 0     $user_callback ||= sub {};
  0            
341 0           my ($callback, @headers);
342 0 0         push @headers, @$headers if $headers;
343              
344 0           my $from_fh;
345 0 0         if(ref $file)
346 0           { $from_fh = $file;
347             $callback = sub
348 0     0     { $user_callback->(@_);
349 0           $self->handleRequests;
350 0           };
351             }
352             else
353 0 0         { -e $file or return
354             $self->sendStatus(RC_NOT_FOUND, "file $file does not exist");
355              
356 0 0         -f _ or return
357             $self->sendStatus(RC_NOT_ACCEPTABLE, "not a file $file");
358              
359 0 0         sysopen $from_fh, $file, O_RDONLY
360             or return $self->sendStatus(RC_FORBIDDEN, "no access to $file");
361              
362             $callback = sub
363 0     0     { $user_callback->(@_);
364 0           close $from_fh; # read errors are ignored
365 0           $self->handleRequests;
366 0           };
367              
368 0           my ($ct, $ce) = guess_media_type $file;
369 0 0         push @headers
370             , Date => time2str(time)
371             , Connection => ($self->{HSC_no_more} ? 'close' : 'keep-alive')
372             , @default_headers
373             , 'Content-Type' => $ct;
374 0 0         push @headers, 'Content-Encoding' => $ce if $ce;
375             }
376              
377 0           my ($size, $mtime) = (stat $from_fh)[7,9];
378 0 0         push @headers, 'Content-Length' => $size if $size;
379              
380 0           my $status = RC_OK;
381 0 0         if($mtime)
382 0 0         { if(my $ims = $req->header('If-Modified-Since'))
383 0           { my $imstime = str2time $ims;
384 0 0         $status = RC_NOT_MODIFIED if $mtime==$imstime;
385             }
386 0           push @headers, 'Last-Modified' => time2str($mtime);
387             }
388              
389 0           my $resp = HTTP::Response
390             ->new($status, status_message($status), \@headers);
391              
392 0           $resp->request($req);
393 0           $resp->protocol($req->protocol);
394              
395 0           my ($mux, $clientfh) = @$self{'HSC_mux', 'HSC_fh'};
396 0           $mux->write($clientfh, $resp->as_string("\r\n"));
397              
398 0 0         if($req->method eq 'HEAD')
399 0           { info $req->id." sent head of $file";
400 0           return $resp;
401             }
402 0 0         if($status==RC_NOT_MODIFIED)
403 0           { info $req->id." file $file was not modified";
404 0           return $resp;
405             }
406              
407 0           info $req->id." sent file $file, ${size}b";
408              
409 0           my $pump = _PUMP::PROXY->new($clientfh, $callback);
410 0           $mux->add($from_fh);
411 0           $mux->set_callback_object($pump, $from_fh);
412 0           undef;
413             }
414              
415              
416             sub cancelConnection()
417 0     0 0   { my $self = shift;
418 0           info $self->id.' connection cancelled';
419 0           delete @$self{'HSC_next', 'HSC_requests'};
420 0           $self->closeConnection;
421             }
422              
423              
424             sub closeConnection()
425 0     0 0   { my $self = shift;
426 0           info $self->id.' connection closed';
427 0           $self->{HSC_no_more}++;
428             }
429              
430              
431             my %filetype =
432             ( &S_IFSOCK => 's', &S_IFLNK => 'l', &S_IFREG => '-', &S_IFBLK => 'b'
433             , &S_IFDIR => 'd', &S_IFCHR => 'c', &S_IFIFO => 'p');
434              
435             my @flags = ('---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx');
436            
437             sub directoryList($$$@)
438 0     0 1   { my ($self, $req, $dirname, $callback, %opts) = @_;
439              
440 0           trace $self->id. " listing of directory $dirname";
441 0 0         opendir my $from_dir, $dirname
442             or return $self->sendStatus($req, RC_FORBIDDEN);
443              
444 0   0       my $names = $opts{names} || qr/^[^.]/;
445             my $prefilter
446 0     0     = ref $names eq 'Regexp' ? sub { $_[0] =~ $names }
447 0 0         : ref $names eq 'CODE' ? $names
    0          
448             : panic "directoryList(names) must be regexp or code, not $names";
449              
450 0   0 0     my $postfilter = $opts{filter} || sub {1};
  0            
451 0 0         ref $postfilter eq 'CODE'
452             or panic "directoryList(filter) must be code, not $postfilter";
453              
454 0           my $hide_symlinks = $opts{hide_symlinks};
455              
456             my $run_async = sub
457 0     0     { my (%dirlist, %users, %groups);
458 0           foreach my $name (grep {$prefilter->($_)} readdir $from_dir)
  0            
459 0           { my $path = $dirname.$name;
460 0           my %d = (name => $name, path => $path);
461 0 0         @d{@stat_fields}
462             = $hide_symlinks ? stat($path) : lstat($path);
463              
464 0 0 0       if(!$hide_symlinks && -l _)
    0          
    0          
465 0           { @d{qw/kind is_symlink /} = ('SYMLINK', 1)}
466 0           elsif(-d _) { @d{qw/kind is_directory/} = ('DIRECTORY',1)}
467 0           elsif(-f _) { @d{qw/kind is_file /} = ('FILE', 1)}
468 0           else { @d{qw/kind is_other /} = ('OTHER', 1)}
469              
470 0 0         $postfilter->(\%d)
471             or next;
472              
473 0 0         if($d{is_symlink})
    0          
    0          
474 0           { my $sl = $d{symlink_dest} = readlink $path;
475 0           $d{symlink_dest_exists} = -e $sl;
476             }
477             elsif($d{is_file})
478 0           { my ($s, $l) = ($d{size}, ' ');
479 0 0         ($s,$l) = ($s/1024, 'kB') if $s > 1024;
480 0 0         ($s,$l) = ($s/1024, 'MB') if $s > 1024;
481 0 0         ($s,$l) = ($s/1024, 'GB') if $s > 1024;
482 0 0         $d{size_nice} = sprintf +($s>=100?"%.0f%s":"%.1f%s"), $s,$l;
483             }
484             elsif($d{is_directory})
485 0           { $d{name} .= '/';
486             }
487              
488 0 0 0       if($d{is_file} || $d{is_directory})
489 0   0       { $d{user} = $users{$d{uid}} ||= getpwuid $d{uid};
490 0   0       $d{group} = $users{$d{gid}} ||= getgrgid $d{gid};
491 0           my $mode = $d{mode};
492 0   0       my $b = $filetype{$mode & S_IFMT} || '?';
493 0           $b .= $flags[ ($mode & S_IRWXU) >> 6 ];
494 0 0         substr($b, -1, -1) = 's' if $mode & S_ISUID;
495 0           $b .= $flags[ ($mode & S_IRWXG) >> 3 ];
496 0 0         substr($b, -1, -1) = 's' if $mode & S_ISGID;
497 0           $b .= $flags[ $mode & S_IRWXO ];
498 0 0         substr($b, -1, -1) = 't' if $mode & S_ISVTX;
499 0           $d{flags} = $b;
500 0           $d{mtime_nice} = strftime "%F %T", localtime $d{mtime};
501             }
502 0           $dirlist{$name} = \%d;
503             }
504 0           \%dirlist;
505 0           };
506              
507 0           $self->async($req, $run_async, $callback);
508 0           undef;
509             }
510              
511              
512             sub async
513 0     0 0   { my ($self, $req, $run, $after) = @_;
514              
515 0           my ($reader, $writer);
516 0 0         unless(pipe $reader, $writer)
517 0           { $self->sendStatus($req, RC_INTERNAL_SERVER_ERROR, "pipe: $!");
518 0           return 0;
519             }
520            
521 0           my $pid = fork;
522 0 0         unless(defined $pid)
523 0           { trace "failed to fork: $!";
524 0           $self->sendStatus($req, RC_INTERNAL_SERVER_ERROR, "fork: $!");
525 0           return 0;
526             }
527              
528 0 0         if($pid==0) # child
529 0           { close $reader;
530 0           my %data;
531 0           $data{user} = [ $run->() ];
532 0           $writer->print(freeze \%data);
533 0           exit 0;
534             }
535              
536             # parent
537 0           close $writer;
538            
539 0           my $mux = $self->{HSC_mux};
540 0           $mux->add($reader);
541             my $callback = sub
542 0     0     { my $data = eval { thaw ${$_[0]} };
  0            
  0            
543 0           $mux->remove($reader);
544 0           waitpid $pid, 0; # need to check return
545 0           $after->(@{$data->{user}});
  0            
546 0           $self->handleRequests;
547 0           };
548              
549 0           $mux->set_callback_object(_PUMP::READFILE->new($callback), $reader);
550 0           1;
551             }
552              
553              
554             sub load($$)
555 0     0 0   { my ($self, $file, $cb) = @_;
556 0           my ($f, $callback);
557              
558 0 0         if(ref $file)
559 0           { ($f, $callback) = ($file, $cb);
560             }
561             else
562 0 0         { open $f, '<', $file
563             or return $cb->(undef);
564              
565 0           trace "reading file $file";
566             $callback = sub
567 0     0     { close $f;
568 0           $cb->($_[0]);
569 0           };
570             }
571              
572 0           my $mux = $self->{HSC_mux};
573 0           $mux->add($f);
574 0           $mux->set_callback_object(_PUMP::READFILE->new($callback), $f);
575 0           undef;
576             }
577              
578 0     0 0   sub readFile(@) {die "readFile() renamed to load() in 0.11"}
579              
580              
581             sub save($$$)
582 0     0 0   { my ($self, $file, $data, $cb) = @_;
583 0           my ($f, $callback);
584 0           my $mux = $self->{HSC_mux};
585              
586 0 0         if(ref $file)
587 0           { ($f, $callback) = ($f, $cb);
588             }
589             else
590             { # IO::Multiplex is not able to deal with write-only file-handles,
591             # Therefore '+>' i.s.o. simply '>' rt.cpan.org#39131
592 0 0         open $f, '+>', $file
593             or return $cb->(undef);
594              
595 0           trace "writing file $file";
596             $callback = sub
597 0     0     { close $f;
598 0           $mux->remove($f);
599 0           $cb->(@_);
600 0           };
601             }
602              
603 0           $mux->add($f);
604 0           $mux->set_callback_object(_PUMP::WRITEFILE->new($callback), $f);
605 0 0         $mux->write($f, ref $data eq 'SCALAR' ? $$data : $data);
606 0           undef;
607             }
608 0     0 0   sub writeFile(@) {die "writeFile() renamed to save() in 0.11"}
609              
610             #------------------------
611              
612              
613             ##### _PUMP::PROXY
614             # Copy from incoming file-handle to out-going filehandle.
615              
616             package _PUMP::PROXY;
617 1     1   10 use vars '$VERSION';
  1         2  
  1         188  
618             $VERSION = '0.11';
619              
620              
621             # $class->new($outfh,$callback)
622 0     0     sub new($$) { my $class = shift; bless \@_, $class }
  0            
623              
624             sub mux_input($$$)
625 0     0     { my ($outfh, $mux, $refdata) = ($_[0][0], $_[1], $_[3]);
626 0           $mux->write($outfh, $$refdata);
627 0           $$refdata = '';
628             }
629              
630 0     0     sub mux_close() { shift->[1]->() }
631              
632             ##### _PUMP::READFILE
633             # Copy from incoming file-handle into a variable
634              
635             package _PUMP::READFILE;
636 1     1   7 use vars '$VERSION';
  1         3  
  1         134  
637             $VERSION = '0.11';
638              
639              
640             # $class->new($callback)
641 0     0     sub new($) { my $class = shift; bless \@_, $class }
  0            
642              
643             sub mux_eof($$$$)
644 0     0     { my ($self, $mux, $fh, $refdata) = @_;
645 0           $self->[0]->($refdata);
646             }
647              
648             ##### _PUMP::WRITEFILE
649             # Copy data to a file, and then call the callback
650              
651             package _PUMP::WRITEFILE;
652 1     1   6 use vars '$VERSION';
  1         2  
  1         143  
653             $VERSION = '0.11';
654              
655              
656             # $class->new($callback)
657 0     0     sub new($) { my $class = shift; bless \@_, $class }
  0            
658              
659             sub mux_eof($$)
660 0     0     { my ($self, $mux, $fh) = @_;
661 0           $self->[0]->();
662             }
663              
664             1;