261 lines
8.4 KiB
Perl
261 lines
8.4 KiB
Perl
# 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.
|
|
|
|
package Bugzilla::Mailer;
|
|
|
|
use 5.10.1;
|
|
use strict;
|
|
use warnings;
|
|
|
|
use parent qw(Exporter);
|
|
@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker generate_email);
|
|
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Hook;
|
|
use Bugzilla::MIME;
|
|
use Bugzilla::Util;
|
|
use Bugzilla::User;
|
|
|
|
use Date::Format qw(time2str);
|
|
|
|
use Email::Sender::Simple qw(sendmail);
|
|
use Email::Sender::Transport::SMTP::Persistent;
|
|
use Bugzilla::Sender::Transport::Sendmail;
|
|
|
|
sub generate_email {
|
|
my ($vars, $templates) = @_;
|
|
my ($lang, $email_format, $msg_text, $msg_html, $msg_header);
|
|
state $use_utf8 = Bugzilla->params->{'utf8'};
|
|
|
|
if ($vars->{to_user}) {
|
|
$lang = $vars->{to_user}->setting('lang');
|
|
$email_format = $vars->{to_user}->setting('email_format');
|
|
} else {
|
|
# If there are users in the CC list who don't have an account,
|
|
# use the default language for email notifications.
|
|
$lang = Bugzilla::User->new()->setting('lang');
|
|
# However we cannot fall back to the default email_format, since
|
|
# it may be HTML, and many of the includes used in the HTML
|
|
# template require a valid user object. Instead we fall back to
|
|
# the plaintext template.
|
|
$email_format = 'text_only';
|
|
}
|
|
|
|
my $template = Bugzilla->template_inner($lang);
|
|
|
|
$template->process($templates->{header}, $vars, \$msg_header)
|
|
|| ThrowTemplateError($template->error());
|
|
$template->process($templates->{text}, $vars, \$msg_text)
|
|
|| ThrowTemplateError($template->error());
|
|
|
|
my @parts = (
|
|
Bugzilla::MIME->create(
|
|
attributes => {
|
|
content_type => 'text/plain',
|
|
charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
|
|
encoding => 'quoted-printable',
|
|
},
|
|
body_str => $msg_text,
|
|
)
|
|
);
|
|
if ($templates->{html} && $email_format eq 'html') {
|
|
$template->process($templates->{html}, $vars, \$msg_html)
|
|
|| ThrowTemplateError($template->error());
|
|
push @parts, Bugzilla::MIME->create(
|
|
attributes => {
|
|
content_type => 'text/html',
|
|
charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
|
|
encoding => 'quoted-printable',
|
|
},
|
|
body_str => $msg_html,
|
|
);
|
|
}
|
|
|
|
my $email = Bugzilla::MIME->new($msg_header);
|
|
if (scalar(@parts) == 1) {
|
|
$email->content_type_set($parts[0]->content_type);
|
|
} else {
|
|
$email->content_type_set('multipart/alternative');
|
|
# Some mail clients need same encoding for each part, even empty ones.
|
|
$email->charset_set('UTF-8') if $use_utf8;
|
|
}
|
|
$email->parts_set(\@parts);
|
|
return $email;
|
|
}
|
|
|
|
sub MessageToMTA {
|
|
my ($msg, $send_now) = (@_);
|
|
my $method = Bugzilla->params->{'mail_delivery_method'};
|
|
return if $method eq 'None';
|
|
|
|
if (Bugzilla->params->{'use_mailer_queue'}
|
|
&& ! $send_now
|
|
&& ! Bugzilla->dbh->bz_in_transaction()
|
|
) {
|
|
Bugzilla->job_queue->insert('send_mail', { msg => $msg });
|
|
return;
|
|
}
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $email = ref($msg) ? $msg : Bugzilla::MIME->new($msg);
|
|
|
|
# If we're called from within a transaction, we don't want to send the
|
|
# email immediately, in case the transaction is rolled back. Instead we
|
|
# insert it into the mail_staging table, and bz_commit_transaction calls
|
|
# send_staged_mail() after the transaction is committed.
|
|
if (! $send_now && $dbh->bz_in_transaction()) {
|
|
# The e-mail string may contain tainted values.
|
|
my $string = $email->as_string;
|
|
trick_taint($string);
|
|
|
|
my $sth = $dbh->prepare("INSERT INTO mail_staging (message) VALUES (?)");
|
|
$sth->bind_param(1, $string, $dbh->BLOB_TYPE);
|
|
$sth->execute;
|
|
return;
|
|
}
|
|
|
|
my $from = $email->header('From');
|
|
|
|
my $hostname;
|
|
my $transport;
|
|
if ($method eq "Sendmail") {
|
|
if (ON_WINDOWS) {
|
|
$transport = Bugzilla::Sender::Transport::Sendmail->new({ sendmail => SENDMAIL_EXE });
|
|
}
|
|
else {
|
|
$transport = Bugzilla::Sender::Transport::Sendmail->new();
|
|
}
|
|
}
|
|
else {
|
|
# Sendmail will automatically append our hostname to the From
|
|
# address, but other mailers won't.
|
|
my $urlbase = Bugzilla->params->{'urlbase'};
|
|
$urlbase =~ m|//([^:/]+)[:/]?|;
|
|
$hostname = $1 || 'localhost';
|
|
$from .= "\@$hostname" if $from !~ /@/;
|
|
$email->header_set('From', $from);
|
|
|
|
# Sendmail adds a Date: header also, but others may not.
|
|
if (!defined $email->header('Date')) {
|
|
$email->header_set('Date', time2str("%a, %d %b %Y %T %z", time()));
|
|
}
|
|
}
|
|
|
|
if ($method eq "SMTP") {
|
|
my ($host, $port) = split(/:/, Bugzilla->params->{'smtpserver'}, 2);
|
|
$transport = Bugzilla->request_cache->{smtp} //=
|
|
Email::Sender::Transport::SMTP::Persistent->new({
|
|
host => $host,
|
|
defined($port) ? (port => $port) : (),
|
|
sasl_username => Bugzilla->params->{'smtp_username'},
|
|
sasl_password => Bugzilla->params->{'smtp_password'},
|
|
helo => $hostname,
|
|
ssl => Bugzilla->params->{'smtp_ssl'},
|
|
debug => Bugzilla->params->{'smtp_debug'} });
|
|
}
|
|
|
|
Bugzilla::Hook::process('mailer_before_send', { email => $email });
|
|
|
|
return if $email->header('to') eq '';
|
|
|
|
if ($method eq "Test") {
|
|
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
|
|
open TESTFILE, '>>', $filename;
|
|
# From - <date> is required to be a valid mbox file.
|
|
print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
|
|
close TESTFILE;
|
|
}
|
|
else {
|
|
# This is useful for Sendmail, so we put it out here.
|
|
local $ENV{PATH} = SENDMAIL_PATH;
|
|
eval { sendmail($email, { transport => $transport }) };
|
|
if ($@) {
|
|
ThrowCodeError('mail_send_error', { msg => $@->message, mail => $email });
|
|
}
|
|
}
|
|
}
|
|
|
|
# Builds header suitable for use as a threading marker in email notifications
|
|
sub build_thread_marker {
|
|
my ($bug_id, $user_id, $is_new) = @_;
|
|
|
|
if (!defined $user_id) {
|
|
$user_id = Bugzilla->user->id;
|
|
}
|
|
|
|
my $sitespec = '@' . Bugzilla->params->{'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 '@'
|
|
}
|
|
|
|
my $threadingmarker;
|
|
if ($is_new) {
|
|
$threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
|
|
}
|
|
else {
|
|
my $rand_bits = generate_random_password(10);
|
|
$threadingmarker = "Message-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" .
|
|
"\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
|
|
"\nReferences: <bug-$bug_id-$user_id$sitespec>";
|
|
}
|
|
|
|
return $threadingmarker;
|
|
}
|
|
|
|
sub send_staged_mail {
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $emails = $dbh->selectall_arrayref('SELECT id, message FROM mail_staging');
|
|
my $sth = $dbh->prepare('DELETE FROM mail_staging WHERE id = ?');
|
|
|
|
foreach my $email (@$emails) {
|
|
my ($id, $message) = @$email;
|
|
MessageToMTA($message);
|
|
$sth->execute($id);
|
|
}
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::Mailer - Provides methods for sending email
|
|
|
|
=head1 METHODS
|
|
|
|
=over
|
|
|
|
=item C<generate_email>
|
|
|
|
Generates a multi-part email message, using the supplied list of templates.
|
|
|
|
=item C<MessageToMTA>
|
|
|
|
Sends the passed message to the mail transfer agent.
|
|
|
|
The actual behaviour depends on a number of factors: if called from within a
|
|
database transaction, the message will be staged and sent when the transaction
|
|
is committed. If email queueing is enabled, the message will be sent to
|
|
TheSchwartz job queue where it will be processed by the jobqueue daemon, else
|
|
the message is sent immediately.
|
|
|
|
=item C<build_thread_marker>
|
|
|
|
Builds header suitable for use as a threading marker in email notifications.
|
|
|
|
=item C<send_staged_mail>
|
|
|
|
Sends all staged messages -- called after a database transaction is committed.
|
|
|
|
=back
|