| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Respite::Server; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Respite::Server - generic Respite based Respite server |
|
4
|
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
1546
|
use strict; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
133
|
|
|
6
|
4
|
|
|
4
|
|
10
|
use warnings; |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
298
|
|
|
7
|
|
|
|
|
|
|
our @ISA; |
|
8
|
4
|
|
|
4
|
|
19
|
use base 'Respite::Common'; # Default _configs |
|
|
4
|
|
|
|
|
5
|
|
|
|
4
|
|
|
|
|
571
|
|
|
9
|
4
|
|
|
4
|
|
21
|
use Digest::MD5 qw(md5_hex); |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
360
|
|
|
10
|
4
|
|
|
4
|
|
17
|
use Throw qw(throw); |
|
|
4
|
|
|
|
|
5
|
|
|
|
4
|
|
|
|
|
33
|
|
|
11
|
4
|
|
|
4
|
|
183
|
use Time::HiRes qw(sleep); |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
30
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
18
|
0
|
33
|
18
|
1
|
224
|
sub server_name { $_[0]->{'server_name'} ||= ($0 =~ m|/(\w+)$|x) ? $1 : throw 'Missing server_name' } |
|
14
|
0
|
|
0
|
0
|
0
|
0
|
sub revision { $_[0]->{'revision'} ||= eval { $_[0]->dispatch_class->_revision } || '-' } |
|
|
|
|
0
|
|
|
|
|
|
15
|
0
|
0
|
|
0
|
0
|
0
|
sub max_request_size { $_[0]->{'max_request_size'} || 2_000_000 } |
|
16
|
5
|
|
|
5
|
1
|
20
|
sub api_meta { shift->{'api_meta'} } |
|
17
|
0
|
|
|
0
|
0
|
0
|
sub dispatch_class { shift->{'dispatch_class'} } |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub new { |
|
22
|
5
|
|
|
5
|
1
|
29
|
my $class = shift; |
|
23
|
5
|
50
|
|
|
|
82
|
my $self = bless ref($_[0]) ? shift : {@_}, $class; |
|
24
|
5
|
50
|
|
|
|
67
|
%$self = (%$_, %$self) if $_ = $self->new_args; |
|
25
|
5
|
50
|
33
|
|
|
51
|
return $self if $self->{'non_daemon'} || ($ENV{'MOD_PERL'} && ! $self->{'force_daemon'}); |
|
|
|
|
33
|
|
|
|
|
|
26
|
5
|
|
|
|
|
2420
|
require Net::Server; |
|
27
|
5
|
|
|
|
|
86436
|
require Net::Server::HTTP; |
|
28
|
5
|
100
|
|
|
|
22401
|
unshift @ISA, qw(Net::Server::HTTP) if !$self->isa(qw(Net::Server::HTTP));; |
|
29
|
5
|
50
|
|
|
|
51
|
throw 'We need a more recent Net::Server revision', {v => $Net::Server::VERSION} if $Net::Server::VERSION < 2.007; |
|
30
|
5
|
|
|
|
|
78
|
$self->json; # vivify before fork |
|
31
|
5
|
|
|
|
|
100
|
my $server = $class->SUPER::new(%$self, %{ $self->server_args }); |
|
|
5
|
|
|
|
|
55
|
|
|
32
|
5
|
|
|
|
|
301
|
@$server{keys %$self} = values %$self; # TODO - avoid duplicates |
|
33
|
5
|
50
|
|
|
|
35
|
$self->dispatch_factory('preload') if !$ENV{'NO_PRELOAD'}; # void call will load necessary classes |
|
34
|
|
|
|
|
|
|
|
|
35
|
5
|
|
|
|
|
50
|
return $server; |
|
36
|
|
|
|
|
|
|
} |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
5
|
0
|
|
sub new_args {} |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub config { |
|
41
|
60
|
|
|
60
|
0
|
91
|
my ($self, $key, $def, $name) = @_; |
|
42
|
60
|
|
33
|
|
|
85
|
$name ||= $self->server_name; |
|
43
|
60
|
|
|
|
|
128
|
my $c = $self->_configs($name); |
|
44
|
|
|
|
|
|
|
return exists($self->{$key}) ? $self->{$key} |
|
45
|
|
|
|
|
|
|
: exists($c->{"${name}_${key}"}) ? $c->{"${name}_${key}"} |
|
46
|
60
|
50
|
33
|
|
|
418
|
: (ref($c->{$name}) && exists $c->{$name}->{$key}) ? $c->{$name}->{$key} |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
: ref($def) eq 'CODE' ? $def->($self) : $def; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub dispatch_factory { |
|
51
|
5
|
|
|
5
|
0
|
21
|
my ($self, $preload) = @_; |
|
52
|
5
|
|
33
|
|
|
46
|
return $self->{'dispatch_factory'} ||= do { |
|
53
|
5
|
|
33
|
|
|
46
|
my $meta = $self->api_meta || $self->dispatch_class || throw "Missing one of api_meta or dispatch_class"; |
|
54
|
5
|
50
|
|
|
|
16
|
if (!ref $meta) { |
|
55
|
5
|
|
|
|
|
17
|
(my $file = "$meta.pm") =~ s|::|/|g; |
|
56
|
5
|
0
|
33
|
|
|
74
|
throw "Failed to load dispatch class", {class => $meta, file => $file, msg => $@} if !$meta->can('new') && !eval { require $file }; |
|
|
0
|
|
|
|
|
0
|
|
|
57
|
5
|
50
|
|
|
|
45
|
throw "Specified class does not have a run_method method", {class => $meta} if ! $meta->can('run_method'); |
|
58
|
5
|
|
|
0
|
|
72
|
sub { $meta->new(@_) }; |
|
|
0
|
|
|
|
|
0
|
|
|
59
|
|
|
|
|
|
|
} else { |
|
60
|
0
|
|
|
|
|
0
|
require Respite::Base; |
|
61
|
0
|
0
|
|
|
|
0
|
Respite::Base->new({api_meta => $meta})->api_preload if $preload; |
|
62
|
0
|
|
|
0
|
|
0
|
sub { Respite::Base->new({%{shift()}, api_meta => $meta}) }; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
}; |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
68
|
|
|
|
|
|
|
# request handling and method dispatching |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# mod_perl handler - used via apache conf |
|
71
|
|
|
|
|
|
|
# |
|
72
|
|
|
|
|
|
|
# SetHandler modperl |
|
73
|
|
|
|
|
|
|
# PerlResponseHandler FooServer |
|
74
|
|
|
|
|
|
|
# |
|
75
|
|
|
|
|
|
|
# sub handler { __PACKAGE__->modperlhandler(@_) } |
|
76
|
|
|
|
|
|
|
sub modperlhandler { |
|
77
|
0
|
|
|
0
|
0
|
0
|
my $class = shift; |
|
78
|
0
|
|
0
|
|
|
0
|
my $r = shift || throw "Missing apache request during ${class}::modperlhandler", {trace => 1}; |
|
79
|
0
|
|
|
|
|
0
|
my $self = $class->new({apache_req => $r, non_daemon => 1}); |
|
80
|
0
|
|
|
|
|
0
|
my %env = %ENV; |
|
81
|
0
|
0
|
|
|
|
0
|
if (eval { $self->modperl_init($r); $r->subprocess_env(); 1 }) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
82
|
0
|
|
|
|
|
0
|
$self->cgihandler(); |
|
83
|
|
|
|
|
|
|
} else { |
|
84
|
0
|
|
|
|
|
0
|
warn my $err = $self->json->encode({error => "$@", type => 'mod_perl_header'}); |
|
85
|
0
|
|
|
|
|
0
|
$self->send_response($err); |
|
86
|
|
|
|
|
|
|
} |
|
87
|
0
|
|
|
|
|
0
|
%ENV = %env; |
|
88
|
0
|
|
|
|
|
0
|
return 0; # OK - TODO - we actually may want a 403 for digest errors |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
my $modperl_init; |
|
92
|
|
|
|
|
|
|
sub modperl_init { |
|
93
|
0
|
0
|
|
0
|
0
|
0
|
return if $modperl_init; |
|
94
|
0
|
|
|
|
|
0
|
$modperl_init = 1; |
|
95
|
0
|
|
|
|
|
0
|
require Apache2::RequestRec; |
|
96
|
0
|
|
|
|
|
0
|
require Apache2::RequestIO; |
|
97
|
0
|
|
|
|
|
0
|
require APR::Table; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# normal cgi-bin or Net::Server::HTTP handler |
|
101
|
|
|
|
|
|
|
# Net::Server::HTTP app => \&cgihandler |
|
102
|
|
|
|
|
|
|
# cgi-bin/server App::cgihandler() or App->new->cgihandler or App->cgihandler |
|
103
|
|
|
|
|
|
|
sub cgihandler { |
|
104
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
|
105
|
0
|
0
|
0
|
|
|
0
|
$self = ($self || __PACKAGE__)->new({%{shift() || {}}, non_daemon => 1}) if ! $self || ! ref($self); |
|
|
0
|
0
|
0
|
|
|
0
|
|
|
106
|
0
|
|
|
|
|
0
|
local $self->{'transport'}; |
|
107
|
0
|
|
|
|
|
0
|
local $self->{'extra_headers'}; |
|
108
|
0
|
|
|
|
|
0
|
local $self->{'cgi_obj'}; |
|
109
|
|
|
|
|
|
|
|
|
110
|
0
|
|
|
|
|
0
|
my $req_sum; |
|
111
|
0
|
|
|
|
|
0
|
my $args = eval { |
|
112
|
0
|
|
|
|
|
0
|
my $r = $self->{'apache_req'}; |
|
113
|
0
|
|
|
|
|
0
|
my $req; |
|
114
|
0
|
0
|
0
|
|
|
0
|
if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /\bjson\b/) { |
|
115
|
0
|
0
|
0
|
|
|
0
|
throw 'JSON data may not be submitted via GET' if !$ENV{'REQUEST_METHOD'} || $ENV{'REQUEST_METHOD'} eq 'GET' || $ENV{'REQUEST_METHOD'} eq 'HEAD'; |
|
|
|
|
0
|
|
|
|
|
|
116
|
0
|
|
0
|
|
|
0
|
my $len = $ENV{'CONTENT_LENGTH'} || throw "Missing CONTENT_LENGTH on $ENV{'REQUEST_METHOD'} request", {len => $ENV{'CONTENT_LENGTH'}}; |
|
117
|
0
|
0
|
|
|
|
0
|
throw "Too large a $ENV{'REQUEST_METHOD'} request found", {length => $len, max => $self->max_request_size} if $len > $self->max_request_size; |
|
118
|
0
|
|
|
|
|
0
|
my $size = 0; |
|
119
|
0
|
|
|
|
|
0
|
while (1) { |
|
120
|
0
|
0
|
|
|
|
0
|
$r ? $r->read($req, $len - $size, $size) : read(STDIN, $req, $len - $size, $size); |
|
121
|
0
|
0
|
|
|
|
0
|
throw "Failed to read bytes", {needed => $len, got => $size} if length($req) == $size; |
|
122
|
0
|
0
|
|
|
|
0
|
last if ($size = length $req) >= $len; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
0
|
0
|
|
|
|
0
|
throw "Failed to read entire $ENV{'REQUEST_METHOD'} request", {length => $len, actual => length($req)} if length($req) != $len; |
|
125
|
|
|
|
|
|
|
} else { |
|
126
|
0
|
|
|
|
|
0
|
my $args = $self->parse_form($r); |
|
127
|
0
|
|
|
|
|
0
|
$req = delete $args->{'POSTDATA'}; # CGI.pm - non-form POST |
|
128
|
0
|
0
|
|
|
|
0
|
if (!$req) { # get |
|
129
|
0
|
|
|
|
|
0
|
$self->{'transport'} = 'form'; |
|
130
|
0
|
0
|
0
|
|
|
0
|
$args = Data::URIEncode::flat_to_complex($args) || {} if !$self->{'no_data_uriencode'} && (eval { require Data::URIEncode } || ((grep {$_ =~ /[:.]/} keys %$args) && throw "Failed to load Data::URIEncode", {msg => $@})); |
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
131
|
0
|
|
|
|
|
0
|
return $args; |
|
132
|
|
|
|
|
|
|
} |
|
133
|
0
|
0
|
|
|
|
0
|
throw "Found other args in addition to POSTDATA", {args => $args} if scalar keys %$args; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
0
|
|
|
|
|
0
|
$self->{'transport'} = 'json'; |
|
136
|
0
|
0
|
|
|
|
0
|
throw "Content data did not look like JSON hash", {head => substr($req, 0, 10)."...", content_type => $ENV{'CONTENT_TYPE'}} if $req !~ /^\{/; |
|
137
|
0
|
|
|
|
|
0
|
$req_sum = md5_hex($req); |
|
138
|
|
|
|
|
|
|
return eval { $self->json->decode($req) } |
|
139
|
0
|
|
0
|
|
|
0
|
|| throw 'Trouble unencoding json', {ip => $ENV{'REMOTE_ADDR'}, msg => $@, head => substr($req, 0, 10)."..."}; |
|
140
|
|
|
|
|
|
|
}; |
|
141
|
0
|
0
|
|
|
|
0
|
if (! $args) { |
|
142
|
0
|
|
|
|
|
0
|
my $err = $self->json->encode({error => "$@", type => 'cgihandler'}); |
|
143
|
0
|
|
|
|
|
0
|
warn $err; |
|
144
|
0
|
|
|
|
|
0
|
return $self->send_response($err); |
|
145
|
|
|
|
|
|
|
} |
|
146
|
0
|
|
0
|
|
|
0
|
$ENV{'PATH_INFO'} ||= ''; |
|
147
|
|
|
|
|
|
|
|
|
148
|
0
|
0
|
|
|
|
0
|
my ($old_out, $out_ref) = $self->{'warn_on_stdout'} ? do { open my $fh, ">", \(my $str =""); (select($fh), \$str) } : (); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
149
|
0
|
|
|
|
|
0
|
local $self->{'_warn_info'}; |
|
150
|
0
|
|
|
|
|
0
|
my $ref = eval { $self->_do_request($args, $req_sum, \%ENV) }; |
|
|
0
|
|
|
|
|
0
|
|
|
151
|
0
|
0
|
|
|
|
0
|
if (! $ref) { |
|
152
|
0
|
|
|
|
|
0
|
$ref = $@; |
|
153
|
0
|
0
|
0
|
|
|
0
|
$ref = eval { throw 'Trouble dispatching', {path => $ENV{'PATH_INFO'}, msg => $ref} } || $@ if !ref($ref) || !$ref->{'error'}; |
|
|
|
|
0
|
|
|
|
|
|
154
|
0
|
0
|
|
|
|
0
|
local @$ref{keys %$_} = values %$_ if $_ = $self->{'_warn_info'}; |
|
155
|
0
|
|
|
|
|
0
|
warn $ref; |
|
156
|
|
|
|
|
|
|
} |
|
157
|
0
|
0
|
|
|
|
0
|
if ($old_out) { |
|
158
|
0
|
|
|
|
|
0
|
select $old_out; |
|
159
|
0
|
0
|
|
|
|
0
|
warn "--- INVALID STDOUT ---\n$$out_ref\n" if $$out_ref; |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
|
|
162
|
0
|
0
|
0
|
|
|
0
|
if (ref($ref) eq 'ARRAY' && @$ref == 3 && $ref->[0] =~ /^\d+$/) { |
|
|
|
|
0
|
|
|
|
|
|
163
|
0
|
0
|
|
|
|
0
|
return $ref if $self->{'is_psgi'}; |
|
164
|
0
|
|
|
|
|
0
|
require Net::Server::PSGI; |
|
165
|
0
|
|
|
|
|
0
|
$self->Net::Server::PSGI::print_psgi_headers($ref->[0], $ref->[1]); |
|
166
|
0
|
|
|
|
|
0
|
$self->Net::Server::PSGI::print_psgi_body($ref->[2]); |
|
167
|
0
|
|
|
|
|
0
|
return 1; |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
0
|
0
|
|
|
|
0
|
$self->{'extra_headers'} = delete $ref->{'_extra_headers'} if $ref->{'_extra_headers'}; |
|
171
|
0
|
|
0
|
|
|
0
|
my $out = eval { $self->json->encode($ref) } || do { warn "Trouble encoding json: $@"; "{'error':'Trouble encoding json - check server logs for details'}" }; |
|
172
|
0
|
|
|
|
|
0
|
return $self->send_response($out); |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub _do_request { |
|
176
|
0
|
|
|
0
|
|
0
|
my ($self, $args, $req_sum, $env) = @_; |
|
177
|
0
|
|
|
|
|
0
|
my ($method, $brand, $extra) = $self->_map_request($args, $env); |
|
178
|
0
|
|
|
|
|
0
|
my $ver = $self->verify_sig($args, $req_sum, $env, $method, $brand); |
|
179
|
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
0
|
$self->{'_warn_info'} = {caller => {who => $args->{'_w'}, source => $args->{'_c'}, method => $method, brand => $brand, ip => $env->{'REMOTE_ADDR'}}}; |
|
181
|
0
|
|
|
|
|
0
|
local $env->{'REMOTE_USER'}; |
|
182
|
|
|
|
|
|
|
my $disp = $self->dispatch_factory->({ |
|
183
|
0
|
0
|
|
|
|
0
|
%{ $extra || {} }, |
|
184
|
|
|
|
|
|
|
is_server => $self->server_name, |
|
185
|
|
|
|
|
|
|
($env->{'HTTP_X_FORWARDED_FOR'} |
|
186
|
|
|
|
|
|
|
? (api_ip => $env->{'HTTP_X_FORWARDED_FOR'}, is_proxy => $env->{'REMOTE_ADDR'}) |
|
187
|
|
|
|
|
|
|
: (api_ip => $env->{'REMOTE_ADDR'})), |
|
188
|
|
|
|
|
|
|
api_auth => $ver, |
|
189
|
|
|
|
|
|
|
api_brand => $brand, |
|
190
|
|
|
|
|
|
|
api_method => $method, |
|
191
|
|
|
|
|
|
|
remote_user => delete($args->{'_w'}), |
|
192
|
|
|
|
|
|
|
remote_ip => delete($args->{'_i'}), |
|
193
|
|
|
|
|
|
|
token => delete($args->{'_t'}) || do { my $k = $self->config('admin_cookie_key'); $k ? $self->parse_cookies->{$k} : '' }, |
|
194
|
|
|
|
|
|
|
caller => delete($args->{'_c'}), |
|
195
|
|
|
|
|
|
|
dbh_cache => $self->_dbh_cache, |
|
196
|
0
|
0
|
0
|
|
|
0
|
transport => $self->{'transport'}, |
|
197
|
|
|
|
|
|
|
}); |
|
198
|
0
|
0
|
|
|
|
0
|
$disp->server_init($method, $args, $self) if $disp->can('server_init'); |
|
199
|
|
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
0
|
local $0 = "$0 ".$self->server_name." $method - $env->{'REMOTE_ADDR'}"; |
|
201
|
0
|
0
|
|
|
|
0
|
return $disp->run_method($method, $args) if !$disp->can('server_post_request'); |
|
202
|
|
|
|
|
|
|
|
|
203
|
0
|
|
|
|
|
0
|
my $ref; |
|
204
|
0
|
|
|
|
|
0
|
my $ok = eval { $ref = $disp->run_method($method, $args); 1 }; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
205
|
0
|
|
|
|
|
0
|
my $err = $@; |
|
206
|
0
|
|
|
|
|
0
|
$disp->server_post_request($method, $args, $ok, $ref, $err); |
|
207
|
0
|
0
|
|
|
|
0
|
return $ref if $ok; |
|
208
|
0
|
|
|
|
|
0
|
die $err; |
|
209
|
|
|
|
|
|
|
} |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub _map_request { |
|
212
|
0
|
|
|
0
|
|
0
|
my ($self, $args, $env) = @_; |
|
213
|
0
|
|
|
|
|
0
|
my $no_brand = $self->_no_brand; |
|
214
|
|
|
|
|
|
|
my ($meth, $brand) = ((!$no_brand || $no_brand < 0) && $env->{'PATH_INFO'} =~ m|^/+(.+)/([^/]+)$|) ? ($1, $2) |
|
215
|
|
|
|
|
|
|
: ($env->{'PATH_INFO'} =~ m|^/+(.+)$|) ? ($1, $no_brand ? undef : throw "Failed to find brand with method", {uri => "/$1"}) |
|
216
|
0
|
0
|
0
|
|
|
0
|
: throw "Failed to find method in URI", {uri => $env->{'PATH_INFO'}}; |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
0
|
delete @$args{qw(_p _b)}; # legacy brand and password passing |
|
218
|
0
|
|
|
|
|
0
|
return ($meth, $brand); |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
|
|
221
|
0
|
|
|
0
|
|
0
|
sub _dbh_cache { {} } # intentionally not persistent |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub cgi_obj { |
|
224
|
0
|
|
|
0
|
0
|
0
|
my ($self, $r) = @_; |
|
225
|
0
|
|
0
|
|
|
0
|
return $self->{'cgi_obj'} ||= do { |
|
226
|
0
|
0
|
0
|
|
|
0
|
eval { CGI::initialize_globals() } or warn "Failed to initialize globals: $@" if $INC{'CGI.pm'}; # CGI.pm caches query parameters |
|
|
0
|
|
|
|
|
0
|
|
|
227
|
0
|
0
|
|
|
|
0
|
eval { $self->{'is_psgi'} ? require CGI::PSGI : require CGI } || throw 'Cannot load CGI library during a non-JSON request', {msg => $@, type => $ENV{'CONTENT_TYPE'}}; |
|
|
0
|
0
|
|
|
|
0
|
|
|
228
|
0
|
|
|
|
|
0
|
local $CGI::POST_MAX = $self->max_request_size; |
|
229
|
0
|
0
|
0
|
|
|
0
|
my $q = $self->{'is_psgi'} ? CGI::PSGI->new($self->{'is_psgi'}) : CGI->new($r || $self->{'apache_req'} || ()); |
|
230
|
|
|
|
|
|
|
}; |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
sub parse_form { |
|
234
|
0
|
|
|
0
|
0
|
0
|
my ($self, $r) = @_; |
|
235
|
0
|
|
|
|
|
0
|
my $q = $self->cgi_obj($r); |
|
236
|
0
|
0
|
|
|
|
0
|
return {map {my @v = $q->param($_); $_ => (@v <= 1 ? $v[0] : \@v)} $q->param}; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub parse_cookies { |
|
240
|
0
|
|
|
0
|
0
|
0
|
my ($self, $r) = @_; |
|
241
|
0
|
|
0
|
|
|
0
|
my $env = $self->{'is_psgi'} || \%ENV; |
|
242
|
0
|
0
|
|
|
|
0
|
return {} if !$env->{'HTTP_COOKIE'}; |
|
243
|
0
|
|
|
|
|
0
|
my $q = $self->cgi_obj($r); |
|
244
|
0
|
0
|
|
|
|
0
|
return {map {my @v = $q->cookie($_); $_ => (@v <= 1 ? $v[0] : \@v)} $q->cookie}; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
0
|
|
0
|
0
|
0
|
0
|
sub content_type { shift->{'content_type'} ||= 'application/json' } |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub send_response { |
|
250
|
0
|
|
|
0
|
0
|
0
|
my ($self, $str) = @_; |
|
251
|
0
|
0
|
|
|
|
0
|
$str =~ s/\s*$/\r\n/ if $self->content_type =~ m{^(?:text/|application/json$)}; |
|
252
|
0
|
0
|
|
|
|
0
|
my @extra = $self->{'extra_headers'} ? @{ $self->{'extra_headers'} } : (); |
|
|
0
|
|
|
|
|
0
|
|
|
253
|
0
|
0
|
0
|
|
|
0
|
if ($self->{'is_psgi'}) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
254
|
0
|
|
|
|
|
0
|
return [200, [(map {$_->[0], $_->[1]} @extra), 'Content-type' => $self->content_type, 'Content-length' => length($str)], [$str]]; |
|
|
0
|
|
|
|
|
0
|
|
|
255
|
|
|
|
|
|
|
} elsif (my $r = $self->{'apache_req'} || eval { $ENV{'MOD_PERL'} && Apache2::RequestUtil->request }) { |
|
256
|
0
|
|
|
|
|
0
|
$r->headers_out->set($_->[0] => $_->[1]) for @extra; |
|
257
|
0
|
|
|
|
|
0
|
$r->headers_out->set('Content-length', length($str)); |
|
258
|
0
|
|
|
|
|
0
|
$r->content_type($self->content_type); |
|
259
|
0
|
|
|
|
|
0
|
$r->print($str); |
|
260
|
|
|
|
|
|
|
} elsif (my $c = $self->{'server'}->{'client'}) { # accelerate output header generation under Net::Server |
|
261
|
0
|
|
|
|
|
0
|
my $ri = $self->{'request_info'}; |
|
262
|
0
|
|
|
|
|
0
|
my $out = "HTTP/1.0 200 OK\015\012"; |
|
263
|
0
|
|
|
|
|
0
|
foreach my $row (@{ $self->http_base_headers }, @extra, ['Content-length', length($str)], ['Content-type', $self->content_type]) { |
|
|
0
|
|
|
|
|
0
|
|
|
264
|
0
|
|
|
|
|
0
|
$out .= "$row->[0]: $row->[1]\015\012"; |
|
265
|
0
|
|
|
|
|
0
|
push @{ $ri->{'response_headers'} }, $row; |
|
|
0
|
|
|
|
|
0
|
|
|
266
|
|
|
|
|
|
|
} |
|
267
|
0
|
|
|
|
|
0
|
$ri->{'response_header_size'} += length $out; |
|
268
|
0
|
|
|
|
|
0
|
$ri->{'http_version'} = '1.0'; |
|
269
|
0
|
|
|
|
|
0
|
$ri->{'response_status'} = 200; |
|
270
|
0
|
|
|
|
|
0
|
$ri->{'headers_sent'} = 1; |
|
271
|
0
|
|
|
|
|
0
|
$ri->{'response_size'} = length $str; |
|
272
|
0
|
|
|
|
|
0
|
$c->print("$out\015\012$str"); |
|
273
|
|
|
|
|
|
|
} else { |
|
274
|
|
|
|
|
|
|
# Otherwise, this is a normal CGI process. |
|
275
|
|
|
|
|
|
|
# XXX - Do we need to also convert "Status" header for the special NPH format? |
|
276
|
0
|
0
|
0
|
|
|
0
|
print "HTTP/1.0 200 OK\r\n" if ($ENV{SCRIPT_FILENAME} // "") =~ m{/nph-[^/]+($|\s)}; |
|
277
|
0
|
|
|
|
|
0
|
for my $h (@extra) { |
|
278
|
0
|
|
|
|
|
0
|
print "$h->[0]: $h->[1]\r\n"; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
0
|
|
|
|
|
0
|
print "Content-Type: ".$self->content_type."\r\nContent-Length: ".length($str)."\r\nContent-Type: ".$self->content_type."\r\n\r\n",$str; |
|
281
|
|
|
|
|
|
|
} |
|
282
|
0
|
|
|
|
|
0
|
return 1; |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
|
|
285
|
0
|
|
|
0
|
|
0
|
sub _no_brand { shift->config(no_brand => undef) } |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
sub verify_sig { |
|
288
|
0
|
|
|
0
|
0
|
0
|
my ($self, $args, $req_sum, $env, $meth, $brand) = @_; |
|
289
|
0
|
|
|
|
|
0
|
my ($ip, $sig, $script, $path_info, $qs, $auth) = @$env{qw(REMOTE_ADDR HTTP_X_RESPITE_AUTH SCRIPT_NAME PATH_INFO QUERY_STRING HTTP_AUTHORIZATION)}; |
|
290
|
0
|
|
0
|
|
|
0
|
my $uri = $script || throw "Missing script"; |
|
291
|
0
|
0
|
|
|
|
0
|
$uri .= $path_info if $path_info; |
|
292
|
0
|
0
|
|
|
|
0
|
$uri .= "?$qs" if $qs; |
|
293
|
|
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
0
|
my ($type, $user, $exception); |
|
295
|
0
|
0
|
|
|
|
0
|
if ($auth) { |
|
296
|
0
|
0
|
|
|
|
0
|
throw "Cannot pass both Authorization and X-Respite-Auth", {authorization => $auth, x_respite_auth => $sig, uri => $uri, ip => $ip} if $sig; |
|
297
|
0
|
0
|
|
|
|
0
|
if ($auth =~ s/^Basic \s+ (\S+)$/$1/x) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
0
|
$type = 'basic'; |
|
299
|
0
|
|
|
|
|
0
|
require MIME::Base64; |
|
300
|
0
|
|
|
|
|
0
|
($user, $sig) = split /:/, MIME::Base64::decode_base64($auth), 2; |
|
301
|
0
|
0
|
|
|
|
0
|
$exception = Throw->new("Basic authentication not allowed", {user => $user}) if ! $self->allow_auth_basic($brand, $user); |
|
302
|
|
|
|
|
|
|
} elsif ($auth =~ s/^Digest \s+//x) { |
|
303
|
0
|
|
|
|
|
0
|
$type = 'digest'; |
|
304
|
0
|
|
|
|
|
0
|
$sig->{'method'} = $ENV{'REQUEST_METHOD'}; |
|
305
|
0
|
0
|
0
|
|
|
0
|
$sig->{$1} = (defined($3) && length($3)) ? $3 : $2 while $auth =~ s/^ (\w+) = (?: "([^\"]+)" | ([^\s\",]+)) (?:\s*$|,\s*) //gxs; |
|
306
|
0
|
|
|
|
|
0
|
$user = $sig->{'username'}; |
|
307
|
|
|
|
|
|
|
} elsif ($auth =~ s/^RespiteAuth \s+//x) { |
|
308
|
0
|
|
|
|
|
0
|
$type = 'signed'; |
|
309
|
0
|
|
|
|
|
0
|
$sig = $auth; |
|
310
|
|
|
|
|
|
|
} else { |
|
311
|
0
|
|
|
|
|
0
|
$exception = Throw->new("Unknown auth type", {authorization => $auth, uri => $uri, ip => $ip, authtype => 'unknown'}); |
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
} else { |
|
314
|
0
|
|
|
|
|
0
|
my $allow_md5 = $self->allow_auth_md5_pass($brand); |
|
315
|
0
|
0
|
0
|
|
|
0
|
$sig ||= $args->{'x_respite_auth'} if $allow_md5; |
|
316
|
0
|
0
|
|
|
|
0
|
$type = !$sig ? 'none' : ($sig !~ /^[a-f0-z]{32}$/) ? 'signed' : $allow_md5 ? 'md5_pass' : throw 'Auth type md5_pass not allowed'; |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
} |
|
318
|
0
|
|
0
|
|
|
0
|
my $pass = $self->get_api_pass($brand || '', $ip, $sig, $type, $user, $exception) || []; |
|
319
|
0
|
0
|
|
|
|
0
|
$pass = ref($pass) ? undef : [$pass] if ref($pass) ne 'ARRAY'; |
|
|
|
0
|
|
|
|
|
|
|
320
|
0
|
0
|
0
|
|
|
0
|
return {authorization_not_required => 1, ip => $ip, brand => $brand, authtype => $type, exception => $exception} if $pass && !@$pass; |
|
321
|
0
|
0
|
|
|
|
0
|
die $exception if defined $exception; |
|
322
|
0
|
0
|
0
|
|
|
0
|
throw "Missing client authorization", {server_name => $self->server_name, ip => $ip, brand => $brand, authtype => $type, uri => $uri} if !$sig && $type && $type ne 'none'; |
|
|
|
|
0
|
|
|
|
|
|
323
|
|
|
|
|
|
|
|
|
324
|
0
|
0
|
|
|
|
0
|
if ($pass) { |
|
325
|
0
|
|
|
|
|
0
|
for my $i (0 .. $#$pass) { |
|
326
|
|
|
|
|
|
|
next if ($type eq 'basic') ? $pass->[$i] ne $sig |
|
327
|
|
|
|
|
|
|
: ($type eq 'md5_pass') ? md5_hex($pass->[$i]) ne $sig |
|
328
|
0
|
|
|
|
|
0
|
: ($type eq 'signed') ? do { my ($_sum, $time) = split /:/, $sig, 2; md5_hex("$pass->[$i]:$time:$uri:$req_sum") ne $_sum } |
|
|
0
|
|
|
|
|
0
|
|
|
329
|
0
|
0
|
0
|
|
|
0
|
: ($type eq 'digest') ? (eval { $self->verify_digest($sig||={}, $pass->[$i], $uri, $req_sum, $meth, $brand, $ip) } ? 0 : do { $sig->{'verify'} = $@; 1 }) |
|
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
: 1; |
|
331
|
0
|
0
|
|
|
|
0
|
return {authtype => $type, ip => $ip, brand => $brand, meth => $meth, i => $i, ($self->{'verify_sig_return_pass'} ? (pass => $pass->[$i]) : ()), ($type eq 'digest'?(digest=>$sig):())}; |
|
|
|
0
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
} |
|
333
|
|
|
|
|
|
|
} |
|
334
|
0
|
0
|
|
|
|
0
|
throw "Invalid client authorization", {($type eq 'digest'?(digest=>$sig):()), server_name => $self->server_name, ip => $ip, brand => $brand, authtype => $type, uri => $uri}; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
my %cidr; |
|
338
|
|
|
|
|
|
|
sub get_api_pass { |
|
339
|
0
|
|
|
0
|
0
|
0
|
my ($self, $brand, $ip, $sig, $type, $user, $except) = @_; |
|
340
|
0
|
|
|
|
|
0
|
my $ref = $self->config(pass => undef); |
|
341
|
0
|
0
|
0
|
|
|
0
|
return $ref if ! ref($ref) || ref($ref) eq 'ARRAY'; |
|
342
|
0
|
0
|
0
|
|
|
0
|
if (exists $ref->{$ip}) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
343
|
0
|
0
|
|
|
|
0
|
return $ref->{$ip} if ref($ref->{$ip}) ne 'HASH'; |
|
344
|
0
|
0
|
|
|
|
0
|
return $ref->{$ip}->{$brand} if exists $ref->{$ip}->{$brand}; |
|
345
|
0
|
0
|
|
|
|
0
|
return $ref->{$ip}->{'~default~'} if exists $ref->{$ip}->{'~default~'}; |
|
346
|
0
|
0
|
|
|
|
0
|
return $ref->{$ip}->{'-default'} if exists $ref->{$ip}->{'-default'}; |
|
347
|
|
|
|
|
|
|
} elsif (exists $ref->{$brand}) { |
|
348
|
0
|
0
|
|
|
|
0
|
return $ref->{$brand} if ref($ref->{$brand}) ne 'HASH'; |
|
349
|
0
|
0
|
|
|
|
0
|
return $ref->{$brand}->{$ip} if exists $ref->{$brand}->{$ip}; |
|
350
|
0
|
0
|
|
|
|
0
|
return $ref->{$brand}->{'~default~'} if exists $ref->{$brand}->{'~default~'}; |
|
351
|
0
|
0
|
|
|
|
0
|
return $ref->{$brand}->{'-default'} if exists $ref->{$brand}->{'-default'}; |
|
352
|
|
|
|
|
|
|
} elsif (my $c = $ref->{'~cidr~'} || $ref->{'-cidr'}) { |
|
353
|
0
|
|
|
|
|
0
|
my $n = _aton($ip); |
|
354
|
0
|
|
|
|
|
0
|
foreach my $cidr (keys %$c) { |
|
355
|
0
|
|
0
|
|
|
0
|
my $range = $cidr{$cidr} ||= _cidr($cidr); |
|
356
|
0
|
0
|
0
|
|
|
0
|
next if $n < $range->[0] || $n > $range->[1]; |
|
357
|
0
|
|
|
|
|
0
|
my $ref = $c->{$cidr}; |
|
358
|
0
|
0
|
|
|
|
0
|
if (ref($ref) eq 'HASH') { |
|
359
|
0
|
0
|
|
|
|
0
|
return $ref->{$brand} if exists $ref->{$brand}; |
|
360
|
0
|
0
|
|
|
|
0
|
return $ref->{'~default~'} if exists $ref->{'~default~'}; |
|
361
|
0
|
0
|
|
|
|
0
|
return $ref->{'-default'} if exists $ref->{'-default'}; |
|
362
|
|
|
|
|
|
|
} |
|
363
|
0
|
|
|
|
|
0
|
return $ref; |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
} |
|
366
|
|
|
|
|
|
|
|
|
367
|
0
|
0
|
|
|
|
0
|
return $ref->{'~default~'} if exists $ref->{'~default~'}; |
|
368
|
0
|
0
|
|
|
|
0
|
return $ref->{'-default'} if exists $ref->{'-default'}; |
|
369
|
0
|
|
|
|
|
0
|
throw "Not authorized - Could not find brand/ip match in pass configuration", {brand => $brand, ip => $ip, service => $self->server_name}; |
|
370
|
|
|
|
|
|
|
} |
|
371
|
0
|
|
|
0
|
|
0
|
sub _aton { my $ip = shift; return unpack "N", pack "C4", split /\./, $ip } |
|
|
0
|
|
|
|
|
0
|
|
|
372
|
0
|
0
|
|
0
|
|
0
|
sub _cidr { (my $c = shift) =~ s/\s+//; my ($ip, $base) = split /\//, $c; my $i = _aton($ip); $i &= 2**32 - 2**(32-$base) if !$_[0]; return [$i, $i+2**(32-$base)-1] } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
373
|
|
|
|
|
|
|
|
|
374
|
0
|
|
|
0
|
0
|
0
|
sub allow_auth_md5_pass { shift->config(allow_auth_md5_pass => undef) } |
|
375
|
0
|
|
|
0
|
0
|
0
|
sub allow_auth_basic { shift->config(allow_auth_basic => undef) } |
|
376
|
0
|
|
|
0
|
0
|
0
|
sub allow_auth_qop_auth { shift->config(allow_auth_qop_auth => undef) } |
|
377
|
0
|
0
|
|
0
|
0
|
0
|
sub digest_realm { shift->config(realm => sub { my $name = shift->server_name; return $name =~ /^(\w+)_server/ ? $1 : $name }) } |
|
|
0
|
|
|
0
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub verify_digest { |
|
380
|
0
|
|
|
0
|
0
|
0
|
my ($self, $digest, $pass, $uri, $req_sum, $meth, $brand, $ip) = @_; |
|
381
|
0
|
0
|
0
|
0
|
|
0
|
my $d = sub { my ($key, $opt) = @_; my $val = $digest->{$key}; $opt ? ($val='') : throw "Digest directive $key was missing" if !defined($val) || !length($val); $val }; |
|
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
382
|
0
|
0
|
0
|
|
|
0
|
throw "Missing or invalid digest username" if $brand && $d->('username') ne $brand; |
|
383
|
0
|
0
|
|
|
|
0
|
throw "Missing or invalid digest realm", {realm => $self->digest_realm} if $d->('realm') ne $self->digest_realm; |
|
384
|
0
|
0
|
|
|
|
0
|
throw "Digest URI did not match", {digest => $d->('uri'), actual => $uri} if $uri ne $d->('uri'); |
|
385
|
0
|
|
|
|
|
0
|
my $ha1 = md5_hex($d->('username') .':'. $d->('realm').":$pass"); |
|
386
|
0
|
0
|
|
|
|
0
|
$ha1 = md5_hex("$ha1:".$d->('nonce').':'.$d->('cnonce')) if lc($d->('algorithm',1)) eq 'md5-sess'; |
|
387
|
0
|
0
|
|
|
|
0
|
my $ha2 = md5_hex($d->('method').":$uri".(($d->('qop',1) eq 'auth-int') ? ":$req_sum" : $self->allow_auth_qop_auth($brand) ? '' : throw 'Digest qop auth not allowed')); |
|
|
|
0
|
|
|
|
|
|
|
388
|
0
|
0
|
|
|
|
0
|
my $sum = md5_hex("$ha1:".$d->('nonce').($d->('qop',1) ? ':'.$d->('nc').':'.$d->('cnonce').':'.$d->('qop') : '').":$ha2"); |
|
389
|
0
|
0
|
|
|
|
0
|
throw 'Digest did not validate' if $sum ne $d->('response'); |
|
390
|
0
|
|
|
|
|
0
|
return 1; |
|
391
|
|
|
|
|
|
|
} |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
394
|
|
|
|
|
|
|
# Net::Server::HTTP bits |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
sub server_args { |
|
397
|
5
|
|
|
5
|
0
|
10
|
my $self = shift; |
|
398
|
5
|
|
|
|
|
24
|
my $name = $self->server_name; |
|
399
|
5
|
|
|
60
|
|
81
|
my $val = sub { my ($key, $def) = @_; $self->config($key, $def, $name) }; |
|
|
60
|
|
|
|
|
171
|
|
|
|
60
|
|
|
|
|
120
|
|
|
400
|
5
|
50
|
|
|
|
37
|
my $path = $val->(path => ($name =~ /^(\w+)_server/ ? $1 : $name)); |
|
401
|
5
|
|
|
|
|
10
|
my $host = $val->(host => '*'); |
|
402
|
5
|
|
|
|
|
25
|
my $port = $val->(port => 443); |
|
403
|
5
|
|
|
|
|
13
|
my $ssl = !$val->(no_ssl => undef); |
|
404
|
5
|
0
|
33
|
|
|
18
|
my $ad = $val->(auto_doc => ''); $ad = ($name =~ /^(\w+)_server/ ? $1 : $name).'_doc' if $ad && $ad eq '1'; |
|
|
5
|
50
|
|
|
|
21
|
|
|
405
|
5
|
50
|
|
|
|
8
|
my $is_dev = eval { defined(&config::is_dev) && config::is_dev() }; |
|
|
5
|
|
|
|
|
25
|
|
|
406
|
5
|
|
33
|
|
|
18
|
my $use_dev_port = $is_dev && $ssl && !$val->(no_dev_port => ''); |
|
407
|
5
|
|
|
|
|
10
|
my $res = $val->(cgi_bin => undef); |
|
408
|
5
|
0
|
|
|
|
15
|
my $app = !$res ? \&cgihandler : ($res ne 1) ? $res : 'cgi-bin/'.($name =~ /^(\w+)_server/ ? $1 : $name); |
|
|
|
0
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
409
|
5
|
0
|
33
|
|
|
15
|
$app = $self->rootdir_server ."/$app" if !ref($app) && $app !~ /^\//; |
|
410
|
5
|
|
|
|
|
10
|
my $st = $val->(server_type => 'PreFork'); |
|
411
|
|
|
|
|
|
|
return { |
|
412
|
|
|
|
|
|
|
server_type => ref($st) ? $st : [$st], |
|
413
|
|
|
|
|
|
|
enable_dispatch => 1, |
|
414
|
|
|
|
|
|
|
ipv => 4, |
|
415
|
5
|
50
|
33
|
|
|
30
|
app => [[(map{$_ => $app} ref($path) ? @$path : $path), |
|
|
5
|
50
|
|
|
|
111
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
($ad ? ($ad => \&cgidoc) : ()), |
|
417
|
|
|
|
|
|
|
'' => \&http_not_found]], |
|
418
|
|
|
|
|
|
|
port => [ |
|
419
|
|
|
|
|
|
|
{port => $port, host => $host, ($ssl ? (proto => 'SSL') : ())}, |
|
420
|
|
|
|
|
|
|
($use_dev_port ? {port => ($port == 443 ? 80 : $port-1), host => $host} : ()), # allow for dev to telnet to a non-ssl |
|
421
|
|
|
|
|
|
|
], |
|
422
|
|
|
|
|
|
|
serialize => ($is_dev && $ssl) ? 'flock' : 'none', # can only do if hard coded to ipv4 and host resolves to one ip |
|
423
|
|
|
|
|
|
|
access_log_file => $val->(access_log_file => "/var/log/${name}/${name}.access_log"), |
|
424
|
|
|
|
|
|
|
log_file => $val->(log_file => "/var/log/${name}/${name}.error_log"), |
|
425
|
|
|
|
|
|
|
pid_file => $val->(pid_file => "/var/run/${name}.pid"), |
|
426
|
|
|
|
|
|
|
user => $val->(user => 'readonly'), |
|
427
|
|
|
|
|
|
|
group => $val->(group => 'cvs'), |
|
428
|
|
|
|
|
|
|
}; |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
|
|
431
|
0
|
|
0
|
0
|
1
|
0
|
sub rootdir_server { shift->config(rootdir_server => $config::config{'rootdir_server'} || sub { require FindBin; $FindBin::RealBin }) } |
|
|
0
|
|
|
0
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
432
|
0
|
|
|
0
|
0
|
0
|
sub SSL_base_domain { 'example.com' } |
|
433
|
0
|
|
|
0
|
0
|
0
|
sub SSL_cert_file { shift->config(ssl_cert => sub { shift->rootdir_server .shift->SSL_base_domain().'.crt' }) } |
|
|
0
|
|
|
0
|
|
0
|
|
|
434
|
0
|
|
|
0
|
0
|
0
|
sub SSL_key_file { shift->config(ssl_key => sub { shift->rootdir_server .shift->SSL_base_domain().'.key' }) } |
|
|
0
|
|
|
0
|
|
0
|
|
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub post_bind { |
|
437
|
2
|
|
|
2
|
1
|
26665
|
my $self = shift; |
|
438
|
2
|
|
|
|
|
73
|
$0 = $self->server_name; |
|
439
|
2
|
|
|
|
|
58
|
$self->SUPER::post_bind(@_); |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
|
|
442
|
0
|
|
|
0
|
1
|
0
|
sub child_init_hook { $0 = shift->server_name ." - waiting" } # prefork server |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
sub run_client_connection { |
|
445
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
446
|
0
|
|
|
|
|
0
|
$0 = $self->server_name . " - connected"; |
|
447
|
0
|
|
|
|
|
0
|
$self->SUPER::run_client_connection(@_); |
|
448
|
0
|
0
|
|
|
|
0
|
$_->($self) for @{ $self->{'post_client_callbacks'} || [] }; |
|
|
0
|
|
|
|
|
0
|
|
|
449
|
|
|
|
|
|
|
} |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
sub server_revision { |
|
452
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
453
|
0
|
0
|
0
|
|
|
0
|
return $self->{'server_revision'} ||= $self->server_name.'/'.$self->revision.($self->{'nshv'} ? ' '.$self->SUPER::server_revision : ''); |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
|
|
456
|
0
|
|
|
0
|
0
|
0
|
sub http_not_found { shift->send_status(404, "Not found", "Not Found") } |
|
457
|
|
|
|
|
|
|
|
|
458
|
0
|
|
|
0
|
1
|
0
|
sub post_process_request_hook { $0 = shift->server_name ." - post_request" } |
|
459
|
|
|
|
|
|
|
|
|
460
|
8
|
|
|
8
|
1
|
25408
|
sub default_values { {background => 1, setsid => 1} } |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
463
|
|
|
|
|
|
|
# Net::Server::HTTP daemonization bits |
|
464
|
|
|
|
|
|
|
|
|
465
|
2
|
|
|
2
|
0
|
466
|
sub run_server { shift->SUPER::run(@_) } |
|
466
|
|
|
|
|
|
|
|
|
467
|
0
|
|
|
0
|
1
|
0
|
sub run { throw "Use either run_server or run_commandline for clarity" } |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
sub run_commandline { |
|
470
|
0
|
|
|
0
|
0
|
0
|
my $class = shift; |
|
471
|
0
|
0
|
0
|
|
|
0
|
my $sub = $ARGV[0] && $class->can("__$ARGV[0]") ? "__$ARGV[0]" : undef; |
|
472
|
0
|
0
|
|
|
|
0
|
shift(@ARGV) if $sub; |
|
473
|
|
|
|
|
|
|
|
|
474
|
0
|
0
|
|
|
|
0
|
if ($ENV{'BOUND_SOCKETS'}) { # HUP |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
475
|
0
|
0
|
|
|
|
0
|
my $self = ref($class) ? $class : $class->new(@_); |
|
476
|
0
|
|
|
|
|
0
|
$self->run_server; # will exit |
|
477
|
0
|
|
|
|
|
0
|
warn "Failed to re-initialize server during HUP\n"; |
|
478
|
0
|
|
|
|
|
0
|
exit 1; |
|
479
|
|
|
|
|
|
|
} elsif ($sub) { # commandline server service |
|
480
|
0
|
0
|
|
|
|
0
|
local $ENV{'NO_PRELOAD'} = 1 if $sub !~ /^__(?:start|restart|reload)$/; |
|
481
|
0
|
0
|
|
|
|
0
|
my $self = ref($class) ? $class : $class->new(@_); |
|
482
|
0
|
|
|
|
|
0
|
$self->$sub(); |
|
483
|
|
|
|
|
|
|
} elsif ($ENV{'PLACK_ENV'}) { |
|
484
|
0
|
|
|
|
|
0
|
return $class->psgi_app(@_); |
|
485
|
|
|
|
|
|
|
} elsif (!@ARGV) { |
|
486
|
0
|
|
|
|
|
0
|
throw "$0 help|start|restart|reload|stop|status|tail_error|tail_access|ps|(or any Respite commands)"; |
|
487
|
|
|
|
|
|
|
} else { |
|
488
|
0
|
0
|
|
|
|
0
|
my $args = ref($_[0]) ? shift : {@_}; |
|
489
|
0
|
0
|
|
|
|
0
|
my $self = ref($class) ? $class : $class->new({%$args, non_daemon => 1}); |
|
490
|
0
|
|
|
|
|
0
|
require Respite::CommandLine; |
|
491
|
0
|
|
|
|
|
0
|
Respite::CommandLine->run({dispatch_factory => $self->dispatch_factory}); |
|
492
|
|
|
|
|
|
|
} |
|
493
|
|
|
|
|
|
|
|
|
494
|
0
|
|
|
|
|
0
|
exit 0; |
|
495
|
|
|
|
|
|
|
} |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub psgi_app { |
|
498
|
0
|
|
|
0
|
0
|
0
|
my ($class, $args) = @_; |
|
499
|
0
|
|
|
|
|
0
|
require IO::Socket; require Net::Server; require Net::Server::PreFork; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
500
|
|
|
|
|
|
|
sub { |
|
501
|
0
|
|
|
0
|
|
0
|
local *ENV = my $env = shift; |
|
502
|
0
|
0
|
|
|
|
0
|
return $class->cgihandler({%{$args||{}}, non_daemon => 1, is_psgi => $env}); |
|
|
0
|
|
|
|
|
0
|
|
|
503
|
0
|
|
|
|
|
0
|
}; |
|
504
|
|
|
|
|
|
|
} |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
sub _get_pid { # taken from Net::Server::Daemonize::check_pid_file - but modified |
|
507
|
3
|
|
|
3
|
|
10
|
my $self = shift; |
|
508
|
3
|
|
|
|
|
38
|
my $pid_file = $self->{'server'}->{'pid_file'}; |
|
509
|
3
|
100
|
|
|
|
84
|
return if ! -e $pid_file; # no pid_file = return success |
|
510
|
1
|
50
|
|
|
|
9
|
return if -z $pid_file; # empty pid_file = return success |
|
511
|
1
|
50
|
|
|
|
62
|
open my $fh, '<', $pid_file or throw "Could not open existing pid_file", {file => $pid_file, msg => $!}; |
|
512
|
1
|
|
|
|
|
24
|
my $line = <$fh>; |
|
513
|
1
|
|
|
|
|
13
|
close $fh; |
|
514
|
1
|
50
|
|
|
|
58
|
return ($line =~ /^(\d{1,10})$/) ? $1 : throw "Could not find pid in existing pid_file", {line => $line}; |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
sub _ok { |
|
518
|
3
|
|
|
3
|
|
12
|
my ($ok, $msg) = @_; |
|
519
|
3
|
50
|
|
|
|
255
|
warn "$msg\e[60G[". ($ok ? "\e[1;32m OK " : "\e[1;31mFAILED") ."\e[0;39m]\n"; |
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
sub __status { |
|
523
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
524
|
0
|
|
|
|
|
0
|
my $pid = $self->_get_pid; |
|
525
|
0
|
0
|
|
|
|
0
|
return _ok(0, "Process is not running - no pid") if ! $pid; |
|
526
|
0
|
0
|
|
|
|
0
|
return _ok(1, "Process appears to be running under pid $pid") if kill 0, $pid; |
|
527
|
0
|
|
|
|
|
0
|
return _ok(0, "Process does not appear to be running - last pid: $pid"); |
|
528
|
|
|
|
|
|
|
} |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub __start { |
|
531
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
532
|
0
|
|
|
|
|
0
|
my $pid = $self->_get_pid; |
|
533
|
0
|
0
|
0
|
|
|
0
|
if ($pid && kill(0, $pid)) { |
|
534
|
0
|
|
|
|
|
0
|
_ok(0, "Starting - pid already exists"); |
|
535
|
0
|
|
|
|
|
0
|
throw "Process appears to already be running under pid $pid ... aborting"; |
|
536
|
|
|
|
|
|
|
} |
|
537
|
|
|
|
|
|
|
|
|
538
|
0
|
|
|
|
|
0
|
my $pid_file = $self->{'server'}->{'pid_file'}; |
|
539
|
0
|
0
|
|
|
|
0
|
if (-e $pid_file) { |
|
540
|
0
|
0
|
|
|
|
0
|
unlink $pid_file or throw "Failed to unlink pid file", {file => $pid_file, msg => $!}; |
|
541
|
|
|
|
|
|
|
} |
|
542
|
|
|
|
|
|
|
|
|
543
|
0
|
|
|
|
|
0
|
require Net::Server::Daemonize; |
|
544
|
0
|
0
|
|
|
|
0
|
if (! Net::Server::Daemonize::safe_fork()) { |
|
545
|
|
|
|
|
|
|
# child |
|
546
|
0
|
|
|
|
|
0
|
$self->run_server(); # will exit |
|
547
|
0
|
|
|
|
|
0
|
_ok(0, "Server run failed - check log"); |
|
548
|
0
|
|
|
|
|
0
|
exit 1; |
|
549
|
|
|
|
|
|
|
} |
|
550
|
|
|
|
|
|
|
|
|
551
|
0
|
|
|
|
|
0
|
sleep 1; |
|
552
|
0
|
|
|
|
|
0
|
$pid = $self->_get_pid; |
|
553
|
0
|
0
|
0
|
|
|
0
|
if (!$pid || ! kill 0, $pid) { |
|
554
|
0
|
|
|
|
|
0
|
_ok(0, "Starting - new pid not started - check log for details"); |
|
555
|
0
|
|
|
|
|
0
|
warn "Log file: $self->{'server'}->{'log_file'}\n"; |
|
556
|
0
|
|
|
|
|
0
|
exit 1; |
|
557
|
|
|
|
|
|
|
} |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# could attempt connection to test for open success |
|
560
|
|
|
|
|
|
|
|
|
561
|
0
|
|
|
|
|
0
|
_ok(1, "Started server"); |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
} |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub __stop { |
|
566
|
3
|
|
|
3
|
|
13
|
my $self = shift; |
|
567
|
3
|
|
|
|
|
144
|
my $pid = $self->_get_pid; |
|
568
|
3
|
|
|
|
|
66
|
my $name = $self->server_name; |
|
569
|
3
|
100
|
|
|
|
35
|
if (!$pid) { |
|
|
|
50
|
|
|
|
|
|
|
570
|
2
|
|
|
|
|
44
|
return _ok(1, "Already Stopped $name"); |
|
571
|
|
|
|
|
|
|
} elsif (! kill 0, $pid) { |
|
572
|
0
|
|
|
|
|
0
|
warn "Cannot kill 0 $pid while stopping: $!\n"; |
|
573
|
0
|
|
|
|
|
0
|
return _ok(0, "Failed to stop $name"); |
|
574
|
|
|
|
|
|
|
} |
|
575
|
1
|
50
|
33
|
|
|
29
|
if (! (kill(15, $pid) || kill(9, $pid))) { |
|
576
|
0
|
|
|
|
|
0
|
warn "Failed to kill TERM or KILL pid $pid while stopping\n"; |
|
577
|
0
|
|
|
|
|
0
|
return _ok(0, "Failed to stop $name"); |
|
578
|
|
|
|
|
|
|
} |
|
579
|
1
|
|
|
|
|
4
|
for (1 .. 25) { |
|
580
|
10
|
100
|
|
|
|
229
|
return _ok(1, "Stopped $name") if !kill 0, $pid; |
|
581
|
9
|
|
|
|
|
1805405
|
sleep 0.2; |
|
582
|
9
|
|
|
|
|
214
|
require POSIX; |
|
583
|
9
|
|
|
|
|
330
|
1 while waitpid(-1, POSIX::WNOHANG()) > 0; # handle rare non-setsid uses of run and _stop |
|
584
|
|
|
|
|
|
|
} |
|
585
|
|
|
|
|
|
|
|
|
586
|
0
|
|
|
|
|
0
|
_ok(0, "Stopping - pid still running"); |
|
587
|
0
|
|
|
|
|
0
|
exit 1; |
|
588
|
|
|
|
|
|
|
} |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub __restart { |
|
591
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
592
|
0
|
|
|
|
|
0
|
$self->__stop; |
|
593
|
0
|
|
|
|
|
0
|
$self->__start; |
|
594
|
|
|
|
|
|
|
} |
|
595
|
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
sub __reload { |
|
597
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
598
|
0
|
|
|
|
|
0
|
my $pid = $self->_get_pid; |
|
599
|
0
|
0
|
|
|
|
0
|
if (!$pid) { |
|
|
|
0
|
|
|
|
|
|
|
600
|
0
|
|
|
|
|
0
|
_ok(1, "Process appears to be stopped already - attempting start"); |
|
601
|
0
|
|
|
|
|
0
|
return $self->__start; |
|
602
|
|
|
|
|
|
|
} elsif (! kill 0, $pid) { |
|
603
|
0
|
|
|
|
|
0
|
_ok(1, "Process appears to be stopped (kill 0) - attempting start"); |
|
604
|
0
|
|
|
|
|
0
|
return $self->__start; |
|
605
|
|
|
|
|
|
|
} |
|
606
|
0
|
0
|
|
|
|
0
|
if (! kill 1, $pid) { |
|
607
|
0
|
|
|
|
|
0
|
_ok(0, "Reload failed: $!"); |
|
608
|
0
|
|
|
|
|
0
|
exit 1; |
|
609
|
|
|
|
|
|
|
} |
|
610
|
|
|
|
|
|
|
|
|
611
|
0
|
|
|
|
|
0
|
sleep 1; |
|
612
|
|
|
|
|
|
|
|
|
613
|
0
|
0
|
|
|
|
0
|
if (kill 0, $pid) { |
|
614
|
0
|
|
|
|
|
0
|
_ok(1, "Reloaded server"); |
|
615
|
|
|
|
|
|
|
} else { |
|
616
|
0
|
|
|
|
|
0
|
_ok(0, "Sent HUP - but server is gone away - attempting start"); |
|
617
|
0
|
|
|
|
|
0
|
$self->__start; |
|
618
|
|
|
|
|
|
|
} |
|
619
|
|
|
|
|
|
|
} |
|
620
|
|
|
|
|
|
|
|
|
621
|
0
|
|
|
0
|
|
0
|
sub __size_access { shift->__size_error('access_log_file') } |
|
622
|
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
sub __size_error { |
|
624
|
0
|
|
|
0
|
|
0
|
my ($self, $file) = @_; |
|
625
|
0
|
|
0
|
|
|
0
|
$file = $self->{'server'}->{$file || 'log_file'} || throw "No log_file to size"; |
|
626
|
0
|
|
|
|
|
0
|
return -s $file; |
|
627
|
|
|
|
|
|
|
} |
|
628
|
|
|
|
|
|
|
|
|
629
|
3
|
|
|
3
|
|
27
|
sub __tail_access { shift->__tail_error(shift(), 'access_log_file') } |
|
630
|
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
sub __tail_error { |
|
632
|
6
|
|
|
6
|
|
39
|
my ($self, $how, $file) = @_; |
|
633
|
6
|
|
50
|
|
|
40
|
$how = quotemeta($how || shift(@ARGV) || 'f'); |
|
634
|
6
|
|
33
|
|
|
89
|
$file = quotemeta($self->{'server'}->{$file || 'log_file'} || throw "No log_file to tail"); |
|
635
|
6
|
|
|
|
|
19
|
my $cmd = "tail -$how $file"; |
|
636
|
6
|
|
|
|
|
208
|
warn "$cmd\n"; |
|
637
|
6
|
50
|
|
|
|
28
|
exec $cmd if $how eq 'f'; |
|
638
|
6
|
|
100
|
|
|
55321
|
warn `$cmd` || "No error log\n"; |
|
639
|
|
|
|
|
|
|
} |
|
640
|
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
sub __ps { |
|
642
|
3
|
|
|
3
|
|
59
|
my $name = shift->server_name; |
|
643
|
3
|
100
|
|
|
|
41635
|
my $out = join '', grep {$_ =~ $name && $_ !~ /\b(?:$$|watch|ps)\b/} `ps auwx`; |
|
|
52
|
|
|
|
|
612
|
|
|
644
|
3
|
|
100
|
|
|
450
|
warn $out || "No processes found\n"; |
|
645
|
|
|
|
|
|
|
} |
|
646
|
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
648
|
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
sub cgidoc_brand { |
|
650
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
651
|
0
|
0
|
0
|
0
|
|
|
return $self->config(no_brand => 0) ? undef : $self->config(brand => sub { eval { config::provider() } || $self->_configs->{'provider'} || do { warn "Missing brand"; '-' } }); |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
} |
|
653
|
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
sub cgidoc { |
|
655
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
656
|
0
|
0
|
0
|
|
|
|
eval { CGI::initialize_globals() } or warn "Failed to initialize globals: $@" if $INC{'CGI.pm'}; # CGI.pm caches query parameters |
|
|
0
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
|
|
658
|
0
|
|
|
|
|
|
my $name = $self->server_name; |
|
659
|
|
|
|
|
|
|
my $disp = $self->dispatch_factory->({ |
|
660
|
|
|
|
|
|
|
is_server => "$name/doc", |
|
661
|
|
|
|
|
|
|
api_ip => $ENV{'REMOTE_ADDR'}, |
|
662
|
|
|
|
|
|
|
api_brand => $self->cgidoc_brand, |
|
663
|
0
|
|
|
|
|
|
remote_ip => $ENV{'REMOTE_ADDR'}, |
|
664
|
|
|
|
|
|
|
remote_user => '-auto-doc-', |
|
665
|
|
|
|
|
|
|
# token and remote_user will be updated by auto_doc_class if it is based on App::_Admin |
|
666
|
|
|
|
|
|
|
dbh_cache => {}, |
|
667
|
|
|
|
|
|
|
transport => 'form-doc', |
|
668
|
|
|
|
|
|
|
}); |
|
669
|
|
|
|
|
|
|
|
|
670
|
0
|
|
|
|
|
|
my $class = $self->config(auto_doc_class => 'Respite::AutoDoc'); |
|
671
|
0
|
|
|
|
|
|
(my $file = "$class.pm") =~ s|::|/|g; |
|
672
|
0
|
|
|
|
|
|
require $file; |
|
673
|
0
|
0
|
|
|
|
|
$class->new({ |
|
674
|
|
|
|
|
|
|
service => (($name =~ /^(\w+)_server/) ? $1 : $name), |
|
675
|
|
|
|
|
|
|
server => $self, |
|
676
|
|
|
|
|
|
|
api_obj => $disp, |
|
677
|
|
|
|
|
|
|
})->navigate; |
|
678
|
|
|
|
|
|
|
} |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
681
|
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
1; |