diff --git a/C4/ClassSplitRoutine/RegEx.pm b/C4/ClassSplitRoutine/RegEx.pm index 3a9f841876..801340a0b9 100644 --- a/C4/ClassSplitRoutine/RegEx.pm +++ b/C4/ClassSplitRoutine/RegEx.pm @@ -19,6 +19,8 @@ package C4::ClassSplitRoutine::RegEx; use Modern::Perl; +use Koha::Regex::Replacement; + =head1 NAME C4::ClassSplitRoutine::RegEx - regex call number sorting key routine @@ -41,13 +43,59 @@ sub split_callnumber { my ( $cn_item, $regexs ) = @_; for my $regex (@$regexs) { - eval "\$cn_item =~ $regex"; ## no critic (StringyEval) + _apply_substitution( \$cn_item, $regex ); } my @lines = split "\n", $cn_item; return @lines; } +=head2 _apply_substitution + + _apply_substitution( \$string, $regex ); + +Applies a single admin-configured s/// substitution to $string in place, +without ever evaluating the input as code. The rule is parsed as +sPATTERNREPLACEMENTFLAGS; the pattern is compiled with +qr// (so embedded-code assertions are treated as a runtime pattern and blocked +by Perl), and the replacement is expanded as data via +Koha::Regex::Replacement::expand_template. The +/e flag is rejected and anything that is not a plain substitution is ignored. +This replaces the previous C stringy eval, which +executed arbitrary Perl. + +=cut + +sub _apply_substitution { + my ( $ref, $regex ) = @_; + + return unless defined $regex; + + # Parse sPATTERNREPLACEMENTFLAGS, allowing any + # non-space delimiter and backslash-escaped delimiters inside the pattern + # and replacement. Anything that is not a well-formed plain substitution is + # ignored rather than executed. + return unless $regex =~ m{^s(\S)((?:\\.|(?!\1).)*)\1((?:\\.|(?!\1).)*)\1([a-z]*)\z}s; + my ( $pattern, $replacement, $flags ) = ( $2, $3, $4 ); + + return if $flags =~ /e/; # never evaluate the replacement as code + my $global = $flags =~ /g/ ? 1 : 0; + my $ci = $flags =~ /i/ ? '(?i)' : ''; + + my $compiled = eval { qr/$ci$pattern/ }; + return unless defined $compiled; + + my $expand = sub { Koha::Regex::Replacement::expand_template( $replacement, [@{^CAPTURE}], {%+} ) }; + + if ($global) { + ${$ref} =~ s/$compiled/$expand->()/ge; + } else { + ${$ref} =~ s/$compiled/$expand->()/e; + } + + return; +} + 1; =head1 AUTHOR