svn commit: r261008 - in user/gonzo/bugzilla-freebsd: . AutoAssigner
Oleksandr Tymoshenko
gonzo at FreeBSD.org
Wed Jan 22 08:33:34 UTC 2014
Author: gonzo
Date: Wed Jan 22 08:33:32 2014
New Revision: 261008
URL: http://svnweb.freebsd.org/changeset/base/261008
Log:
Import bugzilla/freebsd integration stuff
Added:
user/gonzo/bugzilla-freebsd/
user/gonzo/bugzilla-freebsd/AutoAssigner/
user/gonzo/bugzilla-freebsd/AutoAssigner/Config.pm (contents, props changed)
user/gonzo/bugzilla-freebsd/AutoAssigner/Extension.pm (contents, props changed)
user/gonzo/bugzilla-freebsd/README
user/gonzo/bugzilla-freebsd/gnats_in.pl (contents, props changed)
user/gonzo/bugzilla-freebsd/import_ports_index.pl (contents, props changed)
user/gonzo/bugzilla-freebsd/svn_in.pl (contents, props changed)
Added: user/gonzo/bugzilla-freebsd/AutoAssigner/Config.pm
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/gonzo/bugzilla-freebsd/AutoAssigner/Config.pm Wed Jan 22 08:33:32 2014 (r261008)
@@ -0,0 +1,12 @@
+package Bugzilla::Extension::AutoAssigner;
+use strict;
+use constant NAME => 'AutoAssigner';
+use constant REQUIRED_MODULES => [
+ {
+ package => 'Data-Dumper',
+ module => 'Data::Dumper',
+ version => 0,
+ },
+];
+
+__PACKAGE__->NAME;
Added: user/gonzo/bugzilla-freebsd/AutoAssigner/Extension.pm
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/gonzo/bugzilla-freebsd/AutoAssigner/Extension.pm Wed Jan 22 08:33:32 2014 (r261008)
@@ -0,0 +1,66 @@
+package Bugzilla::Extension::AutoAssigner;
+
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::User;
+use Bugzilla::User::Setting;
+use Bugzilla::Util qw(diff_arrays html_quote);
+use Bugzilla::Status qw(is_open_state);
+use Bugzilla::Install::Filesystem;
+
+use constant REL_EXAMPLE => -127;
+
+our $VERSION = '1.0';
+
+use Data::Dumper;
+
+sub bug_end_of_create {
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $bug = $args->{'bug'};
+ my $timestamp = $args->{'timestamp'};
+
+ my $bug_id = $bug->id;
+ my $subject = $bug->short_desc;
+ my $component = $bug->component;
+ if ($component eq 'ports') {
+ if ($subject =~ /^\s*(\S+\/\S+)\s*:/) {
+ my $port = $1;
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("SELECT maintainer FROM freebsd_ports_index where port=?");
+ $sth->execute($port);
+ my ($maintainer) = $sth->fetchrow_array();
+ return unless(defined($maintainer));
+ eval {
+ my $user = Bugzilla::User->check($maintainer);
+ };
+ return if ($@ ne '');
+ $bug->add_cc($maintainer);
+ $bug->update();
+ }
+ }
+}
+
+sub db_schema_abstract_schema {
+ my ($class, $args) = @_;
+ my $schema = $args->{schema};
+
+ $schema->{freebsd_ports_index} = {
+ FIELDS => [
+ port => {TYPE => 'varchar(255)', NOTNULL => 1},
+ maintainer => {TYPE => 'varchar(255)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ freebsd_ports_index_port_id => { FIELDS => ['port'], TYPE => 'UNIQUE' },
+ ],
+ };
+}
+
+# This must be the last line of your extension.
+__PACKAGE__->NAME;
Added: user/gonzo/bugzilla-freebsd/README
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/gonzo/bugzilla-freebsd/README Wed Jan 22 08:33:32 2014 (r261008)
@@ -0,0 +1,10 @@
+AutoAssigner - add port maintainer to Cc for newly created bugs in
+ ports category. Summary of the bug should have "category/port: "
+ prefix. Extension creates table for INDEX file content. File
+ itself is imported by import_ports_index.pl
+
+gnats_in.pl - script that parses emails generated by submit-pr and
+ creates new bug in Bugzilla.
+
+subversion_in.pl - Very basic dfilter-like script. Gets sendmail
+ commit email on input, parses and posts comment to bug mentioned
Added: user/gonzo/bugzilla-freebsd/gnats_in.pl
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/gonzo/bugzilla-freebsd/gnats_in.pl Wed Jan 22 08:33:32 2014 (r261008)
@@ -0,0 +1,454 @@
+#!/usr/local/bin/perl -w
+# -*- 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 Inbound Email System.
+#
+# The Initial Developer of the Original Code is Akamai Technologies, Inc.
+# Portions created by Akamai are Copyright (C) 2006 Akamai Technologies,
+# Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat at bugzilla.org>
+
+use strict;
+use warnings;
+
+# MTAs may call this script from any directory, but it should always
+# 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);
+}
+
+use lib qw(/usr/local/www/bugs42.freebsd.org/lib/ /usr/local/www/bugs42.freebsd.org/);
+
+use Data::Dumper;
+use Email::Address;
+use Email::Reply qw(reply);
+use Email::MIME;
+use Email::MIME::Attachment::Stripper;
+use Getopt::Long qw(:config bundling);
+use Pod::Usage;
+use Encode;
+use Scalar::Util qw(blessed);
+
+use Bugzilla;
+use Bugzilla::Attachment;
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+use Bugzilla::Token;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Hook;
+
+#############
+# Constants #
+#############
+
+
+our %fields_defaults = (
+ op_sys => 'FreeBSD',
+ rep_platform => 'old_All',
+ product => 'FreeBSD',
+ version => 'unspecified',
+);
+
+our %priority_map = (
+ medium => 'normal'
+);
+
+our %severity_map = (
+ 'non-critical' => 'normal',
+ critical => 'old critical',
+ serious => 'normal'
+);
+
+# $input_email is a global so that it can be used in die_handler.
+our ($input_email, %switch);
+
+####################
+# Main Subroutines #
+####################
+
+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 $body = get_body($input_email);
+
+ debug_print("Body:\n" . $body, 3);
+
+ my @body_lines = split(/\r?\n/s, $body);
+
+ # If there are fields specified.
+ my %gnats_fields;
+ my $current_field;
+ foreach my $line (@body_lines) {
+ if ($line =~ /^>([\w-]+):\s*(.*)\s*/) {
+ $current_field = lc($1);
+ $gnats_fields{$current_field} = $2;
+ }
+ else {
+ $gnats_fields{$current_field} .= "\n$line";
+ }
+ }
+
+ foreach my $k (keys %gnats_fields) {
+ $gnats_fields{$k} = trim ($gnats_fields{$k});
+ }
+
+ debug_print("GNATS Fields:\n" . Dumper(\%gnats_fields), 2);
+ $fields{priority} = $priority_map{$gnats_fields{priority}} || $gnats_fields{priority};
+ $fields{severity} = $severity_map{$gnats_fields{severity}} || $gnats_fields{severity};
+ $fields{component} = $gnats_fields{category};
+ # $fields{cf_type} = $gnats_fields{class};
+
+ %fields = %{ Bugzilla::Bug::map_fields(\%fields) };
+
+ my ($reporter) = Email::Address->parse($input_email->header('From'));
+ $fields{'reporter'} = $reporter->address;
+ # $fields{'cf_originator_email'} = $reporter->address;
+ $fields{'reporter'} = 'gonzo at freebsd.org';
+
+ # Default values
+ foreach my $k (keys %fields_defaults) {
+ $fields{$k} = $fields_defaults{$k};
+ }
+
+ my $subject = $input_email->header('Subject');
+ $fields{'short_desc'} = $gnats_fields{'synopsis'} || $subject;
+
+ my $comment = '';
+
+
+ if (defined($gnats_fields{'description'}) && $gnats_fields{'description'} =~ /\S/s) {
+ $comment .= $gnats_fields{'description'};
+ }
+
+ if (defined($gnats_fields{'environment'}) && $gnats_fields{'environment'} =~ /\S/s) {
+ my $envstr = $gnats_fields{'environment'};
+ if ($envstr =~ /(?:FreeBSD )?([0-9]+\.[0-9\.]+-((PRE)?RELEASE|BETA[0-9]|CURRENT|STABLE))(-p\d+)?( [a-z0-9]+)?/) {
+ my $version_name = $1;
+ my $product = Bugzilla::Product->check($fields_defaults{product});
+ eval {
+ my $version_obj = Bugzilla::Version->check({ product => $product,
+ name => $version_name });
+ };
+ if ($@ eq '') {
+ $fields{'version'} = $version_name;
+ }
+ }
+ $comment .= "\n\nEnvironment:\n";
+ $comment .= $gnats_fields{'environment'};
+ }
+
+ if (defined($gnats_fields{'how-to-repeat'}) && $gnats_fields{'how-to-repeat'} =~ /\S/s) {
+ $comment .= "\n\nHow-To-Repeat:\n";
+ $comment .= $gnats_fields{'how-to-repeat'};
+ }
+
+ if (defined($gnats_fields{'fix'})) {
+ my $attachments;
+ my $fix = $gnats_fields{'fix'};
+ ($fix, $attachments) = parse_fix($fix);
+ if ($fix =~ /\S/s) {
+ $comment .= "\n\nFix:\n";
+ $comment .= $fix;
+ }
+
+ $fields{'attachments'} = $attachments if (@$attachments);
+ }
+
+ $fields{'comment'} = $comment;
+
+ debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
+
+ return \%fields;
+}
+
+sub post_bug {
+ my ($fields) = @_;
+ debug_print('Posting a new bug...');
+
+ my $user = Bugzilla->user;
+
+ my ($retval, $non_conclusive_fields) =
+ Bugzilla::User::match_field({
+ 'assigned_to' => { 'type' => 'single' },
+ 'qa_contact' => { 'type' => 'single' },
+ 'cc' => { 'type' => 'multi' }
+ }, $fields, MATCH_SKIP_CONFIRM);
+
+ if ($retval != USER_MATCH_SUCCESS) {
+ ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
+ }
+
+ my $bug = Bugzilla::Bug->create($fields);
+ debug_print("Created bug " . $bug->id);
+ return ($bug, $bug->comments->[0]);
+}
+
+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) {
+ my $data = delete $attachment->{payload};
+ debug_print("Inserting Attachment: " . Dumper($attachment), 2);
+ $attachment->{content_type} ||= 'application/octet-stream';
+ my $ispatch = 0;
+ $ispatch = 1 if ($attachment->{filename} =~ /(diff|patch)$/);
+ my $obj = Bugzilla::Attachment->create({
+ bug => $bug,
+ description => $attachment->{filename},
+ filename => $attachment->{filename},
+ mimetype => $attachment->{content_type},
+ ispatch => $ispatch,
+ data => $data,
+ });
+ # 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();
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub debug_print {
+ my ($str, $level) = @_;
+ $level ||= 1;
+ print STDERR "$str\n" if $level <= $switch{'verbose'};
+}
+
+sub get_body {
+ my ($email) = @_;
+
+ my $ct = $email->content_type || 'text/plain';
+ debug_print("Splitting Body and Attachments [Type: $ct]...");
+
+ my $body;
+ if ($ct =~ /^multipart\/(alternative|signed)/i) {
+ $body = get_text_alternative($email);
+ }
+ else {
+ my $stripper = new Email::MIME::Attachment::Stripper(
+ $email, force_filename => 1);
+ my $message = $stripper->message;
+ $body = get_text_alternative($message);
+ }
+
+ return $body;
+}
+
+sub parse_fix {
+ my ($fix) = @_;
+ my $stripped_fix = '';
+ my $attachments = [];
+ my $attachment;
+ my @fix_lines = split(/\r?\n/s, $fix);
+ foreach my $line (@fix_lines) {
+ if ($line =~ /^\s*--{1,8}\s?([A-Za-z0-9-_.,:%]+) (begins|starts) here\s?--+\s*/mi) {
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = {
+ 'payload' => '',
+ 'filename' => $1,
+ 'content_type' => 'text/plain',
+ };
+ }
+ elsif ($line =~ /^\s*--{1,8}\s?([A-Za-z0-9-_.,:%]+) ends here\s?--+\s*\n/mi) {
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = undef;
+ }
+ elsif ($line =~ /^# This is a shell archive/) {
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = {
+ 'payload' => "$line\n",
+ 'filename' => 'file.shar',
+ 'content_type' => 'application/x-shar',
+ };
+ }
+ elsif (($line =~ /^exit$/) && defined($attachment) && ($attachment->{content_type} =~/x-shar/)) {
+ $attachment->{'payload'} .= "$line\n";
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = undef;
+ }
+
+ elsif (defined($attachment)) {
+ $attachment->{'payload'} .= "$line\n";
+ }
+ else {
+ $stripped_fix .= "$line\n";
+ }
+ }
+ push @$attachments, $attachment if (defined($attachment));
+ return ($stripped_fix, $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("Part Content-Type: $ct", 2);
+ debug_print("Part Character Encoding: $charset", 2);
+ if (!$ct || $ct =~ /^text\/plain/i) {
+ $body = $part->body;
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
+ $body = Encode::decode($charset, $body);
+ }
+ 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_text_plain');
+ }
+
+ return $body;
+}
+
+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/\&/\&/g;
+ $var =~ s/\</</g;
+ $var =~ s/\>/>/g;
+ $var =~ s/\"/\"/g;
+ $var =~ s/@/@/g;
+ # Also remove undesired newlines and consecutive spaces.
+ $var =~ s/[\n\s]+/ /gms;
+ return $var;
+}
+
+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;
+
+ # 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;
+}
+
+###############
+# Main Script #
+###############
+
+$SIG{__DIE__} = \&die_handler;
+
+GetOptions(\%switch, 'help|h', 'verbose|v+', 'default=s%', 'override=s%');
+$switch{'verbose'} ||= 0;
+
+# Print the help message if that switch was selected.
+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_fields = parse_mail($mail_text);
+
+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;
+}
+
+my $user = Bugzilla::User->check($username);
+Bugzilla->set_user($user);
+
+my ($bug, $comment);
+($bug, $comment) = post_bug($mail_fields);
+
+handle_attachments($bug, $attachments, $comment);
+
+# This is here for post_bug and handle_attachments, so that when posting a bug
+# with an attachment, any comment goes out as an attachment comment.
+#
+# Eventually this should be sending the mail for process_bug, too, but we have
+# 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 });
+debug_print("Sent bugmail");
+print "--> " . $bug->id . "\n";
+
+
+__END__
Added: user/gonzo/bugzilla-freebsd/import_ports_index.pl
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/gonzo/bugzilla-freebsd/import_ports_index.pl Wed Jan 22 08:33:32 2014 (r261008)
@@ -0,0 +1,29 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+use warnings;
+
+use lib qw(/usr/local/www/bugs42.freebsd.org/lib/ /usr/local/www/bugs42.freebsd.org/);
+use Bugzilla;
+
+open F, "< /var/ports/INDEX-9";
+
+my %maintainers;
+
+while(<F>) {
+ chomp;
+ my @fields = split /\|/;
+ my $port = $fields[1];
+ $port =~ s@/usr/ports/@@;
+ my $maintainer = $fields[5];
+ $maintainers{$port} = $maintainer;
+}
+
+my $dbh = Bugzilla->dbh;
+$dbh->bz_start_transaction();
+$dbh->do("DELETE from freebsd_ports_index");
+my $sth = $dbh->prepare("insert into freebsd_ports_index values (?, ?)");
+foreach my $k (keys %maintainers) {
+ $sth->execute($k, $maintainers{$k});
+}
+$dbh->bz_commit_transaction();
Added: user/gonzo/bugzilla-freebsd/svn_in.pl
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/gonzo/bugzilla-freebsd/svn_in.pl Wed Jan 22 08:33:32 2014 (r261008)
@@ -0,0 +1,284 @@
+#!/usr/local/bin/perl -w
+# -*- 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 Inbound Email System.
+#
+# The Initial Developer of the Original Code is Akamai Technologies, Inc.
+# Portions created by Akamai are Copyright (C) 2006 Akamai Technologies,
+# Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat at bugzilla.org>
+
+use strict;
+use warnings;
+
+# MTAs may call this script from any directory, but it should always
+# 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);
+}
+
+use lib qw(/usr/local/www/bugs42.freebsd.org/lib/ /usr/local/www/bugs42.freebsd.org/);
+
+use Data::Dumper;
+use Email::Address;
+use Email::Reply qw(reply);
+use Email::MIME;
+use Email::MIME::Attachment::Stripper;
+use Getopt::Long qw(:config bundling);
+use Pod::Usage;
+use Encode;
+use Scalar::Util qw(blessed);
+
+use Bugzilla;
+use Bugzilla::Attachment;
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+use Bugzilla::Token;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Hook;
+
+# $input_email is a global so that it can be used in die_handler.
+our ($input_email, %switch);
+
+####################
+# Main Subroutines #
+####################
+
+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 $body = get_body($input_email);
+
+ # debug_print("Body:\n" . $body, 3);
+
+ my @body_lines = split(/\r?\n/s, $body);
+
+ my @comment_lines = ();
+ foreach my $l (@body_lines) {
+ last if ($l =~ /^Modified:.*\/.*/);
+ push @comment_lines, $l;
+ }
+ my $comment = join "\n", @comment_lines;
+ $fields{'comment'} = $comment;
+ if ($comment =~ /\n\s*PR:\s*(?:\w+\/)?(\d+)/) {
+ $fields{'bug_id'} = $1;
+ }
+
+ debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
+
+ return \%fields;
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub debug_print {
+ my ($str, $level) = @_;
+ $level ||= 1;
+ print STDERR "$str\n" if $level <= $switch{'verbose'};
+}
+
+sub get_body {
+ my ($email) = @_;
+
+ my $ct = $email->content_type || 'text/plain';
+ debug_print("Splitting Body and Attachments [Type: $ct]...");
+
+ my $body;
+ if ($ct =~ /^multipart\/(alternative|signed)/i) {
+ $body = get_text_alternative($email);
+ }
+ else {
+ my $stripper = new Email::MIME::Attachment::Stripper(
+ $email, force_filename => 1);
+ my $message = $stripper->message;
+ $body = get_text_alternative($message);
+ }
+
+ return $body;
+}
+
+sub parse_fix {
+ my ($fix) = @_;
+ my $stripped_fix = '';
+ my $attachments = [];
+ my $attachment;
+ my @fix_lines = split(/\r?\n/s, $fix);
+ foreach my $line (@fix_lines) {
+ if ($line =~ /^\s*--{1,8}\s?([A-Za-z0-9-_.,:%]+) (begins|starts) here\s?--+\s*/mi) {
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = {
+ 'payload' => '',
+ 'filename' => $1,
+ 'content_type' => 'text/plain',
+ };
+ }
+ elsif ($line =~ /^\s*--{1,8}\s?([A-Za-z0-9-_.,:%]+) ends here\s?--+\s*\n/mi) {
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = undef;
+ }
+ elsif ($line =~ /^# This is a shell archive/) {
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = {
+ 'payload' => "$line\n",
+ 'filename' => 'file.shar',
+ 'content_type' => 'application/x-shar',
+ };
+ }
+ elsif (($line =~ /^exit$/) && defined($attachment) && ($attachment->{content_type} =~/x-shar/)) {
+ $attachment->{'payload'} .= "$line\n";
+ push @$attachments, $attachment if (defined($attachment));
+ $attachment = undef;
+ }
+
+ elsif (defined($attachment)) {
+ $attachment->{'payload'} .= "$line\n";
+ }
+ else {
+ $stripped_fix .= "$line\n";
+ }
+ }
+ push @$attachments, $attachment if (defined($attachment));
+ return ($stripped_fix, $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("Part Content-Type: $ct", 2);
+ debug_print("Part Character Encoding: $charset", 2);
+ if (!$ct || $ct =~ /^text\/plain/i) {
+ $body = $part->body;
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
+ $body = Encode::decode($charset, $body);
+ }
+ 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_text_plain');
+ }
+
+ return $body;
+}
+
+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/\&/\&/g;
+ $var =~ s/\</</g;
+ $var =~ s/\>/>/g;
+ $var =~ s/\"/\"/g;
+ $var =~ s/@/@/g;
+ # Also remove undesired newlines and consecutive spaces.
+ $var =~ s/[\n\s]+/ /gms;
+ return $var;
+}
+
+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;
+
+ # 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;
+}
+
+###############
+# Main Script #
+###############
+
+$SIG{__DIE__} = \&die_handler;
+
+GetOptions(\%switch, 'help|h', 'verbose|v+', 'default=s%', 'override=s%');
+$switch{'verbose'} ||= 0;
+
+# Print the help message if that switch was selected.
+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_fields = parse_mail($mail_text);
+
+exit(0) unless(defined($mail_fields->{'bug_id'}));
+
+my $bug_id = $mail_fields->{'bug_id'};
+my $comment = $mail_fields->{'comment'};
+my $bug = Bugzilla::Bug->check($bug_id);
+
+my $username = 'dfilter at freebsd.org';
+my $user = Bugzilla::User->check($username);
+Bugzilla->set_user($user);
+
+$bug->add_comment($comment);
+my $dbh = Bugzilla->dbh;
+$dbh->bz_start_transaction();
+
+$bug->update();
+$dbh->bz_commit_transaction();
+
+__END__
More information about the svn-src-user
mailing list