mirror of
https://github.com/webmin/webmin.git
synced 2026-10-29 22:50:58 +00:00
Merge branch 'master' of github.com:webmin/webmin
This commit is contained in:
@@ -1079,6 +1079,8 @@ if ($in{'special_def'}) {
|
||||
foreach $arr (@pers) {
|
||||
delete($job->{$arr});
|
||||
}
|
||||
# Special times are single words like daily or reboot
|
||||
$in{'special'} =~ /^[a-z]+$/i || &error($text{'save_especial'});
|
||||
$job->{'special'} = $in{'special'};
|
||||
}
|
||||
else {
|
||||
@@ -1092,6 +1094,11 @@ else {
|
||||
# Need to work out and simplify ranges selected
|
||||
local (@range, @newrange, $i);
|
||||
@range = split(/\0/, $in{$arr});
|
||||
# Selected times are plain numbers
|
||||
foreach my $v (@range) {
|
||||
$v =~ /^\d+$/ || &error(&text('save_etime',
|
||||
$text{"edit_$arr"}));
|
||||
}
|
||||
@range = sort { $a <=> $b } @range;
|
||||
local $start = -1;
|
||||
for($i=0; $i<@range; $i++) {
|
||||
@@ -1168,6 +1175,11 @@ if ($in->{'range_def'}) {
|
||||
else {
|
||||
# Validate and store range
|
||||
foreach my $r ("start", "end") {
|
||||
# Dates go into the job's command, so only allow numbers
|
||||
foreach my $f ("day", "month", "year") {
|
||||
$in->{'range_'.$r.'_'.$f} =~ /^\d+$/ ||
|
||||
&error($text{'range_e'.$r});
|
||||
}
|
||||
eval { timelocal(0, 0, 0, $in->{'range_'.$r.'_day'},
|
||||
$in->{'range_'.$r.'_month'}-1,
|
||||
$in->{'range_'.$r.'_year'}-1900) };
|
||||
|
||||
@@ -81,6 +81,9 @@ lcedit_special_reboot=when system boots
|
||||
|
||||
save_err=Failed to save cron job
|
||||
save_ecmd=You did not enter a command to execute
|
||||
save_enewline=The command and comment cannot contain line breaks
|
||||
save_especial=Invalid special time selected
|
||||
save_etime=Invalid $1 selected
|
||||
save_ename=Missing or invalid environment variable name
|
||||
save_evalue=Invalid environment variable value
|
||||
save_euser=You must select a user
|
||||
|
||||
@@ -38,6 +38,10 @@ foreach $f (@files) { &lock_file($f); }
|
||||
if ($in{"cmd"} !~ /\S/ && $access{'command'}) {
|
||||
&error($text{'save_ecmd'});
|
||||
}
|
||||
# A line break would add extra lines to the crontab
|
||||
if ($in{'cmd'} =~ /[\r\n]/ || $in{'comment'} =~ /[\r\n]/) {
|
||||
&error($text{'save_enewline'});
|
||||
}
|
||||
if (&supports_users()) {
|
||||
if (!$in{'user'}) {
|
||||
&error($text{'save_euser'});
|
||||
|
||||
@@ -81,15 +81,23 @@ ok(!cron_move_affects_env(\@jobs, $jobs[0], $jobs[6]),
|
||||
my $cronfile = "$vardir/crontab";
|
||||
my $crondir = "$vardir/crontabs";
|
||||
make_path($crondir);
|
||||
open(my $modfh, ">", "$confdir/cron/config") or die "cron config: $!";
|
||||
print $modfh "single_file=$cronfile\n";
|
||||
print $modfh "cron_dir=$crondir\n";
|
||||
print $modfh "system_crontab=$vardir/system-crontab\n";
|
||||
print $modfh "cronfiles_dir=$vardir/cron.d\n";
|
||||
print $modfh "vixie_cron=1\n";
|
||||
close($modfh);
|
||||
make_path("$vardir/cron.d");
|
||||
|
||||
# Write the Cron module config, with the given lines added.
|
||||
sub write_cron_config
|
||||
{
|
||||
my (@lines) = @_;
|
||||
open(my $fh, ">", "$confdir/cron/config") or die "cron config: $!";
|
||||
print $fh map { "$_\n" } (@lines,
|
||||
"cron_dir=$crondir",
|
||||
"system_crontab=$vardir/system-crontab",
|
||||
"cronfiles_dir=$vardir/cron.d",
|
||||
"vixie_cron=1",
|
||||
"cron_deny_all=2");
|
||||
close($fh);
|
||||
}
|
||||
write_cron_config("single_file=$cronfile");
|
||||
|
||||
# Reset the crontab fixture to its original contents.
|
||||
sub write_crontab
|
||||
{
|
||||
@@ -246,6 +254,39 @@ like($result->{'out'}, qr/^Location:/m,
|
||||
is(read_crontab_fixture(), $original,
|
||||
"re-enabling the normal job restores the fixture");
|
||||
|
||||
# Job fields must not add lines or fields to the crontab
|
||||
seed_acl(%full_acl);
|
||||
my %edit = ( 'idx' => 0, 'user' => 'root', 'active' => 1,
|
||||
'cmd' => 'echo first', 'range_def' => 1,
|
||||
'special_def' => 0, 'all_mins' => 1, 'all_hours' => 1,
|
||||
'all_days' => 1, 'all_months' => 1, 'all_weekdays' => 1 );
|
||||
foreach my $case (
|
||||
[ "a line break in the command",
|
||||
{ 'cmd' => "echo first\nPATH=/tmp" }, qr/cannot contain line breaks/ ],
|
||||
[ "a line break in the comment",
|
||||
{ 'comment' => "x\nPATH=/tmp" }, qr/cannot contain line breaks/ ],
|
||||
[ "a special time with spaces",
|
||||
{ 'special_def' => 1, 'special' => "reboot echo x #" },
|
||||
qr/Invalid special time/ ],
|
||||
[ "a minute with spaces",
|
||||
{ 'all_mins' => 0, 'mins' => "* * * * * echo x #" },
|
||||
qr/Invalid Minutes selected/ ],
|
||||
[ "a minute with a line break",
|
||||
{ 'all_mins' => 0, 'mins' => "5\nPATH=/tmp" },
|
||||
qr/Invalid Minutes selected/ ],
|
||||
[ "a range date with shell text",
|
||||
{ 'range_def' => 0, 'range_start_day' => '1;echo x;',
|
||||
'range_start_month' => 1, 'range_start_year' => 2026,
|
||||
'range_end_day' => 31, 'range_end_month' => 12,
|
||||
'range_end_year' => 2026 }, qr/invalid start date/i ],
|
||||
) {
|
||||
my ($desc, $form, $re) = @$case;
|
||||
$result = run_cgi("save_cron.cgi", { %edit, %$form });
|
||||
like($result->{'out'}, $re, "save_cron.cgi rejects $desc");
|
||||
is(read_crontab_fixture(), $original,
|
||||
"rejected $desc leaves the crontab unchanged");
|
||||
}
|
||||
|
||||
seed_acl(%full_acl);
|
||||
$result = run_cgi("move.cgi", { 'idx' => 4, 'top' => 1 });
|
||||
like($result->{'out'}, qr/^Location:/m,
|
||||
@@ -253,4 +294,23 @@ like($result->{'out'}, qr/^Location:/m,
|
||||
isnt(read_crontab_fixture(), $original,
|
||||
"the permitted top move changes the crontab");
|
||||
|
||||
# Saving a job needs a real Unix user, so check a valid save in a per-user
|
||||
# crontab, as on Linux. Plain shell commands stand in for the crontab
|
||||
# command, so the real one never runs.
|
||||
write_cron_config("cron_copy_command=cat >$crondir/USER",
|
||||
"cron_delete_command=rm -f $crondir/USER");
|
||||
$cronfile = "$crondir/root";
|
||||
write_crontab();
|
||||
$result = run_cgi("save_cron.cgi",
|
||||
{ %edit, 'all_mins' => 0, 'mins' => [ 5, 6, 30 ],
|
||||
'range_def' => 0, 'range_start_day' => 1,
|
||||
'range_start_month' => 1, 'range_start_year' => 2026,
|
||||
'range_end_day' => 31, 'range_end_month' => 12,
|
||||
'range_end_year' => 2026 });
|
||||
like($result->{'out'}, qr/^Location:/m,
|
||||
"save_cron.cgi accepts valid times and dates");
|
||||
like(read_crontab_fixture(),
|
||||
qr/^5-6,30 \* \* \* \* \S+range\.pl 1-1-2026 31-12-2026 echo\\ first$/m,
|
||||
"the saved job has the expected times and date range");
|
||||
|
||||
done_testing();
|
||||
|
||||
@@ -173,6 +173,40 @@ subtest 'convert_to_json / convert_from_json' => sub {
|
||||
is_deeply(main::convert_from_json($pretty), {a=>1,b=>2},
|
||||
'pretty output still parses');
|
||||
|
||||
# Sorting is independent of whitespace, with the same contract in both engines.
|
||||
for my $engine ('JSON::PP', 'JSON::XS') {
|
||||
subtest "$engine canonical output" => sub {
|
||||
eval "require $engine; 1" or plan skip_all => "$engine is not installed";
|
||||
# Force the supported PP fallback even when XS is installed.
|
||||
local $INC{'JSON/XS.pm'} = $engine eq 'JSON::PP' ? undef : $INC{'JSON/XS.pm'};
|
||||
my $data = {z => 2, a => {z => 1, a => 0}};
|
||||
is(main::convert_to_json($data, 0, 1, 1),
|
||||
'{"a":{"a":0,"z":1},"z":2}', 'compact output sorts nested keys');
|
||||
is(main::convert_to_json($data, 1),
|
||||
$engine->new->pretty->canonical->latin1->encode($data),
|
||||
'existing pretty calls retain sorted output');
|
||||
is(main::convert_to_json($data, 1, 0, 0),
|
||||
$engine->new->pretty->latin1->encode($data),
|
||||
'explicit false disables sorting independently of pretty printing');
|
||||
is(main::convert_to_json($data, 0, 0, undef),
|
||||
$engine->new->latin1->encode($data),
|
||||
'undefined canonical flag retains the default');
|
||||
# JSON syntax and byte encoding must remain independent of sorting.
|
||||
my $unicode = {label => "Caf\x{e9} \x{65e5}\x{672c} \x{1f642}"};
|
||||
my $bytes = main::convert_to_json($unicode, 0, 1, 1);
|
||||
ok(!utf8::is_utf8($bytes), 'UTF-8 mode returns bytes');
|
||||
is_deeply(main::convert_from_json($bytes), $unicode, 'Unicode round-trips');
|
||||
is($bytes, JSON::PP->new->canonical->utf8->encode($unicode),
|
||||
'compact UTF-8 matches the existing canonical encoding');
|
||||
my $backup = {content => join('', map { chr } 0..255)};
|
||||
my $raw = main::convert_to_json($backup, 0, 0, 1);
|
||||
is($raw, JSON::PP->new->canonical->encode($backup),
|
||||
'raw configuration bytes retain their existing serialized form');
|
||||
is_deeply(main::convert_from_json($raw, 1), $backup,
|
||||
'raw configuration bytes round-trip');
|
||||
};
|
||||
}
|
||||
|
||||
# Current contract: undef input becomes {} (the `||= {}` default).
|
||||
is(main::convert_to_json(undef), '{}', 'undef input → "{}"');
|
||||
|
||||
|
||||
@@ -13849,18 +13849,20 @@ for(my $i=0; $i<@sp1 || $i<@sp2; $i++) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
=head2 convert_to_json(data, [pretty], [raw-utf8])
|
||||
=head2 convert_to_json(data, [pretty], [raw-utf8], [canonical])
|
||||
|
||||
Converts the given Perl data structure to encoded binary string
|
||||
|
||||
=item data parameter is a hash/array reference
|
||||
=item if the output should be prettified and hash keys sorted
|
||||
=item pretty parameter enables indentation and newlines; also sorts hash keys unless canonical is supplied
|
||||
=item raw-utf8 parameter, if set to 1, encodes data using UTF-8
|
||||
|
||||
=item canonical parameter controls hash key sorting independently of pretty printing; defaults to pretty
|
||||
|
||||
=cut
|
||||
sub convert_to_json
|
||||
{
|
||||
my ($data, $pretty, $raw_utf8) = @_;
|
||||
my ($data, $pretty, $raw_utf8, $canonical) = @_;
|
||||
my $json;
|
||||
|
||||
if (eval { require JSON::XS }) {
|
||||
@@ -13873,7 +13875,8 @@ else {
|
||||
error("Neither JSON::XS nor JSON::PP Perl module is available on your system");
|
||||
}
|
||||
$json->pretty(!!$pretty);
|
||||
$json->canonical(1) if ($pretty);
|
||||
# Preserve sorting for existing pretty calls unless explicitly overridden.
|
||||
$json->canonical(defined($canonical) ? !!$canonical : !!$pretty);
|
||||
$data ||= {};
|
||||
return $raw_utf8 ? $json->utf8->encode($data) : $json->latin1->encode($data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user