Mailer.pm 7.34 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>,
#                 Bryce Nesbitt <bryce-mozilla@nextbus.com>
#                 Dan Mosedale <dmose@mozilla.org>
#                 Alan Raetz <al_raetz@yahoo.com>
#                 Jacob Steenhagen <jake@actex.net>
#                 Matthew Tuck <matty@chariot.net.au>
#                 Bradley Baetz <bbaetz@student.usyd.edu.au>
#                 J. Paul Reed <preed@sigkill.com>
#                 Gervase Markham <gerv@gerv.net>
#                 Byron Jones <bugzilla@glob.com.au>
#                 Frédéric Buclin <LpSolit@gmail.com>
31
#                 Max Kanat-Alexander <mkanat@bugzilla.org>
32 33 34 35 36 37

package Bugzilla::Mailer;

use strict;

use base qw(Exporter);
38
@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker);
39 40

use Bugzilla::Constants;
41
use Bugzilla::Error;
42
use Bugzilla::Hook;
43 44
use Bugzilla::Util;

45 46
use Date::Format qw(time2str);

47
use Encode qw(encode);
48
use Encode::MIME::Header;
49
use Email::Address;
50 51 52 53
use Email::MIME;
# Loading this gives us encoding_set.
use Email::MIME::Modifier;
use Email::Send;
54 55 56

sub MessageToMTA {
    my ($msg) = (@_);
57 58
    my $method = Bugzilla->params->{'mail_delivery_method'};
    return if $method eq 'None';
59

60
    my $email = ref($msg) ? $msg : Email::MIME->new($msg);
61 62 63 64 65 66 67 68 69 70 71

    # We add this header to uniquely identify all email that we
    # send as coming from this Bugzilla installation.
    #
    # We don't use correct_urlbase, because we want this URL to
    # *always* be the same for this Bugzilla, in every email,
    # and some emails we send when we're logged out (in which case
    # some emails might get urlbase while the logged-in emails might 
    # get sslbase). Also, we want this to stay the same even if
    # the admin changes the "ssl" parameter.
    $email->header_set('X-Bugzilla-URL', Bugzilla->params->{'urlbase'});
72 73 74 75
    
    # We add this header to mark the mail as "auto-generated" and
    # thus to hopefully avoid auto replies.
    $email->header_set('Auto-Submitted', 'auto-generated');
76

77 78 79 80 81 82 83 84 85 86 87 88 89 90
    $email->walk_parts(sub {
        my ($part) = @_;
        return if $part->parts > 1; # Top-level
        my $content_type = $part->content_type || '';
        if ($content_type !~ /;/) {
            my $body = $part->body;
            if (Bugzilla->params->{'utf8'}) {
                $part->charset_set('UTF-8');
                # encoding_set works only with bytes, not with utf8 strings.
                my $raw = $part->body_raw;
                if (utf8::is_utf8($raw)) {
                    utf8::encode($raw);
                    $part->body_set($raw);
                }
91
            }
92
            $part->encoding_set('quoted-printable') if !is_7bit_clean($body);
93
        }
94
    });
95

96 97 98
    # MIME-Version must be set otherwise some mailsystems ignore the charset
    $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');

99
    # Encode the headers correctly in quoted-printable
100 101 102 103 104
    foreach my $header ($email->header_names) {
        my @values = $email->header($header);
        # We don't recode headers that happen multiple times.
        next if scalar(@values) > 1;
        if (my $value = $values[0]) {
105
            if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
106
                utf8::decode($value);
107
            }
108 109 110 111

            # avoid excessive line wrapping done by Encode.
            local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;

112 113
            my $encoded = encode('MIME-Q', $value);
            $email->header_set($header, $encoded);
114 115 116
        }
    }

117 118 119 120 121 122
    my $from = $email->header('From');

    my ($hostname, @args);
    if ($method eq "Sendmail") {
        if (ON_WINDOWS) {
            $Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
123
        }
124
        push @args, "-i";
125 126 127 128 129 130 131 132
        # We want to make sure that we pass *only* an email address.
        if ($from) {
            my ($email_obj) = Email::Address->parse($from);
            if ($email_obj) {
                my $from_email = $email_obj->address;
                push(@args, "-f$from_email") if $from_email;
            }
        }
133 134
        push(@args, "-ODeliveryMode=deferred")
            if !Bugzilla->params->{"sendmailnow"};
135
    }
136 137 138 139
    else {
        # Sendmail will automatically append our hostname to the From
        # address, but other mailers won't.
        my $urlbase = Bugzilla->params->{'urlbase'};
140
        $urlbase =~ m|//([^:/]+)[:/]?|;
141 142 143
        $hostname = $1;
        $from .= "\@$hostname" if $from !~ /@/;
        $email->header_set('From', $from);
144 145 146 147 148
        
        # Sendmail adds a Date: header also, but others may not.
        if (!defined $email->header('Date')) {
            $email->header_set('Date', time2str("%a, %e %b %Y %T %z", time()));
        }
149 150
    }

151 152
    if ($method eq "SMTP") {
        push @args, Host  => Bugzilla->params->{"smtpserver"},
153 154
                    username => Bugzilla->params->{"smtp_username"},
                    password => Bugzilla->params->{"smtp_password"},
155 156
                    Hello => $hostname, 
                    Debug => Bugzilla->params->{'smtp_debug'};
157 158
    }

159 160
    Bugzilla::Hook::process('mailer-before_send', { email => $email });

161 162 163
    if ($method eq "Test") {
        my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
        open TESTFILE, '>>', $filename;
164 165
        # From - <date> is required to be a valid mbox file.
        print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
166
        close TESTFILE;
167 168
    }
    else {
169 170 171 172 173 174 175
        # This is useful for both Sendmail and Qmail, so we put it out here.
        local $ENV{PATH} = SENDMAIL_PATH;
        my $mailer = Email::Send->new({ mailer => $method, 
                                        mailer_args => \@args });
        my $retval = $mailer->send($email);
        ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
            if !$retval;
176 177 178
    }
}

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
# 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 {
        $threadingmarker = "In-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
                           "\nReferences: <bug-$bug_id-$user_id$sitespec>";
    }

    return $threadingmarker;
}

206
1;