Skip to content

Commit

Permalink
Merge pull request #1817 from ikedas/issue-1811 by ikedas
Browse files Browse the repository at this point in the history
Allow "custom_subject" to be at the beginning of the subject (#1811)
  • Loading branch information
ikedas committed Aug 25, 2024
2 parents b70974e + c9d1096 commit 7edf1e1
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 105 deletions.
199 changes: 103 additions & 96 deletions src/lib/Sympa/Spindle/TransformIncoming.pm
Original file line number Diff line number Diff line change
Expand Up @@ -95,108 +95,115 @@ sub _twist {
}

# Add Custom Subject

my $parsed_tag;
if ($list->{'admin'}{'custom_subject'}) {
if (($list->{'admin'}{'custom_subject'} // '') =~ /\S/) {
my $custom_subject = $list->{'admin'}{'custom_subject'};
my $tag;

# Check if custom_subject parameter is parsable.
my $data = {
list => {
name => $list->{'name'},
sequence => $message->{xsequence},
},
};
# Check if custom_subject parameter is parsable and parsed tag contains
# non-space character.
my $template = Sympa::Template->new(undef);
unless ($template->parse($data, [$custom_subject], \$parsed_tag)) {
unless (
$template->parse(
{ list => {
name => $list->{'name'},
sequence => $message->{xsequence},
},
},
[$custom_subject],
\$tag
)
) {
$log->syslog('err', 'Can\'t parse custom_subject of list %s: %s',
$list, $template->{last_error});

undef $parsed_tag;
}
}
if ($list->{'admin'}{'custom_subject'} and defined $parsed_tag) {
my $subject_field = $message->{'decoded_subject'};
$subject_field = '' unless defined $subject_field;
## Remove leading and trailing blanks
$subject_field =~ s/^\s*(.*)\s*$/$1/;

## Search previous subject tagging in Subject
my $custom_subject = $list->{'admin'}{'custom_subject'};

## tag_regexp will be used to remove the custom subject if it is
## already present in the message subject.
## Remember that the value of custom_subject can be
## "dude number [%list.sequence"%]" whereas the actual subject will
## contain "dude number 42".
my $list_name_escaped = $list->{'name'};
$list_name_escaped =~ s/(\W)/\\$1/g;
my $tag_regexp = $custom_subject;
## cleanup, just in case dangerous chars were left
$tag_regexp =~ s/([^\w\s\x80-\xFF])/\\$1/g;
## Replaces "[%list.sequence%]" by "\d+"
$tag_regexp =~ s/\\\[\\\%\s*list\\\.sequence\s*\\\%\\\]/\\d+/g;
## Replace "[%list.name%]" by escaped list name
$tag_regexp =~
s/\\\[\\\%\s*list\\\.name\s*\\\%\\\]/$list_name_escaped/g;
## Replaces variables declarations by "[^\]]+"
$tag_regexp =~ s/\\\[\\\%\s*[^]]+\s*\\\%\\\]/[^]]+/g;
## Takes spaces into account
$tag_regexp =~ s/\s+/\\s+/g;

# Add subject tag

## If subject is tagged, replace it with new tag
## Splitting the subject in two parts :
## - what will be before the custom subject (probably some "Re:")
## - what will be after it : the original subject sent to the list.
## The custom subject is not kept.
my $before_tag;
my $after_tag;
if ($custom_subject =~ /\S/) {
$subject_field =~ s/\s*\[$tag_regexp\]\s*/ /;
}
$subject_field =~ s/\s+$//;

# Truncate multiple "Re:" and equivalents.
# Note that Unicode case-ignore match is performed.
my $re_regexp = Sympa::Regexps::re();
$subject_field = Encode::decode_utf8($subject_field);
if ($subject_field =~ s/\A\s*($re_regexp\s*)($re_regexp\s*)*//i) {
$before_tag = Encode::encode_utf8($1);
} else {
$before_tag = '';
} elsif (($tag // '') =~ /\S/) {
my $decoded_subject = $message->{'decoded_subject'} // '';

# Remove the custom subject if it is already present in the
# subject field. The new tag will be added in below.
# Remember that the value of custom_subject can be
# "dude number [%list.sequence%]" whereas the actual subject will
# contain "dude number 42".
# Regexp is derived from the custom_subject parameter:
# - Replaces "[%list.sequence%]" by /\d+/ and remember it.
# - Replaces "[%list.name%]" by escaped list name.
# - Replaces variable interpolation "[% ... %]" by /[^]]+/. FIXME
# - Cleanup, just in case dangerous chars were left.
# - Takes spaces into account.
my $has_sequence;
my $custom_subject_regexp = $custom_subject =~ s{
( \[ % \s* list\.sequence \s* % \] )
| ( \[ % \s* list\.name \s* % \] )
| ( \[ % \s* [^]]+ \s* % \] )
| ( [^\w\s\x80-\xFF] )
| \s+
}{
if ($1) {
$has_sequence = 1;
"\\d+";
} elsif ($2) {
$list->{'name'} =~ s/(\W)/\\$1/gr;
} elsif ($3) {
'[^]]+';
} elsif ($4) {
"\\" . $4;
} else {
"\\s+";
}
}egrx;
$decoded_subject =~ s/\s*\[$custom_subject_regexp\]\s*/ /;

$decoded_subject =~ s/\A\s+//;
$decoded_subject =~ s/\s+\z//;

# Add subject tag.
# Splitting the subject in two parts :
# - what will be prepended to the subject (probably some "Re:").
# - what will be after it : the original subject.
# Multiple "Re:" and equivalents will be truncated.
# Then, they are rejoined as:
# - if the tag has seq number, "[tag] Re: Stem" (6.2.74 and later)
# - otherwise, "Re: [tag] Stem".
my $subject_field;
my $re_regexp = Sympa::Regexps::re();

if ($message->{'subject_charset'}) {
# Note that Unicode case-ignore match is performed.
my $uStem =
Encode::decode_utf8($decoded_subject) =~
s/\A\s*($re_regexp\s*)($re_regexp\s*)*//ir;
my $uRe = $1 // '';

# Encode subject using initial charset.
my $uTag = Encode::decode_utf8(sprintf '[%s]', $tag);
$subject_field = MIME::EncWords::encode_mimewords(
$has_sequence
? join(' ', $uTag, $uRe . $uStem)
: join(' ', $uRe . $uTag, $uStem),
Charset => $message->{'subject_charset'},
Field => 'Subject',
Replacement => 'FALLBACK'
);
} else {
my $stem = $decoded_subject =~
s/\A\s*($re_regexp\s*)($re_regexp\s*)*//ir;
my $re = ($1 // '') =~ s/\s+\z//r;

# Don't try to encode the original subject if it was not
# originally encoded.
my $eTag = MIME::EncWords::encode_mimewords(
Encode::decode_utf8(sprintf '[%s]', $tag),
Charset => Conf::lang2charset($language->get_lang),
Field => 'Subject'
);
$subject_field =
$has_sequence
? join(' ', grep {length} $eTag, $re, $stem)
: join(' ', grep {length} $re, $eTag, $stem);
}

$message->delete_header('Subject');
$message->add_header('Subject', $subject_field);
}
$after_tag = Encode::encode_utf8($subject_field);

## Encode subject using initial charset

## Don't try to encode the subject if it was not originally encoded.
if ($message->{'subject_charset'}) {
$subject_field = MIME::EncWords::encode_mimewords(
Encode::decode_utf8(
$before_tag . '[' . $parsed_tag . '] ' . $after_tag
),
Charset => $message->{'subject_charset'},
Encoding => 'A',
Field => 'Subject',
Replacement => 'FALLBACK'
);
} else {
$subject_field =
$before_tag . ' '
. MIME::EncWords::encode_mimewords(
Encode::decode_utf8('[' . $parsed_tag . ']'),
Charset => Conf::lang2charset($language->get_lang),
Encoding => 'A',
Field => 'Subject'
)
. ' '
. $after_tag;
}

$message->delete_header('Subject');
$message->add_header('Subject', $subject_field);
}

## Prepare tracking if list config allow it
Expand Down
98 changes: 89 additions & 9 deletions t/Spindle_TransformIncoming.t
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,84 @@ foreach my $pinfo (grep { $_->{name} and exists $_->{default} }
unless exists $Conf::Conf{$pinfo->{name}};
}

my $list = bless {
my $list;
my $spindle;
my $message;

# custom_subject and subject prefixes (with [%list.sequence%])

$list = bless {
name => 'list',
domain => $Conf::Conf{'domain'},
admin => {custom_subject => '[%list.name%]:[%list.sequence%]',},
} => 'Sympa::List';

my $spindle = Sympa::Spindle::TransformIncoming->new(
$spindle = Sympa::Spindle::TransformIncoming->new(
context => $list,
splicing_to => 'Sympa::Spindle',
);

my $message;

# custom_subject and subject prefixes

$message = Sympa::Message->new("Subject: Re: Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: [list:1] Test\n\n", 'Re: Re:';
is $message->as_string, "Subject: [list:1] Re: Test\n\n",
'Re: Re: (with list.sequence)';

$message = Sympa::Message->new("Subject: Re:[list:979] Re: Test\n\n",
context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: [list:1] Test\n\n", 'Re:[tag] Re:';
is $message->as_string, "Subject: [list:1] Re: Test\n\n",
'Re:[tag] Re: (with list.sequence)';

$message =
Sympa::Message->new("Subject: [list:979] Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: [list:1] Re: Test\n\n",
'[tag] Re: (with list.sequence)';

$message = Sympa::Message->new(<<'EOF', context => $list);
Subject: =?UTF-8?B?U1Y6IEFudHc6IFZTOiBSRUYgOiBSRTogUkVbMl06IEFXOiDOkc6gOiA=?=
=?UTF-8?B?zpHPgDogzqPOp86VzqQ6IM6jz4fOtc+EOiDQndCwOiDQvdCwOiBWw6E6IFI6IFJJ?=
=?UTF-8?B?RjogQXRiLjogUkVTOiBPZHA6IFludDogQVRCOiDlm57lpI06IOWbnuimhu+8mlZT?=
=?UTF-8?Q?=3a_Fwd=3a_Re=3a_Something?=
EOF
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: [list:1] SV: Fwd: Re: Something\n\n",
'Multilingual "Re:" (with list.sequence)';

# custom_subject and subject prefixes (without [%list.sequence%])

$list = bless {
name => 'list',
domain => $Conf::Conf{'domain'},
admin => {custom_subject => '[%list.name%]',},
} => 'Sympa::List';

$spindle = Sympa::Spindle::TransformIncoming->new(
context => $list,
splicing_to => 'Sympa::Spindle',
);

$message = Sympa::Message->new("Subject: Re: Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: [list] Test\n\n", 'Re: Re:';

$message =
Sympa::Message->new("Subject: Re:[list] Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: [list] Test\n\n", 'Re:[tag] Re:';

$message =
Sympa::Message->new("Subject: [list] Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: [list] Test\n\n", '[tag] Re:';

$message = Sympa::Message->new(<<'EOF', context => $list);
Subject: =?UTF-8?B?U1Y6IEFudHc6IFZTOiBSRUYgOiBSRTogUkVbMl06IEFXOiDOkc6gOiA=?=
Expand All @@ -62,9 +115,36 @@ Subject: =?UTF-8?B?U1Y6IEFudHc6IFZTOiBSRUYgOiBSRTogUkVbMl06IEFXOiDOkc6gOiA=?=
EOF
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: SV: [list:1] Fwd: Re: Something\n\n",
is $message->as_string, "Subject: SV: [list] Fwd: Re: Something\n\n",
'Multilingual "Re:"';

# Empty tmplate

$list->{admin}{custom_subject} = ' ';

$message = Sympa::Message->new("Subject: Re: Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: Re: Test\n\n", 'empty template';

# Error in tmplate

$list->{admin}{custom_subject} = '[%list.name%%]';

$message = Sympa::Message->new("Subject: Re: Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: Re: Test\n\n", 'error in template';

# Empty tag

$list->{admin}{custom_subject} = ' [%# Nothing %] ';

$message = Sympa::Message->new("Subject: Re: Re: Test\n\n", context => $list);
$spindle->{distaff} = Sympa::Spool::Mock->new(message => $message);
$spindle->spin;
is $message->as_string, "Subject: Re: Re: Test\n\n", 'tag parsed to be empty';

done_testing;

package Sympa::Spool::Mock;
Expand Down

0 comments on commit 7edf1e1

Please sign in to comment.