3
0
mirror of https://github.com/pragma-/pbot.git synced 2025-09-08 21:17:25 +02:00

Factoids: Restrict recursion depth for commands that modify factoids (factadd, factchange, etc)

This commit is contained in:
Pragmatic Software 2025-09-04 10:24:55 -07:00
parent 0ecdcc1bf5
commit 9cf4b18538
No known key found for this signature in database
GPG Key ID: CC916B6E3C84ECCE
5 changed files with 100 additions and 12 deletions

View File

@ -132,11 +132,10 @@ sub interpreter($self, $context) {
# debug flag to trace $context location and contents
if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) {
use Data::Dumper;
$Data::Dumper::Sortkeys = sub { [sort grep { not /(?:cmdlist|arglist)/ } keys %$context] };
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 2;
$self->{pbot}->{logger}->log("Commands::interpreter\n");
$self->{pbot}->{logger}->log(Dumper $context);
$Data::Dumper::Sortkeys = 1;
}
# some convenient aliases

View File

@ -133,6 +133,10 @@ sub cmd_as_factoid($self, $context) {
sub cmd_factundo($self, $context) {
my $usage = "Usage: factundo [-l [N]] [-r N] [channel] <keyword> (-l list undo history, optionally starting from N; -r jump to revision N)";
if ($context->{interpret_depth} > 2) {
return "factundo: recursion depth exceeded.\n";
}
my $arguments = $context->{arguments};
my ($list_undos, $goto_revision);
@ -196,8 +200,13 @@ sub cmd_factundo($self, $context) {
return $self->list_undo_history($undos, $list_undos);
}
my $factoids = $self->{pbot}->{factoids}->{data}->{storage};
my $userinfo = $self->{pbot}->{users}->loggedin($channel, $context->{hostmask});
if (!$self->{pbot}->{capabilities}->userhas($userinfo, 'admin') && $channel =~ /^#/ && $channel ne $context->{from}) {
return "/say Switch to $channel to revert this factoid.";
}
my $factoids = $self->{pbot}->{factoids}->{data}->{storage};
if ($factoids->get_data($channel, $trigger, 'locked')) {
return "/say $trigger_name is locked and cannot be reverted." if not $self->{pbot}->{capabilities}->userhas($userinfo, 'admin');
@ -236,6 +245,10 @@ sub cmd_factundo($self, $context) {
sub cmd_factredo($self, $context) {
my $usage = "Usage: factredo [-l [N]] [-r N] [channel] <keyword> (-l list undo history, optionally starting from N; -r jump to revision N)";
if ($context->{interpret_depth} >2) {
return "factredo: recursion depth exceeded.\n";
}
my $arguments = $context->{arguments};
@ -287,6 +300,10 @@ sub cmd_factredo($self, $context) {
return $self->list_undo_history($undos, $list_undos);
}
if ($channel =~ /^#/ && $channel ne $context->{from}) {
return "/say Switch to $channel to revert this factoid.";
}
my $factoids = $self->{pbot}->{factoids}->{data}->{storage};
my $userinfo = $self->{pbot}->{users}->loggedin($channel, $context->{hostmask});
if ($factoids->get_data($channel, $trigger, 'locked')) {
@ -331,6 +348,10 @@ sub cmd_factset($self, $context) {
$context->{from}, $context->{arguments}, 'factset', usage => 'Usage: factset [channel] <factoid> [key [value]]', explicit => 1
);
if ($context->{interpret_depth} > 2) {
return "factset: recursion depth exceeded.\n";
}
return $channel if not defined $trigger; # if $trigger is not defined, $channel is an error message
my $trigger_name = $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, '_name');
@ -372,6 +393,10 @@ sub cmd_factset($self, $context) {
return "/say $trigger_name is locked; unlock before setting.";
}
if ($channel =~ /^#/ && $channel ne $context->{from} && !$self->{pbot}->{capabilities}->userhas($userinfo, 'admin')) {
return "/say Switch to $channel to factset this factoid.";
}
if (lc $key eq 'cap-override' and defined $value) {
if (not $self->{pbot}->{capabilities}->exists($value)) {
return "No such capability $value.";
@ -403,6 +428,10 @@ sub cmd_factset($self, $context) {
sub cmd_factunset($self, $context) {
my $usage = 'Usage: factunset [channel] <factoid> <key>';
if ($context->{interpret_depth} > 2) {
return "factunset: recursion depth exceeded.\n";
}
my ($channel, $trigger, $arguments) = $self->find_factoid_with_optional_channel(
$context->{from}, $context->{arguments}, 'factunset', usage => $usage, explicit => 1
);
@ -457,6 +486,10 @@ sub cmd_factunset($self, $context) {
return "[$channel_name] $trigger_name: key '$key' does not exist.";
}
if ($channel =~ /^#/ && $channel ne $context->{from}) {
return "/say Switch to $channel to factunset this factoid.";
}
if ($key eq 'cap-override') {
if (not $self->{pbot}->{capabilities}->userhas($userinfo, $oldvalue)) {
return "Your user account must have the $oldvalue capability to unset this cap-override.";
@ -475,6 +508,10 @@ sub cmd_factunset($self, $context) {
sub cmd_factmove($self, $context) {
my ($src_channel, $source, $target_channel, $target) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 5);
if ($context->{interpret_depth} > 2) {
return "factmove: recursion depth exceeded.\n";
}
my $usage = "Usage: factmove <source channel> <source factoid> <target channel/factoid> [target factoid]";
return $usage if not defined $target_channel;
@ -557,6 +594,10 @@ sub cmd_factmove($self, $context) {
$target_channel = '.*' if $target_channel !~ /^#/;
if ($target_channel =~ /^#/ && $target_channel ne $context->{from}) {
return "/say Switch to $target_channel to move this factoid.";
}
my $data = $factoids->get_data($found_src_channel, $found_source);
$factoids->remove($found_src_channel, $found_source, undef, 1);
$factoids->add($target_channel, $target, $data);
@ -578,6 +619,10 @@ sub cmd_factmove($self, $context) {
sub cmd_factcopy($self, $context) {
my ($src_channel, $source, $target_channel, $target) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 5);
if ($context->{interpret_depth} > 2) {
return "factcopy: recursion depth exceeded.\n";
}
my $usage = "Usage: factcopy <source channel> <source factoid> <target channel/factoid> [target factoid]";
return $usage if not defined $target_channel;
@ -654,6 +699,10 @@ sub cmd_factcopy($self, $context) {
$target_channel = '.*' if $target_channel !~ /^#/;
if ($target_channel =~ /^#/ && $target_channel ne $context->{from}) {
return "/say Switch to $target_channel to move this factoid.";
}
my $data = $factoids->get_data($found_src_channel, $found_source);
$data->{owner} = $context->{hostmask};
@ -679,6 +728,11 @@ sub cmd_factcopy($self, $context) {
sub cmd_factalias($self, $context) {
my ($chan, $alias, $command) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 3, 0, 1);
if ($context->{interpret_depth} > 2) {
return "factalias: recursion depth exceeded.\n";
}
if (defined $chan and not($chan eq '.*' or $chan =~ m/^#/)) {
# $chan doesn't look like a channel, so shift everything to the right
# and replace $chan with $from
@ -713,6 +767,12 @@ sub cmd_factalias($self, $context) {
if ($self->{pbot}->{commands}->exists($alias)) { return "/say $alias already exists as a built-in command."; }
my $userinfo = $self->{pbot}->{users}->loggedin($chan, $context->{hostmask});
if (!$self->{pbot}->{capabilities}->userhas($userinfo, 'admin') && $chan =~ /^#/ && $chan ne $context->{from}) {
return "/say Switch to $chan to add this factoid.";
}
$self->{pbot}->{factoids}->{data}->add('text', $chan, $context->{hostmask}, $alias, "/call $command");
$self->{pbot}->{logger}->log("$context->{hostmask} [$chan] aliased $alias => $command\n");
return "/say $alias aliases `$command` for " . ($chan eq '.*' ? 'the global channel' : $chan);
@ -755,11 +815,16 @@ my @valid_pastesites = (
'https?://ix.io',
'https?://dpaste.com',
'https?://0x0.st',
'https?://x0.at',
);
sub cmd_factadd($self, $context) {
my ($from_chan, $keyword, $text, $force);
if ($context->{interpret_depth} > 2) {
return "factadd: recursion depth exceeded.\n";
}
my @arglist = @{$context->{arglist}};
if (@arglist) {
@ -872,6 +937,12 @@ sub cmd_factadd($self, $context) {
if ($self->{pbot}->{commands}->exists($keyword)) { return "/say $keyword_text already exists as a built-in command."; }
my $userinfo = $self->{pbot}->{users}->loggedin($from_chan, $context->{hostmask});
if (!$self->{pbot}->{capabilities}->userhas($userinfo, 'admin') && $from_chan =~ /^#/ && $from_chan ne $context->{from}) {
return "/say Switch to $from_chan to add this factoid.";
}
my $exists = $self->{pbot}->{factoids}->{data}->{storage}->exists($from_chan, $keyword);
$self->{pbot}->{factoids}->{data}->add('text', $from_chan, $context->{hostmask}, $keyword, $text);
@ -892,6 +963,10 @@ sub cmd_factadd($self, $context) {
sub cmd_factrem($self, $context) {
my $factoids = $self->{pbot}->{factoids}->{data}->{storage};
if ($context->{interpret_depth} > 2) {
return "factrem: recursion depth exceeded.\n";
}
my ($from_chan, $from_trig) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
if (not defined $from_trig) {
@ -910,7 +985,15 @@ sub cmd_factrem($self, $context) {
$channel_name = 'global' if $channel_name eq '.*';
$trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /;
if ($factoids->get_data($channel, $trigger, 'type') eq 'applet') { return "/say $trigger_name is not a factoid."; }
if ($factoids->get_data($channel, $trigger, 'type') eq 'applet') {
return "/say $trigger_name is not a factoid.";
}
my $is_admin = $self->{pbot}->{users}->loggedin_admin($channel, $context->{hostmask});
if (!$is_admin && $channel =~ /^#/ && $channel ne $context->{from}) {
return "/say Switch to $channel_name to remove this factoid.";
}
if ($channel =~ /^#/ and $from_chan =~ /^#/ and lc $channel ne lc $from_chan) {
return "/say $trigger_name belongs to $channel_name, but this is $from_chan. Please switch to $channel_name or use /msg to remove this factoid.";
@ -1275,6 +1358,10 @@ sub cmd_factchange($self, $context) {
my $factoids_data = $self->{pbot}->{factoids}->{data}->{storage};
my ($channel, $trigger, $keyword, $delim, $tochange, $changeto, $modifier, $url);
if ($context->{interpret_depth} > 2) {
return "factchange: recursion depth exceeded.\n";
}
my $needs_disambig;
if (length $context->{arguments}) {
@ -1361,11 +1448,16 @@ sub cmd_factchange($self, $context) {
$from_chan = '.*' if $from_chan eq 'global';
my $userinfo = $self->{pbot}->{users}->loggedin($channel, $context->{hostmask});
if ($channel =~ /^#/ && $channel ne $context->{from} && !$self->{pbot}->{capabilities}->userhas($userinfo, 'admin')) {
return "/say Switch to $channel_name to change this factoid.";
}
if ($channel =~ /^#/ and $from_chan =~ /^#/ and lc $channel ne lc $from_chan) {
return "/say $trigger_name belongs to $channel_name, but this is $from_chan. Please switch to $channel_name or use /msg to change this factoid.";
}
my $userinfo = $self->{pbot}->{users}->loggedin($channel, $context->{hostmask});
if ($factoids_data->get_data($channel, $trigger, 'locked')) {
return "/say $trigger_name is locked and cannot be changed." if not $self->{pbot}->{capabilities}->userhas($userinfo, 'admin');

View File

@ -236,10 +236,6 @@ sub expand_action_arguments($self, $context, $action, $input = '', $nick = '') {
$action = validate_string($action, $self->{pbot}->{registry}->get_value('factoids', 'max_content_length'));
$input = validate_string($input, $self->{pbot}->{registry}->get_value('factoids', 'max_content_length'));
my %opts = (
nested => 0,
);
my @args = $self->{pbot}->{interpreter}->split_line($input);
$action =~ s/\$arglen\b|\$\{arglen\}/scalar @args/eg;

View File

@ -271,6 +271,7 @@ sub process_line($self, $from, $nick, $user, $host, $text, $tags = '', $is_comma
# takes a $context object containing contextual information about the
# command such as the channel, nick, user, host, command, etc.
sub interpret($self, $context) {
$context->{stack_depth} //= 0;
# log command invocation
$self->{pbot}->{logger}->log("=== [$context->{interpret_depth} ($context->{stack_depth})] Got command: "
. "($context->{from}) $context->{hostmask}: $context->{command}\n");
@ -633,7 +634,7 @@ sub handle_result($self, $context, $result = $context->{result}) {
}
# finish piping
if (exists $context->{pipe}->{$context->{stack_depth}}) {
if (exists $context->{pipe} && exists $context->{pipe}->{$context->{stack_depth}}) {
my ($pipe, $pipe_rest) = (
delete $context->{pipe}->{$context->{stack_depth}},
delete $context->{pipe_rest}->{$context->{stack_depth}}

View File

@ -25,8 +25,8 @@ use PBot::Imports;
# These are set by the /misc/update_version script
use constant {
BUILD_NAME => "PBot",
BUILD_REVISION => 4882,
BUILD_DATE => "2025-08-25",
BUILD_REVISION => 4884,
BUILD_DATE => "2025-09-04",
};
sub initialize {}