]> git.donarmstrong.com Git - debbugs.git/blobdiff - scripts/service
Include the protocol (http://) in gWebDomain and gCGIDomain
[debbugs.git] / scripts / service
index a0bff66f7e6c5aaa6fe26395fb404ac565952bb6..0059ef76df79bd1d5811163611bcc2219d7d6134 100755 (executable)
@@ -7,6 +7,9 @@
 use warnings;
 use strict;
 
+use locale;
+use POSIX qw(strftime locale_h);
+setlocale(LC_TIME, "C");
 
 use Debbugs::Config qw(:globals :config);
 
@@ -18,8 +21,9 @@ use Params::Validate qw(:types validate_with);
 use Debbugs::Common qw(:util :quit :misc :lock);
 
 use Debbugs::Status qw(:read :status :write :versions :hook);
+use Debbugs::Packages qw(binary_to_source);
 
-use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522);
+use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522 create_mime_message);
 use Debbugs::Mail qw(send_mail_message);
 use Debbugs::User;
 use Debbugs::Recipients qw(:all);
@@ -30,6 +34,7 @@ use Debbugs::Status qw(splitpackages);
 
 use Debbugs::CGI qw(html_escape);
 use Debbugs::Control qw(:all);
+use Debbugs::Control::Service qw(:all);
 use Debbugs::Log qw(:misc);
 use Debbugs::Text qw(:templates);
 
@@ -38,6 +43,7 @@ use Scalar::Util qw(looks_like_number);
 use List::Util qw(first);
 
 use Mail::RFC822::Address;
+use Encode qw(decode encode);
 
 chdir($config{spool_dir}) or
      die "Unable to chdir to spool_dir '$config{spool_dir}': $!";
