diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm index b778e431f..41773fb3e 100644 --- a/Bugzilla/Mailer.pm +++ b/Bugzilla/Mailer.pm @@ -98,6 +98,11 @@ sub MessageToMTA { $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version'); + # We ensure there's a Message-ID header set otherwise some mailsystems + # treat us as spam. + $email->header_set('Message-ID', build_message_id()) + if !$email->header('Message-ID'); + # Encode the headers correctly in quoted-printable foreach my $header ($email->header_names) { $header = lc $header; @@ -252,9 +257,9 @@ sub build_thread_marker { my $sitespec = '@' . Bugzilla->localconfig->urlbase; $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain - $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate - if ($2) { - $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@' + $sitespec =~ s/\/.*$//; # Strip path component — / is illegal after @ in Message-ID + if ($sitespec =~ s/^([^:\/]+):(\d+)/$1/) { # Remove port number, to relocate + $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@' } my $threadingmarker = "References: "; @@ -270,4 +275,23 @@ sub build_thread_marker { return $threadingmarker; } +# Builds Message-ID header +sub build_message_id { + my ($user_id) = @_; + + # Don't fall back to current user: this is called from contexts with no logged-in + # user (job queue, email_in.pl). The random bits below ensure uniqueness anyway. + $user_id //= ''; + + my $sitespec = '@' . Bugzilla->localconfig->urlbase; + $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain + $sitespec =~ s/\/.*$//; # Strip path component — / is illegal after @ in Message-ID + if ($sitespec =~ s/^([^:\/]+):(\d+)/$1/) { # Remove port number, to relocate + $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@' + } + + my $rand_bits = generate_random_password(10); + my $message_id = '"; + return $message_id; +} 1; diff --git a/t/014mailer.t b/t/014mailer.t new file mode 100644 index 000000000..a3f9dd1d1 --- /dev/null +++ b/t/014mailer.t @@ -0,0 +1,113 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +################## +#Bugzilla Test 14# +####Mailer.pm##### + +use 5.10.1; +use strict; +use warnings; + +use lib qw(. lib local/lib/perl5 t); +use Support::Files; +use Test::More tests => 18; + +BEGIN { + use_ok('Bugzilla'); + use_ok('Bugzilla::Mailer', 'build_thread_marker'); +} + +# Inject a stub localconfig into the process cache so the sitespec +# transformation functions can be tested without a real installation. +{ + + package Bugzilla::Test::FakeLocalconfig; + + sub new { my ($class, $urlbase) = @_; bless {urlbase => $urlbase}, $class } + sub urlbase { $_[0]->{urlbase} } +} + +sub set_urlbase { + my ($urlbase) = @_; + Bugzilla->process_cache->{localconfig} + = Bugzilla::Test::FakeLocalconfig->new($urlbase); +} + +# ---- build_message_id: sitespec transformation ---- +# +# The central invariant: whatever urlbase looks like, the resulting +# Message-ID must be a valid with no '/' characters +# after the '@'. + +my $mid; + +# Plain http, trailing slash only — baseline case. +set_urlbase('http://bugs.example.org/'); +$mid = Bugzilla::Mailer::build_message_id(); +like($mid, qr/^$/, + 'simple http urlbase: correct format and sitespec'); +unlike($mid, qr/\@[^>]*\//, 'simple http urlbase: no slash after @'); + +# Path component after the hostname — the key regression this patch fixes. +set_urlbase('https://bugs.example.org/bugzilla/'); +$mid = Bugzilla::Mailer::build_message_id(); +like($mid, qr/^$/, + 'https with path: path component stripped from sitespec'); +unlike($mid, qr/\@[^>]*\//, 'https with path: no slash after @'); + +# Non-standard port — port must move before the '@'. +set_urlbase('https://bugs.example.org:8080/'); +$mid = Bugzilla::Mailer::build_message_id(); +like($mid, qr/^$/, + 'https with port: port relocated before @'); +unlike($mid, qr/\@[^>]*\//, 'https with port: no slash after @'); + +# Port and path together. +set_urlbase('https://bugs.example.org:8080/bugzilla/'); +$mid = Bugzilla::Mailer::build_message_id(); +like($mid, qr/^$/, + 'https with port and path: path stripped, port relocated'); +unlike($mid, qr/\@[^>]*\//, 'https with port and path: no slash after @'); + +# ---- build_message_id: user_id handling ---- + +set_urlbase('https://bugs.example.org/'); + +my $mid_with_user = Bugzilla::Mailer::build_message_id(42); +like($mid_with_user, qr/^/, + 'new thread with path: path stripped from sitespec'); +unlike($marker, qr/\@[^>]*\//, 'new thread with path: no slash after @'); + +# Port relocation for non-standard port. +set_urlbase('https://bugs.example.org:8080/'); +$marker = build_thread_marker(99, 7, 1); # new bug +like($marker, qr/Message-ID: /, + 'new thread with port: port relocated before @'); + +# Reply includes In-Reply-To pointing at the root message. +$marker = build_thread_marker(99, 7, 0); # reply +like($marker, qr/In-Reply-To: /, + 'reply thread: In-Reply-To uses correct sitespec');