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*\ ? 'text/html' : 'text/plain'; |
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; |