aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'email_in.pl')
-rwxr-xr-xemail_in.pl766
1 files changed, 398 insertions, 368 deletions
diff --git a/email_in.pl b/email_in.pl
index a936791b7..a03935830 100755
--- a/email_in.pl
+++ b/email_in.pl
@@ -14,10 +14,11 @@ use warnings;
# run from this one so that it can find its modules.
use Cwd qw(abs_path);
use File::Basename qw(dirname);
+
BEGIN {
- # Untaint the abs_path.
- my ($a) = abs_path($0) =~ /^(.*)$/;
- chdir dirname($a);
+ # Untaint the abs_path.
+ my ($a) = abs_path($0) =~ /^(.*)$/;
+ chdir dirname($a);
}
use lib qw(. lib);
@@ -57,10 +58,10 @@ use constant SIGNATURE_DELIMITER => '-- ';
# These MIME types represent a "body" of an email if they have an
# "inline" Content-Disposition (or no content disposition).
use constant BODY_TYPES => qw(
- text/plain
- text/html
- application/xhtml+xml
- multipart/alternative
+ text/plain
+ text/html
+ application/xhtml+xml
+ multipart/alternative
);
# $input_email is a global so that it can be used in die_handler.
@@ -71,239 +72,249 @@ our ($input_email, %switch);
####################
sub parse_mail {
- my ($mail_text) = @_;
- debug_print('Parsing Email');
- $input_email = Email::MIME->new($mail_text);
-
- my %fields = %{ $switch{'default'} || {} };
- Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email,
- fields => \%fields });
-
- my $summary = $input_email->header('Subject');
- if ($summary =~ /\[\S+ (\d+)\](.*)/i) {
- $fields{'bug_id'} = $1;
- $summary = trim($2);
+ my ($mail_text) = @_;
+ debug_print('Parsing Email');
+ $input_email = Email::MIME->new($mail_text);
+
+ my %fields = %{$switch{'default'} || {}};
+ Bugzilla::Hook::process('email_in_before_parse',
+ {mail => $input_email, fields => \%fields});
+
+ my $summary = $input_email->header('Subject');
+ if ($summary =~ /\[\S+ (\d+)\](.*)/i) {
+ $fields{'bug_id'} = $1;
+ $summary = trim($2);
+ }
+
+ # Ignore automatic replies.
+ # XXX - Improve the way to detect such subjects in different languages.
+ my $auto_submitted = $input_email->header('Auto-Submitted') || '';
+ if ($summary =~ /out of( the)? office/i || $auto_submitted eq 'auto-replied') {
+ debug_print("Automatic reply detected: $summary");
+ exit;
+ }
+
+ my ($body, $attachments) = get_body_and_attachments($input_email);
+
+ debug_print("Body:\n" . $body, 3);
+
+ $body = remove_leading_blank_lines($body);
+ my @body_lines = split(/\r?\n/s, $body);
+
+ # If there are fields specified.
+ if ($body =~ /^\s*@/s) {
+ my $current_field;
+ while (my $line = shift @body_lines) {
+
+ # If the sig is starting, we want to keep this in the
+ # @body_lines so that we don't keep the sig as part of the
+ # comment down below.
+ if ($line eq SIGNATURE_DELIMITER) {
+ unshift(@body_lines, $line);
+ last;
+ }
+
+ # Otherwise, we stop parsing fields on the first blank line.
+ $line = trim($line);
+ last if !$line;
+ if ($line =~ /^\@(\w+)\s*(?:=|\s|$)\s*(.*)\s*/) {
+ $current_field = lc($1);
+ $fields{$current_field} = $2;
+ }
+ else {
+ $fields{$current_field} .= " $line";
+ }
}
-
- # Ignore automatic replies.
- # XXX - Improve the way to detect such subjects in different languages.
- my $auto_submitted = $input_email->header('Auto-Submitted') || '';
- if ($summary =~ /out of( the)? office/i || $auto_submitted eq 'auto-replied') {
- debug_print("Automatic reply detected: $summary");
- exit;
+ }
+
+ %fields = %{Bugzilla::Bug::map_fields(\%fields)};
+
+ my ($reporter) = Email::Address->parse($input_email->header('From'));
+ $fields{'reporter'} = $reporter->address;
+
+ # The summary line only affects us if we're doing a post_bug.
+ # We have to check it down here because there might have been
+ # a bug_id specified in the body of the email.
+ if (!$fields{'bug_id'} && !$fields{'short_desc'}) {
+ $fields{'short_desc'} = $summary;
+ }
+
+ # The Importance/X-Priority headers are only used when creating a new bug.
+ # 1) If somebody specifies a priority, use it.
+ # 2) If there is an Importance or X-Priority header, use it as
+ # something that is relative to the default priority.
+ # If the value is High or 1, increase the priority by 1.
+ # If the value is Low or 5, decrease the priority by 1.
+ # 3) Otherwise, use the default priority.
+ # Note: this will only work if the 'letsubmitterchoosepriority'
+ # parameter is enabled.
+ my $importance
+ = $input_email->header('Importance') || $input_email->header('X-Priority');
+ if (!$fields{'bug_id'} && !$fields{'priority'} && $importance) {
+ my @legal_priorities = @{get_legal_field_values('priority')};
+ my $i
+ = firstidx { $_ eq Bugzilla->params->{'defaultpriority'} } @legal_priorities;
+ if ($importance =~ /(high|[12])/i) {
+ $i-- unless $i == 0;
}
-
- my ($body, $attachments) = get_body_and_attachments($input_email);
-
- debug_print("Body:\n" . $body, 3);
-
- $body = remove_leading_blank_lines($body);
- my @body_lines = split(/\r?\n/s, $body);
-
- # If there are fields specified.
- if ($body =~ /^\s*@/s) {
- my $current_field;
- while (my $line = shift @body_lines) {
- # If the sig is starting, we want to keep this in the
- # @body_lines so that we don't keep the sig as part of the
- # comment down below.
- if ($line eq SIGNATURE_DELIMITER) {
- unshift(@body_lines, $line);
- last;
- }
- # Otherwise, we stop parsing fields on the first blank line.
- $line = trim($line);
- last if !$line;
- if ($line =~ /^\@(\w+)\s*(?:=|\s|$)\s*(.*)\s*/) {
- $current_field = lc($1);
- $fields{$current_field} = $2;
- }
- else {
- $fields{$current_field} .= " $line";
- }
- }
+ elsif ($importance =~ /(low|[45])/i) {
+ $i++ unless $i == $#legal_priorities;
}
+ $fields{'priority'} = $legal_priorities[$i];
+ }
- %fields = %{ Bugzilla::Bug::map_fields(\%fields) };
+ my $comment = '';
- my ($reporter) = Email::Address->parse($input_email->header('From'));
- $fields{'reporter'} = $reporter->address;
+ # Get the description, except the signature.
+ foreach my $line (@body_lines) {
+ last if $line eq SIGNATURE_DELIMITER;
+ $comment .= "$line\n";
+ }
+ $fields{'comment'} = $comment;
- # The summary line only affects us if we're doing a post_bug.
- # We have to check it down here because there might have been
- # a bug_id specified in the body of the email.
- if (!$fields{'bug_id'} && !$fields{'short_desc'}) {
- $fields{'short_desc'} = $summary;
- }
+ my %override = %{$switch{'override'} || {}};
+ foreach my $key (keys %override) {
+ $fields{$key} = $override{$key};
+ }
- # The Importance/X-Priority headers are only used when creating a new bug.
- # 1) If somebody specifies a priority, use it.
- # 2) If there is an Importance or X-Priority header, use it as
- # something that is relative to the default priority.
- # If the value is High or 1, increase the priority by 1.
- # If the value is Low or 5, decrease the priority by 1.
- # 3) Otherwise, use the default priority.
- # Note: this will only work if the 'letsubmitterchoosepriority'
- # parameter is enabled.
- my $importance = $input_email->header('Importance')
- || $input_email->header('X-Priority');
- if (!$fields{'bug_id'} && !$fields{'priority'} && $importance) {
- my @legal_priorities = @{get_legal_field_values('priority')};
- my $i = firstidx { $_ eq Bugzilla->params->{'defaultpriority'} } @legal_priorities;
- if ($importance =~ /(high|[12])/i) {
- $i-- unless $i == 0;
- }
- elsif ($importance =~ /(low|[45])/i) {
- $i++ unless $i == $#legal_priorities;
- }
- $fields{'priority'} = $legal_priorities[$i];
- }
+ debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
- my $comment = '';
- # Get the description, except the signature.
- foreach my $line (@body_lines) {
- last if $line eq SIGNATURE_DELIMITER;
- $comment .= "$line\n";
- }
- $fields{'comment'} = $comment;
+ debug_print("Attachments:\n" . Dumper($attachments), 3);
+ if (@$attachments) {
+ $fields{'attachments'} = $attachments;
+ }
- my %override = %{ $switch{'override'} || {} };
- foreach my $key (keys %override) {
- $fields{$key} = $override{$key};
- }
-
- debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
-
- debug_print("Attachments:\n" . Dumper($attachments), 3);
- if (@$attachments) {
- $fields{'attachments'} = $attachments;
- }
-
- return \%fields;
+ return \%fields;
}
sub check_email_fields {
- my ($fields) = @_;
-
- my ($retval, $non_conclusive_fields) =
- Bugzilla::User::match_field({
- 'assigned_to' => { 'type' => 'single' },
- 'qa_contact' => { 'type' => 'single' },
- 'cc' => { 'type' => 'multi' },
- 'newcc' => { 'type' => 'multi' }
- }, $fields, MATCH_SKIP_CONFIRM);
-
- if ($retval != USER_MATCH_SUCCESS) {
- ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
- }
+ my ($fields) = @_;
+
+ my ($retval, $non_conclusive_fields) = Bugzilla::User::match_field(
+ {
+ 'assigned_to' => {'type' => 'single'},
+ 'qa_contact' => {'type' => 'single'},
+ 'cc' => {'type' => 'multi'},
+ 'newcc' => {'type' => 'multi'}
+ },
+ $fields,
+ MATCH_SKIP_CONFIRM
+ );
+
+ if ($retval != USER_MATCH_SUCCESS) {
+ ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
+ }
}
sub post_bug {
- my ($fields) = @_;
- debug_print('Posting a new bug...');
+ my ($fields) = @_;
+ debug_print('Posting a new bug...');
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- check_email_fields($fields);
+ check_email_fields($fields);
- my $bug = Bugzilla::Bug->create($fields);
- debug_print("Created bug " . $bug->id);
- return ($bug, $bug->comments->[0]);
+ my $bug = Bugzilla::Bug->create($fields);
+ debug_print("Created bug " . $bug->id);
+ return ($bug, $bug->comments->[0]);
}
sub process_bug {
- my ($fields_in) = @_;
- my %fields = %$fields_in;
-
- my $bug_id = $fields{'bug_id'};
- $fields{'id'} = $bug_id;
- delete $fields{'bug_id'};
-
- debug_print("Updating Bug $fields{id}...");
-
- my $bug = Bugzilla::Bug->check($bug_id);
-
- if ($fields{'bug_status'}) {
- $fields{'knob'} = $fields{'bug_status'};
- }
- # If no status is given, then we only want to change the resolution.
- elsif ($fields{'resolution'}) {
- $fields{'knob'} = 'change_resolution';
- $fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
- }
- if ($fields{'dup_id'}) {
- $fields{'knob'} = 'duplicate';
- }
-
- # Move @cc to @newcc as @cc is used by process_bug.cgi to remove
- # users from the CC list when @removecc is set.
- $fields{'newcc'} = delete $fields{'cc'} if $fields{'cc'};
-
- # Make it possible to remove CCs.
- if ($fields{'removecc'}) {
- $fields{'cc'} = [split(',', $fields{'removecc'})];
- $fields{'removecc'} = 1;
- }
-
- check_email_fields(\%fields);
-
- my $cgi = Bugzilla->cgi;
- foreach my $field (keys %fields) {
- $cgi->param(-name => $field, -value => $fields{$field});
- }
- $cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
-
- require 'process_bug.cgi';
- debug_print("Bug processed.");
-
- my $added_comment;
- if (trim($fields{'comment'})) {
- # The "old" bug object doesn't contain the comment we just added.
- $added_comment = Bugzilla::Bug->check($bug_id)->comments->[-1];
- }
- return ($bug, $added_comment);
+ my ($fields_in) = @_;
+ my %fields = %$fields_in;
+
+ my $bug_id = $fields{'bug_id'};
+ $fields{'id'} = $bug_id;
+ delete $fields{'bug_id'};
+
+ debug_print("Updating Bug $fields{id}...");
+
+ my $bug = Bugzilla::Bug->check($bug_id);
+
+ if ($fields{'bug_status'}) {
+ $fields{'knob'} = $fields{'bug_status'};
+ }
+
+ # If no status is given, then we only want to change the resolution.
+ elsif ($fields{'resolution'}) {
+ $fields{'knob'} = 'change_resolution';
+ $fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
+ }
+ if ($fields{'dup_id'}) {
+ $fields{'knob'} = 'duplicate';
+ }
+
+ # Move @cc to @newcc as @cc is used by process_bug.cgi to remove
+ # users from the CC list when @removecc is set.
+ $fields{'newcc'} = delete $fields{'cc'} if $fields{'cc'};
+
+ # Make it possible to remove CCs.
+ if ($fields{'removecc'}) {
+ $fields{'cc'} = [split(',', $fields{'removecc'})];
+ $fields{'removecc'} = 1;
+ }
+
+ check_email_fields(\%fields);
+
+ my $cgi = Bugzilla->cgi;
+ foreach my $field (keys %fields) {
+ $cgi->param(-name => $field, -value => $fields{$field});
+ }
+ $cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
+
+ require 'process_bug.cgi';
+ debug_print("Bug processed.");
+
+ my $added_comment;
+ if (trim($fields{'comment'})) {
+
+ # The "old" bug object doesn't contain the comment we just added.
+ $added_comment = Bugzilla::Bug->check($bug_id)->comments->[-1];
+ }
+ return ($bug, $added_comment);
}
sub handle_attachments {
- my ($bug, $attachments, $comment) = @_;
- return if !$attachments;
- debug_print("Handling attachments...");
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- my ($update_comment, $update_bug);
- foreach my $attachment (@$attachments) {
- debug_print("Inserting Attachment: " . Dumper($attachment), 3);
- my $type = $attachment->content_type || 'application/octet-stream';
- # MUAs add stuff like "name=" to content-type that we really don't
- # want.
- $type =~ s/;.*//;
- my $obj = Bugzilla::Attachment->create({
- bug => $bug,
- description => $attachment->filename(1),
- filename => $attachment->filename(1),
- mimetype => $type,
- data => $attachment->body,
- });
- # If we added a comment, and our comment does not already have a type,
- # and this is our first attachment, then we make the comment an
- # "attachment created" comment.
- if ($comment and !$comment->type and !$update_comment) {
- $comment->set_all({ type => CMT_ATTACHMENT_CREATED,
- extra_data => $obj->id });
- $update_comment = 1;
- }
- else {
- $bug->add_comment('', { type => CMT_ATTACHMENT_CREATED,
- extra_data => $obj->id });
- $update_bug = 1;
- }
+ my ($bug, $attachments, $comment) = @_;
+ return if !$attachments;
+ debug_print("Handling attachments...");
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my ($update_comment, $update_bug);
+ foreach my $attachment (@$attachments) {
+ debug_print("Inserting Attachment: " . Dumper($attachment), 3);
+ my $type = $attachment->content_type || 'application/octet-stream';
+
+ # MUAs add stuff like "name=" to content-type that we really don't
+ # want.
+ $type =~ s/;.*//;
+ my $obj = Bugzilla::Attachment->create({
+ bug => $bug,
+ description => $attachment->filename(1),
+ filename => $attachment->filename(1),
+ mimetype => $type,
+ data => $attachment->body,
+ });
+
+ # If we added a comment, and our comment does not already have a type,
+ # and this is our first attachment, then we make the comment an
+ # "attachment created" comment.
+ if ($comment and !$comment->type and !$update_comment) {
+ $comment->set_all({type => CMT_ATTACHMENT_CREATED, extra_data => $obj->id});
+ $update_comment = 1;
+ }
+ else {
+ $bug->add_comment('', {type => CMT_ATTACHMENT_CREATED, extra_data => $obj->id});
+ $update_bug = 1;
}
- # We only update the comments and bugs at the end of the transaction,
- # because doing so modifies bugs_fulltext, which is a non-transactional
- # table.
- $bug->update() if $update_bug;
- $comment->update() if $update_comment;
- $dbh->bz_commit_transaction();
+ }
+
+ # We only update the comments and bugs at the end of the transaction,
+ # because doing so modifies bugs_fulltext, which is a non-transactional
+ # table.
+ $bug->update() if $update_bug;
+ $comment->update() if $update_comment;
+ $dbh->bz_commit_transaction();
}
######################
@@ -311,181 +322,199 @@ sub handle_attachments {
######################
sub debug_print {
- my ($str, $level) = @_;
- $level ||= 1;
- print STDERR "$str\n" if $level <= $switch{'verbose'};
+ my ($str, $level) = @_;
+ $level ||= 1;
+ print STDERR "$str\n" if $level <= $switch{'verbose'};
}
sub get_body_and_attachments {
- my ($email) = @_;
-
- my $ct = $email->content_type || 'text/plain';
- debug_print("Splitting Body and Attachments [Type: $ct]...", 2);
-
- my ($bodies, $attachments) = split_body_and_attachments($email);
- debug_print(scalar(@$bodies) . " body part(s) and " . scalar(@$attachments)
- . " attachment part(s).");
- debug_print('Bodies: ' . Dumper($bodies), 3);
-
- # Get the first part of the email that contains a text body,
- # and make all the other pieces into attachments. (This handles
- # people or MUAs who accidentally attach text files as an "inline"
- # attachment.)
- my $body;
- while (@$bodies) {
- my $possible = shift @$bodies;
- $body = get_text_alternative($possible);
- if (defined $body) {
- unshift(@$attachments, @$bodies);
- last;
- }
+ my ($email) = @_;
+
+ my $ct = $email->content_type || 'text/plain';
+ debug_print("Splitting Body and Attachments [Type: $ct]...", 2);
+
+ my ($bodies, $attachments) = split_body_and_attachments($email);
+ debug_print(
+ scalar(@$bodies)
+ . " body part(s) and "
+ . scalar(@$attachments)
+ . " attachment part(s).");
+ debug_print('Bodies: ' . Dumper($bodies), 3);
+
+ # Get the first part of the email that contains a text body,
+ # and make all the other pieces into attachments. (This handles
+ # people or MUAs who accidentally attach text files as an "inline"
+ # attachment.)
+ my $body;
+ while (@$bodies) {
+ my $possible = shift @$bodies;
+ $body = get_text_alternative($possible);
+ if (defined $body) {
+ unshift(@$attachments, @$bodies);
+ last;
}
+ }
- if (!defined $body) {
- # Note that this only happens if the email does not contain any
- # text/plain parts. If the email has an empty text/plain part,
- # you're fine, and this message does NOT get thrown.
- ThrowUserError('email_no_body');
- }
+ if (!defined $body) {
+
+ # Note that this only happens if the email does not contain any
+ # text/plain parts. If the email has an empty text/plain part,
+ # you're fine, and this message does NOT get thrown.
+ ThrowUserError('email_no_body');
+ }
+
+ debug_print("Picked Body:\n$body", 2);
- debug_print("Picked Body:\n$body", 2);
-
- return ($body, $attachments);
+ return ($body, $attachments);
}
sub get_text_alternative {
- my ($email) = @_;
-
- my @parts = $email->parts;
- my $body;
- foreach my $part (@parts) {
- my $ct = $part->content_type || 'text/plain';
- my $charset = 'iso-8859-1';
- # The charset may be quoted.
- if ($ct =~ /charset="?([^;"]+)/) {
- $charset= $1;
- }
- debug_print("Alternative Part Content-Type: $ct", 2);
- debug_print("Alternative Part Character Encoding: $charset", 2);
- # If we find a text/plain body here, return it immediately.
- if (!$ct || $ct =~ m{^text/plain}i) {
- return _decode_body($charset, $part->body);
- }
- # If we find a text/html body, decode it, but don't return
- # it immediately, because there might be a text/plain alternative
- # later. This could be any HTML type.
- if ($ct =~ m{^application/xhtml\+xml}i or $ct =~ m{text/html}i) {
- my $parser = HTML::FormatText::WithLinks->new(
- # Put footnnote indicators after the text, not before it.
- before_link => '',
- after_link => '[%n]',
- # Convert bold and italics, use "*" for bold instead of "_".
- with_emphasis => 1,
- bold_marker => '*',
- # If the same link appears multiple times, only create
- # one footnote.
- unique_links => 1,
- # If the link text is the URL, don't create a footnote.
- skip_linked_urls => 1,
- );
- $body = _decode_body($charset, $part->body);
- $body = $parser->parse($body);
- }
+ my ($email) = @_;
+
+ my @parts = $email->parts;
+ my $body;
+ foreach my $part (@parts) {
+ my $ct = $part->content_type || 'text/plain';
+ my $charset = 'iso-8859-1';
+
+ # The charset may be quoted.
+ if ($ct =~ /charset="?([^;"]+)/) {
+ $charset = $1;
+ }
+ debug_print("Alternative Part Content-Type: $ct", 2);
+ debug_print("Alternative Part Character Encoding: $charset", 2);
+
+ # If we find a text/plain body here, return it immediately.
+ if (!$ct || $ct =~ m{^text/plain}i) {
+ return _decode_body($charset, $part->body);
+ }
+
+ # If we find a text/html body, decode it, but don't return
+ # it immediately, because there might be a text/plain alternative
+ # later. This could be any HTML type.
+ if ($ct =~ m{^application/xhtml\+xml}i or $ct =~ m{text/html}i) {
+ my $parser = HTML::FormatText::WithLinks->new(
+
+ # Put footnnote indicators after the text, not before it.
+ before_link => '',
+ after_link => '[%n]',
+
+ # Convert bold and italics, use "*" for bold instead of "_".
+ with_emphasis => 1,
+ bold_marker => '*',
+
+ # If the same link appears multiple times, only create
+ # one footnote.
+ unique_links => 1,
+
+ # If the link text is the URL, don't create a footnote.
+ skip_linked_urls => 1,
+ );
+ $body = _decode_body($charset, $part->body);
+ $body = $parser->parse($body);
}
+ }
- return $body;
+ return $body;
}
sub _decode_body {
- my ($charset, $body) = @_;
- if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
- return Encode::decode($charset, $body);
- }
- return $body;
+ my ($charset, $body) = @_;
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
+ return Encode::decode($charset, $body);
+ }
+ return $body;
}
sub remove_leading_blank_lines {
- my ($text) = @_;
- $text =~ s/^(\s*\n)+//s;
- return $text;
+ my ($text) = @_;
+ $text =~ s/^(\s*\n)+//s;
+ return $text;
}
sub html_strip {
- my ($var) = @_;
- # Trivial HTML tag remover (this is just for error messages, really.)
- $var =~ s/<[^>]*>//g;
- # And this basically reverses the Template-Toolkit html filter.
- $var =~ s/\&amp;/\&/g;
- $var =~ s/\&lt;/</g;
- $var =~ s/\&gt;/>/g;
- $var =~ s/\&quot;/\"/g;
- $var =~ s/&#64;/@/g;
- # Also remove undesired newlines and consecutive spaces.
- $var =~ s/[\n\s]+/ /gms;
- return $var;
+ my ($var) = @_;
+
+ # Trivial HTML tag remover (this is just for error messages, really.)
+ $var =~ s/<[^>]*>//g;
+
+ # And this basically reverses the Template-Toolkit html filter.
+ $var =~ s/\&amp;/\&/g;
+ $var =~ s/\&lt;/</g;
+ $var =~ s/\&gt;/>/g;
+ $var =~ s/\&quot;/\"/g;
+ $var =~ s/&#64;/@/g;
+
+ # Also remove undesired newlines and consecutive spaces.
+ $var =~ s/[\n\s]+/ /gms;
+ return $var;
}
sub split_body_and_attachments {
- my ($email) = @_;
-
- my (@body, @attachments);
- foreach my $part ($email->parts) {
- my $ct = lc($part->content_type || 'text/plain');
- my $disposition = lc($part->header('Content-Disposition') || 'inline');
- # Remove the charset, etc. from the content-type, we don't care here.
- $ct =~ s/;.*//;
- debug_print("Part Content-Type: [$ct]", 2);
- debug_print("Part Disposition: [$disposition]", 2);
-
- if ($disposition eq 'inline' and grep($_ eq $ct, BODY_TYPES)) {
- push(@body, $part);
- next;
- }
-
- if (scalar($part->parts) == 1) {
- push(@attachments, $part);
- next;
- }
-
- # If this part has sub-parts, analyze them similarly to how we
- # did above and return the relevant pieces.
- my ($add_body, $add_attachments) = split_body_and_attachments($part);
- push(@body, @$add_body);
- push(@attachments, @$add_attachments);
+ my ($email) = @_;
+
+ my (@body, @attachments);
+ foreach my $part ($email->parts) {
+ my $ct = lc($part->content_type || 'text/plain');
+ my $disposition = lc($part->header('Content-Disposition') || 'inline');
+
+ # Remove the charset, etc. from the content-type, we don't care here.
+ $ct =~ s/;.*//;
+ debug_print("Part Content-Type: [$ct]", 2);
+ debug_print("Part Disposition: [$disposition]", 2);
+
+ if ($disposition eq 'inline' and grep($_ eq $ct, BODY_TYPES)) {
+ push(@body, $part);
+ next;
}
- return (\@body, \@attachments);
+ if (scalar($part->parts) == 1) {
+ push(@attachments, $part);
+ next;
+ }
+
+ # If this part has sub-parts, analyze them similarly to how we
+ # did above and return the relevant pieces.
+ my ($add_body, $add_attachments) = split_body_and_attachments($part);
+ push(@body, @$add_body);
+ push(@attachments, @$add_attachments);
+ }
+
+ return (\@body, \@attachments);
}
sub die_handler {
- my ($msg) = @_;
-
- # In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
- # But of course, we really don't want to actually *die* just because
- # the user-error or code-error template ended. So we don't really die.
- return if blessed($msg) && $msg->isa('Template::Exception')
- && $msg->type eq 'return';
-
- # If this is inside an eval, then we should just act like...we're
- # in an eval (instead of printing the error and exiting).
- die @_ if ($^S // Bugzilla::Error::_in_eval());
-
- # We can't depend on the MTA to send an error message, so we have
- # to generate one properly.
- if ($input_email) {
- $msg =~ s/at .+ line.*$//ms;
- $msg =~ s/^Compilation failed in require.+$//ms;
- $msg = html_strip($msg);
- my $from = Bugzilla->params->{'mailfrom'};
- my $reply = reply(to => $input_email, from => $from, top_post => 1,
- body => "$msg\n");
- MessageToMTA($reply->as_string);
- }
- print STDERR "$msg\n";
- # We exit with a successful value, because we don't want the MTA
- # to *also* send a failure notice.
- exit;
+ my ($msg) = @_;
+
+ # In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
+ # But of course, we really don't want to actually *die* just because
+ # the user-error or code-error template ended. So we don't really die.
+ return
+ if blessed($msg)
+ && $msg->isa('Template::Exception')
+ && $msg->type eq 'return';
+
+ # If this is inside an eval, then we should just act like...we're
+ # in an eval (instead of printing the error and exiting).
+ die @_ if ($^S // Bugzilla::Error::_in_eval());
+
+ # We can't depend on the MTA to send an error message, so we have
+ # to generate one properly.
+ if ($input_email) {
+ $msg =~ s/at .+ line.*$//ms;
+ $msg =~ s/^Compilation failed in require.+$//ms;
+ $msg = html_strip($msg);
+ my $from = Bugzilla->params->{'mailfrom'};
+ my $reply
+ = reply(to => $input_email, from => $from, top_post => 1, body => "$msg\n");
+ MessageToMTA($reply->as_string);
+ }
+ print STDERR "$msg\n";
+
+ # We exit with a successful value, because we don't want the MTA
+ # to *also* send a failure notice.
+ exit;
}
###############
@@ -502,18 +531,19 @@ pod2usage({-verbose => 0, -exitval => 1}) if $switch{'help'};
Bugzilla->usage_mode(USAGE_MODE_EMAIL);
-my @mail_lines = <STDIN>;
-my $mail_text = join("", @mail_lines);
+my @mail_lines = <STDIN>;
+my $mail_text = join("", @mail_lines);
my $mail_fields = parse_mail($mail_text);
-Bugzilla::Hook::process('email_in_after_parse', { fields => $mail_fields });
+Bugzilla::Hook::process('email_in_after_parse', {fields => $mail_fields});
my $attachments = delete $mail_fields->{'attachments'};
my $username = $mail_fields->{'reporter'};
+
# If emailsuffix is in use, we have to remove it from the email address.
if (my $suffix = Bugzilla->params->{'emailsuffix'}) {
- $username =~ s/\Q$suffix\E$//i;
+ $username =~ s/\Q$suffix\E$//i;
}
my $user = Bugzilla::User->check($username);
@@ -521,10 +551,10 @@ Bugzilla->set_user($user);
my ($bug, $comment);
if ($mail_fields->{'bug_id'}) {
- ($bug, $comment) = process_bug($mail_fields);
+ ($bug, $comment) = process_bug($mail_fields);
}
else {
- ($bug, $comment) = post_bug($mail_fields);
+ ($bug, $comment) = post_bug($mail_fields);
}
handle_attachments($bug, $attachments, $comment);
@@ -536,7 +566,7 @@ handle_attachments($bug, $attachments, $comment);
# to wait for $bug->update() to be fully used in email_in.pl first. So
# currently, process_bug.cgi does the mail sending for bugs, and this does
# any mail sending for attachments after the first one.
-Bugzilla::BugMail::Send($bug->id, { changer => Bugzilla->user });
+Bugzilla::BugMail::Send($bug->id, {changer => Bugzilla->user});
debug_print("Sent bugmail");