| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package HTTP::Daemon; # git description: v6.15-4-gbab5825 |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: A simple http server class |
|
4
|
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
68629
|
use strict; |
|
|
1
|
|
|
|
|
12
|
|
|
|
1
|
|
|
|
|
28
|
|
|
6
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
37
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '6.16'; |
|
9
|
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
601
|
use Socket (); |
|
|
1
|
|
|
|
|
3779
|
|
|
|
1
|
|
|
|
|
37
|
|
|
11
|
1
|
|
|
1
|
|
536
|
use IO::Socket::IP; |
|
|
1
|
|
|
|
|
32373
|
|
|
|
1
|
|
|
|
|
6
|
|
|
12
|
|
|
|
|
|
|
our @ISA = qw(IO::Socket::IP); |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $PROTO = "HTTP/1.1"; |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $DEBUG; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub new { |
|
19
|
1
|
|
|
1
|
1
|
137
|
my ($class, %args) = @_; |
|
20
|
1
|
|
50
|
|
|
9
|
$args{Listen} ||= 5; |
|
21
|
1
|
|
50
|
|
|
6
|
$args{Proto} ||= 'tcp'; |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# Handle undefined or empty local address the same way as |
|
24
|
|
|
|
|
|
|
# IO::Socket::INET -- use unspecified address |
|
25
|
1
|
|
|
|
|
3
|
for my $key (qw(LocalAddr LocalHost)) { |
|
26
|
2
|
0
|
0
|
|
|
7
|
if (exists $args{$key} && (!defined $args{$key} || $args{$key} eq '')) { |
|
|
|
|
33
|
|
|
|
|
|
27
|
0
|
|
|
|
|
0
|
delete $args{$key}; |
|
28
|
|
|
|
|
|
|
} |
|
29
|
|
|
|
|
|
|
} |
|
30
|
1
|
|
|
|
|
10
|
return $class->SUPER::new(%args); |
|
31
|
|
|
|
|
|
|
} |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub accept { |
|
34
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
35
|
0
|
|
0
|
|
|
0
|
my $pkg = shift || "HTTP::Daemon::ClientConn"; |
|
36
|
0
|
|
|
|
|
0
|
my ($sock, $peer) = $self->SUPER::accept($pkg); |
|
37
|
0
|
0
|
|
|
|
0
|
if ($sock) { |
|
38
|
0
|
|
|
|
|
0
|
${*$sock}{'httpd_daemon'} = $self; |
|
|
0
|
|
|
|
|
0
|
|
|
39
|
0
|
0
|
|
|
|
0
|
return wantarray ? ($sock, $peer) : $sock; |
|
40
|
|
|
|
|
|
|
} |
|
41
|
|
|
|
|
|
|
else { |
|
42
|
0
|
|
|
|
|
0
|
return; |
|
43
|
|
|
|
|
|
|
} |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub url { |
|
47
|
1
|
|
|
1
|
1
|
1951
|
my $self = shift; |
|
48
|
|
|
|
|
|
|
|
|
49
|
1
|
|
|
|
|
5
|
my $host = $self->sockhost; |
|
50
|
1
|
|
|
|
|
7
|
$host =~ s/%/%25/g; |
|
51
|
1
|
50
|
|
|
|
5
|
$host = "127.0.0.1" if $host eq "0.0.0.0"; |
|
52
|
1
|
50
|
|
|
|
3
|
$host = "::1" if $host eq "::"; |
|
53
|
1
|
50
|
|
|
|
4
|
$host = "[$host]" if $self->sockdomain == Socket::AF_INET6; |
|
54
|
|
|
|
|
|
|
|
|
55
|
1
|
|
|
|
|
9
|
my $url = $self->_default_scheme . "://" . $host; |
|
56
|
1
|
|
|
|
|
9
|
my $port = $self->sockport; |
|
57
|
1
|
50
|
|
|
|
74
|
$url .= ":$port" if $port != $self->_default_port; |
|
58
|
1
|
|
|
|
|
2
|
$url .= "/"; |
|
59
|
1
|
|
|
|
|
8
|
$url; |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub _default_port { |
|
63
|
1
|
|
|
1
|
|
6
|
80; |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub _default_scheme { |
|
67
|
1
|
|
|
1
|
|
3
|
"http"; |
|
68
|
|
|
|
|
|
|
} |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub product_tokens { |
|
71
|
0
|
|
|
0
|
1
|
0
|
"libwww-perl-daemon/$HTTP::Daemon::VERSION"; |
|
72
|
|
|
|
|
|
|
} |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
package # hide from PAUSE |
|
75
|
|
|
|
|
|
|
HTTP::Daemon::ClientConn; |
|
76
|
|
|
|
|
|
|
|
|
77
|
1
|
|
|
1
|
|
948
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
31
|
|
|
78
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
49
|
|
|
79
|
|
|
|
|
|
|
|
|
80
|
1
|
|
|
1
|
|
6
|
use IO::Socket::IP (); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
67
|
|
|
81
|
|
|
|
|
|
|
our @ISA = qw(IO::Socket::IP); |
|
82
|
|
|
|
|
|
|
our $DEBUG; |
|
83
|
|
|
|
|
|
|
*DEBUG = \$HTTP::Daemon::DEBUG; |
|
84
|
|
|
|
|
|
|
|
|
85
|
1
|
|
|
1
|
|
470
|
use HTTP::Request (); |
|
|
1
|
|
|
|
|
23731
|
|
|
|
1
|
|
|
|
|
24
|
|
|
86
|
1
|
|
|
1
|
|
461
|
use HTTP::Response (); |
|
|
1
|
|
|
|
|
7328
|
|
|
|
1
|
|
|
|
|
26
|
|
|
87
|
1
|
|
|
1
|
|
7
|
use HTTP::Status; |
|
|
1
|
|
|
|
|
8
|
|
|
|
1
|
|
|
|
|
260
|
|
|
88
|
1
|
|
|
1
|
|
448
|
use HTTP::Date qw(time2str); |
|
|
1
|
|
|
|
|
3830
|
|
|
|
1
|
|
|
|
|
65
|
|
|
89
|
1
|
|
|
1
|
|
475
|
use LWP::MediaTypes qw(guess_media_type); |
|
|
1
|
|
|
|
|
17171
|
|
|
|
1
|
|
|
|
|
91
|
|
|
90
|
1
|
|
|
1
|
|
7
|
use Carp (); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
3914
|
|
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# "\r\n" is not portable |
|
93
|
|
|
|
|
|
|
my $CRLF = "\015\012"; |
|
94
|
|
|
|
|
|
|
my $HTTP_1_0 = _http_version("HTTP/1.0"); |
|
95
|
|
|
|
|
|
|
my $HTTP_1_1 = _http_version("HTTP/1.1"); |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
sub get_request { |
|
99
|
0
|
|
|
0
|
|
0
|
my ($self, $only_headers) = @_; |
|
100
|
0
|
0
|
|
|
|
0
|
if (${*$self}{'httpd_nomore'}) { |
|
|
0
|
|
|
|
|
0
|
|
|
101
|
0
|
|
|
|
|
0
|
$self->reason("No more requests from this connection"); |
|
102
|
0
|
|
|
|
|
0
|
return; |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
0
|
|
|
|
|
0
|
$self->reason(""); |
|
106
|
0
|
|
|
|
|
0
|
my $buf = ${*$self}{'httpd_rbuf'}; |
|
|
0
|
|
|
|
|
0
|
|
|
107
|
0
|
0
|
|
|
|
0
|
$buf = "" unless defined $buf; |
|
108
|
|
|
|
|
|
|
|
|
109
|
0
|
|
|
|
|
0
|
my $timeout = ${*$self}{'io_socket_timeout'}; |
|
|
0
|
|
|
|
|
0
|
|
|
110
|
0
|
|
|
|
|
0
|
my $fdset = ""; |
|
111
|
0
|
|
|
|
|
0
|
vec($fdset, $self->fileno, 1) = 1; |
|
112
|
0
|
|
|
|
|
0
|
local ($_); |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
READ_HEADER: |
|
115
|
0
|
|
|
|
|
0
|
while (1) { |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# loop until we have the whole header in $buf |
|
118
|
0
|
|
|
|
|
0
|
$buf =~ s/^(?:\015?\012)+//; # ignore leading blank lines |
|
119
|
0
|
0
|
|
|
|
0
|
if ($buf =~ /\012/) { # potential, has at least one line |
|
|
|
0
|
|
|
|
|
|
|
120
|
0
|
0
|
|
|
|
0
|
if ($buf =~ /^\w+[^\012]+HTTP\/\d+\.\d+\015?\012/) { |
|
121
|
0
|
0
|
|
|
|
0
|
if ($buf =~ /\015?\012\015?\012/) { |
|
|
|
0
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
0
|
last READ_HEADER; # we have it |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
elsif (length($buf) > 16 * 1024) { |
|
125
|
0
|
|
|
|
|
0
|
$self->send_error(413); # REQUEST_ENTITY_TOO_LARGE |
|
126
|
0
|
|
|
|
|
0
|
$self->reason("Very long header"); |
|
127
|
0
|
|
|
|
|
0
|
return; |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
else { |
|
131
|
0
|
|
|
|
|
0
|
last READ_HEADER; # HTTP/0.9 client |
|
132
|
|
|
|
|
|
|
} |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
elsif (length($buf) > 16 * 1024) { |
|
135
|
0
|
|
|
|
|
0
|
$self->send_error(414); # REQUEST_URI_TOO_LARGE |
|
136
|
0
|
|
|
|
|
0
|
$self->reason("Very long first line"); |
|
137
|
0
|
|
|
|
|
0
|
return; |
|
138
|
|
|
|
|
|
|
} |
|
139
|
0
|
0
|
|
|
|
0
|
print STDERR "Need more data for complete header\n" if $DEBUG; |
|
140
|
0
|
0
|
|
|
|
0
|
return unless $self->_need_more($buf, $timeout, $fdset); |
|
141
|
|
|
|
|
|
|
} |
|
142
|
0
|
0
|
|
|
|
0
|
if ($buf !~ s/^(\S+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) { |
|
143
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_client_proto'} = _http_version("HTTP/1.0"); |
|
|
0
|
|
|
|
|
0
|
|
|
144
|
0
|
|
|
|
|
0
|
$self->send_error(400); # BAD_REQUEST |
|
145
|
0
|
|
|
|
|
0
|
$self->reason("Bad request line: $buf"); |
|
146
|
0
|
|
|
|
|
0
|
return; |
|
147
|
|
|
|
|
|
|
} |
|
148
|
0
|
|
|
|
|
0
|
my $method = $1; |
|
149
|
0
|
|
|
|
|
0
|
my $uri = $2; |
|
150
|
0
|
|
0
|
|
|
0
|
my $proto = $3 || "HTTP/0.9"; |
|
151
|
0
|
0
|
|
|
|
0
|
$uri = "http://$uri" if $method eq "CONNECT"; |
|
152
|
0
|
|
|
|
|
0
|
$uri = $HTTP::URI_CLASS->new($uri, $self->daemon->url); |
|
153
|
0
|
|
|
|
|
0
|
my $r = HTTP::Request->new($method, $uri); |
|
154
|
0
|
|
|
|
|
0
|
$r->protocol($proto); |
|
155
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_client_proto'} = $proto = _http_version($proto); |
|
|
0
|
|
|
|
|
0
|
|
|
156
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_head'} = ($method eq "HEAD"); |
|
|
0
|
|
|
|
|
0
|
|
|
157
|
|
|
|
|
|
|
|
|
158
|
0
|
0
|
|
|
|
0
|
if ($proto >= $HTTP_1_0) { |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# we expect to find some headers |
|
161
|
0
|
|
|
|
|
0
|
my ($key, $val); |
|
162
|
|
|
|
|
|
|
HEADER: |
|
163
|
0
|
|
|
|
|
0
|
while ($buf =~ s/^([^\012]*)\012//) { |
|
164
|
0
|
|
|
|
|
0
|
$_ = $1; |
|
165
|
0
|
|
|
|
|
0
|
s/\015$//; |
|
166
|
0
|
0
|
|
|
|
0
|
if (/^([^:\s]+)\s*:\s*(.*)/) { |
|
|
|
0
|
|
|
|
|
|
|
167
|
0
|
0
|
|
|
|
0
|
$r->push_header($key, $val) if $key; |
|
168
|
0
|
|
|
|
|
0
|
($key, $val) = ($1, $2); |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
elsif (/^\s+(.*)/) { |
|
171
|
0
|
|
|
|
|
0
|
$val .= " $1"; |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
else { |
|
174
|
0
|
|
|
|
|
0
|
last HEADER; |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
} |
|
177
|
0
|
0
|
|
|
|
0
|
$r->push_header($key, $val) if $key; |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
0
|
my $conn = $r->header('Connection'); |
|
181
|
0
|
0
|
|
|
|
0
|
if ($proto >= $HTTP_1_1) { |
|
182
|
0
|
0
|
0
|
|
|
0
|
${*$self}{'httpd_nomore'}++ if $conn && lc($conn) =~ /\bclose\b/; |
|
|
0
|
|
|
|
|
0
|
|
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
else { |
|
185
|
0
|
0
|
0
|
|
|
0
|
${*$self}{'httpd_nomore'}++ |
|
|
0
|
|
|
|
|
0
|
|
|
186
|
|
|
|
|
|
|
unless $conn && lc($conn) =~ /\bkeep-alive\b/; |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
|
|
189
|
0
|
0
|
|
|
|
0
|
if ($only_headers) { |
|
190
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_rbuf'} = $buf; |
|
|
0
|
|
|
|
|
0
|
|
|
191
|
0
|
|
|
|
|
0
|
return $r; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# Find out how much content to read |
|
195
|
0
|
|
|
|
|
0
|
my $tr_enc = $r->header('Transfer-Encoding'); |
|
196
|
0
|
|
|
|
|
0
|
my $ct_type = $r->header('Content-Type'); |
|
197
|
0
|
|
|
|
|
0
|
my $ct_len = $r->header('Content-Length'); |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# Act on the Expect header, if it's there |
|
200
|
0
|
|
|
|
|
0
|
for my $e ($r->header('Expect')) { |
|
201
|
0
|
0
|
|
|
|
0
|
if (lc($e) eq '100-continue') { |
|
202
|
0
|
|
|
|
|
0
|
$self->send_status_line(100); |
|
203
|
0
|
|
|
|
|
0
|
$self->send_crlf; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
else { |
|
206
|
0
|
|
|
|
|
0
|
$self->send_error(417); |
|
207
|
0
|
|
|
|
|
0
|
$self->reason("Unsupported Expect header value"); |
|
208
|
0
|
|
|
|
|
0
|
return; |
|
209
|
|
|
|
|
|
|
} |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
|
|
212
|
0
|
0
|
0
|
|
|
0
|
if ($tr_enc && lc($tr_enc) eq 'chunked') { |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# Handle chunked transfer encoding |
|
215
|
0
|
|
|
|
|
0
|
my $body = ""; |
|
216
|
|
|
|
|
|
|
CHUNK: |
|
217
|
0
|
|
|
|
|
0
|
while (1) { |
|
218
|
0
|
0
|
|
|
|
0
|
print STDERR "Chunked\n" if $DEBUG; |
|
219
|
0
|
0
|
|
|
|
0
|
if ($buf =~ s/^([^\012]*)\012//) { |
|
220
|
0
|
|
|
|
|
0
|
my $chunk_head = $1; |
|
221
|
0
|
0
|
|
|
|
0
|
unless ($chunk_head =~ /^([0-9A-Fa-f]+)/) { |
|
222
|
0
|
|
|
|
|
0
|
$self->send_error(400); |
|
223
|
0
|
|
|
|
|
0
|
$self->reason("Bad chunk header $chunk_head"); |
|
224
|
0
|
|
|
|
|
0
|
return; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
0
|
|
|
|
|
0
|
my $size = hex($1); |
|
227
|
0
|
0
|
|
|
|
0
|
last CHUNK if $size == 0; |
|
228
|
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
0
|
my $missing = $size - length($buf) + 2; # 2=CRLF at chunk end |
|
230
|
|
|
|
|
|
|
# must read until we have a complete chunk |
|
231
|
0
|
|
|
|
|
0
|
while ($missing > 0) { |
|
232
|
0
|
0
|
|
|
|
0
|
print STDERR "Need $missing more bytes\n" if $DEBUG; |
|
233
|
0
|
|
|
|
|
0
|
my $n = $self->_need_more($buf, $timeout, $fdset); |
|
234
|
0
|
0
|
|
|
|
0
|
return unless $n; |
|
235
|
0
|
|
|
|
|
0
|
$missing -= $n; |
|
236
|
|
|
|
|
|
|
} |
|
237
|
0
|
|
|
|
|
0
|
$body .= substr($buf, 0, $size); |
|
238
|
0
|
|
|
|
|
0
|
substr($buf, 0, $size + 2) = ''; |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
else { |
|
242
|
|
|
|
|
|
|
# need more data in order to have a complete chunk header |
|
243
|
0
|
0
|
|
|
|
0
|
return unless $self->_need_more($buf, $timeout, $fdset); |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
} |
|
246
|
0
|
|
|
|
|
0
|
$r->content($body); |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# pretend it was a normal entity body |
|
249
|
0
|
|
|
|
|
0
|
$r->remove_header('Transfer-Encoding'); |
|
250
|
0
|
|
|
|
|
0
|
$r->header('Content-Length', length($body)); |
|
251
|
|
|
|
|
|
|
|
|
252
|
0
|
|
|
|
|
0
|
my ($key, $val); |
|
253
|
|
|
|
|
|
|
FOOTER: |
|
254
|
0
|
|
|
|
|
0
|
while (1) { |
|
255
|
0
|
0
|
|
|
|
0
|
if ($buf !~ /\012/) { |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# need at least one line to look at |
|
258
|
0
|
0
|
|
|
|
0
|
return unless $self->_need_more($buf, $timeout, $fdset); |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
else { |
|
261
|
0
|
|
|
|
|
0
|
$buf =~ s/^([^\012]*)\012//; |
|
262
|
0
|
|
|
|
|
0
|
$_ = $1; |
|
263
|
0
|
|
|
|
|
0
|
s/\015$//; |
|
264
|
0
|
0
|
|
|
|
0
|
if (/^([\w\-]+)\s*:\s*(.*)/) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
265
|
0
|
0
|
|
|
|
0
|
$r->push_header($key, $val) if $key; |
|
266
|
0
|
|
|
|
|
0
|
($key, $val) = ($1, $2); |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
elsif (/^\s+(.*)/) { |
|
269
|
0
|
|
|
|
|
0
|
$val .= " $1"; |
|
270
|
|
|
|
|
|
|
} |
|
271
|
|
|
|
|
|
|
elsif (!length) { |
|
272
|
0
|
|
|
|
|
0
|
last FOOTER; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
else { |
|
275
|
0
|
|
|
|
|
0
|
$self->reason("Bad footer syntax"); |
|
276
|
0
|
|
|
|
|
0
|
return; |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
} |
|
280
|
0
|
0
|
|
|
|
0
|
$r->push_header($key, $val) if $key; |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
} |
|
283
|
|
|
|
|
|
|
elsif ($tr_enc) { |
|
284
|
0
|
|
|
|
|
0
|
$self->send_error(501); # Unknown transfer encoding |
|
285
|
0
|
|
|
|
|
0
|
$self->reason("Unknown transfer encoding '$tr_enc'"); |
|
286
|
0
|
|
|
|
|
0
|
return; |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
elsif ($ct_len) { |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
# After a security issue, we ensure we comply to |
|
292
|
|
|
|
|
|
|
# RFC-7230 -- HTTP/1.1 Message Syntax and Routing |
|
293
|
|
|
|
|
|
|
# section 3.3.2 -- Content-Length |
|
294
|
|
|
|
|
|
|
# section 3.3.3 -- Message Body Length |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# split and clean up Content-Length ', ' separated string |
|
297
|
0
|
|
|
|
|
0
|
my @vals = map {my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
298
|
|
|
|
|
|
|
split ',', $ct_len; |
|
299
|
|
|
|
|
|
|
# check that they are all numbers (RFC: Content-Length = 1*DIGIT) |
|
300
|
0
|
|
|
|
|
0
|
my @nums = grep { /^[0-9]+$/} @vals; |
|
|
0
|
|
|
|
|
0
|
|
|
301
|
0
|
0
|
|
|
|
0
|
unless (@vals == @nums) { |
|
302
|
0
|
|
|
|
|
0
|
my $reason = "Content-Length value must be an unsigned integer"; |
|
303
|
0
|
|
|
|
|
0
|
$self->send_error(400, $reason); |
|
304
|
0
|
|
|
|
|
0
|
$self->reason($reason); |
|
305
|
0
|
|
|
|
|
0
|
return; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
# check they are all the same |
|
308
|
0
|
|
|
|
|
0
|
my $ct_len = shift @nums; |
|
309
|
0
|
|
|
|
|
0
|
foreach (@nums) { |
|
310
|
0
|
0
|
|
|
|
0
|
next if $_ == $ct_len; |
|
311
|
0
|
|
|
|
|
0
|
my $reason = "Content-Length values are not the same"; |
|
312
|
0
|
|
|
|
|
0
|
$self->send_error(400, $reason); |
|
313
|
0
|
|
|
|
|
0
|
$self->reason($reason); |
|
314
|
0
|
|
|
|
|
0
|
return; |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
# ensure we have now a fixed header, with only 1 value |
|
317
|
0
|
|
|
|
|
0
|
$r->header('Content-Length' => $ct_len); |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
# Plain body specified by "Content-Length" |
|
320
|
0
|
|
|
|
|
0
|
my $missing = $ct_len - length($buf); |
|
321
|
0
|
|
|
|
|
0
|
while ($missing > 0) { |
|
322
|
0
|
0
|
|
|
|
0
|
print "Need $missing more bytes of content\n" if $DEBUG; |
|
323
|
0
|
|
|
|
|
0
|
my $n = $self->_need_more($buf, $timeout, $fdset); |
|
324
|
0
|
0
|
|
|
|
0
|
return unless $n; |
|
325
|
0
|
|
|
|
|
0
|
$missing -= $n; |
|
326
|
|
|
|
|
|
|
} |
|
327
|
0
|
0
|
|
|
|
0
|
if (length($buf) > $ct_len) { |
|
328
|
0
|
|
|
|
|
0
|
$r->content(substr($buf, 0, $ct_len)); |
|
329
|
0
|
|
|
|
|
0
|
substr($buf, 0, $ct_len) = ''; |
|
330
|
|
|
|
|
|
|
} |
|
331
|
|
|
|
|
|
|
else { |
|
332
|
0
|
|
|
|
|
0
|
$r->content($buf); |
|
333
|
0
|
|
|
|
|
0
|
$buf = ''; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
elsif ($ct_type && $ct_type =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*("?)(\w+)\1/i) { |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# Handle multipart content type |
|
339
|
0
|
|
|
|
|
0
|
my $boundary = "$CRLF--$2--"; |
|
340
|
0
|
|
|
|
|
0
|
my $index; |
|
341
|
0
|
|
|
|
|
0
|
while (1) { |
|
342
|
0
|
|
|
|
|
0
|
$index = index($buf, $boundary); |
|
343
|
0
|
0
|
|
|
|
0
|
last if $index >= 0; |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
# end marker not yet found |
|
346
|
0
|
0
|
|
|
|
0
|
return unless $self->_need_more($buf, $timeout, $fdset); |
|
347
|
|
|
|
|
|
|
} |
|
348
|
0
|
|
|
|
|
0
|
$index += length($boundary); |
|
349
|
0
|
|
|
|
|
0
|
$r->content(substr($buf, 0, $index)); |
|
350
|
0
|
|
|
|
|
0
|
substr($buf, 0, $index) = ''; |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
} |
|
353
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_rbuf'} = $buf; |
|
|
0
|
|
|
|
|
0
|
|
|
354
|
|
|
|
|
|
|
|
|
355
|
0
|
|
|
|
|
0
|
$r; |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
sub _need_more { |
|
359
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
#my($buf,$timeout,$fdset) = @_; |
|
362
|
0
|
0
|
|
|
|
0
|
if ($_[1]) { |
|
363
|
0
|
|
|
|
|
0
|
my ($timeout, $fdset) = @_[1, 2]; |
|
364
|
0
|
0
|
|
|
|
0
|
print STDERR "select(,,,$timeout)\n" if $DEBUG; |
|
365
|
0
|
|
|
|
|
0
|
my $n = select($fdset, undef, undef, $timeout); |
|
366
|
0
|
0
|
|
|
|
0
|
unless ($n) { |
|
367
|
0
|
0
|
|
|
|
0
|
$self->reason(defined($n) ? "Timeout" : "select: $!"); |
|
368
|
0
|
|
|
|
|
0
|
return; |
|
369
|
|
|
|
|
|
|
} |
|
370
|
|
|
|
|
|
|
} |
|
371
|
0
|
0
|
|
|
|
0
|
print STDERR "sysread()\n" if $DEBUG; |
|
372
|
0
|
|
|
|
|
0
|
my $n = sysread($self, $_[0], 2048, length($_[0])); |
|
373
|
0
|
0
|
|
|
|
0
|
$self->reason(defined($n) ? "Client closed" : "sysread: $!") unless $n; |
|
|
|
0
|
|
|
|
|
|
|
374
|
0
|
|
|
|
|
0
|
$n; |
|
375
|
|
|
|
|
|
|
} |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
sub read_buffer { |
|
378
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
379
|
0
|
|
|
|
|
0
|
my $old = ${*$self}{'httpd_rbuf'}; |
|
|
0
|
|
|
|
|
0
|
|
|
380
|
0
|
0
|
|
|
|
0
|
if (@_) { |
|
381
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_rbuf'} = shift; |
|
|
0
|
|
|
|
|
0
|
|
|
382
|
|
|
|
|
|
|
} |
|
383
|
0
|
|
|
|
|
0
|
$old; |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub reason { |
|
387
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
388
|
0
|
|
|
|
|
0
|
my $old = ${*$self}{'httpd_reason'}; |
|
|
0
|
|
|
|
|
0
|
|
|
389
|
0
|
0
|
|
|
|
0
|
if (@_) { |
|
390
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_reason'} = shift; |
|
|
0
|
|
|
|
|
0
|
|
|
391
|
|
|
|
|
|
|
} |
|
392
|
0
|
|
|
|
|
0
|
$old; |
|
393
|
|
|
|
|
|
|
} |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub proto_ge { |
|
396
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
397
|
0
|
|
|
|
|
0
|
${*$self}{'httpd_client_proto'} >= _http_version(shift); |
|
|
0
|
|
|
|
|
0
|
|
|
398
|
|
|
|
|
|
|
} |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
sub _http_version { |
|
401
|
2
|
|
|
2
|
|
4
|
local ($_) = shift; |
|
402
|
2
|
50
|
|
|
|
13
|
return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i; |
|
403
|
2
|
|
|
|
|
9
|
$1 * 1000 + $2; |
|
404
|
|
|
|
|
|
|
} |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
sub antique_client { |
|
407
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
408
|
0
|
|
|
|
|
|
${*$self}{'httpd_client_proto'} < $HTTP_1_0; |
|
|
0
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
} |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub force_last_request { |
|
412
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
413
|
0
|
|
|
|
|
|
${*$self}{'httpd_nomore'}++; |
|
|
0
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
sub head_request { |
|
417
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
418
|
0
|
|
|
|
|
|
${*$self}{'httpd_head'}; |
|
|
0
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
} |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub send_status_line { |
|
423
|
0
|
|
|
0
|
|
|
my ($self, $status, $message, $proto) = @_; |
|
424
|
0
|
0
|
|
|
|
|
return if $self->antique_client; |
|
425
|
0
|
|
0
|
|
|
|
$status ||= RC_OK; |
|
426
|
0
|
|
0
|
|
|
|
$message ||= status_message($status) || ""; |
|
|
|
|
0
|
|
|
|
|
|
427
|
0
|
|
0
|
|
|
|
$proto ||= $HTTP::Daemon::PROTO || "HTTP/1.1"; |
|
|
|
|
0
|
|
|
|
|
|
428
|
0
|
|
|
|
|
|
print $self "$proto $status $message$CRLF"; |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
sub send_crlf { |
|
432
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
433
|
0
|
|
|
|
|
|
print $self $CRLF; |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub send_basic_header { |
|
437
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
438
|
0
|
0
|
|
|
|
|
return if $self->antique_client; |
|
439
|
0
|
|
|
|
|
|
$self->send_status_line(@_); |
|
440
|
0
|
|
|
|
|
|
print $self "Date: ", time2str(time), $CRLF; |
|
441
|
0
|
|
|
|
|
|
my $product = $self->daemon->product_tokens; |
|
442
|
0
|
0
|
|
|
|
|
print $self "Server: $product$CRLF" if $product; |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
sub send_header { |
|
446
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
447
|
0
|
|
|
|
|
|
while (@_) { |
|
448
|
0
|
|
|
|
|
|
my ($k, $v) = splice(@_, 0, 2); |
|
449
|
0
|
0
|
|
|
|
|
$v = "" unless defined($v); |
|
450
|
0
|
|
|
|
|
|
print $self "$k: $v$CRLF"; |
|
451
|
|
|
|
|
|
|
} |
|
452
|
|
|
|
|
|
|
} |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
sub send_response { |
|
455
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
456
|
0
|
|
|
|
|
|
my $res = shift; |
|
457
|
0
|
0
|
|
|
|
|
if (!ref $res) { |
|
458
|
0
|
|
0
|
|
|
|
$res ||= RC_OK; |
|
459
|
0
|
|
|
|
|
|
$res = HTTP::Response->new($res, @_); |
|
460
|
|
|
|
|
|
|
} |
|
461
|
0
|
|
|
|
|
|
my $content = $res->content; |
|
462
|
0
|
|
|
|
|
|
my $chunked; |
|
463
|
0
|
0
|
|
|
|
|
unless ($self->antique_client) { |
|
464
|
0
|
|
|
|
|
|
my $code = $res->code; |
|
465
|
0
|
|
|
|
|
|
$self->send_basic_header($code, $res->message, $res->protocol); |
|
466
|
0
|
0
|
0
|
|
|
|
if ($code =~ /^(1\d\d|[23]04)$/) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# make sure content is empty |
|
469
|
0
|
|
|
|
|
|
$res->remove_header("Content-Length"); |
|
470
|
0
|
|
|
|
|
|
$content = ""; |
|
471
|
|
|
|
|
|
|
} |
|
472
|
|
|
|
|
|
|
elsif ($res->request && $res->request->method eq "HEAD") { |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# probably OK |
|
475
|
|
|
|
|
|
|
} |
|
476
|
|
|
|
|
|
|
elsif (ref($content) eq "CODE") { |
|
477
|
0
|
0
|
|
|
|
|
if ($self->proto_ge("HTTP/1.1")) { |
|
478
|
0
|
|
|
|
|
|
$res->push_header("Transfer-Encoding" => "chunked"); |
|
479
|
0
|
|
|
|
|
|
$chunked++; |
|
480
|
|
|
|
|
|
|
} |
|
481
|
|
|
|
|
|
|
else { |
|
482
|
0
|
|
|
|
|
|
$self->force_last_request; |
|
483
|
|
|
|
|
|
|
} |
|
484
|
|
|
|
|
|
|
} |
|
485
|
|
|
|
|
|
|
elsif (length($content)) { |
|
486
|
0
|
|
|
|
|
|
$res->header("Content-Length" => length($content)); |
|
487
|
|
|
|
|
|
|
} |
|
488
|
|
|
|
|
|
|
else { |
|
489
|
0
|
|
|
|
|
|
$self->force_last_request; |
|
490
|
0
|
|
|
|
|
|
$res->header('connection', 'close'); |
|
491
|
|
|
|
|
|
|
} |
|
492
|
0
|
|
|
|
|
|
print $self $res->headers_as_string($CRLF); |
|
493
|
0
|
|
|
|
|
|
print $self $CRLF; # separates headers and content |
|
494
|
|
|
|
|
|
|
} |
|
495
|
0
|
0
|
|
|
|
|
if ($self->head_request) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
# no content |
|
498
|
|
|
|
|
|
|
} |
|
499
|
|
|
|
|
|
|
elsif (ref($content) eq "CODE") { |
|
500
|
0
|
|
|
|
|
|
while (1) { |
|
501
|
0
|
|
|
|
|
|
my $chunk = &$content(); |
|
502
|
0
|
0
|
0
|
|
|
|
last unless defined($chunk) && length($chunk); |
|
503
|
0
|
0
|
|
|
|
|
if ($chunked) { |
|
504
|
0
|
|
|
|
|
|
printf $self "%x%s%s%s", length($chunk), $CRLF, $chunk, $CRLF; |
|
505
|
|
|
|
|
|
|
} |
|
506
|
|
|
|
|
|
|
else { |
|
507
|
0
|
|
|
|
|
|
print $self $chunk; |
|
508
|
|
|
|
|
|
|
} |
|
509
|
|
|
|
|
|
|
} |
|
510
|
0
|
0
|
|
|
|
|
print $self "0$CRLF$CRLF" if $chunked; # no trailers either |
|
511
|
|
|
|
|
|
|
} |
|
512
|
|
|
|
|
|
|
elsif (length $content) { |
|
513
|
0
|
|
|
|
|
|
print $self $content; |
|
514
|
|
|
|
|
|
|
} |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
sub send_redirect { |
|
518
|
0
|
|
|
0
|
|
|
my ($self, $loc, $status, $content) = @_; |
|
519
|
0
|
|
0
|
|
|
|
$status ||= RC_MOVED_PERMANENTLY; |
|
520
|
0
|
0
|
|
|
|
|
Carp::croak("Status '$status' is not redirect") unless is_redirect($status); |
|
521
|
0
|
|
|
|
|
|
$self->send_basic_header($status); |
|
522
|
0
|
|
|
|
|
|
my $base = $self->daemon->url; |
|
523
|
0
|
0
|
|
|
|
|
$loc = $HTTP::URI_CLASS->new($loc, $base) unless ref($loc); |
|
524
|
0
|
|
|
|
|
|
$loc = $loc->abs($base); |
|
525
|
0
|
|
|
|
|
|
print $self "Location: $loc$CRLF"; |
|
526
|
|
|
|
|
|
|
|
|
527
|
0
|
0
|
|
|
|
|
if ($content) { |
|
528
|
0
|
0
|
|
|
|
|
my $ct_type = $content =~ /^\s* ? "text/html" : "text/plain"; |
|
529
|
0
|
|
|
|
|
|
print $self "Content-Type: $ct_type$CRLF"; |
|
530
|
|
|
|
|
|
|
} |
|
531
|
0
|
|
|
|
|
|
print $self $CRLF; |
|
532
|
0
|
0
|
0
|
|
|
|
print $self $content if $content && !$self->head_request; |
|
533
|
0
|
|
|
|
|
|
$self->force_last_request; # no use keeping the connection open |
|
534
|
|
|
|
|
|
|
} |
|
535
|
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
sub send_error { |
|
537
|
0
|
|
|
0
|
|
|
my ($self, $status, $error) = @_; |
|
538
|
0
|
|
0
|
|
|
|
$status ||= RC_BAD_REQUEST; |
|
539
|
0
|
0
|
|
|
|
|
Carp::croak("Status '$status' is not an error") unless is_error($status); |
|
540
|
0
|
|
|
|
|
|
my $mess = status_message($status); |
|
541
|
0
|
|
0
|
|
|
|
$error ||= ""; |
|
542
|
0
|
|
|
|
|
|
$mess = <
|
|
543
|
|
|
|
|
|
|
$status $mess |
|
544
|
|
|
|
|
|
|
$status $mess |
|
545
|
|
|
|
|
|
|
$error |
|
546
|
|
|
|
|
|
|
EOT |
|
547
|
0
|
0
|
|
|
|
|
unless ($self->antique_client) { |
|
548
|
0
|
|
|
|
|
|
$self->send_basic_header($status); |
|
549
|
0
|
|
|
|
|
|
print $self "Content-Type: text/html$CRLF"; |
|
550
|
0
|
|
|
|
|
|
print $self "Content-Length: " . length($mess) . $CRLF; |
|
551
|
0
|
|
|
|
|
|
print $self $CRLF; |
|
552
|
|
|
|
|
|
|
} |
|
553
|
0
|
0
|
|
|
|
|
print $self $mess unless $self->head_request; |
|
554
|
0
|
|
|
|
|
|
$status; |
|
555
|
|
|
|
|
|
|
} |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
sub send_file_response { |
|
558
|
0
|
|
|
0
|
|
|
my ($self, $file) = @_; |
|
559
|
0
|
0
|
|
|
|
|
if (-d $file) { |
|
|
|
0
|
|
|
|
|
|
|
560
|
0
|
|
|
|
|
|
$self->send_dir($file); |
|
561
|
|
|
|
|
|
|
} |
|
562
|
|
|
|
|
|
|
elsif (-f _) { |
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
# plain file |
|
565
|
0
|
|
|
|
|
|
local (*F); |
|
566
|
0
|
0
|
|
|
|
|
sysopen(F, $file, 0) or return $self->send_error(RC_FORBIDDEN); |
|
567
|
0
|
|
|
|
|
|
binmode(F); |
|
568
|
0
|
|
|
|
|
|
my ($mime_type, $file_enc) = guess_media_type($file); |
|
569
|
0
|
|
|
|
|
|
my ($size, $mtime) = (stat _)[7, 9]; |
|
570
|
0
|
0
|
|
|
|
|
unless ($self->antique_client) { |
|
571
|
0
|
|
|
|
|
|
$self->send_basic_header; |
|
572
|
0
|
|
|
|
|
|
print $self "Content-Type: $mime_type$CRLF"; |
|
573
|
0
|
0
|
|
|
|
|
print $self "Content-Encoding: $file_enc$CRLF" if $file_enc; |
|
574
|
0
|
0
|
|
|
|
|
print $self "Content-Length: $size$CRLF" if $size; |
|
575
|
0
|
0
|
|
|
|
|
print $self "Last-Modified: ", time2str($mtime), "$CRLF" if $mtime; |
|
576
|
0
|
|
|
|
|
|
print $self $CRLF; |
|
577
|
|
|
|
|
|
|
} |
|
578
|
0
|
0
|
|
|
|
|
$self->send_file(\*F) unless $self->head_request; |
|
579
|
0
|
|
|
|
|
|
return RC_OK; |
|
580
|
|
|
|
|
|
|
} |
|
581
|
|
|
|
|
|
|
else { |
|
582
|
0
|
|
|
|
|
|
$self->send_error(RC_NOT_FOUND); |
|
583
|
|
|
|
|
|
|
} |
|
584
|
|
|
|
|
|
|
} |
|
585
|
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
sub send_dir { |
|
587
|
0
|
|
|
0
|
|
|
my ($self, $dir) = @_; |
|
588
|
0
|
0
|
|
|
|
|
$self->send_error(RC_NOT_FOUND) unless -d $dir; |
|
589
|
0
|
|
|
|
|
|
$self->send_error(RC_NOT_IMPLEMENTED); |
|
590
|
|
|
|
|
|
|
} |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub send_file { |
|
593
|
0
|
|
|
0
|
|
|
my ($self, $file) = @_; |
|
594
|
0
|
|
|
|
|
|
my $opened = 0; |
|
595
|
0
|
|
|
|
|
|
local (*FILE); |
|
596
|
0
|
0
|
|
|
|
|
if (!ref($file)) { |
|
597
|
0
|
0
|
|
|
|
|
open(FILE, $file) || return undef; |
|
598
|
0
|
|
|
|
|
|
binmode(FILE); |
|
599
|
0
|
|
|
|
|
|
$file = \*FILE; |
|
600
|
0
|
|
|
|
|
|
$opened++; |
|
601
|
|
|
|
|
|
|
} |
|
602
|
0
|
|
|
|
|
|
my $cnt = 0; |
|
603
|
0
|
|
|
|
|
|
my $buf = ""; |
|
604
|
0
|
|
|
|
|
|
my $n; |
|
605
|
0
|
|
|
|
|
|
while ($n = sysread($file, $buf, 8 * 1024)) { |
|
606
|
0
|
0
|
|
|
|
|
last if !$n; |
|
607
|
0
|
|
|
|
|
|
$cnt += $n; |
|
608
|
0
|
|
|
|
|
|
print $self $buf; |
|
609
|
|
|
|
|
|
|
} |
|
610
|
0
|
0
|
|
|
|
|
close($file) if $opened; |
|
611
|
0
|
|
|
|
|
|
$cnt; |
|
612
|
|
|
|
|
|
|
} |
|
613
|
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
sub daemon { |
|
615
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
616
|
0
|
|
|
|
|
|
${*$self}{'httpd_daemon'}; |
|
|
0
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
} |
|
618
|
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
1; |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
__END__ |