line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=license |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Copyright © 2018 Yang Bo |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
This file is part of RSLinux. |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
RSLinux is free software: you can redistribute it and/or modify |
8
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
9
|
|
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or |
10
|
|
|
|
|
|
|
(at your option) any later version. |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
RSLinux is distributed in the hope that it will be useful, |
13
|
|
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15
|
|
|
|
|
|
|
GNU General Public License for more details. |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License |
18
|
|
|
|
|
|
|
along with RSLinux. If not, see . |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=cut |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
package App::rs; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
our $VERSION = 'v2.1.2'; |
25
|
|
|
|
|
|
|
|
26
|
1
|
|
|
1
|
|
314
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
24
|
|
27
|
1
|
|
|
1
|
|
3
|
use warnings qw/all FATAL uninitialized/; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
38
|
|
28
|
1
|
|
|
1
|
|
3
|
use feature qw/state say/; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
238
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
require XSLoader; |
31
|
|
|
|
|
|
|
XSLoader::load(); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub _require ($) { |
34
|
5
|
|
|
5
|
|
9
|
my $r = shift =~ s|::|/|gr . '.pm'; |
35
|
5
|
100
|
|
|
|
927
|
require $r if not $INC{$r}; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
sub flatten (;$) { |
38
|
13
|
50
|
|
13
|
0
|
19
|
my $v = @_ ? shift : $_; |
39
|
13
|
100
|
|
|
|
29
|
ref $v eq 'ARRAY' ? @$v : $v; |
40
|
|
|
|
|
|
|
} |
41
|
0
|
|
|
|
|
0
|
BEGIN { |
42
|
1
|
|
|
1
|
|
5
|
no strict 'refs'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
381
|
|
43
|
1
|
|
|
1
|
|
6
|
my @H = ($^H, ${^WARNING_BITS}, %^H); |
44
|
|
|
|
|
|
|
sub import { |
45
|
2
|
|
|
2
|
|
15
|
my $ns = caller . '::'; |
46
|
2
|
|
|
|
|
2
|
shift; |
47
|
2
|
|
|
|
|
7
|
while (@_) { |
48
|
4
|
|
|
|
|
5
|
my $q = shift; |
49
|
4
|
100
|
|
|
|
10
|
if ($q eq 'iautoload') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
50
|
2
|
|
|
|
|
2
|
my (@pkg, %map); |
51
|
2
|
|
|
|
|
2
|
for (@{+shift}) { |
|
2
|
|
|
|
|
4
|
|
52
|
5
|
|
|
|
|
24
|
my ($p, @f) = flatten; |
53
|
5
|
|
|
|
|
6
|
push @pkg, $p; |
54
|
5
|
|
|
|
|
8
|
for (@f) { |
55
|
8
|
|
|
|
|
10
|
my ($from, $to) = flatten; |
56
|
8
|
|
|
|
|
37
|
$from =~ s/^([$@%&*])//; |
57
|
8
|
|
33
|
|
|
28
|
$to ||= $from; |
58
|
8
|
100
|
|
|
|
13
|
if (my $s = $1) { |
59
|
5
|
|
|
|
|
7
|
state $sigil = {'$' => 'SCALAR', |
60
|
|
|
|
|
|
|
'@' => 'ARRAY', |
61
|
|
|
|
|
|
|
'%' => 'HASH', |
62
|
|
|
|
|
|
|
'&' => 'CODE', |
63
|
|
|
|
|
|
|
'*' => 'GLOB'}; |
64
|
5
|
|
|
|
|
14
|
_require $p; |
65
|
5
|
|
|
|
|
5547
|
*{$ns . $to} = *{"${p}::$from"}{$sigil->{$s}}; |
|
5
|
|
|
|
|
19
|
|
|
5
|
|
|
|
|
24
|
|
66
|
|
|
|
|
|
|
} else { |
67
|
3
|
|
|
|
|
13
|
$map{$to} = {from => $from, |
68
|
|
|
|
|
|
|
module => $p}; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
} |
72
|
2
|
|
|
|
|
903
|
*{$ns . 'AUTOLOAD'} = sub { |
73
|
|
|
|
|
|
|
# "fully qualified name of the original subroutine". |
74
|
0
|
|
|
0
|
|
0
|
my $q = our $AUTOLOAD; |
75
|
|
|
|
|
|
|
# to avoid possibly overwrite @_ by successful regular expression match. |
76
|
0
|
|
|
|
|
0
|
my ($to) = do { $q =~ /.*::(.*)/ }; |
|
0
|
|
|
|
|
0
|
|
77
|
0
|
|
|
|
|
0
|
my $u = $map{$to}; |
78
|
0
|
|
0
|
|
|
0
|
my $from = $u->{from} || $to; |
79
|
0
|
|
0
|
|
|
0
|
for my $p ($u->{module} || @pkg) { |
80
|
|
|
|
|
|
|
# calculate the actual file to be loaded thus avoid eval and |
81
|
|
|
|
|
|
|
# checking $@ mannually. |
82
|
0
|
|
|
|
|
0
|
_require $p; |
83
|
0
|
0
|
|
|
|
0
|
if (my $r = *{"${p}::$from"}{CODE}) { |
|
0
|
|
|
|
|
0
|
|
84
|
1
|
|
|
1
|
|
5
|
no warnings 'prototype'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
435
|
|
85
|
0
|
|
|
|
|
0
|
*$q = $r; |
86
|
|
|
|
|
|
|
# TODO: understand why using goto will lost context. |
87
|
|
|
|
|
|
|
#goto &$r; |
88
|
0
|
|
|
|
|
0
|
return &$r; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
} |
91
|
0
|
|
|
|
|
0
|
confess("unable to autoload $q."); |
92
|
2
|
|
|
|
|
10
|
}; |
93
|
|
|
|
|
|
|
} elsif ($q eq 'oautoload') { |
94
|
1
|
|
|
|
|
2
|
for my $p (@{+shift}) { |
|
1
|
|
|
|
|
1
|
|
95
|
3
|
|
|
|
|
8
|
my $r = $p =~ s|::|/|gr . '.pm'; |
96
|
|
|
|
|
|
|
# ignore already loaded module. |
97
|
3
|
|
|
|
|
5
|
my $f = "${p}::AUTOLOAD"; |
98
|
3
|
50
|
50
|
|
|
18
|
next if $INC{$r} or *$f{CODE}; |
99
|
|
|
|
|
|
|
*$f = sub { |
100
|
1
|
|
|
1
|
|
2
|
my ($f) = do { our $AUTOLOAD =~ /.*::(.*)/ }; |
|
1
|
|
|
|
|
4
|
|
101
|
1
|
|
|
|
|
1
|
my $symtab = *{"${p}::"}{HASH}; |
|
1
|
|
|
|
|
2
|
|
102
|
1
|
|
|
|
|
3
|
delete $symtab->{AUTOLOAD}; |
103
|
1
|
|
|
|
|
673
|
require $r; |
104
|
1
|
|
|
|
|
5311
|
&{$symtab->{$f}}; |
|
1
|
|
|
|
|
3135
|
|
105
|
3
|
|
|
|
|
10
|
}; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
} elsif ($q eq 'sane') { |
108
|
1
|
|
|
|
|
8
|
($^H, ${^WARNING_BITS}, %^H) = @H; |
109
|
|
|
|
|
|
|
} else { |
110
|
0
|
|
|
|
|
|
confess("unknown request $q"); |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
}; |
114
|
1
|
|
|
|
|
2
|
my @a = qw/Cpanel::JSON::XS JSON::XS JSON::PP/; |
115
|
|
|
|
|
|
|
App::rs->import(iautoload => ['Carp', |
116
|
|
|
|
|
|
|
[qw'Compress::Zlib memGunzip'], |
117
|
|
|
|
|
|
|
[qw/File::Path make_path/], |
118
|
|
|
|
|
|
|
[qw'Socket getaddrinfo', |
119
|
1
|
|
|
|
|
2
|
map { "&$_" } qw'AF_UNIX SOCK_STREAM MSG_NOSIGNAL']], |
|
3
|
|
|
|
|
11
|
|
120
|
|
|
|
|
|
|
oautoload => [@a]); |
121
|
1
|
|
|
|
|
2
|
my $o; |
122
|
1
|
|
|
|
|
6
|
for (@a) { |
123
|
1
|
50
|
|
|
|
2
|
last if eval { |
124
|
1
|
|
|
|
|
7
|
$o = $_->new->pretty->canonical; |
125
|
|
|
|
|
|
|
}; |
126
|
|
|
|
|
|
|
} |
127
|
0
|
|
|
0
|
0
|
|
sub jw { $o->encode(shift) } |
128
|
0
|
|
|
0
|
0
|
|
sub jr { $o->decode(shift) } |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
sub xsh { |
131
|
0
|
|
|
0
|
0
|
|
my $f = shift; |
132
|
0
|
0
|
|
|
|
|
if (not ref $f) { |
133
|
0
|
|
|
|
|
|
my $h = {}; |
134
|
0
|
0
|
|
|
|
|
$h->{"capture-stdout"} = 1 if $f & 1; |
135
|
0
|
0
|
|
|
|
|
$h->{"feed-stdin"} = 1 if $f & 2; |
136
|
0
|
|
|
|
|
|
$f = $h; |
137
|
|
|
|
|
|
|
} |
138
|
0
|
|
|
|
|
|
my ($h, $i, $pr, @st) = ({pid => []}, 0); |
139
|
0
|
0
|
|
|
|
|
if ($f->{"feed-stdin"}) { |
140
|
0
|
|
|
|
|
|
my ($fi, $pid) = shift; |
141
|
0
|
|
|
|
|
|
pipe $pr, my $pw; |
142
|
0
|
0
|
|
|
|
|
if (not $pid = fork) { |
143
|
0
|
|
|
|
|
|
close $pr; |
144
|
0
|
|
|
|
|
|
print $pw $fi; |
145
|
0
|
|
|
|
|
|
exit; |
146
|
|
|
|
|
|
|
} else { |
147
|
0
|
|
|
|
|
|
push @{$h->{pid}}, $pid; |
|
0
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
} |
150
|
0
|
|
|
|
|
|
while ($i <= @_) { |
151
|
0
|
|
|
|
|
|
my $l = $i == @_; |
152
|
0
|
0
|
|
|
|
|
my $a = $_[$i] if not $l; |
153
|
0
|
0
|
0
|
|
|
|
if ($l or $a eq "|") { |
154
|
0
|
0
|
0
|
|
|
|
pipe my $r, my $w if not $l or $f->{"capture-stdout"}; |
155
|
|
|
|
|
|
|
# there's no need to fork when executing the last command and we're required |
156
|
|
|
|
|
|
|
# to substitute current process. |
157
|
0
|
0
|
0
|
|
|
|
my $pid = fork unless $l and $f->{substitute}; |
158
|
0
|
0
|
|
|
|
|
if (not $pid) { |
159
|
|
|
|
|
|
|
# always true except possibly the first. |
160
|
0
|
0
|
|
|
|
|
open STDIN, "<&", $pr if $pr; |
161
|
|
|
|
|
|
|
# always true except possibly the last. |
162
|
0
|
0
|
|
|
|
|
open STDOUT, ">&", $w if $w; |
163
|
0
|
|
|
|
|
|
while (ref $st[-1]) { |
164
|
0
|
|
|
|
|
|
my ($h, $f) = pop @st; |
165
|
0
|
0
|
|
|
|
|
if (ref \$h->{from} eq "SCALAR") { open $f, $h->{mode}, $h->{from} or die $! } |
|
0
|
0
|
|
|
|
|
|
166
|
0
|
|
|
|
|
|
else { $f = $h->{from} } |
167
|
0
|
|
|
|
|
|
open $h->{to}, $h->{mode} . "&", $f; |
168
|
|
|
|
|
|
|
} |
169
|
0
|
|
|
|
|
|
exec @st; |
170
|
|
|
|
|
|
|
} else { |
171
|
0
|
|
|
|
|
|
$pr = $r; |
172
|
0
|
|
|
|
|
|
push @{$h->{pid}}, $pid; |
|
0
|
|
|
|
|
|
|
173
|
0
|
|
|
|
|
|
@st = (); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
} else { |
176
|
0
|
|
|
|
|
|
push @st, $a; |
177
|
|
|
|
|
|
|
} |
178
|
0
|
|
|
|
|
|
$i++; |
179
|
|
|
|
|
|
|
} |
180
|
0
|
0
|
|
|
|
|
if ($f->{asynchronous}) { |
181
|
0
|
0
|
|
|
|
|
$h->{stdout} = $pr if $f->{"capture-stdout"}; |
182
|
0
|
0
|
|
|
|
|
if ($f->{compact}) { $h } |
|
0
|
0
|
|
|
|
|
|
183
|
0
|
|
|
|
|
|
elsif ($f->{"capture-stdout"}) { $pr } |
184
|
0
|
0
|
|
|
|
|
else { wantarray ? @{$h->{pid}} : $h->{pid}[-1] } |
|
0
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
} else { |
186
|
0
|
0
|
|
|
|
|
if ($f->{"capture-stdout"}) { |
187
|
0
|
0
|
|
|
|
|
local $/ if not wantarray; |
188
|
0
|
|
|
|
|
|
$h->{stdout} = [<$pr>]; |
189
|
|
|
|
|
|
|
} |
190
|
0
|
|
|
|
|
|
$h->{status} = []; |
191
|
0
|
0
|
|
|
|
|
push @{$h->{status}}, waitpid($_, 0) == -1 ? undef : $? for @{$h->{pid}}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# they're meaningless now as they don't exist anymore. |
193
|
0
|
|
|
|
|
|
delete $h->{pid}; |
194
|
0
|
0
|
|
|
|
|
if ($f->{compact}) { $h } |
|
0
|
0
|
|
|
|
|
|
195
|
0
|
0
|
|
|
|
|
elsif ($f->{"capture-stdout"}) { wantarray ? @{$h->{stdout}} : $h->{stdout}[0] } |
|
0
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
|
else { wantarray ? @{$h->{status}} : not $h->{status}[-1] } |
|
0
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
sub arg_parse { |
200
|
0
|
|
|
0
|
0
|
|
my $h = {}; |
201
|
0
|
|
|
|
|
|
while (@ARGV) { |
202
|
0
|
|
|
|
|
|
my $a = shift @ARGV; |
203
|
0
|
0
|
|
|
|
|
if ($a !~ /^-/) { unshift @ARGV, $a; last } |
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
204
|
0
|
|
|
|
|
|
elsif ($a =~ /^--?$/) { last } |
205
|
0
|
|
|
|
|
|
elsif ($a =~ /^--(.*?)=(.*)$/) { hash_madd_key($h, $1, $2) } |
206
|
0
|
|
|
|
|
|
elsif ($a =~ /^--?(.*)$/) { $h->{$1} = 1 } |
207
|
|
|
|
|
|
|
} |
208
|
0
|
|
|
|
|
|
$h; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
sub hash_madd_key { |
211
|
0
|
|
|
0
|
0
|
|
my ($h, $k, $v) = @_; |
212
|
0
|
0
|
|
|
|
|
if (exists $h->{$k}) { |
213
|
0
|
0
|
|
|
|
|
$h->{$k} = [$h->{$k}] if ref $h->{$k} ne 'ARRAY'; |
214
|
0
|
|
|
|
|
|
push @{$h->{$k}}, $v; |
|
0
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
} else { |
216
|
0
|
|
|
|
|
|
$h->{$k} = $v; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
sub linker { |
220
|
0
|
|
|
0
|
0
|
|
my $s = shift; |
221
|
|
|
|
|
|
|
$s->{i386} ? |
222
|
|
|
|
|
|
|
"$s->{prefix}/lib/ld-linux.so.2" : $s->{arm} ? |
223
|
0
|
0
|
|
|
|
|
"$s->{prefix}/lib/ld-linux-armhf.so.3" : |
|
|
0
|
|
|
|
|
|
224
|
|
|
|
|
|
|
"$s->{prefix}/lib/ld-linux-x86-64.so.2"; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
sub add { |
227
|
0
|
|
|
0
|
0
|
|
my $h = shift; |
228
|
0
|
|
|
|
|
|
while (@_) { |
229
|
0
|
|
|
|
|
|
my ($k, $v) = splice @_, 0, 2; |
230
|
0
|
|
|
|
|
|
$h->{$k} = $v; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
sub slice { |
234
|
0
|
|
|
0
|
0
|
|
my $h = shift; |
235
|
0
|
|
|
|
|
|
map { $_ => $h->{$_} } @_; |
|
0
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
sub wf { |
238
|
0
|
|
|
0
|
0
|
|
local $_ = shift; |
239
|
0
|
0
|
|
|
|
|
if (-e) { unlink or die "$!: unable to remove $_ for writing.\n" } |
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
240
|
0
|
0
|
|
|
|
|
elsif (m|(.*/)|) { make_path($1) unless -d } |
241
|
0
|
0
|
|
|
|
|
open my $fh, '>', $_ or die "open $_ for writing: $!"; |
242
|
0
|
0
|
|
|
|
|
if (@_) { syswrite $fh, shift } |
|
0
|
|
|
|
|
|
|
243
|
0
|
|
|
|
|
|
else { $fh } |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
sub purl { |
246
|
0
|
|
|
0
|
0
|
|
my $o = shift; |
247
|
|
|
|
|
|
|
my $x = {major => 1, |
248
|
|
|
|
|
|
|
minor => 1, |
249
|
|
|
|
|
|
|
type => 'request', |
250
|
|
|
|
|
|
|
method => $o->{method}, |
251
|
0
|
|
|
|
|
|
hf => [qw/Host User-Agent Accept-Encoding Connection/], |
252
|
|
|
|
|
|
|
hv => {connection => 'keep-alive', |
253
|
|
|
|
|
|
|
'user-agent' => 'App-rs', |
254
|
|
|
|
|
|
|
'accept-encoding' => 'gzip'}}; |
255
|
0
|
0
|
|
|
|
|
if ($o->{method} eq 'POST') { |
256
|
0
|
|
|
|
|
|
push @{$x->{hf}}, qw/Content-Length Content-Type/; |
|
0
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
add($x->{hv}, |
258
|
0
|
|
|
|
|
|
'content-length' => undef, |
259
|
|
|
|
|
|
|
'content-type' => 'application/x-www-form-urlencoded'); |
260
|
0
|
|
|
|
|
|
$x->{c} = $o->{'post-data'}; |
261
|
|
|
|
|
|
|
} |
262
|
0
|
|
|
|
|
|
my $url = $o->{url}; |
263
|
0
|
|
|
|
|
|
@$x{qw/protocol request-uri/} = ('http', '/'); |
264
|
0
|
0
|
|
|
|
|
($x->{protocol}, $url) = ($1, $2) if $url =~ m|(.*)://(.*)|; |
265
|
0
|
0
|
|
|
|
|
if ($url =~ m|(.*?)(/.*)|) { |
266
|
0
|
|
|
|
|
|
($x->{hv}{host}, $x->{'request-uri'}) = ($1, $2); |
267
|
|
|
|
|
|
|
} else { |
268
|
0
|
|
|
|
|
|
$x->{hv}{host} = $url; |
269
|
|
|
|
|
|
|
} |
270
|
0
|
|
|
|
|
|
my $r = http_req($x); |
271
|
0
|
|
|
|
|
|
my $c = $r->{c}; |
272
|
0
|
0
|
|
|
|
|
$c = memGunzip($c) if eval { $r->{hv}{'content-encoding'} eq 'gzip' }; |
|
0
|
|
|
|
|
|
|
273
|
0
|
0
|
|
|
|
|
if ($o->{json}) { jr($c) } |
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
274
|
0
|
|
|
|
|
|
elsif ($o->{plain}) { $c } |
275
|
0
|
|
|
|
|
|
elsif ($o->{html}) { html_parse($c) } |
276
|
|
|
|
|
|
|
elsif ($o->{save}) { |
277
|
0
|
0
|
|
|
|
|
die $r->{b} unless $r->{'status-code'} == 200; |
278
|
0
|
|
|
|
|
|
wf($o->{save}, $c); |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
sub http_req { |
282
|
|
|
|
|
|
|
# socket pool. |
283
|
0
|
|
|
0
|
0
|
|
state $pool = {}; |
284
|
0
|
|
|
|
|
|
my ($x, $f) = @_; |
285
|
|
|
|
|
|
|
# host key to identify socket. |
286
|
0
|
|
|
|
|
|
my $hk = $x->{protocol} . '://' . $x->{hv}{host}; |
287
|
0
|
0
|
|
|
|
|
if (not $pool->{$hk}) { |
288
|
0
|
|
|
|
|
|
say "creating new pool socket $hk."; |
289
|
0
|
0
|
|
|
|
|
if ($x->{protocol} eq 'https') { $pool->{$hk} = connect_tls($x->{hv}{host}, 443) } |
|
0
|
|
|
|
|
|
|
290
|
0
|
|
|
|
|
|
else { $pool->{$hk} = connect_tcp($x->{hv}{host}, 80) } |
291
|
|
|
|
|
|
|
} |
292
|
0
|
|
|
|
|
|
send $pool->{$hk}, http_unparse($x), MSG_NOSIGNAL; |
293
|
0
|
|
|
|
|
|
my $h = http_parse_new(); |
294
|
|
|
|
|
|
|
# avoid undefined warning when checking length of $h->{c}. |
295
|
0
|
|
|
|
|
|
$h->{c} = ''; |
296
|
0
|
|
|
|
|
|
while (1) { |
297
|
0
|
|
|
|
|
|
my $b; |
298
|
0
|
|
|
|
|
|
eval { |
299
|
0
|
|
|
0
|
|
|
local $SIG{ALRM} = sub { die }; |
|
0
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
|
alarm 12; |
301
|
0
|
|
|
|
|
|
recv $pool->{$hk}, $b, 1048576, 0; |
302
|
0
|
|
|
|
|
|
alarm 0; |
303
|
|
|
|
|
|
|
}; |
304
|
0
|
0
|
0
|
|
|
|
if ($@ or not $b) { |
305
|
0
|
0
|
|
|
|
|
if ($@) { say 'timeout.' } |
|
0
|
|
|
|
|
|
|
306
|
0
|
|
|
|
|
|
else { say 'remote-close.' } |
307
|
0
|
|
|
|
|
|
my $_h = http_parse_new(); |
308
|
0
|
0
|
0
|
|
|
|
if ($f->{range} and length($h->{c})) { |
309
|
0
|
|
|
|
|
|
$_h->{c} = $h->{c}; |
310
|
0
|
0
|
|
|
|
|
push @{$x->{hf}}, 'Range' if not exists $x->{hv}{range}; |
|
0
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
|
$x->{hv}{range} = 'bytes=' . length($h->{c}) . '-'; |
312
|
|
|
|
|
|
|
} |
313
|
0
|
|
|
|
|
|
$h = $_h; |
314
|
0
|
0
|
|
|
|
|
if ($x->{protocol} eq 'https') { $pool->{$hk} = connect_tls($x->{hv}{host}, 443) } |
|
0
|
|
|
|
|
|
|
315
|
0
|
|
|
|
|
|
else { $pool->{$hk} = connect_tcp($x->{hv}{host}, 80) } |
316
|
0
|
|
|
|
|
|
send $pool->{$hk}, http_unparse($x), MSG_NOSIGNAL; |
317
|
|
|
|
|
|
|
} else { |
318
|
0
|
0
|
|
|
|
|
return $h if http_parse($h, $b); |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
sub connect_tcp { |
323
|
0
|
|
|
0
|
0
|
|
my ($err, $a) = getaddrinfo(@_); |
324
|
0
|
0
|
|
|
|
|
die "getaddrinfo: $err" if $err; |
325
|
0
|
0
|
|
|
|
|
socket my $fh, $a->{family}, SOCK_STREAM, 0 or die $!; |
326
|
0
|
0
|
|
|
|
|
connect $fh, $a->{addr} or die $!; |
327
|
0
|
|
|
|
|
|
$fh; |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
sub connect_tls { |
330
|
0
|
|
|
0
|
0
|
|
my ($host, $port) = @_; |
331
|
0
|
|
|
|
|
|
my ($p, $q); |
332
|
0
|
|
|
|
|
|
socketpair $p, $q, AF_UNIX, SOCK_STREAM, 0; |
333
|
0
|
|
|
|
|
|
xsh({asynchronous => 1}, qw/socat -/, "OPENSSL:$host:$port", |
334
|
|
|
|
|
|
|
{to => *STDIN, |
335
|
|
|
|
|
|
|
from => $q, |
336
|
|
|
|
|
|
|
mode => '<'}, {to => *STDOUT, |
337
|
|
|
|
|
|
|
from => $q, |
338
|
|
|
|
|
|
|
mode => '>'}); |
339
|
0
|
|
|
|
|
|
$p; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
sub http_parse_new { |
342
|
0
|
|
|
0
|
0
|
|
{st => 'reading-header', |
343
|
|
|
|
|
|
|
# remaining length. |
344
|
|
|
|
|
|
|
rl => 'line', |
345
|
|
|
|
|
|
|
# header value. |
346
|
|
|
|
|
|
|
hv => {}, |
347
|
|
|
|
|
|
|
# header field. |
348
|
|
|
|
|
|
|
hf => [], |
349
|
|
|
|
|
|
|
# first line. |
350
|
|
|
|
|
|
|
fl => 1}; |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
sub http_parse { |
353
|
0
|
|
|
0
|
0
|
|
my ($h, $b) = @_; |
354
|
0
|
|
|
|
|
|
$h->{b} .= $b; |
355
|
0
|
|
|
|
|
|
my $i = 0; |
356
|
0
|
|
|
|
|
|
while ($i < length($b)) { |
357
|
0
|
0
|
|
|
|
|
if ($h->{rl} eq "line") { |
358
|
0
|
|
|
|
|
|
pos($b) = $i; |
359
|
0
|
0
|
|
|
|
|
if ($b =~ /\n/g) { |
360
|
0
|
|
|
|
|
|
$h->{l} .= substr($b, $i, pos($b) - $i), $i = pos($b); |
361
|
0
|
|
|
|
|
|
$h->{l} =~ s/\r?\n$//; |
362
|
0
|
0
|
|
|
|
|
if ($h->{st} eq "reading-header") { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
363
|
0
|
0
|
|
|
|
|
if ($h->{fl}) { |
364
|
0
|
0
|
|
|
|
|
if ($h->{l}) { |
365
|
0
|
0
|
|
|
|
|
if ($h->{l} =~ m|^HTTP\s*/\s*(\d)\s*\.\s*(\d)\s+(\d{3})\s+(.*)$|) { |
|
|
0
|
|
|
|
|
|
366
|
0
|
|
|
|
|
|
@$h{qw/type major minor status-code reason-phrase/} = ("reply", $1, $2, $3, $4); |
367
|
|
|
|
|
|
|
} elsif ($h->{l} =~ m|^(.*?)\s+(.*?)\s+HTTP\s*/\s*(\d)\s*\.\s*(\d)$|) { |
368
|
0
|
|
|
|
|
|
@$h{qw/type method request-uri major minor/} = ("request", $1, $2, $3, $4); |
369
|
|
|
|
|
|
|
} else { |
370
|
|
|
|
|
|
|
} |
371
|
0
|
|
|
|
|
|
$h->{fl} = 0; |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
# empty line before request/reply ignored. |
374
|
|
|
|
|
|
|
} else { |
375
|
0
|
0
|
|
|
|
|
if (not $h->{l}) { |
|
|
0
|
|
|
|
|
|
376
|
0
|
0
|
0
|
|
|
|
if ($h->{type} eq "reply" and $h->{"status-code"} =~ /^(1\d{2}|204|304)$/) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
377
|
0
|
|
|
|
|
|
return $i; |
378
|
|
|
|
|
|
|
} elsif (exists $h->{hv}{"transfer-encoding"} and $h->{hv}{"transfer-encoding"} !~ /^identity$/i) { |
379
|
0
|
|
|
|
|
|
$h->{st} = "reading-chunk-size"; |
380
|
|
|
|
|
|
|
} elsif (exists $h->{hv}{"content-length"}) { |
381
|
0
|
|
|
|
|
|
$h->{rl} = $h->{hv}{"content-length"}, $h->{st} = "reading-content"; |
382
|
|
|
|
|
|
|
# content-length could be 0. |
383
|
0
|
0
|
|
|
|
|
return $i if not $h->{rl}; |
384
|
|
|
|
|
|
|
} elsif ($h->{type} eq "reply") { |
385
|
0
|
|
|
|
|
|
$h->{rl} = "eof"; |
386
|
|
|
|
|
|
|
} else { |
387
|
0
|
|
|
|
|
|
return $i; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
} elsif ($h->{l} =~ /^\s/) { |
390
|
0
|
|
|
|
|
|
my $k = lc $h->{hf}[$#{$h->{hf}}]; |
|
0
|
|
|
|
|
|
|
391
|
0
|
0
|
|
|
|
|
if (ref $h->{hv}{$k} eq "ARRAY") { |
392
|
0
|
|
|
|
|
|
my $r = $h->{hv}{$k}; |
393
|
0
|
|
|
|
|
|
$r->[$#$r] .= $h->{l}; |
394
|
|
|
|
|
|
|
} else { |
395
|
0
|
|
|
|
|
|
$h->{hv}{$k} .= $h->{l}; |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
} else { |
398
|
0
|
|
|
|
|
|
my ($f, $v) = $h->{l} =~ /^(.*?)\s*:\s*(.*?)\s*$/; |
399
|
0
|
|
|
|
|
|
my $k = lc($f); |
400
|
0
|
0
|
|
|
|
|
if (exists $h->{hv}{$k}) { |
401
|
0
|
0
|
|
|
|
|
if (ref $h->{hv}{$k} eq "ARRAY") { |
402
|
0
|
|
|
|
|
|
push @{$h->{hv}{$k}}, $v; |
|
0
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
} else { |
404
|
0
|
|
|
|
|
|
$h->{hv}{$k} = [$h->{hv}{$k}, $v]; |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
} else { |
407
|
0
|
|
|
|
|
|
$h->{hv}{$k} = $v; |
408
|
|
|
|
|
|
|
} |
409
|
0
|
|
|
|
|
|
push @{$h->{hf}}, $f; |
|
0
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
} elsif ($h->{st} eq "reading-chunk-size") { |
413
|
0
|
|
|
|
|
|
$h->{l} =~ /^([A-Fa-f0-9]+)/; |
414
|
0
|
0
|
|
|
|
|
if ($1 !~ /^0+$/) { $h->{rl} = hex $1, $h->{st} = "reading-chunk-data" } |
|
0
|
|
|
|
|
|
|
415
|
0
|
|
|
|
|
|
else { $h->{st} = "reading-trailer" } |
416
|
|
|
|
|
|
|
} elsif ($h->{st} eq "reading-crlf") { |
417
|
0
|
|
|
|
|
|
$h->{st} = "reading-chunk-size"; |
418
|
|
|
|
|
|
|
} elsif ($h->{st} eq "reading-trailer") { |
419
|
|
|
|
|
|
|
# trailer ignored. |
420
|
0
|
0
|
|
|
|
|
return $i unless $h->{l}; |
421
|
|
|
|
|
|
|
} |
422
|
0
|
|
|
|
|
|
$h->{l} = ""; |
423
|
|
|
|
|
|
|
} else { |
424
|
0
|
|
|
|
|
|
$h->{l} .= substr($b, $i), $i = length($b); |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
} else { |
427
|
0
|
0
|
0
|
|
|
|
if ($h->{rl} ne "eof" and $h->{rl} <= length($b) - $i) { |
428
|
0
|
|
|
|
|
|
$h->{c} .= substr($b, $i, $h->{rl}), $i += $h->{rl}; |
429
|
0
|
0
|
|
|
|
|
if ($h->{st} eq "reading-chunk-data") { $h->{rl} = "line", $h->{st} = "reading-crlf" } |
|
0
|
|
|
|
|
|
|
430
|
0
|
|
|
|
|
|
else { return $i } |
431
|
|
|
|
|
|
|
} else { |
432
|
0
|
|
|
|
|
|
$h->{c} .= substr($b, $i), $h->{rl} -= length($b) - $i, $i = length($b); |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
} |
436
|
0
|
|
|
|
|
|
undef; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
sub http_unparse { |
439
|
0
|
|
|
0
|
0
|
|
my $h = shift; |
440
|
0
|
|
|
|
|
|
my $b; |
441
|
0
|
|
|
|
|
|
my $v = "HTTP/$h->{major}.$h->{minor}"; |
442
|
0
|
0
|
|
|
|
|
if ($h->{type} eq "request") { $b = join " ", $h->{method}, $h->{"request-uri"}, $v } |
|
0
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
|
else { $b = join " ", $v, $h->{"status-code"}, $h->{"reason-phrase"} } |
444
|
0
|
|
|
|
|
|
$b .= "\r\n"; |
445
|
0
|
0
|
|
|
|
|
$h->{hv}{"content-length"} = length($h->{c}) if exists $h->{hv}{"content-length"}; |
446
|
0
|
|
|
|
|
|
my $i = {}; |
447
|
0
|
|
|
|
|
|
for (@{$h->{hf}}) { |
|
0
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
|
$b .= "$_: "; |
449
|
0
|
|
|
|
|
|
my $k = lc $_; |
450
|
0
|
0
|
|
|
|
|
if (ref $h->{hv}{$k} eq "ARRAY") { $b .= $h->{hv}{$k}[$i->{$k}++] } |
|
0
|
|
|
|
|
|
|
451
|
0
|
|
|
|
|
|
else { $b .= $h->{hv}{$k} } |
452
|
0
|
|
|
|
|
|
$b .= "\r\n"; |
453
|
|
|
|
|
|
|
} |
454
|
0
|
|
|
|
|
|
$b .= "\r\n"; |
455
|
0
|
0
|
|
|
|
|
if (exists $h->{c}) { |
456
|
0
|
0
|
0
|
|
|
|
if (exists $h->{hv}{"transfer-encoding"} and $h->{hv}{"transfer-encoding"} !~ /^identity$/i) { |
457
|
0
|
|
|
|
|
|
$b .= sprintf("%x\r\n", length($h->{c})) . $h->{c} . "\r\n0\r\n\r\n"; |
458
|
|
|
|
|
|
|
} else { |
459
|
0
|
|
|
|
|
|
$b .= $h->{c}; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
} |
462
|
0
|
|
|
|
|
|
$b; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
sub vcmp ($$) { |
465
|
0
|
|
|
0
|
0
|
|
my ($a, $b) = @_; |
466
|
0
|
|
|
|
|
|
version->parse($a) <=> version->parse($b); |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
sub vsat { |
469
|
0
|
|
|
0
|
0
|
|
my ($pkg, $ver) = @_; |
470
|
0
|
0
|
|
|
|
|
return vcmp($^V, $ver) >= 0 if $pkg eq 'perl'; |
471
|
0
|
0
|
|
|
|
|
if (my $pid = fork) { |
472
|
0
|
0
|
|
|
|
|
die unless $pid == waitpid $pid, 0; |
473
|
0
|
|
|
|
|
|
not $?; |
474
|
|
|
|
|
|
|
} else { |
475
|
0
|
|
|
|
|
|
exit not eval { |
476
|
0
|
|
|
|
|
|
require $pkg =~ s|::|/|gr . '.pm'; |
477
|
0
|
|
|
|
|
|
$pkg->VERSION($ver); |
478
|
|
|
|
|
|
|
}; |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
sub rf { |
482
|
0
|
|
|
0
|
0
|
|
local (@ARGV, $/) = @_; |
483
|
0
|
|
|
|
|
|
<>; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
1; |