@@ -114,8 +120,8 @@ if ( defined($header{'reply-to'}) && $header{'reply-to'} ne "" ) {
 my $errors = 0;
 my $controlrequestaddr= ($control ? 'control' : 'request').'@'.$config{email_domain};
 my $transcript_scalar = '';
-my $transcript = IO::Scalar->new(\$transcript_scalar) or
-     die "Unable to create new IO::Scalar";
+open my $transcript, ">:scalar:utf8", \$transcript_scalar or
+     die "Unable to create transcript scalar: $!";
 print {$transcript} "Processing commands for $controlrequestaddr:\n\n";
 
 
@@ -135,6 +141,7 @@ my @common_control_options =
      request_nn        => $nn,
      request_replyto   => $replyto,
      message           => \@log,
+     affected_bugs     => \%bug_affected,
      affected_packages => \%affected_packages,
      recipients        => \%recipients,
      limit             => \%limit,
@@ -166,11 +173,6 @@ my %clonebugs = ();
 my %bcc = ();
 
 
-my @bcc;
-sub addbcc {
-    push @bcc, $_[0] unless grep { $_ eq $_[0] } @bcc;
-}
-
 our $data;
 our $message;
 our $extramessage;
@@ -200,6 +202,10 @@ for ($procline=0; $procline<=$#bodylines; $procline++) {
     # Fixes #488554
     s/\xef\xbb\xbf//g;
     next unless m/\S/;
+    eval {
+       my $temp = decode("utf8",$_,Encode::FB_CROAK);
+       $_ = $temp;
+    };
     print {$transcript} "> $_\n";
     next if m/^\s*\#/;
     $action= '';
@@ -279,7 +285,7 @@ for ($procline=0; $procline<=$#bodylines; $procline++) {
     } elsif (m/^subscribe/i) {
         print {$transcript} <<END;
 There is no $gProject $gBug mailing list.  If you wish to review bug reports
-please do so via http://$gWebDomain/ or ask this mail server
+please do so via $gWebDomain or ask this mail server
 to send them to you.
 soon: MAILINGLISTS_TEXT
 END
@@ -407,12 +413,13 @@ END
            print {$transcript} "No valid user selected\n";
            $errors++;
            $indicated_user = 1;
-        } elsif (&setbug) {
+        } elsif (check_limit(data => read_bug(bug => $ref),
+                            limit => \%limit,
+                            transcript => $transcript)) {
            if (not $indicated_user and defined $user) {
                 print {$transcript} "User is $user\n";
                 $indicated_user = 1;
            }
-           &nochangebug;
            my %ut;
            Debbugs::User::read_usertags(\%ut, $user);
             my @oldtags = (); my @newtags = (); my @badtags = ();
@@ -458,797 +465,24 @@ Unknown command or malformed arguments to command.
 (Use control\@$gEmailDomain to manipulate reports.)
 
 END
-       $errors++;
-        if (++$unknowns >= 3) {
-            print {$transcript} "Too many unknown commands, stopping here.\n\n";
-            last;
-        }
-#### "developer only" ones start here
-    } elsif (m/^close\s+\#?(-?\d+)(?:\s+(\d.*))?$/i) {
-       $ok++;
-       $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref}=1;
-       my $version= $2;
-       if (&setbug) {
-           print {$transcript} "'close' is deprecated; see http://$gWebDomain/Developer$gHTMLSuffix#closing.\n";
-           if (length($data->{done}) and not defined($version)) {
-               print {$transcript} "$gBug is already closed, cannot re-close.\n\n";
-                &nochangebug;
-            } else {
-                $action= "$gBug " .
-                    (defined($version) ?
-                        "marked as fixed in version $version" :
-                        "closed") .
-                    ", send any further explanations to $data->{originator}";
-                do {
-                  $affected_packages{$data->{package}} = 1;
-                   add_recipients(data => $data,
-                                 recipients => \%recipients,
-                                 actions_taken => {done => 1},
-                                 transcript   => $transcript,
-                                 ($dl > 0 ? (debug => $transcript):()),
-                                );
-                   $data->{done}= $replyto;
-                    my @keywords= split ' ', $data->{keywords};
-                   my $extramessage = '';
-                    if (grep $_ eq 'pending', @keywords) {
-                        $extramessage= "Removed pending tag.\n";
-                        $data->{keywords}= join ' ', grep $_ ne 'pending',
-                                                @keywords;
-                    }
-                    addfixedversions($data, $data->{package}, $version, 'binary');
-
-                   my $message= <<END;
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $data->{originator}
-Subject: $gBug#$ref acknowledged by developer
-         ($header{'subject'})
-References: $header{'message-id'} $data->{msgid}
-In-Reply-To: $data->{msgid}
-Message-ID: <handler.$ref.$nn.notifdonectrl.$midix\@$gEmailDomain>
-Reply-To: $ref\@$gEmailDomain
-X-$gProject-PR-Message: they-closed-control $ref
-
-This is an automatic notification regarding your $gBug report
-#$ref: $data->{subject},
-which was filed against the $data->{package} package.
-
-It has been marked as closed by one of the developers, namely
-$replyto.
-
-You should be hearing from them with a substantive response shortly,
-in case you haven't already. If not, please contact them directly.
-
-$gMaintainer
-(administrator, $gProject $gBugs database)
-
-END
-                    &sendmailmessage($message,$data->{originator});
-                } while (&getnextbug);
-            }
-        }
-    } elsif (m/^reassign\s+\#?(-?\d+)\s+ # bug and command
-              (?:(?:((?:src:|source:)?$config{package_name_re}) # new package
-              (?:\s+((?:$config{package_name_re}\/)?
-                      $config{package_version_re}))?)| # optional version
-              ((?:src:|source:)?$config{package_name_re} # multiple package form
-              (?:\s*\,\s*(?:src:|source:)?$config{package_name_re})+))
-              \s*$/xi) {
-        $ok++;
-        $ref= $1;
-       my @new_packages;
-       if (not defined $2) {
-           push @new_packages, split /\s*\,\s*/,$4;
-       }
-       else {
-           push @new_packages, $2;
-       }
-       @new_packages = map {y/A-Z/a-z/; s/^(?:src|source):/src:/; $_;} @new_packages;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref}=1;
-        my $version= $3;
-       eval {
-           set_package(@common_control_options,
-                       bug          => $ref,
-                       package      => \@new_packages,
-                      );
-           # if there is a version passed, we make an internal call
-           # to set_found
-           if (defined($version) && length $version) {
-               set_found(@common_control_options,
-                         bug   => $ref,
-                         found => $version,
-                        );
-           }
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to clear fixed versions and reopen on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^reopen\s+\#?(-?\d+)(?:\s+([\=\!]|(?:\S.*\S)))?$/i) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref}=1;
-       my $new_submitter = $2;
-       if (defined $new_submitter) {
-           if ($new_submitter eq '=') {
-               undef $new_submitter;
-           }
-           elsif ($new_submitter eq '!') {
-               $new_submitter = $replyto;
-           }
-       }
-       eval {
-           reopen(@common_control_options,
-                  bug          => $ref,
-                  submitter    => $new_submitter,
-                 );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to reopen $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m{^(?:(?i)found)\s+\#?(-?\d+)
-              (?:\s+((?:$config{package_name_re}\/)?
-                   $config{package_version_re}
-               # allow for multiple packages
-               (?:\s*,\s*(?:$config{package_name_re}\/)?
-                   $config{package_version_re})*)
-           )?$}x) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       my @versions;
-        if (defined $2) {
-           @versions = split /\s*,\s*/,$2;
-           eval {
-               set_found(@common_control_options,
-                         bug          => $ref,
-                         found        => \@versions,
-                         add          => 1,
-                        );
-           };
-           if ($@) {
-               $errors++;
-               print {$transcript} "Failed to add found on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-           }
-       }
-       else {
-           eval {
-               set_fixed(@common_control_options,
-                         bug          => $ref,
-                         fixed        => [],
-                         reopen       => 1,
-                        );
-           };
-           if ($@) {
-               $errors++;
-               print {$transcript} "Failed to clear fixed versions and reopen on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-           }
-       }
-    }
-    elsif (m{^(?:(?i)notfound)\s+\#?(-?\d+)
-            \s+((?:$config{package_name_re}\/)?
-                $config{package_version_re}
-               # allow for multiple packages
-               (?:\s*,\s*(?:$config{package_name_re}\/)?
-                   $config{package_version_re})*
-           )$}x) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       my @versions;
-        @versions = split /\s*,\s*/,$2;
-       eval {
-           set_found(@common_control_options,
-                     bug          => $ref,
-                     found        => \@versions,
-                     remove       => 1,
-                    );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to remove found on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    }
-    elsif (m{^(?:(?i)fixed)\s+\#?(-?\d+)
-            \s+((?:$config{package_name_re}\/)?
-                   $config{package_version_re}
-               # allow for multiple packages
-               (?:\s*,\s*(?:$config{package_name_re}\/)?
-                   $config{package_version_re})*)
-           \s*$}x) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       my @versions;
-        @versions = split /\s*,\s*/,$2;
-       eval {
-           set_fixed(@common_control_options,
-                     bug          => $ref,
-                     fixed        => \@versions,
-                     add          => 1,
-                    );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to add fixed on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    }
-    elsif (m{^(?:(?i)notfixed)\s+\#?(-?\d+)
-            \s+((?:$config{package_name_re}\/)?
-                   $config{package_version_re}
-               # allow for multiple packages
-               (?:\s*,\s*(?:$config{package_name_re}\/)?
-                   $config{package_version_re})*)
-           \s*$}x) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       my @versions;
-        @versions = split /\s*,\s*/,$2;
-       eval {
-           set_fixed(@common_control_options,
-                     bug          => $ref,
-                     fixed        => \@versions,
-                     remove       => 1,
-                    );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to remove fixed on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    }
-    elsif (m/^submitter\s+\#?(-?\d+)\s+(\!|\S.*\S)$/i) {
-        $ok++;
-        $ref= $1;
-       $bug_affected{$ref}=1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       my $newsubmitter = $2 eq '!' ? $replyto : $2;
-        if (not Mail::RFC822::Address::valid($newsubmitter)) {
-            print {$transcript} "$newsubmitter is not a valid e-mail address; not changing submitter\n";
-            $errors++;
-       }
-       else {
-           eval {
-               set_submitter(@common_control_options,
-                             bug       => $ref,
-                             submitter => $newsubmitter,
-                            );
-           };
-           if ($@) {
-               $errors++;
-               print {$transcript} "Failed to set submitter on $ref: ".cleanup_eval_fail($@,$debug)."\n";
-           }
-        }
-    } elsif (m/^forwarded\s+\#?(-?\d+)\s+(\S.*\S)$/i) {
-        $ok++;
-        $ref= $1;
-       my $forward_to= $2;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-           set_forwarded(@common_control_options,
-                         bug          => $ref,
-                         forwarded    => $forward_to,
-                          );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to set the forwarded-to-address of $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^notforwarded\s+\#?(-?\d+)$/i) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-           set_forwarded(@common_control_options,
-                         bug          => $ref,
-                         forwarded    => undef,
-                          );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to clear the forwarded-to-address of $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^(?:severity|priority)\s+\#?(-?\d+)\s+([-0-9a-z]+)$/i) {
-        $ok++;
-        $ref= $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref}=1;
-        my $newseverity= $2;
-        if (exists $gObsoleteSeverities{$newseverity}) {
-            print {$transcript} "Severity level \`$newseverity' is obsolete. " .
-                "Use $gObsoleteSeverities{$newseverity} instead.\n\n";
-               $errors++;
-        } elsif (not defined first {$_ eq $newseverity}
-           (@gSeverityList, "$gDefaultSeverity")) {
-            print {$transcript} "Severity level \`$newseverity' is not known.\n".
-                 "Recognized are: $gShowSeverities.\n\n";
-           $errors++;
-        } else {
-           eval {
-               set_severity(@common_control_options,
-                            bug => $ref,
-                            severity => $newseverity,
-                           );
-           };
-           if ($@) {
-               $errors++;
-               print {$transcript} "Failed to set severity of $config{bug} $ref to $newseverity: ".cleanup_eval_fail($@,$debug)."\n";
-           }
-       }
-    } elsif (m/^tags?\s+\#?(-?\d+)\s+(\S.*)$/i) {
-       $ok++;
-       $ref = $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref}=1;
-       my $tags = $2;
-       my @tags = map {m/^([+=-])(.+)/ ? ($1,$2):($_)} split /[\s,]+/, $tags;
-       # this is an array of hashrefs which contain two elements, the
-       # first of which is the array of tags, the second is the
-       # option to pass to set_tag (we use a hashref here to make it
-       # more obvious what is happening)
-       my @tag_operations;
-       my @badtags;
-       for my $tag (@tags) {
-           if ($tag =~ /^[=+-]$/) {
-               if ($tag eq '=') {
-                   @tag_operations = {tags => [],
-                                      option => [],
-                                     };
-               }
-               elsif ($tag eq '-') {
-                   push @tag_operations,
-                       {tags => [],
-                        option => [remove => 1],
-                       };
-               }
-               elsif ($tag eq '+') {
-                   push @tag_operations,
-                       {tags => [],
-                        option => [add => 1],
-                       };
-               }
-               next;
-           }
-           if (not defined first {$_ eq $tag} @{$config{tags}}) {
-               push @badtags, $tag;
-               next;
-           }
-           if (not @tag_operations) {
-               @tag_operations = {tags => [],
-                                  option => [add => 1],
-                                 };
-           }
-           push @{$tag_operations[-1]{tags}},$tag;
-       }
-       if (@badtags) {
-            print {$transcript} "Unknown tag/s: ".join(', ', @badtags).".\n".
-                "Recognized are: ".join(' ', @gTags).".\n\n";
-           $errors++;
-       }
-       eval {
-           for my $operation (@tag_operations) {
-               set_tag(@common_control_options,
-                       bug => $ref,
-                       tag => [@{$operation->{tags}}],
-                       warn_on_bad_tags => 0, # don't warn on bad tags,
-                       # 'cause we do that above
-                       @{$operation->{option}},
-                      );
-           }
-       };
-       if ($@) {
-           # we intentionally have two errors here if there is a bad
-           # tag and the above fails for some reason
-           $errors++;
-           print {$transcript} "Failed to alter tags of $config{bug} $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^(un)?block\s+\#?(-?\d+)\s+(?:by|with)\s+(\S.*)?$/i) {
-       $ok++;
-        $ref= $2;
-       my $add_remove = defined $1 && $1 eq 'un';
-       my @blockers = map {exists $clonebugs{$_}?$clonebugs{$_}:$_} split /[\s,]+/, $3;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-            set_blocks(@common_control_options,
-                       bug          => $ref,
-                       block        => \@blockers,
-                       $add_remove ? (remove => 1):(add => 1),
-                      );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to set blocking bugs of $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^retitle\s+\#?(-?\d+)\s+(\S.*\S)\s*$/i) {
-        $ok++;
-        $ref= $1; my $newtitle= $2;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-            set_title(@common_control_options,
-                      bug          => $ref,
-                      title        => $newtitle,
-                     );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to set the title of $ref: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^unmerge\s+\#?(-?\d+)$/i) {
-       $ok++;
-       $ref= $1;
-       $bug_affected{$ref} = 1;
-       if (&setbug) {
-           if (!length($data->{mergedwith})) {
-               print {$transcript} "$gBug is not marked as being merged with any others.\n\n";
-               &nochangebug;
-           } else {
-                $mergelowstate eq 'locked' || die "$mergelowstate ?";
-               $action= "Disconnected #$ref from all other report(s).";
-               my @newmergelist= split(/ /,$data->{mergedwith});
-                my $discref= $ref;
-               @bug_affected{@newmergelist} = 1 x @newmergelist;
-                do {
-                    $affected_packages{$data->{package}} = 1;
-                   add_recipients(data => $data,
-                                  recipients => \%recipients,
-                                  transcript   => $transcript,
-                                  ($dl > 0 ? (debug => $transcript):()),
-                                 );
-                   $data->{mergedwith}= ($ref == $discref) ? ''
-                        : join(' ',grep($_ ne $ref,@newmergelist));
-                } while (&getnextbug);
-           }
-       }
-    } elsif (m/^merge\s+#?(-?\d+(\s+#?-?\d+)+)\s*$/i) {
-       $ok++;
-        my @tomerge= sort { $a <=> $b } split(/\s+#?/,$1);
-        my @newmergelist= ();
-       my %tags = ();
-       my %found = ();
-       my %fixed = ();
-        &getmerge;
-        while (defined($ref= shift(@tomerge))) {
-            print {$transcript} "D| checking merge $ref\n" if $dl;
-           $ref+= 0;
-           if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
-               $ref = $clonebugs{$ref};
-           }
-           next if grep($_ == $ref,@newmergelist);
-           if (!&getbug) { &notfoundbug; @newmergelist=(); last }
-            if (!&checkpkglimit) { &cancelbug; @newmergelist=(); last; }
-            &foundbug;
-            print {$transcript} "D| adding $ref ($data->{mergedwith})\n" if $dl;
-           $mismatch= '';
-           &checkmatch('package','m_package',$data->{package},@newmergelist);
-           &checkmatch('forwarded addr','m_forwarded',$data->{forwarded},@newmergelist);
-           $data->{severity} = '$gDefaultSeverity' if $data->{severity} eq '';
-           &checkmatch('severity','m_severity',$data->{severity},@newmergelist);
-           &checkmatch('blocks','m_blocks',$data->{blocks},@newmergelist);
-           &checkmatch('blocked-by','m_blockedby',$data->{blockedby},@newmergelist);
-           &checkmatch('done mark','m_done',length($data->{done}) ? 'done' : 'open',@newmergelist);
-           &checkmatch('owner','m_owner',$data->{owner},@newmergelist);
-           &checkmatch('summary','m_summary',$data->{summary},@newmergelist);
-           &checkmatch('affects','m_affects',$data->{affects},@newmergelist);
-           foreach my $t (split /\s+/, $data->{keywords}) { $tags{$t} = 1; }
-           foreach my $f (@{$data->{found_versions}}) { $found{$f} = 1; }
-           foreach my $f (@{$data->{fixed_versions}}) { $fixed{$f} = 1; }
-           if (length($mismatch)) {
-               print {$transcript} "Mismatch - only $gBugs in same state can be merged:\n".
-                    $mismatch."\n";
-               $errors++;
-               &cancelbug; @newmergelist=(); last;
-           }
-            push(@newmergelist,$ref);
-            push(@tomerge,split(/ /,$data->{mergedwith}));
-           &cancelbug;
-       }
-       if (@newmergelist) {
-            @newmergelist= sort { $a <=> $b } @newmergelist;
-            $action= "Merged @newmergelist.";
-           delete @fixed{keys %found};
-           for $ref (@newmergelist) {
-               &getbug || die "huh ?  $gBug $ref disappeared during merge";
-                $affected_packages{$data->{package}} = 1;
-               add_recipients(data => $data,
-                              recipients => \%recipients,
-                              transcript   => $transcript,
-                              ($dl > 0 ? (debug => $transcript):()),
-                             );
-               @bug_affected{@newmergelist} = 1 x @newmergelist;
-               $data->{mergedwith}= join(' ',grep($_ != $ref,@newmergelist));
-               $data->{keywords}= join(' ', keys %tags);
-               $data->{found_versions}= [sort keys %found];
-               $data->{fixed_versions}= [sort keys %fixed];
-               &savebug;
-           }
-           print {$transcript} "$action\n\n";
-       }
-        &endmerge;
-    } elsif (m/^forcemerge\s+\#?(-?\d+(?:\s+\#?-?\d+)+)\s*$/i) {
-       $ok++;
-       my @temp = split /\s+\#?/,$1;
-       my $master_bug = shift @temp;
-       my $master_bug_data;
-       my @tomerge = sort { $a <=> $b } @temp;
-        unshift @tomerge,$master_bug;
-       print {$transcript} "D| force merging ".join(',',@tomerge)."\n" if $dl;
-       my @newmergelist= ();
-       my %tags = ();
-       my %found = ();
-       my %fixed = ();
-       # Here we try to do the right thing.
-       # First, if the bugs are in the same package, we merge all of the found, fixed, and tags.
-       # If not, we discard the found and fixed.
-       # Everything else we set to the values of the first bug.
-        &getmerge;
-        while (defined($ref= shift(@tomerge))) {
-            print {$transcript} "D| checking merge $ref\n" if $dl;
-           $ref+= 0;
-           if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
-               $ref = $clonebugs{$ref};
-           }
-           next if grep($_ == $ref,@newmergelist);
-           if (!&getbug) { &notfoundbug; @newmergelist=(); last }
-            if (!&checkpkglimit) { &cancelbug; @newmergelist=(); last; }
-            &foundbug;
-            print {$transcript} "D| adding $ref ($data->{mergedwith})\n" if $dl;
-           $master_bug_data = $data if not defined $master_bug_data;
-           if ($data->{package} ne $master_bug_data->{package}) {
-                print {$transcript} "Mismatch - only $gBugs in the same package can be forcibly merged:\n".
-                    "$gBug $ref is not in the same package as $master_bug\n";
-               $errors++;
-               &cancelbug; @newmergelist=(); last;
-           }
-           for my $t (split /\s+/,$data->{keywords}) {
-                $tags{$t} = 1;
-           }
-           @found{@{$data->{found_versions}}} = (1) x @{$data->{found_versions}};
-           @fixed{@{$data->{fixed_versions}}} = (1) x @{$data->{fixed_versions}};
-           push(@newmergelist,$ref);
-            push(@tomerge,split(/ /,$data->{mergedwith}));
-           &cancelbug;
-       }
-       if (@newmergelist) {
-            @newmergelist= sort { $a <=> $b } @newmergelist;
-            $action= "Forcibly Merged @newmergelist.";
-           delete @fixed{keys %found};
-           for $ref (@newmergelist) {
-               &getbug || die "huh ?  $gBug $ref disappeared during merge";
-                $affected_packages{$data->{package}} = 1;
-               add_recipients(data => $data,
-                              recipients => \%recipients,
-                              transcript   => $transcript,
-                              ($dl > 0 ? (debug => $transcript):()),
-                             );
-               @bug_affected{@newmergelist} = 1 x @newmergelist;
-               $data->{mergedwith}= join(' ',grep($_ != $ref,@newmergelist));
-               $data->{keywords}= join(' ', keys %tags);
-               $data->{found_versions}= [sort keys %found];
-               $data->{fixed_versions}= [sort keys %fixed];
-               my @field_list = qw(forwarded package severity blocks blockedby owner done affects summary);
-               @{$data}{@field_list} = @{$master_bug_data}{@field_list};
-               &savebug;
-           }
-           print {$transcript} "$action\n\n";
-       }
-        &endmerge;
-    } elsif (m/^clone\s+#?(\d+)\s+((-\d+\s+)*-\d+)\s*$/i) {
-       $ok++;
-
-       my $origref = $1;
-       my @newclonedids = split /\s+/, $2;
-       my $newbugsneeded = scalar(@newclonedids);
-
-       $ref = $origref;
-       $bug_affected{$ref} = 1;
-       if (&setbug) {
-           $affected_packages{$data->{package}} = 1;
-           if (length($data->{mergedwith})) {
-               print {$transcript} "$gBug is marked as being merged with others. Use an existing clone.\n\n";
-               $errors++;
-               &nochangebug;
-           } else {
-               &filelock("nextnumber.lock");
-               open(N,"nextnumber") || die "nextnumber: read: $!";
-               my $v=<N>; $v =~ s/\n$// || die "nextnumber bad format";
-               my $firstref= $v+0;  $v += $newbugsneeded;
-               open(NN,">nextnumber"); print NN "$v\n"; close(NN);
-               &unfilelock;
-
-               my $lastref = $firstref + $newbugsneeded - 1;
-
-               if ($newbugsneeded == 1) {
-                   $action= "$gBug $origref cloned as bug $firstref.";
-               } else {
-                   $action= "$gBug $origref cloned as bugs $firstref-$lastref.";
-               }
-
-               my $blocks = $data->{blocks};
-               my $blockedby = $data->{blockedby};
-               
-               &getnextbug;
-               my $ohash = get_hashname($origref);
-               my $clone = $firstref;
-                @bug_affected{@newclonedids} = 1 x @newclonedids;
-               for my $newclonedid (@newclonedids) {
-                   $clonebugs{$newclonedid} = $clone;
-           
-                   my $hash = get_hashname($clone);
-                   copy("db-h/$ohash/$origref.log", "db-h/$hash/$clone.log");
-                   copy("db-h/$ohash/$origref.status", "db-h/$hash/$clone.status");
-                   copy("db-h/$ohash/$origref.summary", "db-h/$hash/$clone.summary");
-                   copy("db-h/$ohash/$origref.report", "db-h/$hash/$clone.report");
-                   &bughook('new', $clone, $data);
-               
-                   # Update blocking info of bugs blocked by or blocking the
-                   # cloned bug.
-                   foreach $ref (split ' ', $blocks) {
-                       &getbug;
-                       $data->{blockedby} = manipset($data->{blockedby}, $clone, 1);
-                       &savebug;
-                   }
-                   foreach $ref (split ' ', $blockedby) {
-                       &getbug;
-                       $data->{blocks} = manipset($data->{blocks}, $clone, 1);
-                       &savebug;
-                   }
-
-                   $clone++;
-               }
-           }
-       }
-    } elsif (m/^package\:?\s+(\S.*\S)?\s*$/i) {
-        $ok++;
-       my @pkgs = split /\s+/, $1;
-       if (scalar(@pkgs) > 0) {
-               %limit_pkgs = map { ($_, 1) } @pkgs;
-               $limit{package} = [@pkgs];
-               print {$transcript} "Limiting to bugs with field 'package' containing at least one of ".join(', ',map {qq('$_')} @pkgs)."\n";
-               print {$transcript} "Limit currently set to ";
-               for my $limit_field (keys %limit) {
-                   print {$transcript} "  '$limit_field':".join(', ',map {qq('$_')} @{$limit{$limit_field}})."\n";
-               }
-               print {$transcript} "\n";
-       } else {
-           %limit_pkgs = ();
-           $limit{package} = [];
-           print {$transcript} "Limit cleared.\n\n";
-       }
-    } elsif (m/^limit\:?\s+(\S.*\S)\s*$/) {
-       $ok++;
-       my ($field,@options) = split /\s+/, $1;
-       $field = lc($field);
-       if ($field =~ /^(?:clear|unset|blank)$/) {
-           %limit = ();
-           print {$transcript} "Limit cleared.\n\n";
-       }
-       elsif (exists $Debbugs::Status::fields{$field} ) {
-           # %limit can actually contain regexes, but because they're
-           # not evaluated in Safe, DO NOT allow them through without
-           # fixing this.
-           $limit{$field} = [@options];
-           print {$transcript} "Limiting to bugs with field '$field' containing at least one of ".join(', ',map {qq('$_')} @options)."\n";
-           print {$transcript} "Limit currently set to ";
-           for my $limit_field (keys %limit) {
-               print {$transcript} "  '$limit_field':".join(', ',map {qq('$_')} @{$limit{$limit_field}})."\n";
-           }
-           print {$transcript} "\n";
-       }
-       else {
-           print {$transcript} "Limit key $field not understood. Stopping processing here.\n\n";
-           $errors++;
+       #### "developer only" ones start here
+    } elsif (defined valid_control($_)) {
+       my ($new_errors,$terminate_control) =
+           control_line(line => $_,
+                        clonebugs => \%clonebugs,
+                        limit => \%limit,
+                        common_control_options => \@common_control_options,
+                        errors => \$errors,
+                        transcript => $transcript,
+                        debug => $debug,
+                        ok => \$ok,
+                        replyto => $replyto,
+                       );
+       if ($terminate_control) {
            last;
        }
-    } elsif (m/^affects?\s+\#?(-?\d+)(?:\s+((?:[=+-])?)\s*(\S.*)?)?\s*$/i) {
-       $ok++;
-        $ref = $1;
-       my $add_remove = $2 || '';
-       my $packages = $3 || '';
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-            affects(@common_control_options,
-                    bug => $ref,
-                    packages     => [splitpackages($3)],
-                    ($add_remove eq '+'?(add => 1):()),
-                    ($add_remove eq '-'?(remove => 1):()),
-                   );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to mark $ref as affecting package(s): ".cleanup_eval_fail($@,$debug)."\n";
-       }
-
-    } elsif (m/^summary\s+\#?(-?\d+)\s*(\d+|)\s*$/i) {
-       $ok++;
-        $ref = $1;
-       my $summary_msg = length($2)?$2:undef;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-           summary(@common_control_options,
-                   bug          => $ref,
-                   summary      => $summary_msg,
-                  );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to give $ref a summary: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-
-    } elsif (m/^owner\s+\#?(-?\d+)\s+((?:\S.*\S)|\!)\s*$/i) {
-       $ok++;
-        $ref = $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       my $newowner = $2;
-       if ($newowner eq '!') {
-           $newowner = $replyto;
-       }
-       $bug_affected{$ref} = 1;
-       eval {
-           owner(@common_control_options,
-                 bug          => $ref,
-                 owner        => $newowner,
-                );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to mark $ref as having an owner: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^noowner\s+\#?(-?\d+)\s*$/i) {
-        $ok++;
-        $ref = $1;
-       $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-       $bug_affected{$ref} = 1;
-       eval {
-           owner(@common_control_options,
-                 bug          => $ref,
-                 owner        => undef,
-                );
-       };
-       if ($@) {
-           $errors++;
-           print {$transcript} "Failed to mark $ref as not having an owner: ".cleanup_eval_fail($@,$debug)."\n";
-       }
-    } elsif (m/^unarchive\s+#?(\d+)$/i) {
-        $ok++;
-        $ref = $1;
-        $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-        $bug_affected{$ref} = 1;
-        eval {
-             bug_unarchive(@common_control_options,
-                           bug        => $ref,
-                           recipients => \%recipients,
-                          );
-        };
-        if ($@) {
-             $errors++;
-        }
-    } elsif (m/^archive\s+#?(\d+)$/i) {
-        $ok++;
-        $ref = $1;
-        $ref = $clonebugs{$ref} if exists $clonebugs{$ref};
-        $bug_affected{$ref} = 1;
-        eval {
-             bug_archive(@common_control_options,
-                         bug => $ref,
-                         ignore_time => 1,
-                         archive_unarchived => 0,
-                        );
-        };
-        if ($@) {
-             $errors++;
-        }
     } else {
-        print {$transcript} "Unknown command or malformed arguments to command.\n\n";
+       print {$transcript} "Unknown command or malformed arguments to command.\n";
        $errors++;
         if (++$unknowns >= 5) {
             print {$transcript} "Too many unknown commands, stopping here.\n\n";
@@ -1270,43 +504,41 @@ my @maintccs = determine_recipients(recipients => \%recipients,
                                    address_only => 1,
                                    cc => 1,
                                   );
-my $maintccs = 'Cc: '.join(",\n    ",
-                   determine_recipients(recipients => \%recipients,
-                                        cc => 1,
-                                       )
-                  )."\n";
-
-my $packagepr = '';
-$packagepr = "X-${gProject}-PR-Package: " . join(keys %affected_packages) . "\n" if keys %affected_packages;
-
-# Add Bcc's to subscribed bugs
-# now handled by Debbugs::Recipients
-#push @bcc, map {"bugs=$_\@$gListDomain"} keys %bug_affected;
-
 if (!defined $header{'subject'} || $header{'subject'} eq "") {
   $header{'subject'} = "your mail";
 }
 
 # Error text here advertises how many errors there were
-my $error_text = $errors > 0 ? " (with $errors errors)":'';
-
-my $reply= <<END;
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $replyto
-${maintccs}Subject: Processed${error_text}: $header{'subject'}
-In-Reply-To: $header{'message-id'}
-END
-$reply .= <<END;
-References: $header{'message-id'}
-Message-ID: <handler.s.$nn.transcript\@$gEmailDomain>
-Precedence: bulk
-${packagepr}X-$gProject-PR-Message: transcript
-
-${transcript_scalar}Please contact me if you need assistance.
-
-$gMaintainer
-(administrator, $gProject $gBugs database)
-END
+my $error_text = $errors > 0 ? " (with $errors error" . ($errors > 1 ? "s" : "") . ")" : "";
+
+my @common_headers;
+push @common_headers, 'X-Loop',$gMaintainerEmail;
+
+my $temp_transcript = $transcript_scalar;
+eval{
+    $temp_transcript = decode("utf8",$temp_transcript,Encode::FB_CROAK);
+};
+my $reply =
+    create_mime_message([From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+                        To            => $replyto,
+                        @maintccs ? (Cc => join(', ',@maintccs)):(),
+                        Subject       => "Processed${error_text}: $header{subject}",
+                        'Message-ID'  => "<handler.s.$nn.transcript\@$gEmailDomain>",
+                        'In-Reply-To' => $header{'message-id'},
+                        References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+                        Precedence    => 'bulk',
+                        keys %affected_packages ?("X-${gProject}-PR-Package" => join(' ',keys %affected_packages)):(),
+                        keys %affected_packages ?("X-${gProject}-PR-Source" =>
+                                                  join(' ',
+                                                       map {defined $_ ?(ref($_)?@{$_}:$_):()}
+                                                       binary_to_source(binary => [keys %affected_packages],
+                                                                        source_only => 1))):(),
+                        "X-$gProject-PR-Message" => 'transcript',
+                        @common_headers,
+                       ],
+                       fill_template('mail/message_body',
+                                     {body => "${temp_transcript}Please contact me if you need assistance."},
+                                    ));
 
 my $repliedshow= join(', ',$replyto,
                      determine_recipients(recipients => \%recipients,
@@ -1314,19 +546,7 @@ my $repliedshow= join(', ',$replyto,
                                           address_only => 1,
                                          )
                     );
-# -1 is the service.in log
-&filelock("lock/-1");
-open(AP,">>db-h/-1.log") || die "open db-h/-1.log: $!";
-print(AP
-      "\2\n$repliedshow\n\5\n$reply\n\3\n".
-      "\6\n".
-      "<strong>Request received</strong> from <code>".
-      html_escape($header{'from'})."</code>\n".
-      "to <code>".html_escape($controlrequestaddr)."</code>\n".
-      "\3\n".
-      "\7\n",escape_log(@log),"\n\3\n") || die "writing db-h/-1.log: $!";
-close(AP) || die "open db-h/-1.log: $!";
-&unfilelock;
+
 utime(time,time,"db-h");
 
 &sendmailmessage($reply,
@@ -1354,12 +574,15 @@ sub fill_template{
      my $variables = {config => \%config,
                      defined($ref)?(ref    => $ref):(),
                      defined($data)?(data  => $data):(),
+                     refs => [map {exists $clonebugs{$_}?$clonebugs{$_}:$_} keys %bug_affected],
                      %{$extra_var},
                     };
      my $hole_var = {'&bugurl' =>
                     sub{"$_[0]: ".
-                             'http://'.$config{cgi_domain}.'/'.
-                                  Debbugs::CGI::bug_url($_[0]);
+                             $config{cgi_domain}.'/'.
+                                  Debbugs::CGI::bug_links(bug=>$_[0],
+                                                          links_only => 1,
+                                                         );
                    }
                    };
      return fill_in_template(template => $template,
@@ -1678,7 +901,7 @@ END
 sub sendlynxdocraw {
     my ($relpath,$description) = @_;
     $doc='';
-    open(L,"lynx -nolist -dump http://$gCGIDomain/\Q$relpath\E 2>&1 |") || die "fork for lynx: $!";
+    open(L,"lynx -nolist -dump $gCGIDomain/\Q$relpath\E 2>&1 |") || die "fork for lynx: $!";
     while(<L>) { $doc.=$_; }
     $!=0; close(L);
     if ($? == 255 && $doc =~ m/^\n*lynx: Can\'t access start file/) {