line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTTP::Daemon; # git description: v6.14-30-gd7c9267 |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: A simple http server class |
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
65669
|
use strict; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
29
|
|
6
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
36
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '6.15'; |
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
519
|
use Socket (); |
|
1
|
|
|
|
|
3650
|
|
|
1
|
|
|
|
|
34
|
|
11
|
1
|
|
|
1
|
|
570
|
use IO::Socket::IP; |
|
1
|
|
|
|
|
31694
|
|
|
1
|
|
|
|
|
4
|
|
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
|
133
|
my ($class, %args) = @_; |
20
|
1
|
|
50
|
|
|
11
|
$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
|
|
|
|
|
11
|
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
|
1962
|
my $self = shift; |
48
|
|
|
|
|
|
|
|
49
|
1
|
|
|
|
|
3
|
my $host = $self->sockhost; |
50
|
1
|
|
|
|
|
8
|
$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
|
|
|
|
|
10
|
my $url = $self->_default_scheme . "://" . $host; |
56
|
1
|
|
|
|
|
8
|
my $port = $self->sockport; |
57
|
1
|
50
|
|
|
|
75
|
$url .= ":$port" if $port != $self->_default_port; |
58
|
1
|
|
|
|
|
3
|
$url .= "/"; |
59
|
1
|
|
|
|
|
7
|
$url; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub _default_port { |
63
|
1
|
|
|
1
|
|
7
|
80; |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub _default_scheme { |
67
|
1
|
|
|
1
|
|
4
|
"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
|
|
889
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
24
|
|
78
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
41
|
|
79
|
|
|
|
|
|
|
|
80
|
1
|
|
|
1
|
|
5
|
use IO::Socket::IP (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
60
|
|
81
|
|
|
|
|
|
|
our @ISA = qw(IO::Socket::IP); |
82
|
|
|
|
|
|
|
our $DEBUG; |
83
|
|
|
|
|
|
|
*DEBUG = \$HTTP::Daemon::DEBUG; |
84
|
|
|
|
|
|
|
|
85
|
1
|
|
|
1
|
|
475
|
use HTTP::Request (); |
|
1
|
|
|
|
|
22635
|
|
|
1
|
|
|
|
|
26
|
|
86
|
1
|
|
|
1
|
|
453
|
use HTTP::Response (); |
|
1
|
|
|
|
|
8745
|
|
|
1
|
|
|
|
|
25
|
|
87
|
1
|
|
|
1
|
|
7
|
use HTTP::Status; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
261
|
|
88
|
1
|
|
|
1
|
|
471
|
use HTTP::Date qw(time2str); |
|
1
|
|
|
|
|
3803
|
|
|
1
|
|
|
|
|
68
|
|
89
|
1
|
|
|
1
|
|
472
|
use LWP::MediaTypes qw(guess_media_type); |
|
1
|
|
|
|
|
17209
|
|
|
1
|
|
|
|
|
72
|
|
90
|
1
|
|
|
1
|
|
7
|
use Carp (); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3878
|
|
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
|
|
|
|
14
|
return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i; |
403
|
2
|
|
|
|
|
11
|
$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__ |