| 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; |