diff --git a/t/web-lib-funcs-acl.t b/t/web-lib-funcs-acl.t new file mode 100644 index 000000000..1a8d46124 --- /dev/null +++ b/t/web-lib-funcs-acl.t @@ -0,0 +1,49 @@ +#!/usr/bin/perl +# Unit tests for ACL preservation helpers in web-lib-funcs.pl. + +use strict; +use warnings; +use Test::More; +use File::Basename qw(dirname); +use File::Spec; + +my $script = File::Spec->rel2abs( + File::Spec->catfile(dirname(__FILE__), '..', 'web-lib-funcs.pl')); +require $script; + +subtest 'physical restore is enabled when supported' => sub { + no warnings 'redefine'; + my $calls = 0; + local *main::backquote_command = sub { + $calls++; + return "setfacl 2.4.0 -- set file access control lists\n". + "Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...\n". + " setfacl [-P] --restore=file\n". + " -P, --physical physical walk, do not follow symbolic links\n". + " --restore=file restore ACLs (inverse of `getfacl -R')\n"; + }; + + is(main::get_setfacl_restore_command('/usr/bin/setfacl'), + '/usr/bin/setfacl -P --restore=-', + 'uses physical restore with setfacl 2.4.0 and later'); + is(main::get_setfacl_restore_command('/usr/bin/setfacl'), + '/usr/bin/setfacl -P --restore=-', + 'keeps using physical restore on later calls'); + is($calls, 1, 'caches the capability check'); +}; + +subtest 'older setfacl remains supported' => sub { + no warnings 'redefine'; + local *main::backquote_command = sub { + return "setfacl 2.3.2 -- set file access control lists\n". + "Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...\n". + " -P, --physical physical walk, do not follow symbolic links\n". + " --restore=file restore ACLs (inverse of `getfacl -R')\n"; + }; + + is(main::get_setfacl_restore_command('/usr/local/bin/setfacl'), + '/usr/local/bin/setfacl --restore=-', + 'does not pass incompatible -P option to older versions'); +}; + +done_testing(); diff --git a/t/xmlrpc.t b/t/xmlrpc.t index 8ec2e3d55..c01d04b9e 100644 --- a/t/xmlrpc.t +++ b/t/xmlrpc.t @@ -110,6 +110,14 @@ subtest 'encode_xml_value type selection' => sub { # Plain strings. like(encode_xml_value('hello'), qr{^hello\s*$}, 'word -> string'); like(encode_xml_value(''), qr{^\s*$}, 'empty string -> empty '); + my @warnings; + my $undef_xml; + { + local $SIG{__WARN__} = sub { push(@warnings, @_); }; + $undef_xml = encode_xml_value(undef); + } + like($undef_xml, qr{^\s*$}, 'undef -> empty '); + is_deeply(\@warnings, [], 'undef emits no warnings'); # A value with control characters cannot live in a (the # printable-range regex fails), so it must fall through to base64. @@ -189,6 +197,19 @@ subtest 'parse_xml_value' => sub { is(parse_xml_value(value_tree('1')),1, 'boolean parsed'); is(parse_xml_value(value_tree('2.5')),'2.5', 'double parsed'); is(parse_xml_value(value_tree('hi')), 'hi', 'string parsed'); + my @warnings; + { + local $SIG{__WARN__} = sub { push(@warnings, @_); }; + is(parse_xml_value(value_tree('')), '', + 'empty string parsed'); + is(parse_xml_value(value_tree('')), '', 'implicit empty string parsed'); + is(parse_xml_value(value_tree('')), '', + 'empty base64 parsed'); + my $empty_name = parse_xml_value(value_tree( + 'x')); + is_deeply($empty_name, { '' => 'x' }, 'empty struct member name parsed'); + } + is_deeply(\@warnings, [], 'empty XML values emit no warnings'); # base64 is decoded back to its raw bytes. my $b64 = encode_base64("ab\x00cd"); diff --git a/web-lib-funcs.pl b/web-lib-funcs.pl index 04ac0cd16..b0399c104 100755 --- a/web-lib-funcs.pl +++ b/web-lib-funcs.pl @@ -10359,16 +10359,17 @@ out progress of an HTTP request. =cut sub progress_callback { +my ($mode, $arg) = @_; if (defined(&theme_progress_callback)) { # Call the theme override return &theme_progress_callback(@_); } -if ($_[0] == 2) { +if ($mode == 2) { # Got size print $progress_callback_prefix; - if ($_[1]) { - $progress_size = $_[1]; - $progress_step = int($_[1] / 10); + if ($arg) { + $progress_size = $arg; + $progress_step = int($arg / 10); print &text('progress_size2', &html_escape($progress_callback_url), &nice_size($progress_size)),"
\n"; @@ -10380,42 +10381,42 @@ if ($_[0] == 2) { } $last_progress_time = $last_progress_size = undef; } -elsif ($_[0] == 3) { +elsif ($mode == 3) { # Got data update my $sp = $progress_callback_prefix.(" " x 5); if ($progress_size) { # And we have a size to compare against - my $st = int(($_[1] * 10) / $progress_size); + my $st = int(($arg * 10) / $progress_size); my $time_now = time(); if ($st != $progress_step || $time_now - $last_progress_time > 60) { # Show progress every 10% or 60 seconds - print $sp,&text('progress_datan', &nice_size($_[1]), - int($_[1]*100/$progress_size)),"
\n"; + print $sp,&text('progress_datan', &nice_size($arg), + int($arg*100/$progress_size)),"
\n"; $last_progress_time = $time_now; } $progress_step = $st; } else { # No total size .. so only show in 1M jumps - if ($_[1] > $last_progress_size+1024*1024) { + if ($arg > $last_progress_size+1024*1024) { print $sp,&text('progress_data2n', - &nice_size($_[1])),"
\n"; - $last_progress_size = $_[1]; + &nice_size($arg)),"
\n"; + $last_progress_size = $arg; } } } -elsif ($_[0] == 4) { +elsif ($mode == 4) { # All done downloading print $progress_callback_prefix,&text('progress_done'),"
\n"; } -elsif ($_[0] == 5) { +elsif ($mode == 5) { # Got new location after redirect - $progress_callback_url = $_[1]; + $progress_callback_url = $arg; } -elsif ($_[0] == 6) { +elsif ($mode == 6) { # URL is in cache - $progress_callback_url = $_[1]; + $progress_callback_url = $arg; print &text('progress_incache', &html_escape($progress_callback_url)),"
\n"; } @@ -11629,7 +11630,8 @@ elsif (defined($main::open_tempfiles{$_[0]})) { } if ($file_acls) { # Set original ACLs - open(my $pipe, '|-', "$setfacl --restore=-"); + my $restore_command = &get_setfacl_restore_command($setfacl); + open(my $pipe, '|-', $restore_command); print($pipe $file_acls); close($pipe); } @@ -11694,6 +11696,27 @@ if (!defined($main::selinux_enabled_cache)) { return $main::selinux_enabled_cache; } +=head2 get_setfacl_restore_command(setfacl-command) + +Returns a command for restoring ACLs from standard input. Uses physical +restore when supported by setfacl, while remaining compatible with versions +older than 2.4.0 which reject -P together with --restore. + +=cut +sub get_setfacl_restore_command +{ +my ($setfacl) = @_; +state %physical_restore_cache; + +if (!exists($physical_restore_cache{$setfacl})) { + my $help = &backquote_command("$setfacl --help 2>&1 [1]->[2]; + return $scalar->[1]->[2] // ""; } elsif ($date) { # Need to decode date @@ -26,7 +26,7 @@ elsif ($date) { } elsif ($base64) { # Convert to binary - return &decode_base64($base64->[1]->[2]); + return &decode_base64($base64->[1]->[2] // ""); } elsif ($struct) { # Parse member names and values @@ -35,7 +35,7 @@ elsif ($struct) { my ($name) = &find_xmls("name", $member, 1); my ($value) = &find_xmls("value", $member, 1); my $perlv = &parse_xml_value($value); - $rv{$name->[1]->[2]} = $perlv; + $rv{$name->[1]->[2] // ""} = $perlv; } return \%rv; } @@ -51,7 +51,7 @@ elsif ($array) { } else { # Fallback - just a string directly in the value - return $value->[1]->[2]; + return $value->[1]->[2] // ""; } } @@ -60,6 +60,7 @@ else { sub encode_xml_value { my ($perlv) = @_; +$perlv = "" if (!defined($perlv)); if (ref($perlv) eq "ARRAY") { # Convert to array XML format my $xmlrv = "\n\n";