diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:51 +0000 |
commit | 4f0770f3df78ecd5dcaefbd214f7a1415366bca6 (patch) | |
tree | 72661b8f81594b855bcc967b819263f63fa30e17 /debian/perl-framework/t/modules | |
parent | Adding upstream version 2.4.56. (diff) | |
download | apache2-4f0770f3df78ecd5dcaefbd214f7a1415366bca6.tar.xz apache2-4f0770f3df78ecd5dcaefbd214f7a1415366bca6.zip |
Adding debian version 2.4.56-1~deb11u2.debian/2.4.56-1_deb11u2debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/perl-framework/t/modules')
49 files changed, 7088 insertions, 0 deletions
diff --git a/debian/perl-framework/t/modules/aaa.t b/debian/perl-framework/t/modules/aaa.t new file mode 100644 index 0000000..ffccec0 --- /dev/null +++ b/debian/perl-framework/t/modules/aaa.t @@ -0,0 +1,257 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil qw(t_write_file); +use File::Spec; + +# test the possibility of doing authz by user id or envvar in conjunction +# with the different AuthTypes + +Apache::TestRequest::user_agent(keep_alive => 1); + +my @headers = qw(WWW-Authenticate Authentication-Info Location); + +my %do_tests = ( basic => 11, + digest => 11, + form => 16, + ); + +my $tests = 2; # AuthzSendForbiddenOnFailure tests +foreach my $t (keys %do_tests) { + $tests += $do_tests{$t}; +} + +plan tests => $tests, + need need_lwp, + need_module('mod_authn_core'), + need_module('mod_authz_core'), + need_module('mod_authn_file'), + need_module('mod_authz_host'), + need_min_apache_version('2.3.7'); + +foreach my $t (sort keys %do_tests) { + if (!have_module("mod_auth_$t")) { + skip("skipping mod_auth_$t tests") for (1 .. $do_tests{$t}); + delete $do_tests{$t}; + } +} + +write_htpasswd(); + +# the auth type we are currently testing +my $type; + +foreach my $t (qw/basic digest/) { + next unless exists $do_tests{$t}; + $type = $t; + my $url = "/authz/$type/index.html"; + + { + my $response = GET $url; + + ok($response->code, + 401, + "$type: no user to authenticate and no env to authorize"); + } + + { + # bad pass + my $response = GET $url, + username => "u$type", password => 'foo'; + + ok($response->code, + 401, + "$type: u$type:foo not found"); + } + + { + # authenticated + my $response = GET $url, + username => "u$type", password => "p$type"; + + ok($response->code, + 200, + "$type: u$type:p$type found"); + } + + { + # authorized by env + my $response = GET $url, 'X-Allowed' => 'yes'; + + ok($response->code, + 200, + "$type: authz by envvar"); + + check_headers($response, 200); + } + + { + # authorized by env / with error + my $response = GET "$url.foo", 'X-Allowed' => 'yes'; + + ok($response->code, + 404, + "$type: not found"); + + check_headers($response, 404); + } +} + +# +# Form based authentication works a bit differently +# +if (exists $do_tests{form} && !have_module("mod_session_cookie")) { + skip("skipping mod_auth_form tests (mod_session_cookie required)") + for (1 .. $do_tests{form}); +} +elsif (exists $do_tests{form}) { + $type = 'form'; + my $url = "/authz/$type/index.html"; + my $login_form_url='/authz/login.html'; + my $login_url='/authz/form/dologin.html'; + + my @params = ( reset => 1, cookie_jar => {}, requests_redirectable => 0 ); + Apache::TestRequest::user_agent(@params); + + { + my $response = GET $url; + + ok($response->code, + 302, + "$type: access without user/env should redirect with 302"); + + my $loc = $response->header("Location"); + if (defined $loc && $loc =~ m{^http://[^/]+(/.*)$}) { + $loc = $1; + } + ok($loc, + "/authz/login.html", + "form: login without user/env should redirect to login form"); + } + + { + Apache::TestRequest::user_agent(@params); + # bad pass + my $response = POST $login_url, + content => "httpd_username=uform&httpd_password=foo"; + ok($response->code, + 302, + "form: login with wrong passwd should redirect with 302"); + + my $loc = $response->header("Location"); + if (defined $loc && $loc =~ m{^http://[^/]+(/.*)$}) { + $loc = $1; + } + ok($loc, + "/authz/login.html", + "form: login with wrong passwd should redirect to login form"); + + $response = GET $url; + ok($response->code, + 302, + "$type: wrong passwd should not allow access"); + } + + { + # authenticated + Apache::TestRequest::user_agent(@params); + my $response = POST $login_url, + content => "httpd_username=uform&httpd_password=pform"; + ok($response->code, + 302, + "form: login with correct passwd should redirect with 302"); + + my $loc = $response->header("Location"); + if (defined $loc && $loc =~ m{^http://[^/]+(/.*)$}) { + $loc = $1; + } + ok($1, + "/authz/form/", + "form: login with correct passwd should redirect to SuccessLocation"); + + $response = GET $url; + ok($response->code, + 200, + "$type: correct passwd did not allow access"); + } + + { + # authorized by env + Apache::TestRequest::user_agent(@params); + my $response = GET $url, 'X-Allowed' => 'yes'; + + ok($response->code, + 200, + "$type: authz by envvar"); + + check_headers($response, 200); + } + + { + # authorized by env / with error + my $response = GET "$url.foo", 'X-Allowed' => 'yes'; + + ok($response->code, + 404, + "$type: not found"); + + check_headers($response, 404); + } +} + +# +# Test AuthzSendForbiddenOnFailure +# +if (have_min_apache_version("2.3.11")) { + foreach my $want (401, 403) { + my $response = GET "/authz/fail/$want", + username => "ubasic", + password => "pbasic"; + my $got = $response->code; + ok($got, $want, "Expected code $want, got $got"); + } +} +else { + skip "skipping tests with httpd <2.3.11" foreach (1..2); +} + +# +# check that none of the authentication related headers exists +# +sub check_headers +{ + my $response = shift; + my $code = shift; + + foreach my $h (@headers) { + ok($response->header($h), + undef, + "$type: $code response should have no $h header"); + } +} + +# +# write out the htpasswd files +# +sub write_htpasswd +{ + my $digest_file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'realm2'); + t_write_file($digest_file, << 'EOF' ); +# udigest/pdigest +udigest:realm2:bccffb0d42943019acfbebf2039b8a3a +EOF + + my $basic_file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'basic1'); + t_write_file($basic_file, << 'EOF' ); +# ubasic:pbasic +ubasic:$apr1$opONH1Fj$dX0sZdZ0rRWEk0Wj8y.Qv1 +EOF + + my $form_file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'form1'); + t_write_file($form_file, << 'EOF' ); +# uform:pform +uform:$apr1$BzhDZ03D$U598kbSXGy/R7OhYXu.JJ0 +EOF +} diff --git a/debian/perl-framework/t/modules/access.t b/debian/perl-framework/t/modules/access.t new file mode 100644 index 0000000..0c8e34e --- /dev/null +++ b/debian/perl-framework/t/modules/access.t @@ -0,0 +1,191 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## +## mod_access test +## + +my $vars = Apache::Test::vars(); +my $localhost_name = $vars->{servername}; +my $remote_addr = $vars->{remote_addr}; +my(@addr) = split /\./, $remote_addr; +my $addr1 = $addr[0]; +my $addr2 = join '.', $addr[0], $addr[1]; + +my @localhost = ( + 'from all', + "from $localhost_name", + "from $remote_addr", + "from $addr2", + "from $remote_addr/255.255.0.0", + "from $remote_addr/16", + 'from somewhere.else.com', + 'from 66.6.6.6' +); +my @order = ('deny,allow', 'allow,deny', 'mutual-failure'); +my @allow = @localhost; +my @deny = @localhost; + +plan tests => (@order * @allow * @deny * 2) + (@order * @allow), \&need_access; + +my $dir = $vars->{t_dir}; +$dir .= "/htdocs/modules/access/htaccess"; + +sub write_htaccess { + my $conf_str = shift; + open (HT, ">$dir/.htaccess") or die "cant open htaccess: $!"; + print HT $conf_str; + close (HT); +} + +my ($config_string, $ok); +foreach my $order (@order) { + foreach my $allow (@allow) { + $config_string = "Order $order\nAllow $allow\n"; + write_htaccess($config_string); + + t_debug "---", $config_string; + + if ($order eq 'deny,allow') { + + ## if allowing by default, + ## there is no 'Deny' directive, so everything + ## is allowed. + t_debug "expecting access."; + ok GET_OK "/modules/access/htaccess/index.html"; + + + } else { + + ## denying by default + + if ($allow =~ /^from $addr1/ + || $allow eq "from $localhost_name" + || $allow eq 'from all') { + + ## if we are explicitly allowed, its ok + t_debug "expecting access."; + ok GET_OK "/modules/access/htaccess/index.html"; + + } else { + + ## otherwise, not ok + t_debug "expecting access denial."; + ok !GET_OK "/modules/access/htaccess/index.html"; + } + } + + + foreach my $deny (@deny) { + $config_string = "Order $order\nDeny $deny\n"; + write_htaccess($config_string); + + t_debug "---", $config_string; + + if ($order eq 'deny,allow') { + + ## allowing by default + + if ($deny =~ /^from $addr1/ + || $deny eq "from $localhost_name" + || $deny eq 'from all') { + + ## if we are denied explicitly + ## its not ok + t_debug "expecting access denial."; + ok !GET_OK "/modules/access/htaccess/index.html"; + + } else { + + ## otherwise, ok + t_debug "expecting access."; + ok GET_OK "/modules/access/htaccess/index.html"; + + } + } else { + + ## if denying by default + ## there is no 'Allow' directive, so + ## everything is denied. + t_debug "expecting access denial."; + ok !GET_OK "/modules/access/htaccess/index.html"; + + } + + $config_string = "Order $order\nAllow $allow\nDeny $deny\n"; + write_htaccess($config_string); + + t_debug "---", $config_string; + + if ($order eq 'deny,allow') { + + ## allowing by default + + if ($allow =~ /^from $addr1/ + || $allow eq "from $localhost_name" + || $allow eq 'from all') { + + ## we are explicitly allowed + ## so it is ok. + t_debug "expecting access."; + ok GET_OK "/modules/access/htaccess/index.html"; + + } elsif ($deny =~ /^from $addr1/ + || $deny eq "from $localhost_name" + || $deny eq 'from all') { + + ## if we are not explicitly allowed + ## and are explicitly denied, + ## we are denied access. + t_debug "expecting access denial."; + ok !GET_OK "/modules/access/htaccess/index.html"; + + } else { + + ## if we are not explicity allowed + ## or explicitly denied, + ## we get access. + t_debug "expecting access."; + ok GET_OK "/modules/access/htaccess/index.html"; + + } + } else { + + ## denying by default + + if ($deny =~ /^from $addr1/ + || $deny eq "from $localhost_name" + || $deny eq 'from all') { + + ## if we are explicitly denied, + ## we get no access. + t_debug "expecting access denial."; + ok !GET_OK "/modules/access/htaccess/index.html"; + + } elsif ($allow =~ /^from $addr1/ + || $allow eq "from $localhost_name" + || $allow eq 'from all') { + + ## if we are not explicitly denied + ## and are explicitly allowed, + ## we get access. + t_debug "expecting access."; + ok GET_OK "/modules/access/htaccess/index.html"; + + } else { + + ## if we are not explicitly denied + ## and not explicitly allowed, + ## we get no access. + t_debug "expecting access denial."; + ok !GET_OK "/modules/access/htaccess/index.html"; + + } + } + } + } +} diff --git a/debian/perl-framework/t/modules/actions.t b/debian/perl-framework/t/modules/actions.t new file mode 100644 index 0000000..337d4d8 --- /dev/null +++ b/debian/perl-framework/t/modules/actions.t @@ -0,0 +1,59 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## +## mod_action tests +## +my @tests_action = ( + [ "mod_actions/", 200, "nada"], # Handler for this location + + [ "modules/actions/action/test.xyz", 404], # No handler for .xyz + [ "modules/actions/action/test.xyz1", 404], # Handler for .xyz1, but not virtual + [ "modules/actions/action/test.xyz22", 404], # No Handler for .xyz2x (but one for .xyz2) + + [ "modules/actions/action/test.xyz2", 200, "nada"], # Handler for .xyz2, and virtual +); + +my @tests_script = ( + [ "modules/actions/script/test.x", 404], + [ "modules/actions/script/test.x?foo=bar", 200, "foo=bar"], +); + +my $r; + +plan tests => scalar @tests_action*2 + scalar @tests_script*(2+2+1), need_module('mod_actions'); + +foreach my $test (@tests_action) { + $r = GET($test->[0]); + ok t_cmp($r->code, $test->[1]); + if ($test->[1] == 200) { + ok t_cmp($r->content, $test->[2]); + } + else { + skip "RC=404, no need to check content", 1; + } +} + +foreach my $test (@tests_script) { + $r = GET($test->[0]); + ok t_cmp($r->code, $test->[1]); + if ($test->[1] == 200) { + ok t_cmp($r->content, $test->[2]); + } + else { + skip "RC=404, no need to check content", 1; + } + + $r = POST($test->[0], content => "foo2=bar2"); + ok t_cmp($r->code, 200); + ok t_cmp($r->content, "POST\nfoo2: bar2\n"); + + # Method not allowed + $r = PUT($test->[0], content => "foo2=bar2"); + ok t_cmp($r->code, 405); +} + diff --git a/debian/perl-framework/t/modules/alias.t b/debian/perl-framework/t/modules/alias.t new file mode 100644 index 0000000..957fccc --- /dev/null +++ b/debian/perl-framework/t/modules/alias.t @@ -0,0 +1,240 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +use constant WINFU => Apache::TestConfig::WINFU(); + +## +## mod_alias test +## + +## redirect codes for Redirect testing ## +my %redirect = ( + perm => '301', + perm2 => '301', + temp => '302', + temp2 => '302', + seeother => '303', + gone => '410', + forbid => '403' +); + +## RedirectMatch testing ## +my %rm_body = ( + p => '301', + t => '302' +); + +my %rm_rc = ( + s => '303', + g => '410', + f => '403' +); + + +my %relative_redirects = ( + "/redirect_relative/default" => "^http", # URL should be absolute + "/redirect_relative/on" => "^/out-on", # URL should be relative + "/redirect_relative/off" => "^http", # URL should be absolute + "/redirect_relative/off/fail" => undef, # 500 due to invalid URL +); + +#XXX: find something that'll on other platforms (/bin/sh aint it) +my $script_tests = WINFU ? 0 : 4 + have_min_apache_version("2.4.19"); + +my $tests = 12 + have_min_apache_version("2.4.19") * 10 + + (keys %redirect) + + (keys %rm_body) * (1 + have_min_apache_version("2.4.19")) * 10 + + (keys %rm_rc) * (1 + have_min_apache_version("2.4.19")) * 10 + + $script_tests; + +if (have_min_apache_version("2.5.1")) { + $tests += (keys %relative_redirects)*2; +} + +#LWP required to follow redirects +plan tests => $tests, need need_module('alias'), need_lwp; + +## simple alias ## +t_debug "verifying simple aliases"; +ok t_cmp((GET_RC "/alias/"), + 200, + "/alias/"); +## alias to a non-existant area ## +ok t_cmp((GET_RC "/bogu/"), + 404, + "/bogu/"); + + +t_debug "verifying alias match with /ali[0-9]."; +for (my $i=0 ; $i <= 9 ; $i++) { + ok t_cmp((GET_BODY "/ali$i"), + $i, + "/ali$i"); +} + +if (have_min_apache_version("2.4.19")) { + t_debug "verifying expression alias match with /expr/ali[0-9]."; + for (my $i=0 ; $i <= 9 ; $i++) { + ok t_cmp((GET_BODY "/expr/ali$i"), + $i, + "/ali$i"); + } +} + +my ($actual, $expected); +foreach (sort keys %redirect) { + ## make LWP not follow the redirect since we + ## are just interested in the return code. + local $Apache::TestRequest::RedirectOK = 0; + + $expected = $redirect{$_}; + $actual = GET_RC "/$_"; + ok t_cmp($actual, + $expected, + "/$_"); +} + +print "verifying body of perm and temp redirect match\n"; +foreach (sort keys %rm_body) { + for (my $i=0 ; $i <= 9 ; $i++) { + $expected = $i; + $actual = GET_BODY "/$_$i"; + ok t_cmp($actual, + $expected, + "/$_$i"); + } +} + +if (have_min_apache_version("2.4.19")) { + print "verifying body of perm and temp redirect match with expression support\n"; + foreach (sort keys %rm_body) { + for (my $i=0 ; $i <= 9 ; $i++) { + $expected = $i; + $actual = GET_BODY "/expr/$_$i"; + ok t_cmp($actual, + $expected, + "/$_$i"); + } + } +} + +print "verifying return code of seeother and gone redirect match\n"; +foreach (keys %rm_rc) { + ## make LWP not follow the redirect since we + ## are just interested in the return code. + local $Apache::TestRequest::RedirectOK = 0; + + $expected = $rm_rc{$_}; + for (my $i=0 ; $i <= 9 ; $i++) { + $actual = GET_RC "$_$i"; + ok t_cmp($actual, + $expected, + "$_$i"); + } +} + +if (have_min_apache_version("2.4.19")) { + print "verifying return code of seeother and gone redirect match with expression support\n"; + foreach (keys %rm_rc) { + ## make LWP not follow the redirect since we + ## are just interested in the return code. + local $Apache::TestRequest::RedirectOK = 0; + + $expected = $rm_rc{$_}; + for (my $i=0 ; $i <= 9 ; $i++) { + $actual = GET_RC "/expr/$_$i"; + ok t_cmp($actual, + $expected, + "$_$i"); + } + } +} + +## create a little cgi to test ScriptAlias and ScriptAliasMatch ## +my $string = "this is a shell script cgi."; +my $cgi =<<EOF; +#!/bin/sh +echo Content-type: text/plain +echo +echo $string +EOF + +my $vars = Apache::Test::vars(); +my $script = "$vars->{t_dir}/htdocs/modules/alias/script"; + +t_write_file($script,$cgi); +chmod 0755, $script; + +## if we get the script here it will be plain text ## +t_debug "verifying /modules/alias/script is plain text"; +ok t_cmp((GET_BODY "/modules/alias/script"), + $cgi, + "/modules/alias/script") unless WINFU; + +if (have_cgi) { + ## here it should be the result of the executed cgi ## + t_debug "verifying same file accessed at /cgi/script is executed code"; + ok t_cmp((GET_BODY "/cgi/script"), + "$string\n", + "/cgi/script") unless WINFU; +} +else { + skip "skipping test without CGI module"; +} + +if (have_cgi) { + ## with ScriptAliasMatch ## + t_debug "verifying ScriptAliasMatch with /aliascgi-script"; + ok t_cmp((GET_BODY "/aliascgi-script"), + "$string\n", + "/aliascgi-script") unless WINFU; +} +else { + skip "skipping test without CGI module"; +} + +if (have_min_apache_version("2.4.19")) { + if (have_cgi) { + ## with ScriptAlias in LocationMatch ## + t_debug "verifying ScriptAlias in LocationMatch with /expr/aliascgi-script"; + ok t_cmp((GET_BODY "/expr/aliascgi-script"), + "$string\n", + "/aliascgi-script") unless WINFU; + } + else { + skip "skipping test without CGI module"; + } +} + +## failure with ScriptAliasMatch ## +t_debug "verifying bad script alias."; +ok t_cmp((GET_RC "/aliascgi-nada"), + 404, + "/aliascgi-nada") unless WINFU; + +## clean up ## +t_rmtree("$vars->{t_logs}/mod_cgi.log"); + + +if (have_min_apache_version("2.5.1")) { + my ($path, $regex); + while (($path, $regex) = each (%relative_redirects)) { + local $Apache::TestRequest::RedirectOK = 0; + my $r; + $r = GET($path); + if (defined($regex)) { + ok t_cmp($r->code, "302"); + ok t_cmp($r->header("Location"), qr/$regex/, "failure on $path"); + } + else { + ok t_cmp($r->code, "500"); + ok t_cmp($r->header("Location"), undef, "failure on $path"); + } + } +} + diff --git a/debian/perl-framework/t/modules/allowmethods.t b/debian/perl-framework/t/modules/allowmethods.t new file mode 100644 index 0000000..d012554 --- /dev/null +++ b/debian/perl-framework/t/modules/allowmethods.t @@ -0,0 +1,64 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +my $r; +my $get = "Get"; +my $head = "Head"; +my $post = "Post"; +my $options = "Options"; + +## +## mod_allowmethods test +## +my @test_cases = ( + [ $get, $get, 200 ], + [ $head, $get, 200 ], + [ $post, $get, 405 ], + [ $get, $head, 200 ], + [ $head, $head, 200 ], + [ $post, $head, 405 ], + [ $get, $post, 405 ], + [ $head, $post, 405 ], + [ $post, $post, 200 ], +); + +my @new_test_cases = ( + [ $get, $post . '/reset', 200 ], + [ $post, $get . '/post', 200 ], + [ $get, $get . '/post', 200 ], + [ $options, $get . '/post', 405 ], + [ $get, $get . '/none', 405 ], + [ $get, "NoPost", 200 ], + [ $post, "NoPost", 405 ], + [ $options, "NoPost" , 200 ], +); + +if (have_min_apache_version('2.5.1')) { + push(@test_cases, @new_test_cases); +} + +plan tests => (scalar @test_cases), have_module 'allowmethods'; + +foreach my $case (@test_cases) { + my ($fct, $allowed, $rc) = @{$case}; + + if ($fct eq $get) { + $r = GET('/modules/allowmethods/' . $allowed . '/'); + } + elsif ($fct eq $head) { + $r = HEAD('/modules/allowmethods/' . $allowed . '/'); + } + elsif ($fct eq $post) { + $r = POST('/modules/allowmethods/' . $allowed . '/foo.txt'); + } + elsif ($fct eq $options) { + $r = OPTIONS('/modules/allowmethods/' . $allowed . '/'); + } + + ok t_cmp($r->code, $rc, "$fct request to /$allowed responds $rc"); +} + diff --git a/debian/perl-framework/t/modules/asis.t b/debian/perl-framework/t/modules/asis.t new file mode 100644 index 0000000..a8c300e --- /dev/null +++ b/debian/perl-framework/t/modules/asis.t @@ -0,0 +1,21 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## +## mod_asis tests +## + +plan tests => 3, need_module 'asis'; + +my $body = GET_BODY "/modules/asis/foo.asis"; +ok t_cmp($body, "This is asis content.\n", "asis content OK"); + +my $rc = GET_RC "/modules/asis/notfound.asis"; +ok t_cmp($rc, 404, "asis gave 404 error"); + +$rc = GET_RC "/modules/asis/forbid.asis"; +ok t_cmp($rc, 403, "asis gave 403 error"); diff --git a/debian/perl-framework/t/modules/authz_core.t b/debian/perl-framework/t/modules/authz_core.t new file mode 100644 index 0000000..6e43aa3 --- /dev/null +++ b/debian/perl-framework/t/modules/authz_core.t @@ -0,0 +1,360 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil qw(t_write_file); +use File::Spec; + +# test RequireAll/RequireAny containers and AuthzMerging + +plan tests => 168 + 14*24, + need need_lwp, + need_module('mod_authn_core'), + need_module('mod_authz_core'), + need_module('mod_authz_host'), + need_module('mod_authz_groupfile'), + need_min_apache_version('2.3.6'); + + +my $text = ''; + +sub check +{ + my $rc = shift; + my $path = shift; + + my @args; + foreach my $e (@_) { + if ($e =~ /user/) { + push @args, username => $e, password => $e; + } + else { + push @args, "X-Allowed$e" => 'yes'; + } + } + my $res = GET "/authz_core/$path", @args; + my $got = $res->code; + print "# got $got, expected $rc [$text: $path @_]\n"; + ok($got == $rc); +} + +sub write_htaccess +{ + my $path = shift; + my $merging = shift || ""; + my $container = shift || ""; + + $text = "$path $merging $container @_"; + + my $need_auth; + my $content = ""; + $content .= "AuthMerging $merging\n" if $merging; + + if ($container) { + $content .= "<Require$container>\n"; + } + foreach (@_) { + my $req = $_; + my $not = ""; + if ($req =~ s/^\!//) { + $not = 'not'; + } + if ($req =~ /all/) { + $content .= "Require $not $req\n"; + } + elsif ($req =~ /user/) { + # 'group' is correct, see comment about mod_authany below + $content .= "Require $not group $req\n"; + $need_auth = 1; + } + else { + $content .= "Require $not env allowed$req\n"; + } + } + if ($container) { + $content .= "</Require$container>\n"; + } + + if ($need_auth) { + $content .= "AuthType basic\n"; + $content .= "AuthName basic1\n"; + $content .= "AuthUserFile basic1\n"; + $content .= "AuthGroupFile groups1\n"; + } + + my $file = File::Spec->catfile(Apache::Test::vars('documentroot'), + "/authz_core/$path/.htaccess"); + t_write_file($file, $content); +} + +# create some users (username == password) +my $basic_file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'basic1'); +t_write_file($basic_file, << 'EOF' ); +user1:NYSYdf7MU5KpU +user2:KJ7Yxzr1VVzAI +user3:xnpSvZ2iqti/c +EOF + +# mod_authany overrides the 'user' provider, so we can't check users directly :-( +# create some groups instead: +my $group_file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'groups1'); +t_write_file($group_file, << 'EOF' ); +user1:user1 +user2:user2 +user3:user3 +EOF + +write_htaccess("a/", undef, undef); +check(200, "a/"); +check(200, "a/", 1); +check(200, "a/", 2); +check(200, "a/", 1, 2); +check(200, "a/", 3); + +write_htaccess("a/", undef, undef, "user1"); +check(401, "a/"); +check(200, "a/", "user1"); +check(401, "a/", "user2"); + +write_htaccess("a/", undef, "Any", 1, 2); +check(403, "a/"); +check(200, "a/", 1); +check(200, "a/", 2); +check(200, "a/", 1, 2); +check(403, "a/", 3); + write_htaccess("a/b/", undef, "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "Off", "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "Or", "Any", 2, 3); + check(403, "a/b/"); + check(200, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "And", "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 1, 2); + check(200, "a/b/", 1, 3); + check(200, "a/b/", 2, 3); + write_htaccess("a/b/", undef, "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 2, 3); + check(403, "a/b/", 1, 3); + write_htaccess("a/b/", "Off", "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 2, 3); + check(403, "a/b/", 1, 3); + write_htaccess("a/b/", "Or", "All", 3, 4); + check(403, "a/b/"); + check(200, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 2, 3); + check(200, "a/b/", 3, 4); + check(403, "a/b/", 3); + check(403, "a/b/", 4); + write_htaccess("a/b/", "And", "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(403, "a/b/", 1, 2); + check(403, "a/b/", 1, 3); + check(200, "a/b/", 2, 3); + + +write_htaccess("a/", undef, "All", 1, "!2"); +check(403, "a/"); +check(200, "a/", 1); +check(403, "a/", 2); +check(403, "a/", 1, 2); +check(403, "a/", 3); + write_htaccess("a/b/", undef, "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "Off", "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "Or", "Any", 3, 4); + check(403, "a/b/"); + check(200, "a/b/", 1); + check(403, "a/b/", 1, 2); + check(200, "a/b/", 1, 2, 3); + check(200, "a/b/", 1, 2, 4); + check(200, "a/b/", 4); + write_htaccess("a/b/", "And", "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(403, "a/b/", 1, 2); + check(200, "a/b/", 1, 3); + check(403, "a/b/", 2, 3); + # should not inherit AuthMerging And from a/b/ + write_htaccess("a/b/c/", undef, "Any", 4); + check(403, "a/b/c/", 1, 3); + check(200, "a/b/c/", 4); + check(200, "a/b/c/", 1, 2, 4); + write_htaccess("a/b/", undef, "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 2, 3); + check(403, "a/b/", 1, 3); + write_htaccess("a/b/", "Off", "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 2, 3); + check(403, "a/b/", 1, 3); + write_htaccess("a/b/", "Or", "All", 3, 4); + check(403, "a/b/"); + check(200, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 2, 3); + check(200, "a/b/", 3, 4); + check(403, "a/b/", 3); + check(403, "a/b/", 4); + write_htaccess("a/b/", "And", "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(403, "a/b/", 1, 2); + check(403, "a/b/", 1, 3); + check(403, "a/b/", 2, 3); + + +write_htaccess("a/", undef, "All", 1, 2); +check(403, "a/"); +check(403, "a/", 1); +check(403, "a/", 2); +check(200, "a/", 1, 2); + write_htaccess("a/b/", undef, "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "Off", "Any", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(200, "a/b/", 2); + check(200, "a/b/", 3); + write_htaccess("a/b/", "Or", "Any", 3, 4); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(200, "a/b/", 1, 2); + check(200, "a/b/", 3); + check(200, "a/b/", 4); + write_htaccess("a/b/", "And", "Any", 3, 4); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(403, "a/b/", 4); + check(403, "a/b/", 1, 2); + check(200, "a/b/", 1, 2, 3); + check(200, "a/b/", 1, 2, 4); + check(403, "a/b/", 1, 3, 4); + write_htaccess("a/b/", undef, "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 2, 3); + check(403, "a/b/", 1, 3); + write_htaccess("a/b/", "Off", "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(200, "a/b/", 2, 3); + check(403, "a/b/", 1, 3); + write_htaccess("a/b/", "Or", "All", 3, 4); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(403, "a/b/", 4); + check(403, "a/b/", 2, 3); + check(200, "a/b/", 3, 4); + check(200, "a/b/", 1, 2); + write_htaccess("a/b/", "And", "All", 2, 3); + check(403, "a/b/"); + check(403, "a/b/", 1); + check(403, "a/b/", 2); + check(403, "a/b/", 3); + check(403, "a/b/", 1, 2); + check(403, "a/b/", 1, 3); + check(403, "a/b/", 2, 3); + check(200, "a/b/", 1, 2, 3); + +# +# To test merging of a mix of user and non-user authz providers, +# we should test all orders. +# + +# helper function to get all permutations of an array +# returns array of references +sub permutations +{ + my @results = [shift]; + + foreach my $el (@_) { + my @new_results; + foreach my $arr (@results) { + my $len = scalar(@{$arr}); + foreach my $i (0 .. $len) { + my @new = @{$arr}; + splice @new, $i, 0, $el; + push @new_results, \@new; + } + } + @results = @new_results; + } + return @results; +} + + +my @perms = permutations(qw/user1 user2 1 2/); +foreach my $p (@perms) { + write_htaccess("a/", undef, "All", @{$p}); + check(403, "a/"); + check(403, "a/", 1); + check(403, "a/", "user1"); + check(401, "a/", 1, 2); + check(401, "a/", 1, 2, "user1"); + check(401, "a/", 1, 2, "user3"); + check(403, "a/", 1, "user1"); + + write_htaccess("a/", undef, "Any", @{$p}); + check(401, "a/"); + check(200, "a/", 1); + check(200, "a/", "user1"); + check(401, "a/", "user3"); + check(200, "a/", 1, 2); + check(200, "a/", 1, "user1"); + check(200, "a/", 1, "user3"); +} diff --git a/debian/perl-framework/t/modules/autoindex.t b/debian/perl-framework/t/modules/autoindex.t new file mode 100644 index 0000000..76c9af4 --- /dev/null +++ b/debian/perl-framework/t/modules/autoindex.t @@ -0,0 +1,444 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; + +## +## mod_autoindex test +## +## 9-4-01 +## this only tests for a very limited set of functionality +## in the autoindex module. namely, file sorting and display +## with IndexOrderDefault directive and FancyIndexing. +## more to come... + +my $htdocs = Apache::Test::vars('documentroot'); +my $ai_dir = "/modules/autoindex"; +my $uri_prefix = "$ai_dir/htaccess"; +my $dir = "$htdocs$uri_prefix"; +my $htaccess = "$dir/.htaccess"; +my $readme = 'autoindex test README'; +my $s = 'HITHERE'; +my $uri = "$uri_prefix/"; +my $file_prefix = 'ai-test'; +my ($C,$O); +my $cfg = Apache::Test::config(); +my $have_apache_2 = have_apache 2; +my $hr = $have_apache_2 ? '<hr>' : '<hr />'; + +my %file = +( + README => + { + size => length($readme), + date => 998932210 + }, + txt => + { + size => 5, + date => 998934398 + }, + jpg => + { + size => 15, + date => 998936491 + }, + gif => + { + size => 1568, + date => 998932291 + }, + html => + { + size => 9815, + date => 922934391 + }, + doc => + { + size => 415, + date => 998134391 + }, + gz => + { + size => 1, + date => 998935991 + }, + tar => + { + size => 1009845, + date => 997932391 + }, + php => + { + size => 913515, + date => 998434391 + } +); + +plan tests => 84, ['autoindex']; + +## set up environment ## +$cfg->gendir("$htdocs/$ai_dir"); +$cfg->gendir("$dir"); +test_content('create'); + +## run tests ## +foreach my $fancy (0,1) { + + ## test default order requests ## + foreach my $order (qw(Ascending Descending)) { + $O = substr($order, 0, 1); + + foreach my $component (qw(Name Date Size)) { + $C = substr($component, 0, 1); + $C = 'M' if $C eq 'D'; + my $config_string = ''; + $config_string = "IndexOptions FancyIndexing\n" if $fancy; + $config_string .= "IndexOrderDefault $order $component\n"; + + print "---\n$config_string\n"; + sok { ai_test($config_string,$C,$O,$uri) }; + + ## test explicit order requests ## + foreach $C (qw(N M S)) { + foreach $O (qw(A D)) { + my $test_uri; + if ($have_apache_2) { + $test_uri = "$uri?C=$C\&O=$O"; + } else { + $test_uri = "$uri?$C=$O"; + } + + print "---\n$config_string\n(C=$C O=$O)\n"; + sok { ai_test($config_string,$C,$O,$test_uri) }; + + } + } + } + } +} + +sub ai_test ($$$$) { + my ($htconf,$c,$o,$t_uri) = @_; + + my $html_head; + + if (have_min_apache_version('2.5.1')) { + $html_head = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'; + } + else { + $html_head = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'; + } + + $html_head .= <<HEAD; + +<html> + <head> + <title>Index of $uri_prefix</title> + </head> + <body> +<h1>Index of $uri_prefix</h1> +HEAD + my $html_foot = "${hr}</pre>\n</body></html>\n"; + + my $i; + my $fail = 0; + my $FancyIndexing = ($htconf =~ /FancyIndex/); + + write_htaccess($htconf); + my $actual = GET_BODY $t_uri; + print "GET $t_uri\n"; + + ################################ + ## this may not be ok! ## + ##----------------------------## + ## should you be able to sort ## + ## by components other than ## + ## name when FancyIndexing is ## + ## not on? ## + ################################ + $c = 'N' unless $FancyIndexing;# + ################################ + ## end questionable block ## + ################################ + + my @file_list; + if ($o =~ /^A$/i) { + ## sort ascending ## + if ($c =~ /^N$/i) { + ## by name ## + @file_list = sort keys %file; + } elsif ($c =~ /^S$/i) { + ## by size ## + @file_list = + sort {$file{$a}{size} <=> $file{$b}{size}} keys %file; + } elsif ($c =~ /^M$/i) { + ## by date ## + @file_list = + sort {$file{$a}{date} <=> $file{$b}{date}} keys %file; + } else { + print "big error: C=$c, O=$o\n"; + return 0; + } + } elsif ($o =~ /^D$/i) { + ## sort decending ## + if ($c =~ /^N$/i) { + ## by name ## + @file_list = reverse sort keys %file; + } elsif ($c =~ /^S$/i) { + ## by size ## + @file_list = + sort {$file{$b}{size} <=> $file{$a}{size}} keys %file; + } elsif ($c =~ /^M$/i) { + ## by date ## + @file_list = + sort {$file{$b}{date} <=> $file{$a}{date}} keys %file; + } else { + print "big error: C=$c, O=$o\n"; + return 0; + } + } else { + print "big error: C=$c, O=$o\n"; + return 0; + } + + my $sep = '&'; + + if ($have_apache_2 && $actual =~ /\?C=.\;/) { + ## cope with new 2.1-style headers which use a semi-colon + ## to separate query segment parameters + $sep = ';'; + } + + if ($actual =~ /<hr \/>/) { + ## cope with new-fangled <hr /> tags + $hr = '<hr />'; + } + + ## set up html for fancy indexing ## + if ($FancyIndexing) { + my $name_href; + my $date_href; + my $size_href; + if ($have_apache_2) { + $name_href = 'C=N'.$sep.'O=A'; + $date_href = 'C=M'.$sep.'O=A'; + $size_href = 'C=S'.$sep.'O=A'; + } else { + $name_href = 'N=A'; + $date_href = 'M=A'; + $size_href = 'S=A'; + } + foreach ($name_href, $date_href, $size_href) { + if ($have_apache_2) { + if ($_ =~ /^C=$c/i) { + #print "changed ->$_<- to "; + $_ = "C=$c$sep"."O=A" if $o =~ /^D$/i; + $_ = "C=$c$sep"."O=D" if $o =~ /^A$/i; + last; + } + } else { + if ($_ =~ /^$c=/i) { + $_ = "$c=A" if $o =~ /^D$/i; + $_ = "$c=D" if $o =~ /^A$/i; + last; + } + } + } + + if ($have_apache_2) { + + $html_head .= + "<pre> <a href=\"?$name_href\">Name</a> <a href=\"?$date_href\">Last modified</a> <a href=\"?$size_href\">Size</a> <a href=\"?C=D$sep"."O=A\">Description</a>${hr} <a href=\"/modules/autoindex/\">Parent Directory</a> - \n"; + + $html_foot = "${hr}</pre>\n</body></html>\n"; + + } else { + + $html_head .= + "<pre><a href=\"?$name_href\">name</a> <a href=\"?$date_href\">last modified</a> <a href=\"?$size_href\">size</a> <a href=\"?d=a\">description</a>\n<hr>\n<parent>\n"; + + $html_foot = "</pre><hr>\n</body></html>\n"; + + } + + } else { + ## html for non fancy indexing ## + + if ($have_apache_2) { + + $html_head .= + "<ul><li><a href=\"/modules/autoindex/\"> Parent Directory</a></li>\n"; + + $html_foot = "</ul>\n</body></html>\n"; + + } else { + + $html_head .= + "<ul><li><a href=\"/modules/autoindex/\"> Parent Directory</a>\n"; + + $html_foot = "</ul></body></html>\n"; + + } + } + + ## verify html heading ## + my @exp_head = split /\n/, $html_head; + my @actual = split /\n/, $actual; + for ($i=0;$i<@exp_head;$i++) { + + $actual[$i] = lc($actual[$i]); + $exp_head[$i] = lc($exp_head[$i]); + + if ($actual[$i] eq $exp_head[$i]) { + next; + } else { + if (!$have_apache_2 && $actual[$i] =~ /parent directory/ && + $exp_head[$i] eq "<parent>") { + ## cursory check on this one due to timestamp + ## in parent directory line in 1.3 + next; + } + + print "expect:\n->$exp_head[$i]<-\n"; + print "actual:\n->$actual[$i]<-\n"; + $fail = 1; + last; + } + } + + if ($fail) { + print "failed on html head (C=$c\&O=$o"; + print " FancyIndexing" if $FancyIndexing; + print ")\n"; + return 0; + } + + ## file list verification ## + my $e = 0; + for ($i=$i;$file_list[$e] && $actual;$i++) { + my $cmp_string = "<li><a href=\"$file_prefix.$file_list[$e]\"> $file_prefix.$file_list[$e]</a></li>"; + $cmp_string = "<li><a href=\"$file_prefix.$file_list[$e]\"> $file_prefix.$file_list[$e]</a>" unless ($have_apache_2); + + $cmp_string = + "<a href=\"$file_prefix.$file_list[$e]\">$file_prefix.$file_list[$e]</a>" + if $FancyIndexing; + + if ($file_list[$e] eq 'README' or + $file_list[$e] eq '.htaccess') { + $cmp_string = + "<a href=\"$file_list[$e]\">$file_list[$e]</a>" + if $FancyIndexing; + $cmp_string = + "<li><a href=\"$file_list[$e]\"> $file_list[$e]</a>" + unless $FancyIndexing; + } + + $actual[$i] = lc($actual[$i]); + $cmp_string = lc($cmp_string); + + if ($actual[$i] =~ /$cmp_string/i) { + $e++; + next; + } else { + print "expect:\n->$cmp_string<-\n"; + print "actual:\n->$actual[$i]<-\n"; + $fail = 1; + last; + } + } + + if ($fail) { + print "failed on file list (C=$c\&O=$o"; + print " FancyIndexing" if $FancyIndexing; + print ")\n"; + exit; + return 0; + } + + ## the only thing left in @actual should be the foot + my @foot = split /\n/, $html_foot; + $e = 0; + for ($i=$i;$foot[$e];$i++) { + $actual[$i] = lc($actual[$i]); + $foot[$e] = lc($foot[$e]); + if ($actual[$i] ne $foot[$e]) { + $fail = 1; + print "expect:\n->$foot[$e]<-\nactual:\n->$actual[$i]<-\n"; + last; + } + $e++; + } + + if ($fail) { + print "failed on html footer (C=$c\&O=$o"; + print " FancyIndexing" if $FancyIndexing; + print ")\n"; + return 0; + } + + ## and at this point there should be no more @actual + if ($i != @actual) { + print "thats not all! there is more than we expected!\n"; + print "i = $i\n"; + print "$actual[$i]\n"; + print "$actual[$i+1]\n"; + return 0; + } + + return 1; +} + + +## clean up ## +test_content('destroy'); +rmdir $dir or print "warning: cant rmdir $dir: $!\n"; +rmdir "$htdocs/$ai_dir"; + +sub write_htaccess { + open (HT, ">$htaccess") or die "cant open $htaccess: $!"; + print HT shift; + close(HT); + + ## add/update .htaccess to the file hash ## + ($file{'.htaccess'}{date}, $file{'.htaccess'}{size}) = + (stat($htaccess))[9,7]; +} + +## manage test content ## +sub test_content { + my $what = shift || 'create'; + return undef if ($what ne 'create' and $what ne 'destroy'); + + foreach (sort keys %file) { + my $file = "$dir/$_"; + $file = "$dir/$file_prefix.$_" unless ($_ eq 'README' + or $_ eq '.htaccess'); + + if ($what eq 'destroy') { + unlink $file or print "warning: cant unlink $file: $!\n"; + next; + } + + open (FILE, ">$file") or die "cant open $file: $!"; + if ($_ eq 'README') { + ## README file will contain actual text ## + print FILE $readme; + } else { + ## everything else is just x's ## + print FILE "x"x$file{$_}{size}; + } + close(FILE); + + if ($file{$_}{date} == 0) { + $file{$_}{date} = (stat($file))[9]; + } else { + utime($file{$_}{date}, $file{$_}{date}, $file) + or die "cant utime $file: $!"; + } + + } + +} + diff --git a/debian/perl-framework/t/modules/autoindex2.t b/debian/perl-framework/t/modules/autoindex2.t new file mode 100644 index 0000000..b4b72f7 --- /dev/null +++ b/debian/perl-framework/t/modules/autoindex2.t @@ -0,0 +1,70 @@ +use strict; +use warnings FATAL => 'all'; + +use File::Spec::Functions qw(catfile catdir); + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +## +## mod_autoindex test part II +## +## this tests how mod_autoindex handles sub-dirs: +## normal, with protected access, with broken .htaccess, etc... + +#my $cfg = Apache::Test::config(); +my $vars = Apache::Test::config()->{vars}; +my $documentroot = $vars->{documentroot}; +my $base_dir = catdir $documentroot, "modules", "autoindex2"; +my $base_uri = "/modules/autoindex2"; +my $have_apache_2 = have_apache 2; + +# which sub-dir listings should be seen in mod_autoindex's output +# 1 == should appear +# 0 == should not appear +my %dirs = ( + dir_normal => 1, # obvious + dir_protected => $have_apache_2?0:1, # + dir_broken => $have_apache_2?0:1, # +); + +plan tests => 3, ['autoindex']; + +setup(); + +my $res = GET_BODY "$base_uri/"; + +# simply test whether we get the sub-dir listed or not +for my $dir (sort keys %dirs) { + my $found = $res =~ /$dir/ ? 1 : 0; + ok t_cmp($found, + $dirs{$dir}, + "$dir should @{[$dirs{$dir}?'':'not ']}be listed"); +} + +sub setup { + t_mkdir $base_dir; + + ### normal dir + t_mkdir catdir $base_dir, "dir_normal"; + + ### passwd protected dir + my $prot_dir = catdir $base_dir, "dir_protected"; + # htpasswd file + t_write_file catfile($prot_dir, "htpasswd"), "nobody:HIoD8SxAgkCdQ"; + # .htaccess file + my $content = <<CONTENT; +AuthType Basic +AuthName "Restricted Directory" +AuthUserFile $prot_dir/htpasswd +Require valid user +CONTENT + t_write_file catfile($prot_dir, ".htaccess"), $content; + + ### dir with a broken .htaccess + my $broken_dir = catdir $base_dir, "dir_broken"; + t_write_file catfile($broken_dir, ".htaccess"), + "This_is_a_broken_on_purpose_.htaccess_file"; + +} diff --git a/debian/perl-framework/t/modules/brotli.t b/debian/perl-framework/t/modules/brotli.t new file mode 100644 index 0000000..0f9dc13 --- /dev/null +++ b/debian/perl-framework/t/modules/brotli.t @@ -0,0 +1,115 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my @qvalue = ( + [ '' , 1], + [ ' ' , 1], + [ ';' , 1], + [';q=' , 1], + [';q=0' , 0], + [';q=0.' , 0], + [';q=0.0' , 0], + [';q=0.00' , 0], + [';q=0.000' , 0], + [';q=0.0000' , 1], # invalid qvalue format +); + +plan tests => (6 * scalar @qvalue) + 4, need_module 'brotli', need_module 'alias'; + +my $r; + +foreach my $q (@qvalue) { + # GET request against the location with Brotli. + print "qvalue: " . $q->[0] . "\n"; + $r = GET("/only_brotli/index.html", "Accept-Encoding" => "br" . $q->[0]); + ok t_cmp($r->code, 200); + if ($q->[1] == 1) { + ok t_cmp($r->header("Content-Encoding"), "br", "response Content-Encoding is OK"); + } + else { + ok t_cmp($r->header("Content-Encoding"), undef, "response without Content-Encoding is OK"); + } + + if (!defined($r->header("Content-Length"))) { + t_debug "Content-Length was expected"; + ok 0; + } + if (!defined($r->header("ETag"))) { + t_debug "ETag field was expected"; + ok 0; + } + + # GET request for a zero-length file. + print "qvalue: " . $q->[0] . "\n"; + $r = GET("/only_brotli/zero.txt", "Accept-Encoding" => "br" . $q->[0]); + ok t_cmp($r->code, 200); + if ($q->[1] == 1) { + ok t_cmp($r->header("Content-Encoding"), "br", "response Content-Encoding is OK"); + } + else { + ok t_cmp($r->header("Content-Encoding"), undef, "response without Content-Encoding is OK"); + } + + if (!defined($r->header("Content-Length"))) { + t_debug "Content-Length was expected"; + ok 0; + } + if (!defined($r->header("ETag"))) { + t_debug "ETag field was expected"; + ok 0; + } + + # HEAD request against the location with Brotli. + print "qvalue: " . $q->[0] . "\n"; + $r = HEAD("/only_brotli/index.html", "Accept-Encoding" => "br" . $q->[0]); + ok t_cmp($r->code, 200); + if ($q->[1] == 1) { + ok t_cmp($r->header("Content-Encoding"), "br", "response Content-Encoding is OK"); + } + else { + ok t_cmp($r->header("Content-Encoding"), undef, "response without Content-Encoding is OK"); + } + + if (!defined($r->header("Content-Length"))) { + t_debug "Content-Length was expected"; + ok 0; + } + if (!defined($r->header("ETag"))) { + t_debug "ETag field was expected"; + ok 0; + } +} + + +if (have_module('deflate')) { + # GET request against the location with fallback to deflate (test that + # Brotli is chosen due to the order in SetOutputFilter). + $r = GET("/brotli_and_deflate/apache_pb.gif", "Accept-Encoding" => "gzip,br"); + ok t_cmp($r->code, 200); + ok t_cmp($r->header("Content-Encoding"), "br", "response Content-Encoding is OK"); + if (!defined($r->header("Content-Length"))) { + t_debug "Content-Length was expected"; + ok 0; + } + if (!defined($r->header("ETag"))) { + t_debug "ETag field was expected"; + ok 0; + } + $r = GET("/brotli_and_deflate/apache_pb.gif", "Accept-Encoding" => "gzip"); + ok t_cmp($r->code, 200); + ok t_cmp($r->header("Content-Encoding"), "gzip", "response Content-Encoding is OK"); + if (!defined($r->header("Content-Length"))) { + t_debug "Content-Length was expected"; + ok 0; + } + if (!defined($r->header("ETag"))) { + t_debug "ETag field was expected"; + ok 0; + } +} else { + skip "skipping tests without mod_deflate" foreach (1..4); +} diff --git a/debian/perl-framework/t/modules/buffer.t b/debian/perl-framework/t/modules/buffer.t new file mode 100644 index 0000000..e508f37 --- /dev/null +++ b/debian/perl-framework/t/modules/buffer.t @@ -0,0 +1,38 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my @testcases = ( + ['/apache/buffer_in/', 'foo'], + ['/apache/buffer_out/', 'foo'], + ['/apache/buffer_in_out/', 'foo'], +); + +plan tests => scalar @testcases * 4, need 'mod_reflector', 'mod_buffer'; + +foreach my $t (@testcases) { + ## Small query ## + my $r = POST($t->[0], content => $t->[1]); + + # Checking for return code + ok t_cmp($r->code, 200, "Checking return code is '200'"); + # Checking for content + ok t_is_equal($r->content, $t->[1]); + + ## Big query ## + # 'foo' is 3 bytes, so 'foo' x 1000000 is ~3M, which is way over the default 'BufferSize' + ### FIXME - testing with to x 10000 is confusing LWP's full-duplex + ### handling: https://github.com/libwww-perl/libwww-perl/issues/299 + ### throttled down to a size which seems to work reliably for now + my $bigsize = 100000; + + $r = POST($t->[0], content => $t->[1] x $bigsize); + + # Checking for return code + ok t_cmp($r->code, 200, "Checking return code is '200'"); + # Checking for content + ok t_is_equal($r->content, $t->[1] x $bigsize); +} diff --git a/debian/perl-framework/t/modules/cache.t b/debian/perl-framework/t/modules/cache.t new file mode 100644 index 0000000..f235de1 --- /dev/null +++ b/debian/perl-framework/t/modules/cache.t @@ -0,0 +1,22 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +plan tests => 3, need 'cache', need_cache_disk, need_min_apache_version('2.1.9'); + +Apache::TestRequest::module('mod_cache'); + +t_mkdir(Apache::Test::vars('serverroot') . '/conf/cacheroot/'); + +my $r = GET("/cache/"); +ok t_cmp($r->code, 200, "non-cached call to index.html"); + +$r = GET("/cache/index.html"); +ok t_cmp($r->code, 200, "call to cache index.html"); + +$r = GET("/cache/"); +ok t_cmp($r->code, 200, "cached call to index.html"); diff --git a/debian/perl-framework/t/modules/cgi.t b/debian/perl-framework/t/modules/cgi.t new file mode 100644 index 0000000..9b6edc2 --- /dev/null +++ b/debian/perl-framework/t/modules/cgi.t @@ -0,0 +1,279 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use File::stat; + +my $have_apache_2 = have_apache 2; +my $have_apache_2050 = have_min_apache_version "2.0.50"; + +my $script_log_length = 40960; + +## mod_cgi test +## +## extra.conf.in: +## <IfModule mod_cgi.c> +## AddHandler cgi-script .sh +## AddHandler cgi-script .pl +## ScriptLog logs/mod_cgi.log +## ScriptLogLength 40960 +## ScriptLogBuffer 256 +## <Directory @SERVERROOT@/htdocs/modules/cgi> +## Options +ExecCGI +## [some AcceptPathInfo stuff] +## </Directory> +## </IfModule> +## + +my @post_content = (10, 99, 250, 255, 256, 257, 258, 1024); + +my %test = ( + 'perl.pl' => { + 'rc' => 200, + 'expect' => 'perl cgi' + }, + 'bogus-perl.pl' => { + 'rc' => 500, + 'expect' => 'none' + }, + 'nph-test.pl' => { + 'rc' => 200, + 'expect' => 'ok' + }, + 'sh.sh' => { + 'rc' => 200, + 'expect' => 'sh cgi' + }, + 'bogus-sh.sh' => { + 'rc' => 500, + 'expect' => 'none' + }, + 'acceptpathinfoon.sh' => { + 'rc' => 200, + 'expect' => '' + }, + 'acceptpathinfoon.sh/foo' => { + 'rc' => 200, + 'expect' => '/foo' + }, + 'acceptpathinfooff.sh' => { + 'rc' => 200, + 'expect' => '' + }, + 'acceptpathinfooff.sh/foo' => { + 'rc' => 404, + 'expect' => 'none' + }, + 'acceptpathinfodefault.sh' => { + 'rc' => 200, + 'expect' => '' + }, + 'acceptpathinfodefault.sh/foo' => { + 'rc' => 200, + 'expect' => '/foo' + }, + 'stderr1.pl' => { + 'rc' => 200, + 'expect' => 'this is stdout' + }, + 'stderr2.pl' => { + 'rc' => 200, + 'expect' => 'this is also stdout' + }, + 'stderr3.pl' => { + 'rc' => 200, + 'expect' => 'this is more stdout' + }, + 'nph-stderr.pl' => { + 'rc' => 200, + 'expect' => 'this is nph-stdout' + }, +); + +#XXX: find something that'll on other platforms (/bin/sh aint it) +if (Apache::TestConfig::WINFU()) { + delete @test{qw(sh.sh bogus-sh.sh)}; +} +if (Apache::TestConfig::WINFU() || !$have_apache_2) { + delete @test{qw(acceptpathinfoon.sh acceptpathinfoon.sh/foo)}; + delete @test{qw(acceptpathinfooff.sh acceptpathinfooff.sh/foo)}; + delete @test{qw(acceptpathinfodefault.sh acceptpathinfodefault.sh/foo)}; +} + +# CGI stderr handling works in 2.0.50 and later only on Unixes. +if (!$have_apache_2050 || Apache::TestConfig::WINFU()) { + delete @test{qw(stderr1.pl stderr2.pl stderr3.pl nph-stderr.pl)}; +} + +my $tests = ((keys %test) * 2) + (@post_content * 3) + 4; +plan tests => $tests, \&need_cgi; + +my ($expected, $actual); +my $path = "/modules/cgi"; +my $vars = Apache::Test::vars(); +my $t_logs = $vars->{t_logs}; +my $cgi_log = "$t_logs/mod_cgi.log"; +my ($bogus,$log_size,$stat) = (0,0,0); + +unlink $cgi_log if -e $cgi_log; + +foreach (sort keys %test) { + $expected = $test{$_}{rc}; + $actual = GET_RC "$path/$_"; + ok t_cmp($actual, + $expected, + "return code for $_" + ); + + if ($test{$_}{expect} ne 'none') { + $expected = $test{$_}{expect}; + $actual = GET_BODY "$path/$_"; + chomp $actual if $actual =~ /\n$/; + + ok t_cmp($actual, + $expected, + "body for $_" + ); + } + elsif ($_ !~ /^bogus/) { + print "# no body test for this one\n"; + ok 1; + } + + ## verify bogus cgi's get handled correctly + ## logging to the cgi log + if ($_ =~ /^bogus/) { + $bogus++; + if ($bogus == 1) { + + ## make sure cgi log got created, get size. + if (-e $cgi_log) { + print "# cgi log created ok.\n"; + ok 1; + $stat = stat($cgi_log); + $log_size = $$stat[7]; + } else { + print "# error: cgi log not created!\n"; + ok 0; + } + } else { + + ## make sure log got bigger. + if (-e $cgi_log) { + $stat = stat($cgi_log); + print "# checking that log size ($$stat[7]) is bigger than it used to be ($log_size)\n"; + ok ($$stat[7] > $log_size); + $log_size = $$stat[7]; + } else { + print "# error: cgi log does not exist!\n"; + ok 0; + } + } + } +} + +## post lots of content to a bad cgi, so we can verify +## ScriptLogBuffer is working. +my $content = 0; +foreach my $length (@post_content) { + $content++; + $expected = '500'; + $actual = POST_RC "$path/bogus-perl.pl", content => "$content"x$length; + + print "# posted content (length $length) to bogus-perl.pl\n"; + ## should get rc 500 + ok t_cmp($actual, $expected, "POST to $path/bogus-perl.pl [content: $content x $length]"); + + if (-e $cgi_log) { + ## cgi log should be bigger. + ## as long as it's under ScriptLogLength + $stat = stat($cgi_log); + if ($log_size < $script_log_length) { + print "# checking that log size ($$stat[7]) is greater than $log_size\n"; + ok ($$stat[7] > $log_size); + } else { + ## should not fall in here at this point, + ## but just in case... + print "# verifying log did not increase in size...\n"; + ok t_cmp($$stat[7], $log_size, "log size should not have increased"); + } + $log_size = $$stat[7]; + + ## there should be less than ScriptLogBuffer (256) + ## characters logged from the post content + open (LOG, $cgi_log) or die "died opening cgi log: $!"; + my $multiplier = 256; + my $log; + { + local $/; + $log = <LOG>; + } + close (LOG); + $multiplier = $length unless $length > $multiplier; + print "# verifying that logged content is $multiplier characters\n"; + if ($log =~ /^(?:$content){$multiplier}\n?$/m) { + ok 1; + } + else { + $log =~ s{^}{# }m; + print "# no log line found with $multiplier '$content' characters\n"; + print "# log is:\n'$log'\n"; + ok 0; + } + } else { + ## log does not exist ## + print "# cgi log does not exist, test fails.\n"; + ok 0; + } +} + +## make sure cgi log does not +## keep logging after it is bigger +## than ScriptLogLength +for (my $i=1 ; $i<=40 ; $i++) { + + ## get out if log does not exist ## + last unless -e $cgi_log; + + ## request the 1k bad cgi + ## (1k of data logged per request) + GET_RC "$path/bogus1k.pl"; + + ## when log goes over max size stop making requests + $stat = stat($cgi_log); + $log_size = $$stat[7]; + last if ($log_size > $script_log_length); + +} +## make sure its over (or equal) our ScriptLogLength +print "# verifying log is greater than $script_log_length bytes.\n"; +ok ($log_size >= $script_log_length); + +## make sure it does not grow now. +GET_RC "$path/bogus1k.pl"; +print "# verifying log did not grow after making bogus request.\n"; +if (-e $cgi_log) { + $stat = stat($cgi_log); + ok ($log_size eq $$stat[7]); +} else { + print "# log does not exist!\n"; + ok 0; +} + +GET_RC "$path/bogus-perl.pl"; +print "# verifying log did not grow after making another bogus request.\n"; +if (-e $cgi_log) { + $stat = stat($cgi_log); + ok ($log_size eq $$stat[7]); +} else { + print "# log does not exist!\n"; + ok 0; +} + +print "# checking that HEAD $path/perl.pl returns 200.\n"; +ok HEAD_RC("$path/perl.pl") == 200; + +## clean up +unlink $cgi_log; diff --git a/debian/perl-framework/t/modules/data.t b/debian/perl-framework/t/modules/data.t new file mode 100644 index 0000000..ef62967 --- /dev/null +++ b/debian/perl-framework/t/modules/data.t @@ -0,0 +1,22 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my @testcases = ( + ['/modules/data/SupportApache-small.png', "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAACXBIWXMAABcSAAAXEgFnn9JSAAA6G2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMTEgNzkuMTU4MzI1LCAyMDE1LzA5LzEwLTAxOjEwOjIwICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTctMDMtMjRUMjA6NDU6MTItMDQ6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxNy0wMi0yMlQxMTo0NjoxODwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMTctMDMtMjRUMjA6NDU6MTItMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6YjY5OTdlMWYtNjFlOC0yZDQ0LWIwNzAtMmM3Mzc5MzcxNjJlPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NDgxMWVmNzEtMTBmNC0xMWU3LTlmZDMtODYwODE2ZGE5NDUxPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6NmJmZGM3ZDktZDhiZC1iNzQzLWE1ZmYtOTExNTY3YjA0NTYyPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDo2YmZkYzdkOS1kOGJkLWI3NDMtYTVmZi05MTE1NjdiMDQ1NjI8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTctMDMtMjRUMjA6NDU6MTItMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YjY5OTdlMWYtNjFlOC0yZDQ0LWIwNzAtMmM3Mzc5MzcxNjJlPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE3LTAzLTI0VDIwOjQ1OjEyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpTZXE+CiAgICAgICAgIDwveG1wTU06SGlzdG9yeT4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+MTUwMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTUwMDAwMC8xMDAwMDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT42NTUzNTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MzAwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjMwMDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+53R3BQAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAE+s0lEQVR42uyddbwkZ5X+v+d9q6rtuo37ZOKKJbhrcNmFJYv7YovsLziLO8F2cQ2BRRLc3QIECHGbyLhev21V9Z7fH21V1X1nJiSBAabyuZm+t7urq0ueOuc5z3mO/B+P5fBchBrCmiVbGB+sUY58RB2KogJ5CdhZnURVWVaYwF/YTzXvEYlPbA2CIGFEPaoypsKsp5hcCZxSqdZZkCrrWEYU1ykXaxi1qBMEiGwMzc+SOMBXBd+gAohDMQSVGrH1UDGoCCKKKhgcViwV4yiWpzHBIPMSY0LBOg8XRJRKBWouZte+fazoH0FFCGsQOsEWFOsUJ4oBkJha2eBbD68YM1urk8fH+II1FgmVWGPECiYW9tbmyJX6WVqpUIkc2z1LKa6zyrNM5ot4anAIXhQTixIZwSqAIp5CbKlpGBjDRF99dHlsqxMLlEcn43h8uZ+bUBgUGHCOQUT7ESkIlICSQg7wANs8iA6IgZpAGZgHrWgsc2JkVozOxU6n1eheD7PfqNk7G1X2eMbuCDyzxzgpx2JAY0QhEgs4jBPEKXhKlPeQGgzVK8xUZpnMD7A8KBKaEFuLcMYHzxKheBoTVS2qAv0RA+U5an4Jrx7i4ZgqDGCiECsWMVAXoVipkItD5gb6sXFMrBY/rKPExMUBqgtT5KOYoZxlOiigVYtGBr8Y4VQBA42zFueEOI4Jch4SRizU+hm20xhTYzI/QC62uMjhiRLVDcaPUGswKngas8tEqIM1UmTO1pq7WIkrlrwRdmqVgbxhQEvkKwUmzRTWEyQSYsAPoFrN4fJVCiKIARcrohbimKmSgxUDGCegeliigseR5R96UVVUHUaFzCnoC2wwyFEirAfW4OQoYGVO/GUo49Vg2qgKRfEZ8nIsuLh5+YFYGhd+8/eDbkfiRiQ+oE2AN4KoQVEiHP1+obHNTicVdhnYBnIdwk0Wrle4BuE6oKxAcxMaF5gqeuSQ/0MvRwDrH2yJ1eEAow6cQzyLMd5YZP2jjcrJwGmiekxsZQPIUtMEkcY1LyloURUEIVIl0pjUs7cAGdSlkawRfzS2Iu48OQKMiOpxIPcHwbReZZgEuV5FrvFDvciJXC7GXOlEtmpy5XoEvo4A1pHlcI6nKNjg5EB1U9UPlqvlqHE/OMOD42ZF8kYdrok02oapJPpoJimXxbL1zkvlZm9i9zoOsBrt8QhlBMOIKLc3oT7BGCh7gbN9I1eNwoUx8VWish1jrwP5/RHoOgJYR5a/GSQ1opHEdX+0CHcXlbuo4fShfOloVUfVy2EEAlWcQiryWAwg5Gb+fut9qZv3ezsya6aEqoRgrOcf5yvHReowanC+AWWrqP7OE34lyA+BS1VbKz0CY0cA68hy61/P2gkRDMYY4f6eyn2J3T2dyO2kQc+jiZTKqANtMN49sUYyf5RDALBbG7wOFqVpj8fa+3kDqHPE7eBNW8+vUlglyqMVwctxpTr9ZezsTwTzLYVZUYfg0NsMkY8sRwDrHz6SSlyMlmFjuR9O7l+ywf0EVrcjjEO5zJLg9BeAlNxWEZakv6weauq5GJDpgfenAmL0WDEc6zl5RiXon3Rifqg2/n6M/sBovEW70uUjyxHAOrIsHkk1H1u4syAPQr1jcznuh7gB57qonwNHTocAUtILoA4GajcbvATxLPVtO3DVCra/H5PLI4GPWAs2Qf1r9z7pSgUPBl5ZAFNS62+xeA4zIsrjakH+cSDO1/h7iFwl8DOFrzfqAYocYcCOANaRpXsxhvWe6MNF5KnACSAdirxXFCIH+TfzWHo97vVaaepw1KFxjEYRrlrFVatoGCGehx3ow/b3d157wMURL5QZuNedsQMDVK/bTLh3P/HMDG6hjKtUUAXxfEwuwBSKmFyuA2hiUkAkSSCTBLAlIzY5QATWBjBNgKIaQR4kwoNAXywqN1qR84yR/1NnLj7Cdx0BrH/aRaRx+keuIRVwKo82gXtyAGeKA9cu8+uBUzxhUWJcko+T4CQCzqFR2AAj5yCOcPU6rlJFwzrEMRjB5POYQh7b30duzUr8iXHsYD9ubp7q9TdQveZaxPcJli9HcrmMXiGBMVFMvH8vK197NoUTTkDDOuGevYR79xHt2Utt6zbCnbsId+8h3LOX+s6dRPuniKdncAsLje1CEOthcjlMoYD4PmIMYixYi4hBTEP4QC8MlUWASxfLNnWt79zZUZA7O/Jzv/Ni/agin1coN9D1yHl8BLD+SYgp5xRfzMbBXN/jY5EnxOqOaYUNPXnobJrXIzpqAVRXOpd5rViDq9Zw1TKmkMcEeUyhgDc4gL98Gd7oCP7EOP6SCYLly/HGx/BGhvGXLMEUCu1Vh3v2MPfzXzL5la8y+7OfgwjBihVoFHfldeoi7OgwUiw2tsEPCFasIFixovduqtcI9+5rgNru3dS37WiA2a7d1HfuJty1m3huHm2CrJuvorUarlKFWDHFIsHqVY396TqhqWZ5MT0wB6YorqH7uqNRuSPom6zwJTHms6rut43o7Ei18Qhg/cPhVDNiEkY8a59Yq0ZPEJE7DRb7CJtRVk9iSg4AUD2jp8VSQ20DWn3LjYz8y6NZ8pxnYvv7GlFUqYQd6L9ZxJQ/McHIYx7FyGMexcKf/sSWl76M+Qt/S/HEE9EoSscq5Xly69eSX7/+0CLQIHdAQHO1Km5unrhcxs0vEC8sEE9NU9++g9pN26hv3crcb37fiJ6s7aSA2kkd9UBpoybTT01m5BOi+rzY854H9lLf6TdrRj4hsVwni0SXR5YjgPX3k/qpA1Ws2GXG47/EyJMVGXRNfsiJ9o6kFgGeFEgdMOLS3sS50CC9B/spnnTirfY9S6eeyqbzv8I1j3gEC3/6E4XjjkPDsMPN9RfReo3tb3gD/tIl+MuWESxbhjc6hj8xgWlGXofM9eXymFx+0ZM32r+fS0+7M5LLI75NA1AWvLKpoPQAr8zrGlhnTozj8MS8XzrbM/F5dapvAb30SMB1BLD+bpfYC46zIs/xjfc0xRViFNEW8MghRVMiCT5qUZDSFGfV83XN3+1gidqNNxzS9rtKmWjffqLJ/YR79lDbtg2tlAmWr2D4EY9MvdYODHL0t77F5Xc9g9qN15Nbt64ZaYEdKOFqZXae826IYySXwxQbUZ03PIK/ZAn+kiV4Y+MES5fiL1/O4L3ufbOBrLVUrrgStzCPP9CHSCLSa6G4psGr9Viz3Fdr37n0v60OSkejGyDyvMdH9D3eOvdVDB90wo+PnP1HAOvvgqJqihZPDVReFAeD/x43o6lFeakeoNIVSR0sijpAtCWSjiFMIUc8uR91DjGd6tvMD39A+ZI/E01PEU9NE+7dQ7RvL9HUNG5ujri8gFYraByDc/SdfgbLz34lA/e8dyfyKRTZ+NlzueoB98HNz3YAR2PEtxQ2bWxe+Nog/MOQcNcOajdsRqtVXBQRT88QrFjBiX+6JAVYO9/9Lmo33kDptNvhN6MzWyhi+kqYYhHb19fm2KK5WeKFWQJvRSp8SsoTtIXwGdDqShsBTGKfJqIuSZBjjT+ZRxlPH2Ws+66qvEPQnytER66MI4B1WAKVhScg5j8d3K5xsmd4DUlcAIcCUikQOjhASZYM6wGGppQnmtpHtGc3/tJl7ad2/+8H2fuVr5EfH0I8D5PPI/k8JhdgB/vxRofAmGbIJ1Suupyrzrw/a951Dkue9bz2egrHncCK17yOra/+f+SP2tQ7PTKNip/kPEypAAy1nwp37aZ02ml4IyOpt+w/73PMXXQx/uhgIyXM55FmaihBgC2V8MbGya1bS33LNnKrliNNkxvtQayLNp9ogpCKZJ5bhPPS3iljW10PYPSBojxQMNtE+KBzvFWPVBWPANbhAFRNx6O7GeEtCndx9FBKy+IpXVfKZxqnviuXiWemcZUKuKj5nGnIDIaGsP2lZoSUvAoPHmmZnEc8P0u4f18KsEonn8LcL35K/qiN3QCbfNi8mnPr1uKNDLH17JfQd9rtKN3h9PZrhx7yUHa//10Q1RE/OKQd2cIBF1bwly1Nc1KT+5CcT+nEY7B9fQ1ZRtzQiBGHuHKNeHqS6ubrmP3Jj7ADg/jLl6HVembbpSd4NcCp0xTe1p20oiiXlpul+K5eXFc74mKlqLzFCE8SeE0IX3JHRKhHAOtvAVSoInCasfIqVR7ptANUcqC0TzIShOaPeJZodpZw505Aya9bTfHudya/cT3+0iUgEE1NUb9pC5XLL6N20024agV/bAx/yZJmc7Om1t8TeDwPVy0TT06mvpO/ZBzEpTSa3XlscifEeMNDhPsK7Hj3WznqvAsS61pGsHYNtRtvwBsePfgOlcTHuAh/fCwNWPv2Ec9NN1I+0wBusQbBW1zpHtYbNoIpIGlGqe07ShPAekVeJKIum+C8Eq89KHDRrhAfY8T8ny/8xljzZoVvyhFy/ghg/bVSP6Ocpn7wZDHyfJeypjsIUPVK+zyL1utUr7iK3Lo1TDz5CfTf7S703+0uBKtW9dyOaO8e5v9wEQu//wNzF/6auV/+nGDZMvzxcTSODpgSijVoWCXcuzsDWEsxOY9EVSADWtr1NyUit3ol1Wsuo75tC8HK1Y2nrKVwwomUL/8T3tjIAVC/B3IREyxfnvpruGcXWp7HDPTTdhBMRma9WnIks9mZqiDavukcALw6oVIqZXTpz8qq7nHdwBWrYoQzPM/7hiqfFSOfU5HvHwGtI4B124GVsNzG8gZEnxpbg6oiegAiPZn29XhOPEs8PU24ZzcTT/93lr3sP8mtW3fwgzY+wdADH8zQAx+MRiF7P/FRdv/PB6lcdTn5Y45p9670rBw2o6Nw1440YC1bhukrAjEYmyHsWTRN1MDiFuqEe3a2AQsgv3oN4kKMOQSsaoOKw+Q9/OVp/VV953bi2gK+lXREY9KVvp4gtgiAaZdo9ADglU0ZraQirhbXlSLpNQ1e7SCs8aKzsPasfDH6oVeLz3aqFx3BrUNbzJFdcBCgapa7rdOzjHKdKk91yRO8dZKbxI90IiojzVa4xI+IYjxLPLmf+pYbWfv+d7H2Q+8/JLDqwg/PZ+KZz+WY7/yA4Yc/nNq1VyJNj/f2tpgkR6aIJ4STe9MgODqKN9AHLsKYJr/e66f5vTrfSUBjNArT2+X7iJH0fml9/+R6pLNuiLF9RfyJJZmUcDciDrEgRqH10yrrJb9jdnttM6XL7IvkazCZ14giounnmj/SPH4i2vlOzb+3axKp/U13kQVwCtbU71sL8r+v+6WzPRcfCbaORFi3JKJq/OcLJ4k1bwd9gDrt3btnujmq1EmbqfSJ7xHu3k24by8bzvsMo499TNfnu1qN+d/8msoVl1HfsR0xFm98nMLxx9N/17tjglw6Qlq6nPUf+yw3vvBZ7P34RyiefBKSz4OLu1JSk/Nw0/vTJ8LwCHZwgHhmFgq53kFVz4irAb4mQ65rrdqJLDlwpNaOeqIQb2AQf2w89dJ43x6Mb9OC2SRPlGXDVVIVPUlwTL28+yRx+049n7wpIagsFnVJO6qSRCUmVVnsUVXEKXVjEbw3FzR+YiT68lj51hHgOgJYN2txKB5m1GDeaq083aFtY7hUbNqDo+qZ+rXO0CaHFE3tx4VVjv7m+Qze5z7p9GfrFna9/xwW/nARlauuxFUqiO81OKMoxBYLFE88icJxJzD00IczcPd7p96/9pwPk1u3ll3veQvB6rXYfK6TqzS31RYLRPvSHJYdGMIbGCCe2t9NvB8IsMIIk8/hjaejotpN1yKF3KHF8G3AquNPLMUbW5LhsHZgCnnEZCqwvWxjtPOLtsAr8SbJpIppgGo+L+0MNZ0ytiUO0hu4MjxXO1VcjJhXwTRzzxg5zuC+KZavi/CaqMafjxQUjwDWoVw3BNgnW+TdigzHTRW59AKqxTgq074np19rGvrocM9ONnzy411gNfW1C9jykhdS37mTYNVKcmtWg2doK9mNARdT23ID8xf9hj2f+l9Wvuq/WfrC/0qtZ9mLzkYEdr79tRROODl9YQuYQp54ah+uUsYUiu3U0pRK4MI02bOYoV9TZhFu207h1DsRrFzT2Y/1OtVrL8MbGmikcAcCKZIAUcf09zW2I5GTh/t2YYqNCmGKSBdSTc1Zkr1DViU5qzQJtzhAdVxtuvis5nFtTQ1CG1yVaOLvrcriwYDLpdNEsTxMkId5OX0jRl4dHwGtIxzWYlyViCyxwjcs5pMOhlNuntINVotyVHRzHA1uRajfeD3DD3kQI49Jz4Oc/PIXue7xj0WNULrD7fDGRxFPmuuiIwgygjc2QvHEEykctZEdb34VW1/xgq7vs/SFZzP88MdQv2kzYm1nGwUk5xPPTxNleCyTD0AjxGg3b5XhfMQa3Pws0ew0E896WToi2rmVcOeWBvCYRYoRpgffpBG2UEiBSjS9Hzc7hcn7Hd7K9OCsshFuj+1ucFpJDizDOZruY9nFdyW4rgaXtcjf0a7v2eK4UvvBku7S0mZFMdBXeVYuEzj+yPCyI4DVtVi4txUuwXBmnI3Fe53ErYvDpsl0SZLANnHhNP8eV+YZO+us1OrLf76Y65/1VHLr1xIsXwou6ubAmherWG0quGNsX4nCCSey56PvZ9urXtj1nVa8+u0NGUOt3CaTMYoEHq48Szw9mQGsPGicBpXm+9TFaL2GK88RTe+jet0V1HfexIpXv4P+u2Qixa+fSzw/gwRB50o8hB91MZLPZQj3PcRzUw1DP+mQ3q3vsijpniG/u8Cri8A/OEC1j2WChG8R/+1zodf5sEjBIVUIMGngUgexcrxn5M+e4ZkdXvVISvjPm/4pGJFjRcx7Ebl/a3CBHIhQz55s0iP1k4RJZvuCEurbtzJ85oMYesiZqW3Z/sbXYnI+/sRYQ0clHaDoqd+iE3EZ31I69TT2fuID2IEBlr38DR0iftlKxp70LPZ+7N3kjzmxTcCLZ4nDKvHMVBqwikXCvXsQ36L1WqPyp67ZqlPE9PXjDQ1igjz5Bz6SoYc9geJJd0ytY+4n32Lfx95JfvXa7nRQD3wsiEO8waE0YE3uxVXmMf39qdtrSk6S7BFsyxikx+sy/zZTwpYQuK2par1fE2mmpFPDZHeCJjkuAJuWRYhLc1wirRSySbG5TKroUsUfi/DhwNrnWaJX1FS/9c9sHvhPCVhOtVGhKYSPVo2/ZNSI4wDCz2zlz5Du78tyVwmwSQKYm5th9LH/ktqW2Z/+iNmf/5DCsceh7RYcTV0QsmhDc3MbPEP+6KPZ+7F3M/KvTyG3uuM7NfHM/2T6G5/Hlec7BnzNaMZVFlLbEqxcTX71CoqnnI43OoE3tgRvfCl2cBh/Yjl2ZBxveAzbPwDW79qvC3/4JVtf/u94w6OYQqEBvocwv7Ct/Hch/thEGswq8wgNqUWqITmr5M9WBBt27M3npGftQLM8VlLFrtoEDknzXS1QTPRgiWZ5Lm3PdNTWOeAyVcUEx9VVUSS9LU6hLpw0rME3Y+NeWFd93z/roIx/OsByKJ6xDJvSGyMTvzJujnG/2VGVaNfr283HGR4FGj5UubWr6b/7PdPc1Ve+iHhes4fQNcn1bvDsspghEYWh2FKJ0MD+z/0vy1/x9k6qOzTKwP0exv4vfJzCUcc0rgwjGHFovZralvGnvJixJ78QU+i72ft15lvnsfO//wNbLOJPTKBRlI4Iu5CiR+CjDpupELpqtRHlmQxoJC5qXQTAOm042hlekZA8JNt0VDM3g/bfkwMQE1/DpMWnbfOHdpSkzb83gcs2Qc41IvI2O9oUv4pLYJRJR1uNidiKhxBYe4467go8VWH+nw20/qk4LFWwyKlFP/h5KSi80jnFJO++ppuENV1cRoKnaokJJcmrtEjpNAEcz0xSPOkk/CWdxt54eoqFi39HsHJZQxiZ4DiypHRW/InRBjneTj8dwaq1TH3tXOpb035Xg/d7GDbnITQ/wwq4qCvCknzxZoOVK8+x/ZVPYdvL/w0zNIy/bBnqwg431Evomfl743soJufhT6Qbn11lDuKouSN6EOo2ZSLR7cCa5Z9Mi1dahLdalPNKvEd6HJvMero4LkiJUHuKT3tVm03nhuVQIlWcxI8NMFf5ah/UmeV9BLD+sZAKxag808P7I4a71VvWxCSqNaZzYhvJnHimwbFotUy8MIurVRoRkWkBSPOks5nUsXlRucoChRNOSKdQf7qIaPc2vP6+dAWuF1C1LuxEBS8LsLZUwk3vZepLH099TulO96Rw7InE8zOpSNDNTB4iKjnimUkql/6W+rbr0yDn5wjWbETyAd7QULtgcLCfbCFDNcL0lfCXrkx/9PQ+xLhGsWERQr19g2hV42xCqd8LANrboWniPbv/M0AmtglcVruOb5uIl0WAy/RQz2eJ+ZZiXrrPoeSV6gDUrDDIt32xbzMq/zSg5f3jY5WCMeRN8KEIeU6cNdPrIVVon+RWGkLP/fuI9u1t+EKNj2KLg2h5nnDXdly5jB0ZJlg60SRqXSfUTxq/Ge3R1LsDjeqdFLCHk2jK1aEHFyTJlJEYf2IJ5T9f2NJpNF5jLPmNx1LffAUyNNgEGkM0tScTzZRZuPCHxFN7Gp5Ze3cST+8jmtqLm5umvv0GUMfaz/6G3JpNzfUEjD/zldSuu4S5H32V/DGndbXpdKWEdIs/cSG2fwBvJK1yD/fvAs+m9kear5JFOX0hwzGR8XLPpIbaXn8P3irLdYkmyHLpfJ5NcFwJTq2tknd6gDSRJlo1P8v1sG52He8tB1jnvdzk5Q7i4sfFoe7TI4D1910JtMKSWr7vfFXOYDG5QvOEMO0UQZDAI9y9i3DXDoonnsDEM57EwH3uRbBqJbavn3h2htrmzcxf9Htmf/IjFi76HaZUIli9uslFadodNDANsjq5fdVyOgpI8FJygFmDPauGLc5qYIhwx03Ut11PsGpDp2K4fBUa1zocsu8RT+/tSu92vPapRJP7MaUi4vmIHyBBwyTPm1hOuPMmtr3ssaz/wsWNXsHmsvTl76Vy2YXUt11LsPqolKf7AWqD7SvaRTW80SXY0TTpHk/txuSCBvgnwKND5uuBAUzThYsDgVfH8kU7fBeJG4LLKO1NkqCXruPTBVwtIXyC31LT2diWg6mKNIDJNLgtl82HEpXKGMVYvZc19nI/Ng8S+OMRwPo7jax84981l7NfimHpgUSgXVGVEapXXUn+qPUse/HzGH/qk7ocML3hYXJr1jJw7/uw/KUvY9/nP8u+L3yO8kW/I7dhI+R8cK7NYRhrEM+mI4CMoDNVdTxUkJI0QS+BRzg1RX3L5hRgeWNLUlGfKeSJptLtOd7IBPnjT6N+/RV4IxOZuKXxKfl1m6he9Sf2vvvFTLz0vYn1L2P1ey5g6/MfRLx3G/748oTVjXStSjXzJcIq/ugEJp9WuUf7dyOFQrqzgEw1LeF03JaUS5Pwzro4QBv8eoKXJMBDMy0+CaW9ZgdaSLMq2WrbMSRaeRLAZbLRFh0ZRMvZQRU1nZYi4xJK+R6EvDoI0YkJP/cbNfFTXMTn+Qe1OP2H5LAU8IUXW/iFwyxNNbGaRcDKgAQWrVVZ+PMf6Dv99hzz/W+y7KUv7gKr7r1oGXvikznmmz9k4tnPo7r5KjSsIl6LFG7eC8NaOhoaGW203mTdATJtP6YHp5VyOUjyMBY0rhNNZ1XshQbZ3uRLTD5PPL0XtzCbQsVgYhkSVtvcSsdRocn3uIj8UScw+flzmDrvnNRn5I4+lRXv+ipufpK4PNtOb7rIdknycc311sv4E+mUOZ7cg5veg232EXa5IGSLEwkinux+Sar36eaaTDaFlzSnmSXqU9uT4uc0LWlJclm9+C3TiaiTr+vJbWV5LZs+641IYJw516p8Qo07Alh/D0ClouRi8z/Wybtd1tYxAwgdYl2QnE9921bqW25kxdn/xbE//h7+kiVdn1HfsZ3a5uvaE2Gyy8rXvYXlL3sF4bYb03IIjYkm0w4JuZVrsH0lROMupXPqIuopsei2V2mc9NI40VsuDa19E4VNoGi8zgY5dGGGeCoNbHZoFBfVD6BIb9jT5DZsYu/7XkL5ovSAmMKJd2bixe8k3H5NithOtfskK61tlXuIHU7zV9HUHtzCNCbw2+8xWRuXXiCWsa5JC3Gb4NWjUV2kQdiLoad1UAu4k4DUs7KYVNEnn7PpK05SlcCMYj7R5pPcBpOVuCRaexyKUyEy4VNqpblfiPOMc/xDCbb+YQBL1GEUPLHnGuHZLsu+Jvkqk+CrfIu6iIU/XoQ3MsRR53+RVW9+Q9f6Z77/Xa552IO5+swHcvUjz+Tqh9yX7W9+HeHunV2vXfaSV1E85TTqN12PeA1bFPEs9R03pV6X33Q8/tJlxAtzqZM4JYkgcSEk2mu6Wk4kceJb025qbgNWeRaDQ6QJaL7FVeeIZ6cyqe54s58wAzbtKLCRi9i+PryxCXa+5vGEu7ek1jH06OdRPPWu1G+4rOHp3gUYrcgjsW7iLseHeHovWp1v6NSS66CzPaZ14+mSBBxoH3Wiry45RPIYmB4yCcnIWHrQCj2j32QHhKW7xcuko610G5imAKpn9TNxs3M4ItW7BoH81PrqZe5dRwDrMCCsiK232oj/fbBPiLNmRxmdjGkd+JzXGDO1+VqWPPOpHP/LHzP0wAekVl296iquf+bT2PyUs5j//W8BhykE1LfeyK53vYWrHnIv9nz4nO5I67/fheS8hjjTgB0eonrFnxq8VmvT8gX67nxvor07MJ5p390PmPJlzemy6nuN8fpKBCvSZoDhzhvB99qAIZ6F2gLx/rTzqDe+DPFINxn3lCHEeEtWoFGVnWc/oqsyuOyN/0ew7mjCHdcggX/AdSGK5AK8ibTTaLR3OxpVECuZhmxN9TmmtFztFE56SgO6pRWJlNH2jpiM6W3K19kWzURhpBueDSkpRJexYFfKr+mexR59iV2glYjeBEFjQTx3N98zVxjkvv8oBvJ/94ClgCecVi/0/Tky3v1Ql+arLF13Wqwgvkfl6quwpQIbz/0k6z78IbzxdEqy/7xzufL+92Lyi58nWLWS/NFHYfuKDf+niXGKp50Krs6Nz38RU1/7Yuq9xRNPY+TRj6e27XrEgj88TOXKiylf9ofU60Yf82S8wYGGvKFXytcCqUz6kWz8bf3dGKA8S7B8NbmNac1X/aYrsf39najGgsY1osld6ZRwdAkml2sPpEhHWiS4LQVXJ1izkdq1f2LfB16cBr7RZax4+zcQT4indiCeSb03tR5iTKmEN5YBrH3bGsxyElxagJtKM5Mi0HRUmEwhk6lXT9dRyfBdmVQ8lS5mRaeJY4V0A1dbfJpNJ7PN1e0ITLujrfbnJACxVxN1M0p0CnEkR3kFfuAHcpbGRwDrb5cCIsTqUNUTrJE/qLqhLr5K0pIFMYBncfUq8xdfyMAZd+L4X/2YkUc/qmv929/039zwnGdiigWKp56CCby0k4E0CGh/bIziSevY+Y7XdrkfDD34URjfNso4nsVVFpj76XfSPNbG4+g7416E229ErOlyD+ju7O903opI82JsXvjWEO7fQf74OyJ+x/Ug3L6Z+k1XYAcGM5yPEE/vyXBY442eQ43SHFmyPzLJHbmY3FEnMPPVDzL77U+kQWvpWpa+/ku4hWkIq4tEV0Bcx5b6sUNjmZRwD8Y3vcn2FAB1FOzdTq/aLmqYduQlqeiMxfiupEhXeqTsGYJ+sVSxi1PrwYNl+be0XfMiKWIT3AzprorsVe0EjJXPGOTBzskRwPpbLHUXMeyXjh3J9f2s4urd3yrjWdXyH9ewhpuZYtlznscxP/x2ajYfQOWyS7nm4Q9hx1veSP6ojfhLJzpzAZPcRyt9U4c/MUH9hmvZ+6kPpqOsU+5IYdNxxPNzoEqwbAWz3/sKLsFZAQw99PG4+Vk0qnWEg6b7xKV5wTWiBk1fHAaIaogVBh70hNT6yxf9iGhyJ5LLJe7kivEM8UzG231oFNPXn/rOi/q6t6pZnoe/ah37znkulYt/kt4Ht7svo099PdHeLemqY+IC1qiOHRzBG05rsNzMHiTIdaKmJI/Wy69eSL+213OSTB0TqtvFgKtd1czwisnILQtchi7eSnr4zR8MtCTTDiaSBa1Oi1jWCyxJxtPo9cdY+Zafix+l+vebIP5d6rAa1UD31JKX+6iCCTVuq4e7UsDEQXa1MsQhR3/7AkqnnZrmePbuZfcHzmHfpz+Jq9co3e60RjqS8ofqri5JM8oI1q5j5jtfZfypz8cODDXO4SBP7ujjqX3/AvyhAezwCJUr/sjUVz/N6Fn/0f7s/ns/nIkXvZa9H3w9heNPaw52cIlIqnVxdQ9MbTdcW0t9y9WM/OsLyB9/euKqj5n99sfwx5d0TvI2h5bHTe3MSC2W4Q2NEe7Z2nF3aO/19Md3fokb3zmqsu89z2TwkS9A44ho1w24yjxar+KNL+8UDbIW7JUp/JV3QZIarGZKaAqFFCh09KLabbtAZsozmpmqk7B9aTnJtqfkJD3btbG/2yO+mis2HW1Xslk65SjaOi7N9bS1WSZjddMSnbYGWjrpiEsTY8Laui1HQ7cl0v69MW5M2+4P0lTNa/IzEuuKjRLY8Cs2lveo8p9HAOuvBFYWea5FPhi222xkcTFo4o5kcz71nfsoX3ZZF2DVt29jx9veQm7tGvJrV3ciDBJ338QdNS03UOxAP9Hu7VSvuYzS7e/aXq8/Moq4elNA6PCXrWT6q59g5AnPQWxHSDPx3NcRbd/M1Nc/R+H4UxvpYWvwZxKo2qlJ4m/WEk/uwvQNMPLkV6W+1+y3P0n1yt+TP/rUhkFeAnFMEHSlhBLk8SZWUN9yOZLxpuq8uYe5iUZ4Y8twC7Ps//h/NYoLxiLWw5QGsEMTHbuZlki2uQ7bN4ib28/c9z+NN7ocb2I1uBhdmMbkC6mxWSm5Qcp2WDPPS7qxoTX6rA1KWYBqktW0fKs6iJQSgLY+p6lGV3oAl6ardrREpU5SrUKitL+btpGxCVJJsSkkFPHaUcg35H2IaEOIazJuFj1AS1XwhBerSN5p/NwjgHWbgpVikPsYzAddr0rgAcCqMbTUw186wQ3PejaFozbSd8YZ7VWUTjmVpf/5n+w6550EK5YmlM+aqtx1W7201OkGjUOizDSalIeVKP74BNVr/syud7yEZf/vvamXLn/TZ1GU2W+dS27tUdi+ATTBlKZ8tqCZ6wrRrhuI56ZY/sb/w/SlQWb6K+c0BZmua4KNKRRxlVlq11zUiGh23UA8sw83u68RMRl6NNFpV8TVPj5xiOQL+Cs29nyuOzzTZlQ3QbRrM/ve92wkyGP6RhquEUYxxVLz6k4AFJKeNZgNtBIRVOOl0qPRsBV9LQZc0rGX6dwWE1vdjLg0EdlJx5yvl3JeEwbyXbBv0i0+kvGZb1vauA7YayujSIJWM9LqaulxnUPmGl//OVbkihD9gBwBrNuIcBN5oCDf1oOB1WIEpSi2lMcfHWbzM5/O8T/7Od5IZ5T6sv98GbM/+i61rTeSW70GdWGqKiS9RJRt18mGM6c/vjRDHE82y/rNAQUuIrfhGKa//GHyG49l+DHPSr1+xZs+hz88yuz3v0C050a8pauwfYOkGsiaYUI8tRs3P0mw+miWvuLjlO72yDRYfeldRDuuw19zTDvFTHp2SakPNGL3m/4FrVVw5dmG6+eSNdjB0bbYtBMR0RusFgm6eobHPdBc4zomX8BfeRSoQ8M6WpvvpIiJ9bf67VIpXjINIxENSSJ9TEZdSeRpdUFoYhpO+7tKe8WqnQ2QZBTVzAVFO+mmmEWiLdNME1vrcZ1tbT+mCVraIfLVdaKtdrTZbOtpmwG6Bi/ZirSg47HVC7QURVTeb0VyTnnX3wto2cdy/GFbB4wRhvpmKOZjnNrnGvh8ajLgzQCrdgUHxRsboXrl5cRT+xl6yEM7O6NYYvA+92ffpz+CGMWWCo37WJclSsbixbNE226gcNxJTDz77PbtUWtV9n38rQ3CNJ/r6MB8DymWmPnmZzD5AsVT7pL65qU7P4i+u52JeD7RzusIt11LPLsPV57BzU/ipnahlVn8JSsYOetslrz8YwRrj0ungt/8MPs++EKClRsQz0u0l9A19QccJl/AGxzFDo01ex7jLnK8pxNrRnJxwJ/F1pESyEpje32/jc09fa5S0bR0CgDtm4t0vz+5LUkqgcxzIumqIOm/NW5UnfV3bUsyTewyXEx+F0ll2pKlGkhEW70mNJH4HoaulFhIm5hm/9DE0vt7ImviWL9WDRQGcu2o8bBEhS/wL4chVDVC3irCmiU3sXSwdlY59j5DlpO4GWCVragQx1Suv5ZNXzqfgXvdN/X5+7/4aW56wVMpnnRqj8gqa4ts0IU5wq3Xsu5T36d0Rmcgw/yvv8+WFzyS3LqjMxecItailTL1G65k6Ss+yNBje9MJ0f6dlC/6EeH263Cz+xFrsRMrya07nsIp90SCQtd75r7zMfa+51l4S9dgSoMJFpd0ekkn2urFqB/SENRbSkj2+rMe4HXaSQm7R94nbRKkE81k3t81yj5Fxif+ddJj8Ko0IydNb4um35ck5lupnWbtj9vv62xj61Bp67GT9Gsd3b+7xHeIM+ttvt45Fh0vZhFc6N64ty96NauGsE6Q2B2WpUT5NP9+WAKWT0QVw7olN65bNlS+vhz63WDVDHXNAXQqktEPte9g1hLt3Yl4luN/c3GX9cuNzzuL6e+cT/G4E5upUTa9FMQYwm03EE/uY8Wb/4eRf312ah1bX/IvLPzmBwSr1pGc0tmJOARXWSDcu42Rxz2P0ee8CbG3LEufOvcNTH3qNXjL1zfAykXdACRpBjvpHHzLAEnxrOBc0ze/nao1NGOOm1FP1x4Ptfe/2rS47qpjJmcTaoaEV3pUFQ8duLRlmpe0UO4azNq9PtXMZ7oEuJD4vbXqWwBa7c9vPu+ygJXYHzZU5vp4VGVZ8XwvlsPW7EF2M3HYbdR8UOLG0XXkcKcVc9XvGRuPacKb6JAjK9MhR8m0MogBCXwq11zBwF3vzsbPfy0VUsRzs1x9/9PQOMSfmOjIDASwHlqZJ9q2meLt7szoE/+Dgfunxacz3/kCO17zNHLrN7UJ8k4ZvRPViLVoWKG+5WoKp96NgQeeRf99HnfzrIpVWfjtNyj/6gLmf/Q5vKVrMYVSswFa02lPFpAOdGK2xro4EK9xdvsi5D1hLnQdD7HGhVoFKs5pTURCI8TNd4sDDzQnKjmEgoDfAQtB22xygp1c7IpJpzPdUQuSjqCQFGi1Hicjr17rSBLeqXVr9+/anjadiZToRDnJbeoVbaUircxQC9cGnQ4Qt3mt5GtdN0iluLl4cdBq6IiFCP13Yv1seVmRaslgo8MrzJLDUkDmw+82nn7SgNb/XA8tsTOZ6Ia04V4SrFog5aLGv4FttoV0IrLUvDrfsvDH37LsBS9jxWvfngad713ADc98FMXjTwHb5BM8j3jfLtzcJGNPfxkT//Gars2vXv1nbnravTCDQ9jBYaSHPKENpk3FOcYS79tGPLOP/DGnUrrLwyne/r7kNt0O8fyeuyncuZny777Nwk+/SO26PyDG4i9fn9FxdU/d6XUWtAhcIwkkMIKgTkR3OOVGD7lhNo62T1biXWv7gx0SsxuR2bq4WVEzV0Aqv9lZrq0Z8aPxgqehUwI1THo1E9VrwURczEWBFIxIf+h0SJUh3zIuEq6INVguKquAVcB6hbE2mLSBSHsDmWYArFfU1EsL4bTjdZVMRzUBNtmoiUzkdADg0sxYejLRlUt26LtO5JYix10mMktGW47u18aJ75wFrWykRSIaa26TdYqJ9Gn71xU/UR8qUMA/rDLDww6wFoIic8WRie3ja66zLuxP+cctBlZGEBzR1BTR9D5MLsD2FcEKrlpGqxVMsYC3ZAJbKiRsjLXhERWFVK+9gqO/eyHFE09Lbc+2VzybyS99gsKJp0HsCLdcjQQBy1//IQYf+Niu7Z/9wZfZ9bYXggF/YgXEUVozle2+z1YgjTR8qmb2YvqHCFYfS7D2WLyxZdi+YTSsEu7bRrTzesKtVxLt3YopDeCNLOuA9WLRVOaoGwRPhFActiEkmnLOXSFwqVpzmV+rXRL36w12Sbyjel3JBZ5yU63K5XvrPHz1ABpBrELVOjwMRYSfbF1g41jAkpJHC7D2eTWieoWlUR9R0EgPay5G1ZD3HEbmqLtBjBpsA0jyaljjiayPjB6njtspHC9wLOBrEzB6AljPVI/ulLANSMlR9pIGwVQUJD2iIjkAv5WJ4Fojw9xBuC3XiTzTKWMiRdSDgNahRFok3pdNDxXCocIpMzr35/np/ZjDyJ/msAOsK5ZsYnp03a9L1ckzkoX03k3MBgzUb7oRDWsUTzqeoQc9gNKpJ+MvXw7GEO3dQ/W6a1n4w++Z/92vifbsxF+5Cm94CFqyBWuobbmB0u1PZ8NnvpHaHlercu3Dbk/9xusQiSmdfk+Wv+b95NYf05WW7Xr7C5n68ofxRpc25A1x2DkTWvIIoz0qRtpjnJdAHOIWphtyg1bESMNpQXJFbP8Qki+kyPPeINUwLpeWeNI0Z2qI3hRKfGFBvd/OhbWLBvLhn8LQn1dnIPDJleeJBw1mpaN2VR+Bp+yo17huKuTey/rQqGEGXTMOi6GA8MvtZdaN+kwUO4C136sR1assiUptwKo7h6qQ8xQjC9RdXxuw1DW+S6BQDwSnjqiuGGeX2jx3UOfOQLmDiNxeYUgdqDWN3ROlFfBdBLtmCfsE35WNuLrALxNZHSxNVO0m5XtwWz1TxATAJVPM9msPFGktwmGlAPYA6aECfba4ddf+69ZfNbktkjRH/zddPDjqMNgMBSJ2D0e4fP7cIFw4BLBq7MbadZsZPvOBjD7+cQze7z4NoWFmGbzfA5qp2hVMf/eb7Dv349RuvJbCxqMagkZ15NeuZ/7XP2Ln285m2X+9pf1ek8uz5r2f46bnP46xJz2f0bOe3x0V/uYH7P3If1O++NfkNx6PyQUQ17tEo92lfO0udZOIwHyLHR7FDo+RFEImSXMR7U73Eulnu2euccb/wVj5joq71qhcO0/8m32ywMZ4kGoc06euLZpsfFYzpI3/NndYzahC1bFLlW84p99Q54hs0J+P3V08KxviWngCIvdwgXcssaZ4LlLzB7Vzp87ONbSZv0uaAmzbbEsSpJrpvpN2C09LBtGIBJVeolNJtBK1dVsuYSPf0mw1rZJb6ndJKuNdQhkvHdAR2wIxTfnGSwuQmzIIs4i4VICq1lcV+sb/sC6Xu9vq/TfNFuvR4QJYGw8DuDLUPbhpovxhofKEoF5F2+bjvQl2sYaFiy9i4slPYv0nP3pIn5M/+jiWHn0cQ2c+gi0vfjqVK/5M4ejj0DhEceQ3HMXu97+Vwkm3Y+hBj+m879hT2PTdy3tySXve9wr2f/4cTC5P8YTbN6IqF/dO/6Q7ykpFRG3JhKR7/ujhAJCVINBQUrepuqZ8Q9X9xIn5WhDXfugivVxKfahz7Sqebe5n22uixeFXPk4wUUKszBHxXZOzmF1lYt9hlgb3dPhn4ngQhuPUSWeAc6/hqa0R862qX1Lj5BKTmmntzw4opYDLNNcj0kzbtNOnmJjGI8noqyU4dQle0yWmRWsLZHqo400GtGi278SddbUGXqRBq/m+5gAR45qraE6gptlYEcV1fL940tKh0V9sXJg82YQzvaee/PUB6zt/8/Mw9ApcsfYOLwyi2jNVdXGwanI84hnKl/yZwfvc+5DBKgVcGzZx1Jd/wHWPux/V664kt3Y96iIkCMitX8f21/4HxVNOJ1jWmZGXBatozw62/dfjKV/8C3Lrj8UWio2oKkmqS4/0TzQRdWWBqq0D6I62DgJUCnhe487unLtcI/lyzpOvVJy7NASC5pX1jzQGShJUT7tcGYc/VfV+quhLVeUeRvQsY81DgQkXutSdQ5KRnGpDMKmJvdSKbhJNzO2WmUQ01a5MtuDNSKKlpiEydYkRPmkJRvP1rVPFkm7tMenI6KCgZTOgpZ2ITg2JSWkd0JJEZNeKtEQMqiHxfHTS5aMbvhSXoseG4jf6LP+2gHWHv/Ept8DuoflTa37tvbl673mBbbCyDf6mcsVl9N3uFI7++le71jh5/leY/vY3iKYnMbkcxeOPZ+C+D6DvDmekPznIsfrdH+PqB90OtzCLKZUAhx0dJ772Cna+7rms+fDXu9bvFuYo/+EX7D7nv6hv20zhuNsh6tJ2LJmev2xK2JX+mWS4lUn9ZDE5gqaU3NayUK/pt2INP5QrmZ/FVa9xErYbYv8JhppLMrQAp/oz59ufBVNRwUbRI8JlxWdQdvfSRgd9J7LJjKVPAVdzJ0ub40pHO+1joR2QaoBQYjKQKkYaqbayeIrYwrR2pJUELW06OxwqaLnOulL9h63eQzpTeFIN04mm6lYaHHq5x1RLhYdv2Hb51wbiyt8WsL7+N/rguKFe4OSho4emRqe/H9RnELXpiiCZqTa+R/Waqxm4+53Z9I2vNjr5W1Hajm1sftpTWPj9b8GCNzyExhEzP/wWez/zYQrHHseqN7ybwvEnt9+TW7+JFa97Nzte90JyxxzfCJ/jiPxRRzP3s+8wff6nGXrkk1LbXbvuMm585kPwl41ROPokiOodwtv04KUWiZCkfYeThE95r/en8b11L2+4TSoO+aJn4o/5Pn+anGY/LqbYbxu00z8BRh3sdqhWoBpXtF4/z5THz/OGZtdHpcqdoxtKT7V5vZdTTaV8aBK4WqmidkbAJWxjpN0g3QGpJAkvrTFhKk1rmFYETFvj1k4RXYILS/BaySESYjoVSEmy4M1oviUyTbk7uB6cVhu0DsGaBjAuImftBa5QuFe4EP408OuN9PhvcIJ5l5R+/7dJA5tc+sBE7SdjrjSmznZyd5Mp9zfBqn7Tjfijw11gFc/OcvXDHkLl6qsonnwS4plOs6+3Eo3qLPz2l2x+0sM59gd/xA53xnaNPv4ZTF3wOepbrsefmGhzSMGqdex8y4so3u4uBKs7PF/hpDsx9tTnM/Pl/0U0bobWi4BTihzvxVP1jqiyQCXNptgWeBujZafyyaiuHxWfPze4lBhjbJPs5ciSrOdYAU+gLkg+vp6+6PqozudMPjpTjH2eUR6o2pg4097/2ovjSri9om1Zhcgi0VKCg2zPGVTFmCYhn0wRW03NzeGprejKmAxoJdO5pP1M6zxJvi45pLVXeugSoCWt8ywBWInCghdF7Fiy7vu1ii7ft3d0n2iA/A1qh94Tlx7/NzmLrIHQDHx4NpRTaoRNgzp6aq0aU5h3E09Ps+mr56XACuDaxz6S6g03ULr97Rqkd0s0aRp73PgexZNPo3rNZWx56dNY9/HzU+8fftjj2fHGFxIsXdI8QR3e8DDx7B62v+oprPvMLxJnn2Hpy86hdvnvqG/fTLB8TdNOmJ6RVTKVE5PgRtCudLGnwLP5XHM6jCruvQ59m1OzO4rA9+nxpiNLz8UoGguEDdvl2LlvYsw3rXF3klhekgu8x4pRqhW3CHA1U8VEi1XbpK/5uybcHlpA0OCEmu1BbUJeOoR8itdq3gCdHBy05BDSw2Tu2a4ENnmzVgTWqkaSqBxqunKoQBBXfWuDn9Qje+I8dXxxf/Wbozcf9f/V4/Q4VpzhGdbnmZBwC+3hay2BR7hnN/HkPjZ+4TP03+PuqdVt/X8vY/YXP6fvDs0KXdIdNNmOE4fkNx3LzPcvYOr8zzH8yCe219F353sRLFmG1uuYnN8Mr0Ny6zZRueQ37Dnn/zHxwremQp6JF7+DLc+8O4xPQBAkUoJeKWC2+ncoUVXjztf0bN8ioheo8t5YuaH91iMYdQspr5bXQfzbec8+rjIzc79S5D+/MFa4r4ZaaOnB2uR+6/d2dHTgaEta/29d+JIg5LNVxBbguQ64JiuIPUHLdNLD5FTrLsmD6/CgnVSxWTJNSh6av5u40wWUBK0oNgS4E45ae8P/7NbZ51Trf4OU0MV/PVt3USX2DDava8XFH3HJ3oQeU43xDNHMNPHUJJu+/mUG75d2Vdjy/17Kng99kNKpJ3eYxB5g1ekhdARLl7Pvsx9i6MzHNWbmAbl1R5Nbfwy166/E5EcTNhwxuU3Hs/fjbyO34TgGz+w0ihdOuRtjz3ot+z/2enLHntYYhtoVVSV9xSUBVL15quRzDaCKZwR5s3O8w2nayunI0iNuj4G64mKDcw6H4DB4NWn7TfW+hwqaN+y+bvYHM9PBD+44VFuSj3JnR37hhRq7tDaKBo8kLhFtSYuo7xFtabpClyLkNQFqiYphh0tKVxCNbbaHts4dl460UtcSiiZBqxVNJUCrLZloVw87nJaJMxqt5uUV4pDIf/a4m7hg7Vjxe0P5fLMC+lcCrL0T9q/2Yc6z5GdCBsr1LxGYNDuamUyCEdTF1G64ho2f/XQXWO1819vY9d73UDzlpKZ/k0vZv3Sb7jXAwo6OEe7YQvW6Kykc2yHg7dAwRNVMdNQw5cstX8Hkx9/A4IOfAKbjpjD6tNdR/tOPqV/zR4I1RzekEQeLqsziEVXr5DBWIZZ3+rb8lkhzk6p+ot/vyJI8bzQSXM1Dy4LpBxm02Fwd6wkaxcS+Q+oxOguuYtHQ9OzvEAe+72ECi9bru+O6vMj5uS96ltc61Qc4Jz2im4RxXzJNdA0jvU60ldBtJeQOSVFEg4xvnismGTWlK4ht0CIZNXWa1NP8VSbSyoJWEqQSJHvrPJRFSPhIYSjvf20unpn4za4rZ/2/4m3Uy+f+OsPKFCW2jlLg3mircvtQMxIGEvSObRyJ8qW/Z8lTn8HYE9NTYPZ99tNse82rKZxwQkNVngQrQ5elcUrn5FlcvUKcsTIWUdSk34MBo4odX0pt82XsOedlTLz4Pan3LX3lp9jy5BOJp3ZjR5aAa/YOGkkpzuUg6V8rqrJqLqJun1oztUuDI+x5+hxS8K0QR4Z62REbA7mI3NAsutRhSxYv5xMXFghsANU6+CHRiEdcN9RunMGUQqLIENUspiRdl5oBxFjUGBR+E+IeaIRHelY+6BzLnHaseFKq9QS31QCcpvtn8rWaiJhcJrpqkvE3C7QOhdNKxOUd4j0BWosBmCSEspAy//NECGPNbd5Z/fblu2fv6tOwSvqrANbxvz/6r3Q3DNg3dM3DZgb2vTL0Cp2bXA+SXeMQrVZY8eKXsfrdaQeFhT/9kc1PejKFE47G9hUSU20WSwMz455qVfyBAXJrNnRWGseEk7s7Aw9azcHNNE6dI1hzFDNf+SCFE+5I//0e336rv2w9S159LnvefBZ2YLAR7fWqABo9SPrH75zEn3KR+R8beUiudiQBTNAtnsDSoQCssj0sU1zq0H6lOBbi6SxSLBJVA3ROccYQG0FrBokFh2BzBrt+kiAIYNZHxyvEFRDfgI0XC+CaLYZ6vuB+rIZXGZWngIw6l+C2WjIIJ23gaOibEikiSbFpIkXUBFeVrSAaabfstEErkR5qkrtqRVouA1oWNM6AliMTdfWoHLZIeBL2Ngl92EJUZeng8F2OHxh62/Jtm/9L3F9ndpi3eemVt/mHxDjyLrfWC/iakOsJVqlhkZU6GtZY+cbXdq0rWLqUkcc+kpkffBtv6MRGA7TGi0RWGSsZz6d21Z8Y/bdn4y9f015n9brLqN9wJf7YkiaING1WWvqq1ij1pSvZ9YazCNYeQ+6oztSdvrs+guqDnszsN/+HYP2JTTJl8aiqBVZiW2JjdzYib43UNcw85EhkBeCcUvQtfUMBzMX86qYyb/rVDp53f3jIAyeYn6pjQoinPYzrNE13r6hxSDQ2uMjDCwz962aY2W3R6RKlSJgJ4gNlnqgyU0dflhd5k0HfgZGnx80LvU2W2xa3pU2JTlpwmu5JlN5qd9ejgmgSWi2T9o7XLKclPcSlLdBqRXy20yCdAq1s5VASPYeS5rOsGOIoZH/Ay+dzoz/Tmfjb5q9QBfLqfvWvkA5C6NXPdc7Dqk0oiOk55cb0FdG4xmV3vhtHnftZCscfn4holnPUF7/Kjc9/Jvs+90kKxx+PKQQNtTnddsbtz7Ae0c6byK3dwMTz00A4/8vv4OanYdnyLpO9VouNaowdHEHL0+x+61NY+cFfY/LF9jrGnvseKn/8Hq48gy30paM7SI/naijTEXWX1COeZ4z+0hpBjkRU7RNmrOhhBnxq1YiP/3ov510+xY+unwfghXYA1CMuR53WpUNar4BVnAMNDf3jMd/0b2DT1CBronF2HoQjbDhim+m5qfAZavWPw0PBObHDr9e105RsekVbvVLEhPSBTjSuRnqDVjuFy6SHJuGr1RL6JxXx2lvykNJZZTzQILMjkhqthNxBUFwoLIwNvn9uKP52JHqbW9F4xdsSr6TZB2y9Oxlj79xQFevi0VVCye0tGad27bVc9eAHcdzPfkZu7brUqte+/yN4E+Ps/sDbKWw8CtPfn2o6TvJWYj3iyd1oZZbVn/sh3uiSzs23PM/UVz6Ct2xVB/DQnkp1XIS/Yh3Vy//IzFfOYfjfzm6vZ+4Hn0Y1bvQciqYdTltMRSv1taAavy2M3P+LnCFnjwCVU6XkGQb7AzCwdV+Fb10yyYf+tI9Ld6fbQXwxtBwZ/nI+TAjqATfkd/P9kSme4AwT1RzlOjizuCBSBJxzRLH+T+z0+9Vq9OEg8O4TxR0mQJvFlS7Q0mblTpKNzJpSjKdSxyxote2/EulhL8lDNu1zifUmmsBTHFa76aITWSWriG1FfpacV8UjXl/w3MurRG83LbnEbQVYtWZp/7ZAKwVszo3kjHwnThLIWbBqaa5Ijl0PyW/aQG3rFq5+2INZ9YY3M/zw9Birla9+E/m1a9n2qhfhL1mKPz6OuqgDVqZBoLq5SeLpvax6z+fJbzw+tbO3vexfiOemyK05Cml5TkmPUeLtqcUxwfqNzJx/DqU7n4nkCkx+4mwqv/sW3vhKpNDg1US6oyqxgiW+uO7k5VGkP/DaE53/udO+wYJhbKBANK985dJJLrhmhm9snmGmcgsLQpq2mekxSZHxqMDlLPCV/us5bkmBtfEEI1XL3AFiLRHBs0K1Fm3etb1y39VrB57ie+6jsYpt3TNpckptuUNLIW+6pQ/SvMjbXFU77WslkotwWuYAOi1JVCFboNVLo9VKAQ+hitjmsyQNXE6VgpO3FU3wG5Ozv7DibjM6y5syt5EOS5Vcrc5QLviyWoZT5YleqSCZqTYoGkfk1q0h3LGV6/79X1n91new5DkvSH3M2FnPwBseZstLnoHGNYKVKzsCUmPQ6gLh9htY9Y7PMHCfh6feu+dDr2XuJ9+meOrtFwUraSvmO9tl+waIowp73vpvoDHh9usIVm8CaxDi9Mgl0zjgFrAafjBG/8PFpjkO6p+Xq4qdMpCzDI/nCOerfOCXe/jIHya5dE/l1jj1WuW0o0W4nRUsyjUGfqtd/Koy6KB/AS49qsKWtTtZvbWfY8MJdhTlgOMWjTTMCKOIT4rhVxb3OcTeoUV5iHaGo0pbs9VMDxNC03bUZBKg1fp/L4FpltPqBVrJCCnRUE0v0NKDkPCtqEsal4JLts81QSsW8EW/PR/mB+dreWdwt0ly6B0X3XhbZIJEVpgaWPovhOZezsWLR1em+3ErpWpFWsHyFdihQba8/EW4ygLL/vPs1OcNnfkYvLEJbnzev1Dfcn3DLiaOIAqp33AFS1/xHgYfmpZGzHzjs+z/5FspHH9ik/86BLBqbZsL8UaWEC9MIQK5dcc1NFi47lHyDrx8jG9qT2DWnRflc81+v38+sGqRxeMlD+nLMTdd539/sY9zfreLq/ZFt9r6reF0a/TVse89WFu2miIE1v8Dwjti+GJy97vGfZKlIUwFdT65ej+P3BtwRm0Fe4MaNcKGdfMiKaIIRCrX2J3lO/oT3vujfO4/XERaJ+USCnmTAa22Gr3bmkabQlIRXTw9TICWSGbqdDv/I+HHlpY7pIasJvmszgDHBImX7nVMzTmUqC9eqH90dqb/aZ5x3BZlQy83f1twpkoO7V8Y8T5ds40qQxasDJ2qoPS0CCYxVTnG9pcoHnc8O97235hSkSXPemHqM/tOvzsbz/sBNzzjkQ1R6FFHU73qUsae+V+MPflFaZL9599kx38/A3/Zakwu17aGyaaBXd5VCTGourBNrqtGqQGYkuDABJkKTPxEk6t9OyT3T8pPNU7wsaEA+ixbdlU597d7+dSf93PN/vqtBoYqDr8Yv9559jWx6/hadbhlcztUv+AJ/2atPJNId6WiLYXhKkx7cN6SnczPKnddWEoJw5Q58HaKABEIleeDuc4a/71xtiVGE7xWW2fVA7REMnKFZqTTirS0h06rCVopa5oWuGRaeDpj7zNRnmTSQU2o4uMkeGb8s5rxSBh79BdrTy0Udr2jGspVt4Uk3bt07LhbnbmKxVA07oP94ULOuqjTBJpSsifuAC3gSozlSskRDKAOUyqR37iBHW96BeU//oY153wy1Qid33QCG//vp9z03Mcw+90LGX/Ov7H0pW9NR1bfOY8dr30q3sQy/KGRhkWy9JIgJIhzOidSK33tpVpPaasA2xd+nKH45brNm0wp+/+JgEoU1o345FT507Vl3nfJJF+9aobZ2q0nWFbn8EQGbGC+FEt8f40TDqHSrJdpY2MawX780NjPX+PBXeZr9Uv3ROCS4uUYagIfL+7ip95uHja7ko3hIJXcQdwJmnZBsXPnaKS/tVY+oSrHproUWj2CLdBpWs+oJlK9ZB9hr+qhWQS0pGNNk/TT6iksbYNUIs3Mclg0tyOZYkqChE9mTS5hoxXqJ5j37hzrrd+b4Y1Vpm/1sN8Y1pDzzoph0cZm8SxxZaHRltBf7MSkSZFn8n00IhlTKlLYdAzT3/wK9W03sPHc72CHOnYx/pIVrP/099j1zlez7BVp0Wl9y3XseO1TsCMT+CPjHbA6IGdFisQ/OFgp1ghO9FWxF73Jy+k/nd1LSw61YjiAnMePrt7Ph/+4ly9dehuE8yjieWusNT+NVdY6twjn1KqOmQbAher1e9b8YoPXd4x4bteQTU+Ibl3ke0ohlw/PsnxHCW/Bg0PwChARwkgu9Gx4YuBxQU3zZ6qLm5qshA5LO2PeJNHO02r16Z0eJiOwtM1Mypc9qdFKVR8Tg3MTIJUE+BS3lVTn95gKnvWGdLEQ+O4MHY2fsKD+52/t27QX5+ytClY4wVg5N1Ws7VKzC65aacj544jqtdeSX7MSKRUa0oDkDMH246aS3TkwQumU21O+4k9c/9SHseHzP0hFWqZvgOWvOyd9EZXn2fqSx2D6BgjGlrSJ+axpXioNJJ0GHrAfsNlOYS1IHL8hVt5kEYj4pxGsqypGYP1wQHEo4Peb53j/7/fy2T9P3TbACHjC0aZQvDByOtSxLO6AWUdAJO2wQKTRp7qgbvB+uaW/8kt9GyjkMoMCO6sgcFRGQvZfVYeFQzyYYrFSiTHRQ6n551vDI1wSVDLShBRoHYjTaiKaadsuJzVSmvJDy1YOW6LoJCgjiZYiSad4JNT4bbvoRNVQ4gRgJdLWUA0mch/Nl6rnhfmcyq049d5z07eeCZc6xeuTf8H37qIuc3dL7CCMUL1+Mxs/9WEKp5zMzre9ndmf/YR4yw3k1q9tCEddnG6pyVQXNQ4pnnAK1asu4fp/fwAr3/IR8huO6bld1asuZtebn0e4bTP5jcelvNdb4JMcGNGLs2qF9W2wSvZuNwWiRuI6mP9wqh/t5d79jxxReQJDI3lWzsf8ZnuZz31vO5+6ePI2REewsNSo/bVzOsQBoqoEVqWeE4W6xOvD6ux5VOLHG+d6aohEweRgZJkS78gzt1+Qgh58AxGcGMzO+UfKeP5Tfs57kosh6gKtboFpErRSIJMAp0bvYQ9h6SIkfEN7leGzWu1wWb7NHeBxSqtFenhHq9ZkTDFfjT+ydP+WZwSt1PfWAKzQ2Vvr/MELZMgG+gVdTHPVtIyp33QjA/e4C6P/1ujJ2/CZT1G+5BL2fvJjTF3wVeq7tpLfuKExxl1dyi4mpWSPQ/KbjqO6+Uo2P+5urHzDBxh88L90bVvliouY/dmvKZ1yVEOf0mOiTRusTDYN7BFZmUwl0ICxZoepzNy97hc2W/Hayvt/9CWOldEBj2Ix4Pc3LvDKH+3gBzfN3raRnHNg5ZgA8+vYueGDVaw1mRJmGnpjBWL3r37d/SyO7P8uKnosg/Ehv1KROSGaM0jfIRxjaUQiceSeXC3p932Rc03cIbbVZdNDukCrUyFsnpeJhulGepgALTkACd+Lz0rQGa0BE+1UMU4+TvhnJVrr2n7wkqk2ohDbp89JcN7e2vSPW5rTWwxYpVvhwmoPWizIB9UzaNRbc4WA1qpIzmfte9+ZWkfxpJNY8573Mf7kp7Dzfe9i+hvnY/tKBGtWI9Z0JAOZthuNQ3Kr1uOmdrPlRY9nTZBj4L6PSK17+FFPxw4Msfu/n07k6ngj4+DC9Ij7XoZ7BwIrOt/LGo1U9R7qdPM/iw5UUaxYRoYKXD1T5v+dfyMXXL3/r/LZVWdWYb3fOa33H/QikM4w01SDXXLeoBrCQF5uiwv/Kwfo41QFDWDpsSHzm/vQSr4p0OSgZHxehRtt/PnA06lNC8G35xM3yA5o0QEFl6ketjY8054jZOQOsggJHyf4qeTA7OS/krCikMz+kmw6mEkNJcMVakNfMl+Y+MR1c5W1dRrSkVsMWLLk1omwfJXVkXNPiKMe3BUdwHK1KsGKZfhLJ3qup3jyqWz4+OeY/vbX2fuZjzL3659gcwHB6jWYIGi6MyRJcEAjvLEJ8CzbX/EUNKwy+KB/Ta134L6PwZb62faiB0NYxl++Bo3DTO9h60Avwlklrw7T4GuMZcG5+M6qXOeZf3y0avBUhsGgSK0ec+30br53405yfsiLz4A+/9bn7NRBFMNJJwwxMTw4ePwy76La7rBfDqBhXzzUyqRWHZnNOhe5pZGLdx1oNbUa2MDQvypm+tqYwcocRQrMEBz04wUlUvcdEV5gRd8Xa9JLPZ0etv7Wlh2Qtn7pDFekMxBCM20xCXvkpPNoChBb606YE7aB7wCEfWobkrxXAuhCFxEga+48seYUa9zFt0ZW6NXz+VsWXQk4FFOpvcbG2vHFSTT9moQTgzc4gCuXueK+D2T0UQ9n4J73pP/ud+9a79CDH8bQgx/G9De/yvQ3v8zcL3+IhnWCtesw1ms3S0mb04rxRsaIjWP7a55O/aZrGH/2a1LrLJ3xAFa883z2nvNi6luuIlh7DLh6KrpqDWo9cGTVNAk07rIQfQyqV9t/AmZdABd77J6Z4887tnPt7D62zUYMFuGs42674oLGUK/Bo87Iw7rRH3DTwkR5IUa8AyvRSXPtjYijJTFIzgmURiXXRSNP89W8ycjiPQiKkI/K1H2PDxTuztjYfp7CZYzpPGVV+qii+NS66f8mVgix4/3Oo2JEP+pckgtKgFaTzyLjv5XmlTIkfKuK2EoZW/2MegA+y2WkDm0753RPIklrZbr5q2wTNs2pQrEx1DX80Oi2XXc2kbvFJ4lcuvFOt8apvD5W3ZzirmynyteaKdj2rPIt8ew09R3bEN8y8uhHsewlL6VwzOKasPkLf8a+z36YuZ99B5ML8FeubhKPiaqeUfAs1CpUr72cgfs/nFXvuaBrXdH+new8+xHUrvsTuY0nNdJDmkMiDsZZNVNIa+SmOuF6hzjPgcHD1mcIgwIGj0gd3nCMN+iQm3y8/jLhPp8w7xPFBqeKhyNSS+A32jxCYrzYxwtz1IIyBVsmIkcc+bTmOmk7pjdEVcXLC76neNaxd9IgGjI46hPOWQo+VCUkUkPR1XGxQqkPdSE4mMcxZSqsi/rZG1YYLkS4KIdzBgk8gkqZeECwyx3lK/tAFduvbIkm8dwCKlD0/NvYZUKJgBpw1LqBn/T15e9Zr8eZ6WcHibC0Y5qnLRBo9eQ19U7qFCvm0rhgT0pO0E5Hl4JnYkreNF/fdjof23s6tgQn12/geeHPGd46xfblg5TyMfkKVPIGDRy6WfGHA64ZbVwGR8/nmLcOY/TOBvlV7Dq0T2tUV3ukvGuO72qZBGr6+3QGWzQeu+YYsPZrk69pRqvtadVKoxlbOzyXalMO0WzdUdeMnlrrixPvc+nH7XW7JJfVwIHcbPxkG/LpWxplye9X3/4Wn1LW47uCeUDqVmwz0ZVNkNytx54BF1K74XrsyBCjj3kME09/Drl1Gxb9rLmffZfdH3oz5Yt/iz820Rgeoa657pbotNH0VLv2EoYe/ASW/fdnuk++eoUdL38w1Ut/Tm7jKU1OK1EN7GW61/w9MKDIsTUNr0IE2wQsU5+m/o8GWINCPGRx1/UxsKZK/5CDogfOcpvrNZTG9/ZiKJrPhPPhWfUwxmRSb+nsmV5LiOJr00K40dvXuThbcwBbTn2ztXgsdEx2ZffN1S/PTfLDhZP58O57sSIo45s6+6TIWp1iSW2Oa3NLKFDjgfXLuWdwBVN+gdpmSzDsc/UolNRw/HyBOeNQcVjhIVXcN+utIMYltqvFZmvHn70DNgmgSnwHbYlS29+XzONEJJQAbRJg45xrNAc2Ze5t0Eo8bu3Pxvs6j117m0i5OognkxWJRxNO038ZYP1u7Z1u0flkkeN9o5e1+7OT/YEmMbE5CVYtoWZT0S6eJS7PU996A974OEue9RzGn/ZcbGlxld7kFz/G1P99hMpVl5BbsQo7PIzGzRYZ00jajYHatRdTuuuDWfGWLyG5dPqr1QW2v+ge1K7+A7lNJzW1Jq4drWUdF5xRiiIU1N4txvwy1DraBCwPnyicZC4XNB7/vQNWzsPurxPmffInTzLol/FG6lCVxsn818iA/ZBwvp+4uvR1+NFr3QFGd/aKskTYp0ofSr419KHl7Nm62LUVYSlYlNq8vVc9lJ9mPQEEGPf3cWV5BW+eejiBV6ff1FBRLDFzpkTdD+iLqkzFBSpOeIH9AffyLmV28wDh0ABbR+GXpW3kQteoygF1A/etLnvzinLf2bMSNbKpJBC1QKvlp6WdKmc6OmoJSJv6rCZHlYqylEx02fwxHvH0DOGe3Q2jP+s19kmtwabb4VHs4AjE2ohQ4wRgHgy02rAgOHUvilXPuUWA9Yd1t/+LwQrAM+bPqJyU8rkynby23XaTaGgW0Q5wtcqjzTH00ew04e6tlE4+lf7T78rQwx9H8cTTeut/qmWmvvJJ9n/63URTe5o6rMZeE9McAmAN4Q2XE6zbxJL/9xHyx6UBWsMa0198O3M/+CQmyLX9rNrupXRS2REJyr8v7Ln/hcH0rwabnENr/sCsFc6YNpzqVjNnlFDjv0/AigPi0CcOiwyM7qKwbpbcsjnwgGqGzL3NJROGaGHsCVF5/Fxj6gf83CxgWcO1ThlQZUk7LWxfXNoGr1YE4hwoHrl47rnGVf4ndZ5hyAdzXF9dzxt2PxqNYobsAg6LSGM2j4hpWwWpgzlnqYlwuncjT772F5TGc1wyMcAHRy9lQSHfPFMXLJw9t5pTpkZfsVvCN0VRwnuqNZknC1oJUGsDhfYCLQ6cGhqLVqrUrr+GYM0mBu79EHLrN2H7BhvUyewstc3XMvuzn1C+9DKCFWuwxRIujJsRV2KftsAzzqSdad+DmjhWqPAXl5PtM4dXJVDm0H8Ug8U8ysALNHkbSkZXWbCyGf+rtsFeh+g2+RzB0iVE+3Yz9/PvM/OdrxIvzFI4+nhMsZQ+ST2f4kl3pP8eD6F2/eVU/vwrvJExxPPaMggRxRtbSn3r1cz/5Iv03fnB2OGOgZ9YD290GbNf/wAmX0J8k25kFkVFCTyhn+IZfyju++23+6aZtyF7/MbP7iDkykLIxqpyQrScmnWEGmMLiskrMmMxuRBXtjjP4lSaxo2KwzTU8SKN4oVajPOIbYhvQhwe6mzaNrmZs7oIjCdYA8Yo5YogOPJFi6sbfAuRNMZd+Ro3TqIgaJuB11GqEjHscpRdRMF3uFoAahg9fjv9t9uNN1ZvAFVEumXqtmb3DdT2rzrOVcd/ZGyFtPq411skqSK4XKDmlA1ZLqvrjqsdfgjAxXpZFMoPY/WJnUfofPrsPNury3j99kdQdQET/hxx80QX0fbUmxZgORXyWsMAF+p6ql7AHaKbmMMy55QVC5aV5YBl5YBllYCN84OYsv+L0KixHvdQTY6KS5TZsyr1zPdOPU42UCuJidXNfeT7RHt2EU7uZfTxz2TlG97H0IMfTfGEU8lvOpb8pmMpnngKA/e4DyOPeAwIzP70R2itjh0aoonw7S0RSFcHNF2lbP7jiaFfRL/V2F+daeaH/HPxhjv8ZdRC47/rnOqGJIQuGl3ZhOapGVG1gcuQnifYWoffTBW3bSa/dgNDj/g3xp78/EVTxV1vfxHTX/0I+TUbwfOa1bzm5/k+0Z6bQJRlr/8ShVPuCUD9+ovZ8fL7YAp92IEhRDIWMU4pjBTYobNvvWLPrrPngxqTpo6fJUoERkNDMSwynM9x/NhqrM5j+2Nky99BhFWvMFYMqUuOaCCkuHyGuOo3x2L9dSuRoVNyda84MFzc4jw7qrHrLtX3eF+j742rBLY45f7Zk1ZTXI6mIqxWqoiaD8eOZzdEpYZRf5a6+py95VFsqQ2xOthPpLZNiveKsGInDSPJZrltR36Yu++/kifsupBy3wg1z0/JxMoSU3OOQgnGJ/TrOuseGreqm8l0rk3CJ/isTLqHS0RRKm275Hb6pgLGI9yzAw0dK978EQbu9ZBDOjazP/0RNzznGbhylWDlWrQedvZdnODL4kRa2B1lTStuWPnLKoaec+5mo5U27mIbEbshIzRJaa7alsc9/p4WrHX/vWHsH2MKBYrHnkQ0s59d73k1s9/7Ekue9yoGHvDork1b+vL3Yqww+bn3kj/2ZMSajngkDvGXribau5WdrziTZW/5Frn1J7D7DY9pjKYfGm1cyFmX0JIlv63yjeKN+8/298+xTuDEJieZClUVFqxjUufp9+fpW9pH9RSv0bNymC8GwQALsSEuRcRVS/nyJfBXntwqNGbeBb5ia/6PqhqP5ldExPHBN8I10sAZI3qRc/KIRXVYmTadtvK7eQtWtNC62Ae9BSou4C3bHsTW6iCrcpNEam/md1KW1Gb44fKTWL9lF2d+7RfUkkMdaE+PRwxEG/x/m3rshm3BlDdA3JRctFwTWgp10wCtpPlf58TtNEQ3npe0JbKCW5jFVcqs+/h3KZx06AHLwD3vw6avfJ1rHnEm0eQ+7MAwxK7tm9UWpEq6JzvVsoMMqcqDctZ9x5qbf4J5LrA3+6xSVUwkb1DXG3ikV/9fwlU0xV0l1eXZ9pumiFNdjB0YpjQyRrR3O9te8XSKn/8Ao099Kf33SN8dJl7yHhCY/vIHyG86qaOlE0XjEH9iFfHsHva+62nY4TG0VsFfvg51YUeLlVAhuzHvG9HPtj9s7W8nWXvo0w6IuYnyiWsgH2APQ5ASq7iKoTbns1C3RJqjooJ1fzsnVC3E5Icdw9Z8T4rR6dVJxfYLXqmtPDnAaSkqGv+fQx6s0LeYZjDpOpBs02kf++ajQByeiXjf1nvxx4WVHJXfS6w3v7lEETyNWVae4nMnnoGWDQ+9YRtEXnevikIUFebiHWOn1IKdlznjiqKm6RTa1FRJUjuWaN9JzhNM6LO6W3cM4fbrWfLSt98ssGotheNOYP3HP8U1j3s0tn+g0+LT9tBqukpoxwcwJSxFyRn5yLaF/Kr5yGK5eQGTN7O/ePOIUAxFP1yVK1X/Nc6qamWR6Crhc5UUlGY9r2SxiKt1F9EIb2IZojHVqy5my/POZNkr38/I4/8jDVr/+R7qN15G9YoLCdYcDdpxZlBXxxscxdUWiPfvwA43bGaSvFW75Qaury/MPGzPg0pM3qd0s/aTc4IsCSmgSFw6vMDKKOG0hz8YMnKHaYybZ0BIT1n5G6hSw50BuZ19/26tu3/dKRpBOCP4Awr1dJSUBSIR97/qzLgqKw6YHoh0T4QhqfDWuhihlJvlkum1/GJ6HWtLk7fI8FcRCrUa6jvef9/7sra8nRPj/UB/13fxqjAyV75hZnDmPgvh/G80TtjJZFtnkqpSEip0SKnjk6078eRe8sedwui/v/Av/j79d7snfbe7PeUrrsQfX5LuM3QZwW6mHQpgwLMrr6lU/uXCcvWL5uYC1lhf+eZdiI2w7lXOJbogDd2ODIukfiI9yFvp5Tiq6S6DdhuOAwP+2o148zPsfseLwBpGHvfc1HYuecVHuelJp6CVWSRfbDcqN05Oh+TymFy+98AIwFqHhPLw3H4fvxig3s2/u9rtEeIE68UJ29W//RKVLbYUsfTpOzHHzB4221X/+dCGmU8Mfbre19CJGx+ieYirqPiIRr3JdufCrxkjO8F7DhxaQ3LjQs/mhoJz8bxHg+P7zv5jGu4yEhPfQsWjM4ZiHLFscpJzgoA3Du1lqX8NsfZjNMPRiaW/XriwNrXw4hD3HrAJM0JSSnjVtIND+6Jpma+3G74bF1+0fxejT3z+LT5Www99OHO//CX+xNLGtarpqTzt6zn7d2B/6LjrmHn93eLSF414Nyue9xYGDv1AxIAf61Cpqk+PlC4itGtcl2TTvnQEc8A0MgNyIunQHRdjBwbBbmD3W5+H19fPwIPP6kh4lq5l5F9fzOTn3kyw7jjanq9NcWgKaLMzA32II3lvvRxeZp2BeW10eN5cYHCGOIawv04+tJi/temoArGSN9A/ppgfTqBfWf433yZnhZi4NF9xP5Yl9UQVGFwV6vtFCsuVOOpJwP0xijjfc+bdInrQNp2kXLDrQjEOEwV7Ck75w8yx/H5uJctys6Mq5kSBGYUpgdVArKp/ABkDdojgGuO/WlUvs1wbtdXJ9M1eGDUhO+sF/t+eU3hT7mesGNgLUuy5qRNDufci9uG7Jqv3rEeKbfW3JvksSRj/JaLkdpSVGBnmohBvcIji7e52iw9b/13vhj8+hkYhYrxOKTKZkvYAMLTRyleQ/NH7awv3mYoWflSQQychvIWpQ7/zq4HAkzepJrLvRYZKpKOozJy+zMCJTusLXTMFk9FV1rpYXYztH8CsPoqdr/l3vGVrKJ7a6UscetyLmPvJeWh1vhllZQsDidmFzaDZsw5r7ddC7XuxDOgtZnMMEIlQqyp5O4X1ImIXwC1vq7pZYbErGuLAUCsUCFxEJS4Qb/3b2zabsiOYr2EHuDAOzGoJ4g4jrWACqO0Hrx+8vkbrZyfFkFidvj/n+a9wTkcOKfNM8ljZDNGB88yNxo+5anop5TiHLwsnqdh3qTIKLFXhCkFmRPVxGN0sav4YhZzh+YZSyaNed6P10G1B7AtQ86Gum5jCMqkxWZrg7duPYfKLX2OfK2HFZcvwGAxBjoe/4wFLdo0VbGEudB1jv4R1caqRWdPTd1p3eEHRegVvyXKClRtu8XHLrVtPsHo14a7dSP9gj8ZoOlbKGf93AerqyOe8D3jV+WPLLj5k6xnPhVOHeM478q6ULxRLz3TWpUNp0pIRUtGVLloJbPMP2egrG7X1qES2c/gmaMV9A+z/2Ospvv8HtEIZUxokt+ZYKpf9ClsspB0YMqBIwyaGqD83NVn2Hu0MGCO3kr1x05soN4hnQrw+xZ+t/dVAIe738K5fwJuPIFdvuLYqHA7zW8PRgHiV/z+mFp/Q6KSX7lDDQW2/qNenDXt2B8YDqdXfqbG5uxa9TT3DqAPxWFnWHTCexcnsTWrnWNG/Fdl9ElFkf2o9PS0O5Tm+p++Ind5eRWKL+qJMKnJ67OT0sOIuDEOH7/N6UJuNrlKghWFJvMCW0ih7SobC3D4qLuiOhoE4lNm4MvJwyRW+j3FpANAEAZ8sFibyxFYEBg23XglyiO/f8jM6yGEKeYij1PDXFpeVsp9RMtsHsXMUvdwxvgnuNh/WfxEc4rnonRgffAhFYycZ5vr2PWPeVjxxtj15gx6clXRxVOm0MDUyy2Tfo90SCRJWxkkQa1UoXIy/Yh21qy+iesXvyJ9weuckHBiFqJrhyLQrFVQDpt9S+s72h+auq8atpupbNfsR08ir1+WYv984phzdtoakIrh+n77LJhn84W7MfMThtpTvOLh+5ti1z3a7XXPgqHQi6dYxzEE0i4RzqD+AaA1c6P0EL/6y9fmZS0yBXrRQnvXfT7SnqGsMDdfIkd8dXi0YTo1uYpPsYq/p1/5aFVWZw9dZcc0RF4YiyM+NMScFufh9Rtwdw9CtCEN9tOeb36Ju5ECHdq5cRhaWcd7Hj2Pirr8/IBET7b/mB/s/s/Z/44uGnm2Hw44djSaqhq19lwpA0wS88Sxaq+BqVUxp4JYF7dUycXkBAj997WejLHqMHmtefmEcMTEw9Lgc/i8wh1ZL92YG/UO40MDGHlFsnoFJ8ARZYLEGDWtoHGH6iz1M9zJVRNMjulpEz0VXutmaINIEMSMQh9S3XZsCLG9iRcMSWSQT+aUjQ2OhHskHfeFXxXHSquhbk7ARqHpCGMlt6nIgCCaGUrVOMCSUH7+cw2lyj1NHbOxA7OV+bHaHzZtzolcuOb2oeYxqe0S8AuBFWyXimaqFT6jUiqlIv5czbasNJwKNGnI7jRCNG0MTNEQjz4q/p35F8fsL++IYBr0ZjjthKxf035HCQg3PJ+h8Snsi86Y4DJ+E8C6xuYlcXt4bxvHH1HEjsPFA39+3ggtqfOanR/P8HduJsMQHIN7MZOE5XsGd7oyeIi5DrGf9qVrbKB2bGZwguTzRnm3Ut16HNzJxi45f7frN1LdvwRsezxj+deYZCs3oTjL+YwlOz4l78JLyruebQ9SDemXZckhl2ShXX+bF9kQv9JsTbHtFVNrcyJjaDTeQX7sKvEa/VVdKh/Z8f8/oCjIzAjsmZ22Oqymwc3PpSNz2D9OS3PZMBU1z0o2z87pgXjD1gA1IcNvmShI68tMh4oS606Zp260QajVvZXGlRrlcZYE63rzD5bymz9FhUKlUh3qWUqmI5/k/iSNdI/UINQ1OptWu0VZvt2Yj+BDNQbwgSP/CQ7yc/3wRczenkui9AxehxA1wcpGgYWPspEagoeDCRn97sokYECkKUUV+Pbm6n8DEhAywNTdKKaqyuIusjgG/s568Joq4MgzDBTU81lp53cEOpwL9g2V2Xj/C9JXrGQhCFmTxuZXeYIisCO9dq+okc3GDp2of8pblcaJqmJESiCjqeWhlnoVffYfiyXe+RYdx5vvfxs1MI0uWQ6Tdsw2z1cLkNrVsm1Gc8dYX1T1iuDZzgR7CDdxbtvMQdFhqqBT9p08N1ZohaA8eyrNUrr6Kiac+kSUveC473/ku9n/h85i+Evl1qxu8kqRB6pCiK3rzXyaZ6CeBzqXvU1qvppEvtd0Nf3crkDflx3sGx8IcunDbX7fiC1jHfAzqfFQ67Td/cdQSRXieYfC4VfQFecKw3tSwHy6LIkFAdXKW2g2732Z9d1rHDrNp7Ks9gIuGQ4BYQ2U6fmU+zOXq5F8Qq4NYVOOGh5M2gUpjEXWNAUzZiybZCpa8PLwwYn7Y+938+DADps6M5tm3d4BcpY4zYBQf6EtEMaKqOcRsdI6fWC8eiUP3BReDWG8D6O6D7Y18FLNnrJ9918yzZMsW7EFdS2Wqujx37szagX+TctykahKpoZLWaml6KCuqeEtXMff9LzJ61ksw/UN/8ZGc/t438ZcsbVYEuyuBHVlDwiueLgkcospkYfhFUawXHIq9uLdr2dxBUwvXaHx4phfZNGAlQCauVkCUJc97FoVjjmH9xz7KyCMezp5PfJS5X/4c8Q25tasRL9ewZOuhu+o190x6jt6SnsAmAlJIizTj6d1gTVdVsJ3qYghy1e/4/du/Kehfd8iNCIMCC+UhovJEyt//LwED1Ri1HnUHtloncJlGrsMhFSSipPZ0xXt5FDcmv2gzfem42DVaStrAZQR1gqm6ikTRBeVy6RvEbSNx6eZGQQwiN6PFIMIQoD8bdjUGdYE94RDzWqIQ1DEqWEMsIlUR06k3iVRV3XhYs9dZT+4J/sXGhICrAeEhlGKoepZLKxMcu3MHTuwBj79TwRTyzx1Yn3vCnCxI6kacnPScbMVxCStlwPQPUrvqz+z/1FsZf/5b/zKw+vZXqVx9Gfn1x3Sq3cnIisyYe6V3O56C5yJia+6xvbhsaRjZXQcTOHheXDhw3CrgiXtIrPHKLFh1zhYl3LmDNe94M4Xjj2+/fejMMxk680xmfvh9pi74MtPfPB/nQvLr1zVsXNT15raEA3JfJpU+JqI2a/AnVqVPxP3bMUG+J9FujILT/fsq+cfG4fpbbRTRzSSbEFVsTchZIfcXpIaRtSgBE8tGIY6oXzXXVm4fNos03Skqc1Ti6EtBf7HZgNw4uTWrtdMWCDdcKWxJyK2cuVu0Pf9EJ7JevFvv6zk1eCbc1edNXSNAIFVGJc9wcZ6QIkWqOPi/2PEj1cYkJ6c6K0bvrmp2NweN/kykZbanr3AxcRwrckBLHBjYFfGLO68kfOwllN3BGlWEWm129tjZuYfdPpr4Rn2+BrZl+ywJvqipzSIRbLUceZ0jWHMU0+d/jNyxd2Dgvo++Wftq9offYusrX0SwdFnTP07T0pBe5Hsrsm35cSW5xWYHZ1HrL69a858Hm63jVQvhgfeoKl5VXpbqU0yClQGNIkyhQN+dentrDd73/gze9/7MPf6J7P30h5n98XcxOY/cqjVNIHFdVEyKu0pxXB3yvANigpvbh798HfmT7poIryKi3Tdhiv2ZqE3bMoay5axKxV+wYXDr8Eh/yZWMUjcKgeDHjUbqQ6bWPSU3P43MKtbOJrOrw2tRqHtFtDRwru/KK12sieEJtEvzItIZk970NzfOw/Nrr9dx/hTtLFxEFKO2wV1xa1RzxWHw/2x0HFQJEVbZOc7wdvLV+nGssWVQmVWV2cS+dcCN6XO2ZU/AnkPcJcwO5rnDpVs46Vc7Cd3ByjBK1QjDof/N/EjuK9UNA4+WStitFpXMh5CUOTgolLB9A+x45ROpb72Osaf81yHtpr0ffx873/IqzOAI3sgorhaSGsCanF8YZ8h3DiAkjQXJ8eyV1V0vDVzkDqR+9EztwJhuhHGL3MNJd2WwjaaBjz82wnVPfApDD3kAS577bPIbj+paV/9d7k7/Xe7O7E++x673v5XKH3+DHR7FX7oErKUzM5uuUmnqcSaNFGuJdm5j6LEvwBQ75drKJT8lvPES/BUbuniLphr3Einpd0b7qodDAELNGHTOw9eOV96BCiGxbylWq5QmFxAXgolvo+rmrcBdRUpcGnr0XJR7gq/SJXtRTZgnucaFp6pYawhduHVuZ+V1A3tGfqPF5qATbbosOEk10P9Fi7X45emvmwUF29I01TjFXsp3chuJ1DYq3rfBUvcjjovmOHnvAGjukFBOjWPvWPXp5PofRbmZTbfTwSYBr82RFpKROSjgIuzQCDk/x94PvYbyRT9l9KyXUDr9vj0/snLJ79nzkXcy/a0LCFZtwJQG0DBMTdbphbRJIr6dTdBjJJgoRmxhei44s7oQf90eIMqSP6w+7YA7x4h9kXjmPYs6ira8231DXF6gduP1+BOjLH3BfzD+pKfgjY0tuvr9X/gEk1/6NLVrLgXPEKxcg7ENqJbEVOa2pbJI4zPbY74U8X3C7VfjL1vNqv/9PZLrpLh73vIEyr/9Gv7KDe1WoXZ0ZR0h/uNj9b8gh8t0ZgGJPTRSjDdHyasQZv2wtOGFLyLUZwUTWpwNGh3yh+niFKxl3Nr6To1jm4xysxq9ZEEEEawv1G/cP1Hrl3sMLRn5UhhGTdlCMt3Szs3o5kZcquAH+N7ssGhtuvXngJiqBLxx/vHMhz5FUyeKBRdL8/zscGvOmaZW2aJaw9hG7edgKWGMIaoLT17xGzYO72V/1Hfo212NGNwdvcZ33utda7pNy6qYjKVylztpSwlvUBXqWzeD8SmceDr5jSc03EsQwu03Ubn6MsqXXISbL+OvXNe4+CPXtlluDc1oDaFof16cqPTGae94F9Pl+24F5p3/jbm6PuxASaH8ef0dDkYMX++UdZ27EQd0FDWeIZqbIdqzk9yG9fTd6Y6MP/UZlE653aKoOPez77Pv3A8x/6sfYYs5ghVrMZ5p1KcTgNVyKWx9pgQB4Y5rMfkcK9//C/yVnaiu8ofvsetVDyZYd2zjjpwYJGGMYgy/mK97d4+dIKKHUSzS2Ma8Kn35GUKXJ4qC9kyl2BPQOsP7d0I1agjD5TDjq5LfJobIH7AzfWuuNoQbJJnWJ4sqvarDTukfKrw5rFRfreriepMHSxd9MgUZNNNgrwfc11jDUKX+Sb8cP9VZkyLERWt80dyFC3InscxNNowXb0XAmnU+a4Iyryr+Hl9jang3A2cF46mncVidmq9ZIyZt9tfLBz4xuKJtaayCw0AYEu3fSzw/j0ZNwy4xkO/DDo0ifq7xd02Dk8sCVmv9cQJA4zRgatw9XUdpWMcJ3lqHuWmx4yaXr7vjoimKgztFwoVdFsgtcZ5NA5Y0R3lhBbHSGOW1azveYD/jT306E09/XqO7e7ED+P3zmfryJ1j4/U8RK3jLVmALxXakZVqVPmvA1andeAXe6Dgr3v09chtOTp2K2559MvHkdrwlK9vhvLT6Fj1HFOpGHJuN6GEZlUTOkPMrOOsjkd8gcz3Fao54so6tVRvSiMN6aThvusB7SeT1v1Ndc7qRSZKUmuaA2kAkqMa1vRHDS/r8H4oL79zGuh5avvasSA4duAxC7Al7ZqeOi+eqV1ov+Sqh31W5pLCBL+UfzVg8T9QUmt5agDVnB1hduZ5HTH27abV8846nKph88XUrloy/1oVRO+LpRFmkPNzTQNV5rKkxaHRN4km6h7bfk4imNE6sMyYDXtJz0k7P6ToCJvaeaGJz7mLpvVzSY8xX+wZn5A3OyKsSR7iHBbJ2JuOY7GQckMDiygvUtmwmv34do495AuNPex52YGjxA/nDC5j+9uepXn0x8f4dgGJyOcRKQwUYlTGlPvInnsHEC9+Lv2pT6v2Tn3wF0194C/lNpzRen5wr6Dss5staM4+VwxSsktUrYyEOItyswVUUb2+Aqfs433C4L7YvRPtkqOa7nbgwL9JxxezwkpIGrubz1ggVF97n2unqjcePljZb2xn2mZa5pHWBYrqfS7WCJbdPDdjaJZcWZk+uWoOXoanyJqQW+Xx3+nHMu36sq9+qgCV9o8Q3/pF9v76AGpabJWqRBqgcs6zfvuG+a+dnynE+bs0wTFkmk0oNW49TgyOQNoCkACs7y9BlhmO4zBxDTaSm2Yk6mYira4YhIBiU+ltiV31Fesx64mv/dukpixB7EOT9rxljHtYekGo7nIMkZw1mHyfTRdNg7sUX4plJwu03UjzhZCae9SL67nof/IllixOS22+getnvqF7zZ9z0nkbLTxDgr95I4eS7UjixW61bvvAb7HrdwwlWbUKCIHECK7F1lIwwvHtiJZXidrzo8L7iFYg9XG6OPXfaicaK1h14ethvtxhHfINHvL/v51KQu6V4phQJ26omaYKAt+S8+o/xqvexZvA39Sg8XVOvz7htkAGuBFeZnFmYBS5jlIUw9wgb5r7mGdcFFxalqFU+rKfzJ1nFcLzQtGu+5YAlwHbt5yn2jzzcv4xJhv6yXR3HRM4911n7wSQ3lOKy3CKTdlwnfUyNBssMaE2NBXMZb/xWVJcErJgMeEkXeKUAK27XvImJrxWrm/LW9jRNlJ8c1z2X0BnwIi0O1tiDUGr3ACWHoy6WDibAKqUqto0DLL4l2rebeG6SYNlyBu77EMaf8RL88WW3+DopX/hN9rzt3zClAezQKBB3buBGCQLYX9U3zezxXhWYmMN9urwC1TmfjUsmGXvZFhho7vfDHK+wQAjb/vfYD9T/0P88O1rr2RuYBi9tW/h6XkgkdbHkXyYqb3fZdio63BUmMYotk2YmwauboFck8C4rkz8xjCymR5eBIiwxM/wsPprPVU9jNJ6jYVx5ywGr4oS87/Puvt9QkEmgcAtCcVBx126dq2+0SGYwRXMQRTLq0u7xXJqcW5hN50gQ5ckRZK4DZtok2dP8FR1gTEVe0jMtVCBvfPbXpu+6pz77q6AXYP32pDO6D5QBP+QRXjk+3wkHqA5qJpJK+F7Z3tNwGs8JiEPLc9S2Xk/+qGMYe/wzGXzov/7FTZlT576RqfPejB0Yxh9ZgmrY6c4XRYySM8ZtiV3fbokqgRzuV31jX9XqBi8SJmqCFx3+24wq1SBghMpdTaXvF7GfaeVKRD+9gMsh9Hm1h8XWfaMa5xvzaUwC5BYBri4vNQ4CXCLkwsoT/Tg61xi3qHYtT52djPBmeRhhLHjO3SqAFQclzNR2Ttr6PQQluoUtVMv7vEfdd+PQVxZCbRLhLdBqilkz0587EZG0U0CXGmGf5rE09d5ECtjq4cymha1J0fEB0kJNp44AgfHYV51992S08JKgRylJrhy/XU/EDgvmY3HePK3xLdIRU1d10DdQqzRGb/cV2zbG7UhLtOEekfBxF9MZvxVP7SXet4Pc+k303/0BDNz/URROOv3QoqqLfsDUuW+gdtkv8VduxBT7Oh7uJlkZBEP4WxPXTvca99fDPryyC47wKI/NDxynXPPTJm+H6RIaj/yOOqMfZ08hXx6PA+ki1LN21ElpQ2R0ygvzIwVrvlEJ5s8UNenIyfSoJnYBl6blEingahZwjNDP/JBx4cyBTgWnQt6r8e7oYfyhuophLd9iwBJgxo5zcvQTRqd/TDkKbtngD4GJos99x9fdWKuYNQ7XxWWlfneZAazJKCs7MboJWtlp0VnyvSdg6SKj7V0nEstWC2N19Pm5a40tbKr3uIt4vYyyPWIbeYWHuybeHHA0lzVovYbWa2Ah2r8ff8lEY8zJYjYxiR+NQryhYbzREdzsNPs/936mv30uheNOpXDC7QlWrsMbHEXyBTCChnVcZZZ473Yql/6C6p9/ikhMsPFkWu36KfuYZqogAmUTvDY0lsN/8FZjJxelypbJpWz7xXKKVP8utrrs5dm0f+uLRnJT47OmhHGuKR5sKtMT+qt2KwngVPE9cKF5ZEXKdzYBZ0pkwWh7VBU05Whth4LOuaXZIalZG6IkbqkQE31ul+RmMHkOxA2IOEac0u/mcWJvlXTcAdX6AnfvC7nTsjWg/bd4nZXIMTkdf8AK78gOfehYKdPdrpMsWCT+lvJiT+whTYp02y6nLZFo4m/ZJmh6CUa7uzI8YyCOjvr57J7jpyK9XDOZkFw3sKGrVFovFe5THRj4oYmiA1QHQTxBozqVyy5h/cc/Qum007j2Xx6NqyyQ37gejcJ0OphJEVMEfuvk8wSiOvHsflx5FmMFCXzEmiaBrg2CJA4xxRJ2bCnG99sQLYl1t6IrwZHPB3+a7e8/LVaDHOYkkCiQUxZmI/TymFw1bLarHM5Q25hiHcTxRuuZa8OBRtu8IN3VvB5poYpQMNF56qIn1E2uJpYAbQqIk420bXaJrqb5nlGXpCUTKhCIYKE/FuYPRUowwjRfdbfnc/FprIyncegtirDK+AxR4QXulxRdTOVm6K8OeN54uiznxTtSaVzCblozaVyXkLQn+Z6OphZLC3uKSBfRZCVFpOq600JVhzX+G4p+6TUt6/I2oHmD3d38TvQB2mN4ZVeDchxTufIKVrzmlYw/5akAHPXl89n8lCcS7t2LNzpM1uPqQFVaNEYrdRCHHRrGG51oNCi3Jji37UE05cDQaKLWHturzS9p2TU//cbq1F78vwPuygE258jtXUoY54mLHof/RgvWKOLr1yIH4poA0ewjawNXwkamI/xUcoGHTskTqr65m+3TQF1Li9UCcU3IIJqnsTaipRZoJSaydz6jHdU1bufGCBHR++qRzh/qrWsOyyazhWHZRJmAHLVbdOuYJ8fJ8U6GojL76LuVbhcgkezMFeJvGk/PdLGkfagSoWeXX1XCPEuQznOJ6161e45jspsqNZX+QNe7LGJiLckoyyPW8H6XTV//mjg1QAK8XaVlqfeogB9xPz8McaZ7Ze2tNcLCxX9kyXOfxcrXvb79VPGEkzjmm9/jynufgauUscV8l0Np8k5oAp9ocg9uZh+mWMAMDmN8Hzc3jdbKENWwwyP4o+NoC5gSrqeC9E49W7WCxhk8U4vjr1aimPBwly85JSoqfjRO0VtFbkyb9j6Hc3AlmGqdsDr9sjhyx6mYxlSzhJIzBVyJ/ExVUGOZ3zf9sr68wRYGv6ZRrdlL2BHFtz2VUsDV6vuT9qh67XGOqTZ660QgtgHTLvfqulEOvZupyDKmOVl2c35wGsvdfsbiGfgLYnWhMbNybXE3pdwCs/GtM2ZXgNhAPeKtubo9s5OGtfyxOmlhV0om2b9lItpsI0Ub7DIpoCT+xgHSwh5TtJJpbONmZO7oEw9golmTSPe9/Fw9gdKKIKsJvFOczXIBnbuXWENt87WMPu4xrPvgB1I7buGPF7HrnHdhh4aa1UAy/WIdiBZrqV5zGfljTmTkOWcTrNmIv3QlEuSIp/YS7dtJuPNG5n7wBapXXkiwZhOmUGi4tCWrlx1yosv6WIxSj90bJgoT+CXaNYTDdYkLlsJMmaHvL0DlUvBi/h70F1Obxk7fsyR4uy3HTccF7YyGbwGMtqvqjTYrbRZEnL1splx/Z75o3qTosDqT4KY0MeyUzomkzZuV0Ta4kaJX0sS7IqjxKFWmXthXi2bFHLr7mCKUtMYTzU85lSvYa0f4hncqfdRv9q6qqmWMBW6fWyD0RimZ4NakPRErv4qi+p80jk8VksZ5mgKUlJ0x3SPB0s6lnckXXW6mKa6MxU03k5Fc4rN7gWasDhFjThha+ZC8ynnJ27X8fsOdUmGlB88yqv/bPpQ95AyuPA9xyElXXYLJ5zu5+aV/5soH3BvRiMJxx6LE7VJysmLYEJ1a6tddRt/p92Dl2z6JN7Z4y44rzzH5mbcw9X/vxR9fih0capAEkubC0r83DpDnaaTiSk6py2HPtgv0C/OXVHG/m8EWDlf3hSTnqQTWJ1o7cV04WNxg6vGittfpIbvNeMsa5suTy4ul4n7f76u5sJJu3ZEebTuZu3N7BmYv1XxTEuECixe6a5Z+98qjpRbxF6kIWhRq3uO5938BUTEgV6vfLA5rl+vn3rnreV7fhczF/Z3eyFspynJGiMLoMZUKX2pZ93TU7i2/riT31K1814xolIzEYdFWnaSItMV3xb15rJQANe6lehdU3efiuH5W0jXVMylTeAXlLotyTM2qYO2mLSx59tNSYBXPzrL5Sf+G8T3yG49ujn/XrjSt0YPoUb3mEgbufj/WfPjrBz0QptjP2LPfTLDuWPa88xmYXIDk8gn/W+3myqR5ApnyF4PcbF3+HiRMovgxeMEJlE8fwgbh4b/NGObi6fu66tyGoBI1ourUIIl2XtapQDXTuFhi+qTv+lDmdoa1ynd9KaJqmkG4pgpBrQlJJEhYSVPwzXRR0ulis7ndRlCt6DuvO34tYv7yal+OOhGWQcrscbmb/X4HbLBTSBwSu+jWT/Yb++p8sSwQU9LsBOb0nIrOGLBsWig9Kq7ag4NKzh3riqg48CRoeqSG0iHCVPTO5aCcau736mHc3i4rEFh7N4f2DvEADUNsscD4U/49tZ/2fe7TVK64gr4z7tSoDi4ykl6spb79RvLHnMSaD331Zh2LgQecRTy1k8mPv7o5zZluh9IE2W5MjAsHPleu9x/+UgYFW4xZmHXILOT9uUZ3/GG9yQ0QKcTV88pOcWIaA7JNgntaDLgErLFMT80/qJgfOsoU4weE9XojHWnyXykZROs81g55r7IIcCWuLFVF1aO/oLWJvu3nRcsVkb+cyLRNHnW0vMBN9QmKHPp8SSfCoNQZWZhhYd5n5jY7KzXOefY88fTptOzw2mlYNjUjNXpr0bQwyzUlATKNMx1yP+tE2pMP7/1YxWHVrC/O9k041zFE9G6canjl14FhP3fi+uHxtUqc1kckDPzrN21j5GEPpnS7jo9W7aYb2XXOeygcc0xnCMQi5eZ4fhbJ51n3sa+D7a5+zf3kAmrXXIwdHKF4u3uQO+rk1PPD//py5n/8BdzsXuzgSCq6Su0LC0ToQGXul6IHG655GCSDHqjnuOGmQaYnJ/Fyhzd3pQomNhRK7nzPs2Oe2AY4NG0+lMS4rdQ10rD0VasMRvmP1mvT10x65WtGon4U06k4tdaVGBOVlCakBoVKhg9O6LuMFcQp121deJTkg3n/lhnno0CJOsafRQa9Q3BuT1QHNcdSM8uxC1MEkbBUwtsIrkBL4btmcv7T1XV7E7fJd7JkuGZCq8QLWsMuWKRaKN0BV1fUlB0DlphD2QWKTftk39q1jnhP6zzy1i0f7TDwsT0+jlz3ByZ+ceUFhh70gNQO2vvxj1HfupXSHW/fEG724hoA8TyqWzYz8YwX440uSZPNM5Nsf8VZLFz4fcQKYsAUCgw98hmMv+Cdqdf23fOxTH3q1c1+Qe0GVoF4zjJ2VPXH/p22zXdGzx7GS06Z+fkg8f+tYmQ0RCqHe3VAYLT2EPz4EcQGl3JgSFA+mgEuwGHIhzHVuPrMoYm+YwuiR9XrUWMgRcuPPJECdq61TjWwLT5tVR2b0ViCR0YB3wj1evyDy/40/e2ZukfuVvjqJTVw7CSl5cD0ofJLyhx57lq+noHcPvYFJcxtNSCkMR37KlfJXSw5PaXVBpONkjoqk0TYlFSItniU7hz8wERaj3+73El7DFrOikhVY6QvOA5T+J1rSZSWz57UfE9A1ds+OhPswLZy8+x3COsESyco3b7TzuPqNWZ+/CNya9agcZS2Is5sYDwzhT++lPGn/Wd6w8I6Nz3nwVQv/x35Y09puI4aRSvz7P/ou8gfezv67/f49usLp96LmS8NoC5qvDYrKhTIFwy/37z/vPJM/bAnrlFH33DAup+vYaCex1QOb2mrUajWIxsN8lkx0ogymoMkQNN+gs0TURNeViVibrDFF2PqrK9E59etdKbmNKMjJQFcKf1WN3B1eJrOe9veWbFlK3v/feldYa0Jb5X92h/HFIL9XDlfa5jfHUo66AQ/cpx2txuIhxLj5W+78g3BNbU/1a8pnCKFOAEKB0gLpTNWXhLdAynlOr2pnlYKr5BKBVPyBnpUFbVHNNa6J1qffL16x6HK7k+1jr9n42ua22+IDJs0ORstCzhzsxQ2bSR/VGeo7fxvf0vt+uvIrV2TDcbSqzCG+vYbWPHKd3ZVBPd94h1ULv4txVPugLqoOTtcMcUSwepxpr50Dn33fBTiN4A0WHk0/tI1xAvTUOrvSgcFJd8XuWt3Dp+/9ZrBw3QqQ+suIviDBu/yWeKbdpIf2Ea0+zCGK1VCz6M4Nv6aYKEw7BXrjZuUdu7MXcAlHeBCwcTeruqS+ffWgvKZZvvw0SKNql07zTCLAFfi9t4TuGjyZ83oQB2UlZcWvbFdhSa03Bp7Nm9j1kiekiuzIAHBIURKc1JkjezltH2XoZWQ4b/CObkwN/brqtf/lM5M6c4BySrIOxxUgp1PkVDpfG9R8admXiu9MWHRaCv1cUpNOKOQ7yff5B29bfE+mscZ3x+4u0fQqS1mQCuemyN/1IamvqG5U37/O1ylAtYkNFaaATsh3L2d4om3Z+wpL0xtVPni37D3w28kf8wJqIvSbRzqsCNLCbdeTe3qP5A/oeF/ZfqHMcMTRFM7ofT/2fvqeFmuKuu1zzlV1Xbd3n3uElciRLCE4BZgsDAzZIDg7jqBYQYngzsEMoR8OIEgERJCQhKIe/JcrltryTn7+6Otqrr7vvsk7zXzff37vV9u+nZ31e06tc7ea6+9dqbhPJkIc673q5ceZU1ZlmprwAIM0GXhnq5VuGdxEY4dtC1zRQBKykJ6T7G7K+e/3+QYgRJQA6YSZSFizMchorcaXQlBGHPxsmVjEulkx6/mlIFgqg9OqgBNvWJF9SiN6vMLI8R7mKA31ftNQJC5A6XiZ7pZQtLBi1oFBBaZaSyX47jFWYN+nd9rOjgjkniOfSfU3QIlM/jYt4cZwErLy6wO74s6IKdx4CnFMCgEUBE+KhqJRcj1MI8VT+9aTMhp1IKFPiP2WsUGjkoc94N8dmBCl8YZgHKCIwCSMMLtNGL8KGIdItpCpDnK47xUX1/ke/F27YSwnQiv0JASCkIwOYolH/x0w/c6dskHQU4CIpEoR1exY4MIMBp6JjY5ScjozRA6TxIGtp/6+fRuqnfLtmllMNHpYctNGdz+0AA6OgCTb2doFUjYLhYnxr4jinnpkQbPEUQaEE6dbI8WiypxUT0KyksRXAOW73a9solepJqIEEsf6qypLqhaSlOLFKIEfditQSn9zkRGlzskDjJ0d+sijsEeXItNyJCHFLyWEKRJoCPIYy1Nwe0cxCxnDtH6MgV4+hqS4mnRNCyEPLHG8EhaiNYtNDVauIlUolUVsFnVsDK9qrHSWKOdDSR7xwvj/R4A1CLbAWDBU8GRE6QVG9lAltf/EIZIRGlLk80ClmpesgQAIeDv2YnUcaeg+9kvifxq7vc/Qe7ma5A6+gSw1jXla/UPCQNe2Z8m9Ag8kBCNCEAMhoHtJq4SfqI8+qpdAYCBjAKGH5XwHt6NDHRbc1fTIJgETk8szT/XleV1YjwgmBWwBgxY1wl2bqLrsaFgcsGLbIehbP5PP6hgSysZRJVIj+iGomqseLtPVXslCf+Hhfqja6zHZNzkDPdiTWkGm7AH05SGlqIlmzWDTmySj2Cg8AB2CAUfuUNTFwmAzo7UbxOy92lBpRgWrT9R85Quks5RTPLQiigPSRkITYerNm3xaRaFhT4xbwyene47ytb4PQRBbe19oOyhrdUJtu/AxO0cYjkmB1E7GrIssA4ax8yH7Gl1dhqDr/ta9GYt5DD2xY/CWrQEcV/u+oeUjXZEMgXVv7j+3vwMTG4GZNmNOTADUshbgsHsCDDb1oS7SkvsmPaxZXwHrKSPKdXGMgYAy+2U9HtXXz5rbIigQuRKwOQJ3EFAZZh3xI+qsnClsTAr8g84Q1O/6Qx6P5grOBAyqGb+jTKI2poONzCHI656mYtrmqGyK7hhe3MOzkvZf+xm486CoKDxxuLNuMcexk+7j4TF1ABaBEYxL3Hi+l0YHg4wicQhdQuhnPlFcJ+5hGVILxDSq3HTZuhQAasiQ0FoA2m4p6hFOBbuK2wIftBajRDe7ITAlF860SkVy/dMwqjyu7V1IqM1Yw+UB6b6Y9HULHnEkTDf/zZIyVq7TO31FZFo8qgT0PWUZ0XeN/6NT6L08N1IHXtyRQpR++pC2ylg3DxU/xLYKzbVg6vRbdBTuyEynY1EvxVAFnt/ak13A6J9PduJCelBiR/d9gAuyQNW2mlb6QXDwDIJvDO98itnUnJp1niRnZE9QOeIVS8Tm0qjciUyqnFX5aD4qflcJ1LK+TBYl002YqR8RAYR0efU072wIwCFel8MEwIj9dCqh0+ze0v+frT67TOn5wgPtrsdv5g5AgFbiHcGFmBhWXISq3dmsXvXchTIPrTXjrG9U+nbJczxGtSoNme0VKU3Te+4RaqIed7TaghzrFrYUI2spCFGyuP9jCqn+dKI8oAGYIOZZ4orCJCdHSg9/HDk6d4XnI89l3wG/u7dsJcurvBQ5VRQF/MIpiew4u0/jEZXbglzV10BZ/kacBBE/KsifxgR9NwkUsedAXJStV/7Ox+Cnpsq67DC6nYGiC2ogckbhJ4C2tihkxMa7lwCz7j3NDyVHFgctDFg2SikRldlE3v+bSRwIyBRXeB6liBSDFKoTW6uSQyIoIX/G2HR9nQp/X5fCElKV5wtucHgsRlwVTkN4lC/YKzNQxABGuclQWMWHyLtHSs4QsHYFrRHDcec4xQeJ+/DMhrDqOmC5EPbbmXAMALXShbHN2gNYsBUGymPKMXFTE3BLCIgBWIbSXM5Q1hN39JrhqKrj0FrAmF1MvGckn4GzEys3HVcMVxrlRLK7i7k77obhbvvQurosn7LWjSMlZ+7BI++4sUIpsZgLV4EmUmV3RZGJ7D04v9ExxnREdhzf/h5uT1n/RGhimILKzW/hOTxT4g85T56e6WbMrZ2bIL0OG/fMHIr0Mb6KwbQ5SIYXQKV60aHpQFPti+4UgLM4q2z0JBNzOZIAuyBzByx7CtHWVWpA4MgFWBLuqDDOIDExzQMyNQrJVU2KjJ3sBlwRbKVkDEgozzgROovZAL5x4evW4FSSUMeghQ7oX24JNB3yhQ2D66C9jQ6S/mynQ0YBsAqk0OHIOSIDrl5JINARvzZEL8tQro3ENRozbTvrVEk7sYw39uapoEh14gY8V551tZQ6zThb6ooZwDIdRLWAIFapoMAg5QFaI3R//4SVn29zkl1P/3Z2Pi76zB5xWUoPnAX9NQ40iedhr4XvQLdz3xhFH9Gd2H00++FNbg4qp+KmwMCYN+F7OpH6qRzQhyaD/eeP0N29zf88VIzjIM/Ti8a8rmNuStJDEOMrds7MHHkX2E7Ptp2crOQSM0GR6az1htsPwUTiVpD6b8CdJZIJJnJRq3YDCGgA/0zD+4kkPyGYS67p3JsQwzJF0DcHLhCZG91OIxkCSIDkP9Wryg/X5iR2LVrGvlsDpatDgkg2IGHs+euwomZTjywehU2b9iEdL4AFwr9lMdKMY4snIon2KG/hAx9LQnyCWTFGOqodXKTexFc4bHQRHqA5r2GEd8rYsQm3Daq3MMg1YrvZXOk4uBvqsgBbMJGC1aD/K3BEI8NnFUrMf7976P3hS9A1znn1l6bPukUpE86BRwECCbHYQ01H9u164OvRTA1huSmo8HaiwlMQ1+nlAh2bUXmjGdADS6v81fj2+HtfgSyo6sBXHVSILWlcLM9FbRvtY0BkTLwsmnYc0NYabw2bhkklJQPYvfHwhBpEZ8UFyrfifLsj2COYPXX9VCCGb17xt8wt6gj7Sf5QpiyUj7CoQhEo6uq+DQy5aaeYlRvGkUCgePvEmzOD6bMzdmsDRs4rrcvZTJpdZeQh8atkYmQKJXQOboTx0yN4dL+AWzv6cNM1sJTrIew1t6B3dwF2odG6YP8mFE68XciOoUj6BTL52KEe+uFyfOjS5PQqmmVkRs1Xa0arA1whCFA7ZrJYVEiszKTkdA6mPccyvYyElZ/P7a/8x046u+3l606wq9TqiVYjX/9k8hefxVSRx4HDrzWKlcC2C0CJkDPy94b+VXp7uthZsehevoadnqlgVxSj7qpEmS7ml8REHT4SP51EMMjyXLa0rbclUKxM/ui8f7gCE0KoqZiR1PgIsUwRYIuMJMDIiZA8x+L3R27yUpdAb/arEyRYRT1wRLRCDsCXDHVvJCAIPwqly8+O+N2QmUtW+rS25yE+ITjJI5OHcicv/16ZFCiAQzPTeOImVHcNrwSyUIBZ7n3QvklpGEfvlkChmEs3ObJ9Cnlzuhq0a+xga9mN7NQMGpF3LdKBVv2KbfGQCYDodUKpVNQy7p7kSC52td6/jfW5tcb2MuWwt36KB567rOw4rNfQGLtur1+Z1M//jZGPvchJFZviBKtzQ5FAqVH70ffv7wbzvoTovzXlV+H6OytQ3QITMnVQKpzlNKdbUtfSYehfGBbJ0Gb7ZDJtkSqShMyIyntL1vaqqQzXHP6bApclbvAFAjKZrAGjNIvKSZSSQQ4n0UFeMKtN9VqoohpDxmRGYJMtWEmOTBfoz36Rilrfm3Pda5hId8Q2OZFCaUWG988FBh9z+Hy5xgnB0dt24a1meXoShewPhjBlO6sTSA/LJdTEwzx31lRI+iEHRS4WUrYvHrXynB0XqVBQ/QUusjzcGflqUlmpe3OQhklwEasjdD985BkROXx2M7qNSj8/TY89IJnofPss9H/kpcjc9qZDV+Wt30zJi79Mqau+DbsJSsgkimw0U0/FyjrukqP3I7ME56J/tf+Z+SzZn/yObj33wxn3XEAx1pYNMAOc3rN3O0Z0b52MirJmLo7icB3YC/m9kwHKy6eiZz9deOJPo5JmRkcGSEV2UAlwK4AewZS8Tc8TRMkzOcEl72yOJQGUpjEQAi4BEfziLob5iMmT7/WLmZNQM9gQ5+UNjZ50EAAsG3BGh//QmYue9i+Vi0E+jwXb33kR6CNGew+axW8giibCx6mhwHB5uCvGV2EQajIsRdOvWUatxfZA+IY1PK11JhmUnPkY5g1lp9XdM+qx0ET7tTAMbW0rzYotTKCvjo0tTbNufyzsCR0Pgt/z07Irg6kTjgBqU1HwFmxCuy7KD10L/K3Xg9vz3Y4K9dAJhOA0fUp0SGfLSEAUhL+rkegFi3B8m/dApFIhbirHdj5b0dBdPVBpjMRYpYEAwFBpfUjyVMm10G1aY5lGKKDsP3yfuz6US+Si732g9XKupHMXU4gZ3jeF4aDKwrt6ICVBCgVKAiApAgguXFycy1qr3TPRKx264M3a/a8BpNsyCHijvK6rE9SYjYQluJ0V+cgESYOF0BUZQFyVqJrzXao9RMwRXHY9yVmsrNu77gxohPMUdtkw/VR9eHxX+HJz5GxX6iPn0fI4jg8sLWZZXJkXH30OtdGf+mQBbOOLjMmsVxpK5AcqEXUhEdrTgOFHCC1hkilkNywESbwULj7dhRuuQFsNIQEKOHAGuxHcv2R5ahK6+YRXGUghbf1fkACiz9+RQSsAGD8s68GBz5kpjsypLU+fcfABHT/zFWL2lbOwI6E7XlYesUIVvAeYHc7BlcMw4SdXctfP53qgqW9+VhN1N0Tquk5ledV5s3ni5q0041PWAGg/Yris7pQK77exgBkqPz/HF5dFJlhWdnY+kmGnBmqL9aAFgq2529fvHPHBElRO5/DFKDCuDbcQQ+zIgGpuQ2uKzyAx4jQiUjkxA15XaQheiHk+nz2MU1Eo02dSFtkdLVgXkj0zIwtUtqlYSExOC8ZhvlTFzamTLYPDEGIofqY+grvwFo3ikNDgEisobNzSJ/1LPS96v2wV26KvG76h/+Bwl+vgrPhuLrnVuw8mQAL4n4rFRqB3k5gxYDsCTB7n8BONQinj9vTRIIZCugKYH9EmmCBt2cIuMAgZngyeJcp2RbPiHf7JgDrsuAqatde97OKtHNVo/jQQq/dYDoqhahKJCzFgGd+8+iU2xZZtnaLSBoHltsN1z3MF7oCKo4wM83Ld2hdqeN4RE0LI+P3A+R5nt8RG8wmu5eqXGl2uKdjAEHN2rgZn9F4rkQtnqP9+DaZYbLTSB59KpxVR0Z+m7vmR5j6zodgr9gQzbsp/kUyGOIeIglux4kTRCAOkNuVxoxMwWnTpmyjAZvMZTZ8S7IGL/iC1vM7AesKo5WfJPd7xldkKk2xYWJ93mXKaE4AUxMX08qoMPYJE5O9/+6KRQd1Es3+AoRvSWzsmkIfFxCQddjDK2bAJ3+nB3MScTNjKg4Nrm1BRIVtHOLDJ2IAN++giqaWM4jMkuGGoEhDOqmlKpnuGdJGz78G97ZGaR9+3+y1UkINLsH4Je9CMLYdA28s29DM/e5SjH/qQqjBpSAnCTSxCamLCSXs/MQW8rz9G+H0WK+ZFIEfMRj8G2OxT8Bk+5FXDMJcd9/xo33DT4fP+wBWociCFDqp9PqkbTl7jH0BIaj0MFCLPXovvruMhvmYdX+sykxfAAz710rYI0LQ4eeLAgHbMRAyD69UROAf/sndFZPOzaREk9SNo5vEfOHPQiKpFvf7gkT0LZ0dCJr9RSoTeP1FOwkyZuFYRfM/t9cFEzb4q64+oWAvX4fpSz8De8VGiGQSY5+8ENbg4rKMwfi1+YitCGDPTu6EsNuy8pboCzB5bwdG3G44qQDtOMzZEGA55mvKuPt9U3RJ9dub5kbGewW+sq5jGXK+ARYiiWg2imVvwFV5ymMLTn703wfUloPmKnogj8AWSAU+uh9hBJsl7DaY3ssA9JLUNn9tBsjrEFjwPqR1e4+P91oZ3FdcqeGVAEzQr1TgDxgnA7m3O6hFNEXzLbIFRWHVln8DKBvO+mMw+c0PAcaDtXg1ZCoJ1n5IudqETyMCsZlgY3ZxOwpGBeDmJebmehD0dYG62nDeIBOkCJ7vU/FkERjs1/dIAjnf/ZflyX5Y0npt0S8hXqfeO3DtJeqKAZcggkFww5iNW2GctgiuS3mB4TWErjMETInawvG2IkXYSkUTnviIuriEWkc6CPnBt7haERLd7AMo0cJILQ2BBHv9aibZPWhrD6bBhL3xQ2lf6AzaRxAllEFLCMiuvjKxrmTF2A81rc48wd1OltJvRyJbpQzciSTc3QQnPYv2HOrKkIY/z2Aw7WucUparG5n7ths4oxmyL1JsaslgswiqmZZrn9LF2uQqhgz4Vb3oBag9uADXtdDTPwnVPwWa2pcb57HNCU2gtnkFatLR2CRva+Lg0NLwrxXYNBgGLiD1a7Xns4YnVa+yOejkA8mhaCFo1OJXrdI7KSt/aSx1bPanU5noJchRYWXQdvNmGJAdjPwjElxg2N2mnHu1HcmGYwywzKBiQkcLmelUSSWFhhM4WDyeevt4WiOXCi7WTZw/WgFRWBIxf7rIjZG14d/owDxczgXbI88WKoCRhMJkF/xs+xCqJHiShC6vP262A1ATMKGWhcUFc1p7TVYXxicRU0KxQQfJBmeIfSCk9islbfF6bvoB0cbo5op8QTQuhTmsiuJmD00MxwgsHU8jZREcu71mJDKAnUEJWaMvUSQQl+csBLgsYWHGzF6usnrGcnpeRIr72F8I+d0oiVgYz1URoRpGEOgPG2aQbo+qK2mAHcIuKwMxSTh8/c6xdcgES5iJpZ25aUncY8JDOaixNBdO4ufhgfYt49qbRQ3PD4gEKGUMuqVCY0Pj3vLLVtHVPkdctMCIufVfLKDgUXHM52lQm4lGO5OErWOMD9+xA0YLwG0vQC0Zg5d2LXrq6Ymes8e110h9LwS4AoCcxJuLi/eASX2ZdQcEgn1c0Y3AFdbURMfgEQQJZNOFax4u7L4tYEC2ydfqskFfVwJH9qyAzwLstEfUx0xQgkqCMA6g54DDi4X6ZtE+3coNKWU9mCZobRKKFHcYU3Nb28ua4gOPqGj/QGm+GMFIH6rUOSGKiyDaTN/UxQncuHMMP567F+36eH3P8m9T8wA9AlzVtC2+Fpj0/QmDUT+16pXMpk9qf/9I+4bUr+rVRJHmhUBo9CJVOmp8yTNOsta0lbGs9m3kM7uBoW2Qsxbap2Rd1oGU2J7RLOpDXnihdy7HDLEOmFJrcqe3/mxmBgmyFUDJVmnWQc33FpLG0sKORU2U+BI0pyDajsPyFTA3IfCCxDCE1V6KCwbw1EzvC09JdC5+0CtAUTT5phbvqU6vITYwSsHT6jVe3kUy7X5WgHDgldpWBD1BECGhbNw7sf3pWyfHS2ko4HAY47U46xIsLDvRgtPXAy9or2ifATiFICs8g4NaTacmadx8qeH+01xKEZDmQ3lFm514g2p9gehYtW7WCn4ylw9S06B2ggQi+JkAR3EXjkiuh9Wl0U4EFoNgiD/5iF+sgVUDOLUCLgY0Wcj4hR2i5N6QFT3PkqR7zUHnEOu3goGBJRwkWb3l3tmt194JXW6Ma5f0GkAmAbwt6ETvb3pQ9L32AiwC9IrObNCTAEp6/ivdpKdv3vxnweZ/+48bVOnASu/35rc/624fpBFRryVutvkCAjDEoEDmJcu2imCEwwjGbbi7HGjLha/bKfpjSCEvZBIr96Y2bw1cBIL3T9LyIR3+vDbU6Bx7kFYsA+hWCdw5ft8bRzz5xSfKM3FGoEFtpMB1IdDjaCwfHsNs0YUwTlsBFgEgi/KYZx1Wsz7GAd7ne0v/9i/ashWAxCGJqg5W2tgkLy8PoirXY9oJEsg28HYlEBSTkCnTdiYSPpsLmblJVDo/cFWhyiH/gdFC+i+B6FrS5+jV9dLewiURCwZ/ENzAf8tQKvHFpN2He8dklwt/tr0IAIY7KzG1uwMnnZdEbqz95CvSNyXyNXi/c7PHDgP2xmAzIBUAecAHpIOBRo0fty8BmSH2DbURR8Rly5vslI1i3oKdCNpFJgQww4ZeDUWnoNZGxqEJynsHLgHAD/Q7pRBICPpE1VZ3XyURC8iqIcDbAjbPyPm4ty+16oR1Dr3z09ZDb9/ilWbbaYciYmhPYiDIwM5mQKU2nItJCCCorkbfJ+J9H4MLPujgJhYGWAcLSR9DNJHMWhjTHqriyt8q8oCcKiHdo6GcNtEJEcGA4fuZz0KLhqb80CzgEHxFV54gCd8UHp3JTv26q3uwy1HmFYEX/eoPDLgqUhfmvDH4lDbioxA+bCFfP8v0xdTM6Mc+JfVuJOz2AgMDICWQOyaJyWwA2YZtFwRq0tm40BAhXv3iQ+09JxT+UR601wsBItE2G660GX5WobTHKg+gLraH4tkwQwh1HEn5nHpE2jhAM/5TdUFXGzvYTn48KYYgi/aXTTqoj5FvFjvPI4loFlKx0Q+wFpeB6acsTScgL1NCvcQIDWHJq6dp5QdnfL9dOnFqjyBnoXPVFLpSM1B5u13vJMY/8KM6p7f9H3sBfyYIfqzDuH0BLEdjZqeDnaVuKNu0xc1VbRHLEH0sweGKZdivvQpc1Z/rMRcB0CSR0CVkCt7l2VS3JaT/0rA70UIlEa3uHQLGGHwjyNhCym+C6FSIAJoFtLSxaOKhNyd8r9Lv2E4bKsPkJXi4F0EiWXFEaD8Oi+Y1X9ofCfuhXcIKgD4od8JCIsoFklJ7k2W1oGUsbhNRAzPg2wqJcRdr8qNtNYbek86imfTAMwKjI6q1OHcVjrjCYCekgCzQj82YV6BhfTEcAgJuuSRaSSJaRVzMpkcI9aryz3X9lW9p9OTTX1s+sfFeoNCGO6oE4GJ88SxKnoQS7TpskhVMixB4r3dP7HXzqZDMAWJJiwRBYSHdTnyAYLZ3gyzsP0PHVW6mbWJwIQBR0pid60LOdmAl2oO/YgBGqE+R0QBENbmLJH80z+UjEDjwsDuReHN6SUcyJYrvDPT8jWL7FnEBAKy4HzsBUJqvL6r8ax8c7oWhRFtBAAnAy0qkVxaQ6c5BTbpoSxdJALCFs/8Ny/v5xoO0WROgFQF5PlQHp33/O2m+41d25/KYO05rze3BuVuAzhI6t8wgZQiiwG0BV0XhrBzLLH65gB+yuAkbiEThpTKgvNbh1yUUbii5l83ozSPndy9996ibcER83NqCJBGtIi5ECXoug4FS4qGCmTx7OpGFzmw5XOlI621fM4wtcWK3j67bBUypLbNBMAGlNV2pUl8SVNSPSQjRygloIa9dwPE8hUMVX8ctVhlNihOhryk+cLHZV1h52jBDCZVStmwLszSZBubGGPeUFKTktmgXYiak7cxTLWIYQ2hGtEc79MMRV/lvmGFgWPa9//GlWbgF9wXaSpYn3tRS+PhF3begOzLEhQhSMEhTqZiXj/eRgARDob3qRARGsUAYWk9wziBMTRLadeg4AyBJGXJ1y0ii6e3DCwAUPnBg29uCYcBXIC7A0L5DKh/Omy9cQicYNhBCdEjLbrB6PhyLQiaBpBFwNSNptwf5mmUNA35Rdw1W4kR72H2y/pvqdxyIBLpLk3/psQpbR5auWmrNlE5Wxo+IIJpzj/secRmgbBNkE+Zcfnpp1J5QWFQ7Ujs9DAgSAt1DIwiQg4Zq0zFz5asjSXYu6FvcRyzgeCjX6jMP6PJRoIwnciJRHiu+9z+A9mvn3OuLKIxE+/7BgiQ83x1gt3DYdVgEoDurccW2NG6zu6DsoC3Sg5OFdeImO/Ok8aBUblyuwBSFGCqOOSBRXcQAqRmBpV6TT2YgfO8rCgYGMgZrjdbrDRaWC7i8ghiSzZ05P/HagvRuTlDQtpyQx4SeJQaDZzkwBQeqr03DKwJgGH6u2BPoirniPvX+0UEVmPI+njsJARP4niKBGeIW58J7WWHc5LVNU735Fml56izR/v+JBAaTHPBh7W2E4mP/EMB0IHDEWBaL/BmINvA+MSxgUssvzjKXzfwbgCVeE4yKSAUTfOXeMkeZe1TWWZoRhWdqRRWzxNaSCK6lk3tX0FefCSQjU8p8Fyz+RaSmMUAKtMxFuz68GaBzFUBdBJLUVlY3UZ6CgMAo5Lm3NTlMBx+FeF9uZWrEklBaJUi4SijOsY4B0EFutl7430tN+Z56ChNCyrCja7my3lfQAkR8+Mc8+YzFxQ4sKkmow5yiGgBWgvonEumnzWoNK/Y9MhDSWTUCV7URJ2enfmGMh15Z/FAgRGQgQXwQCTch7Rt5rsYOWQLNpUuFiwKmy7TlQJF8Y0Dyx6x4tF0BS2cE8tMuvDtHAAswQXtGWASGgezTMtNHQoDDevd5iXI68NSO9+01TTGuDFi+8iBnbQpaN0PuC3jFXrvXt1Y9ow8kx+Sy/asis6hbFg/7whCWAQLCrYljMNXTgYR9+CbkMAGBAVKi+IZeykLVBrhQA0HOoQ0jnM4ZEmBiHDG654tsfOzoXfKv0hhQjP1CkyiKEXZvoIqzFcV4LnaJeAc0XRWUxFeUtDabhP8xX8qLOoqBl8nt+npbltyqD58RqARGxAoIbQDdnkJyDYGk9Ps7RV4GulW61yyyOUi53f6+juuABUmuSpZmx7xUD0j7e033omT3PKDCrcGr8eVNXlgDMo6EhKDoDI/a112eWLt0ciIrdMDmcIqghWAwBEz3LNKOC2EdXsDKGGlnZvjtgUeAoLrTZGUeZJxn4piYlCBg6cIXJ0V+Tib6P0YsZJi1CksiOMKINVt/YVasBo5jxLSTgX5pm296VuI0gilzZkI8u5Tqctu2mYSAICfQOSxgJTSCrGhbaDUGEMTLdc05tMU9OK/UICxz2QtlBOzVxLjGQy3g+jrSwpxfnFGWEBMuMxZMIfE+BkGtkap+VxE3+X28jhSKQ+MYZwyEoOHOvo5FhrH7cC6aTBrYtgu46r5t0Hz4CLVqBHRksvefHt+xODMtXRBXQIW5MqeOalqCZpJRBkOwwXiy993TwmAp7Hc7zDCRtA9NYjUgruVChNFCCBxpGQPLSJaN2YkNGAwLPEeW+lXe6W3b4IoAsG/hgZXbIPu2gqRs23MtEWN5kFnW7fahUPPbp8jVblissXihpX3xvkRSPD+etPocwwaKxKSaSfZMJfwi4mHJvEDELYCn9ZyNxtfWbmZucdDKLReO6rj5IFUGIEggkMUlbuDvPpxdESw1SHfhXOsYyIQPOkxDCBnlfcC2gtfNVCY512xxufo1VoArtLtSjDX0OfHb/vyuwnJlzpmmbqXZbSKJaKBNQ+lis1QxnJZWTofrPKUkgqfxEzcgCArQrimhCQClGMNdBJPvAALRtoAVAEgaZ0VAJjaZPqZ9nBd49s7R1HDNHEBa2DTzNrCkmFB9+amRuUQHqJlZ08HMX/fH5KqRlW0JgNoYpFXH6oxDtx7OFCLVWcBEfhm25RcjE5QOG2AFLJFUXu8SZ/QUwwZ1WQC3Bq748wLgLvcDqTEPxrVfGXQCSjeTRDQHLo7s3dSyXzFabqlEfZb3VSYN5vblrwwInNEY4ASsiSS0bmOujQEkea0vNaDjnNVelFktgIwPFBdaRWdNoi/BBBJ6j/LJ2h2Z5cfz4E041eVGmqm1gn1vgXX4wM0iKzSkjVxLxal2w0mNI6WvD6u2UBQA29VYnJpApqd0GG8mgAL9NM2VER1N2pmrwMVMoXmO5S9WCED78jp/svj30cTiRULYL7ODQpmER1wS0Qhc8SHoHAIuoLWChsAIGHcaz7olKay29kLx5xQ61xbh9ZWQn5XtM2usxSMheEW1MyF6r8e4qTinHCekGFFx6HzpXei1HH9ti5+5xYr24OxSu9JypC/gGWJ0c8uIaAEI3NQPKbTPcmvpB9caYDkKens7j/DvExJ6tLhB33v4OvkJgEgalHZOIy87gNnDA1jlFBnozDj/Ug6TOKREj9f0uHLtQruuYcAGCjl8yAKQSPjvNAGFaINWkohmaqu4DLWeKsZZlPITBMP4qmaC0e178zPK1WnR58MkGFxqc5upsjdQV0Oqtjcuab9CpQP0hG+GeVKhb2Zkt0r7KBEwCqB7wSe0l34jYrSuHs4ne6gYGFJLMOSWKSYXGEFGbRw+RwLqMIXmAkCKwT9MgHeXIFP+YbqZDATLXubkk+sTlKOkdzPLvhpwCYIpct5h/wbZbYFZvFqDIZpGR60lEa2Aq1HIQ3WdHQPK0GWWaG+nOeMJOL0aw8tHoVjDOO2bDhIBMET5oK/fQMSmTDQiDXOTKKoVkuwNJ5pEUczNcaMVzlDlTYV054hyFIE1b4cRG0DccOLUFAJ54VAaq5hSPHXcZ06LmibTREA6KTf+cnOudyTnTx2W5UMA2YwlPIx1XRn4qeJhOQcwA8J+tacFuKIKDqdwHIKwaGdhKC1X5v94eQvIWRemkm6mTHVQ4yj7JpIIahrJtZY41P4rCPDxtyBv5g6TdnnB33GpSBjqM0igD8EeCdHG4EoGCKRebRzuJ+LGBmfe+x/MrSIxjjdMU3Mgmu9e5r1wZUSQRs8k3dJOpVUCkvUjwgTncDNNRBxcWv3cwoWBaa+BVh0cG9T2FL2Jwgga++zyhSB7Ltd3/PRscPXhaCkUFc/T7rkUMgR43uFyFmCwFG9g4livYNzfqglZzgwmAb9HvlMkDOyi98nqpODyxlyx8JlHEsGx+K0VzxWPwCQJWGR9LVCmveZLNsuuLAV/cDMm5QwC095O44YBB8mTFTIIak3D1JSmargurVK9+aIrXuhro6uxFn015dFpc0CqpFbs2YVsMrV1Kt0FpYMWiNIk5ZsPgFpFWfEXzuczQk1AjBAzT6o/tAFyrsHLj04O7/eE2QN92BooWLh2VydGiwIJZR0WbkUxThSCl0TbZxjx5BCR3r/yT5600O/mbli0bfP4o13Ln6xlVw9xffQ81XbUugC1uiZaMVjNea4oqAkhoX2/dP/01u/5YMg2BoDAALawcOIKjUAn4FvtzV8ZAErRsaohkgmNnm+WysVTuCbE+IKUEC2fowXxYtIYaCG2T6R7oSZSFowUW/d5wkfTM25CnM+fu7R4XQtfrDi5z3XepRwBKEx7cwOWVTwsTvVCMHxPwccS2EpAiEPLGtfkLyz+qV6ZiXJXFFOphxcOgSGZkefUB0rGQdL3PzrjCFjcWFmkGmVQIe0pVL4Nc1Itea4ocBEDvhDfFk6n57RxOkgAdF6gc5GH/l4L8C1oam/AIhAM/JM01zVt3EAmVS8f7Z3DXmg6OQ8INsWRFp/FgoCAt1q6CDWb7oBlcL9ldOM9zq0iq3l4rBagUv1+GjisyNuaA1XTAC3eQkSAIA1Pp092OY3DQdlKwfCNBAIN8gKQOMSoyQzJJCDlKzXFtU8cmYFDzWIhIpAxDxTZXP/A8KbuhG8erwK3xl3NJ4kom+5VPgPzOwUxEAMuhjHMoiP10WX9vSBuYwBgwM3bWHbybsiO3SjO2SAL7Q1YjDRreTobag4c+1IpbKV43wvwMObhvvZG3guC8M19qmSgyE9Bgx9U5GUB7qihTZgjCvNJYeRqEf0Asfcxt5QqcLPJTwsi5JtxWgQB/eSUmDkszqNKahRMAqOFTSiWbNj60A7SNAx0WN4TOm0zUNcBNzfli4o562NUhfI+lCAfyPM/BVLVptPsTRLBHN7Q6s9TJIMPu0TUU1MBApH8lp0zY5QtgNtZf6kJCV1Cp5qDLFpIeLKte7PBgBY4xQOlWuxxjZgRAaUmui3GXpUCCFca4yA5Xx7Z5HPJZxR71P2BDShhSgDgscAjIDq+2R9EaJ77RqKovaBrg5a0gagPj/BEVAPNNd/c2gk1bcQGwxAPYm7FkcJN3lseCHgIH2mCtQc4YW4bjGYI71CCZnlhjXUNPC8rM1DGi5i9NNM9cQi4BDE4EHM5j66QgmHb8k3lvYYikojwQphXyxVS0FOoytTAc1XcIPIzhYt10N5kOwD4nkCqw8C2CzB5A/KDtj5fMIMs6/Gs7FrhJOoLy+XO6Gqds5Wwc74oiZtHXnvltLh+j9eCpGbHEIAF87AwBGXryggqSz3Ago6PajQa4vi9VwqB+WULrUSme3t/K9/3GLCZADAZ7wlWr7j3UM+GpwxBuwHGnQJYA1IdynXJICIECTq93NtKsa8oDlzR5hktAGHoK5lCN3Rn/gQIs4lDC4pin0GIdgXOB1yIOEREgavcwEA/0xm1HU29ONqJC2IE0w6ck7PIrhUozSTR9nNJtUG6gKdIE5rnV63uCgkOAgQTO2G8ALJ3GORkAGPql49DqVATmqhVzs8twSkGbjwPb1B5GYG3WJ43anuAmg16wSAkpHdbyiq8RGvRCBrNIqbYyTREW814pmYkPUWrV5EYKxTe1T6qmnq0aBEiSAQoPU8HhS+xMYeUvnW0xlghgfuoF3ZKQ4hDC1iCRaIXzkbFGqbB8yrm8oqoRkpoghD6kt6khaxIfDpHeSimJsDX7NOaKN0pBD5Nmq3Da0gx3t+hEiFlVns+DCQyXQYbp3fA+WMRQUm2dzxIgE7bA4WNfWdqn1Gx2aiBhikVAOlg8L2Xwr3/Fsxe+T3ouVlYA8vBQTB/yocmNDbvhdOaT1TavAYALRUct/D3zqnJ8lpZbHYAYOjAvqtgd4KazVVtlfI1/ZnKuz1iYw2qqRyigLNXEel8wFntgYu5j/oBP8EiJ0OkcoeUy9IerKAH/VgFmzXIHLpjMwBp8ZlCeiljmqVw4agoqnYXIHgWthVKvDsXTA8lMniiMAqmRozPL4mIH6sVQR8GrmplkYCHAuD+Qx0N78/DzVnoWpaFXsGYmekEpdtczmAAKyPPFWDS8fuABPzR7Uif9UKkT3sm0qc9Ex1PvQCzv/wmZn/5HcieYZRnV2JhiveFTtuZJ/WLfFZ1MzMaJWHfWUgPgoigOr1sOTfX9t2FVGd5EYXSwnAEEyXhY79vRVY1I+bjURIa/d0bOKoIm996lBQzIAhS2fREluZXhxKwZMrAKEbJM2DoQzaensBlcSCZc8sVq+gXx7FyKoVgywBQwsD29OcKRRtOAu8g0rH1QwsCrrhdyd6AS4LAht8coP25KwAIXIP0sjyCdRl4c3Zbn2tl5gSMEOdZvgnp50IRt+8h84QX1/7fWrIWamg5dHYGsndxLSKLRD8c469iQMaR1y+gQXqvLX8MAX0vS1MGrOoBLN8btb3SQyU7sV5W1bDzpnTz+OhwLJJqESXNC3Zo5K3qfYqMmt0MxyIuKiu1S17hBSipX+EQShB1ADjFHNapzSBlIA6RPqcKKGOJxc/0WUFWouT5e/oqqaDQyJlkgF3+l/tSBXjp5L+V5xZiHknE/FquqAdpc+AiBrSgW1zhX0XcfuO7onc/wysopBZ76FwewJtJAEF7A2x5TQTSsoKnGxaRew4g6OkxJDachNTJ59YjsvwsZn7837AGlzVfL9wkQlqIPGIhKWHL9kYCE90mZHklKS+RqC0nSeZ6gNY3y08pPOaLQ1HSAsn4lgqFsJkfV7gNivJYzE0CK2pMQavHM4ahKPHirkT2NSS9Q2axKxIBrL4kbuIj4c3asOShaX4OIJGw3I1O2t9YdRWNckvNgKv8kwCgC+KS8RW9vpPw3yiypkuLKP/VKImghmaCxogrntHXCXoCQdoCD83c89yxYA6Jto+uGMWShaOPYkzaErlJOmw+ZwtOXw1hKJF8fgd19LocVKp5dd4kmBxB94vfAwp1Y8z99lJ42x6As+5EcKCbpmhN+atm9jDc+C9iX1N7rrXLaflUzYMQ2M6VkXKKuV7/EUHwAKwW3GqzKKkV8V6T/DdpeEZzUWmzY0YI9Tj4RXLV6OcQEwLpJTxY56X87l+QCA5JZki+gaNsbFAGgQwglTlEt5PBVEKcM0ESjuEmNsRo6ocFAHlOI5nO/9dx9lZMmuF/LpcpTAP/1SzRjGu5op5K3DLqEgKwCs6nl/sb9izhoK0bhwGgVEygZ9EYjjv5NoAlTG97ny9ReRaGr4fPcnXjt8ulIqyhFUif+YLI87nrfw7ZNYhqsaoxrVtYK828PDejafM1t87ibhNU59XVbtlRIegYtrAmbA4p3uPA0Yp4n6f5uQ40LbysQn8EVcLvSL0pnhbGvLMi1cPQ64gVXE0vKRWmfgE2h2a+qgtoV2DnUA5uyYGygkMCV8wE8u2zbI2mfFV1Q4oDly9spDl/47LZR8f8ERszffooz7agDEfo+dYeVnHgahRRhOmD6gh6n9yZpbP971rprYCkoK2FAUQM7Xag2LMN2+CBJpJtO4q+ltoxwSJGTyJ3NJFbbrcJcVh6bhLOmhOgBpbUl+7Dd8B98HbIvqV1yiUcIVSr8JYFPT0Nb3wcqqcfIt1VC7Mi0Vcz0775QK4FtrAwtzDVZRWqm7zy7yQDbO53oVqTYBU1OcWU603dF+rpf1OCPvqVhPmw0FAK1IEukhZyi4JF6FwEEwLlPXu8OJMIPC6JQ0BlKanheg4muBMlkYbFj/0AUE0EwFM95D9JmbIIs3U1j2IaP4EluZEPbvdmsaP/xIuW2Glb6mLMKSsu420+1otjY73C0BbeeANpY3E29xYlb+dSittdxQQYgiGGXpMAz/SBPN3258xM0JZeEoDOEByzkyEBk52Gc8Rp0SjyvluhC1nIAQkYjpDsYSDSMzNQQ0uQecIzkbvpOpQeuQ/2kjUQTqoshWiWEs73/82AKnTr+1r8Xev6ClR5lQnzW7dIxgSA/jhoNfSGxTityL9mqvRWQlBujMzqk6A52vLR0HsYRcOw3YwxBlJYyf6+xSf4Jf8vdAi2xZQTYDZv46E9cyi4Jaj9VNqXbYIVNETTwbIRwILE8oQ6ZWmP6i2QmSdFixZLmBmDQt3zsE5ee6VI4lyFDyqj4UI0+3abJHpxsj3MczX6YlWLJFbg3jMnOr5XSHVUwLW9o6vCRAJDJ85i7bE7oWcL4FT7VzPBAGv9uoKRUtcEn5Wr5pUgugbQ8bR/jbwlf+OvINLd0XIgR9NBFgLe9kfR+5LXoP9f3opgYgTZG6/B+Le/hGByEiKZaR5RxYWkJrT9tSLcK2mTyVlbjalvjkppHa0tkLzRSPmcBsV7E4RsJj1oxllFcmDillXEpuR6vPIRbtMxDMhYUzXVq4Xa82GnEs9LHrH0LxCP/c2RTDHMlMbuq0YxqyUsh/dnrQEAOpCDBEPvheEhMNgZeLzFKwAUI+02FKHDo7GRIyzcnh+5TNlpPCNz/FmKzbDbMP6Jm2i5wqkiGjiqxtYfQr0bUSLQeE6JgnIHfps/dEmC0y6O+rcHwUtd6KJo+3SQAVBAKPyp/5W6KAAr7J4iEIztQOqUZ8EaWhFKB29H4Y7rofqX169oM6dQ14XqG0Tnk55VBo/+Reh5zksx9tXPVtp70KhiD7dq2Tb8kXH441NQnd2QvX2IVJo5ykMzzJjbMbfbhIIlpdKJ0I5CYKP/YgLznAUPkuDWPFakK6OJLmu+9pymkgduck4cHQwaSVVJwBf8qr7bdr6zacvRQUesEpzxPhxvPQ9Oj4aS+2cv48HGImsUQ2IcRXL2WkMreeaUyaAISRRRsjcjxas/WSDsKU1ePZBIo8/pecu074VSRzTYJ3PL6kiMo2oCXBqAhIDQwTsC0pulEm0PViQYpYKDZceNIbVLY/buzD+AUqxcgCGhHg9NS6A4FsEQTCGP9BnPjbwn+6efwhRygLQAbRrTwQoH7e3ZgY4zngp7xdraeycv+wZK992NxKZjwYGJkuvhJSIE3M1b0HXeeUgfdzwmLrsCxfsegj28GGQlolVJlDsbPcE3a4IJj6dVJQ4i64/Av5KE/2rmCkmVTpeyQpkjbgwNYwYZe3cgjUgeoulltWk2wvwDEYlEeDhUs2iPpERKoee+RyYvuPau6e9Leuwwi5mQSnhImmF40sDY08B+2mEZCDzoJFH0JdLFLHyh5guxJEl5lqy5KsRN8tAwrYaJ4Or8A+dYK26ZQmpoIig8T0JGEjhu8K2KR1yIQFrVi4tiwFVjuxR/B+R+JsG6IqZo71TQLSgsWjaFI0+aAI/1wHHb3JUhtO59gX8PZOXWNPV00MyMwdlwEtKPf149isxOI3/NT2ANrqhHSfH7GJXP8X30PP+C+kuMxvQvfgTVPwg2XJsv2bTlJghgCkUMv/nNSB13LAZffSFyt9yGnR/4DxQf2gzZ3R2tyQnA8ewrVWAhTOeo6ZwK3XQAEd3fmzBblAhWmbA1BDVJ97iJm0NEjtDE32o+bgthNX3IvSHSOxtOCxGV33Ns/JdhcMnAP77/w3N9me/rpg4PB+chhEFOGBTvOgY9M27k2u/rQ5oAnpXGlNuDVCGLQKmWwa0iHE0Z6jdcbaSgmIFMHbiqz/oygX537OL+wh64nWvfMSu6QNqNtEUDjd5VjYqsmAlfzNe9Gl4nKLMVLv5VywwM/gGiq4BQzDKWnHQX/HQWufEUSARt3+dsmOFYaq2Q1pNgOEY3S/jju9H3nDeBrLpKP/enn8Ldcj/s9ScCvkZz/3ZCMDWO5JEnoOPsp9ffe+M1yN92ExLrjgRrnodIJ3i796Dj7LOROu7Y8v2STKLz7DPhjYwCSkUrkUQwbDAxM/vrwI9ub2ppNt+Y4Ql5tZuyLyRtmqd8hPn7h5qJysJ9znEQ5Nh23cpKmZuBYihs48ayZbZksDpFq997RO+pXiBulo8RYMlUEbPjvfjuTV0oUgHOAW2SBFt7mEtmMDfbDYddBE0V+wZF2zk5ITRI1xuem1XoKLRz2V7RzRZTlyG5HKzkvwijY0CHGNjRXoGL0KS5mgQsktg+ec9LZ/I52P8AIQoDmPYFzljn4JgzBZDrQt+if4TQCoBm7J4zbw8M11txqmS770J29iN9xvOjZPsNv4Ts7C17jCPkzlAFrhr3tQd9L3lt5L2zV/0cJK2YhiomZ6jIkILJKfQ88xmR9+/59CUoPvIgkhuPqR0fKI+o0z49OKv83YEykZWvZGqm4YKVqPMqg+SFMuZBGla3N/QVchNHUWCv/lhR4AqhWlz1HvkdQukhhW4njskoCFIABZ/h+oUPeR4//bGYxsJM6BPTuPORJZiaTaKnxzvg3ZjYgKTCpNUNq0AtIxOTkOt7xSQ6Ag9exfoyCi6IgBEZAtv8QyF9TFHn010n0WcHbmQ32nfgissfqgtPQLL15t2z99+0mz2k/gHueQIwCWBKCNx961L4kwH+AYJCAMCSjOp4+qruf50rmUhhDETQYzuRPOk8WEvX1bnSLfeieMcNkH1LGtWc4aDD9SC7e9H51LrQNJieQPb6P8AaXlqjhLiFTEFn83CWLUfPc54dOcT0L66E1T1YTkVD79GaIRX/7qiBwQr5HgIsNBmvnSnO/bpgOXlPqLRg0xjZcIzk5iauovEUkMP60XnSwkj1sSKKRGzeYSVcqwFk1X+MmlQbGTCaQJKfBodPNOC/HexqOglGnm1s2zMIKQ/e5GlpDEppA5PMQJIExxYVE4GEP5zlNBLkQsBU+KFmg1MBwYxAKJTI/vC4vxw9dvFlDkq1SiQ1MFZhKKImQXBjk3NVj6UY8JguzCeCb61c+xQsZfMP0IADmGwSmzbuATaOYWyEwR1W2wOVAMMHkLTlC12f7Wrjc02WYBhsNDrOfWXkfTO/+DpMsQAp7Qp/VW5zq7mEVj7d27UFXU9/IZxV62vvnfrxd+Ht3IHkxqNhAtO0Fae8RgW8bdsweNHrYA0N1Y995VXI/vlmOBs3lWX54QohEVgH/1MwLuK7hbKMjO0wBGZ2OeA/kIPnzpv6NXFNRqtC3jwjwWqT6KkSZYXdIhCuFlJdbxWJsmJ6nyb2loYBR6gPssFz2fBBDbMyloeJqQFMjPcjmTh4swhZE0AGqtNFmi00WoQQAi8YLgYJ5CmNLjMHn0QDD1UFElemsDzYfl1ibnbn7akTwTY92ej6cNNoNk5xNqoh6uKGRLb8OwGA2bwuMPytQAg4qT6kKiPu2/amJ4ZbSCK/fBLFDXlkjIXBXvkPwbP7DGSIsKZbvXo2aNww2S1Bdi+Cs+mU+nOlPIp/uxayb7gW4XC8Z5ABNgbMBp3nnR9alxqzf/g1VF8/TFjO0CQdhNYg20Hfi18UOafJH17R1OEyYKBD0njSFjeDrYaoXW1dvrzxRhFAyvV+nSx6zzXUXFPRwC01qxbGNVsRyUGTgRJNA3SgQXPd4DFfH1dUayOpSb6oZg0baH1u0k5DKQVic9BuoGQ3YWZ7P2ZzDro6Dh5gVVuNsiWvsh448tWwAaThtYqArExxiguQYNIQTdglQCHA9sKS/x4qGSxzdp2bt7qGdCCbjN9CQzpYTy3jE5tDg1mZIcsC0a8x8VekNDBCwHARhtsZrAxycynYlMeyU27FTMrHZE5BOIx/hEcRhCFWJy7xM6fUo6s6/6SnR+EcdSZkV10PXvj7dSjecTes5YvBjgZJu9ZDGPZjZ7cE1TuA5MZj6wA5shPezm2QnT3N/a1CXSrenj1In3wy0ieeWE9F9+zB7DXXw162rHyyIazolBJb3OJvd+eK9dGXUQ6rkcw1UoIFfoWC6wNkNQyzqfFYUe+seZ1Fm8kPWs065EZ5VdNKJKKdPBS24Y1L88tmf8mS9F87o/irdBBTlITlYdtsBhTIg16FLJvdKbAWlWSvvoEQ8TLALCcY+GRRVmS4R88CJNDoLUpQ5I9O290/9dUQelTp7aypiQ3f/JKIeLoYbv0hKeAzPq2M/04YIDAKxt+7Wv9wP7zAQiJZwlEnP4yVGQd+rhNtPRixyRphSZdkjQZMfX1UzfdMIRvxvQIANbQMXc95EfyxPfC2Pgidy0MOLoNIpispWvlGDSbGkD75LKj+ejqX++v10FMTkJ199cjMNAtSGHp2Fr3nvyAWXf0Y/ugeOJuOjpDtAJAkgVEq/fhuyqMZdaO6ds41IXwBLcWYVvI6wXwOI56XNlrLMM9DwjeL0JjqU4QRt0AOIxLq5Dtz3WUUocgqrnxHaG5eaJACC4GgVPxvNel8lwJVOlgWIdYsUBi3QY/RQE0CEAQCit3yZOYqYCl5NCwJZgMBg5xIUUoXoVjDkIg11wjkxeRvMiIH8vtWu5Y6F9qEWmiAeCtPM0lE09YfAiigrE7ycwH/Gq8kMJ7vhZhsf7ACgFnTgcedcTfWHDmCyW3D8E0JVLChs7LtpQxwNJzB4Nng0Bivmvaq4nt1zBPQcc4rom9bcwyGP3Y52PdQvPsmzP3hCmT/dCXcHVtAmW5YfcOAEDDFPLqfH+W+pq74PkSmq3xzVSOksIFfNUt0fVj9g+g+96n1jc73MXn5T6H6hsopQuj1BMKo740dLZNXnmqnmqZdiptMJ65NUmF9r0FwTlyaHu7vawAqUwG0qpJQIlQubTFXsJLekERjVTIcZVGLKIvrk9OpCdlfPY5mQJFUnQl8xBTxHhAdMJdlqQDFYgbTE32QygebAw+xyLDFgF+zfQYAaaNUuB9spirXmJGg5WscZz1Y58ugJhRKwubOIEdaiNA8QQMWPtZMPfFX02oXZjMjr4GxIvxU68pi60k7AGCVv/fpksHjElR6xCiFEX8YXsBIm+AfQndlIOAXFTzPAggIdhM6z52Ac3SpJpZu1yqBP2Gj8NvBS+BwND2v/F7nZpBec2zrtWbZSJ1wNlInnI3el74V2et/jdwNv0Ph9pvhj0wjdcyx6DjrvNrr87fdiMIdt8BZtq4uFgWazjsMJqeQ3LQJ9rKltefcLVvhbt0O2d3bMOyYiEEkfpZ3bRTQ/N5UGsWW34Yw4moS8i2tTOcpziWhcfJzxNOqUs1rEJY2e22sCbJ881E4bEJjnkIhIp/LJVHmupAUDKMFCip4F3f5H/VZHjDhZKkAMD64y4eSNtIdBZgDAC0ihjHk+CbxBrh0dQBzV0XuCRbrkXYIQgkwA5r0ysDkaxESAcjJDKV1scLbVSMgApgKRUv/jFIGivDPJkAFr6Piz6hLaJS3iqeLAoSiwdXSC17hyGBPqZjCiBqCLxVSalYKmzW38ZBBIoY2EvkgASOpttlJyyCYU8jPJEA2I+FS26EWASiRgTWnXiIJK4LqZhOTF1hDK1G87Y/Y/b5nofMpL0fqtGdAJDNNP9NetgZ9L3sz+l72ZuRuuApjX/0MMqefHRGazvz2p2Bfg4Uop3MtpjmzkPBHRrHozW+OqLWzN9yEYGIKsn8wYsFcLowRbMLfk8nWuwTduur4eS4oCUVqhBkDtW+pQo+QqFAlonzhIUM/i0q0VHl99XUQXH4fhWiWynOg6PPl/3LkZ0GVXDR8nPDrqPp8+XVE5d/VPhPl44EIlhVc0ZmYfZE8wEktzASrq4BtW1bg+798Hhy7BEvqAwMsFmBfPl9afHlJFd9l2HxOGRtugbDeX48OkQEY2JF+5OfT9uhzbOPUU0ey0BNMc2eQo4BUjVA3gf4vLzHzngSnn046eSVIN6nwRfP35rdpefApgN1CmE+O5oMv9PsBSj1p7C4tgg4sdHUUTxICnrD1XQcj4nysqoIlz0au5OCo9Q/jmA0PIJFwYfwM/GIWKCVgtAUOXEx1aAAG7WQuoQlIGQs9OjGBbr+PTaV9xoSM9zTAJMGBD390B9j1YK88GqmTz4Gz7nikTnpSmThfKNe37VHc/4RNgLDhrNoACAkODKqpaPn45X86XwIXSth0/XWwFy+prZ37n/gM5G+/F2rRcBlZdT1mkQCk4k0AHmh1DgpeoiWGG4Zhqb8hbPO+hjadcFpYaYMpr/B5egtNKBRo0aqDFtoqlK9F+WbhkH2yQX3YQ6R1JzS9J5YyMhhG0wu9YscTNIvrDpRnyZe6sGLxGDYsewRXPtSPvv7RA68OJoOfCtt6dZcZ+HbaSx/lw3+VNBJbEo/Adio7m6BVlkmGDPYYBI052YGkdllAE0PAEOAl8IkB0YPAyA/n2EBRMxuacCRFNXO+apxV3gcENPNPmM2FHaI0IwVhPDGAnEqClUYK4rVG8Btc9k+D255gRcRwfQteSeLJp96Gk459ALM5B6WSA0UABwTZFaCjBAQjeVy3Oo85oWCZNklvCcixxulF5xUr/WTfTIjw5tBAQQYBRgMkYC1aCdZla+Tpyz4LNgR72XqkTjkX6cc/DemTnrD3wyZSWPKRzyN747WYu+5qmJIHNbQEMtNd4bLq1S936zYMvurCEFgBs3+4BnPX/RmJjUeADUciLAEBTcFNReQewDzxLN259uz56w9Cb2BdeqDuB12NllCeu1eLiLj2fDXiqkdR4YiLa9EZUey98Sir+vpaZMcQooJgTaKs2uctIMoSguFD/MGHOJcOlGdhoCOdx1jRwu0/eg/6dQeEMDiQmetM5VmBo4k9P3+k897npEz6b2ZOPNOX/kghGQAM2QNrlwCGorNsGD5Z6A7muCeYI58UoM0fC118DgXe0kw+sYNlPZ1uPnmQI6R7fTXQPSTF+9kr/FJwAM/pxR7hwEMSjgkggbclZOIzRXvyZQWRu0y0WamtCsOuZ2FiqgvPOP12nPO4ezA2uhhuICClhuAEvGwOMiWhAgLNeCim00hmZ2F3M6R3ePVkBIANo5B0oJPpMaH9AeZKVFWNrri+mYPrz3EN2CqUwuwMvLE9IDuB9PFno+Pc89Hx+KdC9gzs9Tzm/vQHzP7hSsxddx3czZshUh2w+heVoy7NKD3wMNb/8hfoPLuOL1te8yaMfeNSOBs2VEb61Al3wQJa+a9xhfd1mue+ob8de+b8W70GrLx3n2a9qZaLinqqF0kLRTmuq/0sQuDTCrBEDJRQAcJwWlgDNwaJygCAEKDVjk/1aKsMghXfgMhn1F8rJUCCN2n4DwgSOJCSkGZC1+A2FK96Hx644UXoTGYPiPYoC2kZD2fuW3p7z007OnQXpBHjnvZP0SrYkpDOMgRmS7WsET6UIYJggyFvAtIYdHf3Pz8n9vxsIl94ebfbdykL3VC9K/NZ0YZZQQaCADaiqH18wgTi4mRXCSVXYpY6kbX7wZSFrT1ITn5P2LjA5NRv/DyeIdFeBTZCuUF9uphGyZU49wm34XGn3Q63RCgWO8s9AsI0ABZnA0BZGJicQH6Jh6IuQR7G3JBBEB6QTA5dPJfs+4D2/XrmYyplEy5LYfw922F8H9bQSoAkWJtyUayawnHl9YGGN7ILJp+Ds3wDUqc8CV1PfzFSx56y1/Pxx0Yxc+UvMPWzn6Bwx50wuQKCbBEdpz8eR/zp2trKDGZmcM9xZ4ANgZKpUDWzPJ9BSwsGootAc/Nex7+uOGreyMGQgZT2Kx043zVhs5oKqAhClL+SMSARMR6rElHVgS4GeBSPvqIREqgCaNUoS3A9ikP4deXR7RBc565Enf8jUf69lHqz6xfXlLTGgbiSMoCENCilR3DpdU/H7onl6OuYPSACHgBSJo1Or/e7vvFfSQKQUo7ncnOr/JJxe3u7fGN0Qx8YAfBJoSvIocdM79ZW5xKekFB29ic6zc+v2t40ja6oOkGJoQP5a2LzC1sWryxoZ0/WdCGAgpuwoZWAwyYB8t8AgwsEqaMD5Y3OTJkVbt5yZZtlg0QGM7NdGOou4pln/R1HHv0oZksW2GhonYLm5oCFOR+sCKpkY2qDjTzvRhBYMFIdeiKeGMwaGaNO7Cv03kbahQGFIqkq7yHh7XgY3ee/DSCBye9+DLJnGKp7COzXJ+iYSCpJABP07Cz8PTshuvuROv50OKs2IH3i6eg4+7y9nl72Lzei9NCDGP3yV9H/8ldg0ZveWPvdyOe/hK1vfQ+SRxxV9s0KRVdaKnQUZi9NFPMXEM3vAUXXHPG4vdyIDAWJjC+zMvAytWGYsg44dVCqgIech3yvRlmySpLHoqwqKMkQnyNiEVI4oqr+nurHj0ZZoc+JpIbl3wlFgG8+bxXEW+HYOBBnUjYCnZkZTGT78eXrng+pi7DVgQ2iYCIIY6BMUDDgpCCCDsyNnhd8KJm0r0JZn1Cz5KlHWQJSEJZNbP/AXNb/eLFjUV9vpjRB8EMF1ootDBkIMAQzhMEeQeY3UvqXZt3OP81pC2yV4Hf1olhy4I/bSPcVlLL162HEuwAsNmBIErAD73hf5O4QbYRW1fU/PdGNzNAoXvbUGzDQ42JktA8aBrZVRBCk5wUsYwmoIoNXJeH4JXjTBonC7F5GPz0WTDsjSKUQdPfv8jlYXPNeNxXAKYuR4I3sgMx0Y/kPHilzR1d+G5Nf+wBMsQS1eC3g++XoqtI3WCPLKz+TkNClEvzxcei5GZDlIP24J6DrSc9Ax5POg7142QK0IqYaWQAA7j31iSg9tBVyaKisLwoPaRUKVm7uaMvN37O3mIF2LlqzgOiBUbKTX5lzul4rdNAYZVVSwRogNIBXjMcigCTHKoKh94SjLFTfF+ayWkRZhCbpHyJRVkgEXh66KhhCOhgU00eIqfH7D5woJqDTxW///BT8ZveTsbR/N4w+ULKWQUK8VZD4LMNAkAAJFLQ2klB2sokMf6ggkk8CyWxh6RI/t4s6/LdoQ5+DqX+vmgRLDh5hI28JjPqrUfhtQOIRIwRcCLgqhayWKOR99PWnbOH6T4RHzxSOeQ6Il9UXHMHy9McGCuaDigTaYug8lZuCS76N2Vwn1qzYheOfdjU6UnkUZ3vhAWARwFKlBQGWVWQEqxwI4wI5gfTcKMhiEB86no41w8t0fjCX7Pt3Efihylxdd8Wej2B6HMOf+DUSm06tp247H8bu970Ypftuh73mKEA55UinplSnCvBVMqsaoAiw1vB274aey8JevAyZs85F7/P+CZnHnb6g85675k944Lznw1m9tt6vGIrymeiBLKlNwXxGldXL+lDXUQu6+sVE8iiv07rb0l75tqimgmGiPM5fNSPfm0VZok6qR7isapQV57Iq6RzFZA8toywKEfDhSK7ye+NI2H7+oY5rtm4gmCr1tf/3isXIch8+O/ZGSKuEhFU6oF24XEQlQMopsOihCMneyNVUcnk4VrC9MJhbMeO62DCi7iaWR3lCsCYBQ5J8oYzF+hZfy9+5Rt4ibLE7IM4wi05AZBSZYYJehoA3SClOBPEwCQPW0SMKQdvVTGkFuRrcDtFVZeOanctAaoPHHXknTjjvEbjECOaSSEqBkhH7BVhWkEOu2AsrWYJUNgraBksXj7nmwUhYJKA848H3LUaMaEdZde7t3IyuZ1+Evtd+qvEjClmM/dcbMPvHnwJ2EvbwSphAVwjwSqSFigC9mipWVfOVlNNks3B37QIlMkgffzK6nnweup/2TNhLW0ddW9/4Dox+8ZtwNm0qa7dC6SAg4HD+IilKXwXvfWOnv/adtiB+xjIBnA6zxbPVSqp2sspYCifihHsoygpHU6HfN0RZNH/FMMJrEZWPgRiXVXsuDILzEfAMnnVgHZ9/l/3c0U8d6NBVAYYtS7jj58fh6g++An1Dc6hV5vZWAWJGKfArm0JdZsBlbcYLfen/mDkIiQ+ilb3wpyWE+VxeFt82JqxTh3znJjYChsIjuxgMAUEGsiKGoUopqZJBAyhXZrVp1GpR+Tu+B64+Ey5marKWw0hJC2J4frkKuHbNbpx30o1YJrZgYmAY2ZJEkjQcUvsNWCrIo1jsguPMwfI7MG0YAgwyj22ZwagAjsSVxPLpXE0FGfXOigq4sOuBkh1IbDwFA2/9YtPPKvztTxj/6keR/9uNcFYfWb5pNDdGV9VUswZcFeUCC7AbwB8ZQTAzh8TKNXDWbUDHaY9H99OfjsTadZHd9t7TnoLS5u2QPb2R6Ko8hp5HLDO1RJhgQWwv7VjSt7AvDIxcYvi/StT9LmH85mmhaAJa+yokDaWVkYphA5dVrgIKCkVXLcSkzcWjoSiMyjuM3RUg18uLs+PJPSInD+C2IdiOC+FJ/PHSf8JsKYXEAkZ+BdCwycYGtRyi0lwe/lRDAe4YvH2zp7xVyqiGKCvc+yeYYFneqjzUVtu1L9PSvITJVL+Whrk4YSaG4tIGjh6DASghYIy525B/mnSRhycOu9EdCYNstgtaCxx/7D04+4y7kDE5zD4k4S7tgEcGCTYHBbBsew4qyKAAg/7ZJUh6A0BVH3dwMRjG05jp3XxRMTn3ZQR2uYIckyyU+SwChIIpZOE+8jBSpz4FQ+/5JqxFKxo/NvCx8z0vx9xvfwx76TpQRw8QBGAdtpappIohwDImlD6CynMbszn445PQs1lYQ8PoOu9p6H3+89D1lCehcOfduOeUJ8NevqJ8w4WlDDAoyuQPsiLxioXuc3Tt0OMXXgUzenEqbXaxDK3wVsr3cIpYlTXEJQ5USQv3McqKVwIbUsNWMgeaR5slTVk+PJq4I/XkyePVE0pga//vQNsy8PMG7znpCdie70Yn9j5UVUPDgo01qxbBJg2HEhBC1r5rwwxtmQ/57H4U3AJcqv+VwZ2a1HEkOgh+vsDGJChmk9EMoKLg1wS4yhVzBD7fDG3OVLYfWK4Al+jwAhYBrpuAnZ7DWaf9DccfsQuzhTTcCQU1mYW3rAMuDi5gWX4HimCkpIW88TBeKgKsDl5UxYAlNRanMivAZqsLXXaMjRHtNfI9DGBCwd18P2RHH/pf95/ofOpLmx5j8tIvYOzLHy+r4Jevg6kKQEPyhyq3FVbTR5TtlUiPmBDki/C27gQpC13nPRWm4CJ/6x0QPb31Vh5Tdb4hdGRnXwzP/HihgKUSZC8YsFhiN5T7M8H+8wz2onzniMVo9Pe1RmcCmxCnFB9EATRVzIddIGrTdarq+trnhFxJQ44N9abpcJ8hQGUZPTitj6O+4FPpm0beiZkDoJ4CwIWFfNCLbigkFhTFGigh4aYk2HGwa24PSkGplmYxAxnjXLvY6f+ohq6NB49PIJSCUcilLsmJDvSp4ktZmkQ1WqOGWAy1tBORT4k+X3bWKH9HMhC/yOVKz4XjQFIHSpY5vHYsVLaIIaXwjKf+FMsH92B6ehVcQ1DCe8wPzpbG+Nw0tuR3oTMD6ODgZIeaAOVDdfOmqxLCAmlRqeyhLkpmDolFQzKFIICzYhP88VHs+fArUbz9Rgy8+ZNl+5jQo+8Vb0bqxLMw8sl3IXvT9bCXrYFMd4B10DiuK2zSF3ZlYNRAUyQSSGxYD/Y0Zv9wLUhasIaXwLhedZFXshoCGzlCgf6xxe6CRdZq5ZoFrwmwFPDmrLdMTvvPg2w06mOujAITtTuwEVBi76kav1FkCGvFtqISAVXbb8LWMzXLGVM2HKw3WJe1VzX30pgYksIGWhz+7MoAh34P+Wv63uH/NbndnQn+25j9K1t3dni4desyZN0kepHfpxtABgAsgiQHju9BBQChHNbaQtzAttjGZFaEea8aDBGhxNZcys9/+yT3Ntw3uPHdBdEBVUnjmZvb84VHoTa38hMA+EGj8LogK67pnunH7Hp/uded2I7AHLbe4Kqbx+7pfhwxsA29mRympnqQsStR9iF4uEGAHqsbw71dGOzeAdGVPXDAEuVNb3Zy47ezxcRGn/26pVOoKtgAIiHnUNYasmcIMtODmZ9/C7nbrsfgGz6Gjic8J3Ko5BHHY9V3/4DRL30c41/7FAJpw166GjBB5DO5iQVyWABaAzFTvqnsxYsBAxjPi9CsDEAYhpKlt88M9sDQwnc7unX9ifuUUBsAkqzfKh/ncTMhaZzLkoi039TSOVknviFDXFYrXVZIOFqrBLaSOVCIgJ8vNWwhdSBmpBZ14K7Z7d13jE7PZvYjNezpNbjr+uPx4F/PRhpTC+QJDZRQ6F/XD5VQkGxBBYQAefhSQ7CAjwDS4LMJY7/VwDTIGTQTBpL21yXmXsO52b4pNTDhCQuCTdSDnRtTQzTwYQBLgmSGMeZrrOkikprZ91HSQ6/JD6i/aofuOJw6BjYEb5xwzPKteOFp16FUmkQ+n0RvKg0fgCz4EOP5xzAlBKQy8LQL7aVgsUGmbw+MCGDtR1s9VfhiDQnpZ56ULw5cDfIi8oNwVbBKukdI+HDKaCpBgVDwd2+HnptFz4tfj0Xv/HTT4+dvvQG7Pvo2lB56AM7qTZWeP65zV9VUUIdASiOq56oeW9dTyTDZzgBsklNQQR/2sYVN9e+a3EcOkKGtxAezfX3nEYKmY70i5n5VT6z4HMOI6XuoAbpanapGR/FJ0KHf1YwEiWGYym4O4RfGU9LaYSrN0aiPQwoPrWBB8CYLWFTqvuIpHZPn9nWWIPZxtgQPAUWRxy1QSO9vVqk9+AbwlIYRpXILAzGS7PwewFvr07Wp0kpDSMDCDeN/+VZHz2IMdZ1woiqOQ0BXmsFDaV848m2SFlbcdWHl/J8YoT4QpPgBylkQjoGXyPzs+PzdI/2Pjn4Nmg9LdCUEI+fZ2FPqQvIkwjNPux0JpfGImwLRoUdQZoISJbBOYHpuKbJsUPBtGGWBpVjY4iEAmmHpEnos3ZsW4jckXNR6BVtNpmk6AIIimQ8HPqyhZRCdA5j49mdQuv9OLPvc5ZBdvZFTSJ98Jtb99M/Y9pZ/xtTlP4K9ciXUwBDgBk09rxCL6lCxJW9oxjb1v1FBYKaQ/2QuCMqW2vtwZ6msSe8jYAEmELeB/Wsh6In1PqZyRBN2I2WuEOLlsKxxlHyVyzIh2ikMHKgb8jUOXY25jYZmHpfTvBBQRb6UuBd8Jf8WoQGszHADjS4ndU5fetl/s5h6o+sApjexoO9IFgIkZRGO7IXGgXEoBgYJ34GlEwAHIBIgKW7UIigxI0Gop7iGGAkncX/37PJbsiM+7MWT/wYimIq+pZoicWgxI8IX1mQUM0T0k4CDHyjJ1xkpIFk8WVg4Hkq9KZD2smnsWZFwdkAcJr+rydkeLO0YwStOHYE5rQN5k0Ehn4agucPWw1juGtBIKgMTSLBF6HDHIEoaRixAY8QMT0l46Z7Objv4szaeE3FJqfFWoapgqIjLTI0AUpuEU57UTJaN1NEnoXjXbdj80rPR9/LXo/fFr46o0sl2sPLL/4PUMSdj8vLvo3DP3UisPaL8mtCEnFpEBWoEzHDBNDwk1RCEwuweO/uFaeHu8/xOxUv3fVqcZAMKzLsCqFujYzZQ55TiQBayjYlMvalyXiHeqk6q1/ktUF3tHx5GUbeXocjI+3AkF3ayocqFpeqxwpN6audalktoLmA8n3lDQXUV9GTp3ZlfTNUXRiRvQr3dB4C3PInux3djLNsLDwQBxj44yNsE6gOwp5YikIYvZLnLXmsUi7ls0nZut5Q6jbns2UMEWKRw1+yDn9VpiX7d06ddfT4piimnKj/FojOqkOohbqRXsnqLn7K/SWxWUmAkJQRK5EJhdO1fhjduL8jjDn1h0EuDp5dh6ZEPY/nxf8dsvwV/XENoAWG1hcYePhMsYbAoWcRQcQdQ8ICF1LZ8QKdtjKdSfwk83hTVQMWcGOJgZSjWF1hePSY8UKJGjjPsNZuQvfYmpE68q+XpDL72beh94Suw44PvxNQVl8NZtRaUTANaRyOq+aKrJgZ/HvvvXtvRV1L7MUlJwZX7sZNIMHAbCTNngE5CE3BqVjGMO5Ki0W4Z4UiqGQEfTu04Np8QXLcF5pBPlwiNrqcQyc6h1NAQWHAooiPACDAFSHSJd6V3e3d13j7ywwXdU6UkRp+4Hk8/+0Hs3J3G3eOLMYgpLMThnJmJwc8SEM/R0N8E8PP6X8PQmpArFmEJdaNl26dxxcSfKkTdZHH8ukxHH4YGFr3UyxVhqoDWarBqaOhszRmL0M2M54EY8HXFHpwwoyZx1sQ5zzwqe8yjdvlLPWTRDLGEKA4iEHlkn/RxLD/tfwA3g+mRDACC3UZOzDXmItDY07EJcmASg907a7wPNKKecKpMm5RmezA1t/oT5LlHGkZkejOHsgqOE95hC/OGKIsRn4QDIeE+fD+6n/V0LPn3L88PEH0DWPXl78JZthK7P/1JqO4+2IuXgmtN91HpQ/U4BtFoq7a+iT2t6evIAcF+kJ8qOIDBCRLiYsHmUw2hH8UqhpHRXpVwlkJGfVQPgSKme2HpQszHvQ52Ya6LKoAWs1BmNMk1q6CFEJ9VP35kJM+0D6/P+sHEc9f+RmFmurtroqGi4/vAZGEQDnVAd/sobjFY2VvAW//1Wrz/m6dg5+RqLMIcJEyDv2eMI3QDE3zTYmsDgX4qhADAl4NxGzNvFkqMDw8MWTBmUxAENYcJAwNP0sypqVMezZkSioXCK6N2MdTwExABqShwUSXBpvqiL4jsy5YWl1y5LL/+8CBBZhbueR9A4tgrUJzrR7GYgFQMbuNBEUYKBDIJl9MwgYCQRThOEOJ4BVwvBVgMT2aeq5V4jwiDVY1kD4NQPLqK/75+L1QBrD6BXkDPTgNEWPLxr0fONXvD1Zi96pdY+vEvNPwdi9/7EaROOBk73vse5G+/HYn1myAspz5EFYiAV/PJ7wRB/Dtb0H6b/Cri/bXzZTDo0wLiTQxaFomc4pPlqykYQpKH+HSd8EWpSg0Q8n4P6R5YV7gvrpP6YSAzpmL0V52yU4F8FvVJenUZRNXsr5oWViKt6mENAa5B4Eh4x3b/LSmDJ6N/YkuEFpOAKQFz0xmkxQCEOw172sfEZAaUGsf5z7kOj9yYwP950AZhDCq2sziwMYh+GGhQWXhoDMzbYfA9EP6LCS9m8IsFJEgwdLV3QtS5KC1tdE/OfHV0+SxPiMSRK8YGT9SWrnEj4ZpgfVIORyCsDNQci7gAIspC8AUE+nlBFA75TV8cuAOl076K4IjfIOeMwxrbgIQwEG09IaL8sKiIfMnBeGk1CsUMUukHsLR/GggqkVUpgZHd6yAz+hWC8H1JhVD6F77xqxNqqA5SkfQvlgqaZilbeUP2tj+K4Q/9N6xFdTfQYHoCO97zWhTuegTF++7C8Hs+hswpUVF591OfgczjTsfIFz6Pkc99HrKrB9bwErDrN+fPYtEVgSHAbzmQ71PpA5hwyTBQhIskyV8bNFYsyoLMKEjVIqBqlNUwaYfKA0NjVsnxiAthUKtqr7gumyjzXZXjiDhoxfRXFdCqgWz4dVQRufkMaya7ipRzy3R2xSYwJogNAhCkEHADD8oYWDIPhgZLC1IaTE6noIMS/vXVv8Ta71yEqXteiCSim8SUM4s/J/8Cy2PA45BWiu/yTfA0B6mXEvEHPOFuElUCvZroVaJWmyT+nL37v1PZLqzrWfM6I/wQd1cvtVLkMlFEFsGhkJQEIEnABHyVlrhQSOyiwwAOhdO/hM2PfzNS3Rqd2VVQ4+sBVQ3f/xEe5cKTJAMpAiihIx0iEB4Sjjk/IP5+TdBc6+WjGs9YszyORFSh4omJVgVr1tch7RakBfeBu9F57gvQ97LXRc5y7EufhLt1MzrPPg2Fu+/AIy9/Lobf9n4MXRTFF9XTg6Uf+ShSRx2Nbe98Lwp33YPkhk2VkV8mShPExs8T9JcCE2w+kE1G6UAcAGABBnylY5kHIGhjvFLQauhqmICvKaQjcw7rKV0dRMpARgINyvrIC7n+c7lKicZJ0YhLHcKgxTFxapiHM/ADq99D6hYwHw1j8j4TlJDwTAFQLgjRidJSMuZyDqbEbjx5aANwz9Mavse5RAk3r7sXHW4C0AQyPrRfti4xSoPz+jJmvgxd9Ezy6SMMPpHD6RwYmoJb1y47ZXduTw7uXP58q9sGB7Fpg5XUFwzEm3SqM3MECQgSCLTO+r7/JlvK7x6OKMYf/D1mz/kYvGNvhtzVCXvXMChBgND4h5pyGrpbLFlCodSLyanesr6JGUS0khx9BUxlUw1HQ1XAkwr+xCgQ+JB9i8vr3MQjp3hVMFa5ExL+yC7Inn4sufgrkTPL33IDxr/7ZSQ2HA3jenDWbIA/NYMdH3g3sn+5Ecs/9QXYixZH3tN7/vnInHIKtr7hLZj+1e/grFkLmUihbCjZhLsCw9N4n+YDGzas/NSBXfxyrGAuSgZ8ranMJm7QZcWHT4RH21erfLX+olDJVIZAKTygsNZbV8+KqpFcfYo01dtJascJjbyP8VzNSHiIUOUQFHIvcFdJgV8ymSeX9TcCGj6CSOkwqhkyrkChWESzmuyYHgVcA2iGsBTIN0BQloUSFHzbfTyMfJrUajXD9IX7mAwAQQIdQdc3FZfgDKknENGgDkzo66ZQCkh1iU6YrzMEEgLa6J0B06Vs8PlSyR2zM6lDelvrjj2Yftb7odPfAWwBOX00RHEOsAMAFv6RH4I0/CCJfDEJwwaCMSwkbhAUQFQ20EhFEAQSCv7oDtgrjgSlu5G/6bewhlaAnAyqncphsDJxPqvCw+hiEcH4CFZ+49eQ3XXDA52bw/a3XwjZ0Q1hOzCBBvsBZKYTqaOPxcxVv0Hhrrux+ivfROb0M6Ll7GXLsP4XP8HOj3wcE9+7HKZYQm3OqQmvf4Io8sWBy3P2Abp5qCPHjz/wmgir68Y77vvZbGLseWW9UCiVi0gQqK4FopjMgeKSh3KfYc0xNF51RJ1wrKWGHJM0VMq6ghon7FQBrBZo1woFlUirSsJXUslw+w4bghZ4kpLiWgT8VAYOSsMaM4O1ARlTVe+/DoYuImUfBQBGm5BuqgxCFlkooZj/za7ffmdVZh2W9y9/vReUgJggLyADwQRRBS/i6hfngvk2tvl3XBBXloruXYFtgoyTgJSHrvTm9z6CmVMugb/xV9DDI0jcsRRUTEN3/COlf3uLsQiCAkhZggKdIYFfGUPdHGl9CYEVCQSTIwATht7/Q8i+xZj81ocw9YNPQiQ7YQ2tgPErflY1CQNHIy5DYBLwtj2K4fd8Gpkznxo5p63/dj78sTE4azaCPb9u5lcJDFJHHANv5y48/JIXo/vpz8KyT3wCqjs6GmzpR96PYHIGo1+/FM7KFVE30fJ8zEfTQeZDGZI4UHtHlUvvPAhFXIKwzSdsJJ5nmGs9fpGB0VVyfD6ZAyHyvtrfJptMgQYaBKVlDkuC/RKCuWlwKVcWW0oJmUxCdnYD6TSqhYYaZxCincvRVQW0mOu6LhOVQzCAAHiCJelWAk5joHDQljWJc4zEZYLQL7So1hVHQbRHs5/LqIxlC6dvxptaKogTMlBXLO5a7CftNLRvzjAw2y0VdBmjOowWhog9i60Cw0wyeASEbQHLh4UM/i4LuA0sxkyGgVwZnMuTiQ4RoQ5g7InvRfLMr2NOTiExtwRqfCNIzwEHMN+xbauGYEjgTAKu1zEvq6h8ATBBgGB6Aos+dHk5FQTQ96p/R+Ko0zH++bej9NBdsFcdCZCopZjNJAz+zm1IrN6Avn9+c+RcRj79Icxd9wekjnsc2PcjDc11YWgAe/FiBNkCxr/9Xcz96c9Y+aUvouuJT6h9jrt5CyYu/zmsRUMNuiuCgEDwttnuqYPy/ak75353MEIDgK1b+/uW35p2ek82ga5xJdF2m8iVA8tKVBSWOSA6GZqZYwQ7amZ8CAlKpS2h81kEo7uguruQ2HAMnFXrIbt7YQpZBLu3wnvkTniP3As1sAiqpw/MJuoHVT1mC9Cq/z0V0CpXFI+xJP0N4GMMG38+wQK1cFQkpjJ3BGGD8TmS8nWsUQo4+DYUX69Y3ayJtpAOvJRMY0t2M6aCaRzbdcxUQDphHPO9dbwOAfPjfHIXgcXPJ6f6n5d0iouE8okp8NaVVuXH7PFSzspBkkBgykO4ZGWeJDEiTqyH4uEe/0M8csTXMLH6BpxglsAePxZCekBKo71m7hy8CEuSWCKIrgs0NUgVovoqhr9rM7pf9Dakz3hu5HPSp5yHxDdOxdh/vQFzV/4QaslqUGcvEOgotwUBnc9Bz0xh+X9fEY1o9+zE+DcuQWL9kWBtIhFehINiwPgawnaQPuEElB7dioee+0Ks/OwnMfCqfwEAbH/HBxGMTyG5aQAc6IhYu0cqT1n+L6PiygMArEGx8SBciPK/Lq/vAihzf5E8CBaRaMkQIGrtO5UoK6yKD1XmmmmzwE2kVFWeSymUtm6GyqTR8+JXo+dZL0HiyMambn/7w5j55beR/8tv4W59CPay1YCS4EpjcC1l5HqfYcSOxoRJ/3JsZgzBB220hPoDiJ6jmWebXRcWDONmahFj+NElu9Iee+tZOP8iYR/J5D9Vuvh9YDR0dwDllye0CCGgEhIUAFQQZzHJHtJ6t/Rw3VwSICOOsMvc23OZ1UW+sL/S2zWJQh6QvgNhRxVYhMOjCiituRrFMz8L76jfQI8lkR5dC3TYgAjwv/XBINiST7XJ/mVQ7ZVqiKzqYMVMkJ19cB++C4WbrkTqtGdEPk9mujF88Q9gLVmD6Z9/B3rzA7CXry9XhI2pUCoG3paHsOgdn0D65LMi79/5gTcCyoJIpsrjvzhUaeTYIKbq/WkCOCtXIpiYxqMXvgHenjFYQ0OY/s0fkdiwrgxWlQhNESEwjJuz06/1jcbBqjCro/hJB6t4C3/OPLCH7n6fyfj/IQKnntYJNLbsoJGAb+gzFLHXIda2A4YQCqWH7kfq2BOx5KNfQmLdES3P0Vq+DgNv+AR6L3g3xr/yPmR/9R3YqzfUo6YYaNUV9vVIq+bfFUoPAzaQ0j7bEvwQMz/bgP8aP3Z3aTHGNv4Y//H3WWgGBh0XAQgGASwn7RSKBWbyPrskuWqrz365WkkE4grrpAHfuJj2C1jesRzrUpmPTwfTkFn6ujUlwCvT8Lq871tjpaxQ9v/JdM18ua87O7JiaPJnE9MZIBeAcXgjl8LR/4P8yV+Hu/wWKGlg7d4AlRPwyMXB2oHbEqzKE8vfTsyfNszzRFbRthvRPQT34dux+33PR+pxT8Xg274INbQ88tn9r/0oOp/5Soxd8j7M/eFnsJethbDTYBIo3X8Hup/7SvRf+M7Ie3Zd/A7M/v7XSBx1AjjQdWO+MFgZNPQLsgEQBJBdXUgdeST2fPISsAbsFcujBTEASRKYgn/1d3Ij3ykZc9Css9VNA1ce1PxcQX6iV3e+nkBLTIyfClcMI1FWmIBHk6qhqZDfovz+aiRGykJp80NIbjoGq797FcheWCul7OzGond/GfA9ZK+6FM66o8EmQM30r0GjFQOtcPRV+cNM2cBsUAq6WZB4PoCfhY/pzQyhb+PNOPUVN+N9l25CoSixBCUU4WK52jDVyYNTzI07UdUBWttF+KYE1gbTeTPkwJwhpIR2zO1+nwGjBOEKY0n1E7bUk1NEV8/Ndn717vEVP5MSYKcIl21YKIIOsR9MYegmzD7z7TArbwb8FNTMCkiickT1v4RQb7mRE2Br+iDA/x4YEemBbQpWYbsY34PsXgSRGUD+pquw/dVnYPji/0HymKig0166Gks/+SOMf+3jGPvCh6AGlwJCwVmzEUs++qXIa2f/+GuMf/3zSKw/GqimgtVBE4iBVat+QVPmD+wlS8oOpUT14RKVl7m+huxJXnDxkuMg9cHbJJXjdh7UwJcNIYB8uWXztVWVQzjFM6ikhhSqIMbdHBAtzUb4rFCbjp6dhcx0Y+VXf7JgsAo/Bt/6OeRvugrB1Dhkd2+l0boVaFGI5K+AlgjLLcoVkcAAFqmfKpLPZJjabuDDgz/dj2cdnceKV87hXd87ChNQWIZpDIh+lKgOhrFvFG7ggbWGkgmAGVLYLyImGMMIktYNnJAgS0L6jAf8ezEr564Z8Ja8fZla/Zk8cqv9ktkMaSAg4HgSttQVIWFDMfGgPoL+h5E/7SuYHP42qDeL9PhRMMwVb379vxqouGwjBSXow9LgIwahHldeAFjVXlOOTpw1x8Ib2Ymdb30W+v7l/eh9+dsbjjnwmvfDXrEeez7xDuT/vBnrfvNzkFN3GAnGR7Hzva+DtWgZyLLAgQlNxUGjfYxB1KqmwY2B6o4nVYUQMxRZGEnRx8fJ350oHlyfeyU6Ow/6hSoKfZ02fK1d9J5IZCId3GW/qUbyvKaTiskPol9gBSgkwBBwd2zF8Ns/BNU/tF/nKtIdGHjDf2HkYxdCdvaG3E2b2CuHQasqeYikh/VmbJ8FbMG/dhR/cKKIjxV9AyEAo4EdOzI4ZtMcvvSvd+ELv1+JG3em0OUJSFHfoTQ0MiIDKSx4FgNQNVmHa1yUgvxrSDAEycttVtMgwNE2sv4s/j51D3z2sBk7Pzs0vPgl3enUp3zlv6C6Ag3bAAgWMYQhyDyFJukcjC0LKKz+HfSGq5E79gcwPXsgH1gKNbEUSIVFef+LwQqAlLxGsPiE7+OFYeV308jKMNgYsNbl6AUSJFRlyEPFOkgHsAaWQmdnMPb5dyP/txvQ/6/vQfLoU6N86HkvRHLTcZj55Y/Q+aRnRXmrD70ZweQkEhuPqUsYIpFTLBWMuDyEQCre7BySMfjKQrJUeGD19MgHhiiAMnRQL7daPXbfY5KzkydfNdGxZLPnSAhTvxlrN7cJVdxMWNUeMueLp4lV0CIGfA+qpx9dT3veAZ1r59NehpmffR3+7i1QPWUzs/KmFm33iYIW1aIxro7ioqotV5WMBwItLk4q+wXDmeC5geFtAmUR6fY9KaxZlsMlr7oTn//ZElx31yrMTUh02ho9MoOUrfGody/G8nNAvoKilYXS4/SftKJjw5GGCyA59nlGCYBESWokCjZeLF9e4UA0/En9UneJ/1PDrmMMu7XzZIInyi1AVsBISAtyP6w+GiQKJ/wI29Z/Dv6KWzDUZYOmFsHafQyMXwJb//jCzwXRIgxYhDOloKuNJqsKzw0AICS4WEQwNQJTciESaZCdLD/vetD5XFnA2dkH0TVY3siDAJTsgr3uWORvuhr5v16Hwbf8F3pf9JpoirhiHQbf+MHIc1M//QFmfvMzJDYcXZMw1AZKNEsFm/0cAivTpF+QmEEkkZWJi7qpG52ghYwa3DfAChLqMblwQYfY4jri47KI98ca2OrgFNdmtaoaxgdPGCCYnkL6mOORWHvgVc7UCWdj6t5bIbv7Qg3BaJoewlSHMdTFpfVhENEKohcAHZY67vhF6o6iMY8XwH0AIAVjz2QCyWSAtzx/K5b0duDvM/3oSjKUTmAq24UsZWASRUBT3ceLAA06gUQBxu8bnRs54mZT9SqRDOEBPfmHQSiBWcEW8uGS8r74d/xto/GCOxtcDSRBGKDP6sIxOHq/d0J35Z+QfeLFKB1xNUp7gOTkCii/C0b7/+tTv3DqDgYsGy+XhEt1RbZAseZjCAWTm0EwugOyewipk5+G5HFPhLV4LURnL0gqmFIR/p6t8B69G/lbr4W3YzOsvmEYY8CsASY4qzYhmJ3Bnotfj8Jtf8bw+y+B7OppXpV96F5se90rYC1eBlIWTC26onrqZ9CQ9tUammPRVFPXUwCestFVmL6pb3b8Ok8+NpuT2r3o+MfmCgpCai77AZjCBYHkZWgyZQeog1bti6PmVcNo3yFBZ7NwVq09KKdqLd9QbxWqMEm1SEtEQauu00IdtCphb9XloQZaxOXJRoxuS9I9BLwKxN/hSqRVLClkS104/cj78MIBAz/Q8PgG/OT6J+KR25+Lo9KzIeM/hmaBREf27KQ9hXundv7oz7OFuhqfCDYMXj7IyEgLrimbECrL+tpG69RODrixUFOxg9YAJnKEHqNbFnOIGJp8mOxg/UY46n9QOvZHyK6+BlJ6sEaOgjWTq3QnMP6feTAgIGDZ9GUhcVFEEFq7qRVYe/C23gfVuxTd//QeZJ74Etgrmle1E5tOBp70QvReUMLUDz+DyUs/BzW0rMY3mSCASHcise4YzP7m/6Bwxy1Y8rGvI/24sxuLTN19GHrrBzD+jUtgCiVYy1cDWjdxJw1Pw6FYP2Bz3yuEXA+SWmBWyRfm0gLWY+T5o5IjI49NdYQZWhKMxJMkxEO1uSrh1BCh1DDsqBD2zQqlgzVbGQI4YIj0weHfRCoDFnYNMMvnUi0ixEArRLKj6g1PHG3QpnAFsWxdbJhICPq2IvN8JryXGfcQMdgwsoUUpgsabHxAJnD+cVfjhrtS+PPkIgyIXEVVb5CxVc/Koe4XF4IedDn3fvnMxbcgPGqQhITpeTyKIgnNfhmDA0DM2XNNqYSqYBQEP/BRrlI2PnwtkS11ouSvhFh2GzD8Z4ys+yV41VVQOgWRWwylHbAVH5v0vx2oqv2q/GRbqS8CtFHrGF8FAoSFYHoUZnocnU97NbrPfzvU4oWNqyInAQgLplSse1vVwKIsgUhsPBbejs3Y/paXoecF/4zB178fIpGsb8iDi7D43Rcjc+pZ2PO5TyD7lxuRXLMRIpGstPbEOSyKpYVUi8JaGfMRBDyZf5mXsHaVOlc9ZiylKsrHtvPdaHokoehfJfR3NMciKcSqhnFBaZXDomg6WGkGgimWDs66c0vgIABXPUHDx6/yD0QgwXEb+bpnQs0Sp17pi6eI2gCCxDOlwHks8DLN+HF97ZWHRMzmM1iT3oOnnLYL2x7agKHOEowhaNJI6syLwZbM+zN3LOle89AacXT0uyaDQikH43q1CURlvmRvEzQIQpT/Ds9TcFmATRLTMwpekEAq42HF0MN4xvBmrF37P5jt3A3XSyM1dgQkFMjyAPpHdVHYr+0YgAEJA0XyE0z6PUaHblETUo1LC/7oNkAbDLzze8g88Z/27f7JzWL659+CGlgSA6vQYIrAh1q8Gjo7i9H//jjmrvktFn/488g87swoX3v2Oeg8+xzs/Mh7MPb1r0D2DsDqHQDrYH4Oa97ICmXrbsIPPSe4TMAgY7zHLMJW5PqP6aUVAHzXfNdKOhezxFJtuGFqTtidNMpnNfpYVQFMJJLwtm87KOfo794GBDrUdBo7fh0R6kR8ZeGWK4lh2UOF1xJNeC0GmBgBQ0kpL1fEzwi84E2BNrOh0BTFfApnr3sUd8+eDh10IWG5IBAKRfUK12UkJH2P/TzyXIiHtRBGgGnhvKQ2EnOlJDwjIXUeSWsKye48Op0CNg2PYN2SKQx3zyGR2QzHKsI2i+GOboKUQZ1s/H/qUfE4ZuckbQVfh8bxRjdJAZnAJKEndoOkg0UX/wzOxsc1fpoOkPvTT+E9cjeCqVFwoCEyPXBWbkT6rGehdO+tCEZ3wV6+odIviIgivZp5MHyIZBqpYx8Hd9sWPPqyp2HpRz+Pvpde2HDMpR/5T6RPPhVbLnoN9PgUnDVrK9FbSG8U5t2a6LLq4m8Btrw5w9kLrFJ1fuVj91CFVOkxv8QGDC3xz0lt/5FCu1Cz1BBh+iM8HSfWhyi7e5G/43Z4u3fBXrzkgM4v//c/Q6S7a1qq6lCHiPlf9XQMVaQnMb4qXEGs8VrlaTzgRkcJrQEh6AJbqqenU3wB4P62Gnpm3TQWDe/Eusw2/P6OM7Cqfzd8IwYCW58uhIHrBZe7HIRKp+WzE2Qw53aCgh50ZWaRsNzaxJwwF8VMmC1kUPJtZBIFrOofw1HLHsEqexw9myaR7plD8IjC0KAPcADtORjJJ5AtpDCYVBWnz/9XHwTL4gsZzje8ICQybAJWXMhCT4xg+FNXNwErxuwvv4HZX30L7kN3lD/GSpY3Qc8D+xr2FV+DSKRhDa2IEvcctUfmUInS6AD20lXQM7PY8YG3oHDvXVj8rn+H7OqOHL3nGc+Fc+UqbH/7W5G9+a+wBodh9Q/B+H40Fax6tDcDKwCuFWAom3x/f6HTMD/260I9OL31UKT6KAV89amdy988kOn/wkwpgKiO/6qAVi01FBUbGhGuGtYbpFGpIpKTQunRuzDz659h8NVv2O9zy/3lD8jdeDWc1eur5lJlSwxU5hyaymALQlnwWAsOQ2Z4Vb6qqsuiaopIFSCu81816QOjMryT+jtS8jdszNWG6d8J5nqA4bspnL7xNtz86BqMFgQyafMaJgGHguscW+7RUDWPKwZhrpTCTL4DG4fvw6olN+PvD52B8Wwv+jJT5Y55wSh5CUzlO6CkxsqBPTh55QPYOLQVS/sK6ExPw89KBMMGvmLkKYnpbAbGMBQEDOcO2STl9sInAgIGBQYs1GuMTr6FGRtrrrARO+OQvkprBCPbMPju7yN5fLT9Lf/X32Liy++Bt+1ByHQX7BVHVmQPVWuYcmVYz04jmJ6G7OwF+0HMqK8CVnE1uiGw1hCZTjgr12Hyh9/B3DV/xMC/vh6D//b6aHX86GOx8ffXYOLS72PnRy9G4cGHkFy7rpIizkOycz2id1hcYUzyi1nKgOixrwir5VOnHrJAOl9KXKL8/Dl2R+mZWstoabRqa1yNurgJCV8jkABoA2twGBOXXXpAgDX62Q9ApjoBqjRBh6eTCKobAAK1qKlMzFcn1TRJEUXIKC+sng9Nq65a35ZbejQA68kkrCcnLfNNcs1Fs/nOYN3wozh+zV341Z1PwpqO0VdKliiRuaUgA4jKsWcK3Qi0jZV94zjn2NtwwvIbofp34NRFe3D5387FQ2NL4fkBcqUElvSP4bxj/ooNi3Zg05Jt6EvPwHcliroHe2b6QHmGzJXAiQC+VguaTPW/+mEYVPDBi1IrOKW/Ao+fFmiF8Gg7jo+PB0BCwd1yDzJPeTky57wi8pGlB27F7vc+HyQU7BWbyiCkOTr0lA1gCCLVWRmCGkTV6GGwipxHKPoyBiQVnHVHwN8zgu3vfgvcLVuw7D8aJz73v+ICpE88CY+89ALkb7sdiSOOBCkLMCY6ot7UeVsDAV/ZoxmdeFGpSyArZ3EoBMHK9PQcMppy0ghAFZ8/bJkRHcjeiANpVe5gwm7GddvYmv1LiIS3+odQuP8+bHnDq7Dqi9/a53Ma/8anULzn70gedQKgg5jXVlkcWou0alqrysmFo78wr0Uho8K4XguoGwKG3CgQ0nsJ6AuRSD6JJX3A9+z/OWLRg7hxy9FrWDhrDXx0cep3pWIPJrwOBCWJVf3bcPyKa3HK6rsx3FXArnwCW8ZW4dTBHXjzk36Ab9/0LOyZzeD4ZQ/hqDVbsGFgBxAQJovd2D09AMkBpKUOy7TktmWpuFy9ZUelZI/9bkqr93pGWSjpmsUtx7zUa2UWLrt4inQ3+i78zyhXOrIVu972NIhUF6yBpZXGY45MuQnbG0f82ONpIDch+GMpm9EMGFO2U+rux+hXv4TcrbdhxWc+j9Qxx0XOLXnEETjy5j9j65vejqmf/gr2wFB5ZESTVNCQhDCMxeM7z5Pai1ETjzFgDa148JAdbKisJfLzc+r5ivm6mh9y2OyPQv2wVd6nUoCqAVgFLBBoJNesx+TlP4KwHSx+74dhDSygTcdojH/vixj/6ifhrN4IrtSiIyPICLXWIcNVE/16/S8c/YV5rWoWwRWuikw4RQwNekVsqEZVIa8BI9RqAVyWc7svWp8afe/q9Nh5O4K1SAlv5GG375q0UDhhycM4culmnLr0b0gmxjBdTGFkrh8l40NZHsZm+pFOZPGK038NIQrIpAqYdfuxa2oACgaGRBlU+f8DVHxrJSEySojzyZKf8HvlInYrCzQMVqYOMrWLWGmL8PdsQ8+L3wHZE1qLOsDIx14JLhZgrzwS7AcNI7lagRW3iqziYBVK38KVPhgDFhKpo45D8Z578MDTz8Pi934Ai14fzUxEIoHVX/8S0scdi+1v/yCs5SvQoMdiwEgJJ5F/y+DsrjuIGHwIF5GiXNchLQZLBkjJPwWq9Gnyg3corco3c6gqHpY61HLAMGjVqohl1Xly3RGYvPxHmLvuGix689vRd/5LINKZponp9M8vw+QPvorifbfDWroCsJPlJr/aegsBSgVsIKqTbEIRH1FlY4nyWuAQ4EWqiKgxTqgMx+BKehkm74GyTXO5+V2caWeSfxapJPwJIEhZX8107sCFa36BU1L3A2tSmJnpwuT0IIT0IUV94UhhkHdTEJaGbZUwNj0ITyQgyID+P0g1pSyY0S9Iv8lJ2xcxUb/2Q1E1ozlXVSsKVQwd/RJk1yA6nn1R5POnf/wFFG6+HsmjToTxg7qzQbWpH/WKHFdvAiaEeeyWYGXq6WSNINexFFEzmAM4a9YhmJzGjne8E7O//T1WfPbTSKyPzpnMXvcXUCodbYJGWVvJloDvqWu8meQXbk08bt4Zm48JYB09uuQw7GESvi69c2f37jNm08GpUsvISK9qpF0dkFLTZ1VBzdSHHpZdEwiJ1evhT09g5/vfhcn/uRTpk09BcuMmqK4umEIe7rZHUbjzFuRvvREik4GzelMZiLQuDxURiDY/h/aNmpI9XEGsNmlTiNeqjASLvJfCLT3NuK0QcFEUuBKyhG2zi7Bztg/JRAm7zPL/OX/p73DKsjuR3TKIwmwvKNAtq3ZEjMBIkFYLmDn9/yJIlUMHIiQU0euI5YeNCToZAsbUrX2ap38I+Veh1uYSTE8gdcKTYQ3Wfas48JH94+WwFi0uDx6NgFXss5uBYsTzvZGzCr+Pw2AFNNoeewFkRyeSRx2DuT/dgPuecC6WXvwRDL7qnwEA2970Toxd8SOkjzixTPSHSPZASljGL6zdfdcLjMfgwyAQVhY7h2WxKNGNRNI7x0/O7AlyfkZXrYiroMUxJXxYVCrr6Vt1MjFrDdXdC/T2wt+zBxPf/xaYNYStAC63nMiuTjirNpR5JaPLmFcFSBMDrUpkVY+eQhESomQ8EJM+hOb9Rd9bGWoakj+g6vIgULfPqVQdU8rFRL4bs0EG3UH+4d65yYce96St0NbQJ9zuRFaw+QwDbrjP8v8/Fg5XBDHMnHiNAF7NjOFyS029BzActTREVZU6f3SQKcClAqzFq6NE+71/hbftEajB5bWBEbUUELG0j1tEVmhOsMfBikNgFe8RjAIeI7lxE/yRcTx64b+BiyUk1q7FyFe/i+SaI8s0STgVBUMIgXRJndsPbwbJcLnwEOLGt3pvPHz7W0nleuaCs9Zl+v+spEzpyiDG8PRocKx9J6yEp1D7TnWkOgOisxtOV3dlizQgUW2vQcWuo9JSIzgygsxU+gWpZjZIFfDkkPK9KnuoT+Op9uQR5kkRq4uNQg3Ulb4apioRX9lliUGCERiBu0bWwYOFYiHxzdMG7saS3kf7J7Nd7zHdgAjM6wn0bSnpaiZcx9VRUf8fjRrzvfrySILoPAg8XkK+TgcqyRybmBIDqiipzg0j4yOgoRki0xsFrAfvgCkUamK8lnxVHKwY9bFdC+CswkZ8iLuINmm74SCA6utHqqMLuz76STAT7KXLIGwH7OuIJlgxgV3zKoPUjQ8tOSM2ffMQAtbD2zcftnXkAUgAty/N9L+8U6ifauNFcuZa+T88u1BURJocjbRY1KuI9d+VP6S8Q3GkmEHVnY1Cz4fEviTq4XTDpOqKb5ep2hhXRabUmCKCwjR7VP4AqjAAIZV8NT5LqwK2TS/CXRNr0KPmwFJ8a80xW2G53lN0MQ0WDAYthsAHhKAPCFL3EfADEsE3DHji/6NU/aan8rVZlxTyVWzkK12mRWBAM0I7Y9yFIM5TIQpWiFULK69nQxCZaOXdzE1XJttUAaaq46m3pDZUAhkR4Ap7acVBtXrcsngrQJDNg4SCTHeAjW7eI2iqVIUpv7anF0YbkGVHvNmry1OD3pPT+PYMigCXDtvlVKcsOu7wbnwMjLnmZyT50gSJVxjWtYvZFLRCJg7QMdDisG9WtB8wYrMcT/NMDLS42fFCsFMFMWCvKWJ1YTXwYrUJPZVZiFWXikq20aEKuHHyWIzkuzGcyf950QBPru/cg3zJPh6x92oARHSEIPxHStrvG2b5Uyb+OQG/AhAlIv5X1/cAw1yxsgaIuYsdeb5enDlfgM6jSuqluR7xRoAq5FIQ5akQ8VqPRmChDciUrzxZiYbkkyHrYMUt+KoWGqumOqsQwU7SRjC6B8HkJES6A/bylYBmmEIREKqxRzBuJaMZLCSIZPkDQ2AlmUCkH3w0mf+vfNpA8eElH9STZ7Ye3g2QAd/4kLa6YMuyo9f7bJ+S8Argai9LXaMXkjuEZgnGG5W5ruNqBlogjrjW1EErVM3bC2g147XKdjKxKmJYHW/q0VZ12GuNr6pWB0McljYSD06thFRAdrL/8sWrbgF1b0cws+jFHAHXsP0MIFhkUpAXBMJcIEC7iPBjQP0SENeB/vfCVnU7sKTsJtA5DDwN4OcYSb3GsgCfo9F1zFYlMh6+FnmJ+rWKp3+IRWHhdpbG6kdNOhNVxi8QrEwsoosR7MH0JDKnnYGO089EYsMRSB15FKZ//Utse8tb4KxcE3Vc4DrhXzPjCx2rJg5lhpESJZHIZ6HO9JGBZHPYuVLVWZppjxXnAzNzI2flre7fe5Z1tjA6MkG6VkSMyB0qnBRCHFLMkqYB0HSFTG+ItELupyHb46pGjGpgSDW/p9q8xf/L3pfHyVGW+X+ft6qrj7knM5NkkszkvhPkEhDlVjwIuCAqeOKK4vEDdV1dRNcTT3QPV1k8WEEFZHG9QBS5oigKQiAk5L5mcs4kc/d0d1W97/P7o6qr3re6JkFX1wBT+fRnMj3d1VXVVd96nu/zfb4PTVBFjJqpEaFenDYiIYGI7Wgas0VsH+rEU4e60OSMeUMl59YlU9eCSb1UstXNoQxDHzTDmnuPG8pAGJhBAu/3Vf37Ad5pgR8TwAYi/JYIDzNj2DwCzxo6SgefmUT0Esk4Xvk8Ny/yZ5FAU0WqGGQkT8BPadU0BENHeXwU/qF9AFmw22aCMtlA9sJa+sbJdFBLCX0FVRw1ttFqbg8jm3hkXbQOlVIJ1GgRVil+6lVuK0zz3N27Me39/4T2N10WfWbhBcdB1NWDXQ+wHQOMIudQGV0+NW03yrKR8ysH7dLQSyzO97fDgsX8N7/h2UfTyThn71Z3LNd6xvquY/Y7qjiV9AOpg5YMAUWfYwitXUYn4oWeOmrpIXMw0zAFtGLBp3aSpvFaYbsNJoq2SBOXVn+HbqWlpZVaT2JTpoxf9HRjx1gBUwvutrZZ2w+9aMpOtBzquHIwqePSwkUydiaIFlhF3NlsUpgNEXi4E2NEAKtZiAcB9UcC1rLCEGwGHAX+Wwrfq8WR8OYhWUUVVIvQScQnAjiRBF7EzKcxB/NgzatJi4j0CAIpkVAYzbs7n4bV1I7mN3wUVlMHDv7H+yEaWmA1TAmmKiWjMD0l1CyQ5eiwsTu5xceC8g1Qvh+kXYmo6ojkOqeQ6xqQkZPD+BNrAA2w7PoGWA3NwRBUC6hOeDMiqxQgrCL6aL5B5g6NnbrgwMbNRxNG2IP/R605z+g8ZcZgYytkXr3aGqOHlWLTcVQHLYbZvpMGWiIuT0OGfxPxCc0yBC3WrJk1gWf0e1qKGPFSiUhJJLmtmCRhrm2iRoIfI1Io+1msG5wG2CX0l3OPnGrtQIc9Vujz214RbXvIhVC1Bk8xl0tGnkQajxJHpAQ0EtEqkLXK9yUINEY5egzD9houiqfsHNYTYQMDIwBBsUJkDXQYRixoZ+LasIhTMyVIZngImsaVnqvbAmSLDiheWOfkjrPJXiElrbQFHwdWtqeLJZMRYpKbSnJNSJDpvgdZKqLp/PegcdUVyMxYEP2970vvAFdcWFM6w56+FKCC9jsFE8gNwFpyHJyuhfB274DVMrXG4bOGXP8TwKra8ByHStVMQoYDUkUqWNUcG21El7BstI70v7F+fPCoAisAsB954QuPsk2SyHny96pIbxZS3qxsYYasIYAoFV731hFACzHIJL3hI9AKwSpK8TjkyapOojoZLxFPf456AuPBGdBcSWu4LegWNVoDbbQOoDlTwo6RKXhqaAry2TGMMb7/ooNPwtlTeb2clbFE2QsjMy0NDd0fquQMJ6Mu/ZpmMq/hSMGPeth8Og9kTgcT7JwCWI0Iwg6Q3JXPOFuVhe0SvBuEPgHsB3CICBUieOCQsiaKKqekZ+96JEqwADhSoZC3RCvBmiYVTXdszBKgbnLVQtmenwugi8sqW7BykMzwfTZA+Iggpe+vMT6ODJ2Dcj1AEVrfdi3IiQnzxldcBlHfggOffRvUvh5kpnWDPa/GeqV6M2IAcPJwe7aawCwsNJx+HvZ/+Wrkp8wAfP8Zkes6Uc5IgBW0cWEVF3brFJNhGRyEHB4LBqtw7bSbicz4hBBQIvO15fvW3ma5xaOOErAd1z2qNkiJoHdQ+fK7wldDlmX9VIr45IoOLiUsaRC3tdSAVhgpkYq5VCPS0iuMVc4q6gEM5QdVFboGVFGKWL3kQ/YyHp5hcltRolbVY1WjIRGndo2Wjx/0zcSmUhYLMt6elWLvPVNOqGAw2/FBKsnQaymMmSju62HSKlAR/sUpK7SfpF/kpKnH/JBnBgM+gcCNAI5RpI5xKAuyAVdJgAgWW5AWRhVxWbCoKMCFhNtSqJNEkJ4MvDeYAafFFiDYitgGKANGVkoryywKzQ7lCTbKHsNCML5NycDSp9pHqsAJeYFZZT4iQOm5fbXiF+lOAAgbLMtwezYiO9+smte/5NVwbliCvR/+O1S2b4LTvTgCrYi8RqyrslumovjIapS3rUdu3rJoPa2XvAeDP/ou/P59sFo6gsbkGqBKrwRyUtJgTGQOjlk+0cxcWvc05KFB2FM6zMjKkFJo62CGsDPIkvioD3GtJRwARyFgER9ddSMRcj7FaTkoi36WG6m8Oj8sf2xEWroJqBZpGdXECJgS7goaR0DaUIsIWFLIeDDFAyaS0gdKVCc1zVYEDtVxYBQngDoJX424LGKMuQ4eG5wC2ylj2978HZ35XtQ3jC4fdxuXkDSJqrjSyAZ3FWu64rtxRPxT7IsIpEyI13I+1jrS/eigxwMjmdAARkP0HDMsYQcDZSXHx1GIeGKwlj4BBD8KBoNhGDrAUCL3TAco7caTymHF4YhR7dOqyswErlTApfQL1OlahM7P/hA9V5wJd9cWOLMWBJbaulq9Cgq2A39oACN33YrclZ+Jz+u6erRfcQ16rroUuaaOZ5QCRn2BVbBScSWxSpDL0VE4M2ah8TRz+ETx4UcAKwOkjeaCCVYAUMjkMDB84LMPjOy/1gLhj27xqDS8to+2Dapee15DBjJrQY2XflLw5b+IjPV+ybGUoVrJq+qWRNQ8rUkeohQwHtNjVgzjoRYRGZ8ELY65IH1EfbWKWI3MIv90ndvSdVeI78RVfy9dnc9EsIXEiJdFX8VGnSphfXPutpnzJNrgXL5TKoiqfameaumldQr7FZkM8IrSRGipI8ezIBNVh9rJOTpxlahoJl8oVbzHrB1nc0UTVP64VvJS83xCzJl6u02kfFGEq5PuGmAQCCwVlBdnG27PJmSmdQWzAgE4c5ag6xursfvKV6G0+Slk5y0DK2Uqy8M7qDNtNoZ//t9ou/xqiHxdtM7mVZdg6Od3YOTBu5FftDKaD3hEvgqm7CGq5tkZlDZtwfQrr4KtcdFqfBzDq38De+o0TSQ6MVgRAUI4P60UR6/ZWRqBFQTbR6X59VE73kR4CparYCnAY3xAMa639BM+6dHDcSWkVtGr5ZBJl0aZOAkUGc+xIbijuGFVJbQzid6v6uuNz4zC+oA0V1W1cfhotjxsH2vAlmIeY+zvX9q05/dvm7YdlUr2EiVF/FoVn9BmtYkCx4CwMRthG5NxQVT5luoj3CdjW/UH1/4/rcz+5zz4cJ+ltAsr2u/qdgfHQkkfcmQA/oEeeHt2wN+3C3JoMFBqa/uop1msUDMRhlkEdi+l2CO/vOEx9Fx+OtRYLPtxuheh65urkVt6Itxd28K7FpnrVARR34zK7h4c/OaXas7rmdf+J+yWdlR2bAsiIFV7Xhr7r2+r/pxlwduzD07nDEy76irjMw5+71aUNm2DXd9Qy1klwSq4ia12ffkaWDYaANQDaAXQdhQ+jvp5TNVuG1/h3WBcadz9lQ5SGmhFbQgUvYYVan6PvkCZvBAD0FJJf6E0jyLt70rGwGVWdKgW/KKLKPDbkgrICYVf9bdhV8XCPlH4zrnFtejc87vle5XTbrGIwSQEmjTwiu/YVTCqXgAaKCX4Ev3ijkCsCg7SBDOoEORlCuA800f4fnO9wWfVfD6LoMKpGKpcgn9oH7yeTfB6NkENDyAzdQ4KJ5+Phpe+BXUvfg3sqbPhH9yHyq5NUJVKwE8lAUDbz+h3TwajtMIlO38FSo8/ih2XHg+3Jy6W2R0zMPvGXyO/4mSU1j8eEpnVYxSCl5TIzl6Avv/8AoqP/tpMaVrbMedbPwWEjcrWLSArY5wX8bYmgFaaOio5XIS3rw/zb/k+nK7YGUIOj2DPtdch2zkD7LN5U6sBKwDATb5UZyiw92xQ49nPgm2MMzaorxKQJdCXjDRColZcipiMjyqI0e+orSAqmIJTrcoYKdIp/n/Qh6gR8olKIiZKEzlRTQzTt6yQ6Cvl8NvhBjiiAn9U3XbW4n5gZtMFviuQgTR1VpGMIeR7NB6LOZ2jYo2vC1IBrdxEKZoDjgnZ1HSOD/Nl4Qj6h9Q3aG0wvgcujUGODYHdCkSuAKupA7lFJyPTvRRO1xI4c5bDmbMiEHdWP84to7z+YYz95kcYu+82yMFDsKd1A76fomyPI1MlGexV4i2xHTgLF6PSuwM7Lz8Lc7//COy2zjhS+soP0fu+CzH22/uQW/SCkHTn+CS0c6CGFvRe8y4suvMxo/qYX3oM5t9+H3ZecSmKTz6O/OLlgLCgpDKHmCbI9eD7taBGixhftw6zPvkpNJz6YuNo9n7iWlR6elG34hhINyHDSIAVATf7UG8lPHukwzaeZYsCX2cRLSWmy5ReLkrotKg6uVUfHRbJIFIqiBoZXwWbqEGVYl4rur71KmPCxqwKSIbHVaSY15TyWvGrI+vjoaFmPDFcB9vx9i1uGXyye2oOg5j5dke5QZShVxYTIMOs/U1ruOQkNFBCk6TtlKnfqjL6KdpyTetBlg2/fzdUuRiKYwXsjllA6AmesgWJMl9Y1bQz8Pt6IccGQcKGaGyF3T4L+ePPhTP3GGS6FsGZuRD21NmHhz8nh/yxZyJ/7JmoP+0iHPjCO1De+ASy81bGIai+/xxXb7hSNtbDLOB0LYC3Zyd6r7oQ3d+8F6IQGEOKfAHdN/wCPVddjOG77kB+2QvCaK4qupVwps/C+FOPY9+XPorOa0wv9dyCpVj404fQe837cPDW74AyeWSmdsJqaIpvbBFYBSeSHC+hsnUHoBRmferTmPGxj5qVwQ2b0Pev1yM/fxGk55s3DV1nFdzEH5as3jLhqO9JwPrLhVtS4m0CvEdY/FGVsNuAVkyE0tw8q1ooQyWvVRAp0c5DSelDGK4IrU0ninYmjrYiUh6JKiZphDgABxKrB1sw4OZQyBS/e9asHswqjL24Z6xxNhGbfYwcE+p69S82HiCtdSf26WGCrlZNVOEIXEOux6w2QUO76mcIC/7BfcgtPRWZGfPBbhnKLaG89tdh1GcdJqoypi5AloZROOlVsKfNRWbmAmS6lsCZudCITP7UJf+C0zHr+ofQd917MPbQXXC6FqaqzIPvl4IUsrqF2TzIyUOOjsCZvRSlzeuw821nY8ZnbkR2fixX6Pq3/8a+jvfj0PeuhzNnUdAGE4br7PnILViGQ7d8GyJbj2kf/ITJ09bVo/tfv4WGl5yNkYdWY/yJNShv3gz2/ODzrQzgS8hSGVx2kZk2HVMuvhhT3vAGNJ1ztrEuOTKC7Ze9C1TXCDhZwJc17TZEBJsBn/l2T/CbLFSV0TwJWH/dBJHhgz9mycwaAfVDVdUxTQRalJIeVrMjEU+eMFJE0mRKFIMemCNNFyfTrhTdVjW6qq5bTxMR9hXmSGJ/OYsHBlvRmPPgj8lbTyoWYfnWR6uz3nSr3mRvYgRQ0cZo0RVF+V8sjqV4sIcZGiarhZQSqcW/qOIQAAvtH7oJotAQvWL/xy9C6bFfwZ4+J13kmUwFGfD296L9fW9E/riznnm0PTaEyvan4O5YD1Uchd0+A/WnX2iAnNXcjumfuR37Pv4mjN57B7JzlwPKN6QQVZ5PjceyBpGvC6IsfwDsecjNXY7K9s3Yfulp6Lr+J6g7Pk7Fpl/zL2CRQd8NX0J+0QqIbBYsg4kzsBxkpndj31c+jeJTazD7qzfBamw29qP1okvQetEl8AcOYfSh1Sg+/ji8vj6o0THAySLT1oHc/AVoeMmLkV+8uOY4yMFBPH3Gq1Bc+zTyK5aDK17tSC4ASkl4VuazpPgaxR5sPPua4Z+FgBWeagLwSpn/yQjvDSLnf19p3fQGaOmi0aTAVLuoI14rmSISjHSwWuGDgCF/MC9BU1pRA1xktvi0OT5WD7dgw3gOBAfHd/pPrpzuYf947lx9zRwBK4X/zPSKNU4sjq6QiL7i9NGUMXCtdOEwZzNZFry9O9HyhqsNsAKAutNeg+LDd00sU0gDL8UYf+KBiQFL+XB3b0Nl21pUtq6Ft3sLvN3b4O3fBTU2ElQ8ADhdn0PrW69Bw0vNkfDTP34TKlvXwz/UB9E8JSp4RHeuUIsVbVHGAdlZwFdhQcCD07UQ7t5d6LniAsz70R/hzJwTvb7z6i9C5Otw4F8/hezcRaB8fZAS+wpkZVBYcTxGH7wXmy84E7O/fjPyS1bUXoytU9By/oVoOf/CZ3wdlJ7eiC0XvQmlzduRXz4xWFlCoOSW/3lAqE9PtQpQvotnWzr4rAYsABDEUOBbBLggiL5pdJ5XiXhdjW5EX0kyPmWEl0bO1qrjEUdbVJtJGdFW4trURacKhJzw8ehoI4ZUA2T54NPHTt3IM3LyzTtLtjnPEKa+izXY0nsWkwR7DXilAZN28pK5IybSVGVH40WIuhY0nX9FzfeSW3ISRH0rlOsF8+0Ot1S/m1w9vJ0ba/48es8tKP7hF/D7euHt74Ec7AP7HkSuDqLQCLt5KjBlRtUGA96B3djzoUsw7Z/60fy6/6edLAKN570V+79wFfItHWDpG9EiM0G5OumeAWVyYF/FEbHvITO9C27vLmy/7JWYe+NdcGbFdsjT3vdxiLoG7P3MPyE7ay5EY3PYfxisILdkBco7d2LL61ah4+/fjfa3vqNmIvOfsgzdeTe2veXd8IvjyC1dEjgzJF0XAORIgJi/qoBPCz7Cnehov+bxLF+ICL4vv+WX+TQhaBPp6YtWymYVyhQkEo6LsRSCdUWxTDSlqtpJuwjL/kqzo615TbVEr5DoPQu2JwuJ/aUcVg+3oCAYrdnKdS8v7AaP0yclC8OGRNf6RA6VKl4Xa66XSTFiJBWI3puQOeiyBU0KEf/UpACw4fZuR+MFVwQEe2LJTOuG070McuhQrR5K6RKAeJ1U14rKjo2Qw6ZZavG3d2LgO9+Fu2srSDjITJ8HZ9YS2G1doEIzGHYosWCwVMh0zIIzeyH2fe5KjK3+qbGupldciuy8ZZAjw7EkRPvelabDqvJYHHJBUdro+sjMmA3/YD+2XXI2Ru6/y3hPx+UfQNeXv43K7l74/f0gK5BVKAmoio/szDkQmTz2XPtJbPq7V6L3mg9j+Fe/NNLRwy1u724c+No3sPX1l2HrpZeDhY3c/PkBWOkyHzCYBJosR26qjFy2szJyZR3Zz3rvfxvPgYUUwffwG8rxMaToMUG8zAAtnoDXQshLPdMUMTRDjeyYqw8ZtO5AU8FDM+iribi0OvIU28P9w614fLwBeVXENKfpvxY0zJwzUCnNhlLaJB5E5oNGXyAlA6HqNGqKOKg4OGNTHa5HbXqPIXjCdh0AUKVRWC3T0fLaf4jf4XsAq0BiICzUveh8FB+5D1brjMg7xSD9k3dOpwBv3y5UtjyJwgkxoVw4+ZUYeeAnEA1TUOuOYPJqQYOwAhUaQHUNGPjv/0T96edrfFYb6l/8Kgzc8jU43Qs1eQMBsKAqZl+tyNVDuV4AiNqhYc9Dtms+3L292PH2C9H1Lzeh5YI4BW19zZsgcgXsfO9lkK6PTEcn4PnhDcaHqGtAbskK+Af60XfD9ej/zn8ht2gJCiuOQd3yFXDmzIXd2gKybXDFhbtvPyo9vRh/6mmMPvAQylu2gbIFZGbOBDkOuCpf0MXLJOBI72CO6YwDylvfDGCuLXB06tefZ4AFCibeSEbF93FqvUP3KtAJkmVEVNeQ8RzLHmpSRH0UfTRkIqHZSnJbykwBmRLqAJ0D04CrAIk14w0ouhm4Lfzkefw05oz2f2RrazuE9GPRKiEaC4aE/XKNM4PBeVGN/xZRCqnEOj9OteljyMwFg0J70fK6q2A1xcMWDl5/NfIrTkH9GRcFPNaLz4d98+fB5TIo46SATJwfc4i8qlRGZccGA7BEoREkMsaw3WRbjukSGlh5ZKZ1o7xpLSrbNwQj3cIlu2BlOIgEiQETFlgTjgahYg5Kqho/LQaBPR9WczvcXXux84pLUVh5ArJz5kdvbT7vIsytb8S2t70eXNkNZ8asuBVHBUpQq6kZVlML2Fdwe/eg9OQ6HPQkKF+AyOYAssCeDzk2DlV2IWwHmfZ25Jcug6r6hVX91xNZuxL2sDM+/CKy5BbHsuEo9TcbHDGZEk6YHgK2sIYPlcZOHPfKt2aEbXIliZFHpjJeSxGTLT1VkJKJdE9OkCYaqQZqJ/Fqr3MlYV8lG8wWdOkbp8zwQA3ZC1RZxp8HGI6UrKd0idYNs32GatT41fQxTiEpLA9Q7bjzRBtONeVUrkThpHONYz/y8+9h/Infxdf69G7kVp4G78CeqJVGT0ujdFOTpJDlwNth8lhWa0fAJSmOtrd6rUbNxyn7S5YDOTiA0lOPJNLVWaBcQ8RNRd+dEFBlc7gC5fKANu6Kq35XA4OobNsKNTyClosuwdz/+hHsto6a87HxjJdiwQ/uBKRCefv2sBUHZmuSDF0emlqQnTMP2QUL4UzrhFXfBFGog9XcgmxXN/ILFyI7ezaoUBcKTDmVXA++SXocECsUaMuzkVh/3gAWAFhkoaI8lHzvUmK6SCTL9hpAVTvYWeexqnyLDAFJwuy1kwkw0vrvoEyOiWXcrpPslYMCCmDsreSxoVxAiR2sUgNfv9QavGgH59tFaEdb5T+S7SUxKJq8UGo/YJKnSvbyyRjElNYvqYNMNaT09+1G3fFnonDcadFhHX/8QXh7D6C89mFAxoLFxle8EVzxAhK/piXIfLAiIFsHt2+vSeDPXwFrWjdkcSy9x05R6v5W21v8vv3m+VHfFACgVGZ/ISzIkglYIpsH+ypsfLfgH+hDed1aiEI9Oq/5PObdejdmX38Lml5+AayGxtTzsf7EU7Dwx7+AVdeA8sZNQStOSq8kV8d7yfCytDMgOxtGl1qrmDL5RqMSCIIH9aFx+McTqPe5OK3yOQdYDIZFAoIEfOb/YcZLBTBW04OoaiMPJPq1otfJ2IYEVTCbALSSPWt6MzUnGlzbrAoeLzZgTakRELjltZ39Tj7r3lFyRW2jsBYdsUr5m9LcJ5PR1wQNxhNFU/FrOXwASgXN2t7QAJpf+14EvrvBMnDrf4AaGlHavA7Fx38TyxtOeTmc+SvgDQ0EkZEiKCYNHKuPkCu0s5DDQ4ZfMOXr4HQtghweNraV0xrAtQb26nGHZbIeqlyC8ryweVp7v7DAiQhLFOqDISl9/ShvfBqZzi50XvMFzL/jfrS9+Qpk5y0yJQbr12LXB68KGpu1Jb9sBRbfdS8y02dgfP3TgSBUByvtPIN+nh3OcE9vvicKLHyU+nuf1ZckPXcH6wo8hxcCQUHdK5VaRkx/FEm3h0T6N2G0ZXT3a5VEpbk9JC8m/e7HWmNs+FAMZJmxptSA0fGW8Te39Tx4ccueX2wdrw8EffqJzOkVxtrP0dJGRm1FcEJgSk9Z9QZcQMA/sAf5ZSeh/vRV0WEsb34SY7+9B5kZc6EqLoq//UV8/C0LhRPPhrt/TzjWnDX+CDVN2MLJw+vfD69vj/E9Ot2LIMeL0TGpAShtW6sN31Vjxsy0mca6/P4D8EfHNDM0RPunPM/g9VRxHKUNI7Bb2zHjE1/GgjvuR/vlV8FuMd09SxvXY+dV78Tm15yHfV/+d2x/+5uh3HJiH7qx+J77kV+4GKWn1gVtPOHMTNNIL+VGk3ZjQew1lmF1yFd8jst0o8Bzewr4cxqw4vIX95TLzomuoq8LXRfFqLXwSLl4a+QP+l1QpYBU2nP6nV8BdZDYWs7j6wMzsaixv//atu3vGS+LMytKmA4GTCYvlThhI/CSKa+ZCMBUAoxUIlJLTWEJ/qGDaHn9e42jO3DLfwTpFdmw22dg7Pf3gSsxed14zsWw8o1gzzc/M+04ZXLwDuxFecs6My2ctyxMjThdGpFifSPHRpDpnI26E05LgMvagKgmk68ELKiya2ixcouWo+uLn8KCH61G25veCXKcGOPcCkZ/8wB6r34/tly8CgO33wqroRlNZ70I4+vWY/P5r4K7u8fkz9rasPhXv0J+xTEoPrkuODerKW01LdZoB5XmtBB1KxAsIigl78yOjyyD798H8dy/nJ8HgBUW0RRhDOo9LujVAhgxKuwJmxpDszUBt6VbvaDGxgZmOpkEE0UosMIfxptwRmEQd3U92l0P75heNxuMUlK1rzcuTpWS1sIk1JWq9X5KBaUqRyWTXBgZaYu3bzcKx5+BpldeEl/86x7F8J23wpkxD+xLiPoWlDc9hZF7/ye+6Jcci7pTXgZv/54J7WZi0BDgkovy+sdMwFp6PKwpM6BKldT02kidFMBko7huMxrOXAW7Y3oMMl4FI/fdBat1qrae8HslG1yqGJXClle/HtP/8WMQhdiET40XcfDmb2HjeWdh25tfi/7vfgciV4fcwqUgOwtVcpFfuATjT67F+he/GKO/+XWCQ2vAkvvvRcvLz0XxsSfCIrQwIvaatF+aN6pAYsMA0QdKbnGV9PwDguj5cCk/PwArqCAyBAge8JNxkosF6J6anZcTcFsKCcO3RDppmMwlSPhkRTF87qDvYHmmiBs7nkaBGT1uLgIrI2WriQJrva7SKnqG2FGZ/lWpZnqp0VWVcBfwBwbQcvE7jMM1+INvhEM8nADgJIOsLIq/f8B4Xd1JZwd9cakRnva7BChbh8q2TWZkMn0WnBmzIcdGtdDS3GbFwUARf2gIxcceQf2JJ6L97R8wK5n33YXxp9bAamzVCP/w2JGAcl3wBDMO5NAg9v/bF7Fp1dnY9aH3we3djcyMbmTnLAh6Dt24mqg8P5A4+AqbXrUKxSeeSJD5WSz88R2YesU74B04GEkTUtO/lCpg1rK3DbrFU0cr5X+xbQt4noDV8wqw9C9cAvtY4Vwb4vOUTPpVEqC0O57haDoR10ATk/Da81IFGqw+38GA78COVPG10ZGe1tWQy2nglTJrLpqSzokILHmR6Oly+PAP9SO/4kQ0nvua6M/uzs0Yue9OZGbMixp9WQGivgXu3t4Ef7MQsByt+gij6Vj/XdQ3we3ZDtZSMwBwZs2DHBkJyXuGLJXh9feh0rMD5W2bUd6yAe7eXlj1Deh4xwcw79YHYLdNNQH2x7dB5BpSpSCBcLRimPgBgH+wD33f/Bo2nf9S7P7Ux+Ad6EN+0bJg3RyAtGE8Ua0uuz6cmV0QdQ3Y/HcXo7R+fc25OOf6f0euaxa8g0ORTONwYMVkA+DvQ8ilHvh3zJG72/NmsfE8XKpCcwZd7bn0ZEbwP8CiE+JmWJgdzVq7D1V7FEmbfMOa/5RArJQPe/gM47+qeR8Av/p+XSVfHYih28bovlq6UNTYGYr4/WBDQ+/39O5ss0WQEy/RlfTCgrt3D1pee3lQiQqX4bvvgDdwEJlps8AqHh8h6ppQ3vQ0ypvWIrdoJQCgsOJE5BathLunB3ZrW6raPRpxkatHuXcXypvXIb/8+BiwFixHeccwZGUDRC4Pu7UNuUUrkZ09H1ZTKzJtU5Gdtxj5ZcfCamyqWX/f17+I0d88AGfm7CAqBGosZlTFq1G793z4fei/6VbkF89FYcVxYF8GUgddVZ6McKsuEK4Pp6sblS3bsfHcCzDnhv9A86tejvEn1mLornswfN9q+MNFiKameH2q9uZBwUm213HH/80l+qJyMhAQoRCYJwHr+YRcXgW3UQa32RZ/mIDPG1+/5lwKLYMhqU2VJq29pwa4YksYHbhSW2r0KTtCO/H1cV0acGk+fDG4GL+Tfi1Cb7ehFPCCVtCMVOUMyPI4REs7ms7T3A9YYfjBu2G3z4DypLkuOwuvvx9Dd96OaSFgUS6PxpddhL2f+xCslo5geMOEMb8Nf3AA4+vXGIBV94KT0HLBy1B/8pnILT0G2TkLke2e94y+5t0ffz/6v/lV5BYuC/giVqavPwBmAVXyDBO/6sHJdLbDapoSt8AANRbZNaaA1QhOenDmzIO3dz+2v/29aHjJqRh7+FGUd/ci09oBu6MjnBDONREVAFhE8BRuBPtXNrjFoswUnuN1wEnAOnxOHAKLZPUFG/Q4QXxHEnUGDpxayzvBaO2pGvFFUZOI/bKM4aukAxdDF5UbJnrJSM5wVdDGrhMbIKXjjeHtWeNqnPC20h1LOelGGj5nCbi7dqD14rci2xWDw/Avf4TxNY8gt3hlLfiwgtU+DWN/WB0oXkO9VsNZq2DfcB1UpQLKZA4TGDCIbFS2m0OH88uOxdybf/knfbel9U9i77VXY/ShB5FbvDLozfNVAnCqB8ECfGmY+AGA3dIeWMxUW7SOBFQJXhAgwPWQae+AGi9j6O77YU1pRX7J8oAXCAWsaS1+ggm+4Le4hJstCbCwopvjJGA9z5eA2+JfSbIW5mTlOiWsK3xhQVTTHdaAy/BIT0Q3YZNzFG2hCma1qSKgR2IacEmY0Zj+t2iGIqIR9XrqSgYopaSP+vxCTtkXLRLjiguQhbZL32kcq0O3fRuivinWVCVPqqZWlJ5ei+Jjv0PdiS8BAGS756Fw7MkY+90DcGbNqbU40dPSQgMq2/70KeksfZQ3PIXSxvUo/vFhDN/3C/hDg8gtXhGM8fKVCVKJBmrlScMTCwAomwNLTtGpaUCVkspxsi1KSSCTQWZGZ2hVo2p4xhCuQWAIwq8U8ZUe88ZoJNzkMglYtaBFxUxl/F3Kcb4rOPMxSfbLWTfE1qKhqo9WUEZKcE1Cc0GQHKePIYBFZKlkI+JKposRqCXTvgl4KyYzQotdKWq5LEqmhLoBoLBQ3r4ZLRe/CbklK2Mu6BtfwcGb7kZuUSPKW7cAIvBxhxAgywp+2hl4B0Zw8Hs3RIAFAM2rXo/he++Op9UkSaxwG636FpS3bIG3f3eN8LO6VHZtQ2XbZpQ3b4A/cBD+0CAqO7aivHUz/KHAFz4zrRPZ2e1B2qr3hQIpqR2BKx5k0bSYEYU6sKfiCjImiKhUDDjJUWY1KSSQmv4JAQil1iiIT0mWP2ZLBM62k5fmJGAdDrRYCAjl/86qlF8xVtf0NpvweWa0c3Liip7KJSZAk/E3Con2hPe6EXVxTRoZgZfUwEuL8Cg57oTJyP7SAKwGxGpyyeB35ZZBTh5T3/lB4/hkOjrR9ZVPQ+TzUONFqEoJXKlAlUpQ5XGwG/w/09mN8uZN8Pv2w+6YBgBoetkFyM1fBjk0GA9bSOEMycmhsnMbSuvXGoBV3roJfdd/GZXeXXD39MI/2A9VLgUmgZYNq1AP0dCCbHNHDEiuNEExMURVBxD2VE0DtFWoD24qimqHkRrro5q/cfKzkpEYxUMhLEGouPzpPLv/zBkHeN7V/yYB638NXYoE2FI3qgruICU+S1m8x2KCDBuTDdAS5nUR8UAG96WR85QAO9JO0Im4rsQH6OAVD6RIAJie/RCbTyeI9yj1tAQqO3ag9e9eB6d7rrHKlle//hnmZww5NAjKxiO4yM6g4dSz0Petf0d+UQuYVUpqFqbVFRflrZvQePYro6fl0AD6vvVNZGZMg908BZnpXUFYotjk6ZKeWWnAURNpiWA2YaIBmgqFWgNGPToD1bTNMJA+UkzVFjosEvCU95ORcvmfsn7TRmlNwtRhOefJQ3Ak3CKwjxGp1HuJ6HxP8ROMlHQm4QJh6Iyq6vjob1RjZWM09CZEqJCHEX4mB5xOMDXYcElIqOY5obBWFR/IZNH2liv+V8fNamk1VOIA0PTyV4MyOaiqj1NChxVomRjI5FHaZFrNFI59IRrOPAdk58MR8sKcDJ3Sz8nK7A+t0WBVvx8OIiyuMfErmN9FTZsTDOuhCfVUKnk7JAhgP4HfXVHeq8elu1EITHJVkxHWXyZPDIyB8bM15eLPujPZMzsc+2M+40zFtSlNjUwgTAGJ06bwVNfPBilejbqiKC0tZUSC80IidUzwU7VRFdXsJxPg7tmLuhNOQeGY440/D997N9jzYDU1waqrBzlZiFwOlM1ChKZzlMnEhn0pS90JJ6P+1LMw9ujvkQlTxdrojGA1tKK0fj1UqQSRzwebZ1nIz1+C0hNPAs1Tasj6w0VQmCjqgUagK0COlxIpYQGAFYAaUXolkBM1hLQozwAqWlch75NgvtNhu0xEsCcciTa5TALW/yIcrSgFSfyA9OgBT4p3Ojn5EcXUxQn3zoh3SqSKEcelA4o2zQdszjY0wAuo4bsmTA15AvA8HIhxAApyYAjN564y9n38ycex5aJXwmpqhigUwqkyNshxQJlMDFwZJ/iZy4XAxZj5ic/DmdkdrSu/5BgM3X13YB2s0illUaiH29OD8paNKKw8Nj5hW9sgK67pUjEBQB05FSRDPAsfUGOmt3pu8cLgu5Fsyks4BQAnINOjr5JESSn3K1LxR6UjAKkmmapJwPrrLQzAJoIgQEmC59MNNvxvs7LeJ4T4uGKuT00VEz7vOnCBTYI+sjROcl2YALyQmIZDCWDipKYrEXUY4Ejw+/qRnb8IU173RmNXxv7wMJDJIjt7XqC9Cq0EWClwxYM/XgaUDJwblARLCfZ9lLbtR/0pp6Hj798Vp3bLVgaVRaYam+boh7DgDY6gtHGDAVhWUzO44sfvnQigJgKqJEjpPJcvwV5M0suREQz8909g5+uNKCr1/YcBqvDnV5SiL/nw9wfDnKzEvPDJZRKw/rrUFgQxJMNn5uuI1fcExNUMrAIwhzFBxKWnhDqAUGKwBWIFfTRGPg28jCnPtdFXXDGcOKqKgE0IVHr3oOvz10VpGBAMmDh0+23ItHeGU3OsSBBKdk0gZyyqVEF5s6mpajz7ZcgtWQ6vrx92S0tqZBT0Lim4PWZfYm7BwtgJlOiwKWANSKW9VgchKwuuuPD6+jHww5/gwL/fgMqu3cjOmR0Ac1pKybVgG3RnERTzIBj3MalPMGh9dZTcJExNku5/S3qrer3vB3AVg+cyywstog0WCfOmyzGJDmPcWOyqoKQ2iix6HSU80RPrkAm7GNZsjXU3S/21OpEvg/e5B/pQWL4SU9/5LmMf+79zI8Ye/gMyUzpqtj36fYJHpm0qRlf/OnAMrZ50+TxaVl0Ib/eeQGQmUWv7qwBYGXiHDpnE+3HHIzN9JtRYqfZ9kYOnOUKME8cK2j7rFtC57tkYuvNX2Hj2Bdh11dVw+wfhzJ4dOKKmNLGn9f2FgXQfs3ofs5rtK75YMdZPXimTgHXUpo1E9KNxVVw66o2/JSusR2iiF6ZdpJziKJp2Ueo+4FwLSkgCmO6hnrJOgoDbswdtb7oMsGICmJVC/003wZ7WGUyQ0aqfqRdu4iHqGjG+YQOG7vmFsfst550Pq3kKVMWttbYJwZTsLNxe030009aO3Ow5kEMjCasdDZxUyo2BY/8yowqrHzs7A/fAQbgDQ3DmzoXd1hakuJJTvgMzWAtncfdYoI+4kue7LP8N4JHDRZ+TyyRgHRWLRRY86aMsKzeDrJMU03k28AeHRO3ZyymglXA/PSJ46aB0OACTKSAWRhfewQFk58xF2xtN7mropz9B8dHH4bRNrblw+Zk8JIOcAoZ/afYB5hYuQt3xJ8Ldvdc0C9QeVn0jypu3Qo6OGu/NzlsAf3isFqBSQCUGdtNoMSkFUQpQvgTl8rDqQwua6v7K9GiqegERaIuA/3ah1FzJ9DkAo5OJ3yRgPYuiLIYggSAlZIDprjElT97sjp9jKXyNgQ10pIhLomZydRK8UlOdlJQodb2JKMzb14ep73o3rIaGeJNYYf9Xvw67pT2ySU4HBzpMWsjITpuB4XsfRGXnTmOXWy98DdRw0TDx08FU5OtR2bkb42vXGu+rO+648HO5Zr90gOIJtjcy/UtaK6eBvZqQBthDoJtAeIMSciFDfjs8CpPLJGA9yyMuABUo9Ev3PqXUe6XipQBdQsDPA4KWa7mutAsnKXhM+LmzrOXHDBAzLub4dWp0HM60Tky9wnQUHfjB7Ri+fzUynTPBPptj65PzBVOfD8fQ5wpwd+/DoVtvMwHrNRchu3Ax/MERM4Ks7gNZ8A8NobjmSeN9DWecBqtlCtS4a+6bosNGlkoeIVpNpJD6F6KEBZCAAP5IhCsBNVcx3ipBtxy+7DC5TALWs5DXEiDkKD7cEnybYn6VkljMyv6yRdgnAlFh7QrSIqSUwRkRgOkK+5QL2JyFR/AODqLulJONNhoA6PvWTci0tAcWKCodDJ/Jgz2JTMdUDP/yPvPky+XQ/NJz4PbsSeXWWDJEXQNK6zfC3bsXw/fci72fuw67PvARWE3NwWBSOXEEdcQo6jBK9OqgByEAJruSd0vftj33VI/FiUz4KgPuJEz93y+Tsoa/0VI90SVjE6T1QUeoaypQF0jgIgfitUxc48BilNBTRKCa9tT4vy5joBTjvkxbO9wdu7Hh7PNAGRuivg5gRmXHHmRmzgwdB6h2OxKEzuEuXntKO0YfWYPRhx9GwymnRM93vOty9N34XaiKl+qTle3qxthDj2DjORfA3b0HsliC1VAPZ9ZMQDFYxew3px2r5HHj1E3XdoJADAgCpFT3Q9EPlIOfOZXSPte2oex8ep44uUwC1vMGuIghGBWX1O0VpW4ntj5igU4SQhzLxK8A0zImPrKym9KBTPfGSsq0QABl83D390OOjAaiT98HiOB0TgtkB7pJH00MUZyGWpEnmIVMWzt6P/Rx5JcsAlk2yMmA7AyyXbOhxktxvK/r04SALFXAFReZ6TOQEVaQonnqyOCEZwBQ0XdAAPMuUuoeRXiUQX9wpb+WPBu2w1BCgEnEpo6TyyRgTaaNQUoowdugaJtFdAtI/iMDZwllncfEL2XQch2ISJnOobXOB4cBsWpkoiRgO7CmmANCgzRKTXCB/4k7qBTslikob92FsT+ujXJYyjhwumYCdjZQ0CcjJaUAKwMqZILnFf9p4MQpwSERRHzcdoD4AfbFXUrxjzJCshQEkBW0UE2SJpOANbkcOeoiowdQ3S+UuF+yAIiPtUmer1icI5hPZhI20tJHTABiNMFP/UKvcSFNuej/nECj4kE0NcNpak4AI2PiHZgggnqG0VPssBO0AxAYQmKtsvAgCf6hkvzrgD/jgLS3Jp2oJgFrcvmLMF5BsMBrlOA1Vrn0ySypztFCwzlWRZ0BIU4h8HwAdjz8icE4DA82UXaX9v/Dvf7PDSn/HHA6wiopsckiwKkeJfw/CL9wT2O5uLpEpS1jjc1wyI+FCIRJBn0SsCaXvxaACcVgm/cq8M0k+WbfErDKspMIXZRTS8H8QrA4gYBjg2yTAkv6sC9RId0U8BmBw1/ywuY//202ETIglKAgOGqLWcfAEyz4Uce1HstIa3epUOxhZoYUyHiE8axK5MWTyyRgTS5/1YXDTmmdT1IKe4mw1yL1e4a6UUoBAs20iJeB5XKL+QUQ1jEMzBdAvhqHqapvQNCwe2RM4vQU8hmDEz2zl8aeFKxFTQQhGFDwXaV2DLN6opmsdR7jKSJ+iglbJSgcnQUIGYp3w5RQib804k4uk4A1ufx5cVfksxXnN4qxG4TdQqlfZqWHopOBR/aUAmieD7UYzIsttuYycReY5wqgg0kQWEWaMAUGUdWWhf/8KEsf8soaHAkrrERWk1kO2O5g1NohAnYqpl4h1HaQ2uiWrY0ZJbYdUu7enVYRp9uNKFfTP33vKRyLNglQk4A1uTxbkketbB96bSngEAiHfJaPKFLIKwtl5WHcLTlT8i1T85WxTs/KTnctdDF4agb2FKVkB4haCCJLgKMUZ0FwANgEZAA4HJxPIkEjKQJ8AB4DHgAfSlQEwQWxKxmu7VcGIaxDCnSQoA5Iot58ZWyfm8nug+/vJeGUJFkASRBJeJ4FEeJaJhqrNglLz4fl/w8AucrtX63V9OsAAAAASUVORK5CYII="], +); + +plan tests => scalar @testcases * 2, need 'mod_data'; + +foreach my $t (@testcases) { + ## Small query ## + my $r = GET($t->[0]); + + # Checking for return code + ok t_cmp($r->code, 200, "Checking return code is '200'"); + # Checking for content + ok t_is_equal($r->content, $t->[1]); +} diff --git a/debian/perl-framework/t/modules/dav.t b/debian/perl-framework/t/modules/dav.t new file mode 100644 index 0000000..73046cd --- /dev/null +++ b/debian/perl-framework/t/modules/dav.t @@ -0,0 +1,168 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use HTTP::Date; + +## +## mod_dav tests +## + +plan tests => 19, [qw(dav HTTP::DAV)]; +require HTTP::DAV; + +my $vars = Apache::Test::vars(); +my $dav = HTTP::DAV->new; +my $server = "$vars->{servername}:$vars->{port}"; + +my $htdocs = Apache::Test::vars('documentroot'); +my $response; +my $dir = "modules/dav"; +my $uri = "/$dir/dav.html"; +my $body = <<CONTENT; +<html> + <body> + <center> + <h1>mod_dav test page</h1> + this is a page generated by<br> + the mod_dav test in the Apache<br> + perl test suite.<br> + </center> + </body> +</html> +CONTENT + +## make sure its clean before we begin ## +unlink "$htdocs$uri" if -e "$htdocs$uri"; +mkdir "$htdocs/$dir", oct('755') unless -e "$htdocs/$dir"; + +Apache::TestUtil::t_chown("$htdocs/$dir"); + +## set up resource and lock it ## +my $resource = $dav->new_resource( -uri => "http://$server$uri"); +$response = $resource->lock; +print "resource lock test:\n"; +ok $response->is_success; + +## write new resource ## +$response = $resource->put($body); +print "DAV put test:\n"; +ok $response->is_success; + +## get properties ## +## Wait until none of the returned time +## properties equals "now" +sleep(2); +$response = $resource->propfind; +print "getting DAV resource properties:\n"; +ok $response->is_success; + +my $createdate = $resource->get_property( "creationdate" ); +my $lastmodified = $resource->get_property( "getlastmodified" ); +my $now = HTTP::Date::time2str(time()); +print "created: $createdate\n"; +print "modified: $lastmodified\n"; +print "now: $now\n"; +ok $createdate ne $now; +ok $createdate eq $lastmodified; + +## should be locked ## +print "resource lock status test:\n"; +ok $resource->is_locked; + +## unlock ## +print "resource unlock test:\n"; +$response = $resource->unlock; +ok $response->is_success; + +## should be unlocked ## +print "resource lock status test:\n"; +$response = $resource->is_locked; +ok !$resource->is_locked; + +## verify new resource using regular http get ## +my $actual = GET_BODY $uri; +print "getting uri...\nexpect:\n->$body<-\ngot:\n->$actual<-\n"; +ok $actual eq $body; + + +## testing with second dav client ## +my $d2 = HTTP::DAV->new; +my $r2 = $d2->new_resource( -uri => "http://$server$uri"); + +## put an unlocked resource (will work) ## +$response = $r2->get; +my $b2 = $r2->get_content; +$b2 =~ s#<h1>mod_dav test page</h1>#<h1>mod_dav test page take two</h1>#; + +print "putting with 2nd dav client (on unlocked resource)\n"; +$response = $r2->put($b2); +ok $response->is_success; + +$actual = GET_BODY $uri; +print "getting new uri...\nexpect:\n->$b2<-\ngot:\n->$actual<-\n"; +ok $actual eq $b2; + +## client 1 locks, client 2 should not be able to lock ## +print "client 1 locking resource\n"; +$response = $resource->lock +( + -owner => 'mod_dav test client 1', + -depth => 'Infinity', + -scope => 'exclusive', + -type => 'write', + -timeout => 120 +); +ok $response->is_success; + +print "client 2 attempting to lock same resource\n"; +$response = $r2->lock +( + -owner => 'mod_dav test client 2', + -depth => 'Infinity', + -scope => 'exclusive', + -type => 'write', + -timeout => 120 +); +ok !$response->is_success; + +## client 2 should not be able to put because the resource is already locked by client 1 ## +$response = $r2->get; +my $b3 = $r2->get_content; +$b3 =~ s#mod_dav#f00#g; + +print "client 2 attempting to put resource locked by client 1\n"; +$response = $r2->put($b3); +ok !$response->is_success; + +print "verifying all is well through http\n"; +$actual = GET_BODY $uri; +print "getting new uri...\nexpect:\n->$b2<-\ngot:\n->$actual<-\n"; +ok $actual ne $b3; +ok $actual eq $b2; + +## delete resource ## +$response = $resource->forcefully_unlock_all; ## trusing this will work +$response = $resource->delete; +print "resource delete test:\n"; +ok $response->is_success; + +$actual = GET_RC $uri; +print "expect 404 not found got: $actual\n"; +ok $actual == 404; + +## PR 49825 ## +my $user_agent = $dav->get_user_agent; +# invalid content-range header +$user_agent->default_header('Content-Range' => 'bytes 1-a/44' ); +$response = $resource->put($body); +$actual = $response->code; +print "PR 49825: expect 400 bad request got: $actual\n"; +ok $actual == 400; +$user_agent->default_header('Content-Range' => undef); + +## clean up ## +rmdir "$htdocs/$dir/.DAV" or print "warning: could not remove .DAV dir: $!"; +rmdir "$htdocs/$dir" or print "warning: could not remove dav dir: $!"; diff --git a/debian/perl-framework/t/modules/deflate.t b/debian/perl-framework/t/modules/deflate.t new file mode 100644 index 0000000..3b368ce --- /dev/null +++ b/debian/perl-framework/t/modules/deflate.t @@ -0,0 +1,137 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my @server_deflate_uris=("/modules/deflate/index.html", + "/modules/deflate/apache_pb.gif", + "/modules/deflate/asf_logo_wide.jpg", + "/modules/deflate/zero.txt", + ); +my $server_inflate_uri="/modules/deflate/echo_post"; +my @server_bucketeer_uri = ("/modules/deflate/bucketeer/P.txt", + "/modules/deflate/bucketeer/F.txt", + "/modules/deflate/bucketeer/FP.txt", + "/modules/deflate/bucketeer/FBP.txt", + "/modules/deflate/bucketeer/BB.txt", + "/modules/deflate/bucketeer/BBF.txt", + "/modules/deflate/bucketeer/BFB.txt" + ); + +my $cgi_tests = 3; +my $tests_per_uri = 4; +my $tests = $tests_per_uri * (@server_deflate_uris + @server_bucketeer_uri) + $cgi_tests; +my $vars = Apache::Test::vars(); +my $module = 'default'; + +plan tests => $tests, need 'deflate', 'echo_post'; + +print "testing $module\n"; + +my @deflate_headers; +push @deflate_headers, "Accept-Encoding" => "gzip"; + +my @deflate_headers_q0; +push @deflate_headers_q0, "Accept-Encoding" => "gzip;q=0"; + +my @inflate_headers; +push @inflate_headers, "Content-Encoding" => "gzip"; + +if (have_module('bucketeer')) { + push @server_deflate_uris, @server_bucketeer_uri; +} +else { + skip "skipping bucketing deflate tests without mod_bucketeer" + foreach (1 .. ($tests_per_uri * @server_bucketeer_uri)); +} +for my $server_deflate_uri (@server_deflate_uris) { + my $original_str = GET_BODY($server_deflate_uri); + + my $deflated_str = GET_BODY($server_deflate_uri, @deflate_headers); + my $deflated_str_q0 = GET_BODY($server_deflate_uri, @deflate_headers_q0); + + my $inflated_str = POST_BODY($server_inflate_uri, @inflate_headers, + content => $deflated_str); + + ok $original_str eq $inflated_str; + ok $original_str eq $deflated_str_q0; + my $resp = POST($server_inflate_uri, @inflate_headers, + content => "foo123456789012346"); + if (have_min_apache_version("2.5")) { + ok($resp->code, 400, "did not detect invalid compressed request body for $server_deflate_uri"); + } + elsif (have_min_apache_version("2.4.5")) { + ok($resp->content, '!!!ERROR!!!', "did not detect invalid compressed request body for $server_deflate_uri"); + } + else { + ok($resp->code, 200, "invalid response for $server_deflate_uri"); + } + + # Disabled because not working reliably. + # If the compressed data it big enough, a partial response + # will get flushed to the client before the trailing spurious data + # is found. + # + #if (have_min_apache_version("2.5")) { + # $resp = POST($server_inflate_uri, @inflate_headers, + # content => $deflated_str . "foobarfoo"); + # ok($resp->code, 400, "did not detect spurious data after compressed request body for $server_deflate_uri"); + #} + #elsif (have_min_apache_version("2.4.5")) { + # # The "x 1000" can be removed, once r1502772 is ported back to 2.4.x + # $resp = POST($server_inflate_uri, @inflate_headers, + # content => $deflated_str . ("foobarfoo" x 1000)); + # ok($resp->content, '/.*!!!ERROR!!!$/', "did not detect spurious data after compressed request body for $server_deflate_uri"); + #} + #else { + # ok($resp->code, 200, "invalid response for $server_deflate_uri"); + #} + + my $broken = $deflated_str; + my $offset = (length($broken) > 35) ? 20 : -15; + substr($broken, $offset, 15, "123456789012345"); + $resp = POST($server_inflate_uri, @inflate_headers, + content => $broken); + if (have_min_apache_version("2.5")) { + ok($resp->code, 400, "did not detect broken compressed request body for $server_deflate_uri"); + } + elsif (have_min_apache_version("2.4.5")) { + ok($resp->content, '/.*!!!ERROR!!!$/', "did not detect broken compressed request body for $server_deflate_uri"); + } + else { + ok($resp->code, 200, "invalid response for $server_deflate_uri"); + } +} + +# mod_deflate fixes still pending to make this work... +if (have_module('cgi') && have_min_apache_version('2.1.0')) { + my $sock = Apache::TestRequest::vhost_socket('default'); + + ok $sock; + + Apache::TestRequest::socket_trace($sock); + + $sock->print("GET /modules/cgi/not-modified.pl HTTP/1.0\r\n"); + $sock->print("Accept-Encoding: gzip\r\n"); + $sock->print("\r\n"); + + # Read the status line + chomp(my $response = Apache::TestRequest::getline($sock) || ''); + $response =~ s/\s$//; + + ok t_cmp($response, qr{HTTP/1\.. 304}, "response was 304"); + + do { + chomp($response = Apache::TestRequest::getline($sock) || ''); + $response =~ s/\s$//; + } + while ($response ne ""); + + # now try and read any body: should return 0, EOF. + my $ret = $sock->read($response, 1024); + ok t_cmp($ret, 0, "expect EOF after 304 header"); +} else { + skip "skipping 304/deflate tests without mod_cgi and httpd >= 2.1.0" foreach (1..$cgi_tests); +} diff --git a/debian/perl-framework/t/modules/digest.t b/debian/perl-framework/t/modules/digest.t new file mode 100644 index 0000000..4d2e76c --- /dev/null +++ b/debian/perl-framework/t/modules/digest.t @@ -0,0 +1,176 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil qw(t_cmp t_write_file); +use File::Spec; + +plan tests => 13, need need_lwp, + need_module('mod_auth_digest'), + need_min_apache_version('2.0.51'); + +my ($no_query_auth, $query_auth, $bad_query); + +# write out the authentication file +my $file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'realm1'); +t_write_file($file, <DATA>); + +my $url = '/digest/index.html'; +my $query = 'try=til%7Ede'; + +{ + my $response = GET $url; + + ok t_cmp($response->code, + 401, + 'no user to authenticate'); +} + +{ + # bad pass + my $response = GET $url, + username => 'user1', password => 'foo'; + + ok t_cmp($response->code, + 401, + 'user1:foo not found'); +} + +{ + # authenticated + my $response = GET $url, + username => 'user1', password => 'password1'; + + ok t_cmp($response->code, + 200, + 'user1:password1 found'); + + # set up for later + $no_query_auth = $response->request->headers->authorization; +} + +# now that we know normal digest auth works, play with the query string + +{ + # add a query string + my $response = GET "$url?$query", + username => 'user1', password => 'password1'; + + ok t_cmp($response->code, + 200, + 'user1:password1 with query string found'); + + # set up for later + $query_auth = $response->request->headers->authorization; +} + +{ + # do the auth header ourselves + my $response = GET "$url?$query", Authorization => $query_auth; + + ok t_cmp($response->code, + 200, + 'manual Authorization header query string'); +} + +{ + # remove the query string from the uri - bang! + (my $noquery = $query_auth) =~ s!$query!!; + + my $response = GET "$url?$query", + Authorization => $noquery; + + ok t_cmp($response->code, + 400, + 'manual Authorization with no query string in header'); +} + +{ + # same with changing the query string in the header + ($bad_query = $query_auth) =~ s!$query!something=else!; + + my $response = GET "$url?$query", + Authorization => $bad_query; + + ok t_cmp($response->code, + 400, + 'manual Authorization header with mismatched query string'); +} + +{ + # another mismatch + my $response = GET $url, + Authorization => $query_auth; + + ok t_cmp($response->code, + 400, + 'manual Authorization header with mismatched query string'); +} + +# finally, the MSIE tests + +{ + if (have_min_apache_version("2.5.0")) { + skip "'AuthDigestEnableQueryStringHack' has been removed in r1703305"; + } + else + { + # fake current MSIE behavior - this should work as of 2.0.51 + my $response = GET "$url?$query", + Authorization => $no_query_auth, + 'X-Browser' => 'MSIE'; + + ok t_cmp($response->code, + 200, + 'manual Authorization with no query string in header + MSIE'); + } +} + +{ + # pretend MSIE fixed itself + my $response = GET "$url?$query", + username => 'user1', password => 'password1', + 'X-Browser' => 'MSIE'; + + ok t_cmp($response->code, + 200, + 'a compliant response coming from MSIE'); +} + +{ + # this still bombs + my $response = GET "$url?$query", + Authorization => $bad_query, + 'X-Browser' => 'MSIE'; + + ok t_cmp($response->code, + 400, + 'manual Authorization header with mismatched query string + MSIE'); +} + +{ + # as does this + my $response = GET $url, + Authorization => $query_auth, + 'X-Browser' => 'MSIE'; + + ok t_cmp($response->code, + 400, + 'manual Authorization header with mismatched query string + MSIE'); +} + +{ + # no hack required + my $response = GET $url, + username => 'user1', password => 'password1', + 'X-Browser' => 'MSIE'; + + ok t_cmp($response->code, + 200, + 'no query string + MSIE'); +} + +__DATA__ +# user1/password1 +user1:realm1:4b5df5ee44449d6b5fbf026a7756e6ee diff --git a/debian/perl-framework/t/modules/dir.t b/debian/perl-framework/t/modules/dir.t new file mode 100644 index 0000000..51e632e --- /dev/null +++ b/debian/perl-framework/t/modules/dir.t @@ -0,0 +1,115 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; + +## +## mod_dir tests +## + +my @index = qw(1 2 3 4 5 6 7 8 9 0); +my @bad_index = qw(foo goo moo bleh); +my $htdocs = Apache::Test::vars('documentroot'); +my $htaccess = "$htdocs/modules/dir/htaccess/.htaccess"; +my $url = "/modules/dir/htaccess/"; +my ($actual, $expected); + +#XXX: this is silly; need a better way to be portable +sub my_chomp { + $actual =~ s/[\r\n]+$//s; +} + +plan tests => @bad_index * @index * 5 + @bad_index + 5 + 3, need_module 'dir'; + +foreach my $bad_index (@bad_index) { + + print "expecting 403 (forbidden) using DirectoryIndex $bad_index\n"; + $expected = (have_module 'autoindex') ? 403 : 404; + write_htaccess("$bad_index"); + $actual = GET_RC $url; + ok ($actual == $expected); + + foreach my $index (@index) { + + print "running 5 test gambit for \"$index.html\"\n"; + ## $index will be expected for all + ## tests at this level + $expected = $index; + + write_htaccess("$index.html"); + $actual = GET_BODY $url; + ok ($actual eq $expected); + + write_htaccess("$bad_index $index.html"); + $actual = GET_BODY $url; + ok ($actual eq $expected); + + write_htaccess("$index.html $bad_index"); + $actual = GET_BODY $url; + ok ($actual eq $expected); + + write_htaccess("/modules/alias/$index.html"); + $actual = GET_BODY $url; + ok ($actual eq $expected); + + write_htaccess("$bad_index /modules/alias/$index.html"); + $actual = GET_BODY $url; + ok ($actual eq $expected); + } +} + +print "DirectoryIndex /modules/alias/index.html\n"; +$expected = "alias index"; +write_htaccess("/modules/alias/index.html"); +$actual = GET_BODY $url; +my_chomp(); +ok ($actual eq $expected); + +print "expecting 403 for DirectoryIndex @bad_index\n"; +$expected = (have_module 'autoindex') ? 403 : 404; +write_htaccess("@bad_index"); +$actual = GET_RC $url; +ok ($actual == $expected); + +$expected = $index[0]; +my @index_html = map { "$_.html" } @index; +print "expecting $expected with DirectoryIndex @index_html\n"; +write_htaccess("@index_html"); +$actual = GET_BODY $url; +ok ($actual eq $expected); + +print "expecting $expected with DirectoryIndex @bad_index @index_html\n"; +write_htaccess("@bad_index @index_html"); +$actual = GET_BODY $url; +ok ($actual eq $expected); + +unlink $htaccess; +print "removed .htaccess (no DirectoryIndex), expecting default (index.html)\n"; +$expected = "dir index"; +$actual = GET_BODY $url; +my_chomp(); +ok ($actual eq $expected); + +# DirectorySlash stuff +my $res = GET "/modules/dir", redirect_ok => 0; +ok ($res->code == 301); +$res = GET "/modules/dir/htaccess", redirect_ok => 0; +ok ($res->code == 403); + +if (!have_min_apache_version('2.5.1')) { + skip("missing DirectorySlash NotFound"); +} +else { + $res = GET "/modules/dir/htaccess/sub", redirect_ok => 0; + ok ($res->code == 404); +} + + +sub write_htaccess { + my $string = shift; + + open (HT, ">$htaccess") or die "cannot open $htaccess: $!"; + print HT "DirectoryIndex $string"; + close (HT); +} diff --git a/debian/perl-framework/t/modules/directorymatch.t b/debian/perl-framework/t/modules/directorymatch.t new file mode 100644 index 0000000..7b4fa38 --- /dev/null +++ b/debian/perl-framework/t/modules/directorymatch.t @@ -0,0 +1,26 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +## +## directorymatch tests +## + +my @ts = ( + { url => "/index.html", code => 200, hname => "DMMATCH1"}, + # TODO: PR41867 (DirectoryMatch matches files) +); + +plan tests => 2* scalar @ts, have_module 'headers'; + +for my $t (@ts) { + my $r = GET $t->{'url'}; + ok t_cmp($r->code, $t->{code}, "code for " . $t->{'url'}); + ok t_cmp($r->header($t->{'hname'}), "1", "check for " . $t->{'hname'}); +} + + diff --git a/debian/perl-framework/t/modules/env.t b/debian/perl-framework/t/modules/env.t new file mode 100644 index 0000000..c1de003 --- /dev/null +++ b/debian/perl-framework/t/modules/env.t @@ -0,0 +1,40 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; + +## +## mod_env tests +## + +my %test = ( + 'host' => $ENV{APACHE_TEST_HOSTNAME}, + 'set' => "mod_env test environment variable", + 'setempty' => '', + 'unset' => '(none)', + 'type' => '(none)', + 'nothere' => '(none)' +); + +if (Apache::TestConfig::WIN32) { + #what looks like a bug in perl 5.6.1 prevents %ENV + #settings to be inherited by process created with + #Win32::Process::Create. the test works fine if APACHE_TEST_HOSTNAME + #is set in the command shell environment + delete $test{'host'}; +} + +plan tests => (keys %test) * 1, need_module('env', 'include'); + +my ($actual, $expected); +foreach (sort keys %test) { + $expected = $test{$_}; + sok { + $actual = GET_BODY "/modules/env/$_.shtml"; + $actual =~ s/[\r\n]+$//s; + print "# $_: /modules/env/$_.shtml\n", + "# $_: EXPECT ->$expected<- ACTUAL ->$actual<-\n"; + return $actual eq $expected; + }; +} diff --git a/debian/perl-framework/t/modules/expires.t b/debian/perl-framework/t/modules/expires.t new file mode 100644 index 0000000..5c992c2 --- /dev/null +++ b/debian/perl-framework/t/modules/expires.t @@ -0,0 +1,307 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Time::Local; + +## mod_expires tests +## +## extra.conf.in: +## +## <Directory @SERVERROOT@/htdocs/modules/expires> +## ExpiresActive On +## ExpiresDefault "modification plus 10 years 6 months 2 weeks 3 days 12 hours 30 minutes 19 seconds" +## ExpiresByType text/plain M60 +## ExpiresByType image/gif A120 +## ExpiresByType image/jpeg A86400 +## </Directory> +## + +## calculate "modification plus 10 years 6 months 2 weeks 3 days 12 hours 30 minutes 19 seconds" +my $expires_default = calculate_seconds(10,6,2,3,12,30,19); + +my $htdocs = Apache::Test::vars('documentroot'); +my $htaccess = "$htdocs/modules/expires/htaccess/.htaccess"; +my @page = qw(index.html text.txt image.gif foo.jpg); +my @types = qw(text/plain image/gif image/jpeg); +my @directive = qw(ExpiresDefault ExpiresByType); + +## first the settings in extra.conf.in (server level) +my %exp = default_exp(); + +my %names = + ( + 'Date' => 'access', + 'Expires' => 'expires', + 'Last-Modified' => 'modified', + 'Content-Type' => 'type', + ); + +my %month = (); +my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +@month{@months} = 0..@months-1; + +plan tests => (@page * 2) + ((((@page * 3) * @types) + @page) * 2) + @page, + have_module 'expires'; + +foreach my $page (@page) { + my $head = HEAD_STR "/modules/expires/$page"; + $head = '' unless defined $head; + print "# debug: $page\n$head\n"; + ok ($head =~ /^HTTP\/1\.[1|0] 200 OK/); + ok expires_test(1,$head); +} + +unlink $htaccess if -e $htaccess; +## with no .htaccess file, everything should be inherited here ## +foreach my $page (@page) { + my $head = HEAD_STR "/modules/expires/htaccess/$page"; + ok expires_test(1,$head); +} + +## testing with .htaccess ## +foreach my $on_off (qw(On Off)) { + + my $ExpiresActive = "ExpiresActive $on_off\n"; + write_htaccess($ExpiresActive); + %exp = default_exp(); + + ## if ExpiresActive is 'On', everything else will be inherited ## + foreach my $page (@page) { + my $head = HEAD_STR "/modules/expires/htaccess/$page"; + print "# ---\n# $ExpiresActive"; + ok expires_test(($on_off eq 'On'),$head); + } + + foreach my $t (@types) { + + my ($head, $directive_string, $gmsec, $a_m, + $ExpiresDefault, $ExpiresByType); + + ## testing with just ExpiresDefault directive ## + $a_m = (qw(A M))[int(rand(2))]; + ($gmsec, $ExpiresDefault) = get_rand_time_str($a_m); + %exp = default_exp(); + set_exp('default', "$a_m$gmsec"); + $directive_string = $ExpiresActive . + "ExpiresDefault $ExpiresDefault\n"; + write_htaccess($directive_string); + foreach my $page (@page) { + $head = HEAD_STR "/modules/expires/htaccess/$page"; + print "#---\n# $directive_string"; + ok expires_test(($on_off eq 'On'), $head); + } + + ## just ExpiresByType directive ## + $a_m = (qw(A M))[int(rand(2))]; + ($gmsec, $ExpiresByType) = get_rand_time_str($a_m); + %exp = default_exp(); + set_exp($t, "$a_m$gmsec"); + $directive_string = $ExpiresActive . + "ExpiresByType $t $ExpiresByType\n"; + write_htaccess($directive_string); + foreach my $page (@page) { + $head = HEAD_STR "/modules/expires/htaccess/$page"; + print "# ---\n# $directive_string"; + ok expires_test(($on_off eq 'On'), $head); + } + + ## both ## + $a_m = (qw(A M))[int(rand(2))]; + ($gmsec, $ExpiresDefault) = get_rand_time_str($a_m); + %exp = default_exp(); + set_exp('default', "$a_m$gmsec"); + $a_m = (qw(A M))[int(rand(2))]; + ($gmsec, $ExpiresByType) = get_rand_time_str($a_m); + set_exp($t, "$a_m$gmsec"); + $directive_string = $ExpiresActive . + "ExpiresDefault $ExpiresDefault\n" . + "ExpiresByType $t $ExpiresByType\n"; + write_htaccess($directive_string); + foreach my $page (@page) { + $head = HEAD_STR "/modules/expires/htaccess/$page"; + print "# ---\n# $directive_string"; + ok expires_test(($on_off eq 'On'), $head); + } + } +} + +## clean up ## +unlink $htaccess if -e $htaccess; + +sub set_exp { + my $key = shift; + my $exp = shift; + + if ($key eq 'all') { + foreach (keys %exp) { + $exp{$_} = $exp; + } + } else { + $exp{$key} = $exp; + } +} + +sub get_rand_time_str { + my $a_m = shift; + my ($y, $m, $w, $d, $h, $mi, $s, $rand_time_str); + $y = int(rand(2)); + $m = int(rand(4)); + $w = int(rand(3)); + $d = int(rand(20)); + $h = int(rand(9)); + $mi = int(rand(50)); + $s = int(rand(50)); + my $gmsec = calculate_seconds($y,$m,$w,$d,$h,$mi,$s); + + ## whether to write it out or not ## + if (int(rand(2))) { + ## write it out ## + + ## access or modification ## + if ($a_m eq 'A') { + $rand_time_str = "\"access plus"; + } else { + $rand_time_str = "\"modification plus"; + } + + $rand_time_str .= " $y years" if $y; + $rand_time_str .= " $m months" if $m; + $rand_time_str .= " $w weeks" if $w; + $rand_time_str .= " $d days" if $d; + $rand_time_str .= " $h hours" if $h; + $rand_time_str .= " $mi minutes" if $mi; + $rand_time_str .= " $s seconds" if $s; + $rand_time_str .= "\""; + + } else { + ## easy format ## + $rand_time_str = "$a_m$gmsec"; + } + + return ($gmsec, $rand_time_str); +} + +sub write_htaccess { + open (HT, ">$htaccess") or die "cant open $htaccess: $!"; + print HT shift; + close(HT); +} + +sub expires_test { + my $expires_active = shift; + my $head_str = shift; + my %headers = (); + + foreach my $header (split /\n/, $head_str) { + if ($header =~ /^([\-\w]+): (.*)$/) { + print "# debug: [$1] [$2]\n"; + $headers{$names{$1}} = $2 if exists $names{$1}; + } + } + + ## expires header should not exist if ExpiresActive is Off ## + return !$headers{expires} unless ($expires_active); + + for my $h (grep !/^type$/, values %names) { + print "# debug: $h @{[$headers{$h}||'']}\n"; + if ($headers{$h}) { + $headers{$h} = convert_to_time($headers{$h}) || 0; + } else { + $headers{$h} = 0; + } + print "# debug: $h $headers{$h}\n"; + } + + my $exp_conf = ''; + if ( exists $exp{ $headers{type} } and $exp{ $headers{type} }) { + $exp_conf = $exp{ $headers{type} }; + } else { + $exp_conf = $exp{'default'}; + } + + ## if expect is set to '0', Expire header should not exist. ## + if ($exp_conf eq '0') { + return !$headers{expires}; + } + + my $expected = ''; + my $exp_type = ''; + if ($exp_conf =~ /^([A|M])(\d+)$/) { + $exp_type = $1; + $expected = $2; + ## With modification date as base expire times can be in the past + ## Correct behaviour for the server in this case is to set expires + ## time equal to access time. + if (($exp_type eq 'M') + && ($headers{access} > $headers{modified} + $expected)) { + $expected = $headers{access} - $headers{modified}; + } + } else { + print STDERR "\n\ndoom: $exp_conf\n\n"; + return 0; + } + + my $actual = 0; + if ($exp_type eq 'M') { + $actual = $headers{expires} - $headers{modified}; + } elsif ($exp_type eq 'A') { + $actual = $headers{expires} - $headers{access}; + } + + print "# debug: expected: $expected\n"; + print "# debug: actual : $actual\n"; + return ($actual == $expected); + +} + +sub convert_to_time { + my $timestr = shift; + return undef unless $timestr; + + my ($sec,$min,$hours,$mday,$mon,$year); + if ($timestr =~ /^\w{3}, (\d+) (\w{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2}).*$/) { + $mday = $1; + $mon = $month{$2}; + $year = $3; + $hours = $4; + $min = $5; + $sec = $6; + } + + return undef + unless + defined $sec && + defined $min && + defined $hours && + defined $mday && + defined $mon && + defined $year; + + return Time::Local::timegm($sec, $min, $hours, $mday, $mon, $year); +} + +sub calculate_seconds { + ## takes arguments: + ## years, months, weeks, days, hours, minutes, seconds + my $exp_years = shift() * 60 * 60 * 24 * 365; + my $exp_months = shift() * 60 * 60 * 24 * 30; + my $exp_weeks = shift() * 60 * 60 * 24 * 7; + my $exp_days = shift() * 60 * 60 * 24; + my $exp_hours = shift() * 60 * 60; + my $exp_minutes = shift() * 60; + return $exp_years + $exp_months + $exp_weeks + + $exp_days + $exp_hours + $exp_minutes + shift; +} + +sub default_exp { + ## set the exp hash to the defaults as defined in the conf file. + return + ( + 'default' => "M$expires_default", + 'text/plain' => 'M60', + 'image/gif' => 'A120', + 'image/jpeg' => 'A86400' + ); +} diff --git a/debian/perl-framework/t/modules/ext_filter.t b/debian/perl-framework/t/modules/ext_filter.t new file mode 100644 index 0000000..79622ae --- /dev/null +++ b/debian/perl-framework/t/modules/ext_filter.t @@ -0,0 +1,40 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +Apache::TestRequest::user_agent(keep_alive => 1); + +my $iters = 10; +if (!have_min_apache_version("2.4.0")) { + # Not interested in 2.2 + $iters = 0; +} +my $tests = 4 + $iters * 2; + +plan tests => $tests, need + need_module('ext_filter'), need_cgi; + +my $content = GET_BODY("/apache/extfilter/out-foo/foobar.html"); +chomp $content; +ok t_cmp($content, "barbar", "sed output filter"); + +$content = GET_BODY("/apache/extfilter/out-slow/foobar.html"); +chomp $content; +ok t_cmp($content, "foobar", "slow filter process"); + +my $r = POST "/apache/extfilter/in-foo/modules/cgi/perl_echo.pl", content => "foobar\n"; +ok t_cmp($r->code, 200, "echo worked"); +ok t_cmp($r->content, "barbar\n", "request body filtered"); + + + +# PR 60375 -- appears to be intermittent failure with 2.4.x ... but works with trunk? +foreach (1..$iters) { + $r = POST "/apache/extfilter/out-limit/modules/cgi/perl_echo.pl", content => "foo and bar\n"; + + ok t_cmp($r->code, 413, "got 413 error"); + ok t_cmp($r->content, qr/413 Request Entity Too Large/, "got 413 error body"); +} diff --git a/debian/perl-framework/t/modules/filter.t b/debian/perl-framework/t/modules/filter.t new file mode 100644 index 0000000..3ab7796 --- /dev/null +++ b/debian/perl-framework/t/modules/filter.t @@ -0,0 +1,25 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil qw(t_cmp t_write_file); +use File::Spec; + +my @testcases = ( + ['/modules/cgi/xother.pl' => 'HELLOWORLD'], + ['/modules/filter/bytype/test.txt' => 'HELLOWORLD'], + ['/modules/filter/bytype/test.xml' => 'HELLOWORLD'], + ['/modules/filter/bytype/test.css' => 'helloworld'], + ['/modules/filter/bytype/test.html' => 'helloworld'], +); + +plan tests => scalar @testcases, need need_cgi, + need_module('mod_filter'), + need_module('mod_case_filter'); + +foreach my $t (@testcases) { + my $r = GET_BODY($t->[0]); + chomp $r; + ok t_cmp($r, $t->[1]); +} diff --git a/debian/perl-framework/t/modules/headers.t b/debian/perl-framework/t/modules/headers.t new file mode 100644 index 0000000..c72c690 --- /dev/null +++ b/debian/perl-framework/t/modules/headers.t @@ -0,0 +1,311 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +## +## mod_headers tests +## + +my $htdocs = Apache::Test::vars('documentroot'); +my $htaccess = "$htdocs/modules/headers/htaccess/.htaccess"; +my @header_types = ('set', 'append', 'add', 'unset'); + +my @testcases = ( + ## htaccess + ## Header to set in the request + ## Expected result + + # echo + [ + "Header echo Test-Header\nHeader echo ^Aaa\$\nHeader echo ^Aa\$", + [ 'Test-Header' => 'value', 'Aaa' => 'b' , 'Aa' => 'bb' ], + [ 'Test-Header' => 'value', 'Aaa' => 'b' , 'Aa' => 'bb' ], + ], + [ + "Header echo Test-Header\nHeader echo XXX\nHeader echo ^Aa\$", + [ 'Test-Header' => 'foo', 'aaa' => 'b', 'aa' => 'bb' ], + [ 'Test-Header' => 'foo', 'aa' => 'bb' ], + ], + [ + "Header echo Test-Header.*", # regex + [ 'Test-Header' => 'foo', 'Test-Header1' => 'value1', 'Test-Header2' => 'value2' ], + [ 'Test-Header' => 'foo', 'Test-Header1' => 'value1', 'Test-Header2' => 'value2' ], + ], + # edit + [ + "Header echo Test-Header\nHeader edit Test-Header foo bar", # sizeof(foo) = sizeof(bar) + [ 'Test-Header' => 'foofoo' ], + [ 'Test-Header' => 'barfoo' ], + ], + [ + "Header echo Test-Header\nHeader edit Test-Header foo2 bar", # sizeof(foo2) > sizeof(bar) + [ 'Test-Header' => 'foo2foo2' ], + [ 'Test-Header' => 'barfoo2' ], + ], + [ + "Header echo Test-Header\nHeader edit Test-Header foo bar2", # sizeof(foo) < sizeof(bar2) + [ 'Test-Header' => 'foofoo' ], + [ 'Test-Header' => 'bar2foo' ], + ], + # edit* + [ + "Header echo Test-Header\nHeader edit* Test-Header foo bar", # sizeof(foo) = sizeof(bar) + [ 'Test-Header' => 'foofoo' ], + [ 'Test-Header' => 'barbar' ], + ], + [ + "Header echo Test-Header\nHeader edit* Test-Header foo2 bar", # sizeof(foo2) > sizeof(bar) + [ 'Test-Header' => 'foo2foo2' ], + [ 'Test-Header' => 'barbar' ], + ], + [ + "Header echo Test-Header\nHeader edit* Test-Header foo bar2", # sizeof(foo) < sizeof(bar2) + [ 'Test-Header' => 'foofoo' ], + [ 'Test-Header' => 'bar2bar2' ], + ], + # merge + [ + "Header merge Test-Header foo", # missing header + [ ], + [ 'Test-Header' => 'foo' ], + ], + [ + "Header echo Test-Header\nHeader merge Test-Header foo", # already existing, same value + [ 'Test-Header' => 'foo' ], + [ 'Test-Header' => 'foo' ], + ], + [ + "Header echo Test-Header\nHeader merge Test-Header foo", # already existing, same value, but with "" + [ 'Test-Header' => '"foo"' ], + [ 'Test-Header' => '"foo", foo' ], + ], + [ + "Header echo Test-Header\nHeader merge Test-Header bar", # already existing, different value + [ 'Test-Header' => 'foo' ], + [ 'Test-Header' => 'foo, bar' ], + ], + # setifempty + [ + "Header echo Test-Header\nHeader setifempty Test-Header bar", # already existing + [ 'Test-Header' => 'foo' ], + [ 'Test-Header' => 'foo' ], + ], + [ + "Header echo Test-Header\nHeader setifempty Test-Header2 bar", # missing header + [ 'Test-Header' => 'foo' ], + [ 'Test-Header' => 'foo', 'Test-Header2' => 'bar' ], + ], + # env= + [ + "SetEnv MY_ENV\nHeader set Test-Header foo env=MY_ENV", # env defined + [ ], + [ 'Test-Header' => 'foo' ], + ], + [ + "Header set Test-Header foo env=!MY_ENV", # env NOT defined + [ ], + [ 'Test-Header' => 'foo' ], + ], + # expr= + [ + "Header set Test-Header foo \"expr=%{REQUEST_URI} =~ m#htaccess#\"", # expr + [ ], + [ 'Test-Header' => 'foo' ], + ], +); + +plan tests => + @header_types**4 + @header_types**3 + @header_types**2 + @header_types**1 + scalar @testcases * 2, + have_module 'headers'; + +# Test various configurations +foreach my $header1 (@header_types) { + + ok test_header($header1); + foreach my $header2 (@header_types) { + + ok test_header($header1, $header2); + foreach my $header3 (@header_types) { + + ok test_header($header1, $header2, $header3); + foreach my $header4 (@header_types) { + + ok test_header($header1, $header2, $header3, $header4); + + } + + } + + } + +} + +# Test some other Header directives, including regex +my $ua = LWP::UserAgent->new(); +my $hostport = Apache::TestRequest::hostport(); +foreach my $t (@testcases) { + test_header2($t); +} + +## clean up ## +unlink $htaccess; + +sub test_header { + my @h = @_; + my $test_header = "Test-Header"; + my (@expected_value, @actual_value) = ((),()); + my ($expected_exists, $expected_value, $actual_exists) = (0,0,0); + + open (HT, ">$htaccess"); + foreach (@h) { + + ## create a unique header value ## + my $r = int(rand(9999)); + my $test_value = "mod_headers test header value $r"; + + ## evaluate $_ to come up with expected results + ## and write out the .htaccess file + if ($_ eq 'unset') { + print HT "Header $_ $test_header\n"; + @expected_value = (); + $expected_exists = 0; + $expected_value = 0; + } else { + print HT "Header $_ $test_header \"$test_value\"\n"; + + if ($_ eq 'set') { + + ## should 'set' work this way? + ## currently, even if there are multiple headers + ## with the same name, 'set' blows them all away + ## and sets a single one with this value. + @expected_value = (); + $expected_exists = 1; + + $expected_value = $test_value; + } elsif ($_ eq 'append') { + + ## should 'append' work this way? + ## currently, if there are multiple headers + ## with the same name, 'append' appends the value + ## to the FIRST instance of that header. + if (@expected_value) { + $expected_value[0] .= ", $test_value"; + + } elsif ($expected_value) { + $expected_value .= ", $test_value"; + } else { + $expected_value = $test_value; + } + $expected_exists++ unless $expected_exists; + + } elsif ($_ eq 'add') { + if ($expected_value) { + push(@expected_value, $expected_value); + $expected_value = 0; + } + $expected_value = $test_value; + $expected_exists++; + } + } + } + close(HT); + + push(@expected_value, $expected_value) if $expected_value; + + ## get the actual headers ## + my $h = HEAD_STR "/modules/headers/htaccess/"; + + ## parse response headers looking for our headers + ## and save the value(s) + my $exists = 0; + my $actual_value; + foreach my $head (split /\n/, $h) { + if ($head =~ /^$test_header: (.*)$/) { + $actual_exists++; + push(@actual_value, $1); + } + } + + ## ok if 'unset' and there are no headers ## + return 1 if ($actual_exists == 0 and $expected_exists == 0); + + if (($actual_exists == $expected_exists) && + (@actual_value == @expected_value)) { + + ## go through each actual header ## + foreach my $av (@actual_value) { + my $matched = 0; + + ## and each expected header ## + for (my $i = 0 ; $i <= @expected_value ; $i++) { + + if ($av eq $expected_value[$i]) { + + ## if we match actual and expected, + ## record it, and remove the header + ## from the expected list + $matched++; + splice(@expected_value, $i, 1); + last; + + } + } + + ## not ok if actual value does not match expected ## + return 0 unless $matched; + } + + ## if we made it this far, all is well. ## + return 1; + + } else { + + ## not ok if the number of expected and actual + ## headers do not match + return 0; + + } +} + +sub test_header2 { + my @test = @_; + my $h = HTTP::Headers->new; + + print "\n\n\n"; + for (my $i = 0; $i < scalar @{$test[0][1]}; $i += 2) { + print "Header sent n°" . $i/2 . ":\n"; + print " header: " . $test[0][1][$i] . "\n"; + print " value: " . $test[0][1][$i+1] . "\n"; + $h->header($test[0][1][$i] => $test[0][1][$i+1]); + } + + open (HT, ">$htaccess"); + print HT $test[0][0]; + close(HT); + + ## + my $r = HTTP::Request->new('GET', "http://$hostport/modules/headers/htaccess/", $h); + my $res = $ua->request($r); + ok t_cmp($res->code, 200, "Checking return code is '200'"); + + my $isok = 1; + for (my $i = 0; $i < scalar @{$test[0][2]}; $i += 2) { + print "\n"; + print "Header received n°" . $i/2 . ":\n"; + print " header: " . $test[0][2][$i] . "\n"; + print " expected: " . $test[0][2][$i+1] . "\n"; + if ($res->header($test[0][2][$i])) { + print " received: " . $res->header($test[0][2][$i]) . "\n"; + } else { + print " received: <undefined>\n"; + } + $isok = $isok && $res->header($test[0][2][$i]) && $test[0][2][$i+1] eq $res->header($test[0][2][$i]); + } + print "\nResponse received is:\n" . $res->as_string; + + ok $isok; +} diff --git a/debian/perl-framework/t/modules/heartbeat.t b/debian/perl-framework/t/modules/heartbeat.t new file mode 100644 index 0000000..d9f6f18 --- /dev/null +++ b/debian/perl-framework/t/modules/heartbeat.t @@ -0,0 +1,30 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil qw/t_start_error_log_watch t_finish_error_log_watch/; + +my $r; +my $line; +my $count = 0; +my $nb_seconds = 5; +# Because of timing, we may see less than what could be expected +my $nb_expected = $nb_seconds - 2; + +plan tests => 1, sub { need_module('mod_heartbeat', 'mod_heartmonitor') && !need_apache_mpm('prefork') }; + +# Give some time to the heart to beat a few times +t_start_error_log_watch(); +sleep($nb_seconds); +my @loglines = t_finish_error_log_watch(); + +# Heartbeat sent by mod_heartbeat and received by mod_heartmonitor are logged with DEBUG AH02086 message +foreach $line (@loglines) { + if ($line =~ "AH02086") { + $count++; + } +} + +print "Expecting at least " . $nb_expected . " heartbeat ; Seen: " . $count . "\n"; +ok($count >= $nb_expected); diff --git a/debian/perl-framework/t/modules/http2.t b/debian/perl-framework/t/modules/http2.t new file mode 100644 index 0000000..02725f5 --- /dev/null +++ b/debian/perl-framework/t/modules/http2.t @@ -0,0 +1,535 @@ +use strict; +use warnings FATAL => 'all'; + +use Net::SSLeay; +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +my $tls_version_suite = 4; +my $num_suite = 24; +my $vhost_suite = 4; +my $total_tests = 2 * $num_suite + $vhost_suite + $tls_version_suite; + +Net::SSLeay::initialize(); + +my $sni_available = Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x01000000; +my $alpn_available = $sni_available && exists &Net::SSLeay::CTX_set_alpn_protos; + +plan tests => $total_tests, need 'Protocol::HTTP2::Client', 'AnyEvent', + need_module 'http2', need_min_apache_version('2.4.17'); + +# Check support for TLSv1_2 and later + +Apache::TestRequest::set_ca_cert(); + +# If we can, detect the SSL protocol the server speaks and do not run +# against anything pre-TLSv1.2 +# On some setups, we do not get a socket here (for not understood reasons) +# and run the tests. Better to fail visibly then. +# +my $tls_modern = 1; +my $tls_version = 0; + +my $sock = Apache::TestRequest::vhost_socket('h2'); +if ($sock) { + ok ($sock->connected); + + my $req = "GET / HTTP/1.1\r\n". + "Host: " . Apache::TestRequest::hostport() . "\r\n". + "\r\n"; + + ok $sock->print($req); + my $line = Apache::TestRequest::getline($sock) || ''; + ok t_cmp($line, qr{^HTTP/1\.. 200}, "read first response-line"); + $tls_version = $sock->get_sslversion(); + ok t_cmp($tls_version, qr{^(SSL|TLSv\d(_\d)?$)}, "TLS version in use"); + + if ($tls_version =~ /^(SSL|TLSv1(|_0|_1)$)/) { + print STDOUT "Disabling TLS tests due to TLS version $tls_version\n"; + $tls_modern = 0; + } +} +else { + skip "skipping test as socket not defined" foreach(1..$tls_version_suite); +} + +Apache::TestRequest::module("http2"); + +my $config = Apache::Test::config(); +my $host = $config->{vhosts}->{h2c}->{servername}; +my $port = $config->{vhosts}->{h2c}->{port}; + +my $shost = $config->{vhosts}->{h2}->{servername}; +my $sport = $config->{vhosts}->{h2}->{port}; +my $serverdir = $config->{vars}->{t_dir}; +my $htdocs = $serverdir . "/htdocs"; + +require Protocol::HTTP2::Client; +use AnyEvent; +use AnyEvent::Socket; +use AnyEvent::Handle; +use Net::SSLeay; +use AnyEvent::TLS; +use Carp qw( croak ); + +no warnings 'redefine'; +no strict 'refs'; +{ + my $old_ref = \&{ 'AnyEvent::TLS::new' }; + *{ 'AnyEvent::TLS::new' } = sub { + my ( $class, %param ) = @_; + + my $self = $old_ref->( $class, %param ); + + $self->{host_name} = $param{host_name} + if exists $param{host_name}; + + $self; + }; +} + +{ + my $old_ref = \&{ 'AnyEvent::TLS::_get_session' }; + *{ 'AnyEvent::TLS::_get_session' } = sub($$;$$) { + my ($self, $mode, $ref, $cn) = @_; + + my $session = $old_ref->( @_ ); + + if ( $mode eq 'connect' ) { + if ( $self->{host_name} ) { + print 'setting host_name to ' . $self->{host_name}; + Net::SSLeay::set_tlsext_host_name( $session, $self->{host_name} ); + } + } + + $session; + }; +} + + +sub connect_and_do { + my %args = ( + @_ + ); + my $scheme = $args{ctx}->{scheme}; + my $host = $args{ctx}->{host}; + my $port = $args{ctx}->{port}; + my $client = $args{ctx}->{client}; + my $host_name = $args{ctx}->{host_name}; + my $w = AnyEvent->condvar; + + tcp_connect $host, $port, sub { + my ($fh) = @_ or do { + print "connection failed: $!\n"; + $w->send; + return; + }; + + my $tls; + my $tls_ctx; + if ($scheme eq 'https') { + $tls = "connect"; + eval { + # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.1) + if ( $alpn_available ) { + $tls_ctx = AnyEvent::TLS->new( method => "TLSv1_2", + host_name => $host_name ); + Net::SSLeay::CTX_set_alpn_protos( $tls_ctx->ctx, ['h2'] ); + } + else { + $tls_ctx = AnyEvent::TLS->new( host_name => $host_name ); + } + }; + if ($@) { + print "Some problem with SSL CTX: $@\n"; + $w->send; + return; + } + } + + my $handle; + $handle = AnyEvent::Handle->new( + fh => $fh, + tls => $tls, + tls_ctx => $tls_ctx, + autocork => 1, + on_error => sub { + $_[0]->destroy; + print "connection error\n"; + $w->send; + }, + on_eof => sub { + $handle->destroy; + $w->send; + } + ); + + # First write preface to peer + while ( my $frame = $client->next_frame ) { + $handle->push_write($frame); + } + + $handle->on_read(sub { + my $handle = shift; + + $client->feed( $handle->{rbuf} ); + $handle->{rbuf} = undef; + + while ( my $frame = $client->next_frame ) { + $handle->push_write($frame); + } + + # Terminate connection if all done + $handle->push_shutdown if $client->shutdown; + }); + }; + $w->recv; + +} + +################################################################################ +# +# Add a request to the client, will be started whenever a STREAM to +# the server is available. +# +sub add_request { + my ($scheme, $client, $host, $port); + my %args = ( + method => 'GET', + headers => [], + rc => 200, + on_done => sub { + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $req = $args{request}; + my $resp = $args{response}; + my $hr = $resp->{headers}; + my %headers = @$hr; + ok t_cmp($headers{':status'}, $req->{rc}, + "$req->{method} $ctx->{scheme}://$ctx->{host}:$ctx->{port}$req->{path}"); + }, + @_ + ); + $client = $args{ctx}->{client}; + $scheme = $args{ctx}->{scheme}; + $host = $args{ctx}->{host}; + $port = $args{ctx}->{port}; + + $client->request( + ':scheme' => $scheme, + ':authority' => $args{authority} || $host . ':' . $port, + ':path' => $args{path}, + ':method' => $args{method}, + headers => $args{headers}, + on_done => sub { + my ($headers, $data) = @_; + $args{on_done}( + ctx => $args{ctx}, + request => \%args, + response => { headers => \@$headers, data => $data } + ); + } + ); +} + +################################################################################ +# +# Add a list of request that will be processed in order. Only when the previous +# request is done, will a new one be started. +# +sub add_sequential { + my ($scheme, $client, $host, $port); + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $requests = $args{requests}; + + $client = $args{ctx}->{client}; + $scheme = $args{ctx}->{scheme}; + $host = $args{ctx}->{host}; + $port = $args{ctx}->{port}; + + my $request = shift @$requests; + + if ($request) { + my %r = ( + method => 'GET', + headers => [], + rc => 200, + on_done => sub { + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $req = $args{request}; + my $resp = $args{response}; + my $hr = $resp->{headers}; + my %headers = @$hr; + ok t_cmp($headers{':status'}, $req->{rc}, + "$req->{method} $ctx->{scheme}://$ctx->{host}:$ctx->{port}$req->{path}"); + }, + %$request + ); + + print "test case: $r{descr}: $r{method} $ctx->{scheme}://$ctx->{host}:$ctx->{port}$r{path}\n"; + $client->request( + ':scheme' => $scheme, + ':authority' => $r{authority} || $host . ':' . $port, + ':path' => $r{path}, + ':method' => $r{method}, + headers => $r{headers}, + on_done => sub { + my ($headers, $data) = @_; + $r{on_done}( + ctx => ${ctx}, + request => \%r, + response => { headers => \@$headers, data => $data } + ); + add_sequential( + ctx => $ctx, + requests => $requests + ); + } + ); + } +} + +sub cmp_content_length { + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $req = $args{request}; + my $resp = $args{response}; + my $hr = $resp->{headers}; + my %headers = @$hr; + ok t_cmp($headers{':status'}, $req->{rc}, "response status"); + ok t_cmp(length $resp->{data}, $req->{content_length}, "content-length"); +} + +sub cmp_content { + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $req = $args{request}; + my $resp = $args{response}; + my $hr = $resp->{headers}; + my %headers = @$hr; + ok t_cmp($headers{':status'}, $req->{rc}, "response status"); + ok t_cmp($resp->{data}, $req->{content}, "content comparision"); +} + +sub cmp_file_response { + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $req = $args{request}; + my $resp = $args{response}; + my $hr = $resp->{headers}; + my %headers = @$hr; + ok t_cmp($headers{':status'}, $req->{rc}, "response status"); + open(FILE, "<$htdocs$req->{path}") or die "cannot open $req->{path}"; + undef $/; + my $content = <FILE>; + close(FILE); + ok t_is_equal($resp->{data}, $content); +} + +sub check_redir { + my %args = ( @_ ); + my $ctx = $args{ctx}; + my $req = $args{request}; + my $resp = $args{response}; + my $hr = $resp->{headers}; + my %headers = @$hr; + ok t_cmp($headers{':status'}, 302, "response status"); + ok t_cmp( + $headers{location}, + "$ctx->{scheme}://$ctx->{host}:$ctx->{port}$req->{redir_path}", + "location header" + ); +} + +################################################################################ +# +# Perform common tests to h2c + h2 hosts +# +sub do_common { + my %args = ( + scheme => 'http', + host => 'localhost', + port => 80, + @_ + ); + my $true_tls = ($args{scheme} eq 'https' and $sni_available); + + $args{client} = Protocol::HTTP2::Client->new( upgrade => 0 ); + + my $r = [ + { + descr => 'TC0001, expecting 200', + path => '/' + }, + { + descr => 'TC0002, expecting 404', + rc => 404, + path => '/not_here' + }, + { + descr => 'TC0005, cmp index.html file', + path => '/modules/h2/index.html', + on_done => \&cmp_file_response + }, + { + descr => 'TC0006, cmp image file', + path => '/modules/h2/003/003_img.jpg', + on_done => \&cmp_file_response + }, + ]; + + if (have_module 'mod_rewrite') { + push @$r, { + descr => 'TC0007, rewrite handling', + path => '/modules/h2/latest.tar.gz', + redir_path => "/modules/h2/xxx-1.0.2a.tar.gz", + on_done => \&check_redir + } + } + else { + skip "skipping test as mod_rewrite not available" foreach(1..2); + } + + if (have_cgi) { + # my $sni_host = $true_tls? 'localhost' : ''; + my $content = <<EOF; +<html><body> +<h2>Hello World!</h2> +</body></html> +EOF + + push @$r, { + descr => 'TC0008, hello.pl with ssl vars', + path => '/modules/h2/hello.pl', + content => $content, + on_done => \&cmp_content, + }; + + $content = <<EOF; +<html><body> +<p>No query was specified.</p> +</body></html> +EOF + push @$r, { + descr => 'TC0009, necho.pl without arguments', + path => '/modules/h2/necho.pl', + content => $content, + rc => 400, + on_done => \&cmp_content, + }; + push @$r, { + descr => 'TC0010, necho.pl 2x10', + path => '/modules/h2/necho.pl?count=2&text=0123456789', + content => "01234567890123456789", + on_done => \&cmp_content, + }; + push @$r, { + descr => 'TC0011, necho.pl 10x10', + path => '/modules/h2/necho.pl?count=10&text=0123456789', + content_length => 100, + on_done => \&cmp_content_length, + }; + push @$r, { + descr => 'TC0012, necho.pl 100x10', + path => '/modules/h2/necho.pl?count=100&text=0123456789', + content_length => 1000, + on_done => \&cmp_content_length, + }; + push @$r, { + descr => 'TC0013, necho.pl 1000x10', + path => '/modules/h2/necho.pl?count=1000&text=0123456789', + content_length => 10000, + on_done => \&cmp_content_length, + }; + push @$r, { + descr => 'TC0014, necho.pl 10000x10', + path => '/modules/h2/necho.pl?count=10000&text=0123456789', + content_length => 100000, + on_done => \&cmp_content_length, + }; + push @$r, { + descr => 'TC0015, necho.pl 100000x10', + path => '/modules/h2/necho.pl?count=100000&text=0123456789', + content_length => 1000000, + on_done => \&cmp_content_length, + }; + } + else { + skip "skipping test as mod_cgi not available" foreach(1..16); + } + + add_sequential( + ctx => \%args, + requests => $r + ); + connect_and_do( ctx => \%args ); +} + +################################################################################ +# +# Perform tests for virtual host setups, requires a client with SNI+ALPN +# +sub do_vhosts { + my %args = ( + scheme => 'http', + host => 'localhost', + port => 80, + @_ + ); + $args{client} = Protocol::HTTP2::Client->new( upgrade => 0 ); + + my $r = [ + { + descr => 'VHOST000, expecting 200', + path => '/' + }, + { + descr => 'VHOST001, expect 404 or 421 (using Host:)', + rc => 404, + path => '/misdirected', + header => [ 'host' => 'noh2.example.org' . $args{port} ] + }, + { + descr => 'VHOST002, expect 421 (using :authority)', + rc => 421, + path => '/misdirected', + authority => 'noh2.example.org:' . $args{port} + }, + { + descr => 'VHOST003, expect 421 ', + rc => (have_min_apache_version('2.4.18')? 404 : 421), + path => '/misdirected', + authority => 'test.example.org:' . $args{port} + }, + ]; + + add_sequential( + ctx => \%args, + requests => $r + ); + connect_and_do( ctx => \%args ); +} + +################################################################################ +# +# Bring it on +# +do_common( 'scheme' => 'http', 'host' => $host, 'port' => $port ); +if ($tls_modern) { + do_common( 'scheme' => 'https', 'host' => $shost, 'port' => $sport ); +} else { + skip "skipping test as TLS version '$tls_version' is not supported" foreach(1..$num_suite); +} +if ($sni_available) { + if ($tls_modern) { + do_vhosts( 'scheme' => 'https', 'host' => $shost, 'port' => $sport, host_name => "$shost:${sport}" ); + } else { + skip "skipping test as TLS version '$tls_version' is not supported" foreach(1..$vhost_suite); + } +} else { + skip "skipping test as SNI not available" foreach(1..$vhost_suite); +} diff --git a/debian/perl-framework/t/modules/include.t b/debian/perl-framework/t/modules/include.t new file mode 100644 index 0000000..9ff2411 --- /dev/null +++ b/debian/perl-framework/t/modules/include.t @@ -0,0 +1,661 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +use File::Spec::Functions qw(catfile splitpath); + +Apache::TestRequest::scheme('http'); #ssl not listening on this vhost +Apache::TestRequest::module('mod_include'); #use this module's port + +use constant WINFU => Apache::TestConfig::WINFU; + +## mod_include tests +my($res, $str, $doc); +my $dir = "/modules/include/"; +my $have_apache_1 = have_apache 1; +my $have_apache_2 = have_apache 2; +my $have_apache_21 = have_min_apache_version "2.1.0"; +my $have_apache_20 = $have_apache_2 && ! $have_apache_21; +my $htdocs = Apache::Test::vars('documentroot'); + +# these match the SSI files with their expected results. +# the expectations are set by the current 2.1 mod_include +# implementation. + +my %test = ( +"echo.shtml" => "echo.shtml", +"set.shtml" => "set works", +"comment.shtml" => "No comment here", +"include1.shtml" => "inc-two.shtml body include.shtml body", +"include2.shtml" => "inc-two.shtml body include.shtml body", +"include3.shtml" => "inc-two.shtml body inc-one.shtml body ". + "include.shtml body", +"include4.shtml" => "inc-two.shtml body inc-one.shtml body ". + "include.shtml body", +"include5.shtml" => "inc-two.shtml body inc-one.shtml body ". + "inc-three.shtml body include.shtml body", +"include6.shtml" => "inc-two.shtml body inc-one.shtml body ". + "inc-three.shtml body include.shtml body", +"foo.shtml" => "[an error occurred while processing this ". + "directive] foo.shtml body", +"foo1.shtml" => "[an error occurred while processing this ". + "directive] foo.shtml body", +"foo2.shtml" => "[an error occurred while processing this ". + "directive] foo.shtml body", +"encode.shtml" => "\# \%\^ \%23\%20\%25\%5e", +"errmsg1.shtml" => "errmsg", +"errmsg2.shtml" => "errmsg", +"errmsg3.shtml" => "errmsg", +"errmsg4.shtml" => "pass errmsg", +"errmsg5.shtml" => "<!-- pass -->", +"if1.shtml" => "pass", +"if2.shtml" => "pass pass", +"if3.shtml" => "pass pass pass", +"if4.shtml" => "pass pass", +"if5.shtml" => "pass pass pass", +"if6.shtml" => "[an error occurred while processing this ". + "directive]", +"if7.shtml" => "[an error occurred while processing this ". + "directive]", +"if8.shtml" => "pass", +"if9.shtml" => "pass pass", +"if10.shtml" => "pass", +"if11.shtml" => "pass", +"big.shtml" => "hello pass pass pass hello", +"newline.shtml" => "inc-two.shtml body", +"inc-rfile.shtml" => "inc-extra2.shtml body inc-extra1.shtml body ". + "inc-rfile.shtml body", +"inc-rvirtual.shtml" => "inc-extra2.shtml body inc-extra1.shtml body ". + "inc-rvirtual.shtml body", +"extra/inc-bogus.shtml" => "[an error occurred while processing this ". + "directive] inc-bogus.shtml body", +"abs-path.shtml" => "inc-extra2.shtml body inc-extra1.shtml body ". + "abs-path.shtml body", +"parse1.shtml" => "-->", +"parse2.shtml" => '"', +"regex.shtml" => "(none) 1 (none)", +"retagged1.shtml" => ["retagged1.shtml", "retagged1"], +"retagged2.shtml" => ["----retagged2.shtml", "retagged1"], +"echo1.shtml" => ["<!-- pass undefined echo -->", "echo1" ], +"echo2.shtml" => ["<!-- pass undefined echo --> pass config ". + " echomsg pass", "echo1"], +"echo3.shtml" => ['<!--#echo var="DOCUMENT_NAME" -->', "retagged1"], +"notreal.shtml" => "pass <!--", +"malformed.shtml" => "[an error occurred while processing this ". + "directive] malformed.shtml", +"exec/off/cmd.shtml" => "[an error occurred while processing this ". + "directive]", +"exec/on/cmd.shtml" => "pass", +"exec/off/cgi.shtml" => "[an error occurred while processing this ". + "directive]", +"exec/on/cgi.shtml" => "perl cgi", +"ranged-virtual.shtml" => "x"x32768, +"var128.shtml" => "x"x126 . "yz", # PR#32985 +"virtualq.shtml?foo=bar" => "foo=bar pass inc-two.shtml body foo=bar", # PR#12655 + +"inc-nego.shtml" => "index.html.en", # requires mod_negotiation +"mod_request/echo.shtml"=> "echo.shtml", +"mod_request/post.shtml?foo=bar&foo2=bar2" + => "GET foo: bar foo2: bar2", +"mod_request/post.shtml"=> "POST foo: bar foo2: bar2", # will be twice, only the first one succeed +); + +my %ap_expr_test = ( +"apexpr/if1.shtml" => "pass", +"apexpr/err.shtml" => "[an error occurred while processing this ". + "directive] err.shtml", +"apexpr/restrict.shtml" => "[an error occurred while processing this ". + "directive] restrict.shtml", +"apexpr/var.shtml" => "pass pass pass", +"apexpr/lazyvar.shtml" => "pass", +); + +if (have_min_apache_version "2.3.13") { + %test = (%test, %ap_expr_test); +} + +# now, assuming 2.1 has the proper behavior across the board, +# let's adjust our expectations for other versions + +# these tests are known to be broken in 2.0 +# we'll mark them as TODO tests in the hopes +# that the 2.1 fixes will be backported + +my %todo = ( +); + +# some behaviors will never be backported, for various +# reasons. these are the 1.3 legacy tests and expectations +my %legacy_1_3 = ( +"errmsg4.shtml" => "pass", +"malformed.shtml" => "", +"if6.shtml" => "", +"if7.shtml" => "", +); + +# 2.0 has no legacy tests at the moment +# but when it does, they will go here +my %legacy_2_0 = (); + +# ok, now that we have our hashes established, here are +# the manual tweaks +if ($have_apache_1) { + # apache 1.3 uses different semantics for some + # of the if.*shtml tests to achieve the same results + $test{"if8a.shtml"} = delete $test{"if8.shtml"}; + $test{"if9a.shtml"} = delete $test{"if9.shtml"}; + $test{"if10a.shtml"} = delete $test{"if10.shtml"}; + + # while other tests are for entirely new behaviors + # and don't make sense to test at all in 1.3 + delete $test{"echo1.shtml"}; + delete $test{"echo2.shtml"}; + delete $test{"echo3.shtml"}; + delete $test{"retagged1.shtml"}; + delete $test{"retagged2.shtml"}; + delete $test{"regex.shtml"}; + + # finally, these tests are only broken in 1.3 + $todo{"notreal.shtml"} = delete $test{"notreal.shtml"}; +} + +unless ($have_apache_20) { + # these tests are broken only in 2.0 - + # in 1.3 they work fine so shift them from %todo to %test + + # none at the moment, but the syntax here would be + # $test{"errmsg5.shtml"} = delete $todo{"errmsg5.shtml"}; +} + +unless (have_min_apache_version "2.0.53") { + # this test doesn't work in 2.0 yet but should work in 1.3 and 2.1 + delete $test{"ranged-virtual.shtml"}; +} + +unless ($have_apache_21) { + # apache 1.3 and 2.0 do not support these tests + delete $test{"echo2.shtml"}; +} + +unless (have_module 'mod_negotiation') { + delete $test{"inc-nego.shtml"}; +} + +# this test does not work on win32 (<!--#exec cmd="echo pass"-->) +if (WINFU) { + delete $test{'exec/on/cmd.shtml'}; +} + +my @patterns = ( + 'mod_include test', + 'Hello World', + 'footer', +); + +# with the tweaks out of the way, we can get on +# with planning the tests + +# first, total the number of hashed tests +# note that some legacy tests will redefine the main +# %test hash, so the total is not necessarily the sum +# of all the keys +my %tests = (); + +if ($have_apache_21) { + %tests = (%test, %todo); +} +elsif ($have_apache_2) { + %tests = (%test, %todo, %legacy_2_0); +} +else { + %tests = (%test, %todo, %legacy_1_3); +} + +# now for the TODO tests +my @todo = (); +unless ($have_apache_21) { + # if 1.3 or 2.0, dynamically determine which of %test + # will end up being TODO tests. + + my $counter = 0; + foreach my $test (sort keys %tests) { + $counter++; + push @todo, $counter if $todo{$test}; + } +} + +unless ($have_apache_2) { + # fsize comes immediately after the hashed tests + push @todo, (scalar keys %tests) + 1; +} + +# in addition to %tests, there are 1 mod_request expected failure, +# 1 fsize and 1 flastmod test, +# 1 GET test, 2 query string tests, 14 XBitHack tests and 14 +# tests that use mod_bucketeer to construct brigades for mod_include + +my $tests = (scalar keys %tests) + 1 + @patterns + 1 + 1 + 1 + 2 + 14 + 14; + +plan tests => $tests, + todo => \@todo, + need 'DateTime', need_lwp, need_module 'include'; + +foreach $doc (sort keys %tests) { + # do as much from %test as we can + if (ref $tests{$doc}) { + ok t_cmp(super_chomp(GET_BODY "$dir$doc", Host => $tests{$doc}[1]), + $tests{$doc}[0], + "GET $dir$doc" + ); + } + elsif ($doc =~ m/ranged/) { + if (have_cgi) { + ok t_cmp(GET_BODY("$dir$doc", Range => "bytes=0-"), + $tests{$doc}, + "GET $dir$doc with Range" + ); + } + else { + skip "Skipping virtual-range test; no cgi module", 1; + } + } + elsif ($doc =~ m/cgi/) { + if (have_cgi) { + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $tests{$doc}, + "GET $dir$doc" + ); + } + else { + skip "Skipping 'exec cgi' test; no cgi module.", 1; + } + } + elsif ($doc =~ m/mod_request.*\?/) { + # param in the url ==> use GET + if (have_cgi) { + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $tests{$doc}, + "GET $dir$doc" + ); + } + else { + skip "Skipping 'exec cgi' test; no cgi module.", 1; + } + } + elsif ($doc =~ m/mod_request/) { + # no param in the url ==> use POST with a content + if (have_cgi) { + ok t_cmp(super_chomp(POST_BODY "$dir$doc", content => "foo=bar&foo2=bar2"), + $tests{$doc}, + "POST $dir$doc" + ); + if ($doc =~ m/mod_request.*post/) { + # KeptBodySize is 32 + my $r = POST("$dir$doc", content => "foo=bar&foo2=bar2&foo3=bar3&foo4=bar4"); + ok t_cmp($r->code, 413, "sizeof(body) > KeptBodySize"); + } + } + else { + skip "Skipping 'exec cgi' test; no cgi module.", 2; + } + } + else { + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $tests{$doc}, + "GET $dir$doc" + ); + } +} + +### FLASTMOD/FSIZE TESTS + +# marked as TODO in 1.3 - hoping for a format backport +{ + my $file = catfile($htdocs, splitpath($dir), "size.shtml"); + my $size = (stat $file)[7]; + + # round perl's stat size for <!--#config sizefmt="abbrev"--> + # this assumes the size of size.shtml is such that it is + # rendered in K (which it is). if size.shtml is made much + # larger or smaller this formatting will need to change too + my $abbrev = sprintf("%.1fK", $size/1024); + + # and commify for <!--#config sizefmt="bytes"--> + my $bytes = commify($size); + + my $expected = join ' ', $bytes, $bytes, $abbrev, $abbrev; + + my $result = super_chomp(GET_BODY "${dir}size.shtml"); + + # trim output + $result =~ s/X//g; # the Xs were there just to pad the filesiez + $result = single_space($result); + + ok t_cmp("$result", + "$expected", + "GET ${dir}size.shtml" + ); +} + +unless(eval "require POSIX") { + skip "POSIX module not found", 1; +} +else { + # use DateTime and avoid the system locale messing things up + use DateTime; + # Only for checking, whether system strftime supports %s + use POSIX; + my $strftime_gnu = (POSIX::strftime("%s", gmtime()) eq '%s' ? 0 : 1); + + my $result = super_chomp(GET_BODY "${dir}file.shtml"); + $result = single_space($result); + + my $httpdtz = $1 if $result =~ /\w+, \d+-\w+-\d+ \d+:\d+:\d+ (\w+) /; + + my $file = catfile($htdocs, splitpath($dir), "file.shtml"); + my $mtime = (stat $file)[9]; + + my $dt = DateTime->from_epoch( epoch => $mtime, + locale => 'en_US', time_zone => $httpdtz||'UTC' ); + + my $expected = join ' ' => + $dt->strftime("%A, %B %e, %G"), + $dt->strftime("%A, %B %e, %G"), + $strftime_gnu ? $dt->strftime("%s") : '%s', + $strftime_gnu ? $dt->strftime("%s") : '%s'; + + # trim output + $expected = single_space($expected); + + ok t_cmp("$result", + "$expected", + "GET ${dir}file.shtml" + ); +} + +# some tests that can't be easily assimilated + +$doc = "printenv.shtml"; +ok t_cmp(GET("$dir$doc")->code, + "200", + "GET $dir$doc" + ); + +### test include + query string +$res = GET "${dir}virtual.shtml"; + +ok $res->is_success; + +$str = $res->content; + +ok $str; + +for my $pat (@patterns) { + ok t_cmp($str, qr/$pat/, "/$pat/"); +} + +### MOD_BUCKETEER+MOD_INCLUDE TESTS +if (WINFU) { + for (1..13) { + skip "Skipping XBitHack tests on this platform", 1; + } +} +else { + ### XBITHACK TESTS + # test xbithack off + $doc = "xbithack/off/test.html"; + foreach ("0444", "0544", "0554") { + chmod oct($_), "$htdocs/$dir$doc"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"),, + "<BODY> <!--#include virtual=\"../../inc-two.shtml\"--> </BODY>", + "XBitHack off [$_]" + ); + } + + # test xbithack on + $doc = "xbithack/on/test.html"; + chmod 0444, "$htdocs$dir$doc"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + "<BODY> <!--#include virtual=\"../../inc-two.shtml\"--> </BODY>", + "XBitHack on [0444]" + ); + + foreach ("0544", "0554") { + chmod oct($_), "$htdocs/$dir$doc"; + ok t_cmp(check_xbithack(GET "$dir$doc"), + "No Last-modified date ; <BODY> inc-two.shtml body </BODY>", + "XBitHack on [$_]" + ); + } + + # test timefmt - make sure filter only inserted once + # if Option Include and xbithack both say to process + $doc = "xbithack/both/timefmt.shtml"; + my @now = localtime(); + my $year = $now[5] + 1900; + chmod 0555, "$htdocs/$dir$doc"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + "xx${year}xx", + "XBitHack both [timefmt]" + ); + + # test xbithack full + $doc = "xbithack/full/test.html"; + chmod 0444, "$htdocs/$dir$doc"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + "<BODY> <!--#include virtual=\"../../inc-two.shtml\"--> </BODY>", + "XBitHack full [0444]" + ); + chmod 0544, "$htdocs/$dir$doc"; + ok t_cmp(check_xbithack(GET "$dir$doc"), + "No Last-modified date ; <BODY> inc-two.shtml body </BODY>", + "XBitHack full [0544]" + ); + + my $lm; + + chmod 0554, "$htdocs/$dir$doc"; + ok t_cmp(check_xbithack(GET("$dir$doc"), \$lm), + "Has Last-modified date ; <BODY> inc-two.shtml body </BODY>", + "XBitHack full [0554]" + ); + + ok t_cmp(check_xbithack_etag(GET("$dir$doc", 'If-Modified-Since' => $lm)), + "No ETag ; ", + "XBitHack full [0554] / ETag" + ); + + ok t_cmp(GET("$dir$doc", 'If-Modified-Since' => $lm)->code, 304, + "XBitHack full [0554] / If-Modified-Since" + ); + + chmod 0544, "$htdocs/$dir$doc"; + ok t_cmp(GET("$dir$doc", 'If-Modified-Since' => $lm)->code, 200, + "XBitHack full [0544] / If-Modified-Since" + ); + + ok t_cmp(check_xbithack_etag(GET("$dir$doc", 'If-Modified-Since' => $lm)), + "No ETag ; <BODY> inc-two.shtml body </BODY>", + "XBitHack full [0544] / ETag" + ); +} + +# we can use mod_bucketeer to create edge conditions for mod_include, since +# it allows us to create bucket and brigade boundaries wherever we want +if (have_module 'mod_bucketeer') { + + my $expected = "____ _____ _____ ___________________ </table> ". + "##################################1/8</tr> ". + "##################################2/8</tr> ". + "##################################3/8</tr> ". + "##################################4/8</tr> ". + "##################################5/8</tr> ". + "##################################6/8$htdocs</tr> ". + "##################################7/8</tr> ". + "##################################8/8</tr> ". + "@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@"; + + $doc = "bucketeer/y.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + $expected = "____ ___________________________________". + "________________________________________". + "___ ____________________________________". + "________________________________________". + "__________ ___________________ </table> ". + "#####################################</tr> ". + "#####################################</tr> ". + "#####################################</tr> ". + "#####################################</tr> ". + "#####################################</tr> ". + "#####################################</tr> ". + "#####################################</tr> ". + "#####################################</tr> ". + "@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@"; + + for (0..3) { + $doc = "bucketeer/y$_.shtml"; + my ($body) = super_chomp(GET_BODY "$dir$doc"); + $body =~ s/\002/^B/g; + $body =~ s/\006/^F/g; + $body =~ s/\020/^P/g; + ok t_cmp($body, + $expected, + "GET $dir$doc" + ); + } + + $expected = "[an error occurred while processing this directive]"; + $doc = "bucketeer/y4.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + + $expected= "pass [an error occurred while processing this directive] ". + "pass pass1"; + $doc = "bucketeer/y5.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + $expected= "BeforeIfElseBlockAfterIf"; + $doc = "bucketeer/y6.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + $expected= "Before If <!-- comment -->SomethingElse". + "<!-- right after if -->After if"; + $doc = "bucketeer/y7.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + $expected= "FalseSetDone"; + $doc = "bucketeer/y8.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + $expected= "FalseSetDone"; + $doc = "bucketeer/y9.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + $expected= "\"pass\""; + $doc = "bucketeer/y10.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc"), + $expected, + "GET $dir$doc" + ); + + ### exotic SSI(Start|End)Tags + + $expected= "----retagged3.shtml"; + $doc = "bucketeer/retagged3.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc", Host => 'retagged1'), + $expected, + "GET $dir$doc" + ); + + $expected= "---pass"; + $doc = "bucketeer/retagged4.shtml"; + ok t_cmp(super_chomp(GET_BODY "$dir$doc", Host => 'retagged2'), + $expected, + "GET $dir$doc" + ); +} +else { + for (1..14) { + skip "Skipping bucket boundary tests, no mod_bucketeer", 1; + } +} + +sub super_chomp { + my ($body) = shift; + + ## super chomp - all leading and trailing \n (and \r for win32) + $body =~ s/^[\n\r]*//; + $body =~ s/[\n\r]*$//; + ## and all the rest change to spaces + $body =~ s/\n/ /g; + $body =~ s/\r//g; #rip out all remaining \r's + + $body; +} + +sub check_xbithack { + my ($resp) = shift; + my ($body) = super_chomp($resp->content); + my ($lastmod) = ($resp->last_modified) + ? "Has Last-modified date" : "No Last-modified date"; + + my $data = shift; + $$data = $resp->header('Last-Modified') if $data; + + "$lastmod ; $body"; +} + +sub check_xbithack_etag { + my ($resp) = shift; + my ($body) = super_chomp($resp->content); + my ($etag) = ($resp->header('ETag')) + ? "Has ETag" : "No ETag"; + + my $data = shift; + $$data = $etag if $data; + + "$etag ; $body"; +} + +sub commify { + # add standard commas to numbers. from perlfaq5 + + local $_ = shift; + 1 while s/^([-+]?\d+)(\d{3})/$1,$2/; + return $_; +} + +sub single_space { + # condense multiple spaces between values to a single + # space. also trim initial and trailing whitespace + + local $_ = shift; + s/\s+/ /g; + s/(^ )|( $)//; + return $_; +} diff --git a/debian/perl-framework/t/modules/info.t b/debian/perl-framework/t/modules/info.t new file mode 100644 index 0000000..21cee4e --- /dev/null +++ b/debian/perl-framework/t/modules/info.t @@ -0,0 +1,69 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; + +## +## mod_info quick test +## + +plan tests => 1, need_module 'info'; + +my $uri = '/server-info'; +my $info = GET_BODY $uri; +my $config = Apache::Test::config(); +my $mods = $config->{modules}; +my (@actual,@expected) = ((),()); + +## extract module names from html ## +foreach (split /\n/, $info) { + if ($_ =~ /<a name=\"(\w+\.c)\">/) { + if ($1 eq 'util_ldap.c') { + push(@actual,'mod_ldap.c'); + } elsif ($1 eq 'mod_apreq2.c') { + push(@actual,'mod_apreq.c'); + } else { + push(@actual, $1); + } + } +} + +foreach (sort keys %$mods) { + ($mods->{$_} && !$config->should_skip_module($_)) or next; + if ($_ =~ /^mod_mpm_(eventopt|event|motorz|prefork|worker)\.c$/) { + push(@expected,"$1.c"); + } elsif ($_ eq 'mod_mpm_simple.c') { + push(@expected,'simple_api.c'); + # statically linked mod_ldap + } elsif ($_ eq 'util_ldap.c') { + push(@expected,'mod_ldap.c'); + # statically linked mod_apreq2 + } elsif ($_ eq 'mod_apreq2.c') { + push(@expected,'mod_apreq.c'); + } else { + push(@expected,$_); + } +} +@actual = sort @actual; +@expected = sort @expected; + +## verify all mods are there ## +my $ok = 1; +if (@actual == @expected) { + for (my $i=1 ; $i<@expected ; $i++) { + if ($expected[$i] ne $actual[$i]) { + $ok = 0; + print "comparing expected ->$expected[$i]<-\n"; + print "to actual ->$actual[$i]<-\n"; + print "actual:\n@actual\nexpect:\n@expected\n"; + last; + } + } +} else { + $ok = 0; + my $a = @actual; my $e = @expected; + print "actual($a modules):\n@actual\nexpect($e modules):\n@expected\n"; +} + +ok $ok; diff --git a/debian/perl-framework/t/modules/ldap.t b/debian/perl-framework/t/modules/ldap.t new file mode 100644 index 0000000..d3bb8e9 --- /dev/null +++ b/debian/perl-framework/t/modules/ldap.t @@ -0,0 +1,52 @@ +use strict; +use warnings FATAL => 'all'; + +# +# To run tests for mod_authnz_ldap: +# +# a) run an LDAP server with root DN of dc=example,dc=com on localhost port 8389 +# b) populate the directory with the LDIF from scripts/httpd.ldif +# c) configure & run the test suite passing "--defines LDAP" to ./t/TEST +# + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig; + +my $defs = Apache::Test->vars('defines'); +my $ldap_defined = $defs =~ /LDAP/; + +# URL -> username, password, expected-status +my @cases = ( + ['/modules/ldap/simple/' => '', '', 401], + ['/modules/ldap/simple/' => 'alpha', 'badpass', 401], + ['/modules/ldap/simple/' => 'alpha', 'Alpha', 200], + ['/modules/ldap/simple/' => 'gamma', 'Gamma', 200], + ['/modules/ldap/group/' => 'gamma', 'Gamma', 401], + ['/modules/ldap/group/' => 'delta', 'Delta', 200], + ['/modules/ldap/refer/' => 'alpha', 'Alpha', 401], + ['/modules/ldap/refer/' => 'beta', 'Beta', 200], +); + +plan tests => scalar @cases, + need need_module('authnz_ldap'), { "LDAP testing not configured" => $ldap_defined }; + +foreach my $t (@cases) { + my $url = $t->[0]; + my $username = $t->[1]; + my $password = $t->[2]; + my $response; + my $creds; + + if ($username) { + $response = GET $url, username => $username, password => $password; + $creds = "$username/$password"; + } + else { + $response = GET $url; + $creds = "no credentials"; + } + + ok t_cmp($response->code, $t->[3], "test for $url with $creds"); +} diff --git a/debian/perl-framework/t/modules/lua.t b/debian/perl-framework/t/modules/lua.t new file mode 100644 index 0000000..9e6836d --- /dev/null +++ b/debian/perl-framework/t/modules/lua.t @@ -0,0 +1,81 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +my $config = Apache::Test::config(); +my $server = $config->server; +my $version = $server->{version}; +my $scheme = Apache::Test::vars()->{scheme}; +my $hostport = Apache::TestRequest::hostport(); + +my $https = "nope"; +$https = "yep" if $scheme eq "https"; + +my $pfx = "/modules/lua"; + +my @ts = ( + { url => "$pfx/hello.lua", rcontent => "Hello Lua World!\n", + ctype => "text/plain" }, + { url => "$pfx/404?translateme=1", rcontent => "Hello Lua World!\n" }, + + { url => "$pfx/translate-inherit-before/404?translateme=1", rcontent => "other lua handler\n" }, + { url => "$pfx/translate-inherit-default-before/404?translateme=1", rcontent => "other lua handler\n" }, + { url => "$pfx/translate-inherit-after/404?translateme=1", rcontent => "Hello Lua World!\n" }, + + { url => "$pfx/translate-inherit-before/404?translateme=1&ok=1", rcontent => "other lua handler\n" }, + { url => "$pfx/translate-inherit-default-before/404?translateme=1&ok=1", rcontent => "other lua handler\n" }, + # the more specific translate_name handler will run first and return OK. + { url => "$pfx/translate-inherit-after/404?translateme=1&ok=1", rcontent => "other lua handler\n" }, + + { url => "$pfx/version.lua", rcontent => qr(^$version) }, + { url => "$pfx/method.lua", rcontent => "GET" }, + { url => "$pfx/201.lua", rcontent => "", code => 201 }, + { url => "$pfx/https.lua", rcontent => $https }, + { url => "$pfx/setheaders.lua", rcontent => "", + headers => { "X-Header" => "yes", + "X-Host" => $hostport } }, + { url => "$pfx/setheaderfromparam.lua?HeaderName=foo&HeaderValue=bar", + rcontent => "Header set", + headers => { "foo" => "bar" } }, + { url => "$pfx/filtered/foobar.html", + rcontent => "prefix\nbucket:foobar\nsuffix\n" }, +); + +plan tests => 4 * scalar @ts, need 'lua'; + +for my $t (@ts) { + my $url = $t->{"url"}; + my $r = GET $url; + my $code = $t->{"code"} || 200; + my $headers = $t->{"headers"}; + + ok t_cmp($r->code, $code, "code for $url"); + ok t_cmp($r->content, $t->{"rcontent"}, "response content for $url"); + + if ($t->{"ctype"}) { + ok t_cmp($r->header("Content-Type"), $t->{"ctype"}, "c-type for $url"); + } + else { + skip 1; + } + + if ($headers) { + my $correct = 1; + while (my ($name, $value) = each %{$headers}) { + my $actual = $r->header($name) || "<unset>"; + t_debug "'$name' header value is '$actual' (expected '$value')"; + + if ($actual ne $value) { + $correct = 0; + } + } + ok $correct; + } + else { + skip 1; + } +} diff --git a/debian/perl-framework/t/modules/negotiation.t b/debian/perl-framework/t/modules/negotiation.t new file mode 100644 index 0000000..9218aa1 --- /dev/null +++ b/debian/perl-framework/t/modules/negotiation.t @@ -0,0 +1,185 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## mod_negotiation test (see extra.conf.in) + +my ($en, $fr, $de, $fu, $bu, $zh) = qw(en fr de fu bu zh-TW); + +my @language = ($en, $fr, $de, $fu); +if (have_min_apache_version("2.4.38")) { + push @language, $zh; +} + +my @ct_tests = ( + # [ Accept header, Expected response ] + [ "*/*", "text/plain" ], + [ "text/*", "text/plain" ], + [ "text/html", "text/html" ], + [ "image/*", "image/jpeg" ], + [ "image/gif", "image/gif" ], + + [ "*", "text/plain" ], # Dubious + + # Tests which expect a 406 response + [ "", undef ], + [ "*bad", undef ], + [ "/*", undef ], + [ "*/", undef ], + [ "te/*", undef ], +); + +my $tests = (@language * 3) + (@language * @language * 5) + (scalar @ct_tests) + + 7; + +plan tests => $tests, need + need_module('negotiation') && need_cgi && need_module('mime'); + +my $actual; + +#XXX: this is silly; need a better way to be portable +sub my_chomp { + $actual =~ s/[\r\n]+$//s; +} + +foreach (@language) { + + ## verify that the correct default language content is returned + $actual = GET_BODY "/modules/negotiation/$_/"; + print "# GET /modules/negotiation/$_/\n"; + my_chomp(); + ok t_cmp($actual, "index.html.$_", + "Verify correct default language for index.$_.foo"); + + $actual = GET_BODY "/modules/negotiation/$_/compressed/"; + print "# GET /modules/negotiation/$_/compressed/\n"; + my_chomp(); + ok t_cmp($actual, "index.html.$_.gz", + "Verify correct default language for index.$_.foo.gz"); + + $actual = GET_BODY "/modules/negotiation/$_/two/index"; + print "# GET /modules/negotiation/$_/two/index\n"; + my_chomp(); + ok t_cmp($actual, "index.$_.html", + "Verify correct default language for index.$_.html"); + + foreach my $ext (@language) { + + ## verify that you can explicitly request all language files. + my $resp = GET("/modules/negotiation/$_/index.html.$ext"); + print "# GET /modules/negotiation/$_/index.html.$ext\n"; + ok t_cmp($resp->code, + 200, + "Explicitly request $_/index.html.$ext"); + $resp = GET("/modules/negotiation/$_/two/index.$ext.html"); + print "# GET /modules/negotiation/$_/two/index.$ext.html\n"; + ok t_cmp($resp->code, + 200, + "Explicitly request $_/two/index.$ext.html"); + + ## verify that even tho there is a default language, + ## the Accept-Language header is obeyed when present. + $actual = GET_BODY "/modules/negotiation/$_/", + 'Accept-Language' => $ext; + print "# GET /modules/negotiation/$_/\n# Accept-Language: $ext\n"; + my_chomp(); + ok t_cmp($actual, "index.html.$ext", + "Verify with a default language Accept-Language still obeyed"); + + $actual = GET_BODY "/modules/negotiation/$_/compressed/", + 'Accept-Language' => $ext; + print "# GET /modules/negotiation/$_/compressed/\n# Accept-Language: $ext\n"; + my_chomp(); + ok t_cmp($actual, "index.html.$ext.gz", + "Verify with a default language Accept-Language still ". + "obeyed (compression on)"); + + $actual = GET_BODY "/modules/negotiation/$_/two/index", + 'Accept-Language' => $ext; + print "# GET /modules/negotiation/$_/two/index\n# Accept-Language: $ext\n"; + my_chomp(); + ok t_cmp($actual, "index.$ext.html", + "Verify with a default language Accept-Language still obeyed"); + + } +} + +## more complex requests ## + +## 'fu' has a quality rating of 0.9 which is higher than the rest +## we expect Apache to return the 'fu' content. +$actual = GET_BODY "/modules/negotiation/$en/", + 'Accept-Language' => "$en; q=0.1, $fr; q=0.4, $fu; q=0.9, $de; q=0.2"; +print "# GET /modules/negotiation/$en/\n# Accept-Language: $en; q=0.1, $fr; q=0.4, $fu; q=0.9, $de; q=0.2\n"; +my_chomp(); +ok t_cmp($actual, "index.html.$fu", + "fu has a higher quality rating, so we expect fu"); + +$actual = GET_BODY "/modules/negotiation/$en/two/index", + 'Accept-Language' => "$en; q=0.1, $fr; q=0.4, $fu; q=0.9, $de; q=0.2"; +print "# GET /modules/negotiation/$en/two/index\n# Accept-Language: $en; q=0.1, $fr; q=0.4, $fu; q=0.9, $de; q=0.2\n"; +my_chomp(); +ok t_cmp($actual, "index.$fu.html", + "fu has a higher quality rating, so we expect fu"); + +$actual = GET_BODY "/modules/negotiation/$en/compressed/", + 'Accept-Language' => "$en; q=0.1, $fr; q=0.4, $fu; q=0.9, $de; q=0.2"; +print "# GET /modules/negotiation/$en/compressed/\n# Accept-Language: $en; q=0.1, $fr; q=0.4, $fu; q=0.9, $de; q=0.2\n"; +my_chomp(); +ok t_cmp($actual, "index.html.$fu.gz", + "fu has a higher quality rating, so we expect fu"); + +## 'bu' has the highest quality rating, but is non-existant, +## so we expect the next highest rated 'fr' content to be returned. +$actual = GET_BODY "/modules/negotiation/$en/", + 'Accept-Language' => "$en; q=0.1, $fr; q=0.4, $bu; q=1.0"; +print "# GET /modules/negotiation/$en/\n# Accept-Language: $en; q=0.1, $fr; q=0.4, $bu; q=1.0\n"; +my_chomp(); +ok t_cmp($actual, "index.html.$fr", + "bu has the highest quality but is non-existant, so fr is next best"); + +$actual = GET_BODY "/modules/negotiation/$en/two/index", + 'Accept-Language' => "$en; q=0.1, $fr; q=0.4, $bu; q=1.0"; +print "# GET /modules/negotiation/$en/two/index\n# Accept-Language: $en; q=0.1, $fr; q=0.4, $bu; q=1.0\n"; +my_chomp(); +ok t_cmp($actual, "index.$fr.html", + "bu has the highest quality but is non-existant, so fr is next best"); + +$actual = GET_BODY "/modules/negotiation/$en/compressed/", + 'Accept-Language' => "$en; q=0.1, $fr; q=0.4, $bu; q=1.0"; +print "# GET /modules/negotiation/$en/compressed/\n# Accept-Language: $en; q=0.1, $fr; q=0.4, $bu; q=1.0\n"; +my_chomp(); +ok t_cmp($actual, "index.html.$fr.gz", + "bu has the highest quality but is non-existant, so fr is next best"); + +$actual = GET_BODY "/modules/negotiation/query/test?foo"; +print "# GET /modules/negotiation/query/test?foo\n"; +my_chomp(); +ok t_cmp($actual, "QUERY_STRING --> foo", + "The type map gives the script the highest quality;" + . "\nthe request included a query string"); + +## Content-Type tests + +foreach my $test (@ct_tests) { + my $accept = $test->[0]; + my $expected = $test->[1]; + + my $r = GET "/modules/negotiation/content-type/test.var", + Accept => $accept; + + if ($expected) { + $actual = $r->content; + + # Strip whitespace from the body (we pad the variant map with spaces). + $actual =~ s/^\s+|\s+$//g; + + ok t_cmp $expected, $actual, "should send correct variant"; + } + else { + ok t_cmp $r->code, 406, "expect Not Acceptable for Accept: $accept"; + } +} diff --git a/debian/perl-framework/t/modules/proxy.t b/debian/perl-framework/t/modules/proxy.t new file mode 100644 index 0000000..0a81f4f --- /dev/null +++ b/debian/perl-framework/t/modules/proxy.t @@ -0,0 +1,233 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); +use Misc; + +my $num_tests = 46; +plan tests => $num_tests, need need_module 'proxy', need_module 'setenvif'; + +Apache::TestRequest::module("proxy_http_reverse"); +Apache::TestRequest::user_agent(requests_redirectable => 0); + +my $r = GET("/reverse/"); +ok t_cmp($r->code, 200, "reverse proxy"); +ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body"); + +$r = GET("/reverse/index.html"); +ok t_cmp($r->code, 200, "reverse proxy to index.html"); +ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body to index.html"); + +if (have_min_apache_version('2.4.49')) { + $r = GET("/reverse-match/"); + ok t_cmp($r->code, 200, "reverse proxy match"); + ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body match"); + + $r = GET("/reverse-match/index.html"); + ok t_cmp($r->code, 200, "reverse proxy match to index.html"); + ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body match to index.html"); +} +else { + skip "skipping reverse-match test with httpd <2.5.1" foreach (1..4); +} + +$r = GET("/reverse-slash"); +ok t_cmp($r->code, 200, "reverse proxy match no slash"); +ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body no slash"); + +$r = GET("/reverse-slash/"); +ok t_cmp($r->code, 200, "reverse proxy match w/ slash"); +ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body w/ slash"); + +$r = GET("/reverse-slash/index.html"); +ok t_cmp($r->code, 200, "reverse proxy match w/ slash to index.html"); +ok t_cmp($r->content, qr/^welcome to /, "reverse proxied body w/ slash to index.html"); + +if (have_min_apache_version('2.4.0')) { + $r = GET("/reverse/locproxy/"); + ok t_cmp($r->code, 200, "reverse Location-proxy to index.html"); + ok t_cmp($r->content, qr/^welcome to /, "reverse Location-proxied body"); +} +else { + skip "skipping per-location test with httpd <2.4" foreach (1..2); +} + +if (have_min_apache_version('2.4.26')) { + # This location should get trapped by the SetEnvIf and NOT be + # proxied, hence should get a 404. + $r = GET("/reverse/locproxy/index.html"); + ok t_cmp($r->code, 404, "reverse Location-proxy blocked by no-proxy env"); +} else { + skip "skipping no-proxy test with httpd <2.4.26"; +} + +if (have_cgi) { + $r = GET("/reverse/modules/cgi/env.pl"); + ok t_cmp($r->code, 200, "reverse proxy to env.pl"); + ok t_cmp($r->content, qr/^APACHE_TEST_HOSTNAME = /, "reverse proxied env.pl response"); + ok t_cmp($r->content, qr/HTTP_X_FORWARDED_FOR = /, "X-Forwarded-For enabled"); + + if (have_min_apache_version('2.4.28')) { + Apache::TestRequest::module("proxy_http_nofwd"); + $r = GET("/reverse/modules/cgi/env.pl"); + ok t_cmp($r->code, 200, "reverse proxy to env.pl without X-F-F"); + ok !t_cmp($r->content, qr/HTTP_X_FORWARDED_FOR = /, "reverse proxied env.pl w/o X-F-F"); + + Apache::TestRequest::module("proxy_http_reverse"); + } + else { + skip "skipping tests with httpd < 2.4.28" foreach (1..2); + } + + $r = GET("/reverse/modules/cgi/env.pl?reverse-proxy"); + ok t_cmp($r->code, 200, "reverse proxy with query string"); + ok t_cmp($r->content, qr/QUERY_STRING = reverse-proxy\n/s, "reverse proxied query string OK"); + + $r = GET("/reverse/modules/cgi/nph-dripfeed.pl"); + ok t_cmp($r->code, 200, "reverse proxy to dripfeed CGI"); + ok t_cmp($r->content, "abcdef", "reverse proxied to dripfeed CGI content OK"); + + if (have_min_apache_version('2.1.0')) { + $r = GET("/reverse/modules/cgi/nph-102.pl"); + ## Uncomment next 2 lines and comment out the subsequant 2 lines + ## when LWP is fixed to work w/ 1xx + ##ok t_cmp($r->code, 200, "reverse proxy to nph-102"); + ##ok t_cmp($r->content, "this is nph-stdout", "reverse proxy 102 response"); + ok t_cmp($r->code, 102, "reverse proxy to nph-102"); + ok t_cmp($r->content, "", "reverse proxy 102 response"); + } else { + skip "skipping tests with httpd <2.1.0" foreach (1..2); + } + +} else { + skip "skipping tests without CGI module" foreach (1..11); +} + +if (have_min_apache_version('2.0.55')) { + # trigger the "proxy decodes abs_path issue": with the bug present, the + # proxy URI-decodes on the way through, so the origin server receives + # an abs_path of "/reverse/nonesuch/file%", which it fails to parse and + # returns a 400 response. + $r = GET("/reverse/nonesuch/file%25"); + ok t_cmp($r->code, 404, "reverse proxy URI decoding issue, PR 15207"); +} else { + skip "skipping PR 15207 test with httpd < 2.0.55"; +} + +$r = GET("/reverse/notproxy/local.html"); +ok t_cmp($r->code, 200, "ProxyPass not-proxied request"); +my $c = $r->content; +chomp $c; +ok t_cmp($c, "hello world", "ProxyPass not-proxied content OK"); + +# Testing ProxyPassReverseCookieDomain and ProxyPassReverseCookiePath +if (have_min_apache_version('2.4.34') && have_module('lua')) { + # '/' is escaped as %2F + # ';' is escaped as %3B + # '=' is escaped as %3D + $r = GET("/reverse/modules/lua/setheaderfromparam.lua?HeaderName=Set-Cookie&HeaderValue=fakedomain%3Dlocal%3Bdomain%3Dlocal"); + ok t_cmp($r->code, 200, "Lua executed"); + ok t_cmp($r->header("Set-Cookie"), "fakedomain=local;domain=remote", "'Set-Cookie domain=' wrongly updated by ProxyPassReverseCookieDomain, PR 61560"); + + $r = GET("/reverse/modules/lua/setheaderfromparam.lua?HeaderName=Set-Cookie&HeaderValue=fakepath%3D%2Flocal%3Bpath%3D%2Flocal"); + ok t_cmp($r->code, 200, "Lua executed"); + ok t_cmp($r->header("Set-Cookie"), "fakepath=/local;path=/remote", "'Set-Cookie path=' wrongly updated by ProxyPassReverseCookiePath, PR 61560"); + + $r = GET("/reverse/modules/lua/setheaderfromparam.lua?HeaderName=Set-Cookie&HeaderValue=domain%3Dlocal%3Bpath%3D%2Flocal%3bfoo%3Dbar"); + ok t_cmp($r->code, 200, "Lua executed"); + ok t_cmp($r->header("Set-Cookie"), "domain=remote;path=/remote;foo=bar", "'Set-Cookie path=' wrongly updated by ProxyPassReverseCookiePath and/or ProxyPassReverseCookieDomain"); +} +else { + skip "skipping tests which need mod_lua" foreach (1..6); +} + +if (have_module('alias')) { + $r = GET("/reverse/perm"); + ok t_cmp($r->code, 301, "reverse proxy of redirect"); + ok t_cmp($r->header("Location"), qr{http://[^/]*/reverse/alias}, "reverse proxy rewrote redirect"); + + if (have_module('proxy_balancer')) { + # More complex reverse mapping case with the balancer, PR 45434 + Apache::TestRequest::module("proxy_http_balancer"); + my $hostport = Apache::TestRequest::hostport(); + $r = GET("/pr45434/redirect-me"); + ok t_cmp($r->code, 301, "reverse proxy of redirect via balancer"); + ok t_cmp($r->header("Location"), "http://$hostport/pr45434/5.html", "reverse proxy via balancer rewrote redirect"); + Apache::TestRequest::module("proxy_http_reverse"); # flip back + } else { + skip "skipping tests without mod_proxy_balancer" foreach (1..2); + } + +} else { + skip "skipping tests without mod_alias" foreach (1..4); +} + +sub uds_script +{ + use Socket; + use strict; + + my $socket_path = shift; + my $sock_addr = sockaddr_un($socket_path); + socket(my $server, PF_UNIX, SOCK_STREAM, 0) || die "socket: $!"; + bind($server, $sock_addr) || die "bind: $!"; + listen($server,1024) || die "listen: $!"; + open(MARKER, '>', $socket_path.'.marker') or die "Unable to open file $socket_path.marker : $!"; + close(MARKER); + if (accept(my $new_sock, $server)) { + my $data = <$new_sock>; + print $new_sock "HTTP/1.0 200 OK\r\n"; + print $new_sock "Content-Type: text/plain\r\n\r\n"; + print $new_sock "hello world\n"; + close $new_sock; + } + unlink($socket_path); + unlink($socket_path.'.marker'); +} + +if (have_min_apache_version('2.4.7')) { + my $socket_path = '/tmp/test-ptf.sock'; + unlink($socket_path); + my $pid = fork(); + unless (defined $pid) { + t_debug "couldn't fork UDS script"; + ok 0; + exit; + } + if ($pid == 0) { + uds_script($socket_path); + exit; + } + unless (Misc::cwait('-e "'.$socket_path.'.marker"', 10, 50)) { + ok 0; + exit; + } + sleep(1); + $r = GET("/uds/"); + ok t_cmp($r->code, 200, "ProxyPass UDS path"); + my $c = $r->content; + chomp $c; + ok t_cmp($c, "hello world", "UDS content OK"); + +} +else { + skip "skipping UDS tests with httpd < 2.4.7" foreach (1..2); +} + +if (have_min_apache_version('2.4.49')) { + + $r = GET("/notexisting/../mapping/mapping.html"); + ok t_cmp($r->code, 200, "proxy mapping=servlet map it to /servlet/mapping.html"); + + $r = GET("/notexisting/..;/mapping/mapping.html"); + ok t_cmp($r->code, 200, "proxy mapping=servlet map it to /servlet/mapping.html"); + + $r = GET("/mapping/mapping.html"); + ok t_cmp($r->code, 200, "proxy to /servlet/mapping.html"); +} +else { + skip "skipping tests with mapping=servlet" foreach (1..3); +} diff --git a/debian/perl-framework/t/modules/proxy_balancer.t b/debian/perl-framework/t/modules/proxy_balancer.t new file mode 100644 index 0000000..94753b7 --- /dev/null +++ b/debian/perl-framework/t/modules/proxy_balancer.t @@ -0,0 +1,125 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +my @echos = ('A'x8, 'A'x64, 'A'x2048, 'A'x4096); + +my $skipbodyfailover = !need_min_apache_version("2.4.42"); +my $referertest = 0; + +if (have_min_apache_version("2.4.41")) { + $referertest = 2; +} + +plan tests => 6+(2*scalar @echos)+$referertest, need 'proxy_balancer', 'proxy_http'; + +Apache::TestRequest::module("proxy_http_balancer"); +Apache::TestRequest::user_agent(requests_redirectable => 0); + +# Extract the nonce from response to the URL +sub GetNonce { + my $url = shift; + my $balancer = shift; + my $r; + $r = GET($url); + my $NONCE; + foreach my $query ( split( /\?b=/, $r->content ) ){ + if ($query =~ m/$balancer/) { + foreach my $var ( split( /&/, $query ) ){ + if ($var =~ m/nonce=/) { + foreach my $nonce ( split( /nonce=/, $var ) ){ + my $ind = index ($nonce, "\""); + $nonce = substr($nonce, 0, ${ind}); + if ( $nonce =~ m/^[0-9a-fA-F-]+$/ ) { + $NONCE = $nonce; + last; + } + } + last; + } + } + last; + } + } + return $NONCE; +} + +my $r; + +if (have_module('lbmethod_byrequests')) { + $r = GET("/baltest1/index.html"); + ok t_cmp($r->code, 200, "Balancer did not die"); +} else { + skip "skipping tests without mod_lbmethod_byrequests" foreach (1..1); +} + +if (have_module('lbmethod_bytraffic')) { + $r = GET("/baltest2/index.html"); + ok t_cmp($r->code, 200, "Balancer did not die"); +} else { + skip "skipping tests without mod_lbmethod_bytraffic" foreach (1..1); +} + +if (have_module('lbmethod_bybusyness')) { + $r = GET("/baltest3/index.html"); + ok t_cmp($r->code, 200, "Balancer did not die"); +} else { + skip "skipping tests without mod_lbmethod_bybusyness" foreach (1..1); +} + +if (have_module('lbmethod_heartbeat')) { + #$r = GET("/baltest4/index.html"); + #ok t_cmp($r->code, 200, "Balancer did not die"); +} else { + #skip "skipping tests without mod_lbmethod_heartbeat" foreach (1..1); +} + + + +# PR63891 +foreach my $t (@echos) { + $r = POST "/baltest_echo_post", content => $t; + skip $skipbodyfailover, t_cmp($r->code, 200, "failed over"); + skip $skipbodyfailover, t_cmp($r->content, $t, "response body echoed"); +} + +# test dynamic part +$r = GET("/balancer-manager"); +ok t_cmp($r->code, 200, "Can't find balancer-manager"); + +# get the nonce and add a worker +my $result = GetNonce("/balancer-manager", "dynproxy"); + +my $query = "b_lbm=byrequests&b_tmo=0&b_max=0&b_sforce=0&b_ss=&b_nwrkr=ajp%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3A0%3A0%3A1%5D%3A8080&b_wyes=1&b=dynproxy&nonce=" . $result; +my @proxy_balancer_headers; +my $vars = Apache::Test::vars(); +push @proxy_balancer_headers, "Referer" => "http://" . $vars->{servername} . ":" . $vars->{port} . "/balancer-manager"; + +# First try without the referer it should fail. +if (have_min_apache_version("2.4.41")) { + $r = POST("/balancer-manager", content => $query); + ok t_cmp($r->code, 200, "request failed"); + ok !t_cmp($r->content, qr/ajp/, "AJP worker created"); +} + +# Try with the referer and http (byrequests) +if (have_min_apache_version("2.4.49") && have_module('lbmethod_byrequests')) { + $r = GET("/dynproxy"); + ok t_cmp($r->code, 503, "request should fail for /dynproxy"); + # create it + $query = "b_lbm=byrequests&b_tmo=0&b_max=0&b_sforce=0&b_ss=&b_nwrkr=http%3A%2F%2Flocalhost%3A8529&b_wyes=1&b=dynproxy&nonce=" . $result; + $r = POST("/balancer-manager", content => $query, @proxy_balancer_headers); + # enable it. + $query = "w=http%3A%2F%2Flocalhost%3A8529&b=dynproxy&w_status_D=0&nonce=" . $result; + $r = POST("/balancer-manager", content => $query, @proxy_balancer_headers); + # make a query + $r = GET("/dynproxy"); + ok t_cmp($r->code, 200, "request failed to /dynproxy"); +} else { + skip "skipping tests without lbmethod_byrequests"; + skip "skipping tests without lbmethod_byrequests"; +} diff --git a/debian/perl-framework/t/modules/proxy_fcgi.t b/debian/perl-framework/t/modules/proxy_fcgi.t new file mode 100644 index 0000000..2f62580 --- /dev/null +++ b/debian/perl-framework/t/modules/proxy_fcgi.t @@ -0,0 +1,300 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Misc; + +my $have_fcgisetenvif = have_min_apache_version('2.4.26'); +my $have_fcgibackendtype = have_min_apache_version('2.4.26'); +# NOTE: This will fail if php-fpm is installed but not in $PATH +my $have_php_fpm = `php-fpm -v` =~ /fpm-fcgi/; + +plan tests => (7 * $have_fcgisetenvif) + (2 * $have_fcgibackendtype) + + (2 * $have_fcgibackendtype * have_module('rewrite')) + + (7 * have_module('rewrite')) + (7 * have_module('actions')) + + (15 * $have_php_fpm * have_module('actions')) + 2, + need ( + 'mod_proxy_fcgi', + 'FCGI', + 'IO::Select' + ); + +require FCGI; +require IO::Select; + +Apache::TestRequest::module("proxy_fcgi"); + +# Launches a short-lived FCGI daemon that will handle exactly one request with +# the given handler function. Returns the child PID; exits on failure. + +sub run_fcgi_handler($$) +{ + my $fcgi_port = shift; + my $handler_func = shift; + + # Use a pipe for ready-signalling between the child and parent. Much faster + # (and more reliable) than just sleeping for a few seconds. + pipe(READ_END, WRITE_END); + my $pid = fork(); + + unless (defined $pid) { + t_debug "couldn't fork FCGI process"; + ok 0; + exit; + } + + if ($pid == 0) { + # Child process. Open up a listening socket. + my $sock = FCGI::OpenSocket(":$fcgi_port", 10); + + # Signal the parent process that we're ready. + print WRITE_END 'x'; + close WRITE_END; + + # Listen for and respond to exactly one request from the client. + my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, + $sock, &FCGI::FAIL_ACCEPT_ON_INTR); + + if ($request->Accept() == 0) { + # Run the handler. + $handler_func->(); + $request->Finish(); + } + + # Clean up and exit. + FCGI::CloseSocket($sock); + exit; + } + + # Parent process. Wait for the daemon to launch. + unless (IO::Select->new((\*READ_END,))->can_read(2)) { + t_debug "timed out waiting for FCGI process to start"; + ok 0; + + kill 'TERM', $pid; + # Note that we don't waitpid() here because Perl's fork() implementation + # on some platforms (Windows) doesn't guarantee that the pseudo-TERM + # signal will be delivered. Just wait for the child to be cleaned up + # when we exit. + + exit; + } + + return $pid; +} + +# Convenience wrapper for run_fcgi_handler() that will echo back the envvars in +# the response. Returns the child PID; exits on failure. +sub launch_envvar_echo_daemon($) +{ + my $fcgi_port = shift; + + return run_fcgi_handler($fcgi_port, sub { + # Echo all the envvars back to the client. + print("Content-Type: text/plain\r\n\r\n"); + foreach my $key (sort(keys %ENV)) { + print($key, "=", $ENV{$key}, "\n"); + } + }); +} + +# Runs a single request using launch_envvar_echo_daemon(), then returns a +# hashref containing the environment variables that were echoed by the FCGI +# backend. +# +# Calling this function will run one test that must be accounted for in the test +# plan. +sub run_fcgi_envvar_request +{ + my $fcgi_port = shift; + my $uri = shift; + my $backend = shift || "FCGI"; + + # Launch the FCGI process. + my $child = launch_envvar_echo_daemon($fcgi_port) unless ($fcgi_port <= 0) ; + + # Hit the backend. + my $r = GET($uri); + ok t_cmp($r->code, 200, "proxy to $backend backend works (" . $uri . ")"); + + # Split the returned envvars into a dictionary. + my %envs = (); + + foreach my $line (split /\n/, $r->content) { + t_debug("> $line"); # log the response lines for debugging + + my @components = split /=/, $line, 2; + $envs{$components[0]} = $components[1]; + } + + if ($fcgi_port > 0) { + if ($r->code eq '500') { + # Unknown failure, probably the request didn't hit the FCGI child + # process, so it will hang waiting for our request + kill 'TERM', $child; + } else { + # Rejoin the child FCGI process. + waitpid($child, 0); + } + } + + return \%envs; +} + +# +# MAIN +# + +# XXX There appears to be no way to get the value of a dynamically-reserved +# @NextAvailablePort@ from Apache::Test. We assume here that the port reserved +# for the proxy_fcgi vhost is one greater than the reserved FCGI_PORT, but +# depending on the test conditions, that may not always be the case... +my $fcgi_port = Apache::Test::vars('proxy_fcgi_port') - 1; +my $envs; +my $docroot = Apache::Test::vars('documentroot'); +my $servroot = Apache::Test::vars('serverroot'); + +if ($have_fcgisetenvif) { + # ProxyFCGISetEnvIf tests. Query the backend. + $envs = run_fcgi_envvar_request($fcgi_port, "/fcgisetenv?query"); + + # Check the response values. + ok t_cmp($envs->{'QUERY_STRING'}, 'test_value', "ProxyFCGISetEnvIf can override an existing variable"); + ok t_cmp($envs->{'TEST_NOT_SET'}, undef, "ProxyFCGISetEnvIf does not set variables if condition is false"); + ok t_cmp($envs->{'TEST_EMPTY'}, '', "ProxyFCGISetEnvIf can set empty values"); + ok t_cmp($envs->{'TEST_DOCROOT'}, $docroot, "ProxyFCGISetEnvIf can replace with request variables"); + ok t_cmp($envs->{'TEST_CGI_VERSION'}, 'v1.1', "ProxyFCGISetEnvIf can replace with backreferences"); + ok t_cmp($envs->{'REMOTE_ADDR'}, undef, "ProxyFCGISetEnvIf can unset var"); +} + +# Tests for GENERIC backend type behavior. +if ($have_fcgibackendtype) { + # Regression test for PR59618. + $envs = run_fcgi_envvar_request($fcgi_port, "/modules/proxy/fcgi-generic/index.php?query"); + + ok t_cmp($envs->{'SCRIPT_FILENAME'}, + $docroot . '/modules/proxy/fcgi-generic/index.php', + "GENERIC SCRIPT_FILENAME should have neither query string nor proxy: prefix"); +} + +if ($have_fcgibackendtype && have_module('rewrite')) { + # Regression test for PR59815. + $envs = run_fcgi_envvar_request($fcgi_port, "/modules/proxy/fcgi-generic-rewrite/index.php?query"); + + ok t_cmp($envs->{'SCRIPT_FILENAME'}, + $docroot . '/modules/proxy/fcgi-generic-rewrite/index.php', + "GENERIC SCRIPT_FILENAME should have neither query string nor proxy: prefix"); +} + +if (have_module('rewrite')) { + # Regression test for general FPM breakage when using mod_rewrite for + # nice-looking URIs; see + # https://github.com/apache/httpd/commit/cab0bfbb2645bb8f689535e5e2834e2dbc23f5a5#commitcomment-20393588 + $envs = run_fcgi_envvar_request($fcgi_port, "/modules/proxy/fcgi-rewrite-path-info/path/info?query"); + + # Not all of these values make sense, but unfortunately FPM expects some + # breakage and doesn't function properly without it, so we can't fully fix + # the problem by default. These tests verify that we follow the 2.4.20 way + # of doing things for the "rewrite-redirect PATH_INFO to script" case. + ok t_cmp($envs->{'SCRIPT_FILENAME'}, "proxy:fcgi://127.0.0.1:" . $fcgi_port + . $docroot + . '/modules/proxy/fcgi-rewrite-path-info/index.php', + "Default SCRIPT_FILENAME has proxy:fcgi prefix for compatibility"); + ok t_cmp($envs->{'SCRIPT_NAME'}, '/modules/proxy/fcgi-rewrite-path-info/index.php', + "Default SCRIPT_NAME uses actual path to script"); + ok t_cmp($envs->{'PATH_INFO'}, '/path/info', + "Default PATH_INFO is correct"); + ok t_cmp($envs->{'PATH_TRANSLATED'}, $docroot . '/path/info', + "Default PATH_TRANSLATED is correct"); + ok t_cmp($envs->{'QUERY_STRING'}, 'query', + "Default QUERY_STRING is correct"); + ok t_cmp($envs->{'REDIRECT_URL'}, '/modules/proxy/fcgi-rewrite-path-info/path/info', + "Default REDIRECT_URL uses original client URL"); +} + +if (have_module('actions')) { + # Regression test to ensure that the bizarre Action invocation for FCGI + # still works as it did in 2.4.20. Almost none of this follows any spec at + # all. As far as I can tell, this method does not work with FPM. + $envs = run_fcgi_envvar_request($fcgi_port, "/modules/proxy/fcgi-action/index.php/path/info?query"); + + ok t_cmp($envs->{'SCRIPT_FILENAME'}, "proxy:fcgi://127.0.0.1:" . $fcgi_port + . $docroot + . '/fcgi-action-virtual', + "Action SCRIPT_FILENAME has proxy:fcgi prefix and uses virtual action Location"); + ok t_cmp($envs->{'SCRIPT_NAME'}, '/fcgi-action-virtual', + "Action SCRIPT_NAME is the virtual action Location"); + ok t_cmp($envs->{'PATH_INFO'}, '/modules/proxy/fcgi-action/index.php/path/info', + "Action PATH_INFO contains full URI path"); + ok t_cmp($envs->{'PATH_TRANSLATED'}, $docroot . '/modules/proxy/fcgi-action/index.php/path/info', + "Action PATH_TRANSLATED contains full URI path"); + ok t_cmp($envs->{'QUERY_STRING'}, 'query', + "Action QUERY_STRING is correct"); + ok t_cmp($envs->{'REDIRECT_URL'}, '/modules/proxy/fcgi-action/index.php/path/info', + "Action REDIRECT_URL uses original client URL"); + + # Testing using php-fpm directly + if ($have_php_fpm) { + my $pid_file = "/tmp/php-fpm-" . $$ . "-" . time . ".pid"; + my $pid = fork(); + unless (defined $pid) { + t_debug "couldn't start PHP-FPM"; + ok 0; + exit; + } + if ($pid == 0) { + system "php-fpm -n -D -g $pid_file -p $servroot/php-fpm"; + exit; + } + # Wait for php-fpm to start-up + unless ( Misc::cwait('-e "'.$pid_file.'"', 10, 50) ) { + ok 0; + exit; + } + sleep(1); + $envs = run_fcgi_envvar_request(0, "/php/fpm/action/sub2/test.php/foo/bar?query", "PHP-FPM"); + ok t_cmp($envs->{'SCRIPT_NAME'}, '/php/fpm/action/sub2/test.php', + "Handler PHP-FPM sets correct SCRIPT_NAME"); + ok t_cmp($envs->{'PATH_INFO'}, '/foo/bar', + "Handler PHP-FPM sets correct PATH_INFO"); + ok t_cmp($envs->{'QUERY_STRING'}, 'query', + "Handler PHP-FPM sets correct QUERY_STRING"); + ok t_cmp($envs->{'PATH_TRANSLATED'}, $docroot . '/foo/bar', + "Handler PHP-FPM sets correct PATH_TRANSLATED"); + ok t_cmp($envs->{'FCGI_ROLE'}, 'RESPONDER', + "Handler PHP-FPM sets correct FCGI_ROLE"); + + $envs = run_fcgi_envvar_request(0, "/php-fpm-pp/php/fpm/pp/sub1/test.php/foo/bar?query", "PHP-FPM"); + ok t_cmp($envs->{'SCRIPT_NAME'}, '/php-fpm-pp/php/fpm/pp/sub1/test.php', + "ProxyPass PHP-FPM sets correct SCRIPT_NAME"); + ok t_cmp($envs->{'PATH_INFO'}, '/foo/bar', + "ProxyPass PHP-FPM sets correct PATH_INFO"); + ok t_cmp($envs->{'QUERY_STRING'}, 'query', + "ProxyPass PHP-FPM sets correct QUERY_STRING"); + ok t_cmp($envs->{'PATH_TRANSLATED'}, $docroot . '/foo/bar', + "ProxyPass PHP-FPM sets correct PATH_TRANSLATED"); + ok t_cmp($envs->{'FCGI_ROLE'}, 'RESPONDER', + "ProxyPass PHP-FPM sets correct FCGI_ROLE"); + + $envs = run_fcgi_envvar_request(0, "/php-fpm-pp/php/fpm/pp/sub1/test.php", "PHP-FPM"); + ok t_cmp($envs->{'PATH_INFO'}, undef, + "ProxyPass PHP-FPM sets correct empty PATH_INFO"); + ok t_cmp($envs->{'PATH_TRANSLATED'}, undef, + "ProxyPass PHP-FPM does not set PATH_TRANSLATED w/ empty PATH_INFO"); + + # TODO: Add more tests here + + # Clean up php-fpm process(es) + kill 'TERM', $pid; # Kill child process + kill 'TERM', `cat $pid_file`; # Kill php-fpm daemon + waitpid($pid, 0); + } + +} + +# Regression test for PR61202. +$envs = run_fcgi_envvar_request($fcgi_port, "/modules/proxy/fcgi/index.php"); +ok t_cmp($envs->{'SCRIPT_NAME'}, '/modules/proxy/fcgi/index.php', "Server sets correct SCRIPT_NAME by default"); + diff --git a/debian/perl-framework/t/modules/proxy_websockets.t b/debian/perl-framework/t/modules/proxy_websockets.t new file mode 100644 index 0000000..ed7ea97 --- /dev/null +++ b/debian/perl-framework/t/modules/proxy_websockets.t @@ -0,0 +1,53 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use Apache::TestConfig (); + +my $total_tests = 1; + +plan tests => $total_tests, need 'AnyEvent::WebSocket::Client', + need_module('proxy_http', 'lua'), need_min_apache_version('2.4.47'); + +require AnyEvent; +require AnyEvent::WebSocket::Client; + +my $config = Apache::Test::config(); +my $hostport = Apache::TestRequest::hostport(); + +my $client = AnyEvent::WebSocket::Client->new(timeout => 5); + +my $quit_program = AnyEvent->condvar; + +my $pingok = 0; + +$client->connect("ws://$hostport/proxy/wsoc")->cb(sub { + our $connection = eval { shift->recv }; + t_debug("wsoc connected"); + if($@) { + # handle error... + warn $@; + $quit_program->send(); + return; + } + + $connection->send('ping'); + + # recieve message from the websocket... + $connection->on(each_message => sub { + # $connection is the same connection object + # $message isa AnyEvent::WebSocket::Message + my($connection, $message) = @_; + t_debug("wsoc msg received: " . $message->body); + if ("ping" eq $message->body) { + $pingok = 1; + } + $connection->send('quit'); + $quit_program->send(); + }); +}); + +$quit_program->recv; +ok t_cmp($pingok, 1); diff --git a/debian/perl-framework/t/modules/ratelimit.t b/debian/perl-framework/t/modules/ratelimit.t new file mode 100644 index 0000000..27ce3a8 --- /dev/null +++ b/debian/perl-framework/t/modules/ratelimit.t @@ -0,0 +1,43 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use MIME::Base64; +use Data::Dumper; +use HTTP::Response; +use Socket; + +use LWP::UserAgent (); + + +my @testcases = ( + ['/apache/ratelimit/' => '200', "ratelimited small file"], + ['/apache/ratelimit/autoindex/' => '200', "ratelimited small autoindex output"], + ['/apache/ratelimit/chunk?0,8192' => '200', "ratelimited chunked response"], +); + +plan tests => scalar @testcases, need need_lwp, + need_module('mod_ratelimit'), + need_module('mod_autoindex'), + need_min_apache_version('2.4.35'); + +my $ua = LWP::UserAgent->new; +$ua->timeout(4); + +foreach my $t (@testcases) { + my $r; + + # trap a die() in WLP when the the status line is invalid to avoid + # 'dubious test...' instead of just a failure. + eval { $r = GET($t->[0]) ; + chomp $r; + t_debug "Status Line: '" . $r->status_line . "'"; + ok t_cmp($r->code, $t->[1], $t->[2]); + }; + # Check if the eval() die'ed + ok t_cmp($@, undef, $t->[2]) if $@ + +} + diff --git a/debian/perl-framework/t/modules/reflector.t b/debian/perl-framework/t/modules/reflector.t new file mode 100644 index 0000000..5d5c86b --- /dev/null +++ b/debian/perl-framework/t/modules/reflector.t @@ -0,0 +1,44 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my @testcases = ( + ['/apache/reflector_nodeflate/', "Text that will not reach the DEFLATE filter"], + ['/apache/reflector_deflate/', "Text that should be gzipped"], +); + +my @headers; +push @headers, "header2reflect" => "1"; +push @headers, "header2update" => "1"; +push @headers, "header2delete" => "1"; +push @headers, "Content-Encoding" => "gzip"; +push @headers, "Accept-Encoding" => "gzip"; + +plan tests => scalar @testcases * 7, need 'mod_reflector', 'mod_deflate'; + +foreach my $t (@testcases) { + my $r = POST($t->[0], @headers, content => $t->[1]); + + # Checking for return code + ok t_cmp($r->code, 200, "Checking return code is '200'"); + + # Checking for content + if (index($t->[0], "_nodeflate") != -1) { + # With no filter, we should receive what we have sent + ok t_is_equal($r->content, $t->[1]); + ok t_cmp($r->header("Content-Encoding"), undef, "'Content-Encoding' has not been added because there was no filter"); + } else { + # With DEFLATE, input should have been updated and 'Content-Encoding' added + ok not t_is_equal($r->content, $t->[1]); + ok t_cmp($r->header("Content-Encoding"), "gzip", "'Content-Encoding' has been added by the DEFLATE filter"); + } + + # Checking for headers + ok t_cmp($r->header("header2reflect"), "1", "'header2reflect' is present"); + ok t_cmp($r->header("header2update"), undef, "'header2update' is absent"); + ok t_cmp($r->header("header2updateUpdated"), "1", "'header2updateUpdated' is present"); + ok t_cmp($r->header("header2delete"), undef, "'header2delete' is absent"); +} diff --git a/debian/perl-framework/t/modules/remoteip.t b/debian/perl-framework/t/modules/remoteip.t new file mode 100644 index 0000000..0fbadcd --- /dev/null +++ b/debian/perl-framework/t/modules/remoteip.t @@ -0,0 +1,97 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; +use HTTP::Response; + +## +## mod_remoteip tests +## +## PROXY protocol: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt +## +Apache::TestRequest::module("remote_ip"); +plan tests => 12, + need( + need_module('remoteip'), + need_min_apache_version('2.4.30') + ); + +sub slurp +{ + my $s = shift; + my $r = ""; + my $b; + while ($s->read($b, 10000) > 0) { + $r .= $b; + } + return $r; +} + +ok(my $sock = Apache::TestRequest::vhost_socket("remote_ip")); + +# +# Test human readable format: TCP4 +# +my $proxy = "PROXY TCP4 192.168.192.66 192.168.192.77 1111 2222\r\n"; +my $url = "GET /index.html HTTP/1.1\r\nConnection: close\r\n"; +$url .= "Host: dummy\r\n\r\n"; + +$sock->print($proxy . $url); +$sock->shutdown(1); + +my $response_data = slurp($sock); +my $r = HTTP::Response->parse($response_data); +chomp(my $content = $r->content); +ok t_cmp($r->code, 200, "PROXY human readable TCP4 protocol check"); +ok t_cmp($content, "PROXY-OK", "Content check"); +$sock->shutdown(2); + +# +# BAD format test +# +$proxy = "PROXY FOO 192.168.192.66 192.168.192.77 1111 2222\r\n"; +ok ($sock = Apache::TestRequest::vhost_socket("remote_ip")); +$sock->print($proxy . $url); +$sock->shutdown(1); + +# In httpd, a bad PROXY format simply results in the connection +# being dropped. So ensure we don't get anything that looks +# like a response +$response_data = slurp($sock); +$r = HTTP::Response->parse($response_data); +chomp($content = $r->content); +ok t_cmp($r->code, undef, "broken PROXY human readable protocol check"); +ok t_cmp($content, "", "Content check"); +$sock->shutdown(2); + +# +# Test human readable format: TCP6 +# +$proxy = "PROXY TCP6 2001:DB8::21f:5bff:febf:ce22:8a2e 2001:DB8::12f:8baa:eafc:ce29:6b2e 3333 4444\r\n"; +ok ($sock = Apache::TestRequest::vhost_socket("remote_ip")); +$sock->print($proxy . $url); +$sock->shutdown(1); +$response_data = slurp($sock); +$r = HTTP::Response->parse($response_data); +chomp($content = $r->content); +ok t_cmp($r->code, 200, "PROXY human readable TCP6 protocol check"); +ok t_cmp($content, "PROXY-OK", "Content check"); +$sock->shutdown(2); + +# Test binary format +$proxy = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; # header +$proxy .= "\x21"; # protocol version and command (AF_INET STREAM) +$proxy .= "\x11"; # transport protocol and address family (TCP over IPv4) +$proxy .= "\x00\x0C"; # 12 bytes coming up +$proxy .= "\xC0\xA8\xC0\x42\xC0\xA8\xC0\x4D\x01\xF0\x01\xF1"; # IP addresses and ports +ok ($sock = Apache::TestRequest::vhost_socket("remote_ip")); +$sock->print($proxy . $url); +$sock->shutdown(1); +$response_data = slurp($sock); +$r = HTTP::Response->parse($response_data); +chomp($content = $r->content); +ok t_cmp($r->code, 200, "PROXY binary protocol TCP4 check"); +ok t_cmp($content, "PROXY-OK", "Content check"); +$sock->shutdown(2); diff --git a/debian/perl-framework/t/modules/rewrite.t b/debian/perl-framework/t/modules/rewrite.t new file mode 100644 index 0000000..30bb334 --- /dev/null +++ b/debian/perl-framework/t/modules/rewrite.t @@ -0,0 +1,186 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## mod_rewrite tests +## +## extra.conf.in: + +my @map = qw(txt rnd prg); #dbm XXX: howto determine dbm support is available? +my @num = qw(1 2 3 4 5 6); +my @url = qw(forbidden gone perm temp); +my @todo; +my $r; + +if (!have_min_apache_version('2.4.19')) { + # PR 50447, server context + push @todo, 26 +} +if (!have_min_apache_version('2.4')) { + # PR 50447, directory context (r1044673) + push @todo, 24 +} + +# Specific tests for PR 58231 +my $vary_header_tests = (have_min_apache_version("2.4.30") ? 9 : 0) + (have_min_apache_version("2.4.29") ? 4 : 0); +my $cookie_tests = have_min_apache_version("2.4.47") ? 6 : 0; + +plan tests => @map * @num + 16 + $vary_header_tests + $cookie_tests, todo => \@todo, need_module 'rewrite'; + +foreach (@map) { + foreach my $n (@num) { + ## throw $_ into upper case just so we can test out internal + ## 'tolower' map in mod_rewrite + $_=uc($_); + + $r = GET_BODY("/modules/rewrite/$n", 'Accept' => $_); + chomp $r; + $r =~ s/\r//g; + + if ($_ eq 'RND') { + ## check that $r is just a single digit. + unless ($r =~ /^[\d]$/) { + ok 0; + next; + } + + ok ($r =~ /^[$r-6]$/); + } else { + ok ($r eq $n); + } + } +} + +$r = GET_BODY("/modules/rewrite/", 'Accept' => 7); +chomp $r; +$r =~ s/\r//g; +ok ($r eq "BIG"); +$r = GET_BODY("/modules/rewrite/", 'Accept' => 0); +chomp $r; +$r =~ s/\r//g; +ok ($r eq "ZERO"); +$r = GET_BODY("/modules/rewrite/", 'Accept' => 'lucky13'); +chomp $r; +$r =~ s/\r//g; +ok ($r eq "JACKPOT"); + +$r = GET_BODY("/modules/rewrite/qsa.html?baz=bee"); +chomp $r; +ok t_cmp($r, qr/\nQUERY_STRING = foo=bar\&baz=bee\n/s, "query-string append test"); + +# PR 50447 (double URL-escaping of the query string) +my $hostport = Apache::TestRequest::hostport(); + +$r = GET("/modules/rewrite/redirect-dir.html?q=%25", redirect_ok => 0); +ok t_cmp($r->code, 301, "per-dir redirect response code is OK"); +ok t_cmp($r->header("Location"), "http://$hostport/foobar.html?q=%25", + "per-dir query-string escaping is OK"); + +$r = GET("/modules/rewrite/redirect.html?q=%25", redirect_ok => 0); +ok t_cmp($r->code, 301, "redirect response code is OK"); +ok t_cmp($r->header("Location"), "http://$hostport/foobar.html?q=%25", + "query-string escaping is OK"); + +if (have_module('mod_proxy')) { + $r = GET_BODY("/modules/rewrite/proxy.html"); + chomp $r; + ok t_cmp($r, "JACKPOT", "request was proxied"); + + # PR 46428 + $r = GET_BODY("/modules/proxy/rewrite/foo bar.html"); + chomp $r; + ok t_cmp($r, "foo bar", "per-dir proxied rewrite escaping worked"); +} else { + skip "Skipping rewrite to proxy; no proxy module." foreach (1..2); +} + +if (have_module('mod_proxy') && have_cgi) { + # regression in 1.3.32, see PR 14518 + $r = GET_BODY("/modules/rewrite/proxy2/env.pl?fish=fowl"); + chomp $r; + ok t_cmp($r, qr/QUERY_STRING = fish=fowl\n/s, "QUERY_STRING passed OK"); + + ok t_cmp(GET_RC("/modules/rewrite/proxy3/env.pl?horse=norman"), 404, + "RewriteCond QUERY_STRING test"); + + $r = GET_BODY("/modules/rewrite/proxy3/env.pl?horse=trigger"); + chomp $r; + ok t_cmp($r, qr/QUERY_STRING = horse=trigger\n/s, "QUERY_STRING passed OK"); + + $r = GET("/modules/rewrite/proxy-qsa.html?bloo=blar"); + ok t_cmp($r->code, 200, "proxy/QSA test success"); + + ok t_cmp($r->as_string, qr/QUERY_STRING = foo=bar\&bloo=blar\n/s, + "proxy/QSA test appended args correctly"); +} else { + skip "Skipping rewrite QUERY_STRING test; missing proxy or CGI module" foreach (1..5); +} + +if (have_min_apache_version('2.4')) { + # See PR 60478 and the corresponding config in extra.conf + $r = GET("/modules/rewrite/pr60478-rewrite-loop/a/X/b/c"); + ok t_cmp($r->code, 500, "PR 60478 rewrite loop is halted"); +} else { + skip "Skipping PR 60478 test; requires ap_expr in version 2.4" +} + +if (have_min_apache_version("2.4.29")) { + # PR 58231: Vary:Host header (was) mistakenly added to the response + $r = GET("/modules/rewrite/vary1.html", "Host" => "test1"); + ok t_cmp($r->content, qr/VARY2/, "Correct internal redirect happened, OK"); + ok t_cmp($r->header("Vary"), qr/(?!.*Host.*)/, "Vary:Host header not added, OK"); + + $r = GET("/modules/rewrite/vary1.html", "Host" => "test2"); + ok t_cmp($r->content, qr/VARY2/, "Correct internal redirect happened, OK"); + ok t_cmp($r->header("Vary"), qr/(?!.*Host.*)/, "Vary:Host header not added, OK"); +} + +if (have_min_apache_version("2.4.30")) { + # PR 58231: Vary header added when a condition evaluates to true and + # the RewriteRule happens in a directory context. + $r = GET("/modules/rewrite/vary3.html", "User-Agent" => "directory-agent"); + ok t_cmp($r->content, qr/VARY4/, "Correct internal redirect happened, OK"); + ok t_cmp($r->header("Vary"), qr/User-Agent/, "Vary:User-Agent header added, OK"); + + # Corner cases in which two RewriteConds are joined using the [OR] + # operator (or similar). + # 1) First RewriteCond condition evaluates to true, so only the related + # header value is added to the Vary list even though the second condition + # evaluates to true as well. + $r = GET("/modules/rewrite/vary3.html", + "Referer" => "directory-referer", + "Accept" => "directory-accept"); + ok t_cmp($r->content, qr/VARY4/, "Correct internal redirect happened, OK"); + ok t_cmp($r->header("Vary"), qr/Accept/, "Vary:Accept header added, OK"); + # 2) First RewriteCond condition evaluates to false and the second to true, + # so only the second condition's header value is added to the Vary list. + $r = GET("/modules/rewrite/vary3.html", + "Referer" => "directory-referer", + "Accept" => "this-is-not-the-value-in-the-rewritecond"); + ok t_cmp($r->content, qr/VARY4/, "Correct internal redirect happened, OK"); + ok t_cmp($r->header("Vary"), qr/Referer/, "Vary:Referer header added, OK"); + ok t_cmp($r->header("Vary"), qr/(?!.*Accept.*)/, "Vary:Accept header not added, OK"); + + # Vary:Host header (was) mistakenly added to the response + $r = GET("/modules/rewrite/vary3.html", "Host" => "directory-domain"); + ok t_cmp($r->content, qr/VARY4/, "Correct internal redirect happened, OK"); + ok t_cmp($r->header("Vary"), qr/(?!.*Host.*)/, "Vary:Host header not added, OK"); +} + +if (have_min_apache_version("2.4.47")) { + $r = GET("/modules/rewrite/cookie/"); + ok t_cmp($r->header("Set-Cookie"), qr/(?!.*SameSite=.*)/, "samesite not present with no arg"); + $r = GET("/modules/rewrite/cookie/0"); + ok t_cmp($r->header("Set-Cookie"), qr/(?!.*SameSite=.*)/, "samesite not present with 0"); + $r = GET("/modules/rewrite/cookie/false"); + ok t_cmp($r->header("Set-Cookie"), qr/(?!.*SameSite=.*)/, "samesite not present with false"); + $r = GET("/modules/rewrite/cookie/none"); + ok t_cmp($r->header("Set-Cookie"), qr/SameSite=none/, "samesite=none"); + $r = GET("/modules/rewrite/cookie/lax"); + ok t_cmp($r->header("Set-Cookie"), qr/SameSite=lax/, "samesite=lax"); + $r = GET("/modules/rewrite/cookie/foo"); + ok t_cmp($r->header("Set-Cookie"), qr/SameSite=foo/, "samesite=foo"); +} diff --git a/debian/perl-framework/t/modules/sed.t b/debian/perl-framework/t/modules/sed.t new file mode 100644 index 0000000..10edcd7 --- /dev/null +++ b/debian/perl-framework/t/modules/sed.t @@ -0,0 +1,26 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +my @ts = ( + # see t/conf/extra.conf.in + { url => "/apache/sed/out-foo/foobar.html", content => 'barbar', msg => "sed output filter", code => 200 } +); + +my $tests = 2*scalar @ts; + +plan tests => $tests, need_module('sed'); + + +for my $t (@ts) { + my $req = GET $t->{'url'}; + ok t_cmp($req->code, $t->{'code'}, "status code for " . $t->{'url'}); + my $content = $req->content; + chomp($content); + ok t_cmp($content, $t->{content}, $t->{msg}); +} + + diff --git a/debian/perl-framework/t/modules/session.t b/debian/perl-framework/t/modules/session.t new file mode 100644 index 0000000..617239c --- /dev/null +++ b/debian/perl-framework/t/modules/session.t @@ -0,0 +1,208 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## +## mod_session tests +## + +# Code, session data, dirty, expiry, content. +my $checks_per_test = 5; + +# Session, API, Encoding, SessionEnv, SessionHeader, SessionMaxAge, +# SessionExpiryUpdateInterval, SessionInclude/Exclude. +my $num_tests = 2 + 4 + 5 + 2 + 1 + 4 + 7 + 3; + +my @todo = ( + # Session writable after decode failure - PR 58171 + 53, 54, + # Session writable after expired - PR 56052 + 88, 89 +); + +# Until the fix for PR 57300 is backported, sessions are always saved. +if (!have_min_apache_version('2.4.41')) { + my @todo_backport = ( 8, 18, 38, 43, 48, 58, 63, 133 ); + push(@todo, @todo_backport); +} + +plan tests => $num_tests * $checks_per_test, + todo => \@todo, + need need_module('session'), + need_min_apache_version('2.3.0'); + +# APR time is in microseconds. +use constant APR_TIME_PER_SEC => 1000000; + +# Don't use math ops, the result is too big for 32 Bit Perl +# Use adding of trailing "0"s instead +sub expiry_from_seconds +{ + my $seconds = shift; + return $seconds . "0" x (length(APR_TIME_PER_SEC) - 1); +} + +# check_result(name, res, session, dirty, expiry, response) +sub check_result +{ + my $name = shift; + my $res = shift; + my $session = shift // '(none)'; + my $dirty = shift // 0; + my $expiry = shift // 0; + my $response = shift // ''; + + ok t_cmp($res->code, 200, "response code ($name)"); + my $gotSession = $res->header('X-Test-Session') // '(none)'; + my $sessionData = $gotSession; + + if ($gotSession =~ /^(?:(.+)&)?expiry=([0-9]+)(?:&(.*))?$/i) { + # Don't use math ops, $2 is too big for 32 Bit Perl + # Use stripping of trailing "0"s instead + my $gotExpiry = substr($2, 0, -1 * (length(APR_TIME_PER_SEC) - 1)); + t_debug "expiry of $gotExpiry ($name)"; + ok $expiry && time() < $gotExpiry; + + # Combine the remaining data (if there is any) without the expiry. + $sessionData = join('&', grep(defined, ($1, $3))); + } + else { + t_debug "no expiry ($name)"; + ok !$expiry; + } + + ok t_cmp($sessionData, $session, "session header ($name)"); + my $got = $res->header('X-Test-Session-Dirty') // 0; + ok t_cmp($got, $dirty, "session dirty ($name)"); + $got = $res->content; + chomp($got); + ok t_cmp($got, $response, "body ($name)"); + return $gotSession; +} + +# check_get(name, path, session, dirty, expiry, response) +sub check_get +{ + my $name = shift; + my $path = shift; + + t_debug "$name: GET $path"; + my $res = GET "/sessiontest$path"; + return check_result $name, $res, @_; +} + +# check_post(name, path, data, session, dirty, expiry, response) +sub check_post +{ + my $name = shift; + my $path = shift; + my $data = shift; + + t_debug "$name: POST $path"; + my $res = POST "/sessiontest$path", content => $data; + return check_result $name, $res, @_; +} + +# check_custom(name, result, session, dirty, expiry, response) +sub check_custom +{ + my $name = shift; + my $res = shift; + + t_debug "$name"; + return check_result $name, $res, @_; +} + +my $session = 'test=value'; +my $encoded_prefix = 'TestEncoded:'; +my $encoded_session = $encoded_prefix . $session; +my $create_session = 'action=set&name=test&value=value'; +my $read_session = 'action=get&name=test'; + +# Session directive +check_post 'Cannot write session when off', '/', $create_session; +check_get 'New empty session is not saved', '/on'; + +# API optional functions +check_post 'Set session', '/on', $create_session, $session, 1; +check_post 'Get session', "/on?$session", $read_session, + undef, 0, 0, 'value'; +check_post 'Delete session', "/on?$session", 'action=set&name=test', '', 1; +check_post 'Edit session', "/on?$session", 'action=set&name=test&value=', + 'test=', 1; + +# Encoding hooks +check_post 'Encode session', '/on/encode', $create_session, + $encoded_session, 1; +check_post 'Decode session', "/on/encode?$encoded_session", $read_session, + undef, 0, 0, 'value'; +check_get 'Custom decoder failure', "/on/encode?$session"; +check_get 'Identity decoder failure', "/on?&=test"; +check_post 'Session writable after decode failure', "/on/encode?$session", + $create_session, $encoded_session, 1; + +# SessionEnv directive - requires mod_include +if (have_module('include')) { + check_custom 'SessionEnv Off', GET("/modules/session/env.shtml?$session"), + undef, 0, 0, '(none)'; + check_get 'SessionEnv On', "/on/env/on/env.shtml?$session", + undef, 0, 0, $session; +} +else { + for (1 .. 2 * $checks_per_test) { + skip "SessionEnv tests require mod_include", 1; + } +} + +# SessionHeader directive +check_custom 'SessionHeader', GET("/sessiontest/on?$session&another=1", + 'X-Test-Session-Override' => 'another=5&last=7'), + "$session&another=5&last=7", 1; + +# SessionMaxAge directive +my $future_expiry = expiry_from_seconds(time() + 200); + +check_get 'SessionMaxAge adds expiry', "/on/expire?$session", $session, 0, 1; +check_get 'Discard expired session', "/on/expire?$session&expiry=1", '', 0, 1; +check_get 'Keep non-expired session', + "/on/expire?$session&expiry=$future_expiry", $session, 0, 1; +check_post 'Session writable after expired', '/on/expire?expiry=1', + $create_session, $session, 1, 1; + +# SessionExpiryUpdateInterval directive - new in 2.4.41 +if (have_module('version') && have_min_apache_version('2.4.41')) { + my $max_expiry = expiry_from_seconds(time() + 100); + my $threshold_expiry = expiry_from_seconds(time() + 40); + + check_get 'SessionExpiryUpdateInterval off by default', + "/on/expire?$session&expiry=$max_expiry", $session, 0, 1; + check_get 'SessionExpiryUpdateInterval skips save', + "/on/expire/cache?$session&expiry=$max_expiry"; + check_post 'Session readable when save skipped', + "/on/expire/cache?$session&expiry=$max_expiry", $read_session, + undef, 0, 0, 'value'; + check_post 'Dirty overrides SessionExpiryUpdateInterval', + "/on/expire/cache?$session&expiry=$max_expiry", $create_session, + $session, 1, 1; + check_get 'Old session always updates expiry', + "/on/expire/cache?$session&expiry=$threshold_expiry", $session, 0, 1; + check_get 'New empty session with expiry not saved', "/on/expire/cache"; + check_post 'Can create session with SessionExpiryUpdateInterval', + "/on/expire/cache", $create_session, $session, 1, 1; +} +else { + for (1 .. 7 * $checks_per_test) { + skip "SessionExpiryUpdateInterval tests require backporting"; + } +} + +# SessionInclude/Exclude directives +check_post 'Cannot write session when not included', + "/on/include?$session", $create_session; +check_post 'Can read session when included', + "/on/include/yes?$session", $read_session, undef, 0, 0, 'value'; +check_post 'SessionExclude overrides SessionInclude', + "/on/include/yes/no?$session", $create_session; diff --git a/debian/perl-framework/t/modules/session_cookie.t b/debian/perl-framework/t/modules/session_cookie.t new file mode 100644 index 0000000..46f7bf2 --- /dev/null +++ b/debian/perl-framework/t/modules/session_cookie.t @@ -0,0 +1,29 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +plan tests => have_min_apache_version('2.5.0') ? 4 : 2, + need_module 'session_cookie'; + +my $uri = '/modules/session_cookie/test404'; +my $r = GET($uri); +my @set_cookie_headers = $r->header("Set-Cookie"); +ok t_cmp($r->code, 404); + +# See PR: 60910 +if (have_min_apache_version('2.5.0')) { + ok t_cmp(scalar(@set_cookie_headers), 1, "Set-Cookie header not duplicated in error response (404)."); +} + +$uri = '/modules/session_cookie/test'; +$r = GET($uri); +@set_cookie_headers = $r->header("Set-Cookie"); +ok t_cmp($r->code, 200); + +# See PR: 60910 +if (have_min_apache_version('2.5.0')) { + ok t_cmp(scalar(@set_cookie_headers), 1, "Set-Cookie header not duplicated in successful response (200)."); +}
\ No newline at end of file diff --git a/debian/perl-framework/t/modules/setenvif.t b/debian/perl-framework/t/modules/setenvif.t new file mode 100644 index 0000000..cb561c2 --- /dev/null +++ b/debian/perl-framework/t/modules/setenvif.t @@ -0,0 +1,193 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +my $vars = Apache::Test::vars(); +my $htdocs = Apache::Test::vars('documentroot'); +my $body; + +## +## mod_setenvif tests +## + +my $good_ua = '^libwww-perl/.*'; +my $bad_ua = 'foo-browser/0.1'; + +my $page = "/modules/setenvif/htaccess/setenvif.shtml"; +my %var_att = + ( + 'Remote_Host' => + { + 'pass' => $vars->{remote_addr}, + 'fail' => 'some.where.else.com' + }, + 'Remote_Addr' => + { + 'pass' => $vars->{remote_addr}, + 'fail' => '63.125.18.195' + }, + 'Request_Method' => + { + 'pass' => 'GET', + 'fail' => 'POST' + }, + 'Request_Protocol' => + { + 'pass' => 'HTTP', + 'fail' => 'FTP' + }, + 'Request_URI' => + { + 'pass' => $page, + 'fail' => 'foo.html' + }, + # Test with a regex. Looking for 'User-Agent' + '^User-Ag' => + { + 'pass' => $good_ua, + 'fail' => $bad_ua + } + ); + +my @var = qw(VAR_ONE VAR_TWO VAR_THREE); + +my $htaccess = "$htdocs/modules/setenvif/htaccess/.htaccess"; + +plan tests => @var * 10 + (keys %var_att) * 6 * @var + 4, + have_module qw(setenvif include); + +sub write_htaccess { + my $string = shift; + open (HT, ">$htaccess") or die "can't open $htaccess: $!"; + print HT $string; + close(HT); +} + +sub test_all_vars { + my $exp_modifier = shift; + my $conf_str = shift; + my $set = 'set'; + + my ($actual, $expected); + foreach my $var (@var) { + $conf_str .= " $var=$set"; + write_htaccess($conf_str); + $expected = set_expect($exp_modifier, $conf_str); + $actual = GET_BODY $page; + $actual =~ s/\r//sg; #win32 + + print "---\n"; + print "conf:\n$conf_str\n"; + print "expecting:\n->$expected<-\n"; + print "got:\n->$actual<-\n"; + + ok ($actual eq $expected); + } +} + +sub set_expect { + my $not = shift; + my $conf_str = shift; + my ($v, $exp_str) = ('',''); + + my %exp = + ( + 1 => 'VAR_ONE', + 2 => 'VAR_TWO', + 3 => 'VAR_THREE' + ); + + foreach (sort keys %exp) { + my $foo = $exp{$_}; + $v = '(none)'; + if ($conf_str =~ /$foo=(\S+)/) { + $v = $1 unless $not; + } + + $exp_str .= "$_:$v\n"; + } + + return $exp_str; +} + +## test simple browser match ## +test_all_vars(0,"BrowserMatch $good_ua"); +test_all_vars(1,"BrowserMatch $bad_ua"); + +## test SetEnvIf with variable attributes ## +foreach my $attribute (sort keys %var_att) { + test_all_vars(0,"SetEnvIf $attribute $var_att{$attribute}{pass}"); + test_all_vars(1,"SetEnvIf $attribute $var_att{$attribute}{fail}"); + + ## some 'relaying' variables ## + test_all_vars(0, + "SetEnvIf $attribute $var_att{$attribute}{pass} RELAY=1\nSetEnvIf RELAY 1"); + test_all_vars(1, + "SetEnvIf $attribute $var_att{$attribute}{pass} RELAY=1\nSetEnvIf RELAY 0"); + + ## SetEnvIfNoCase tests ## + my $uc = uc $var_att{$attribute}{pass}; + test_all_vars(0,"SetEnvIfNoCase $attribute $uc"); + $uc = uc $var_att{$attribute}{fail}; + test_all_vars(1,"SetEnvIfNoCase $attribute $uc"); +} + +## test 'relaying' variables ## +test_all_vars(0,"BrowserMatch $good_ua RELAY=1\nSetEnvIf RELAY 1"); +test_all_vars(0, + "BrowserMatch $good_ua RELAY=1\nSetEnvIf RELAY 1 R2=1\nSetEnvIf R2 1"); +test_all_vars(1, + "BrowserMatch $good_ua RELAY=1\nSetEnvIf RELAY 1 R2=1\nSetEnvIf R2 0"); +test_all_vars(1,"BrowserMatch $good_ua RELAY=0\nSetEnvIf RELAY 1"); +test_all_vars(1,"BrowserMatch $good_ua RELAY=1\nSetEnvIf RELAY 0"); + +## test '!' ## +# We set then unset 'R2' (see a few lines above for the corresponding test, without the 'unset' +test_all_vars(1, + "BrowserMatch $good_ua RELAY=1\nSetEnvIf RELAY 1 R2=1\nSetEnvIf RELAY 1 !R2\nSetEnvIf R2 1"); + +## test SetEnvIfExpr ## +test_all_vars(0, "SetEnvIfExpr \"%{REQUEST_URI} =~ /\.shtml\$/\""); +test_all_vars(1, "SetEnvIfExpr \"%{REQUEST_URI} =~ /\.foo\$/\""); + +## test SetEnvIfExpr with replacement ## +write_htaccess("SetEnvIfExpr \"%{REQUEST_URI} =~ /\.\(sh\)tml\$/\" VAR_ONE=\$0 VAR_TWO=\$1"); +$body = GET_BODY $page; +ok t_cmp($body, "1:.shtml\n2:sh\n3:(none)\n"); + +write_htaccess("SetEnvIfExpr \"%{REQUEST_URI} !~ /\.\(sh\)tml\$/\" VAR_ONE=\$0 VAR_TWO=\$1"); +$body = GET_BODY $page; +ok t_cmp($body, "1:(none)\n2:(none)\n3:(none)\n"); + +## test SetEnvIfExpr with replacement when regex does NOT match ## +write_htaccess("SetEnvIfExpr \"%{REQUEST_URI} =~ /\.\(sh\)tmlXXX\$/\" VAR_ONE=\$0 VAR_TWO=\$1"); +$body = GET_BODY $page; +ok t_cmp($body, "1:(none)\n2:(none)\n3:(none)\n"); + +if (need_min_apache_version("2.4.38")) { + ## test SetEnvIfExpr with replacement when regex is REQUIRED to NOT match ## + write_htaccess("SetEnvIfExpr \"%{REQUEST_URI} !~ /\.\(sh\)tmlXXX\$/\" VAR_ONE=\$0 VAR_TWO=\$1"); + $body = GET_BODY $page; + ok t_cmp($body, "1:\$0\n2:\$1\n3:(none)\n"); +} +else { + # Skip for versions without r1786235 backported + skip "skipping inverted match test with version <2.4.38" +} + +## i think this should work, but it doesnt. +## leaving it commented now pending investigation. +## seems you cant override variables that have been previously set. +## +## test_all_vars(0, +## "SetEnv RELAY 1\nSetEnvIf RELAY 1 RELAY=2\nSetEnvIf RELAY 2"); +## test_all_vars(0, +## "BrowserMatch $good_ua RELAY=1\nSetEnvIf RELAY 1 RELAY=2\nSetEnvIf RELAY 2"); +## +## + +## clean up ## +unlink $htaccess if -e $htaccess; diff --git a/debian/perl-framework/t/modules/speling.t b/debian/perl-framework/t/modules/speling.t new file mode 100644 index 0000000..85af159 --- /dev/null +++ b/debian/perl-framework/t/modules/speling.t @@ -0,0 +1,64 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +my @testcasespaths = ( + ['/modules/speling/nocase/'], + ['/modules/speling/caseonly/'], +); + +my @testcases = ( + ## File Test CheckCaseOnly Off On + ['good.html', "normal", 200, 200], + ['god.html', "omission", 301, 404], + ['goood.html', "insertion", 301, 404], + ['godo.html', "transposition", 301, 404], + ['go_d.html', "wrong character", 301, 404], + + ['good.wrong_ext', "wrong extension", 300, 300], + ['GOOD.wrong_ext', "NC wrong extension", 300, 300], + + ['Bad.html', "wrong filename", 404, 404], + ['dogo.html', "double transposition", 404, 404], + ['XooX.html', "double wrong character", 404, 404], + + ['several0.html', "multiple choice", 300, 404], +); + +# macOS HFS is case-insensitive but case-preserving so the below tests +# would cause misleading failures +if ($^O ne "darwin") { + push (@testcases, ['GOOD.html', "case", 301, 301]); +} + +plan tests => scalar @testcasespaths * scalar @testcases * 2, need 'mod_speling'; + +my $r; +my $code = 2; + +# Disable redirect +local $Apache::TestRequest::RedirectOK = 0; + +foreach my $p (@testcasespaths) { + foreach my $t (@testcases) { + ## + #local $Apache::TestRequest::RedirectOK = 0; + $r = GET($p->[0] . $t->[0]); + + # Checking for return code + ok t_cmp($r->code, $t->[$code], "Checking " . $t->[1] . ". Expecting: ". $t->[$code]); + + # Checking that the expected filename is in the answer + if ($t->[$code] != 200 && $t->[$code] != 404) { + ok t_cmp($r->content, qr/good\.html|several1\.html/, "Redirect ok"); + } + else { + skip "Skipping. No redirect with status " . $t->[$code]; + } + } + + $code = $code+1; +} diff --git a/debian/perl-framework/t/modules/status.t b/debian/perl-framework/t/modules/status.t new file mode 100644 index 0000000..6a3dab1 --- /dev/null +++ b/debian/perl-framework/t/modules/status.t @@ -0,0 +1,20 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; + +## +## mod_status quick test +## + +plan tests => 1, need_module 'status'; + +my $uri = '/server-status'; +my $servername = Apache::Test::vars()->{servername}; + +my $title = "Apache Server Status for $servername"; + +my $status = GET_BODY $uri; +print "$status\n"; +ok ($status =~ /$title/i); diff --git a/debian/perl-framework/t/modules/substitute.t b/debian/perl-framework/t/modules/substitute.t new file mode 100644 index 0000000..0f111c0 --- /dev/null +++ b/debian/perl-framework/t/modules/substitute.t @@ -0,0 +1,125 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil qw(t_write_file); + +Apache::TestRequest::user_agent(keep_alive => 1); + +my $debug = 0; +my $url = '/modules/substitue/test.txt'; + +# mod_bucketeer control chars +my $B = chr(0x02); +my $F = chr(0x06); +my $P = chr(0x10); + +my @simple_cases = (); + +my @test_cases = ( + [ "f${B}o${P}ofoo" => 's/foo/bar/' ], + [ "f${B}o${P}ofoo" => 's/fo/fa/', 's/fao/bar/' ], + [ "foofoo" => 's/Foo/bar/' ], + [ "fo${F}ofoo" => 's/Foo/bar/i' ], + [ "foOFoo" => 's/OF/of/', 's/foo/bar/' ], + [ "fofooo" => 's/(.)fo/$1of/', 's/foo/bar/' ], + [ "foof\noo" => 's/f.oo/bar/' ], + [ "xfooo" => 's/foo/fo/' ], + [ "xfoo" x 4000 => 's/foo/bar/', 's/FOO/BAR/' ], + [ "foox\n" x 4000 => 's/foo/bar/', 's/FOO/BAR/' ], + [ "a.baxb(" => 's/a.b/a$1/n' ], + [ "a.baxb(" => 's/a.b/a$1/n', 's/1axb(/XX/n' ], + [ "xfoo" x 4000 => 's/foo/bar/n', 's/FOO/BAR/n' ], +); + +if (have_min_apache_version("2.3.5")) { + # tests for r1307067 + push @test_cases, [ "x<body>x" => 's/<body>/&/' ], + [ "x<body>x" => 's/<body>/$0/' ], + [ "foobar" => 's/(oo)b/c$1/' ], + [ "foobar" => 's/(oo)b/c\$1/' ], + [ "foobar" => 's/(oo)b/\d$1/' ]; +} + +if (have_min_apache_version("2.4.42")) { + push @simple_cases, [ "foo\nbar" => 's/foo.*/XXX$0XXX', "XXXfooXXX\nbar" ], +} +plan tests => scalar @test_cases + scalar @simple_cases, + need need_lwp, + need_module('mod_substitute'), + need_module('mod_bucketeer'); + +foreach my $t (@test_cases) { + my ($content, @rules) = @{$t}; + + write_testfile($content); + write_htaccess(@rules); + + # We assume that perl does the right thing (TM) and compare that with + # mod_substitute's result. + my $expect = $content; + $expect =~ s/[$B$F$P]+//g; + foreach my $rule (@rules) { + if ($rule =~ s/n$//) { + # non-regex match, escape specials for perl + my @parts = split('/', $rule); + $parts[1] = quotemeta($parts[1]); + $parts[2] = quotemeta($parts[2]); + $rule = join('/', @parts); + $rule .= '/' if (scalar @parts == 3); + } + else { + # special case: HTTPD uses $0 for the whole match, perl uses $& + $rule =~ s/\$0/\$&/g; + } + $rule .= "g"; # mod_substitute always does global search & replace + + # "no warnings" because the '\d' in one of the rules causes a warning, + # which we have set to be fatal. + eval "{\n no warnings ; \$expect =~ $rule\n}"; + } + + my $response = GET('/modules/substitute/test.txt'); + my $rc = $response->code; + my $got = $response->content; + my $ok = ($rc == 200) && ($got eq $expect); + print "got $rc '$got'", ($ok ? ": OK\n" : ", expected '$expect'\n"); + + ok($ok); +} + +foreach my $t (@simple_cases) { + my ($content, $rule, $expect) = @{$t}; + write_testfile($content); + write_htaccess($rule); + my $response = GET('/modules/substitute/test.txt'); + my $rc = $response->code; + my $got = $response->content; + my $ok = ($rc == 200) && ($got eq $expect); + print "got $rc '$got'", ($ok ? ": OK\n" : ", expected '$expect'\n"); + + ok($ok); +} +exit 0; + +### sub routines +sub write_htaccess +{ + my @rules = @_; + my $file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'htdocs', + 'modules', 'substitute', '.htaccess'); + my $content = "SetOutputFilter BUCKETEER;SUBSTITUTE\n"; + $content .= "Substitute $_\n" for @rules; + t_write_file($file, $content); + print "$content<===\n" if $debug; +} + +sub write_testfile +{ + my $content = shift; + my $file = File::Spec->catfile(Apache::Test::vars('serverroot'), 'htdocs', + 'modules', 'substitute', 'test.txt'); + t_write_file($file, $content); + print "$content<===\n" if $debug; +} diff --git a/debian/perl-framework/t/modules/unique_id.t b/debian/perl-framework/t/modules/unique_id.t new file mode 100644 index 0000000..a3f206b --- /dev/null +++ b/debian/perl-framework/t/modules/unique_id.t @@ -0,0 +1,27 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestRequest; +use Apache::TestUtil; + +## +## mod_unique_id tests +## + +my $iters = 100; +my $url = "/modules/cgi/unique-id.pl"; +my %idx = (); + +plan tests => 3 * $iters, need need_cgi, need_module('unique_id'); + +foreach (1..$iters) { + my $r = GET $url; + ok t_cmp($r->code, 200, "fetch unique ID"); + my $v = $r->content; + print "# unique id: $v\n"; + chomp $v; + ok length($v) >= 20; + ok !exists($idx{$v}); + $idx{$v} = 1; +} diff --git a/debian/perl-framework/t/modules/usertrack.t b/debian/perl-framework/t/modules/usertrack.t new file mode 100644 index 0000000..d9f62da --- /dev/null +++ b/debian/perl-framework/t/modules/usertrack.t @@ -0,0 +1,74 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my @testcases = ( + ['/modules/usertrack/foo.html'], + ['/modules/usertrack/bar.html'], + ['/modules/usertrack/foo.html'], + ['/modules/usertrack/bar.html'], +); + +my $iters = 100; +my %cookiex = (); + +plan tests => (scalar (@testcases) * 2 + 2) * $iters + 1 + 3, need 'mod_usertrack'; + +foreach (1..$iters) { + my $nb_req = 1; + my $cookie = ""; + + foreach my $t (@testcases) { + ## + my $r = GET($t->[0], "Cookie" => $cookie); + + # Checking for return code + ok t_cmp($r->code, 200, "Checking return code is '200'"); + + # Checking for content + my $setcookie = $r->header('Set-Cookie'); + + # Only the first and third requests of an iteration must have a Set-Cookie + if ((($nb_req == 1) || ($nb_req == 3)) && (defined $setcookie)) { + ok defined $setcookie; + + print "Set-Cookie: " . $setcookie . "\n"; + # Copy the cookie in order to send it back in the next requests + $cookie = substr($setcookie, 0, index($setcookie, ";") ); + print "Cookie: " . $cookie . "\n"; + + # This cookie must not have been already seen + ok !exists($cookiex{$cookie}); + $cookiex{$cookie} = 1; + } + else { + ok !(defined $setcookie); + } + + # After the 2nd request, we lie and send a modified cookie. + # So the 3rd request whould receive a new cookie + if ($nb_req == 2) { + $cookie = "X" . $cookie; + } + + $nb_req++; + } +} + +# Check the overall number of cookies generated +ok ((scalar (keys %cookiex)) == ($iters * 2)); + +# Check that opt-in flags aren't set +my $r = GET("/modules/usertrack/foo.html"); +ok t_cmp($r->code, 200, "Checking return code is '200'"); +# Checking for content +my $setcookie = $r->header('Set-Cookie'); +t_debug("$setcookie"); +ok defined $setcookie; +$setcookie =~ m/(Secure|HTTPonly|SameSite)/i; +ok t_cmp($1, undef); + + diff --git a/debian/perl-framework/t/modules/vhost_alias.t b/debian/perl-framework/t/modules/vhost_alias.t new file mode 100644 index 0000000..a89a97b --- /dev/null +++ b/debian/perl-framework/t/modules/vhost_alias.t @@ -0,0 +1,101 @@ +use strict; +use warnings FATAL => 'all'; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestRequest; + +my $htdocs = Apache::Test::vars('documentroot'); +my $url = '/index.html'; +my $cgi_name = "test-cgi"; +my $cgi_string = "test cgi for"; +my $root = "$htdocs/modules/vhost_alias"; +my $ext; + +my @vh = qw(www.vha-test.com big.server.name.from.heck.org ab.com w-t-f.net); + +plan tests => @vh * 2, need need_module('vhost_alias'), need_cgi, need_lwp; + +Apache::TestRequest::scheme('http'); #ssl not listening on this vhost +Apache::TestRequest::module('mod_vhost_alias'); #use this module's port + +## test environment setup ## +t_mkdir($root); + +foreach (@vh) { + my @part = split /\./, $_; + my $d = "$root/"; + + ## create VirtualDocumentRoot htdocs/modules/vhost_alias/%2/%1.4/%-2/%2+ + ## %2 ## + if ($part[1]) { + $d .= $part[1]; + } else { + $d .= "_"; + } + t_mkdir($d); + + $d .= "/"; + ## %1.4 ## + if (length($part[0]) < 4) { + $d .= "_"; + } else { + $d .= substr($part[0], 3, 1); + } + t_mkdir($d); + + $d .= "/"; + ## %-2 ## + if ($part[@part-2]) { + $d .= $part[@part-2]; + } else { + $d .= "_"; + } + t_mkdir($d); + + $d .= "/"; + ## %2+ ## + for (my $i = 1;$i < @part;$i++) { + $d .= $part[$i]; + $d .= "." if $part[$i+1]; + } + t_mkdir($d); + + ## write index.html for the VirtualDocumentRoot ## + t_write_file("$d$url",$_); + + ## create directories for VirtualScriptAlias tests ## + $d = "$root/$_"; + t_mkdir($d); + $d .= "/"; + + ## write cgi ## + my $cgi_content = <<SCRIPT; +echo Content-type: text/html +echo +echo $cgi_string $_ +SCRIPT + + $ext = Apache::TestUtil::t_write_shell_script("$d$cgi_name", $cgi_content); + chmod 0755, "$d$cgi_name.$ext"; +} + +## run tests ## +foreach (@vh) { + ## test VirtalDocumentRoot ## + ok t_cmp(GET_BODY($url, Host => $_), + $_, + "VirtalDocumentRoot test" + ); + + ## test VirtualScriptAlias ## + my $cgi_uri = "/cgi-bin/$cgi_name.$ext"; + my $actual = GET_BODY $cgi_uri, Host => $_; + $actual =~ s/[\r\n]+$//; + ok t_cmp($actual, + "$cgi_string $_", + "VirtualScriptAlias test" + ); +} + + |