]> git.donarmstrong.com Git - debbugs.git/commitdiff
merge changes from dla source
authorDebian BTS <debbugs@rietz>
Sat, 9 Aug 2008 14:19:13 +0000 (14:19 +0000)
committerDebian BTS <debbugs@rietz>
Sat, 9 Aug 2008 14:19:13 +0000 (14:19 +0000)
76 files changed:
Debbugs/Bugs.pm
Debbugs/CGI.pm
Debbugs/CGI/Bugreport.pm [new file with mode: 0644]
Debbugs/CGI/Pkgreport.pm [new file with mode: 0644]
Debbugs/Common.pm
Debbugs/Config.pm
Debbugs/Control.pm
Debbugs/Log.pm
Debbugs/MIME.pm
Debbugs/Packages.pm
Debbugs/Recipients.pm [new file with mode: 0644]
Debbugs/SOAP.pm
Debbugs/Status.pm
Debbugs/Text.pm
Debbugs/User.pm
Debbugs/Versions.pm
Makefile
cgi/bugreport.cgi
cgi/bugs.css [deleted file]
cgi/pkgreport.cgi
cgi/soap.cgi
cgi/version.cgi
debian/changelog
html/bugs.css
scripts/age-1 [new file with mode: 0755]
scripts/age-1.in [deleted file]
scripts/config [new file with mode: 0644]
scripts/config.in [deleted file]
scripts/db2html [new file with mode: 0755]
scripts/db2html.in [deleted file]
scripts/errorlib [new file with mode: 0755]
scripts/errorlib.in [deleted file]
scripts/expire [new file with mode: 0755]
scripts/expire.in [deleted file]
scripts/gen-indices [new file with mode: 0755]
scripts/gen-indices.in [deleted file]
scripts/html-control [new file with mode: 0755]
scripts/html-control.in [deleted file]
scripts/html-install [new file with mode: 0755]
scripts/html-install.in [deleted file]
scripts/mailsummary [new file with mode: 0755]
scripts/mailsummary.in [deleted file]
scripts/process [new file with mode: 0755]
scripts/process.in [deleted file]
scripts/processall [new file with mode: 0755]
scripts/processall.in [deleted file]
scripts/rebuild [new file with mode: 0755]
scripts/rebuild.in [deleted file]
scripts/receive [new file with mode: 0755]
scripts/receive.in [deleted file]
scripts/service [new file with mode: 0755]
scripts/service.in [deleted file]
scripts/spamscan [new file with mode: 0755]
scripts/spamscan.in [deleted file]
scripts/summary [new file with mode: 0755]
scripts/summary.in [deleted file]
scripts/text [new file with mode: 0644]
scripts/text.in [deleted file]
t/06_mail_handling.t
t/07_bugreport.t
t/09_soap.t
t/lib/DebbugsTest.pm
templates/en_US/cgi/bugreport.tmpl [new file with mode: 0644]
templates/en_US/cgi/bugreport_buginfo.tmpl [new file with mode: 0644]
templates/en_US/cgi/bugreport_pkginfo.tmpl [new file with mode: 0644]
templates/en_US/cgi/no_such_bug.tmpl [new file with mode: 0644]
templates/en_US/cgi/pkgreport_javascript.tmpl [new file with mode: 0644]
templates/en_US/cgi/pkgreport_options.tmpl [new file with mode: 0644]
templates/en_US/cgi/pkgreport_options_include_exclude.tmpl [new file with mode: 0644]
templates/en_US/cgi/pkgreport_options_include_exclude_key.tmpl [new file with mode: 0644]
templates/en_US/cgi/pkgreport_options_search_key.tmpl [new file with mode: 0644]
templates/en_US/cgi/quit.tmpl [new file with mode: 0644]
templates/en_US/cgi/short_bug_status.tmpl [new file with mode: 0644]
templates/en_US/html/html_tail.tmpl [new file with mode: 0644]
templates/en_US/html/post_title.tmpl [new file with mode: 0644]
templates/en_US/html/pre_title.tmpl [new file with mode: 0644]

index 62cd03f8c03e483acc01ef4f786aca4bfec0e81b..9bfd4ae3c620d0ccc9678212f5ddd18bf783d5e3 100644 (file)
@@ -90,6 +90,8 @@ for limited regular expressions, and/or more complex expressions.
 
 =item owner -- owner of the bug
 
+=item correspondent -- address of someone who sent mail to the log
+
 =item dist -- distribution (I don't know about this one yet)
 
 =item bugs -- list of bugs to search within
@@ -177,6 +179,9 @@ sub get_bugs{
                                          dist      => {type => SCALAR|ARRAYREF,
                                                        optional => 1,
                                                       },
+                                         correspondent => {type => SCALAR|ARRAYREF,
+                                                           optional => 1,
+                                                          },
                                          function  => {type => CODEREF,
                                                        optional => 1,
                                                       },
@@ -299,14 +304,16 @@ sub newest_bug {
 
 Allows filtering bugs on commonly used criteria
 
+
+
 =cut
 
 sub bug_filter {
      my %param = validate_with(params => \@_,
-                              spec   => {bug => {type  => SCALAR,
-                                                 regex => qr/^\d+$/,
-                                                },
-                                         status => {type => HASHREF,
+                              spec   => {bug    => {type => ARRAYREF|SCALAR,
+                                                    optional => 1,
+                                                   },
+                                         status => {type => HASHREF|ARRAYREF,
                                                     optional => 1,
                                                    },
                                          seen_merged => {type => HASHREF,
@@ -334,6 +341,9 @@ sub bug_filter {
         not defined $param{seen_merged}) {
          croak "repeat_merged false requires seen_merged to be passed";
      }
+     if (not exists $param{bug} and not exists $param{status}) {
+        croak "one of bug or status must be passed";
+     }
 
      if (not exists $param{status}) {
          my $location = getbuglocation($param{bug}, 'summary');
@@ -402,6 +412,9 @@ sub get_bugs_by_idx{
                                          bugs      => {type => SCALAR|ARRAYREF,
                                                        optional => 1,
                                                       },
+                                         correspondent => {type => SCALAR|ARRAYREF,
+                                                           optional => 1,
+                                                          },
                                          usertags  => {type => HASHREF,
                                                        optional => 1,
                                                       },
@@ -498,10 +511,13 @@ sub get_bugs_flatfile{
                                          tag       => {type => SCALAR|ARRAYREF,
                                                        optional => 1,
                                                       },
+                                         owner     => {type => SCALAR|ARRAYREF,
+                                                       optional => 1,
+                                                      },
+                                         correspondent => {type => SCALAR|ARRAYREF,
+                                                           optional => 1,
+                                                          },
 # not yet supported
-#                                        owner     => {type => SCALAR|ARRAYREF,
-#                                                      optional => 1,
-#                                                     },
 #                                        dist      => {type => SCALAR|ARRAYREF,
 #                                                      optional => 1,
 #                                                     },
@@ -544,11 +560,23 @@ sub get_bugs_flatfile{
          delete @param{qw(maint src)};
          $param{package} = [@packages];
      }
+     my $grep_bugs = 0;
+     my %bugs;
+     if (exists $param{bugs}) {
+         $bugs{$_} = 1 for make_list($param{bugs});
+         $grep_bugs = 1;
+     }
+     if (exists $param{owner} or exists $param{correspondent}) {
+         $bugs{$_} = 1 for get_bugs_by_idx(exists $param{correspondent}?(correspondent => $param{correspondent}):(),
+                                           exists $param{owner}?(owner => $param{owner}):(),
+                                          );
+         $grep_bugs = 1;
+     }
      my @bugs;
      while (<$flatfile>) {
          next unless m/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+\[\s*([^]]*)\s*\]\s+(\w+)\s+(.*)$/;
          my ($pkg,$bug,$time,$status,$submitter,$severity,$tags) = ($1,$2,$3,$4,$5,$6,$7);
-         next if exists $param{bugs} and not grep {$bug == $_} make_list($param{bugs});
+         next if $grep_bugs and not exists $bugs{$bug};
          if (exists $param{package}) {
               my @packages = splitpackages($pkg);
               next unless grep { my $pkg_list = $_;
@@ -670,6 +698,7 @@ my %field_match = (
     },
     'severity' => \&__exact_field_match,
     'pending' => \&__exact_field_match,
+    'package' => \&__exact_field_match,
     'originator' => \&__contains_field_match,
     'forwarded' => \&__contains_field_match,
     'owner' => \&__contains_field_match,
index 8fc14f28289c27815e38723b37b06878cdf046a9..1ba47996744eeb2ec4358db4da55910e30d9ba8e 100644 (file)
@@ -37,7 +37,7 @@ use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
 use base qw(Exporter);
 use Debbugs::URI;
 use HTML::Entities;
-use Debbugs::Common qw(getparsedaddrs);
+use Debbugs::Common qw(getparsedaddrs make_list);
 use Params::Validate qw(validate_with :types);
 use Debbugs::Config qw(:config);
 use Debbugs::Status qw(splitpackages isstrongseverity);
@@ -45,6 +45,12 @@ use Mail::Address;
 use POSIX qw(ceil);
 use Storable qw(dclone);
 
+use List::Util qw(max);
+
+use Carp;
+
+use Debbugs::Text qw(fill_in_template);
+
 our %URL_PARAMS = ();
 
 
@@ -55,18 +61,21 @@ BEGIN{
      @EXPORT = ();
      %EXPORT_TAGS = (url    => [qw(bug_url bug_links bug_linklist maybelink),
                                qw(set_url_params pkg_url version_url),
-                               qw(submitterurl mainturl munge_url)
+                               qw(submitterurl mainturl munge_url),
+                               qw(package_links bug_links),
                               ],
                     html   => [qw(html_escape htmlize_bugs htmlize_packagelinks),
                                qw(maybelink htmlize_addresslinks htmlize_maintlinks),
                               ],
                     util   => [qw(cgi_parameters quitcgi),
                               ],
+                    forms  => [qw(option_form form_options_and_normal_param)],
                     misc   => [qw(maint_decode)],
+                    package_search => [qw(@package_search_key_order %package_search_keys)],
                     #status => [qw(getbugstatus)],
                    );
      @EXPORT_OK = ();
-     Exporter::export_ok_tags(qw(url html util misc));
+     Exporter::export_ok_tags(keys %EXPORT_TAGS);
      $EXPORT_TAGS{all} = [@EXPORT_OK];
 }
 
@@ -112,6 +121,8 @@ sub bug_url{
      else {
          %params = @_;
      }
+     carp "bug_url is deprecated, use bug_links instead";
+
      return munge_url('bugreport.cgi?',%params,bug=>$ref);
 }
 
@@ -124,6 +135,7 @@ sub pkg_url{
      else {
          %params = @_;
      }
+     carp "pkg_url is deprecated, use package_links instead";
      return munge_url('pkgreport.cgi?',%params);
 }
 
@@ -151,22 +163,64 @@ sub munge_url {
 
 =head2 version_url
 
-     version_url($package,$found,$fixed)
+     version_url(package => $package,found => $found,fixed => $fixed)
 
 Creates a link to the version cgi script
 
+=over
+
+=item package -- source package whose graph to display
+
+=item found -- arrayref of found versions
+
+=item fixed -- arrayref of fixed versions
+
+=item width -- optional width of graph
+
+=item height -- optional height of graph
+
+=item info -- display html info surrounding graph; defaults to 1 if
+width and height are not passed.
+
+=item collapse -- whether to collapse the graph; defaults to 1 if
+width and height are passed.
+
+=back
+
 =cut
 
 sub version_url{
-     my ($package,$found,$fixed,$width,$height) = @_;
+     my %params = validate_with(params => \@_,
+                               spec   => {package => {type => SCALAR,
+                                                     },
+                                          found   => {type => ARRAYREF,
+                                                      default => [],
+                                                     },
+                                          fixed   => {type => ARRAYREF,
+                                                      default => [],
+                                                     },
+                                          width   => {type => SCALAR,
+                                                      optional => 1,
+                                                     },
+                                          height  => {type => SCALAR,
+                                                      optional => 1,
+                                                     },
+                                          absolute => {type => BOOLEAN,
+                                                       default => 0,
+                                                      },
+                                          collapse => {type => BOOLEAN,
+                                                       default => 1,
+                                                      },
+                                          info     => {type => BOOLEAN,
+                                                       optional => 1,
+                                                      },
+                                         }
+                              );
+     if (not defined $params{width} and not defined $params{height}) {
+         $params{info} = 1 if not exists $params{info};
+     }
      my $url = Debbugs::URI->new('version.cgi?');
-     $url->query_form(package => $package,
-                     found   => $found,
-                     fixed   => $fixed,
-                     (defined $width)?(width => $width):(),
-                     (defined $height)?(height => $height):(),
-                     (defined $width or defined $height)?(collapse => 1):(info => 1),
-                    );
+     $url->query_form(%params);
      return $url->as_string;
 }
 
@@ -231,168 +285,233 @@ sub cgi_parameters {
 sub quitcgi {
     my $msg = shift;
     print "Content-Type: text/html\n\n";
-    print "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY>\n";
-    print "An error occurred. Dammit.\n";
-    print "Error was: $msg.\n";
-    print "</BODY></HTML>\n";
+    print fill_in_template(template=>'cgi/quit',
+                          variables => {msg => $msg}
+                         );
     exit 0;
 }
 
 
 =head HTML
 
-=head2 htmlize_bugs
+=head2 htmlize_packagelinks
 
-     htmlize_bugs({bug=>1,status=>\%status,extravars=>\%extra},{bug=>...}});
+     htmlize_packagelinks
 
-Turns a list of bugs into an html snippit of the bugs.
+Given a scalar containing a list of packages separated by something
+that L<Debbugs::CGI/splitpackages> can separate, returns a
+formatted set of links to packages in html.
 
 =cut
-#     htmlize_bugs(bugs=>[@bugs]);
-sub htmlize_bugs{
-     my @bugs = @_;
-     my @html;
-
-     for my $bug (@bugs) {
-         my $html = sprintf "<li><a href=\"%s\">#%d: %s</a>\n<br>",
-              bug_url($bug->{bug}), $bug->{bug}, html_escape($bug->{status}{subject});
-         $html .= htmlize_bugstatus($bug->{status}) . "\n";
-     }
-     return @html;
+
+sub htmlize_packagelinks {
+    my ($pkgs) = @_;
+    return '' unless defined $pkgs and $pkgs ne '';
+    my @pkglist = splitpackages($pkgs);
+
+    carp "htmlize_packagelinks is deprecated, use package_links instead";
+
+    return 'Package' . (@pkglist > 1 ? 's' : '') . ': ' .
+           package_links(package =>\@pkglist,
+                        class   => 'submitter'
+                       );
 }
 
+=head2 package_links
 
-sub htmlize_bugstatus {
-     my %status = %{$_[0]};
+     join(', ', package_links(packages => \@packages))
 
-     my $result = "";
+Given a list of packages, return a list of html which links to the package
 
-     my $showseverity;
-     if  ($status{severity} eq $config{default_severity}) {
-         $showseverity = '';
-     } elsif (isstrongseverity($status{severity})) {
-         $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
-     } else {
-         $showseverity = "Severity: <em>$status{severity}</em>;\n";
-     }
+=over
+
+=item package -- arrayref or scalar of package(s)
+
+=item submitter -- arrayref or scalar of submitter(s)
+
+=item src -- arrayref or scalar of source(s)
+
+=item maintainer -- arrayref or scalar of maintainer(s)
+
+=item links_only -- return only links, not htmlized links, defaults to
+returning htmlized links.
+
+=item class -- class of the a href, defaults to ''
+
+=back
 
-     $result .= htmlize_packagelinks($status{"package"}, 1);
+=cut
 
-     my $showversions = '';
-     if (@{$status{found_versions}}) {
-         my @found = @{$status{found_versions}};
-         local $_;
-         s{/}{ } foreach @found;
-         $showversions .= join ', ', map html_escape($_), @found;
+our @package_search_key_order = (package   => 'in package',
+                                tag       => 'tagged',
+                                severity  => 'with severity',
+                                src       => 'in source package',
+                                maint     => 'in packages maintained by',
+                                submitter => 'submitted by',
+                                owner     => 'owned by',
+                                status    => 'with status',
+                                correspondent => 'with mail from',
+                                newest        => 'newest bugs',
+                               );
+our %package_search_keys = @package_search_key_order;
+
+
+sub package_links {
+     my %param = validate_with(params => \@_,
+                              spec   => {(map { ($_,{type => SCALAR|ARRAYREF,
+                                                     optional => 1,
+                                                    });
+                                           } keys %package_search_keys,
+                                         ),
+                                         links_only => {type => BOOLEAN,
+                                                        default => 0,
+                                                       },
+                                         class => {type => SCALAR,
+                                                   default => '',
+                                                  },
+                                         separator => {type => SCALAR,
+                                                       default => ', ',
+                                                      },
+                                         options => {type => HASHREF,
+                                                     default => {},
+                                                    },
+                                        },
+                              normalize_keys =>
+                              sub {
+                                   my ($key) = @_;
+                                   my %map = (source => 'src',
+                                              maintainer => 'maint',
+                                              pkg        => 'package',
+                                             );
+                                   return $map{$key} if exists $map{$key};
+                                   return $key;
+                              }
+                             );
+     my %options = %{$param{options}};
+     for ((keys %package_search_keys,qw(msg att))) {
+         delete $options{$_} if exists $options{$_};
      }
-     if (@{$status{fixed_versions}}) {
-         $showversions .= '; ' if length $showversions;
-         $showversions .= '<strong>fixed</strong>: ';
-         my @fixed = @{$status{fixed_versions}};
-         $showversions .= join ', ', map {s#/##; html_escape($_)} @fixed;
+     my @links = ();
+     for my $type (qw(src package)) {
+         push @links, map {(munge_url('pkgreport.cgi?',
+                                      %options,
+                                      $type => $_,
+                                     ),
+                            $_);
+                      } make_list($param{$type}) if exists $param{$type};
      }
-     $result .= " ($showversions)" if length $showversions;
-     $result .= ";\n";
-
-     $result .= $showseverity;
-     $result .= htmlize_addresslinks("Reported by: ", \&submitterurl,
-                                $status{originator});
-     $result .= ";\nOwned by: " . html_escape($status{owner})
-         if length $status{owner};
-     $result .= ";\nTags: <strong>" 
-         . html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
-              . "</strong>"
-                   if (length($status{tags}));
-
-     $result .= ";\nMerged with ".
-         bug_linklist(', ',
-                      'submitter',
-                      split(/ /,$status{mergedwith}))
-              if length $status{mergedwith};
-     $result .= ";\nBlocked by ".
-         bug_linklist(", ",
-                      'submitter',
-                      split(/ /,$status{blockedby}))
-              if length $status{blockedby};
-     $result .= ";\nBlocks ".
-         bug_linklist(", ",
-                      'submitter',
-                      split(/ /,$status{blocks})
-                     )
-              if length $status{blocks};
-
-     my $days = 0;
-     if (length($status{done})) {
-         $result .= "<br><strong>Done:</strong> " . html_escape($status{done});
-         $days = ceil($debbugs::gRemoveAge - -M buglog($status{id}));
-         if ($days >= 0) {
-              $result .= ";\n<strong>Will be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
-         } else {
-              $result .= ";\n<strong>Archived</strong>";
+     for my $type (qw(maint owner submitter correspondent)) {
+         push @links, map {my $addr = getparsedaddrs($_);
+                           $addr = defined $addr?$addr->address:'';
+                           (munge_url('pkgreport.cgi?',
+                                      %options,
+                                      $type => $addr,
+                                     ),
+                            $_);
+                      } make_list($param{$type}) if exists $param{$type};
+     }
+     my @return = ();
+     my ($link,$link_name);
+     my $class = '';
+     if (length $param{class}) {
+         $class = q( class=").html_escape($param{class}).q(");
+     }
+     while (($link,$link_name) = splice(@links,0,2)) {
+         if ($param{links_only}) {
+              push @return,$link
+         }
+         else {
+              push @return,
+                   qq(<a$class href=").
+                        html_escape($link).q(">).
+                             html_escape($link_name).q(</a>);
          }
      }
+     if (wantarray) {
+         return @return;
+     }
      else {
-         if (length($status{forwarded})) {
-              $result .= ";\n<strong>Forwarded</strong> to "
-                   . maybelink($status{forwarded});
-         }
-         my $daysold = int((time - $status{date}) / 86400);   # seconds to days
-         if ($daysold >= 7) {
-              my $font = "";
-              my $efont = "";
-              $font = "em" if ($daysold > 30);
-              $font = "strong" if ($daysold > 60);
-              $efont = "</$font>" if ($font);
-              $font = "<$font>" if ($font);
-
-              my $yearsold = int($daysold / 365);
-              $daysold -= $yearsold * 365;
-
-              $result .= ";\n $font";
-              my @age;
-              push @age, "1 year" if ($yearsold == 1);
-              push @age, "$yearsold years" if ($yearsold > 1);
-              push @age, "1 day" if ($daysold == 1);
-              push @age, "$daysold days" if ($daysold > 1);
-              $result .= join(" and ", @age);
-              $result .= " old$efont";
-        }
-    }
+         return join($param{separator},@return);
+     }
+}
 
-    $result .= ".";
+=head2 bug_links
 
-    return $result;
-}
+     join(', ', bug_links(bug => \@packages))
 
-=head2 htmlize_packagelinks
+Given a list of bugs, return a list of html which links to the bugs
 
-     htmlize_packagelinks
+=over
 
-Given a scalar containing a list of packages separated by something
-that L<Debbugs::CGI/splitpackages> can separate, returns a
-formatted set of links to packages.
+=item bug -- arrayref or scalar of bug(s)
 
-=cut
+=item links_only -- return only links, not htmlized links, defaults to
+returning htmlized links.
 
-sub htmlize_packagelinks {
-    my ($pkgs,$strong) = @_;
-    return unless defined $pkgs and $pkgs ne '';
-    my @pkglist = splitpackages($pkgs);
+=item class -- class of the a href, defaults to ''
 
-    $strong = 0;
-    my $openstrong  = $strong ? '<strong>' : '';
-    my $closestrong = $strong ? '</strong>' : '';
+=back
 
-    return 'Package' . (@pkglist > 1 ? 's' : '') . ': ' .
-           join(', ',
-                map {
-                    '<a class="submitter" href="' . pkg_url(pkg=>$_||'') . '">' .
-                    $openstrong . html_escape($_) . $closestrong . '</a>'
-                } @pkglist
-           );
+=cut
+
+sub bug_links {
+     my %param = validate_with(params => \@_,
+                              spec   => {bug => {type => SCALAR|ARRAYREF,
+                                                 optional => 1,
+                                                },
+                                         links_only => {type => BOOLEAN,
+                                                        default => 0,
+                                                       },
+                                         class => {type => SCALAR,
+                                                   default => '',
+                                                  },
+                                         separator => {type => SCALAR,
+                                                       default => ', ',
+                                                      },
+                                         options => {type => HASHREF,
+                                                     default => {},
+                                                    },
+                                        },
+                             );
+     my %options = %{$param{options}};
+
+     for (qw(bug)) {
+         delete $options{$_} if exists $options{$_};
+     }
+     my @links;
+     push @links, map {(munge_url('bugreport.cgi?',
+                                 %options,
+                                 bug => $_,
+                                ),
+                       $_);
+                 } make_list($param{bug}) if exists $param{bug};
+     my @return;
+     my ($link,$link_name);
+     my $class = '';
+     if (length $param{class}) {
+         $class = q( class=").html_escape($param{class}).q(");
+     }
+     while (($link,$link_name) = splice(@links,0,2)) {
+         if ($param{links_only}) {
+              push @return,$link
+         }
+         else {
+              push @return,
+                   qq(<a$class href=").
+                        html_escape($link).q(">).
+                             html_escape($link_name).q(</a>);
+         }
+     }
+     if (wantarray) {
+         return @return;
+     }
+     else {
+         return join($param{separator},@return);
+     }
 }
 
 
+
 =head2 maybelink
 
      maybelink($in);
@@ -444,6 +563,8 @@ or submitterurl which returns the URL for each individual address.
 
 sub htmlize_addresslinks {
      my ($prefixfunc, $urlfunc, $addresses,$class) = @_;
+     carp "htmlize_addresslinks is deprecated";
+
      $class = defined $class?qq(class="$class" ):'';
      if (defined $addresses and $addresses ne '') {
          my @addrs = getparsedaddrs($addresses);
@@ -473,10 +594,11 @@ sub emailfromrfc822{
      return $addr;
 }
 
-sub mainturl { pkg_url(maint => emailfromrfc822($_[0])); }
-sub submitterurl { pkg_url(submitter => emailfromrfc822($_[0])); }
+sub mainturl { package_links(maint => $_[0], links_only => 1); }
+sub submitterurl { package_links(submitter => $_[0], links_only => 1); }
 sub htmlize_maintlinks {
     my ($prefixfunc, $maints) = @_;
+    carp "htmlize_maintlinks is deprecated";
     return htmlize_addresslinks($prefixfunc, \&mainturl, $maints);
 }
 
@@ -484,33 +606,6 @@ sub htmlize_maintlinks {
 our $_maintainer;
 our $_maintainer_rev;
 
-=head2 bug_links
-
-     bug_links($one_bug);
-     bug_links($starting_bug,$stoping_bugs,);
-
-Creates a set of links to bugs, starting with bug number
-$starting_bug, and finishing with $stoping_bug; if only one bug is
-passed, makes a link to only a single bug.
-
-The content of the link is the bug number.
-
-XXX Use L<Params::Validate>; we want to be able to support query
-arguments here too.
-
-=cut
-
-sub bug_links{
-     my ($start,$stop,$query_arguments) = @_;
-     $stop = $stop || $start;
-     $query_arguments ||= '';
-     my @output;
-     for my $bug ($start..$stop) {
-         push @output,'<a href="'.bug_url($bug,'').qq(">$bug</a>);
-     }
-     return join(', ',@output);
-}
-
 =head2 bug_linklist
 
      bug_linklist($separator,$class,@bugs)
@@ -528,14 +623,234 @@ too.]
 
 sub bug_linklist{
      my ($sep,$class,@bugs) = @_;
-     if (length $class) {
-         $class = qq(class="$class" );
+     carp "bug_linklist is deprecated; use bug_links instead";
+     return scalar bug_links(bug=>\@bugs,class=>$class,separator=>$sep);
+}
+
+
+
+=head1 Forms
+
+=cut
+
+=head2 form_options_and_normal_param
+
+     my ($form_option,$param) = form_options_and_normal_param(\%param)
+           if $param{form_options};
+     my $form_option = form_options_and_normal_param(\%param)
+           if $param{form_options};
+
+Translates from special form_options to a set of parameters which can
+be used to run the current page.
+
+The idea behind this is to allow complex forms to relatively easily
+cause options that the existing cgi scripts understand to be set.
+
+Currently there are two commands which are understood:
+combine, and concatenate.
+
+=head3 combine
+
+Combine works by entering key,value pairs into the parameters using
+the key field option input field, and the value field option input
+field.
+
+For example, you would have
+
+ <input type="hidden" name="_fo_combine_key_fo_searchkey_value_fo_searchvalue" value="1">
+
+which would combine the _fo_searchkey and _fo_searchvalue input fields, so
+
+ <input type="text" name="_fo_searchkey" value="foo">
+ <input type="text" name="_fo_searchvalue" value="bar">
+
+would yield foo=>'bar' in %param.
+
+=head3 concatenate
+
+Concatenate concatenates values into a single entry in a parameter
+
+For example, you would have
+
+ <input type="hidden" name="_fo_concatentate_into_foo_with_:_fo_blah_fo_bleargh" value="1">
+
+which would combine the _fo_searchkey and _fo_searchvalue input fields, so
+
+ <input type="text" name="_fo_blah" value="bar">
+ <input type="text" name="_fo_bleargh" value="baz">
+
+would yield foo=>'bar:baz' in %param.
+
+
+=cut
+
+my $form_option_leader = '_fo_';
+sub form_options_and_normal_param{
+     my ($orig_param) = @_;
+     # all form_option parameters start with _fo_
+     my ($param,$form_option) = ({},{});
+     for my $key (keys %{$orig_param}) {
+         if ($key =~ /^\Q$form_option_leader\E/) {
+              $form_option->{$key} = $orig_param->{$key};
+         }
+         else {
+              $param->{$key} = $orig_param->{$key};
+         }
      }
-     return join($sep,map{qq(<a ${class}href=").
-                              bug_url($_).qq(">#$_</a>)
-                         } @bugs);
+     # at this point, we check for commands
+ COMMAND: for my $key (keys %{$form_option}) {
+         $key =~ s/^\Q$form_option_leader\E//;
+         if (my ($key_name,$value_name) = 
+             $key =~ /combine_key(\Q$form_option_leader\E.+)
+             _value(\Q$form_option_leader\E.+)$/x
+            ) {
+              next unless defined $form_option->{$key_name};
+              next unless defined $form_option->{$value_name};
+              my @keys = make_list($form_option->{$key_name});
+              my @values = make_list($form_option->{$value_name});
+              for my $i (0 .. $#keys) {
+                   last if $i > $#values;
+                   next if not defined $keys[$i];
+                   next if not defined $values[$i];
+                   __add_to_param($param,
+                                  $keys[$i],
+                                  $values[$i],
+                                 );
+              }
+         }
+         elsif (my ($field,$concatenate_key,$fields) = 
+                $key =~ /concatenate_into_(.+?)((?:_with_[^_])?)
+                         ((?:\Q$form_option_leader\E.+?)+)
+                         $/x
+               ) {
+              if (length $concatenate_key) {
+                   $concatenate_key =~ s/_with_//;
+              }
+              else {
+                   $concatenate_key = ':';
+              }
+              my @fields = $fields =~ m/(\Q$form_option_leader\E.+?)(?:(?=\Q$form_option_leader\E)|$)/g;
+              my %field_list;
+              my $max_num = 0;
+              for my $f (@fields) {
+                   next COMMAND unless defined $form_option->{$f};
+                   $field_list{$f} = [make_list($form_option->{$f})];
+                   $max_num = max($max_num,$#{$field_list{$f}});
+              }
+              for my $i (0 .. $max_num) {
+                   next unless @fields == grep {$i <= $#{$field_list{$_}} and
+                                                     defined $field_list{$_}[$i]} @fields;
+                   __add_to_param($param,
+                                  $field,
+                                  join($concatenate_key,
+                                       map {$field_list{$_}[$i]} @fields
+                                      )
+                                 );
+              }
+         }
+     }
+     return wantarray?($form_option,$param):$form_option;
 }
 
+=head2 option_form
+
+     print option_form(template=>'pkgreport_options',
+                      param   => \%param,
+                      form_options => $form_options,
+                     )
+
+
+
+=cut
+
+sub option_form{
+     my %param = validate_with(params => \@_,
+                              spec   => {template => {type => SCALAR,
+                                                     },
+                                         variables => {type => HASHREF,
+                                                       default => {},
+                                                      },
+                                         language => {type => SCALAR,
+                                                      optional => 1,
+                                                     },
+                                         param => {type => HASHREF,
+                                                   default => {},
+                                                  },
+                                         form_options => {type => HASHREF,
+                                                          default => {},
+                                                         },
+                                        },
+                             );
+
+     # First, we need to see if we need to add particular types of
+     # parameters
+     my $variables = dclone($param{variables});
+     $variables->{param} = dclone($param{param});
+     for my $key (keys %{$param{form_option}}) {
+         # strip out leader; shouldn't be anything here without one,
+         # but skip stupid things anyway
+         my $o_key = $key;
+         next unless $key =~ s/^\Q$form_option_leader\E//;
+         if ($key =~ /^add_(.+)$/) {
+              # this causes a specific parameter to be added
+              __add_to_param($variables->{param},
+                             $1,
+                             ''
+                            );
+         }
+         elsif ($key =~ /^delete_(.+?)(?:_(\d+))?$/) {
+              next unless exists $variables->{param}{$1};
+              if (ref $variables->{param}{$1} eq 'ARRAY' and
+                  defined $2 and
+                  defined $variables->{param}{$1}[$2]
+                 ) {
+                   splice @{$variables->{param}{$1}},$2,1;
+              }
+              else {
+                   delete $variables->{param}{$1};
+              }
+         }
+         # we'll add extra comands here once I figure out what they
+         # should be
+     }
+     # add in a few utility routines
+     $variables->{output_select_options} = sub {
+         my ($options,$value) = @_;
+         my @options = @{$options};
+         my $output = '';
+         while (my ($o_value,$name) = splice @options,0,2) {
+              my $selected = '';
+              if (defined $value and $o_value eq $value) {
+                   $selected = ' selected';
+              }
+              $output .= qq(<option value="$o_value"$selected>$name</option>\n);
+         }
+         return $output;
+     };
+     $variables->{make_list} = sub { make_list(@_);
+     };
+     # now at this point, we're ready to create the template
+     return Debbugs::Text::fill_in_template(template=>$param{template},
+                                           (exists $param{language}?(language=>$param{language}):()),
+                                           variables => $variables,
+                                          );
+}
+
+sub __add_to_param{
+     my ($param,$key,@values) = @_;
+
+     if (exists $param->{$key} and not
+        ref $param->{$key}) {
+         @{$param->{$key}} = [$param->{$key},
+                              @values
+                             ];
+     }
+     else {
+         push @{$param->{$key}}, @values;
+     }
+}
+
+
 
 =head1 misc
 
diff --git a/Debbugs/CGI/Bugreport.pm b/Debbugs/CGI/Bugreport.pm
new file mode 100644 (file)
index 0000000..faed9be
--- /dev/null
@@ -0,0 +1,409 @@
+# This module is part of debbugs, and is released
+# under the terms of the GPL version 2, or any later version. See the
+# file README and COPYING for more information.
+#
+# [Other people have contributed to this file; their copyrights should
+# be listed here too.]
+# Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
+
+
+package Debbugs::CGI::Bugreport;
+
+=head1 NAME
+
+Debbugs::CGI::Bugreport -- specific routines for the bugreport cgi script
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+
+=head1 BUGS
+
+None known.
+
+=cut
+
+use warnings;
+use strict;
+use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
+use base qw(Exporter);
+
+use IO::Scalar;
+use Params::Validate qw(validate_with :types);
+use Debbugs::MIME qw(convert_to_utf8 decode_rfc1522 create_mime_message);
+use Debbugs::CGI qw(:url :html :util);
+use Debbugs::Common qw(globify_scalar);
+use POSIX qw(strftime);
+
+BEGIN{
+     ($VERSION) = q$Revision: 494 $ =~ /^Revision:\s+([^\s+])/;
+     $DEBUG = 0 unless defined $DEBUG;
+
+     @EXPORT = ();
+     %EXPORT_TAGS = ();
+     @EXPORT_OK = (qw(display_entities handle_record handle_email_message));
+     Exporter::export_ok_tags(keys %EXPORT_TAGS);
+     $EXPORT_TAGS{all} = [@EXPORT_OK];
+}
+
+
+
+=head2 display_entity
+
+     display_entity(entity      => $entity,
+                    bug_num     => $ref,
+                    outer       => 1,
+                    msg_num     => $msg_num,
+                    attachments => \@attachments,
+                    output      => \$output);
+
+
+=over
+
+=item entity -- MIME::Parser entity
+
+=item bug_num -- Bug number
+
+=item outer -- Whether this is the outer entity; defaults to 1
+
+=item msg_num -- message number in the log
+
+=item attachments -- arrayref of attachments
+
+=item output -- scalar reference for output
+
+=back
+
+=cut
+
+sub display_entity {
+    my %param = validate_with(params => \@_,
+                             spec   => {entity      => {type => OBJECT,
+                                                       },
+                                        bug_num     => {type => SCALAR,
+                                                        regex => qr/^\d+$/,
+                                                       },
+                                        outer       => {type => BOOLEAN,
+                                                        default => 1,
+                                                       },
+                                        msg_num     => {type => SCALAR,
+                                                       },
+                                        attachments => {type => ARRAYREF,
+                                                        default => [],
+                                                       },
+                                        output      => {type => SCALARREF|HANDLE,
+                                                        default => \*STDOUT,
+                                                       },
+                                        terse       => {type => BOOLEAN,
+                                                        default => 0,
+                                                       },
+                                        msg         => {type => SCALAR,
+                                                        optional => 1,
+                                                       },
+                                        att         => {type => SCALAR,
+                                                        optional => 1,
+                                                       },
+                                        trim_headers => {type => BOOLEAN,
+                                                         default => 1,
+                                                        },
+                                       }
+                            );
+
+    $param{output} = globify_scalar($param{output});
+    my $entity = $param{entity};
+    my $ref = $param{bug_num};
+    my $top = $param{outer};
+    my $xmessage = $param{msg_num};
+    my $attachments = $param{attachments};
+
+    my $head = $entity->head;
+    my $disposition = $head->mime_attr('content-disposition');
+    $disposition = 'inline' if not defined $disposition or $disposition eq '';
+    my $type = $entity->effective_type;
+    my $filename = $entity->head->recommended_filename;
+    $filename = '' unless defined $filename;
+    $filename = decode_rfc1522($filename);
+
+    if ($param{outer} and
+       not $param{terse} and
+       not exists $param{att}) {
+        my $header = $entity->head;
+        print {$param{output}} "<pre class=\"headers\">\n";
+        if ($param{trim_headers}) {
+             my @headers;
+             foreach (qw(From To Cc Subject Date)) {
+                  my $head_field = $head->get($_);
+                  next unless defined $head_field and $head_field ne '';
+                  push @headers, qq(<b>$_:</b> ) . html_escape(decode_rfc1522($head_field));
+             }
+             print {$param{output}} join(qq(), @headers);
+        } else {
+             print {$param{output}} html_escape(decode_rfc1522($entity->head->stringify));
+        }
+        print {$param{output}} "</pre>\n";
+    }
+
+    if (not (($param{outer} and $type =~ m{^text(?:/plain)?(?:;|$)})
+            or $type =~ m{^multipart/}
+           )) {
+       push @$attachments, $param{entity};
+       # output this attachment
+       if (exists $param{att} and
+           $param{att} == $#$attachments) {
+           my $head = $entity->head;
+           chomp(my $type = $entity->effective_type);
+           my $body = $entity->stringify_body;
+           print {$param{output}} "Content-Type: $type";
+           my ($charset) = $head->get('Content-Type:') =~ m/charset\s*=\s*\"?([\w-]+)\"?/i;
+           print {$param{output}} qq(; charset="$charset") if defined $charset;
+           print {$param{output}}"\n";
+           if ($filename ne '') {
+               my $qf = $filename;
+               $qf =~ s/"/\\"/g;
+               $qf =~ s[.*/][];
+               print {$param{output}} qq{Content-Disposition: inline; filename="$qf"\n};
+           }
+           print {$param{output}} "\n";
+           my $decoder = MIME::Decoder->new($head->mime_encoding);
+           $decoder->decode(IO::Scalar->new(\$body), $param{output});
+           return;
+       }
+       elsif (not exists $param{att}) {
+            my @dlargs = (msg=>$xmessage, att=>$#$attachments);
+            push @dlargs, (filename=>$filename) if $filename ne '';
+            my $printname = $filename;
+            $printname = 'Message part ' . ($#$attachments + 1) if $filename eq '';
+            print {$param{output}} '<pre class="mime">[<a href="' .
+                 html_escape(bug_links(bug => $ref,
+                                       links_only => 1,
+                                       options => {@dlargs})
+                            ) . qq{">$printname</a> } .
+                                 "($type, $disposition)]</pre>\n";
+       }
+    }
+
+    return if not $param{outer} and $disposition eq 'attachment' and not exists $param{att};
+    return unless ($type =~ m[^text/?] and
+                  $type !~ m[^text/(?:html|enriched)(?:;|$)]) or
+                 $type =~ m[^application/pgp(?:;|$)] or
+                 $entity->parts;
+
+    if ($entity->is_multipart) {
+       my @parts = $entity->parts;
+       foreach my $part (@parts) {
+           display_entity(entity => $part,
+                          bug_num => $ref,
+                          outer => 0,
+                          msg_num => $xmessage,
+                          output => $param{output},
+                          attachments => $attachments,
+                          terse => $param{terse},
+                          exists $param{msg}?(msg=>$param{msg}):(),
+                          exists $param{att}?(att=>$param{att}):(),
+                         );
+           # print {$param{output}} "\n";
+       }
+    } elsif ($entity->parts) {
+       # We must be dealing with a nested message.
+        if (not exists $param{att}) {
+             print {$param{output}} "<blockquote>\n";
+        }
+       my @parts = $entity->parts;
+       foreach my $part (@parts) {
+           display_entity(entity => $part,
+                          bug_num => $ref,
+                          outer => 1,
+                          msg_num => $xmessage,
+                          ouput => $param{output},
+                          attachments => $attachments,
+                          terse => $param{terse},
+                          exists $param{msg}?(msg=>$param{msg}):(),
+                          exists $param{att}?(att=>$param{att}):(),
+                         );
+           # print {$param{output}} "\n";
+       }
+        if (not exists $param{att}) {
+             print {$param{output}} "</blockquote>\n";
+        }
+    } elsif (not $param{terse}) {
+        my $content_type = $entity->head->get('Content-Type:') || "text/html";
+        my ($charset) = $content_type =~ m/charset\s*=\s*\"?([\w-]+)\"?/i;
+        my $body = $entity->bodyhandle->as_string;
+        $body = convert_to_utf8($body,$charset) if defined $charset;
+        $body = html_escape($body);
+        # Attempt to deal with format=flowed
+        if ($content_type =~ m/format\s*=\s*\"?flowed\"?/i) {
+             $body =~ s{^\ }{}mgo;
+             # we ignore the other things that you can do with
+             # flowed e-mails cause they don't really matter.
+        }
+        # Add links to URLs
+        # We don't html escape here because we escape above;
+        # wierd terminators are because of that
+        $body =~ s{((?:ftp|http|https|svn|ftps|rsync)://[\S~-]+?/?) # Url
+                   ((?:\&gt\;)?[)]?(?:'|\&\#39\;)?[:.\,]?(?:\s|$)) # terminators
+             }{<a href=\"$1\">$1</a>$2}gox;
+        # Add links to bug closures
+        $body =~ s[(closes:\s*(?:bug)?\#?\s?\d+(?:,?\s*(?:bug)?\#?\s?\d+)*)]
+                  [my $temp = $1;
+                   $temp =~ s{(\d+)}
+                             {bug_links(bug=>$1)}ge;
+                   $temp;]gxie;
+
+        if (not exists $param{att}) {
+             print {$param{output}} qq(<pre class="message">$body</pre>\n);
+        }
+    }
+}
+
+
+=head2 handle_email_message
+
+     handle_email_message($record->{text},
+                         ref        => $bug_number,
+                         msg_num => $msg_number,
+                        );
+
+Returns a decoded e-mail message and displays entities/attachments as
+appropriate.
+
+
+=cut
+
+sub handle_email_message{
+     my ($email,%param) = @_;
+
+     my $output = '';
+     my $parser = MIME::Parser->new();
+     # Because we are using memory, not tempfiles, there's no need to
+     # clean up here like in Debbugs::MIME
+     $parser->tmp_to_core(1);
+     $parser->output_to_core(1);
+     my $entity = $parser->parse_data( $email);
+     my @attachments = ();
+     display_entity(entity  => $entity,
+                   bug_num => $param{ref},
+                   outer   => 1,
+                   msg_num => $param{msg_num},
+                   output => \$output,
+                   attachments => \@attachments,
+                   terse       => $param{terse},
+                   exists $param{msg}?(msg=>$param{msg}):(),
+                   exists $param{att}?(att=>$param{att}):(),
+                  );
+     return $output;
+
+}
+
+=head2 handle_record
+
+     push @log, handle_record($record,$ref,$msg_num);
+
+Deals with a record in a bug log as returned by
+L<Debbugs::Log::read_log_records>; returns the log information that
+should be output to the browser.
+
+=cut
+
+sub handle_record{
+     my ($record,$bug_number,$msg_number,$seen_msg_ids) = @_;
+
+     my $output = '';
+     local $_ = $record->{type};
+     if (/html/) {
+         my ($time) = $record->{text} =~ /<!--\s+time:(\d+)\s+-->/;
+         my $class = $record->{text} =~ /^<strong>(?:Acknowledgement|Reply|Information|Report|Notification)/ ? 'infmessage':'msgreceived';
+         $output .= decode_rfc1522($record->{text});
+         # Link to forwarded http:// urls in the midst of the report
+         # (even though these links already exist at the top)
+         $output =~ s,((?:ftp|http|https)://[\S~-]+?/?)([\)\'\:\.\,]?(?:\s|\.<|$)),<a href=\"$1\">$1</a>$2,go;
+         # Add links to the cloned bugs
+         $output =~ s{(Bug )(\d+)( cloned as bugs? )(\d+)(?:\-(\d+)|)}{$1.bug_links(bug=>$2).$3.bug_links(bug=>[$4..$5])}eo;
+         # Add links to merged bugs
+         $output =~ s{(?<=Merged )([\d\s]+)(?=\.)}{join(' ',map {bug_links(bug=>$_)} (split /\s+/, $1))}eo;
+         # Add links to blocked bugs
+         $output =~ s{(?<=Blocking bugs)(?:( of )(\d+))?( (?:added|set to|removed):\s+)([\d\s\,]+)}
+                     {(defined $2?$1.bug_links(bug=>$2):'').$3.
+                           join(' ',map {bug_links(bug=>$_)} (split /\,?\s+/, $4))}eo;
+         # Add links to reassigned packages
+         $output =~ s{(Bug reassigned from package \`)([^']+?)((?:'|\&\#39;) to \`)([^']+?)((?:'|\&\#39;))}
+         {$1.q(<a href=").html_escape(pkg_url(pkg=>$2)).qq(">$2</a>).$3.q(<a href=").html_escape(pkg_url(pkg=>$4)).qq(">$4</a>).$5}eo;
+         if (defined $time) {
+              $output .= ' ('.strftime('%a, %d %b %Y %T GMT',gmtime($time)).') ';
+         }
+         $output .= '<a href="' .
+              html_escape(bug_links(bug => $bug_number,
+                                    options => {msg => ($msg_number+1)},
+                                    links_only => 1,
+                                   )
+                         ) . '">Full text</a> and <a href="' .
+                              html_escape(bug_links(bug => $bug_number,
+                                                    options => {msg => ($msg_number+1),
+                                                                mbox => 'yes'},
+                                                    links_only => 1)
+                                         ) . '">rfc822 format</a> available.';
+
+         $output = qq(<div class="$class"><hr>\n<a name="$msg_number"></a>\n) . $output . "</div>\n";
+     }
+     elsif (/recips/) {
+         my ($msg_id) = $record->{text} =~ /^Message-Id:\s+<(.+)>/im;
+         if (defined $msg_id and exists $$seen_msg_ids{$msg_id}) {
+              return ();
+         }
+         elsif (defined $msg_id) {
+              $$seen_msg_ids{$msg_id} = 1;
+         }
+         $output .= qq(<hr><p class="msgreceived"><a name="$msg_number"></a>\n);
+         $output .= 'View this message in <a href="' . html_escape(bug_links(bug=>$bug_number, links_only => 1, options=>{msg=>$msg_number, mbox=>'yes'})) . '">rfc822 format</a></p>';
+         $output .= handle_email_message($record->{text},
+                                         ref     => $bug_number,
+                                         msg_num => $msg_number,
+                                        );
+     }
+     elsif (/autocheck/) {
+         # Do nothing
+     }
+     elsif (/incoming-recv/) {
+         my ($msg_id) = $record->{text} =~ /^Message-Id:\s+<(.+)>/im;
+         if (defined $msg_id and exists $$seen_msg_ids{$msg_id}) {
+              return ();
+         }
+         elsif (defined $msg_id) {
+              $$seen_msg_ids{$msg_id} = 1;
+         }
+         # Incomming Mail Message
+         my ($received,$hostname) = $record->{text} =~ m/Received: \(at (\S+)\) by (\S+)\;/;
+         $output .= qq|<hr><p class="msgreceived"><a name="$msg_number"></a><a name="msg$msg_number"></a><a href="#$msg_number">Message #$msg_number</a> received at |.
+              html_escape("$received\@$hostname") .
+                   q| (<a href="| . html_escape(bug_links(bug => $bug_number, links_only => 1, options => {msg=>$msg_number})) . '">full text</a>'.
+                        q|, <a href="| . html_escape(bug_links(bug => $bug_number,
+                                                               links_only => 1,
+                                                               options => {msg=>$msg_number,
+                                                                           mbox=>'yes'}
+                                                              )
+                                                    ) .'">mbox</a>)'.":</p>\n";
+         $output .= handle_email_message($record->{text},
+                                         ref     => $bug_number,
+                                         msg_num => $msg_number,
+                                        );
+     }
+     else {
+         die "Unknown record type $_";
+     }
+     return $output;
+}
+
+
+
+1;
+
+
+__END__
+
+
+
+
+
+
diff --git a/Debbugs/CGI/Pkgreport.pm b/Debbugs/CGI/Pkgreport.pm
new file mode 100644 (file)
index 0000000..51ebe42
--- /dev/null
@@ -0,0 +1,790 @@
+# This module is part of debbugs, and is released
+# under the terms of the GPL version 2, or any later version. See the
+# file README and COPYING for more information.
+#
+# [Other people have contributed to this file; their copyrights should
+# be listed here too.]
+# Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
+
+
+package Debbugs::CGI::Pkgreport;
+
+=head1 NAME
+
+Debbugs::CGI::Pkgreport -- specific routines for the pkgreport cgi script
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+
+=head1 BUGS
+
+None known.
+
+=cut
+
+use warnings;
+use strict;
+use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
+use base qw(Exporter);
+
+use IO::Scalar;
+use Params::Validate qw(validate_with :types);
+
+use Debbugs::Config qw(:config :globals);
+use Debbugs::CGI qw(:url :html :util);
+use Debbugs::Common qw(:misc :util :date);
+use Debbugs::Status qw(:status);
+use Debbugs::Bugs qw(bug_filter);
+use Debbugs::Packages qw(:mapping);
+
+use Debbugs::Text qw(:templates);
+
+use POSIX qw(strftime);
+
+
+BEGIN{
+     ($VERSION) = q$Revision: 494 $ =~ /^Revision:\s+([^\s+])/;
+     $DEBUG = 0 unless defined $DEBUG;
+
+     @EXPORT = ();
+     %EXPORT_TAGS = (html => [qw(short_bug_status_html pkg_htmlizebugs),
+                             qw(pkg_javascript),
+                             qw(pkg_htmlselectyesno pkg_htmlselectsuite),
+                             qw(buglinklist pkg_htmlselectarch)
+                            ],
+                    misc => [qw(generate_package_info make_order_list),
+                             qw(myurl),
+                             qw(get_bug_order_index determine_ordering),
+                            ],
+                   );
+     @EXPORT_OK = (qw());
+     Exporter::export_ok_tags(keys %EXPORT_TAGS);
+     $EXPORT_TAGS{all} = [@EXPORT_OK];
+}
+
+=head2 generate_package_info
+
+     generate_package_info($srcorbin,$package)
+
+Generates the informational bits for a package and returns it
+
+=cut
+
+sub generate_package_info{
+     my %param = validate_with(params => \@_,
+                              spec  => {binary => {type => BOOLEAN,
+                                                   default => 1,
+                                                  },
+                                        package => {type => SCALAR|ARRAYREF,
+                                                   },
+                                        options => {type => HASHREF,
+                                                   },
+                                        bugs    => {type => ARRAYREF,
+                                                   },
+                                       },
+                             );
+
+     my $output_scalar = '';
+     my $output = globify_scalar(\$output_scalar);
+
+     my $package = $param{package};
+
+     my %pkgsrc = %{getpkgsrc()};
+     my $srcforpkg = $package;
+     if ($param{binary} and exists $pkgsrc{$package}
+        and defined $pkgsrc{$package}) {
+         $srcforpkg = $pkgsrc{$package};
+     }
+
+     my $showpkg = html_escape($package);
+     my $maintainers = getmaintainers();
+     my $maint = $maintainers->{$srcforpkg};
+     if (defined $maint) {
+         print {$output} '<p>';
+         print {$output} (($maint =~ /,/)? "Maintainer for $showpkg is "
+                          : "Maintainers for $showpkg are ") .
+                               package_links(maint => $maint);
+         print {$output} ".</p>\n";
+     }
+     else {
+         print {$output} "<p>No maintainer for $showpkg. Please do not report new bugs against this package.</p>\n";
+     }
+     my @pkgs = getsrcpkgs($srcforpkg);
+     @pkgs = grep( !/^\Q$package\E$/, @pkgs );
+     if ( @pkgs ) {
+         @pkgs = sort @pkgs;
+         if ($param{binary}) {
+              print {$output} "<p>You may want to refer to the following packages that are part of the same source:\n";
+         }
+         else {
+              print {$output} "<p>You may want to refer to the following individual bug pages:\n";
+         }
+         #push @pkgs, $src if ( $src && !grep(/^\Q$src\E$/, @pkgs) );
+         print {$output} scalar package_links(package=>[@pkgs]);
+         print {$output} ".\n";
+     }
+     my @references;
+     my $pseudodesc = getpseudodesc();
+     if ($package and defined($pseudodesc) and exists($pseudodesc->{$package})) {
+         push @references, "to the <a href=\"http://${debbugs::gWebDomain}/pseudo-packages${debbugs::gHTMLSuffix}\">".
+              "list of other pseudo-packages</a>";
+     }
+     else {
+         if ($package and defined $gPackagePages) {
+              push @references, sprintf "to the <a href=\"%s\">%s package page</a>",
+                   html_escape("http://${gPackagePages}/$package"), html_escape("$package");
+         }
+         if (defined $gSubscriptionDomain) {
+              my $ptslink = $param{binary} ? $srcforpkg : $package;
+              push @references, q(to the <a href="http://).html_escape("$gSubscriptionDomain/$ptslink").q(">Package Tracking System</a>);
+         }
+         # Only output this if the source listing is non-trivial.
+         if ($param{binary} and $srcforpkg) {
+              push @references,
+                   "to the source package ".
+                        package_links(src=>$srcforpkg,
+                                      options => $param{options}) .
+                             "'s bug page";
+         }
+     }
+     if (@references) {
+         $references[$#references] = "or $references[$#references]" if @references > 1;
+         print {$output} "<p>You might like to refer ", join(", ", @references), ".</p>\n";
+     }
+     if (defined $param{maint} || defined $param{maintenc}) {
+         print {$output} "<p>If you find a bug not listed here, please\n";
+         printf {$output} "<a href=\"%s\">report it</a>.</p>\n",
+              html_escape("http://${debbugs::gWebDomain}/Reporting${debbugs::gHTMLSuffix}");
+     }
+     if (not $maint and not @{$param{bugs}}) {
+         print {$output} "<p>There is no record of the " . html_escape($package) .
+              ($param{binary} ? " package" : " source package") .
+                   ", and no bugs have been filed against it.</p>";
+     }
+     return $output_scalar;
+}
+
+
+=head2 short_bug_status_html
+
+     print short_bug_status_html(status => read_bug(bug => 5),
+                                 options => \%param,
+                                );
+
+=over
+
+=item status -- status hashref as returned by read_bug
+
+=item options -- hashref of options to pass to package_links (defaults
+to an empty hashref)
+
+=item bug_options -- hashref of options to pass to bug_links (default
+to an empty hashref)
+
+=item snippet -- optional snippet of information about the bug to
+display below
+
+
+=back
+
+
+
+=cut
+
+sub short_bug_status_html {
+     my %param = validate_with(params => \@_,
+                              spec   => {status => {type => HASHREF,
+                                                   },
+                                         options => {type => HASHREF,
+                                                     default => {},
+                                                    },
+                                         bug_options => {type => HASHREF,
+                                                         default => {},
+                                                        },
+                                         snippet => {type => SCALAR,
+                                                     default => '',
+                                                    },
+                                        },
+                             );
+
+     my %status = %{$param{status}};
+
+     $status{tags_array} = [sort(split(/\s+/, $status{tags}))];
+     $status{date_text} = strftime('%a, %e %b %Y %T UTC', gmtime($status{date}));
+     $status{mergedwith_array} = [split(/ /,$status{mergedwith})];
+
+     my @blockedby= split(/ /, $status{blockedby});
+     $status{blockedby_array} = [];
+     if (@blockedby && $status{"pending"} ne 'fixed' && ! length($status{done})) {
+         for my $b (@blockedby) {
+              my %s = %{get_bug_status($b)};
+              next if $s{"pending"} eq 'fixed' || length $s{done};
+              push @{$status{blockedby_array}},{bug_num => $b, subject => $s{subject}, status => \%s};
+         }
+     }
+
+     my @blocks= split(/ /, $status{blocks});
+     $status{blocks_array} = [];
+     if (@blocks && $status{"pending"} ne 'fixed' && ! length($status{done})) {
+         for my $b (@blocks) {
+              my %s = %{get_bug_status($b)};
+              next if $s{"pending"} eq 'fixed' || length $s{done};
+              push @{$status{blocks_array}}, {bug_num => $b, subject => $s{subject}, status => \%s};
+         }
+     }
+
+
+     return fill_in_template(template => 'cgi/short_bug_status',
+                            variables => {status => \%status,
+                                          isstrongseverity => \&Debbugs::Status::isstrongseverity,
+                                          html_escape   => \&Debbugs::CGI::html_escape,
+                                          looks_like_number => \&Scalar::Util::looks_like_number,
+                                         },
+                            hole_var  => {'&package_links' => \&Debbugs::CGI::package_links,
+                                          '&bug_links'     => \&Debbugs::CGI::bug_links,
+                                          '&version_url'   => \&Debbugs::CGI::version_url,
+                                          '&secs_to_english' => \&Debbugs::Common::secs_to_english,
+                                          '&strftime'      => \&POSIX::strftime,
+                                         },
+                           );
+
+     my $result = "";
+
+     my $showseverity;
+     if ($status{severity} eq 'normal') {
+         $showseverity = '';
+     }
+     elsif (isstrongseverity($status{severity})) {
+         $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
+     }
+     else {
+         $showseverity = "Severity: <em>$status{severity}</em>;\n";
+     }
+
+     $result .= package_links(package => $status{package},
+                             options  => $param{options},
+                            );
+
+     my $showversions = '';
+     if (@{$status{found_versions}}) {
+         my @found = @{$status{found_versions}};
+         $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @found;
+     }
+     if (@{$status{fixed_versions}}) {
+         $showversions .= '; ' if length $showversions;
+         $showversions .= '<strong>fixed</strong>: ';
+         my @fixed = @{$status{fixed_versions}};
+         $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @fixed;
+     }
+     $result .= ' (<a href="'.
+         version_url(package => $status{package},
+                     found   => $status{found_versions},
+                     fixed   => $status{fixed_versions},
+                    ).qq{">$showversions</a>)} if length $showversions;
+     $result .= ";\n";
+
+     $result .= $showseverity;
+     $result .= "Reported by: ".package_links(submitter=>$status{originator},
+                                             class => "submitter",
+                                            );
+     $result .= ";\nOwned by: " . package_links(owner => $status{owner},
+                                               class => "submitter",
+                                              )
+         if length $status{owner};
+     $result .= ";\nTags: <strong>"
+         . html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
+              . "</strong>"
+                   if (length($status{tags}));
+
+     $result .= (length($status{mergedwith})?";\nMerged with ":"") .
+         bug_links(bug => [split(/ /,$status{mergedwith})],
+                   class => "submitter",
+                  );
+     $result .= (length($status{blockedby})?";\nBlocked by ":"") .
+         bug_links(bug => [split(/ /,$status{blockedby})],
+                   class => "submitter",
+                  );
+     $result .= (length($status{blocks})?";\nBlocks ":"") .
+         bug_links(bug => [split(/ /,$status{blocks})],
+                   class => "submitter",
+                  );
+
+     if (length($status{done})) {
+         $result .= "<br><strong>Done:</strong> " . html_escape($status{done});
+         my $days = bug_archiveable(bug => $status{id},
+                                    status => \%status,
+                                    days_until => 1,
+                                   );
+         if ($days >= 0 and defined $status{location} and $status{location} ne 'archive') {
+              $result .= ";\n<strong>Can be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
+         }
+         elsif (defined $status{location} and $status{location} eq 'archived') {
+              $result .= ";\n<strong>Archived.</strong>";
+         }
+     }
+
+     unless (length($status{done})) {
+         if (length($status{forwarded})) {
+              $result .= ";\n<strong>Forwarded</strong> to "
+                   . join(', ',
+                          map {maybelink($_)}
+                          split /\,\s+/,$status{forwarded}
+                         );
+         }
+         # Check the age of the logfile
+         my ($days_last,$eng_last) = secs_to_english(time - $status{log_modified});
+         my ($days,$eng) = secs_to_english(time - $status{date});
+
+         if ($days >= 7) {
+              my $font = "";
+              my $efont = "";
+              $font = "em" if ($days > 30);
+              $font = "strong" if ($days > 60);
+              $efont = "</$font>" if ($font);
+              $font = "<$font>" if ($font);
+
+              $result .= ";\n ${font}$eng old$efont";
+         }
+         if ($days_last > 7) {
+              my $font = "";
+              my $efont = "";
+              $font = "em" if ($days_last > 30);
+              $font = "strong" if ($days_last > 60);
+              $efont = "</$font>" if ($font);
+              $font = "<$font>" if ($font);
+
+              $result .= ";\n ${font}Modified $eng_last ago$efont";
+         }
+     }
+
+     $result .= ".";
+
+     return $result;
+}
+
+
+sub pkg_htmlizebugs {
+     my %param = validate_with(params => \@_,
+                              spec   => {bugs => {type => ARRAYREF,
+                                                 },
+                                         names => {type => ARRAYREF,
+                                                  },
+                                         title => {type => ARRAYREF,
+                                                  },
+                                         prior => {type => ARRAYREF,
+                                                  },
+                                         order => {type => ARRAYREF,
+                                                  },
+                                         ordering => {type => SCALAR,
+                                                     },
+                                         bugusertags => {type => HASHREF,
+                                                         default => {},
+                                                        },
+                                         bug_rev => {type => BOOLEAN,
+                                                     default => 0,
+                                                    },
+                                         bug_order => {type => SCALAR,
+                                                      },
+                                         repeatmerged => {type => BOOLEAN,
+                                                          default => 1,
+                                                         },
+                                         include => {type => ARRAYREF,
+                                                     default => [],
+                                                    },
+                                         exclude => {type => ARRAYREF,
+                                                     default => [],
+                                                    },
+                                         this     => {type => SCALAR,
+                                                      default => '',
+                                                     },
+                                         options  => {type => HASHREF,
+                                                      default => {},
+                                                     },
+                                        }
+                             );
+     my @bugs = @{$param{bugs}};
+
+     my @status = ();
+     my %count;
+     my $header = '';
+     my $footer = "<h2 class=\"outstanding\">Summary</h2>\n";
+
+     my @dummy = ($gRemoveAge); #, @gSeverityList, @gSeverityDisplay);  #, $gHTMLExpireNote);
+
+     if (@bugs == 0) {
+         return "<HR><H2>No reports found!</H2></HR>\n";
+     }
+
+     if ( $param{bug_rev} ) {
+         @bugs = sort {$b<=>$a} @bugs;
+     }
+     else {
+         @bugs = sort {$a<=>$b} @bugs;
+     }
+     my %seenmerged;
+
+     my %common = (
+                  'show_list_header' => 1,
+                  'show_list_footer' => 1,
+                 );
+
+     my %section = ();
+     # Make the include/exclude map
+     my %include;
+     my %exclude;
+     for my $include (make_list($param{include})) {
+         next unless defined $include;
+         my ($key,$value) = split /\s*:\s*/,$include,2;
+         unless (defined $value) {
+              $key = 'tags';
+              $value = $include;
+         }
+         push @{$include{$key}}, split /\s*,\s*/, $value;
+     }
+     for my $exclude (make_list($param{exclude})) {
+         next unless defined $exclude;
+         my ($key,$value) = split /\s*:\s*/,$exclude,2;
+         unless (defined $value) {
+              $key = 'tags';
+              $value = $exclude;
+         }
+         push @{$exclude{$key}}, split /\s*,\s*/, $value;
+     }
+
+     foreach my $bug (@bugs) {
+         my %status = %{get_bug_status(bug=>$bug,
+                                       (exists $param{dist}?(dist => $param{dist}):()),
+                                       bugusertags => $param{bugusertags},
+                                       (exists $param{version}?(version => $param{version}):()),
+                                       (exists $param{arch}?(arch => $param{arch}):(arch => $config{default_architectures})),
+                                      )};
+         next unless %status;
+         next if bug_filter(bug => $bug,
+                            status => \%status,
+                            repeat_merged => $param{repeatmerged},
+                            seen_merged => \%seenmerged,
+                            (keys %include ? (include => \%include):()),
+                            (keys %exclude ? (exclude => \%exclude):()),
+                           );
+
+         my $html = "<li>"; #<a href=\"%s\">#%d: %s</a>\n<br>",
+              #bug_url($bug), $bug, html_escape($status{subject});
+         $html .= short_bug_status_html(status  => \%status,
+                                        options => $param{options},
+                                       ) . "\n";
+         push @status, [ $bug, \%status, $html ];
+     }
+     if ($param{bug_order} eq 'age') {
+         # MWHAHAHAHA
+         @status = sort {$a->[1]{log_modified} <=> $b->[1]{log_modified}} @status;
+     }
+     elsif ($param{bug_order} eq 'agerev') {
+         @status = sort {$b->[1]{log_modified} <=> $a->[1]{log_modified}} @status;
+     }
+     for my $entry (@status) {
+         my $key = "";
+         for my $i (0..$#{$param{prior}}) {
+              my $v = get_bug_order_index($param{prior}[$i], $entry->[1]);
+              $count{"g_${i}_${v}"}++;
+              $key .= "_$v";
+         }
+         $section{$key} .= $entry->[2];
+         $count{"_$key"}++;
+     }
+
+     my $result = "";
+     if ($param{ordering} eq "raw") {
+         $result .= "<UL class=\"bugs\">\n" . join("", map( { $_->[ 2 ] } @status ) ) . "</UL>\n";
+     }
+     else {
+         $header .= "<div class=\"msgreceived\">\n<ul>\n";
+         my @keys_in_order = ("");
+         for my $o (@{$param{order}}) {
+              push @keys_in_order, "X";
+              while ((my $k = shift @keys_in_order) ne "X") {
+                   for my $k2 (@{$o}) {
+                        $k2+=0;
+                        push @keys_in_order, "${k}_${k2}";
+                   }
+              }
+         }
+         for my $order (@keys_in_order) {
+              next unless defined $section{$order};
+              my @ttl = split /_/, $order;
+              shift @ttl;
+              my $title = $param{title}[0]->[$ttl[0]] . " bugs";
+              if ($#ttl > 0) {
+                   $title .= " -- ";
+                   $title .= join("; ", grep {($_ || "") ne ""}
+                                  map { $param{title}[$_]->[$ttl[$_]] } 1..$#ttl);
+              }
+              $title = html_escape($title);
+
+              my $count = $count{"_$order"};
+              my $bugs = $count == 1 ? "bug" : "bugs";
+
+              $header .= "<li><a href=\"#$order\">$title</a> ($count $bugs)</li>\n";
+              if ($common{show_list_header}) {
+                   my $count = $count{"_$order"};
+                   my $bugs = $count == 1 ? "bug" : "bugs";
+                   $result .= "<H2 CLASS=\"outstanding\"><a name=\"$order\"></a>$title ($count $bugs)</H2>\n";
+              }
+              else {
+                   $result .= "<H2 CLASS=\"outstanding\">$title</H2>\n";
+              }
+              $result .= "<div class=\"msgreceived\">\n<UL class=\"bugs\">\n";
+              $result .= "\n\n\n\n";
+              $result .= $section{$order};
+              $result .= "\n\n\n\n";
+              $result .= "</UL>\n</div>\n";
+         } 
+         $header .= "</ul></div>\n";
+
+         $footer .= "<div class=\"msgreceived\">\n<ul>\n";
+         for my $i (0..$#{$param{prior}}) {
+              my $local_result = '';
+              foreach my $key ( @{$param{order}[$i]} ) {
+                   my $count = $count{"g_${i}_$key"};
+                   next if !$count or !$param{title}[$i]->[$key];
+                   $local_result .= "<li>$count $param{title}[$i]->[$key]</li>\n";
+              }
+              if ( $local_result ) {
+                   $footer .= "<li>$param{names}[$i]<ul>\n$local_result</ul></li>\n";
+              }
+         }
+         $footer .= "</ul>\n</div>\n";
+     }
+
+     $result = $header . $result if ( $common{show_list_header} );
+     $result .= $footer if ( $common{show_list_footer} );
+     return $result;
+}
+
+sub pkg_javascript {
+     return fill_in_template(template=>'cgi/pkgreport_javascript',
+                           );
+}
+
+sub pkg_htmlselectyesno {
+     my ($name, $n, $y, $default) = @_;
+     return sprintf('<select name="%s"><option value=no%s>%s</option><option value=yes%s>%s</option></select>', $name, ($default ? "" : " selected"), $n, ($default ? " selected" : ""), $y);
+}
+
+sub pkg_htmlselectsuite {
+     my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
+     my @suites = ("stable", "testing", "unstable", "experimental");
+     my %suiteaka = ("stable", "etch", "testing", "lenny", "unstable", "sid");
+     my $defaultsuite = "unstable";
+
+     my $result = sprintf '<select name=dist id="%s">', $id;
+     for my $s (@suites) {
+         $result .= sprintf '<option value="%s"%s>%s%s</option>',
+              $s, ($defaultsuite eq $s ? " selected" : ""),
+                   $s, (defined $suiteaka{$s} ? " (" . $suiteaka{$s} . ")" : "");
+     }
+     $result .= '</select>';
+     return $result;
+}
+
+sub pkg_htmlselectarch {
+     my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
+     my @arches = qw(alpha amd64 arm hppa i386 ia64 m68k mips mipsel powerpc s390 sparc);
+
+     my $result = sprintf '<select name=arch id="%s">', $id;
+     $result .= '<option value="any">any architecture</option>';
+     for my $a (@arches) {
+         $result .= sprintf '<option value="%s">%s</option>', $a, $a;
+     }
+     $result .= '</select>';
+     return $result;
+}
+
+sub myurl {
+     my %param = @_;
+     return html_escape(pkg_url(map {exists $param{$_}?($_,$param{$_}):()}
+                               qw(archive repeatmerged mindays maxdays),
+                               qw(version dist arch package src tag maint submitter)
+                              )
+                      );
+}
+
+sub make_order_list {
+     my $vfull = shift;
+     my @x = ();
+
+     if ($vfull =~ m/^([^:]+):(.*)$/) {
+         my $v = $1;
+         for my $vv (split /,/, $2) {
+              push @x, "$v=$vv";
+         }
+     }
+     else {
+         for my $v (split /,/, $vfull) {
+              next unless $v =~ m/.=./;
+              push @x, $v;
+         }
+     }
+     push @x, "";              # catch all
+     return @x;
+}
+
+sub get_bug_order_index {
+     my $order = shift;
+     my $status = shift;
+     my $pos = -1;
+
+     my %tags = ();
+     %tags = map { $_, 1 } split / /, $status->{"tags"}
+         if defined $status->{"tags"};
+
+     for my $el (@${order}) {
+         $pos++;
+         my $match = 1;
+         for my $item (split /[+]/, $el) {
+              my ($f, $v) = split /=/, $item, 2;
+              next unless (defined $f and defined $v);
+              my $isokay = 0;
+              $isokay = 1 if (defined $status->{$f} and $v eq $status->{$f});
+              $isokay = 1 if ($f eq "tag" && defined $tags{$v});
+              unless ($isokay) {
+                   $match = 0;
+                   last;
+              }
+         }
+         if ($match) {
+              return $pos;
+              last;
+         }
+     }
+     return $pos + 1;
+}
+
+sub buglinklist {
+     my ($prefix, $infix, @els) = @_;
+     return '' if not @els;
+     return $prefix . bug_linklist($infix,'submitter',@els);
+}
+
+
+# sets: my @names; my @prior; my @title; my @order;
+
+sub determine_ordering {
+     my %param = validate_with(params => \@_,
+                             spec => {cats => {type => HASHREF,
+                                              },
+                                      param => {type => HASHREF,
+                                               },
+                                      ordering => {type => SCALARREF,
+                                                  },
+                                      names    => {type => ARRAYREF,
+                                                  },
+                                      pend_rev => {type => BOOLEAN,
+                                                   default => 0,
+                                                  },
+                                      sev_rev  => {type => BOOLEAN,
+                                                   default => 0,
+                                                  },
+                                      prior    => {type => ARRAYREF,
+                                                  },
+                                      title    => {type => ARRAYREF,
+                                                  },
+                                      order    => {type => ARRAYREF,
+                                                  },
+                                     },
+                            );
+     $param{cats}{status}[0]{ord} = [ reverse @{$param{cats}{status}[0]{ord}} ]
+         if ($param{pend_rev});
+     $param{cats}{severity}[0]{ord} = [ reverse @{$param{cats}{severity}[0]{ord}} ]
+         if ($param{sev_rev});
+
+     my $i;
+     if (defined $param{param}{"pri0"}) {
+         my @c = ();
+         $i = 0;
+         while (defined $param{param}{"pri$i"}) {
+              my $h = {};
+
+              my ($pri) = make_list($param{param}{"pri$i"});
+              if ($pri =~ m/^([^:]*):(.*)$/) {
+                   $h->{"nam"} = $1; # overridden later if necesary
+                   $h->{"pri"} = [ map { "$1=$_" } (split /,/, $2) ];
+              }
+              else {
+                   $h->{"pri"} = [ split /,/, $pri ];
+              }
+
+              ($h->{"nam"}) = make_list($param{param}{"nam$i"})
+                   if (defined $param{param}{"nam$i"});
+              $h->{"ord"} = [ map {split /\s*,\s*/} make_list($param{param}{"ord$i"}) ]
+                   if (defined $param{param}{"ord$i"});
+              $h->{"ttl"} = [ map {split /\s*,\s*/} make_list($param{param}{"ttl$i"}) ]
+                   if (defined $param{param}{"ttl$i"});
+
+              push @c, $h;
+              $i++;
+         }
+         $param{cats}{"_"} = [@c];
+         ${$param{ordering}} = "_";
+     }
+
+     ${$param{ordering}} = "normal" unless defined $param{cats}{${$param{ordering}}};
+
+     sub get_ordering {
+         my @res;
+         my $cats = shift;
+         my $o = shift;
+         for my $c (@{$cats->{$o}}) {
+              if (ref($c) eq "HASH") {
+                   push @res, $c;
+              }
+              else {
+                   push @res, get_ordering($cats, $c);
+              }
+         }
+         return @res;
+     }
+     my @cats = get_ordering($param{cats}, ${$param{ordering}});
+
+     sub toenglish {
+         my $expr = shift;
+         $expr =~ s/[+]/ and /g;
+         $expr =~ s/[a-z]+=//g;
+         return $expr;
+     }
+     $i = 0;
+     for my $c (@cats) {
+         $i++;
+         push @{$param{prior}}, $c->{"pri"};
+         push @{$param{names}}, ($c->{"nam"} || "Bug attribute #" . $i);
+         if (defined $c->{"ord"}) {
+              push @{$param{order}}, $c->{"ord"};
+         }
+         else {
+              push @{$param{order}}, [ 0..$#{$param{prior}[-1]} ];
+         }
+         my @t = @{ $c->{"ttl"} } if defined $c->{ttl};
+         if (@t < $#{$param{prior}[-1]}) {
+              push @t, map { toenglish($param{prior}[-1][$_]) } @t..($#{$param{prior}[-1]});
+         }
+         push @t, $c->{"def"} || "";
+         push @{$param{title}}, [@t];
+     }
+}
+
+
+
+
+1;
+
+
+__END__
+
+
+
+
+
+
index bb42bf8b207c171f240c48779a3c18b680124ddc..f4a3f0cea33e70d78603d12c3bc2a62bbecc601e 100644 (file)
@@ -43,10 +43,10 @@ BEGIN{
                                qw(getmaintainers_reverse),
                                qw(getpseudodesc),
                               ],
-                    misc   => [qw(make_list)],
+                    misc   => [qw(make_list globify_scalar)],
                     date   => [qw(secs_to_english)],
                     quit   => [qw(quit)],
-                    lock   => [qw(filelock unfilelock @cleanups lockpid)],
+                    lock   => [qw(filelock unfilelock lockpid)],
                    );
      @EXPORT_OK = ();
      Exporter::export_ok_tags(qw(lock quit date util misc));
@@ -54,8 +54,12 @@ BEGIN{
 }
 
 #use Debbugs::Config qw(:globals);
+
+use Carp;
+
 use Debbugs::Config qw(:config);
 use IO::File;
+use IO::Scalar;
 use Debbugs::MIME qw(decode_rfc1522);
 use Mail::Address;
 use Cwd qw(cwd);
@@ -178,14 +182,11 @@ Opens a file for appending and writes data to it.
 =cut
 
 sub appendfile {
-       my $file = shift;
-       if (!open(AP,">>$file")) {
-               print $DEBUG_FH "failed open log<\n" if $DEBUG;
-               print $DEBUG_FH "failed open log err $!<\n" if $DEBUG;
-               &quit("opening $file (appendfile): $!");
-       }
-       print(AP @_) || &quit("writing $file (appendfile): $!");
-       close(AP) || &quit("closing $file (appendfile): $!");
+       my ($file,@data) = @_;
+       my $fh = IO::File->new($file,'a') or
+            die "Unable top open $file for appending: $!";
+       print {$fh} @data or die "Unable to write to $file: $!";
+       close $fh or die "Unable to close $file: $!";
 }
 
 =head2 getparsedaddrs
@@ -214,6 +215,14 @@ sub getparsedaddrs {
     return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0];
 }
 
+=head2 getmaintainers
+
+     my $maintainer = getmaintainers()->{debbugs}
+
+Returns a hashref of package => maintainer pairs.
+
+=cut
+
 our $_maintainer;
 our $_maintainer_rev;
 sub getmaintainers {
@@ -222,8 +231,8 @@ sub getmaintainers {
     my %maintainer_rev;
     for my $file (@config{qw(maintainer_file maintainer_file_override pseduo_maint_file)}) {
         next unless defined $file;
-        my $maintfile = new IO::File $file,'r' or
-             &quitcgi("Unable to open $file: $!");
+        my $maintfile = IO::File->new($file,'r') or
+             die "Unable to open maintainer file $file: $!";
         while(<$maintfile>) {
              next unless m/^(\S+)\s+(\S.*\S)\s*$/;
              ($a,$b)=($1,$2);
@@ -239,6 +248,15 @@ sub getmaintainers {
     $_maintainer_rev = \%maintainer_rev;
     return $_maintainer;
 }
+
+=head2 getmaintainers_reverse
+
+     my @packages = @{getmaintainers_reverse->{'don@debian.org'}||[]};
+
+Returns a hashref of maintainer => [qw(list of packages)] pairs.
+
+=cut
+
 sub getmaintainers_reverse{
      return $_maintainer_rev if $_maintainer_rev;
      getmaintainers();
@@ -319,7 +337,6 @@ FLOCKs the passed file. Use unfilelock to unlock it.
 =cut
 
 our @filelocks;
-our @cleanups;
 
 sub filelock {
     # NB - NOT COMPATIBLE WITH `with-lock'
@@ -346,11 +363,17 @@ sub filelock {
        }
         if (--$count <=0) {
             $errors =~ s/\n+$//;
-            &quit("failed to get lock on $lockfile -- $errors");
+            die "failed to get lock on $lockfile -- $errors";
         }
         sleep 10;
     }
-    push(@cleanups,\&unfilelock);
+}
+
+# clean up all outstanding locks at end time
+END {
+     while (@filelocks) {
+         unfilelock();
+     }
 }
 
 
@@ -371,7 +394,6 @@ sub unfilelock {
         return;
     }
     my %fl = %{pop(@filelocks)};
-    pop(@cleanups);
     flock($fl{fh},LOCK_UN)
         or warn "Unable to unlock lockfile $fl{file}: $!";
     close($fl{fh})
@@ -380,6 +402,7 @@ sub unfilelock {
         or warn "Unable to unlink lockfile $fl{file}: $!";
 }
 
+
 =head2 lockpid
 
       lockpid('/path/to/pidfile');
@@ -422,20 +445,18 @@ These functions are exported with the :quit tag.
 
      quit()
 
-Exits the program by calling die after running some cleanups.
+Exits the program by calling die.
 
-This should be replaced with an END handler which runs the cleanups
-instead. (Or possibly a die handler, if the cleanups are important)
+Usage of quit is deprecated; just call die instead.
 
 =cut
 
 sub quit {
-    print $DEBUG_FH "quitting >$_[0]<\n" if $DEBUG;
-    my ($u);
-    while ($u= $cleanups[$#cleanups]) { &$u; }
-    die "*** $_[0]\n";
+     print {$DEBUG_FH} "quitting >$_[0]<\n" if $DEBUG;
+     carp "quit() is deprecated; call die directly instead";
 }
 
+
 =head1 MISC
 
 These functions are exported with the :misc tag
@@ -457,6 +478,42 @@ sub make_list {
 }
 
 
+=head2 globify_scalar
+
+     my $handle = globify_scalar(\$foo);
+
+if $foo isn't already a glob or a globref, turn it into one using
+IO::Scalar. Gives a new handle to /dev/null if $foo isn't defined.
+
+Will carp if given a scalar which isn't a scalarref or a glob (or
+globref), and return /dev/null. May return undef if IO::Scalar or
+IO::File fails. (Check $!)
+
+=cut
+
+sub globify_scalar {
+     my ($scalar) = @_;
+     my $handle;
+     if (defined $scalar) {
+         if (defined ref($scalar)) {
+              if (ref($scalar) eq 'SCALAR' and
+                  not UNIVERSAL::isa($scalar,'GLOB')) {
+                   return IO::Scalar->new($scalar);
+              }
+              else {
+                   return $scalar;
+              }
+         }
+         elsif (UNIVERSAL::isa(\$scalar,'GLOB')) {
+              return $scalar;
+         }
+         else {
+              carp "Given a non-scalar reference, non-glob to globify_scalar; returning /dev/null handle";
+         }
+     }
+     return IO::File->new('/dev/null','w');
+}
+
 
 1;
 
index 5f2936a58891db5f278a5fb77bc21386a9017346..2a34f188089291dc5bbf97e34e76574b35dca787 100644 (file)
@@ -50,6 +50,7 @@ BEGIN {
                                 qw($gSubmitList $gMaintList $gQuietList $gForwardList),
                                 qw($gDoneList $gRequestList $gSubmitterList $gControlList),
                                 qw($gStrongList),
+                                qw($gBugSubscriptionDomain),
                                 qw($gPackageVersionRe),
                                 qw($gSummaryList $gMirrorList $gMailer $gBug),
                                 qw($gBugs $gRemoveAge $gSaveOldBugs $gDefaultSeverity),
@@ -60,14 +61,16 @@ BEGIN {
                                 qw($gVersionTimeIndex),
                                 qw($gSendmail $gLibPath $gSpamScan @gExcludeFromControl),
                                 qw(%gSeverityDisplay @gTags @gSeverityList @gStrongSeverities),
+                                qw(%gTagsSingleLetter),
                                 qw(%gSearchEstraier),
                                 qw(%gDistributionAliases),
+                                qw(%gObsoleteSeverities),
                                 qw(@gPostProcessall @gRemovalDefaultDistributionTags @gRemovalDistributionTags @gRemovalArchitectures),
                                 qw(@gRemovalStrongSeverityDefaultDistributionTags),
                                 qw(@gDefaultArchitectures),
                                 qw($gTemplateDir),
                                 qw($gDefaultPackage),
-                                qw($gSpamMaxThreads $gSpamSpamsPerThread $gSpamKeepRunning $gSpamScan $gSpamCrossassassinDb)
+                                qw($gSpamMaxThreads $gSpamSpamsPerThread $gSpamKeepRunning $gSpamScan $gSpamCrossassassinDb),
                                ],
                     text     => [qw($gBadEmailPrefix $gHTMLTail $gHTMLExpireNote),
                                 ],
@@ -76,6 +79,7 @@ BEGIN {
      @EXPORT_OK = ();
      Exporter::export_ok_tags(qw(globals text config));
      $EXPORT_TAGS{all} = [@EXPORT_OK];
+     $ENV{HOME} = '' if not defined $ENV{HOME};
 }
 
 use File::Basename qw(dirname);
@@ -295,8 +299,6 @@ set_default(\%config,'unknown_maintainer_email',$config{maintainer_email});
 
 =item mirror_list
 
-=back
-
 =cut
 
 set_default(\%config,   'submit_list',   'bug-submit-list');
@@ -311,6 +313,21 @@ set_default(\%config,  'summary_list',  'bug-summary-list');
 set_default(\%config,   'mirror_list',   'bug-mirror-list');
 set_default(\%config,   'strong_list',   'bug-strong-list');
 
+=item bug_subscription_domain
+
+Domain of list for messages regarding a single bug; prefixed with
+bug=${bugnum}@ when bugs are actually sent out. Set to undef or '' to
+disable sending messages to the bug subscription list.
+
+Default: list_domain
+
+=back
+
+=cut
+
+set_default(\%config,'bug_subscription_domain',$config{list_domain});
+
+
 =head2 Misc Options
 
 =over
@@ -400,6 +417,18 @@ set_default(\%config,'default_architectures',
            [qw(i386 amd64 arm powerpc sparc alpha)]
           );
 
+=item removal_unremovable_tags
+
+Bugs which have these tags set cannot be archived
+
+Default: []
+
+=cut
+
+set_default(\%config,'removal_unremovable_tags',
+           [],
+          );
+
 =item removal_distribution_tags
 
 Tags which specifiy distributions to check
@@ -530,27 +559,115 @@ set_default(\%config,'exclude_from_control',[]);
 
 
 
+=item default_severity
+
+The default severity of bugs which have no severity set
+
+Default: normal
+
+=cut
 
 set_default(\%config,'default_severity','normal');
-set_default(\%config,'show_severities','critical, grave, normal, minor, wishlist');
-set_default(\%config,'strong_severities',[qw(critical grave)]);
-set_default(\%config,'severity_list',[qw(critical grave normal wishlist)]);
+
+=item severity_display
+
+A hashref of severities and the informative text which describes them.
+
+Default:
+
+ {critical => "Critical $config{bugs}",
+  grave    => "Grave $config{bugs}",
+  normal   => "Normal $config{bugs}",
+  wishlist => "Wishlist $config{bugs}",
+ }
+
+=cut
+
 set_default(\%config,'severity_display',{critical => "Critical $config{bugs}",
                                         grave    => "Grave $config{bugs}",
                                         normal   => "Normal $config{bugs}",
                                         wishlist => "Wishlist $config{bugs}",
                                        });
 
+=item show_severities
+
+A scalar list of the severities to show
+
+Defaults to the concatenation of the keys of the severity_display
+hashlist with ', ' above.
+
+=cut
+
+set_default(\%config,'show_severities',join(', ',keys %{$config{severity_display}}));
+
+=item strong_severities
+
+An arrayref of the serious severities which shoud be emphasized
+
+Default: [qw(critical grave)]
+
+=cut
+
+set_default(\%config,'strong_severities',[qw(critical grave)]);
+
+=item severity_list
+
+An arrayref of a list of the severities
+
+Defaults to the keys of the severity display hashref
+
+=cut
+
+set_default(\%config,'severity_list',[keys %{$config{severity_display}}]);
+
+=item obsolete_severities
+
+A hashref of obsolete severities with the replacing severity
+
+Default: {}
+
+=cut
+
+set_default(\%config,'obsolete_severities',{});
+
+=item tags
+
+An arrayref of the tags used
+
+Default: [qw(patch wontfix moreinfo unreproducible fixed)] and also
+includes the distributions.
+
+=cut
+
 set_default(\%config,'tags',[qw(patch wontfix moreinfo unreproducible fixed),
                             @{$config{distributions}}
                            ]);
 
+set_default(\%config,'tags_single_letter',
+           {patch => '+',
+            wontfix => '',
+            moreinfo => 'M',
+            unreproducible => 'R',
+            fixed   => 'F',
+           }
+          );
+
 set_default(\%config,'bounce_froms','^mailer|^da?emon|^post.*mast|^root|^wpuser|^mmdf|^smt.*|'.
            '^mrgate|^vmmail|^mail.*system|^uucp|-maiser-|^mal\@|'.
            '^mail.*agent|^tcpmail|^bitmail|^mailman');
 
 set_default(\%config,'config_dir',dirname(exists $ENV{DEBBUGS_CONFIG_FILE}?$ENV{DEBBUGS_CONFIG_FILE}:'/etc/debbugs/config'));
 set_default(\%config,'spool_dir','/var/lib/debbugs/spool');
+
+=item usertag_dir
+
+Directory which contains the usertags
+
+Default: $config{spool_dir}/user
+
+=cut
+
+set_default(\%config,'usertag_dir',$config{spool_dir}.'/user');
 set_default(\%config,'incoming_dir','incoming');
 set_default(\%config,'web_dir','/var/lib/debbugs/www');
 set_default(\%config,'doc_dir','/var/lib/debbugs/www/txt');
@@ -729,7 +846,6 @@ Site rules directory for spamassassin, defaults to
 
 set_default(\%config,'spam_rules_dir','/usr/share/spamassassin');
 
-
 =back
 
 
@@ -768,6 +884,8 @@ set_default(\%config,'text_instructions',$config{bad_email_prefix});
 
 This shows up at the end of (most) html pages
 
+In many pages this has been replaced by the html/tail template.
+
 =cut
 
 set_default(\%config,'html_tail',<<END);
index aaba6d54c8334325e56dcb59603b31da65cb47a8..30a642b0bff22214fed508fa0423095e2a0f5dcf 100644 (file)
@@ -64,7 +64,7 @@ the above information is faked, and appended to the log file. When it
 is true, the above options must be present, and their values are used.
 
 
-=head1 FUNCTIONS
+=head1 GENERAL FUNCTIONS
 
 =cut
 
@@ -78,26 +78,27 @@ BEGIN{
      $DEBUG = 0 unless defined $DEBUG;
 
      @EXPORT = ();
-     %EXPORT_TAGS = (archive => [qw(bug_archive bug_unarchive),
+     %EXPORT_TAGS = (owner   => [qw(owner)],
+                    archive => [qw(bug_archive bug_unarchive),
                                ],
                     log     => [qw(append_action_to_log),
                                ],
                    );
      @EXPORT_OK = ();
-     Exporter::export_ok_tags(qw(archive log));
+     Exporter::export_ok_tags(keys %EXPORT_TAGS);
      $EXPORT_TAGS{all} = [@EXPORT_OK];
 }
 
 use Debbugs::Config qw(:config);
-use Debbugs::Common qw(:lock buglog make_list get_hashname);
+use Debbugs::Common qw(:lock buglog :misc get_hashname);
 use Debbugs::Status qw(bug_archiveable :read :hook writebug);
 use Debbugs::CGI qw(html_escape);
 use Debbugs::Log qw(:misc);
+use Debbugs::Recipients qw(:add);
 
 use Params::Validate qw(validate_with :types);
 use File::Path qw(mkpath);
 use IO::File;
-use IO::Scalar;
 
 use Debbugs::Text qw(:templates);
 
@@ -105,17 +106,28 @@ use Debbugs::Mail qw(rfc822_date);
 
 use POSIX qw(strftime);
 
+use Carp;
+
 # These are a set of options which are common to all of these functions
 
-my %common_options = (debug       => {type => SCALARREF,
+my %common_options = (debug       => {type => SCALARREF|HANDLE,
                                      optional => 1,
                                     },
-                     transcript  => {type => SCALARREF,
+                     transcript  => {type => SCALARREF|HANDLE,
                                      optional => 1,
                                     },
                      affected_bugs => {type => HASHREF,
                                        optional => 1,
                                       },
+                     affected_packages => {type => HASHREF,
+                                           optional => 1,
+                                          },
+                     recipients    => {type => HASHREF,
+                                       default => {},
+                                      },
+                     limit         => {type => HASHREF,
+                                       default => {},
+                                      },
                     );
 
 
@@ -144,6 +156,127 @@ my %append_action_options =
      );
 
 
+# this is just a generic stub for Debbugs::Control functions.
+# sub foo {
+#     my %param = validate_with(params => \@_,
+#                            spec   => {bug => {type   => SCALAR,
+#                                               regex  => qr/^\d+$/,
+#                                              },
+#                                       # specific options here
+#                                       %common_options,
+#                                       %append_action_options,
+#                                      },
+#                           );
+#     our $locks = 0;
+#     $locks = 0;
+#     local $SIG{__DIE__} = sub {
+#      if ($locks) {
+#          for (1..$locks) { unfilelock(); }
+#          $locks = 0;
+#      }
+#     };
+#     my ($debug,$transcript) = __handle_debug_transcript(%param);
+#     my (@data);
+#     ($locks, @data) = lock_read_all_merged_bugs($param{bug});
+#     __handle_affected_packages(data => \@data,%param);
+#     add_recipients(data => \@data,
+#                   recipients => $param{recipients}
+#                  );
+# }
+
+=head1 OWNER FUNCTIONS
+
+=head2 owner
+
+     eval {
+           owner(bug          => $ref,
+                 transcript   => $transcript,
+                 ($dl > 0 ? (debug => $transcript):()),
+                 requester    => $header{from},
+                 request_addr => $controlrequestaddr,
+                 message      => \@log,
+                 recipients   => \%recipients,
+                 owner        => undef,
+                );
+       };
+       if ($@) {
+           $errors++;
+           print {$transcript} "Failed to mark $ref as having an owner: $@";
+       }
+
+Handles all setting of the owner field; given an owner of undef or of
+no length, indicates that a bug is not owned by anyone.
+
+=cut
+
+sub owner {
+     my %param = validate_with(params => \@_,
+                              spec   => {bug => {type   => SCALAR,
+                                                 regex  => qr/^\d+$/,
+                                                },
+                                         owner => {type => SCALAR|UNDEF,
+                                                  },
+                                         %common_options,
+                                         %append_action_options,
+                                        },
+                             );
+     our $locks = 0;
+     $locks = 0;
+     local $SIG{__DIE__} = sub {
+         if ($locks) {
+              for (1..$locks) { unfilelock(); }
+              $locks = 0;
+         }
+     };
+     my ($debug,$transcript) = __handle_debug_transcript(%param);
+     my (@data);
+     ($locks, @data) = lock_read_all_merged_bugs($param{bug});
+     __handle_affected_packages(data => \@data,%param);
+     @data and defined $data[0] or die "No bug found for $param{bug}";
+     add_recipients(data => \@data,
+                   recipients => $param{recipients}
+                  );
+     my $action = '';
+     for my $data (@data) {
+         print {$debug} "Going to change owner to '".(defined $param{owner}?$param{owner}:'(going to unset it)')."'\n";
+         print {$debug} "Owner is currently '$data->{owner}' for bug $data->{bug_num}\n";
+         if (not defined $param{owner} or not length $param{owner}) {
+              $param{owner} = '';
+              $action = "Removed annotation that $config{bug} was owned by " .
+                   "$data->{owner}.";
+         }
+         else {
+              if (length $data->{owner}) {
+                   $action = "Owner changed from $data->{owner} to $param{owner}.";
+              }
+              else {
+                   $action = "Owner recorded as $param{owner}."
+              }
+         }
+         $data->{owner} = $param{owner};
+         append_action_to_log(bug => $data->{bug_num},
+                              get_lock => 0,
+              __return_append_to_log_options(
+                                             %param,
+                                             action => $action,
+                                            ),
+                             )
+              if not exists $param{append_log} or $param{append_log};
+         writebug($data->{bug_num},$data);
+         print {$transcript} "$action\n";
+         add_recipients(data => $data,
+                        recipients => $param{recipients},
+                       );
+     }
+     if ($locks) {
+         for (1..$locks) { unfilelock(); }
+     }
+}
+
+
+=head1 ARCHIVE FUNCTIONS
+
+
 =head2 bug_archive
 
      my $error = '';
@@ -163,6 +296,22 @@ my %append_action_options =
 
 This routine archives a bug
 
+=over
+
+=item bug -- bug number
+
+=item check_archiveable -- check wether a bug is archiveable before
+archiving; defaults to 1
+
+=item archive_unarchived -- whether to archive bugs which have not
+previously been archived; defaults to 1. [Set to 0 when used from
+control@]
+
+=item ignore_time -- whether to ignore time constraints when archiving
+a bug; defaults to 0.
+
+=back
+
 =cut
 
 sub bug_archive {
@@ -173,6 +322,9 @@ sub bug_archive {
                                          check_archiveable => {type => BOOLEAN,
                                                                default => 1,
                                                               },
+                                         archive_unarchived => {type => BOOLEAN,
+                                                                default => 1,
+                                                               },
                                          ignore_time => {type => BOOLEAN,
                                                          default => 0,
                                                         },
@@ -181,6 +333,7 @@ sub bug_archive {
                                         },
                              );
      our $locks = 0;
+     $locks = 0;
      local $SIG{__DIE__} = sub {
          if ($locks) {
               for (1..$locks) { unfilelock(); }
@@ -197,41 +350,31 @@ sub bug_archive {
          die "Bug $param{bug} cannot be archived";
      }
      print {$debug} "$param{bug} considering\n";
-     my ($data);
-     ($locks, $data) = lockreadbugmerge($param{bug});
+     my (@data);
+     ($locks, @data) = lock_read_all_merged_bugs($param{bug});
+     __handle_affected_packages(data => \@data,%param);
      print {$debug} "$param{bug} read $locks\n";
-     defined $data or die "No bug found for $param{bug}";
-     print {$debug} "$param{bug} read ok (done $data->{done})\n";
+     @data and defined $data[0] or die "No bug found for $param{bug}";
      print {$debug} "$param{bug} read done\n";
-     my @bugs = ($param{bug});
-     # my %bugs;
-     # @bugs{@bugs} = (1) x @bugs;
-     if (length($data->{mergedwith})) {
-         push(@bugs,split / /,$data->{mergedwith});
+
+     if (not $param{archive_unarchived} and
+        not exists $data[0]{unarchived}
+       ) {
+         print {$transcript} "$param{bug} has not been archived previously\n";
+         die "$param{bug} has not been archived previously";
      }
+     add_recipients(recipients => $param{recipients},
+                   data => \@data,
+                  );
+     my @bugs = map {$_->{bug_num}} @data;
      print {$debug} "$param{bug} bugs ".join(' ',@bugs)."\n";
      for my $bug (@bugs) {
-         my $newdata;
-         print {$debug} "$param{bug} $bug check\n";
-         if ($bug != $param{bug}) {
-              print {$debug} "$param{bug} $bug reading\n";
-              $newdata = lockreadbug($bug) || die "huh $bug ?";
-              print {$debug} "$param{bug} $bug read ok\n";
-              $locks++;
-         } else {
-              $newdata = $data;
-         }
-         print {$debug} "$param{bug} $bug read/not\n";
-         my $expectmerge= join(' ',grep($_ != $bug, sort { $a <=> $b } @bugs));
-         $newdata->{mergedwith} eq $expectmerge ||
-              die "$param{bug} differs from $bug: ($newdata->{mergedwith}) vs. ($expectmerge) (".join(' ',@bugs).")";
-         print {$debug} "$param{bug} $bug merge-ok\n";
-         if ($param{check_archiveable}) {
-              die "Bug $bug cannot be archived (but $param{bug} can?)"
-                   unless bug_archiveable(bug=>$bug,
-                                          ignore_time => $param{ignore_time},
-                                         );
-         }
+        if ($param{check_archiveable}) {
+            die "Bug $bug cannot be archived (but $param{bug} can?)"
+                unless bug_archiveable(bug=>$bug,
+                                       ignore_time => $param{ignore_time},
+                                      );
+        }
      }
      # If we get here, we can archive/remove this bug
      print {$debug} "$param{bug} removing\n";
@@ -242,23 +385,22 @@ sub bug_archive {
          append_action_to_log(bug => $bug,
                               get_lock => 0,
                               __return_append_to_log_options(
-                                (map {exists $param{$_}?($_,$param{$_}):()}
-                                 keys %append_action_options,
-                                ),
+                                %param,
                                 action => $action,
                                )
                              )
               if not exists $param{append_log} or $param{append_log};
-         my @files_to_remove = map {s#db-h/$dir/##; $_} glob("db-h/$dir/$bug.*");
+         my @files_to_remove = map {s#$config{spool_dir}/db-h/$dir/##; $_} glob("$config{spool_dir}/db-h/$dir/$bug.*");
          if ($config{save_old_bugs}) {
-              mkpath("archive/$dir");
+              mkpath("$config{spool_dir}/archive/$dir");
               foreach my $file (@files_to_remove) {
-                   link( "db-h/$dir/$file", "archive/$dir/$file" ) || copy( "db-h/$dir/$file", "archive/$dir/$file" );
+                   link( "$config{spool_dir}/db-h/$dir/$file", "$config{spool_dir}/archive/$dir/$file" ) or
+                        copy( "$config{spool_dir}/db-h/$dir/$file", "$config{spool_dir}/archive/$dir/$file" );
               }
 
               print {$transcript} "archived $bug to archive/$dir (from $param{bug})\n";
          }
-         unlink(map {"db-h/$dir/$_"} @files_to_remove);
+         unlink(map {"$config{spool_dir}/db-h/$dir/$_"} @files_to_remove);
          print {$transcript} "deleted $bug (from $param{bug})\n";
      }
      bughook_archive(@bugs);
@@ -300,58 +442,41 @@ sub bug_unarchive {
                                          %append_action_options,
                                         },
                              );
+     our $locks = 0;
+     local $SIG{__DIE__} = sub {
+         if ($locks) {
+              for (1..$locks) { unfilelock(); }
+              $locks = 0;
+         }
+     };
      my $action = "$config{bug} unarchived.";
      my ($debug,$transcript) = __handle_debug_transcript(%param);
      print {$debug} "$param{bug} considering\n";
-     my ($locks, $data) = lockreadbugmerge($param{bug},'archive');
+     my @data = ();
+     ($locks, @data) = lock_read_all_merged_bugs($param{bug},'archive');
+     __handle_affected_packages(data => \@data,%param);
      print {$debug} "$param{bug} read $locks\n";
-     if (not defined $data) {
-         print {$transcript} "No bug found for $param{bug}\n";
-         die "No bug found for $param{bug}";
+     if (not @data or not defined $data[0]) {
+        print {$transcript} "No bug found for $param{bug}\n";
+        die "No bug found for $param{bug}";
      }
-     print {$debug} "$param{bug} read ok (done $data->{done})\n";
      print {$debug} "$param{bug} read done\n";
-     my @bugs = ($param{bug});
-     # my %bugs;
-     # @bugs{@bugs} = (1) x @bugs;
-     if (length($data->{mergedwith})) {
-         push(@bugs,split / /,$data->{mergedwith});
-     }
+     my @bugs = map {$_->{bug_num}} @data;
      print {$debug} "$param{bug} bugs ".join(' ',@bugs)."\n";
-     for my $bug (@bugs) {
-         my $newdata;
-         print {$debug} "$param{bug} $bug check\n";
-         if ($bug != $param{bug}) {
-              print {$debug} "$param{bug} $bug reading\n";
-              $newdata = lockreadbug($bug,'archive') or die "huh $bug ?";
-              print {$debug} "$param{bug} $bug read ok\n";
-              $locks++;
-         } else {
-              $newdata = $data;
-         }
-         print {$debug} "$param{bug} $bug read/not\n";
-         my $expectmerge= join(' ',grep($_ != $bug, sort { $a <=> $b } @bugs));
-         if ($newdata->{mergedwith} ne $expectmerge ) {
-              print {$transcript} "$param{bug} differs from $bug: ($newdata->{mergedwith}) vs. ($expectmerge) (@bugs)";
-              die "$param{bug} differs from $bug: ($newdata->{mergedwith}) vs. ($expectmerge) (@bugs)";
-         }
-         print {$debug} "$param{bug} $bug merge-ok\n";
-     }
-     # If we get here, we can archive/remove this bug
-     print {$debug} "$param{bug} removing\n";
+     print {$debug} "$param{bug} unarchiving\n";
      my @files_to_remove;
      for my $bug (@bugs) {
          print {$debug} "$param{bug} removing $bug\n";
          my $dir = get_hashname($bug);
-         my @files_to_copy = map {s#archive/$dir/##; $_} glob("archive/$dir/$bug.*");
+         my @files_to_copy = map {s#$config{spool_dir}/archive/$dir/##; $_} glob("$config{spool_dir}/archive/$dir/$bug.*");
          mkpath("archive/$dir");
          foreach my $file (@files_to_copy) {
               # die'ing here sucks
-              link( "archive/$dir/$file", "db-h/$dir/$file" ) or
-                   copy( "archive/$dir/$file", "db-h/$dir/$file" ) or
-                        die "Unable to copy archive/$dir/$file to db-h/$dir/$file";
+              link( "$config{spool_dir}/archive/$dir/$file", "$config{spool_dir}/db-h/$dir/$file" ) or
+                   copy( "$config{spool_dir}/archive/$dir/$file", "$config{spool_dir}/db-h/$dir/$file" ) or
+                        die "Unable to copy $config{spool_dir}/archive/$dir/$file to $config{spool_dir}/db-h/$dir/$file";
          }
-         push @files_to_remove, map {"archive/$dir/$_"} @files_to_copy;
+         push @files_to_remove, map {"$config{spool_dir}/archive/$dir/$_"} @files_to_copy;
          print {$transcript} "Unarchived $config{bug} $bug\n";
      }
      unlink(@files_to_remove) or die "Unable to unlink bugs";
@@ -366,14 +491,15 @@ sub bug_unarchive {
          append_action_to_log(bug => $bug,
                               get_lock => 0,
                               __return_append_to_log_options(
-                                (map {exists $param{$_}?($_,$param{$_}):()}
-                                 keys %append_action_options,
-                                ),
+                                %param,
                                 action => $action,
                                )
                              )
               if not exists $param{append_log} or $param{append_log};
          writebug($bug,$newdata);
+         add_recipients(recipients => $param{recipients},
+                        data       => $newdata,
+                       );
      }
      print {$debug} "$param{bug} unlocking $locks\n";
      if ($locks) {
@@ -443,11 +569,34 @@ sub append_action_to_log{
 
 =head1 PRIVATE FUNCTIONS
 
+=head2 __handle_affected_packages
+
+     __handle_affected_packages(affected_packages => {},
+                                data => [@data],
+                               )
+
+
+
+=cut
+
+sub __handle_affected_packages{
+     my %param = validate_with(params => \@_,
+                              spec   => {%common_options,
+                                         data => {type => ARRAYREF|HASHREF
+                                                 },
+                                        },
+                              allow_extra => 1,
+                             );
+     for my $data (make_list($param{data})) {
+         $param{affected_packages}{$data->{package}} = 1;
+     }
+}
+
 =head2 __handle_debug_transcript
 
      my ($debug,$transcript) = __handle_debug_transcript(%param);
 
-Returns a debug and transcript IO::Scalar filehandle
+Returns a debug and transcript filehandle
 
 
 =cut
@@ -457,16 +606,14 @@ sub __handle_debug_transcript{
                               spec   => {%common_options},
                               allow_extra => 1,
                              );
-     my $fake_scalar;
-     my $debug = IO::Scalar->new(exists $param{debug}?$param{debug}:\$fake_scalar);
-     my $transcript = IO::Scalar->new(exists $param{transcript}?$param{transcript}:\$fake_scalar);
+     my $debug = globify_scalar(exists $param{debug}?$param{debug}:undef);
+     my $transcript = globify_scalar(exists $param{transcript}?$param{transcript}:undef);
      return ($debug,$transcript);
-
 }
 
 sub __return_append_to_log_options{
      my %param = @_;
-     my $action = 'Unknown action';
+     my $action = $param{action} if exists $param{action};
      if (not exists $param{requester}) {
          $param{requester} = $config{control_internal_requester};
      }
@@ -474,7 +621,6 @@ sub __return_append_to_log_options{
          $param{request_addr} = $config{control_internal_request_addr};
      }
      if (not exists $param{message}) {
-         $action = $param{action} if exists $param{action};
          my $date = rfc822_date();
          $param{message} = fill_in_template(template  => 'mail/fake_control_message',
                                             variables => {request_addr => $param{request_addr},
@@ -484,8 +630,14 @@ sub __return_append_to_log_options{
                                                          },
                                            );
      }
+     if (not defined $action) {
+         carp "Undefined action!";
+         $action = "unknown action";
+     }
      return (action => $action,
-            %param);
+            (map {exists $append_action_options{$_}?($_,$param{$_}):()}
+             keys %param),
+           );
 }
 
 
index 35881c43bf76e34fc32194d5f762843b18148dde..c4d741e6ab46f322a9519075c2d60cbba416de64 100644 (file)
@@ -35,6 +35,11 @@ BEGIN {
     $EXPORT_TAGS{all} = [@EXPORT_OK];
 }
 
+use Carp;
+
+use Debbugs::Common qw(getbuglocation getbugcomponent);
+use Params::Validate qw(:types validate_with);
+
 =head1 NAME
 
 Debbugs::Log - an interface to debbugs .log files
@@ -132,15 +137,69 @@ C<[html]> as above; C<recips> is a reference to an array of recipients
 
 Creates a new log reader based on a .log filehandle.
 
+      my $log = Debbugs::Log->new($logfh);
+      my $log = Debbugs::Log->new(bug_num => $nnn);
+      my $log = Debbugs::Log->new(logfh => $logfh);
+
+Parameters
+
+=over
+
+=item bug_num -- bug number
+
+=item logfh -- log filehandle
+
+=item log_name -- name of log
+
+=back
+
+One of the above options must be passed.
+
 =cut
 
 sub new
 {
     my $this = shift;
+    my %param;
+    if (@_ == 1) {
+        ($param{logfh}) = @_;
+    }
+    else {
+        %param = validate_with(params => \@_,
+                               spec   => {bug_num => {type => SCALAR,
+                                                      optional => 1,
+                                                     },
+                                          logfh   => {type => SCALAR,
+                                                      optional => 1,
+                                                     },
+                                          log_name => {type => SCALAR,
+                                                       optional => 1,
+                                                      },
+                                         }
+                              );
+    }
+    if (grep({exists $param{$_} and defined $param{$_}} qw(bug_num logfh log_name)) ne 1) {
+        croak "Exactly one of bug_num, logfh, or log_name must be passed and must be defined";
+    }
+
     my $class = ref($this) || $this;
     my $self = {};
     bless $self, $class;
-    $self->{logfh} = shift;
+
+    if (exists $param{logfh}) {
+        $self->{logfh} = $param{logfh}
+    }
+    elsif (exists $param{log_name}) {
+        $self->{logfh} = IO::File->new($param{log_name},'r') or
+             die "Unable to open bug log $param{log_name} for reading: $!";
+    }
+    elsif (exists $param{bug_num}) {
+        my $location = getbuglocation($param{bug_num},'log');
+        my $bug_log = getbugcomponent($param{bug_num},'log',$location);
+        $self->{logfh} = IO::File->new($bug_log, 'r') or
+             die "Unable to open bug log $bug_log for reading: $!";
+    }
+
     $self->{state} = 'kill-init';
     $self->{linenum} = 0;
     return $self;
index 183adc7df072bb732155b1c022639f676cc5c3af..785997477ce9a3e2af44051f6a76595d02e61155 100644 (file)
@@ -56,12 +56,12 @@ sub getmailbody
     return undef;
 }
 
-sub parse ($)
+sub parse
 {
     # header and decoded body respectively
     my (@headerlines, @bodylines);
 
-    my $parser = new MIME::Parser;
+    my $parser = MIME::Parser->new();
     mkdir "mime.tmp.$$", 0777;
     $parser->output_under("mime.tmp.$$");
     my $entity = eval { $parser->parse_data($_[0]) };
@@ -215,8 +215,7 @@ BEGIN {
        ]));
 }
 
-sub decode_rfc1522 ($)
-{
+sub decode_rfc1522 {
     my ($string) = @_;
 
     # this is craptacular, but leading space is hacked off by unmime.
@@ -240,7 +239,7 @@ MIME::Words::encode_mimeword on distinct words as appropriate.
 # We cannot use MIME::Words::encode_mimewords because that function
 # does not handle spaces properly at all.
 
-sub encode_rfc1522 ($) {
+sub encode_rfc1522 {
      my ($rawstr) = @_;
 
      # handle being passed undef properly
index 0bceb72bfba1d7d2dab7d2d9cb726cbe029e00c8..191b4531f955a02c95964cedb195adad09c794bc 100644 (file)
@@ -12,11 +12,11 @@ package Debbugs::Packages;
 use warnings;
 use strict;
 
-use Debbugs::Config qw(:config :globals);
-
 use base qw(Exporter);
 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @EXPORT);
 
+use Debbugs::Config qw(:config :globals);
+
 BEGIN {
     $VERSION = 1.00;
 
@@ -39,6 +39,8 @@ use Debbugs::Common qw(make_list);
 
 use List::Util qw(min max);
 
+use IO::File;
+
 $MLDBM::DumpMeth = 'portable';
 $MLDBM::RemoveTaint = 1;
 
@@ -75,17 +77,17 @@ sub getpkgsrc {
     my %pkgcomponent;
     my %srcpkg;
 
-    open(MM,"$Debbugs::Packages::gPackageSource")
-       or die("open $Debbugs::Packages::gPackageSource: $!");
-    while(<MM>) {
+    my $fh = IO::File->new($config{package_source},'r')
+       or die("Unable to open $config{package_source} for reading: $!");
+    while(<$fh>) {
        next unless m/^(\S+)\s+(\S+)\s+(\S.*\S)\s*$/;
        my ($bin,$cmp,$src)=($1,$2,$3);
-       $bin =~ y/A-Z/a-z/;
+       $bin = lc($bin);
        $pkgsrc{$bin}= $src;
        push @{$srcpkg{$src}}, $bin;
        $pkgcomponent{$bin}= $cmp;
     }
-    close(MM);
+    close($fh);
     $_pkgsrc = \%pkgsrc;
     $_pkgcomponent = \%pkgcomponent;
     $_srcpkg = \%srcpkg;
diff --git a/Debbugs/Recipients.pm b/Debbugs/Recipients.pm
new file mode 100644 (file)
index 0000000..4922c3d
--- /dev/null
@@ -0,0 +1,371 @@
+# This module is part of debbugs, and is released
+# under the terms of the GPL version 2, or any later version. See the
+# file README and COPYING for more information.
+# Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
+# $Id: perl_module_header.pm 1221 2008-05-19 15:00:40Z don $
+
+package Debbugs::Recipients;
+
+=head1 NAME
+
+Debbugs::Recipients -- Determine recipients of messages from the bts
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+
+=head1 BUGS
+
+None known.
+
+=cut
+
+use warnings;
+use strict;
+use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
+use base qw(Exporter);
+
+BEGIN{
+     ($VERSION) = q$Revision: 1221 $ =~ /^Revision:\s+([^\s+])/;
+     $DEBUG = 0 unless defined $DEBUG;
+
+     @EXPORT = ();
+     %EXPORT_TAGS = (add    => [qw(add_recipients)],
+                    det    => [qw(determine_recipients)],
+                   );
+     @EXPORT_OK = ();
+     Exporter::export_ok_tags(keys %EXPORT_TAGS);
+     $EXPORT_TAGS{all} = [@EXPORT_OK];
+
+}
+
+use Debbugs::Config qw(:config);
+use Params::Validate qw(:types validate_with);
+use Debbugs::Common qw(:misc :util);
+use Debbugs::Status qw(splitpackages isstrongseverity);
+
+use Debbugs::Mail qw(get_addresses);
+
+use Carp;
+
+=head2 add_recipients
+
+     add_recipients(data => $data,
+                    recipients => \%recipients;
+                   );
+
+Given data (from read_bug or similar) (or an arrayref of data),
+calculates the addresses which need to receive mail involving this
+bug.
+
+=over
+
+=item data -- Data from read_bug or similar; can be an arrayref of data
+
+=item recipients -- hashref of recipient data structure; pass to
+subsequent calls of add_recipients or
+
+=item debug -- optional 
+
+
+=back
+
+=cut
+
+
+sub add_recipients {
+     # Data structure is:
+     #   maintainer email address &c -> assoc of packages -> assoc of bug#'s
+     my %param = validate_with(params => \@_,
+                              spec   => {data => {type => HASHREF|ARRAYREF,
+                                                 },
+                                         recipients => {type => HASHREF,
+                                                       },
+                                         debug => {type => HANDLE|SCALARREF,
+                                                   optional => 1,
+                                                  },
+                                         transcript => {type => HANDLE|SCALARREF,
+                                                        optional => 1,
+                                                       },
+                                         actions_taken => {type => HASHREF,
+                                                           default => {},
+                                                          },
+                                        },
+                             );
+
+     $param{transcript} = globify_scalar($param{transcript});
+     $param{debug} = globify_scalar($param{debug});
+     if (ref ($param{data}) eq 'ARRAY') {
+         for my $data (@{$param{data}}) {
+              add_recipients(data => $data,
+                             map {exists $param{$_}?($_,$param{$_}):()}
+                             qw(recipients debug transcript actions_taken)
+                            );
+         }
+         return;
+     }
+     my ($p, $addmaint);
+     my $anymaintfound=0; my $anymaintnotfound=0;
+     my $ref = $param{data}{bug_num};
+     for my $p (splitpackages($param{data}{package})) {
+         $p = lc($p);
+         if (defined $config{subscription_domain}) {
+              my @source_packages = binarytosource($p);
+              if (@source_packages) {
+                   for my $source (@source_packages) {
+                        _add_address(recipients => $param{recipients},
+                                     address => "$source\@".$config{subscription_domain},
+                                     reason => $source,
+                                     type  => 'bcc',
+                                    );
+                   }
+              }
+              else {
+                   _add_address(recipients => $param{recipients},
+                                address => "$p\@".$config{subscription_domain},
+                                reason => $p,
+                                type  => 'bcc',
+                               );
+              }
+         }
+         if (defined $param{data}{severity} and defined $config{strong_list} and
+             isstrongseverity($param{data}{severity})) {
+              _add_address(recipients => $param{recipients},
+                           address => "$config{strong_list}\@".$config{list_domain},
+                           reason => $param{data}{severity},
+                           type  => 'bcc',
+                          );
+         }
+         if (defined(getmaintainers()->{$p})) {
+              $addmaint= getmaintainers()->{$p};
+              print {$param{debug}} "MR|$addmaint|$p|$ref|\n";
+              _add_address(recipients => $param{recipients},
+                           address => $addmaint,
+                           reason => $p,
+                           bug_num => $param{data}{bug_num},
+                           type  => 'cc',
+                          );
+              print {$param{debug}} "maintainer add >$p|$addmaint<\n";
+         }
+         else { 
+              print {$param{debug}} "maintainer none >$p<\n";
+              print {$param{transcript}} "Warning: Unknown package '$p'\n";
+              print {$param{debug}} "MR|unknown-package|$p|$ref|\n";
+              _add_address(recipients => $param{recipients},
+                           address => $config{unknown_maintainer_email},
+                           reason => $p,
+                           bug_num => $param{data}{bug_num},
+                           type  => 'cc',
+                          )
+                   if defined $config{unknown_maintainer_email} and
+                        length $config{unknown_maintainer_email};
+         }
+      }
+     if (defined $config{bug_subscription_domain} and
+        length $config{bug_subscription_domain}) {
+         _add_address(recipients => $param{recipients},
+                      address    => 'bug='.$param{data}{bug_num}.'@'.
+                                    $config{bug_subscription_domain},
+                      reason     => "bug $param{data}{bug_num}",
+                      bug_num    => $param{data}{bug_num},
+                      type       => 'bcc',
+                     );
+     }
+
+     if (length $param{data}{owner}) {
+         $addmaint = $param{data}{owner};
+         print {$param{debug}} "MO|$addmaint|$param{data}{package}|$ref|\n";
+         _add_address(recipients => $param{recipients},
+                      address => $addmaint,
+                      reason => "owner of $param{data}{bug_num}",
+                      bug_num => $param{data}{bug_num},
+                      type  => 'cc',
+                     );
+       print {$param{debug}} "owner add >$param{data}{package}|$addmaint<\n";
+     }
+     if (exists $param{actions_taken}) {
+         if (exists $param{actions_taken}{done} and
+             $param{actions_taken}{done} and
+             length($config{done_list}) and
+             length($config{list_domain})
+            ) {
+              _add_address(recipients => $param{recipients},
+                           type       => 'cc',
+                           address    => $config{done_list}.'@'.$config{list_domain},
+                           bug_num    => $param{data}{bug_num},
+                           reason     => "bug $param{data}{bug_num} done",
+                          );
+         }
+         if (exists $param{actions_taken}{forwarded} and
+             $param{actions_taken}{forwarded} and
+             length($config{forward_list}) and
+             length($config{list_domain})
+            ) {
+              _add_address(recipients => $param{recipients},
+                           type       => 'cc',
+                           address    => $config{forward_list}.'@'.$config{list_domain},
+                           bug_num    => $param{data}{bug_num},
+                           reason     => "bug $param{data}{bug_num} forwarded",
+                          );
+         }
+     }
+}
+
+=head2 determine_recipients
+
+     my @recipients = determine_recipients(recipients => \%recipients,
+                                           bcc => 1,
+                                          );
+     my %recipients => determine_recipients(recipients => \%recipients,);
+
+     # or a crazy example:
+     send_mail_message(message => $message,
+                       recipients =>
+                        [make_list(
+                          values %{{determine_recipients(
+                                recipients => \%recipients)
+                                  }})
+                        ],
+                      );
+
+Using the recipient hashref, determines the set of recipients.
+
+If you specify one of C<bcc>, C<cc>, or C<to>, you will receive only a
+LIST of recipients which the main should be Bcc'ed, Cc'ed, or To'ed
+respectively. By default, a LIST with keys bcc, cc, and to is returned
+with ARRAYREF values correponding to the users to whom a message
+should be sent.
+
+=over
+
+=item address_only -- whether to only return mail addresses without reasons or realnamesq
+
+=back
+
+Passing more than one of bcc, cc or to is a fatal error.
+
+=cut
+
+sub determine_recipients {
+     my %param = validate_with(params => \@_,
+                              spec   => {recipients => {type => HASHREF,
+                                                       },
+                                         bcc        => {type => BOOLEAN,
+                                                        default => 0,
+                                                       },
+                                         cc         => {type => BOOLEAN,
+                                                        default => 0,
+                                                       },
+                                         to         => {type => BOOLEAN,
+                                                        default => 0,
+                                                       },
+                                         address_only => {type => BOOLEAN,
+                                                          default => 0,
+                                                         }
+                                        },
+                             );
+
+     if (1 < scalar grep {$param{$_}} qw(to cc bcc)) {
+         croak "Passing more than one of to, cc, or bcc is non-sensical";
+     }
+
+     my %final_recipients;
+     # start with the to recipients
+     for my $addr (keys %{$param{recipients}}) {
+         my $level = 'bcc';
+         my @reasons;
+         for my $reason (keys %{$param{recipients}{$addr}}) {
+              my @bugs;
+              for my $bug (keys %{$param{recipients}{$addr}{$reason}}) {
+                   push @bugs, $bug;
+                   my $t_level = $param{recipients}{$addr}{$reason}{$bug};
+                   if ($level eq 'to' or
+                       $t_level eq 'to') {
+                        $level = 'to';
+                   }
+                   elsif ($t_level eq 'cc') {
+                        $level = 'cc';
+                   }
+              }
+              # strip out all non-word non-spaces
+              $reason =~ s/[^\ \w]//g;
+              push @reasons, $reason . ' for {'.join(',',@bugs).'}';
+         }
+         if ($param{address_only}) {
+              push @{$final_recipients{$level}}, get_addresses($addr);
+         }
+         else {
+              push @{$final_recipients{$level}}, $addr . ' ('.join(', ',@reasons).')';
+         }
+     }
+     for (qw(to cc bcc)) {
+         if ($param{$_}) {
+              return @{$final_recipients{$_}};
+         }
+     }
+     return %final_recipients;
+}
+
+
+=head1 PRIVATE FUNCTIONS
+
+=head2 _add_address
+
+         _add_address(recipients => $param{recipients},
+                      address => $addmaint,
+                      reason => $param{data}{package},
+                      bug_num => $param{data}{bug_num},
+                      type  => 'cc',
+                     );
+
+
+=cut
+
+
+sub _add_address {
+     my %param = validate_with(params => \@_,
+                              spec => {recipients => {type => HASHREF,
+                                                     },
+                                       bug_num    => {type => SCALAR,
+                                                      regex => qr/^\d*$/,
+                                                      default => '',
+                                                     },
+                                       reason     => {type => SCALAR,
+                                                      default => '',
+                                                     },
+                                       address    => {type => SCALAR|ARRAYREF,
+                                                     },
+                                       type       => {type => SCALAR,
+                                                      default => 'cc',
+                                                      regex   => qr/^(?:b?cc|to)$/i,
+                                                     },
+                                      },
+                             );
+     for my $addr (make_list($param{address})) {
+         if (lc($param{type}) eq 'bcc' and
+             exists $param{recipients}{$addr}{$param{reason}}{$param{bug_num}}
+            ) {
+              next;
+         }
+         elsif (lc($param{type}) eq 'cc' and
+                exists $param{recipients}{$addr}{$param{reason}}{$param{bug_num}}
+                and $param{recipients}{$addr}{$param{reason}}{$param{bug_num}} eq 'to'
+               ) {
+              next;
+         }
+         $param{recipients}{$addr}{$param{reason}}{$param{bug_num}} = lc($param{type});
+     }
+}
+
+1;
+
+
+__END__
+
+
+
+
+
+
index 5f0138ab209e4ca843c422075e55a9553da07236..32e874e0b366626982412b5e6609add1a67c67e1 100644 (file)
@@ -225,13 +225,7 @@ sub get_bug_log{
      my $VERSION = __populate_version(pop);
      my ($self,$bug,$msg_num) = @_;
 
-     my $location = getbuglocation($bug,'log');
-     my $bug_log = getbugcomponent($bug,'log',$location);
-
-     my $log_fh = IO::File->new($bug_log, 'r') or
-         die "Unable to open bug log $bug_log for reading: $!";
-
-     my $log = Debbugs::Log->new($log_fh) or
+     my $log = Debbugs::Log->new(bug_num => $bug) or
          die "Debbugs::Log was unable to be initialized";
 
      my %seen_msg_ids;
index 73a1dbc108f9d7af6d844a0120ed06e5d35c2de3..1a5e7aeea6fe3d5ad4b01e5f5a8eb88502b22ab1 100644 (file)
@@ -32,6 +32,7 @@ status of a particular bug
 
 use warnings;
 use strict;
+
 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
 use base qw(Exporter);
 
@@ -55,7 +56,9 @@ BEGIN{
      %EXPORT_TAGS = (status => [qw(splitpackages get_bug_status buggy bug_archiveable),
                                qw(isstrongseverity bug_presence),
                               ],
-                    read   => [qw(readbug read_bug lockreadbug lockreadbugmerge)],
+                    read   => [qw(readbug read_bug lockreadbug lockreadbugmerge),
+                               qw(lock_read_all_merged_bugs),
+                              ],
                     write  => [qw(writebug makestatus unlockwritebug)],
                     versions => [qw(addfoundversions addfixedversions),
                                  qw(removefoundversions removefixedversions)
@@ -129,6 +132,10 @@ path to the summary file instead of the bug number and/or location.
 
 =item summary -- complete path to the .summary file which will be read
 
+=item lock -- whether to obtain a lock for the bug to prevent
+something modifying it while the bug has been read. You B<must> call
+C<unfilelock();> if something not undef is returned from read_bug.
+
 =back
 
 One of C<bug> or C<summary> must be passed. This function will return
@@ -154,6 +161,9 @@ sub read_bug{
                                         summary  => {type => SCALAR,
                                                      optional => 1,
                                                     },
+                                        lock     => {type => BOOLEAN,
+                                                     optional => 1,
+                                                    },
                                        },
                             );
     die "One of bug or summary must be passed to read_bug"
@@ -178,8 +188,17 @@ sub read_bug{
         $log =~ s/\.summary$/.log/;
         ($location) = $status =~ m/(db-h|db|archive)/;
     }
-    my $status_fh = new IO::File $status, 'r' or
-        warn "Unable to open $status for reading: $!" and return undef;
+    if ($param{lock}) {
+       filelock("$config{spool_dir}/lock/$param{bug}");
+    }
+    my $status_fh = IO::File->new($status, 'r');
+    if (not defined $status_fh) {
+       warn "Unable to open $status for reading: $!";
+       if ($param{lock}) {
+           unfilelock();
+       }
+       return undef;
+    }
 
     my %data;
     my @lines;
@@ -193,7 +212,13 @@ sub read_bug{
     }
 
     # Version 3 is the latest format version currently supported.
-    return undef if $version > 3;
+    if ($version > 3) {
+        warn "Unsupported status version '$version'";
+        if ($param{lock}) {
+            unfilelock();
+        }
+        return undef;
+    }
 
     my %namemap = reverse %fields;
     for my $line (@lines) {
@@ -226,6 +251,8 @@ sub read_bug{
     # Add log last modified time
     $data{log_modified} = (stat($log))[9];
     $data{location} = $location;
+    $data{archived} = $location eq 'archive';
+    $data{bug_num} = $param{bug};
 
     return \%data;
 }
@@ -244,10 +271,7 @@ See readbug above for information on what this returns
 
 sub lockreadbug {
     my ($lref, $location) = @_;
-    &filelock("lock/$lref");
-    my $data = read_bug(bug => $lref, location => $location);
-    &unfilelock unless defined $data;
-    return $data;
+    return read_bug(bug => $lref, location => $location, lock => 1);
 }
 
 =head2 lockreadbugmerge
@@ -270,7 +294,7 @@ sub lockreadbugmerge {
          return (1,$data);
      }
      unfilelock();
-     filelock('lock/merge');
+     filelock("$config{spool_dir}/lock/merge");
      $data = lockreadbug(@_);
      if (not defined $data) {
          unfilelock();
@@ -279,6 +303,67 @@ sub lockreadbugmerge {
      return (2,$data);
 }
 
+=head2 lock_read_all_merged_bugs
+
+     my ($locks,@bug_data) = lock_read_all_merged_bugs($bug_num,$location);
+
+Performs a filelock, then reads the bug passed. If the bug is merged,
+locks the merge lock, then reads and locks all of the other merged
+bugs. Returns a list of the number of locks and the bug data for all
+of the merged bugs.
+
+Will also return undef if any of the merged bugs failed to be read,
+even if all of the others were read properly.
+
+=cut
+
+sub lock_read_all_merged_bugs {
+    my ($bug_num,$location) = @_;
+    my @data = (lockreadbug(@_));
+    if (not @data and not defined $data[0]) {
+       return (0,undef);
+    }
+    if (not length $data[0]->{mergedwith}) {
+       return (1,@data);
+    }
+    unfilelock();
+    filelock("$config{spool_dir}/lock/merge");
+    my $locks = 0;
+    @data = (lockreadbug(@_));
+    if (not @data and not defined $data[0]) {
+       unfilelock(); #for merge lock above
+       return (0,undef);
+    }
+    $locks++;
+    my @bugs = split / /, $data[0]->{mergedwith};
+    for my $bug (@bugs) {
+       my $newdata = undef;
+       if ($bug ne $bug_num) {
+           $newdata = lockreadbug($bug,$location);
+           if (not defined $newdata) {
+               for (1..$locks) {
+                   unfilelock();
+               }
+               $locks = 0;
+               warn "Unable to read bug: $bug while handling merged bug: $bug_num";
+               return ($locks,undef);
+           }
+           $locks++;
+           push @data,$newdata;
+       }
+       # perform a sanity check to make sure that the merged bugs are
+       # all merged with eachother
+       my $expectmerge= join(' ',grep($_ != $bug, sort { $a <=> $b } @bugs));
+       if ($newdata->{mergedwith} ne $expectmerge) {
+           for (1..$locks) {
+               unfilelock();
+           }
+           die "Bug $bug_num differs from bug $bug: ($newdata->{mergedwith}) vs. ($expectmerge) (".join(' ',@bugs).")";
+       }
+    }
+    return (2,@data);
+}
+
 
 my @v1fieldorder = qw(originator date subject msgid package
                       keywords done forwarded mergedwith severity);
@@ -369,17 +454,17 @@ sub writebug {
     for my $version (keys %outputs) {
         next if defined $minversion and $version < $minversion;
         my $status = getbugcomponent($ref, $outputs{$version}, $location);
-        &quit("can't find location for $ref") unless defined $status;
-        open(S,"> $status.new") || &quit("opening $status.new: $!");
+        die "can't find location for $ref" unless defined $status;
+        open(S,"> $status.new") || die "opening $status.new: $!";
         print(S makestatus($data, $version)) ||
-            &quit("writing $status.new: $!");
-        close(S) || &quit("closing $status.new: $!");
+            die "writing $status.new: $!";
+        close(S) || die "closing $status.new: $!";
         if (-e $status) {
             $change = 'change';
         } else {
             $change = 'new';
         }
-        rename("$status.new",$status) || &quit("installing new $status: $!");
+        rename("$status.new",$status) || die "installing new $status: $!";
     }
 
     # $disablebughook is a bit of a hack to let format migration scripts use
@@ -627,6 +712,16 @@ sub bug_archiveable{
          print STDERR "Cannot archive $param{bug} because it is not done\n" if $DEBUG;
          return $cannot_archive
      }
+     # Check to make sure that the bug has none of the unremovable tags set
+     if (@{$config{removal_unremovable_tags}}) {
+         for my $tag (split ' ', ($status->{tags}||'')) {
+              if (grep {$tag eq $_} @{$config{removal_unremovable_tags}}) {
+                   print STDERR "Cannot archive $param{bug} because it has an unremovable tag '$tag'\n" if $DEBUG;
+                   return $cannot_archive;
+              }
+         }
+     }
+
      # If we just are checking if the bug can be archived, we'll not even bother
      # checking the versioning information if the bug has been -done for less than 28 days.
      my $log_file = getbugcomponent($param{bug},'log');
@@ -725,7 +820,10 @@ sub bug_archiveable{
               last if $buggy eq 'found';
               $min_fixed_time = min($time_versions{$version},$min_fixed_time);
          }
-         $min_archive_days = max($min_archive_days,ceil($config{remove_age} - (time - $min_fixed_time)/(60*60*24)));
+         $min_archive_days = max($min_archive_days,ceil($config{remove_age} - (time - $min_fixed_time)/(60*60*24)))
+              # if there are no versions in the archive at all, then
+              # we can archive if enough days have passed
+              if @sourceversions;
      }
      # If $param{ignore_time}, then we should ignore time.
      if ($param{ignore_time}) {
@@ -862,7 +960,8 @@ sub get_bug_status {
      $status{"pending"} = 'fixed'          if ($tags{fixed});
 
 
-     my $presence = bug_presence(map{(exists $param{$_})?($_,$param{$_}):()}
+     my $presence = bug_presence(status => \%status,
+                                map{(exists $param{$_})?($_,$param{$_}):()}
                                 qw(bug sourceversions arch dist version found fixed package)
                                );
      if (defined $presence) {
@@ -1231,7 +1330,7 @@ sub update_realtime {
 
 sub bughook_archive {
        my @refs = @_;
-       &filelock("debbugs.trace.lock");
+       &filelock("$config{spool_dir}/debbugs.trace.lock");
        &appendfile("debbugs.trace","archive ".join(',',@refs)."\n");
        my %bugs = update_realtime("$config{spool_dir}/index.db.realtime",
                                   map{($_,'REMOVE')} @refs);
@@ -1242,7 +1341,7 @@ sub bughook_archive {
 
 sub bughook {
        my ( $type, %bugs_temp ) = @_;
-       &filelock("debbugs.trace.lock");
+       &filelock("$config{spool_dir}/debbugs.trace.lock");
 
        my %bugs;
        for my $bug (keys %bugs_temp) {
index 0f3a5299a226d8647e07aa97d54d3f59de5faf3c..61c77816a239019b4abc9ed1415f2a19a08c1c09 100644 (file)
@@ -16,8 +16,8 @@ Debbugs::Text -- General routines for text templates
 
 =head1 SYNOPSIS
 
-use Debbugs::Text qw(:templates);
-print fill_in_template(template => 'cgi/foo');
+ use Debbugs::Text qw(:templates);
+ print fill_in_template(template => 'cgi/foo');
 
 =head1 DESCRIPTION
 
@@ -164,6 +164,7 @@ sub fill_in_template{
                             qw(padsv padav padhv padany),
                             qw(rv2gv refgen srefgen ref),
                             qw(caller require entereval),
+                            qw(gmtime time sprintf prtf),
                            );
          $safe->share('*STDERR');
          $safe->share('%config');
@@ -191,7 +192,7 @@ sub fill_in_template{
      my $tt;
      if ($tt_type eq 'FILE' and
         defined $tt_templates{$tt_source} and
-        (stat $tt_source)[9] > $tt_templates{$tt_source}{mtime}
+        (stat $tt_source)[9] <= $tt_templates{$tt_source}{mtime}
        ) {
          $tt = $tt_templates{$tt_source}{template};
      }
@@ -202,6 +203,7 @@ sub fill_in_template{
          }
          $tt = Text::Template->new(TYPE => $tt_type,
                                    SOURCE => $tt_source,
+                                   UNTAINT => 1,
                                   );
          if ($tt_type eq 'FILE') {
               $tt_templates{$tt_source}{template} = $tt;
@@ -210,10 +212,7 @@ sub fill_in_template{
      if (not defined $tt) {
          die "Unable to create Text::Template for $tt_type:$tt_source";
      }
-     my $ret = $tt->fill_in(#(defined $param{nosafe} and $param{nosafe})?():(HASH=>$param{variables}),
-                           #(defined $param{nosafe} and $param{nosafe})?():(SAFE=>$safe),
-                           SAFE => $safe,
-                           #(defined $param{nosafe} and $param{nosafe})?(PACKAGE => 'main'):(),
+     my $ret = $tt->fill_in(SAFE => $safe,
                            defined $param{output}?(OUTPUT=>$param{output}):(),
                           );
      if (not defined $ret) {
index b82ce704a6bb8162f7399ee5dc8dad28365a683e..cbb0fa857989d8d8d4a9c59695d63a057c5349c9 100644 (file)
@@ -6,7 +6,7 @@
 # [Other people have contributed to this file; their copyrights should
 # go here too.]
 # Copyright 2004 by Anthony Towns
-
+# Copyright 2008 by Don Armstrong <don@donarmstrong.com>
 
 
 package Debbugs::User;
@@ -36,6 +36,37 @@ $u->{"name"}
 read_usertags(\%ut, $userid);
 write_usertags(\%ut, $userid);
 
+=head1 USERTAG FILE FORMAT
+
+Usertags are in a file which has (roughly) RFC822 format, with stanzas
+separated by newlines. For example:
+
+ Tag: search
+ Bugs: 73671, 392392
+ Value: priority
+ Bug-73671: 5
+ Bug-73487: 2
+ Value: bugzilla
+ Bug-72341: http://bugzilla/2039471
+ Bug-1022: http://bugzilla/230941
+ Category: normal
+ Cat1: status
+ Cat2: debbugs.tasks
+ Category: debbugs.tasks
+ Hidden: yes
+ Cat1: debbugs.tasks
+
+ Cat1Options:
+  tag=quick
+  tag=medium
+  tag=arch
+  tag=not-for-me
+
+
 =head1 EXPORT TAGS
 
 =over
@@ -54,87 +85,25 @@ use Fcntl ':flock';
 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
 use base qw(Exporter);
 
-use Debbugs::Config qw(:globals);
+use Debbugs::Config qw(:config);
 use List::Util qw(min);
 
+use Carp;
+use IO::File;
+
 BEGIN {
     ($VERSION) = q$Revision: 1.4 $ =~ /^Revision:\s+([^\s+])/;
     $DEBUG = 0 unless defined $DEBUG;
 
     @EXPORT = ();
-    @EXPORT_OK = qw(is_valid_user open read_usertags write_usertags);
+    @EXPORT_OK = qw(is_valid_user read_usertags write_usertags);
     $EXPORT_TAGS{all} = [@EXPORT_OK];
 }
 
-# Obsolete compatability functions
-
-sub read_usertags {
-    my $ut = shift;
-    my $u = shift;
-    
-    my $user = get_user($u);
-    for my $t (keys %{$user->{"tags"}}) {
-        $ut->{$t} = [] unless defined $ut->{$t};
-        push @{$ut->{$t}}, @{$user->{"tags"}->{$t}};
-    }
-}
-
-sub write_usertags {
-    my $ut = shift;
-    my $u = shift;
-    
-    my $user = get_user($u, 1); # locked
-    $user->{"tags"} = { %{$ut} };
-    $user->write();
-}
 
 #######################################################################
 # Helper functions
 
-sub filefromemail {
-    my $e = shift;
-    my $l = length($e) % 7;
-    return "$gSpoolDir/user/$l/" . join("", 
-        map { m/^[0-9a-zA-Z_+.-]$/ ? $_ : sprintf("%%%02X", ord($_)) }
-            split //, $e);
-}
-
-sub read_stanza {
-    my $f = shift;
-    my $field = 0;
-    my @res;
-    while (<$f>) {
-          chomp;
-          last if (m/^$/);
-
-        if ($field && m/^ (.*)$/) {
-            $res[-1] .= "\n" . $1;
-        } elsif (m/^([^:]+):(\s+(.*))?$/) {
-            $field = $1;
-            push @res, ($1, $3);
-        }
-    }
-    return @res;
-}
-
-sub fmt {
-    my $s = shift;
-    my $n = shift;
-    my $sofar = 0;
-    my $res = "";
-    while ($s =~ m/^([^,]*,\s*)(.*)$/ || $s =~ m/^([^,]+)()$/) {
-        my $k = $1;
-        $s = $2;
-        unless ($sofar == 0 or $sofar + length($k) <= $n) {
-               $res .= "\n ";
-               $sofar = 1;
-           }
-           $res .= $k;
-           $sofar += length($k);
-    }
-    return $res . $s;
-}
-
 sub is_valid_user {
     my $u = shift;
     return ($u =~ /^[a-zA-Z0-9._+-]+[@][a-z0-9-.]{4,}$/);
@@ -144,32 +113,48 @@ sub is_valid_user {
 # The real deal
 
 sub get_user {
-    my $ut = {};
-    my $user = { 
-        "tags" => $ut, 
-        "categories" => {}, 
-        "visible_cats" => [],
-        "unknown_stanzas" => [] 
-    };
+     return Debbugs::User->new(@_);
+}
 
-    my $u = shift;
-    my $need_lock = shift || 0;
-    my $p = filefromemail($u);
+=head2 new
 
-    my $uf;
-    $user->{"filename"} = $p;
-    if (not -r $p) {
-        return bless $user, "Debbugs::User";
+     my $user = Debbugs::User->new('foo@bar.com',$lock);
+
+Reads the user file associated with 'foo@bar.com' and returns a
+Debbugs::User object.
+
+=cut
+
+sub new {
+    my $class = shift;
+    $class = ref($class) || $class;
+    my ($email,$need_lock) = @_;
+    $need_lock ||= 0;
+
+    my $ut = {};
+    my $self = {"tags" => $ut,
+               "categories" => {},
+               "visible_cats" => [],
+               "unknown_stanzas" => [],
+               values => {},
+               email => $email,
+              };
+    bless $self, $class;
+
+    $self->{filename} = _file_from_email($self->{email});
+    if (not -r $self->{filename}) {
+        return $self;
     }
-    open($uf, "< $p") or die "Unable to open file $p for reading: $!";
+    my $uf = IO::File->new($self->{filename},'r')
+        or die "Unable to open file $self->{filename} for reading: $!";
     if ($need_lock) {
-        flock($uf, LOCK_EX); 
-        $user->{"locked"} = $uf;
+        flock($uf, LOCK_EX);
+        $self->{"locked"} = $uf;
     }
-    
+
     while(1) {
-        my @stanza = read_stanza($uf);
-        last if ($#stanza == -1);
+        my @stanza = _read_stanza($uf);
+        last unless @stanza;
         if ($stanza[0] eq "Tag") {
             my %tag = @stanza;
             my $t = $tag{"Tag"};
@@ -208,52 +193,66 @@ sub get_user {
                     }
                     $c{"ttl"} = [@ttl];
                     $c{"pri"} = [@pri];
-                    push @cat, { %c };                    
+                    push @cat, { %c };
                 } else {
                     push @cat, $stanza{"Cat${i}"};
                 }
             }
-            $user->{"categories"}->{$catname} = [@cat];
-            push @{$user->{"visible_cats"}}, $catname
-                unless ($stanza{"Hidden"} || "no") eq "yes";                        
-        } else {
-            push @{$user->{"unknown_stanzas"}}, [@stanza];
+            $self->{"categories"}->{$catname} = [@cat];
+            push @{$self->{"visible_cats"}}, $catname
+                unless ($stanza{"Hidden"} || "no") eq "yes";
+       }
+       elsif ($stanza[0] eq 'Value') {
+           my ($value,$value_name,%bug_values) = @stanza;
+           while (my ($k,$v) = each %bug_values) {
+               my ($bug) = $k =~ m/^Bug-(\d+)/;
+               next unless defined $bug;
+               $self->{values}{$bug}{$value_name} = $v;
+           }
+       }
+       else {
+            push @{$self->{"unknown_stanzas"}}, [@stanza];
         }
     }
-    close($uf) unless $need_lock;
 
-    bless $user, "Debbugs::User";
-    return $user;
+    return $self;
 }
 
 sub write {
-    my $user = shift;
-    my $uf;
-    my $ut = $user->{"tags"};
-    my $p = $user->{"filename"};
+    my $self = shift;
+
+    my $ut = $self->{"tags"};
+    my $p = $self->{"filename"};
 
-    if ($p =~ m/^(.+)$/) { $p = $1; } else { return; } 
-    open $uf, "> $p" or return;
+    if (not defined $self->{filename} or not
+       length $self->{filename}) {
+        carp "Tried to write a usertag with no filename defined";
+        return;
+    }
+    my $uf = IO::File->new($self->{filename},'w');
+    if (not $uf) {
+        carp "Unable to open $self->{filename} for writing: $!";
+        return;
+    }
 
-    for my $us (@{$user->{"unknown_stanzas"}}) {
+    for my $us (@{$self->{"unknown_stanzas"}}) {
         my @us = @{$us};
-        while (@us) {
-            my $k = shift @us; my $v = shift @us;
+        while (my ($k,$v) = splice (@us,0,2)) {
            $v =~ s/\n/\n /g;
-            print $uf "$k: $v\n";
-        }
-        print $uf "\n";
+           print {$uf} "$k: $v\n";
+       }
+        print {$uf} "\n";
     }
 
     for my $t (keys %{$ut}) {
         next if @{$ut->{$t}} == 0;
-        print $uf "Tag: $t\n";
-        print $uf fmt("Bugs: " . join(", ", @{$ut->{$t}}), 77) . "\n";
+        print {$uf} "Tag: $t\n";
+        print {$uf} _wrap_to_length("Bugs: " . join(", ", @{$ut->{$t}}), 77) . "\n";
         print $uf "\n";
     }
 
-    my $uc = $user->{"categories"};
-    my %vis = map { $_, 1 } @{$user->{"visible_cats"}};
+    my $uc = $self->{"categories"};
+    my %vis = map { $_, 1 } @{$self->{"visible_cats"}};
     for my $c (keys %{$uc}) {
         next if @{$uc->{$c}} == 0;
 
@@ -283,11 +282,143 @@ sub write {
        }
        print $uf "\n";
     }
+    # handle the value stanzas
+    my %value;
+    # invert the bug->value hash slightly
+    for my $bug (keys %{$self->{values}}) {
+        for my $value (keys %{$self->{values}{$bug}}) {
+             $value{$value}{$bug} = $self->{values}{$bug}{$value}
+        }
+    }
+    for my $value (keys %value) {
+        print {$uf} "Value: $value\n";
+        for my $bug (keys %{$value{$value}}) {
+             my $bug_value = $value{$value}{$bug};
+             $bug_value =~ s/\n/\n /g;
+             print {$uf} "Bug-$bug: $bug_value\n";
+        }
+        print {$uf} "\n";
+    }
 
     close($uf);
-    delete $user->{"locked"};
+    delete $self->{"locked"};
+}
+
+=head1 OBSOLETE FUNCTIONS
+
+=cut
+
+=head2 read_usertags
+
+     read_usertags($usertags,$email)
+
+
+=cut
+
+sub read_usertags {
+    my ($usertags,$email) = @_;
+
+    carp "read_usertags is deprecated";
+    my $user = get_user($email);
+    for my $tag (keys %{$user->{"tags"}}) {
+        $usertags->{$tag} = [] unless defined $usertags->{$tag};
+        push @{$usertags->{$tag}}, @{$user->{"tags"}->{$tag}};
+    }
+    return $usertags;
+}
+
+=head2 write_usertags
+
+     write_usertags($usertags,$email);
+
+Gets a lock on the usertags, applies the usertags passed, and writes
+them out.
+
+=cut
+
+sub write_usertags {
+    my ($usertags,$email) = @_;
+
+    carp "write_usertags is deprecated";
+    my $user = Debbugs::User->new($email,1); # locked
+    $user->{"tags"} = { %{$usertags} };
+    $user->write();
+}
+
+
+=head1 PRIVATE FUNCTIONS
+
+=head2 _file_from_email
+
+     my $filename = _file_from_email($email)
+
+Turns an email into the filename where the usertag can be located.
+
+=cut
+
+sub _file_from_email {
+    my ($email) = @_;
+    my $email_length = length($email) % 7;
+    my $escaped_email = $email;
+    $escaped_email =~ s/([^0-9a-zA-Z_+.-])/sprintf("%%%02X", ord($1))/eg;
+    return "$config{usertag_dir}/$email_length/$escaped_email";
+}
+
+=head2 _read_stanza
+
+     my @stanza = _read_stanza($fh);
+
+Reads a single stanza from a filehandle and returns it
+
+=cut
+
+sub _read_stanza {
+    my ($file_handle) = @_;
+    my $field = 0;
+    my @res;
+    while (<$file_handle>) {
+        chomp;
+        last if (m/^$/);
+        if ($field && m/^ (.*)$/) {
+             $res[-1] .= "\n" . $1;
+        } elsif (m/^([^:]+):(\s+(.*))?$/) {
+             $field = $1;
+             push @res, ($1, $3||'');
+        }
+    }
+    return @res;
 }
 
+
+=head2 _wrap_to_length
+
+     _wrap_to_length
+
+Wraps a line to a specific length by splitting at commas
+
+=cut
+
+sub _wrap_to_length {
+    my ($content,$line_length) = @_;
+    my $current_line_length;
+    my $result = "";
+    while ($content =~ m/^([^,]*,\s*)(.*)$/ || $content =~ m/^([^,]+)()$/) {
+        my $current_word = $1;
+        $content = $2;
+        if ($current_line_length != 0 and
+           $current_line_length + length($current_word) <= $line_length) {
+           $result .= "\n ";
+           $current_line_length = 1;
+       }
+       $result .= $current_word;
+       $current_line_length += length($current_word);
+    }
+    return $result . $content;
+}
+
+
+
+
 1;
 
 __END__
index 26f01380e3d001b14a1d822e6547df6aa290a48d..5545b487e3fbcc09790ab5d0facbdda2f020a3aa 100644 (file)
@@ -8,6 +8,8 @@
 
 package Debbugs::Versions;
 
+use warnings;
+
 use strict;
 
 =head1 NAME
@@ -61,7 +63,7 @@ function.
 
 =cut
 
-sub new ($$)
+sub new
 {
     my $this = shift;
     my $class = ref($this) || $this;
@@ -81,7 +83,7 @@ This method is expected mainly to be used internally by the C<merge> method.
 
 =cut
 
-sub isancestor ($$$)
+sub isancestor
 {
     my $self = shift;
     my $ancestor = shift;
@@ -104,7 +106,7 @@ This method is mainly for internal use.
 
 =cut
 
-sub leaves ($)
+sub leaves
 {
     my $self = shift;
 
@@ -126,7 +128,7 @@ the next in the list.
 
 =cut
 
-sub merge ($@)
+sub merge
 {
     my $self = shift;
     return unless @_;
@@ -157,7 +159,7 @@ whitespace.
 
 =cut
 
-sub load ($*)
+sub load
 {
     my $self = shift;
     my $fh = shift;
@@ -175,7 +177,7 @@ method.
 
 =cut
 
-sub save ($*)
+sub save
 {
     my $self = shift;
     my $fh = shift;
@@ -217,7 +219,7 @@ that nothing is known about any of the found versions.
 
 =cut
 
-sub buggy ($$$$)
+sub buggy
 {
     my $self = shift;
     my $version = shift;
@@ -275,7 +277,7 @@ number of known and interested versions.
 
 =cut
 
-sub allstates ($$$;$)
+sub allstates
 {
     my $self = shift;
     my $found = shift;
index 0fdfa0c06755b617617f8c90c02981a400cc87d5..d1185b61bcf9298e4b0c73f89128c52cd2e8a4cf 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ man_dir               := $(DESTDIR)/usr/share/man
 man8_dir       := $(man_dir)/man8
 examples_dir   := $(doc_dir)/examples
 
-scripts_in     := $(filter-out scripts/config.in scripts/errorlib.in scripts/text.in, $(wildcard scripts/*.in))
+scripts_in     := $(filter-out scripts/config scripts/errorlib scripts/text, $(wildcard scripts/*))
 htmls_in       := $(wildcard html/*.html.in)
 cgis           := $(wildcard cgi/*.cgi cgi/*.pl)
 
@@ -48,13 +48,13 @@ $(var_dir)/spool/db-h $(scripts_dir) $(perl_dir) $(examples_dir) $(man8_dir); \
 
 
        # install the scripts
-       $(foreach script,$(scripts_in), $(install_exec) $(script) $(scripts_dir)/$(patsubst scripts/%.in,%,$(script));)
-       $(install_data) scripts/errorlib.in $(scripts_dir)/errorlib
+       $(foreach script,$(scripts_in), $(install_exec) $(script) $(scripts_dir)/$(script);)
+       $(install_data) scripts/errorlib $(scripts_dir)/errorlib
 
        # install examples
-       $(install_data) scripts/config.in $(examples_dir)/config
+       $(install_data) scripts/config $(examples_dir)/config
        $(install_data) scripts/config.debian $(examples_dir)/config.debian
-       $(install_data) scripts/text.in $(examples_dir)/text
+       $(install_data) scripts/text $(examples_dir)/text
        $(install_data) debian/crontab misc/nextnumber misc/Maintainers \
          misc/Maintainers.override misc/pseudo-packages.description \
          misc/sources $(examples_dir)
@@ -67,7 +67,7 @@ $(var_dir)/spool/db-h $(scripts_dir) $(perl_dir) $(examples_dir) $(man8_dir); \
 
        # install the CGIs
        for cgi in $(cgis); do $(install_exec) $$cgi $(var_dir)/www/cgi; done
-       $(install_exec) cgi/bugs-fetch2.pl.in $(var_dir)/www/cgi/bugs-fetch2.pl
+       $(install_exec) cgi/bugs-fetch2.pl $(var_dir)/www/cgi/bugs-fetch2.pl
 
 #      # install Perl modules
 #      for perl in $(perls); do $(install_data) $$perl $(perl_dir); done
index abc8dbef10be1496d80fe3c20fa53b48e556709d..5711e1b1028998f2ff57ecdcfa7f3f7aed0e5763 100755 (executable)
@@ -2,7 +2,8 @@
 
 use warnings;
 use strict;
-use POSIX qw(strftime tzset);
+
+use POSIX qw(strftime);
 use MIME::Parser;
 use MIME::Decoder;
 use IO::Scalar;
@@ -12,13 +13,16 @@ use Debbugs::Config qw(:globals :text);
 
 # for read_log_records
 use Debbugs::Log qw(read_log_records);
-use Debbugs::MIME qw(convert_to_utf8 decode_rfc1522 create_mime_message);
 use Debbugs::CGI qw(:url :html :util);
+use Debbugs::CGI::Bugreport qw(:all);
 use Debbugs::Common qw(buglog getmaintainers);
 use Debbugs::Packages qw(getpkgsrc);
 use Debbugs::Status qw(splitpackages get_bug_status isstrongseverity);
 
 use Scalar::Util qw(looks_like_number);
+
+use Debbugs::Text qw(:templates);
+
 use CGI::Simple;
 my $q = new CGI::Simple;
 
@@ -28,7 +32,7 @@ my %param = cgi_parameters(query => $q,
                                      qw(mboxstat mboxmaint archive),
                                      qw(repeatmerged)
                                     ],
-                          default => {msg       => '',
+                          default => {msg       => '',
                                       boring    => 'no',
                                       terse     => 'no',
                                       reverse   => 'no',
@@ -42,21 +46,19 @@ my %param = cgi_parameters(query => $q,
                          );
 # This is craptacular.
 
-my $tail_html;
-
 my $ref = $param{bug} or quitcgi("No bug number");
 $ref =~ /(\d+)/ or quitcgi("Invalid bug number");
 $ref = $1;
 my $short = "#$ref";
-my $msg = $param{'msg'};
-my $att = $param{'att'};
+my ($msg) = $param{msg} =~ /^(\d+)$/ if exists $param{msg};
+my ($att) = $param{att} =~ /^(\d+)$/ if exists $param{att};
 my $boring = $param{'boring'} eq 'yes';
 my $terse = $param{'terse'} eq 'yes';
 my $reverse = $param{'reverse'} eq 'yes';
 my $mbox = $param{'mbox'} eq 'yes';
 my $mime = $param{'mime'} eq 'yes';
 
-my $trim_headers = ($param{trim} || ($msg?'no':'yes')) eq 'yes';
+my $trim_headers = ($param{trim} || ((defined $msg and $msg)?'no':'yes')) eq 'yes';
 
 my $mbox_status_message = $param{mboxstat} eq 'yes';
 my $mbox_maint = $param{mboxmaint} eq 'yes';
@@ -69,307 +71,34 @@ my $archive = $param{'archive'} eq 'yes';
 my $repeatmerged = $param{'repeatmerged'} eq 'yes';
 
 my $buglog = buglog($ref);
-
-if (defined $ENV{REQUEST_METHOD} and $ENV{REQUEST_METHOD} eq 'HEAD' and not defined($att) and not $mbox) {
-    print "Content-Type: text/html; charset=utf-8\n";
-    my @stat = stat $buglog;
-    if (@stat) {
-       my $mtime = strftime '%a, %d %b %Y %T GMT', gmtime($stat[9]);
-       print "Last-Modified: $mtime\n";
-    }
-    print "\n";
-    exit 0;
-}
-
-sub display_entity ($$$$\$\@);
-sub display_entity ($$$$\$\@) {
-    my $entity = shift;
-    my $ref = shift;
-    my $top = shift;
-    my $xmessage = shift;
-    my $this = shift;
-    my $attachments = shift;
-
-    my $head = $entity->head;
-    my $disposition = $head->mime_attr('content-disposition');
-    $disposition = 'inline' if not defined $disposition or $disposition eq '';
-    my $type = $entity->effective_type;
-    my $filename = $entity->head->recommended_filename;
-    $filename = '' unless defined $filename;
-    $filename = decode_rfc1522($filename);
-
-    if ($top and not $terse) {
-        my $header = $entity->head;
-        $$this .= "<pre class=\"headers\">\n";
-        if ($trim_headers) {
-             my @headers;
-             foreach (qw(From To Cc Subject Date)) {
-                  my $head_field = $head->get($_);
-                  next unless defined $head_field and $head_field ne '';
-                  push @headers, qq(<b>$_:</b> ) . html_escape(decode_rfc1522($head_field));
-             }
-             $$this .= join(qq(), @headers) unless $terse;
-        } else {
-             $$this .= html_escape(decode_rfc1522($entity->head->stringify));
-        }
-        $$this .= "</pre>\n";
-    }
-
-    unless (($top and $type =~ m[^text(?:/plain)?(?:;|$)]) or
-           ($type =~ m[^multipart/])) {
-       push @$attachments, $entity;
-       my @dlargs = ($ref, msg=>$xmessage, att=>$#$attachments);
-       push @dlargs, (filename=>$filename) if $filename ne '';
-       my $printname = $filename;
-       $printname = 'Message part ' . ($#$attachments + 1) if $filename eq '';
-       $$this .= '<pre class="mime">[<a href="' . html_escape(bug_url(@dlargs)) . qq{">$printname</a> } .
-                 "($type, $disposition)]</pre>\n";
-
-       if ($msg and defined($att) and $att == $#$attachments) {
-           my $head = $entity->head;
-           chomp(my $type = $entity->effective_type);
-           my $body = $entity->stringify_body;
-           print "Content-Type: $type";
-           my ($charset) = $head->get('Content-Type:') =~ m/charset\s*=\s*\"?([\w-]+)\"?/i;
-           print qq(; charset="$charset") if defined $charset;
-           print "\n";
-           if ($filename ne '') {
-               my $qf = $filename;
-               $qf =~ s/"/\\"/g;
-               $qf =~ s[.*/][];
-               print qq{Content-Disposition: inline; filename="$qf"\n};
-           }
-           print "\n";
-           my $decoder = new MIME::Decoder($head->mime_encoding);
-           $decoder->decode(new IO::Scalar(\$body), \*STDOUT);
-           exit(0);
-       }
-    }
-
-    return if not $top and $disposition eq 'attachment' and not defined($att);
-    return unless ($type =~ m[^text/?] and
-                  $type !~ m[^text/(?:html|enriched)(?:;|$)]) or
-                 $type =~ m[^application/pgp(?:;|$)] or
-                 $entity->parts;
-
-    if ($entity->is_multipart) {
-       my @parts = $entity->parts;
-       foreach my $part (@parts) {
-           display_entity($part, $ref, 0, $xmessage,
-                          $$this, @$attachments);
-           $$this .= "\n";
-       }
-    } elsif ($entity->parts) {
-       # We must be dealing with a nested message.
-       $$this .= "<blockquote>\n";
-       my @parts = $entity->parts;
-       foreach my $part (@parts) {
-           display_entity($part, $ref, 1, $xmessage,
-                          $$this, @$attachments);
-           $$this .= "\n";
-       }
-       $$this .= "</blockquote>\n";
-    } else {
-        if (not $terse) {
-             my $content_type = $entity->head->get('Content-Type:') || "text/html";
-             my ($charset) = $content_type =~ m/charset\s*=\s*\"?([\w-]+)\"?/i;
-             my $body = $entity->bodyhandle->as_string;
-             $body = convert_to_utf8($body,$charset) if defined $charset;
-             $body = html_escape($body);
-             # Attempt to deal with format=flowed
-             if ($content_type =~ m/format\s*=\s*\"?flowed\"?/i) {
-                  $body =~ s{^\ }{}mgo;
-                  # we ignore the other things that you can do with
-                  # flowed e-mails cause they don't really matter.
-             }
-             # Add links to URLs
-             $body =~ s,((ftp|http|https)://[\S~-]+?/?)((\&gt\;)?[)]?[']?[:.\,]?(\s|$)),<a href=\"$1\">$1</a>$3,go;
-             # Add links to bug closures
-             $body =~ s[(closes:\s*(?:bug)?\#?\s?\d+(?:,?\s*(?:bug)?\#?\s?\d+)*)
-                       ][my $temp = $1; $temp =~ s{(\d+)}{qq(<a href=").html_escape(bug_url($1)).qq(">$1</a>)}ge; $temp;]gxie;
-             $$this .= qq(<pre class="message">$body</pre>\n);
-        }
-    }
-}
-
-my %maintainer = %{getmaintainers()};
-my %pkgsrc = %{getpkgsrc()};
-
-my $indexentry;
-my $showseverity;
-
-my $tpack;
-my $tmain;
-
-my $dtime = strftime "%a, %e %b %Y %T UTC", gmtime;
-$tail_html = $gHTMLTail;
-$tail_html =~ s/SUBSTITUTE_DTIME/$dtime/;
-
-my %status = %{get_bug_status(bug=>$ref)};
-unless (%status) {
-    print <<EOF;
-Content-Type: text/html; charset=utf-8
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html>
-<head><title>$short - $gProject $gBug report logs</title></head>
-<body>
-<h1>$gProject $gBug report logs - $short</h1>
-<p>There is no record of $gBug $short.
-Try the <a href="http://$gWebDomain/">search page</a> instead.</p>
-$tail_html</body></html>
-EOF
-    exit 0;
-}
-
-$|=1;
-
-$tpack = lc $status{'package'};
-my @tpacks = splitpackages($tpack);
-
-if  ($status{severity} eq 'normal') {
-       $showseverity = '';
-} elsif (isstrongseverity($status{severity})) {
-       $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
-} else {
-       $showseverity = "Severity: $status{severity};\n";
-}
-
-if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
-     $indexentry.= q(<div style="float:right"><a href=").
-         html_escape(version_url($status{package},
-                                 $status{found_versions},
-                                 $status{fixed_versions},
-                                )).
-         q("><img alt="version graph" src=").
-              html_escape(version_url($status{package},
-                                      $status{found_versions},
-                                      $status{fixed_versions},
-                                      2,
-                                      2,
-                                     )).qq{"></a></div>};
-}
-
-
-$indexentry .= "<div class=\"msgreceived\">\n";
-$indexentry .= htmlize_packagelinks($status{package}, 0) . ";\n";
-
-foreach my $pkg (@tpacks) {
-    my $tmaint = defined($maintainer{$pkg}) ? $maintainer{$pkg} : '(unknown)';
-    my $tsrc = defined($pkgsrc{$pkg}) ? $pkgsrc{$pkg} : '(unknown)';
-
-    $indexentry .=
-            htmlize_maintlinks(sub { $_[0] == 1 ? "Maintainer for $pkg is\n"
-                                            : "Maintainers for $pkg are\n" },
-                           $tmaint);
-    $indexentry .= ";\nSource for $pkg is\n".
-            '<a href="'.html_escape(pkg_url(src=>$tsrc))."\">$tsrc</a>" if ($tsrc ne "(unknown)");
-    $indexentry .= ".\n";
-}
-
-$indexentry .= "<br>";
-$indexentry .= htmlize_addresslinks("Reported by: ", \&submitterurl,
-                                $status{originator}) . ";\n";
-$indexentry .= sprintf "Date: %s.\n",
-               (strftime "%a, %e %b %Y %T UTC", localtime($status{date}));
-
-$indexentry .= "<br>Owned by: " . html_escape($status{owner}) . ".\n"
-              if length $status{owner};
-
-$indexentry .= "</div>\n";
-
-my @descstates;
-
-$indexentry .= "<h3>$showseverity";
-$indexentry .= sprintf "Tags: %s;\n", 
-               html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
-                       if length($status{tags});
-$indexentry .= "<br>" if (length($showseverity) or length($status{tags}));
-
-my @merged= split(/ /,$status{mergedwith});
-if (@merged) {
-       my $descmerged = 'Merged with ';
-       my $mseparator = '';
-       for my $m (@merged) {
-               $descmerged .= $mseparator."<a href=\"" . html_escape(bug_url($m)) . "\">#$m</a>";
-               $mseparator= ",\n";
-       }
-       push @descstates, $descmerged;
-}
-
-if (@{$status{found_versions}}) {
-    my $foundtext = 'Found in ';
-    $foundtext .= (@{$status{found_versions}} == 1) ? 'version ' : 'versions ';
-    $foundtext .= join ', ', map html_escape($_), @{$status{found_versions}};
-    push @descstates, $foundtext;
-}
-if (@{$status{fixed_versions}}) {
-    my $fixedtext = '<strong>Fixed</strong> in ';
-    $fixedtext .= (@{$status{fixed_versions}} == 1) ? 'version ' : 'versions ';
-    $fixedtext .= join ', ', map html_escape($_), @{$status{fixed_versions}};
-    if (length($status{done})) {
-       $fixedtext .= ' by ' . html_escape(decode_rfc1522($status{done}));
-    }
-    push @descstates, $fixedtext;
-}
-
-if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
-     push @descstates, '<a href="'.
-         html_escape(version_url($status{package},
-                                 $status{found_versions},
-                                 $status{fixed_versions},
-                                )).qq{">Version Graph</a>};
-}
-
-if (length($status{done})) {
-    push @descstates, "<strong>Done:</strong> ".html_escape(decode_rfc1522($status{done}));
-}
-
-if (length($status{forwarded})) {
-    my $forward_link = html_escape($status{forwarded});
-    $forward_link =~ s,((ftp|http|https)://[\S~-]+?/?)((\&gt\;)?[)]?[']?[:.\,]?(\s|$)),<a href="$1">$1</a>$3,go;
-    push @descstates, "<strong>Forwarded</strong> to $forward_link";
-}
-
-
-my @blockedby= split(/ /, $status{blockedby});
-if (@blockedby && $status{"pending"} ne 'fixed' && ! length($status{done})) {
-    for my $b (@blockedby) {
-        my %s = %{get_bug_status($b)};
-        next if $s{"pending"} eq 'fixed' || length $s{done};
-        push @descstates, "Fix blocked by <a href=\"" . html_escape(bug_url($b)) . "\">#$b</a>: ".html_escape($s{subject});
-    }
-}
-
-my @blocks= split(/ /, $status{blocks});
-if (@blocks && $status{"pending"} ne 'fixed' && ! length($status{done})) {
-    for my $b (@blocks) {
-        my %s = %{get_bug_status($b)};
-        next if $s{"pending"} eq 'fixed' || length $s{done};
-        push @descstates, "Blocking fix for <a href=\"" . html_escape(bug_url($b)) . "\">#$b</a>: ".html_escape($s{subject});
-    }
+my @stat = stat $buglog;
+my $mtime = '';
+if (@stat) {
+     $mtime = strftime '%a, %d %b %Y %T GMT', gmtime($stat[9]);
 }
 
-if ($buglog !~ m#^\Q$gSpoolDir/db#) {
-    push @descstates, "Bug is archived. No further changes may be made";
+if ($q->request_method() eq 'HEAD' and not defined($att) and not $mbox) {
+     print $q->header(-type => "text/html",
+                     -charset => 'utf-8',
+                     (length $mtime)?(-last_modified => $mtime):(),
+                    );
+     exit 0;
 }
 
-$indexentry .= join(";\n<br>", @descstates) . ".\n" if @descstates;
-$indexentry .= "</h3>\n";
-
-my $descriptivehead = $indexentry;
 
 my $buglogfh;
 if ($buglog =~ m/\.gz$/) {
     my $oldpath = $ENV{'PATH'};
     $ENV{'PATH'} = '/bin:/usr/bin';
-    $buglogfh = new IO::File "zcat $buglog |" or &quitcgi("open log for $ref: $!");
+    $buglogfh = IO::File->new("zcat $buglog |") or quitcgi("open log for $ref: $!");
     $ENV{'PATH'} = $oldpath;
 } else {
-    $buglogfh = new IO::File "<$buglog" or &quitcgi("open log for $ref: $!");
+    $buglogfh = IO::File->new($buglog,'r') or quitcgi("open log for $ref: $!");
 }
 
 
+my %status = %{get_bug_status(bug=>$ref)};
+
 my @records;
 eval{
      @records = read_log_records($buglogfh);
@@ -379,123 +108,11 @@ if ($@) {
 }
 undef $buglogfh;
 
-=head2 handle_email_message
-
-     handle_email_message($record->{text},
-                         ref        => $bug_number,
-                         msg_number => $msg_number,
-                        );
-
-Returns a decoded e-mail message and displays entities/attachments as
-appropriate.
-
-
-=cut
-
-sub handle_email_message{
-     my ($email,%options) = @_;
-
-     my $output = '';
-     my $parser = new MIME::Parser;
-     # Because we are using memory, not tempfiles, there's no need to
-     # clean up here like in Debbugs::MIME
-     $parser->tmp_to_core(1);
-     $parser->output_to_core(1);
-     my $entity = $parser->parse_data( $email);
-     my @attachments = ();
-     display_entity($entity, $options{ref}, 1, $options{msg_number}, $output, @attachments);
-     return $output;
-
-}
-
-=head2 handle_record
-
-     push @log, handle_record($record,$ref,$msg_num);
-
-Deals with a record in a bug log as returned by
-L<Debbugs::Log::read_log_records>; returns the log information that
-should be output to the browser.
-
-=cut
-
-sub handle_record{
-     my ($record,$bug_number,$msg_number,$seen_msg_ids) = @_;
-
-     my $output = '';
-     local $_ = $record->{type};
-     if (/html/) {
-         my ($time) = $record->{text} =~ /<!--\s+time:(\d+)\s+-->/;
-         my $class = $record->{text} =~ /^<strong>(?:Acknowledgement|Reply|Information|Report|Notification)/ ? 'infmessage':'msgreceived';
-         $output .= decode_rfc1522($record->{text});
-         # Link to forwarded http:// urls in the midst of the report
-         # (even though these links already exist at the top)
-         $output =~ s,((?:ftp|http|https)://[\S~-]+?/?)([\)\'\:\.\,]?(?:\s|\.<|$)),<a href=\"$1\">$1</a>$2,go;
-         # Add links to the cloned bugs
-         $output =~ s{(Bug )(\d+)( cloned as bugs? )(\d+)(?:\-(\d+)|)}{$1.bug_links($2).$3.bug_links($4,$5)}eo;
-         # Add links to merged bugs
-         $output =~ s{(?<=Merged )([\d\s]+)(?=\.)}{join(' ',map {bug_links($_)} (split /\s+/, $1))}eo;
-         # Add links to blocked bugs
-         $output =~ s{(?<=Blocking bugs)(?:( of )(\d+))?( (?:added|set to|removed):\s+)([\d\s\,]+)}
-                     {(defined $2?$1.bug_links($2):'').$3.
-                           join(' ',map {bug_links($_)} (split /\,?\s+/, $4))}eo;
-         # Add links to reassigned packages
-         $output =~ s{(Bug reassigned from package \`)([^']+?)((?:'|\&\#39;) to \`)([^']+?)((?:'|\&\#39;))}
-         {$1.q(<a href=").html_escape(pkg_url(pkg=>$2)).qq(">$2</a>).$3.q(<a href=").html_escape(pkg_url(pkg=>$4)).qq(">$4</a>).$5}eo;
-         if (defined $time) {
-              $output .= ' ('.strftime('%a, %d %b %Y %T GMT',gmtime($time)).') ';
-         }
-         $output .= '<a href="' . html_escape(bug_url($ref, msg => ($msg_number+1))) . '">Full text</a> and <a href="' .
-              html_escape(bug_url($ref, msg => ($msg_number+1), mbox => 'yes')) . '">rfc822 format</a> available.';
-
-         $output = qq(<div class="$class"><hr>\n<a name="$msg_number"></a>\n) . $output . "</div>\n";
-     }
-     elsif (/recips/) {
-         my ($msg_id) = $record->{text} =~ /^Message-Id:\s+<(.+)>/im;
-         if (defined $msg_id and exists $$seen_msg_ids{$msg_id}) {
-              return ();
-         }
-         elsif (defined $msg_id) {
-              $$seen_msg_ids{$msg_id} = 1;
-         }
-         $output .= qq(<hr><p class="msgreceived"><a name="$msg_number"></a>\n);
-         $output .= 'View this message in <a href="' . html_escape(bug_url($ref, msg=>$msg_number, mbox=>'yes')) . '">rfc822 format</a></p>';
-         $output .= handle_email_message($record->{text},
-                                   ref        => $bug_number,
-                                   msg_number => $msg_number,
-                                  );
-     }
-     elsif (/autocheck/) {
-         # Do nothing
-     }
-     elsif (/incoming-recv/) {
-         my ($msg_id) = $record->{text} =~ /^Message-Id:\s+<(.+)>/im;
-         if (defined $msg_id and exists $$seen_msg_ids{$msg_id}) {
-              return ();
-         }
-         elsif (defined $msg_id) {
-              $$seen_msg_ids{$msg_id} = 1;
-         }
-         # Incomming Mail Message
-         my ($received,$hostname) = $record->{text} =~ m/Received: \(at (\S+)\) by (\S+)\;/;
-         $output .= qq|<hr><p class="msgreceived"><a name="$msg_number"></a><a name="msg$msg_number"></a><a href="#$msg_number">Message #$msg_number</a> received at |.
-              html_escape("$received\@$hostname") .
-                   q| (<a href="| . html_escape(bug_url($ref, msg=>$msg_number)) . '">full text</a>'.
-                        q|, <a href="| . html_escape(bug_url($ref, msg=>$msg_number,mbox=>'yes')) .'">mbox</a>)'.":</p>\n";
-         $output .= handle_email_message($record->{text},
-                                   ref        => $bug_number,
-                                   msg_number => $msg_number,
-                                  );
-     }
-     else {
-         die "Unknown record type $_";
-     }
-     return $output;
-}
 
 my $log='';
 my $msg_num = 0;
 my $skip_next = 0;
-if (looks_like_number($msg) and ($msg-1) <= $#records) {
+if (defined($msg) and ($msg-1) <= $#records) {
      @records = ($records[$msg-1]);
      $msg_num = $msg - 1;
 }
@@ -570,6 +187,16 @@ END
 }
 
 else {
+     if (defined $att and defined $msg and @records) {
+         $msg_num++;
+         print handle_email_message($records[0]->{text},
+                                    ref => $ref,
+                                    msg_num => $msg_num,
+                                    att => $att,
+                                    msg => $msg,
+                                   );
+         exit 0;
+     }
      my %seen_msg_ids;
      for my $record (@records) {
          $msg_num++;
@@ -586,67 +213,116 @@ else {
 $log = join("\n",@log);
 
 
-print "Content-Type: text/html; charset=utf-8\n";
+# All of the below should be turned into a template
 
-my @stat = stat $buglog;
-if (@stat) {
-     my $mtime = strftime '%a, %d %b %Y %T GMT', gmtime($stat[9]);
-     print "Last-Modified: $mtime\n";
+my %maintainer = %{getmaintainers()};
+my %pkgsrc = %{getpkgsrc()};
+
+my $indexentry;
+my $showseverity;
+
+my $tpack;
+my $tmain;
+
+my $dtime = strftime "%a, %e %b %Y %T UTC", gmtime;
+
+unless (%status) {
+    print $q->header(-type => "text/html",
+                    -charset => 'utf-8',
+                    (length $mtime)?(-last_modified => $mtime):(),
+                   );
+    print fill_in_template(template=>'cgi/no_such_bug',
+                          variables => {modify_time => $dtime,
+                                        bug_num     => $ref,
+                                       },
+                         );
+    exit 0;
 }
 
-print "\n";
-
-my $title = html_escape($status{subject});
-
-my $dummy2 = $gWebHostBugDir;
-
-print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
-print <<END;
-<HTML><HEAD>
-<TITLE>$short - $title - $gProject $gBug report logs</TITLE>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
-<link rel="stylesheet" href="$gWebHostBugDir/css/bugs.css" type="text/css">
-<script type="text/javascript">
-<!--
-function toggle_infmessages()
-{
-        allDivs=document.getElementsByTagName("div");
-        for (var i = 0 ; i < allDivs.length ; i++ )
-        {
-                if (allDivs[i].className == "infmessage")
-                {
-                        allDivs[i].style.display=(allDivs[i].style.display == 'none' | allDivs[i].style.display == '') ? 'block' : 'none';
-                }
-        }
+#$|=1;
+
+my %package;
+my @packages = splitpackages($status{package});
+
+foreach my $pkg (@packages) {
+     $package{$pkg} = {maintainer => exists($maintainer{$pkg}) ? $maintainer{$pkg} : '(unknown)',
+                      source     => exists($pkgsrc{$pkg}) ? $pkgsrc{$pkg} : '(unknown)',
+                      package    => $pkg,
+                     };
 }
--->
-</script>
-</HEAD>
-<BODY>
-END
-print "<H1>" . "$gProject $gBug report logs - <A HREF=\"mailto:$ref\@$gEmailDomain\">$short</A>" .
-      "<BR>" . $title . "</H1>\n";
-print "$descriptivehead\n";
 
-if (looks_like_number($msg)) {
-     printf qq(<p><a href="%s">Full log</a></p>),html_escape(bug_url($ref));
+# fixup various bits of the status
+$status{tags_array} = [sort(split(/\s+/, $status{tags}))];
+$status{date_text} = strftime('%a, %e %b %Y %T UTC', gmtime($status{date}));
+$status{mergedwith_array} = [split(/ /,$status{mergedwith})];
+
+
+my $version_graph = '';
+if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
+     $version_graph = q(<a href=").
+         html_escape(version_url(package => $status{package},
+                                 found => $status{found_versions},
+                                 fixed => $status{fixed_versions},
+                                )
+                    ).
+         q("><img alt="version graph" src=").
+         html_escape(version_url(package => $status{package},
+                                 found => $status{found_versions},
+                                 fixed => $status{fixed_versions},
+                                 width => 2,
+                                 height => 2,
+                                )
+                    ).
+         qq{"></a>};
 }
-else {
-     print qq(<p><a href="mailto:$ref\@$gEmailDomain">Reply</a> ),
-         qq(or <a href="mailto:$ref-subscribe\@$gEmailDomain">subscribe</a> ),
-              qq(to this bug.</p>\n);
-     print qq(<p><a href="javascript:toggle_infmessages();">Toggle useless messages</a></p>);
-     printf qq(<div class="msgreceived"><p>View this report as an <a href="%s">mbox folder</a>, ).
-         qq(<a href="%s">status mbox</a>, <a href="%s">maintainer mbox</a></p></div>\n),
-              html_escape(bug_url($ref, mbox=>'yes')),
-                   html_escape(bug_url($ref, mbox=>'yes',mboxstatus=>'yes')),
-                        html_escape(bug_url($ref, mbox=>'yes',mboxmaint=>'yes'));
+
+
+
+my @blockedby= split(/ /, $status{blockedby});
+$status{blockedby_array} = [];
+if (@blockedby && $status{"pending"} ne 'fixed' && ! length($status{done})) {
+    for my $b (@blockedby) {
+        my %s = %{get_bug_status($b)};
+        next if $s{"pending"} eq 'fixed' || length $s{done};
+       push @{$status{blockedby_array}},{bug_num => $b, subject => $s{subject}, status => \%s};
+   }
 }
-print "$log";
-print "<HR>";
-print "<p class=\"msgreceived\">Send a report that <a href=\"/cgi-bin/bugspam.cgi?bug=$ref\">this bug log contains spam</a>.</p>\n<HR>\n";
-print $tail_html;
 
-print "</BODY></HTML>\n";
+my @blocks= split(/ /, $status{blocks});
+$status{blocks_array} = [];
+if (@blocks && $status{"pending"} ne 'fixed' && ! length($status{done})) {
+    for my $b (@blocks) {
+        my %s = %{get_bug_status($b)};
+        next if $s{"pending"} eq 'fixed' || length $s{done};
+       push @{$status{blocks_array}}, {bug_num => $b, subject => $s{subject}, status => \%s};
+    }
+}
+
+if ($buglog !~ m#^\Q$gSpoolDir/db#) {
+     $status{archived} = 1;
+}
+
+my $descriptivehead = $indexentry;
 
-exit 0;
+print $q->header(-type => "text/html",
+                -charset => 'utf-8',
+                (length $mtime)?(-last_modified => $mtime):(),
+               );
+
+print fill_in_template(template => 'cgi/bugreport',
+                      variables => {status => \%status,
+                                    package => \%package,
+                                    log           => $log,
+                                    bug_num       => $ref,
+                                    version_graph => $version_graph,
+                                    isstrongseverity => \&Debbugs::Status::isstrongseverity,
+                                    html_escape   => \&Debbugs::CGI::html_escape,
+                                    looks_like_number => \&Scalar::Util::looks_like_number,
+                                   },
+                      hole_var  => {'&package_links' => \&Debbugs::CGI::package_links,
+                                    '&bug_links'     => \&Debbugs::CGI::bug_links,
+                                    '&version_url'   => \&Debbugs::CGI::version_url,
+                                    '&bug_url'       => \&Debbugs::CGI::bug_url,
+                                    '&strftime'      => \&POSIX::strftime,
+                                   }
+                     );
diff --git a/cgi/bugs.css b/cgi/bugs.css
deleted file mode 100644 (file)
index ed42581..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-html {
-    color: #000; 
-    background: #fefefe;
-    font-family: serif;
-    margin: 1em;
-    border: 0;
-    padding: 0;
-    line-height: 120%;
-}
-
-body {
-    margin: 0;
-    border: 0;
-    padding: 0;
-}
-
-h1, h2, h3 {
-    text-align: left; 
-    font-family: sans-serif;
-}
-
-h1 {
-    font-size: 150%;
-    line-height: 150%;
-}
-
-h2 {
-    font-size: 130%;
-}
-
-h3 {
-    font-size: 100%;
-}
-
-a:link {
-    color: #1b56ce;
-    font-weight: bold;
-}
-
-a:visited {
-    color:#1b56ce;
-}
-
-a:link:active, a:link:visited {
-    color:#ff0000;
-}
-
-a:link:hover, a:visited:hover {
-    color: #d81e1e;
-}
-
-a.submitter:link {
-    color: #242424;
-    font-family: sans-serif;
-    font-size: 95%;
-    text-decoration: underline;
-    font-weight: normal;
-}
-
-a.submitter:visited, a.submitter:active {
-    color: #6e6e6e;
-    font-family: sans-serif;
-    font-size: 95%;
-}
-
-a.submitter:hover, a.submitter:visited:hover {
-    color: #d01414;
-    font-family: sans-serif;
-    font-size: 95%;
-}
-
-pre.message {
-    font-family: monospace;
-    padding-top: 0;
-    margin-top: 0;
-    border-top: 0;
-}
-
-.sparse li {
-    padding-top: 1ex;
-    margin-top: 1ex;
-    border-top: 1ex;
-}
-
-a.bugtitle {
-    font-weight: bold;
-    font-size: 110%;
-}
-
-
-pre.headers {
-    font-family: sans-serif;
-    font-size: 95%;
-    color: #3c3c3c;
-    background-color: #f0f0f0;
-    padding: 2px;
-    border: #a7a7a7 1px solid;
-    line-height: 120%
-}
-
-pre.mime {
-    font-family: monospace;
-    font-size: 95%;
-    color: #797979;
-}
-
-/* This must be separate from the other CSS to make the showing of
-   unimportant messages work in bugreport.cgi. */ 
-.infmessage { display: none; }
-
-.infmessage {
-    font-family: sans-serif;
-    font-size: 90%;
-    color: #686868;
-}
-
-.msgreceived {
-    font-family: sans-serif;
-    font-size: 90%;
-    color: #686868;
-}
-
-pre.tags {
-    color: #a3a3a3;
-    font-size: 90%;
-}
-
-hr.thin {
-    color: #a1a1a1;
-}
-
-em.severity {
-    color: #c31212;
-}
-
-code, address {
-    font-family: sans-serif;
-}
-
-p {
-    font-family: sans-serif;
-    font-size: 95%;
-}
-
-h2.outstanding {
-    font-family: sans-serif;
-    background-color: #f0f0f0;
-    color: #3c3c3c;
-    border: #a7a7a7 1px solid;
-    padding: 10px;
-}
-
-a.options:link, a.options:visited {
-    font-family: sans-serif;
-    background-color: #f0f0f0;
-    color: #3c3c3c;
-    text-decoration: none;
-    border-bottom: #3c3c3c 1px dotted;
-}
-
-li {
-    list-style-type: square;
-}
-
-.bugs li {
-    margin-top: 5px;
-}
-
-input {
-    border: #000 1px solid;
-    margin: 3px;
-}
-
-table.forms {
-    font-size: 95%;
-    font-family: sans-serif;
-    margin-left: 10px;
-}
-
-select {
-    margin: 3px;
-    border: #000 1px solid;
-}
-
index c61bc01a7604ee601e2d454500d610e0a8dace0d..3750949728c8dbbe38672d0d25d89445743383e1 100755 (executable)
 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
 
 
-package debbugs;
-
 use warnings;
 use strict;
+
 use POSIX qw(strftime nice);
 
 use Debbugs::Config qw(:globals :text :config);
+
 use Debbugs::User;
-use Debbugs::CGI qw(version_url maint_decode);
-use Debbugs::Common qw(getparsedaddrs :date make_list getmaintainers getpseudodesc);
+
+use Debbugs::Common qw(getparsedaddrs make_list getmaintainers getpseudodesc);
+
 use Debbugs::Bugs qw(get_bugs bug_filter newest_bug);
 use Debbugs::Packages qw(getsrcpkgs getpkgsrc get_versions);
-use Debbugs::Status qw(:status);
-use Debbugs::CGI qw(:all);
 
-use vars qw($gPackagePages $gWebDomain %gSeverityDisplay @gSeverityList);
+use Debbugs::CGI qw(:all);
 
-if (defined $ENV{REQUEST_METHOD} and $ENV{REQUEST_METHOD} eq 'HEAD') {
-    print "Content-Type: text/html; charset=utf-8\n\n";
-    exit 0;
-}
+use Debbugs::CGI::Pkgreport qw(:all);
 
-nice(5);
+use Debbugs::Text qw(:templates);
 
 use CGI::Simple;
 my $q = new CGI::Simple;
 
+if ($q->request_method() eq 'HEAD') {
+     print $q->header(-type => "text/html",
+                     -charset => 'utf-8',
+                    );
+     exit 0;
+}
+
+my $default_params = {ordering => 'normal',
+                     archive  => 0,
+                     repeatmerged => 0,
+                     include      => [],
+                     exclude      => [],
+                    };
+
 our %param = cgi_parameters(query => $q,
                            single => [qw(ordering archive repeatmerged),
                                       qw(bug-rev pend-rev sev-rev),
                                       qw(maxdays mindays version),
                                       qw(data which dist newest),
                                      ],
-                           default => {ordering => 'normal',
-                                       archive  => 0,
-                                       repeatmerged => 1,
-                                      },
+                           default => $default_params,
                           );
 
+my ($form_options,$param) = ({},undef);
+($form_options,$param)= form_options_and_normal_param(\%param)
+     if $param{form_options};
+
+%param = %{$param} if defined $param;
+
+if (exists $param{form_options} and defined $param{form_options}) {
+     delete $param{form_options};
+     delete $param{submit} if exists $param{submit};
+     for my $default (keys %{$default_params}) {
+         if (exists $param{$default} and
+             not ref($default_params->{$default}) and
+             $default_params->{$default} eq $param{$default}
+            ) {
+              delete $param{$default};
+         }
+     }
+     for my $incexc (qw(include exclude)) {
+         next unless exists $param{$incexc};
+         $param{$incexc} = [grep /\S\:\S/, make_list($param{$incexc})];
+     }
+     print $q->redirect(munge_url('pkgreport.cgi?',%param));
+     exit 0;
+}
+
 # map from yes|no to 1|0
 for my $key (qw(repeatmerged bug-rev pend-rev sev-rev)) {
      if (exists $param{$key}){
@@ -69,7 +101,6 @@ elsif (lc($param{archive}) eq 'yes') {
 }
 
 
-my $archive = ($param{'archive'} || "no") eq "yes";
 my $include = $param{'&include'} || $param{'include'} || "";
 my $exclude = $param{'&exclude'} || $param{'exclude'} || "";
 
@@ -85,6 +116,8 @@ unless (defined $ordering) {
    $ordering = "raw" if $raw_sort;
    $ordering = 'age' if $age_sort;
 }
+$param{ordering} = $ordering;
+
 our ($bug_order) = $ordering =~ /(age(?:rev)?)/;
 $bug_order = '' if not defined $bug_order;
 
@@ -124,6 +157,7 @@ for my $incexcmap (@inc_exc_mapping) {
      delete $param{$incexcmap->{key}};
 }
 
+
 my $maxdays = ($param{'maxdays'} || -1);
 my $mindays = ($param{'mindays'} || 0);
 my $version = $param{'version'} || undef;
@@ -164,10 +198,6 @@ our %cats = (
     "normal" => [ qw(status severity classification) ],
 );
 
-my @select_key = (qw(submitter maint pkg package src usertag),
-                 qw(status tag maintenc owner severity newest)
-                );
-
 if (exists $param{which} and exists $param{data}) {
      $param{$param{which}} = [exists $param{$param{which}}?(make_list($param{$param{which}})):(),
                              make_list($param{data}),
@@ -182,12 +212,10 @@ if (defined $param{maintenc}) {
 }
 
 
-if (not grep {exists $param{$_}} @select_key and exists $param{users}) {
+if (not grep {exists $param{$_}} keys %package_search_keys and exists $param{users}) {
      $param{usertag} = [make_list($param{users})];
 }
 
-quitcgi("You have to choose something to select by") unless grep {exists $param{$_}} @select_key;
-
 if (exists $param{pkg}) {
      $param{package} = $param{pkg};
      delete $param{pkg};
@@ -213,9 +241,12 @@ if (defined $param{usertag}) {
      }
 }
 
-my $Archived = $archive ? " Archived" : "";
+quitcgi("You have to choose something to select by") unless grep {exists $param{$_}} keys %package_search_keys;
 
-our $this = munge_url('pkgreport.cgi?',
+
+my $Archived = $param{archive} ? " Archived" : "";
+
+my $this = munge_url('pkgreport.cgi?',
                      %param,
                     );
 
@@ -223,8 +254,8 @@ my %indexentry;
 my %strings = ();
 
 my $dtime = strftime "%a, %e %b %Y %T UTC", gmtime;
-my $tail_html = $debbugs::gHTMLTail;
-$tail_html = $debbugs::gHTMLTail;
+my $tail_html = $gHTMLTail;
+$tail_html = $gHTMLTail;
 $tail_html =~ s/SUBSTITUTE_DTIME/$dtime/;
 
 our %seen_users;
@@ -284,20 +315,13 @@ for my $package (# For binary packages, add the binary package
 
 # walk through the keys and make the right get_bugs query.
 
-my @search_key_order = (package   => 'in package',
-                       tag       => 'tagged',
-                       severity  => 'with severity',
-                       src       => 'in source package',
-                       maint     => 'in packages maintained by',
-                       submitter => 'submitted by',
-                       owner     => 'owned by',
-                       status    => 'with status',
-                      );
-my %search_keys = @search_key_order;
+my $form_option_variables = {};
+$form_option_variables->{search_key_order} = [@package_search_key_order];
 
 # Set the title sanely and clean up parameters
 my @title;
-while (my ($key,$value) = splice @search_key_order, 0, 2) {
+my @temp = @package_search_key_order;
+while (my ($key,$value) = splice @temp, 0, 2) {
      next unless exists $param{$key};
      my @entries = ();
      $param{$key} = [map {split /\s*,\s*/} make_list($param{$key})];
@@ -339,7 +363,7 @@ if (defined $param{maint} and $param{maint} eq "" or ref($param{maint}) and not
      @bugs = get_bugs(function =>
                      sub {my %d=@_;
                           foreach my $try (splitpackages($d{"pkg"})) {
-                               return 1 if !getparsedaddrs($maintainers{$try});
+                               return 1 if not exists $maintainers{$try};
                           }
                           return 0;
                      }
@@ -354,7 +378,7 @@ elsif (defined $param{newest}) {
 else {
      #yeah for magick!
      @bugs = get_bugs((map {exists $param{$_}?($_,$param{$_}):()}
-                      keys %search_keys, 'archive'),
+                      keys %package_search_keys, 'archive'),
                      usertags => \%ut,
                     );
 }
@@ -369,14 +393,35 @@ elsif (defined $param{dist}) {
 $title = html_escape($title);
 
 my @names; my @prior; my @order;
-determine_ordering();
+determine_ordering(cats => \%cats,
+                  param => \%param,
+                  ordering => \$ordering,
+                  names => \@names,
+                  prior => \@prior,
+                  title => \@title,
+                  order => \@order,
+                 );
 
 # strip out duplicate bugs
 my %bugs;
 @bugs{@bugs} = @bugs;
 @bugs = keys %bugs;
 
-my $result = pkg_htmlizebugs(\@bugs);
+my $result = pkg_htmlizebugs(bugs => \@bugs,
+                            names => \@names,
+                            title => \@title,
+                            order => \@order,
+                            prior => \@prior,
+                            ordering => $ordering,
+                            bugusertags => \%bugusertags,
+                            bug_rev => $bug_rev,
+                            bug_order => $bug_order,
+                            repeatmerged => $param{repeatmerged},
+                            include => $include,
+                            exclude => $exclude,
+                            this => $this,
+                            options => \%param,
+                           );
 
 print "Content-Type: text/html; charset=utf-8\n\n";
 
@@ -403,82 +448,18 @@ if (defined $pseudodesc and defined $pkg and exists $pseudodesc->{$pkg}) {
 # output infomration about the packages
 
 for my $package (make_list($param{package}||[])) {
-     output_package_info('binary',$package);
+     print generate_package_info(binary => 1,
+                                package => $package,
+                                options => \%param,
+                                bugs    => \@bugs,
+                               );
 }
 for my $package (make_list($param{src}||[])) {
-     output_package_info('source',$package);
-}
-
-sub output_package_info{
-    my ($srcorbin,$package) = @_;
-
-    my %pkgsrc = %{getpkgsrc()};
-    my $srcforpkg = $package;
-    if ($srcorbin eq 'binary') {
-        $srcforpkg = $pkgsrc{$package};
-        defined $srcforpkg or $srcforpkg = $package;
-    }
-
-    my $showpkg = html_escape($package);
-    my $maintainers = getmaintainers();
-    my $maint = $maintainers->{$srcforpkg};
-    if (defined $maint) {
-        print '<p>';
-        print htmlize_maintlinks(sub { $_[0] == 1 ? "Maintainer for $showpkg is "
-                                        : "Maintainers for $showpkg are "
-                                   },
-                             $maint);
-        print ".</p>\n";
-    } else {
-        print "<p>No maintainer for $showpkg. Please do not report new bugs against this package.</p>\n";
-    }
-    my @pkgs = getsrcpkgs($srcforpkg);
-    @pkgs = grep( !/^\Q$package\E$/, @pkgs );
-    if ( @pkgs ) {
-        @pkgs = sort @pkgs;
-        if ($srcorbin eq 'binary') {
-             print "<p>You may want to refer to the following packages that are part of the same source:\n";
-        } else {
-             print "<p>You may want to refer to the following individual bug pages:\n";
-        }
-        #push @pkgs, $src if ( $src && !grep(/^\Q$src\E$/, @pkgs) );
-        print join( ", ", map( "<A href=\"" . html_escape(munge_url($this,package=>$_,src=>[],newest=>[])) . "\">$_</A>", @pkgs ) );
-        print ".\n";
-    }
-    my @references;
-    my $pseudodesc = getpseudodesc();
-    if ($package and defined($pseudodesc) and exists($pseudodesc->{$package})) {
-        push @references, "to the <a href=\"http://${debbugs::gWebDomain}/pseudo-packages${debbugs::gHTMLSuffix}\">".
-             "list of other pseudo-packages</a>";
-    } else {
-        if ($package and defined $gPackagePages) {
-             push @references, sprintf "to the <a href=\"%s\">%s package page</a>",
-                  html_escape("http://${debbugs::gPackagePages}/$package"), html_escape("$package");
-        }
-        if (defined $gSubscriptionDomain) {
-             my $ptslink = $package ? $srcforpkg : $src;
-             push @references, q(to the <a href="http://).html_escape("$gSubscriptionDomain/$ptslink").q(">Package Tracking System</a>);
-        }
-        # Only output this if the source listing is non-trivial.
-        if ($srcorbin eq 'binary' and $srcforpkg) {
-             push @references, sprintf "to the source package <a href=\"%s\">%s</a>'s bug page", html_escape(munge_url($this,src=>$srcforpkg,package=>[],newest=>[])), html_escape($srcforpkg);
-        }
-    }
-    if (@references) {
-        $references[$#references] = "or $references[$#references]" if @references > 1;
-        print "<p>You might like to refer ", join(", ", @references), ".</p>\n";
-    }
-    if (defined $param{maint} || defined $param{maintenc}) {
-        print "<p>If you find a bug not listed here, please\n";
-        printf "<a href=\"%s\">report it</a>.</p>\n",
-             html_escape("http://${debbugs::gWebDomain}/Reporting${debbugs::gHTMLSuffix}");
-    }
-    if (not $maint and not @bugs) {
-        print "<p>There is no record of the " . html_escape($package) .
-             ($srcorbin eq 'binary' ? " package" : " source package") .
-                   ", and no bugs have been filed against it.</p>";
-        $showresult = 0;
-    }
+     print generate_package_info(binary => 0,
+                                package => $package,
+                                options => \%param,
+                                bugs    => \@bugs,
+                               );
 }
 
 if (exists $param{maint} or exists $param{maintenc}) {
@@ -492,682 +473,145 @@ if (exists $param{submitter}) {
     print "different addresses.\n";
 }
 
-my $archive_links;
-my @archive_links;
-my %archive_values = (both => 'archived and unarchived',
-                     0    => 'not archived',
-                     1    => 'archived',
-                    );
-while (my ($key,$value) = each %archive_values) {
-     next if $key eq lc($param{archive});
-     push @archive_links, qq(<a href=").
-         html_escape(pkg_url((
-                      map {
-                           $_ eq 'archive'?():($_,$param{$_})
-                      } keys %param),
-                           archive => $key
-                          )).qq(">$value reports </a>);
-}
-print '<p>See the '.join (' or ',@archive_links)."</p>\n";
-
-print $result if $showresult;
+my $archive_links;
+my @archive_links;
+my %archive_values = (both => 'archived and unarchived',
+#                    0    => 'not archived',
+#                    1    => 'archived',
+#                   );
+while (my ($key,$value) = each %archive_values) {
+     next if $key eq lc($param{archive});
+     push @archive_links, qq(<a href=").
+#        html_escape(pkg_url((
+#                     map {
+#                          $_ eq 'archive'?():($_,$param{$_})
+#                     } keys %param),
+#                          archive => $key
+#                         )).qq(">$value reports </a>);
+}
+print '<p>See the '.join (' or ',@archive_links)."</p>\n";
+
+print $result;
 
 print pkg_javascript() . "\n";
-print "<h2 class=\"outstanding\"><a class=\"options\" href=\"javascript:toggle(1)\">Options</a></h2>\n";
-print "<div id=\"a_1\">\n";
-printf "<form action=\"%s\" method=POST>\n", myurl();
-
-print "<table class=\"forms\">\n";
-
-my ($checked_any, $checked_sui, $checked_ver) = ("", "", "");
-if (defined $dist) {
-  $checked_sui = "CHECKED";
-} elsif (defined $version) {
-  $checked_ver = "CHECKED";
-} else {
-  $checked_any = "CHECKED";
-}
 
-print "<tr><td>Show bugs applicable to</td>\n";
-print "    <td><input id=\"b_1_1\" name=vt value=none type=radio onchange=\"enable(1);\" $checked_any>anything</td></tr>\n";
-print "<tr><td></td>";
-print "    <td><input id=\"b_1_2\" name=vt value=bysuite type=radio onchange=\"enable(1);\" $checked_sui>" . pkg_htmlselectsuite(1,2,1) . " for " . pkg_htmlselectarch(1,2,2) . "</td></tr>\n";
-
-if (defined $pkg) {
-    my $v = html_escape($version) || "";
-    my $pkgsane = html_escape($pkg->[0]);
-    print "<tr><td></td>";
-    print "    <td><input id=\"b_1_3\" name=vt value=bypkg type=radio onchange=\"enable(1);\" $checked_ver>$pkgsane version <input id=\"b_1_3_1\" name=version value=\"$v\"></td></tr>\n";
-} elsif (defined $src) {
-    my $v = html_escape($version) || "";
-    my $srcsane = html_escape($src->[0]);
-    print "<tr><td></td>";
-    print "    <td><input name=vt value=bysrc type=radio onchange=\"enable(1);\" $checked_ver>$srcsane version <input id=\"b_1_3_1\" name=version value=\"$v\"></td></tr>\n";
-}
-print "<tr><td>&nbsp;</td></tr>\n";
-
-my $includetags = html_escape(join(" ", grep { !m/^subj:/i } map {split /[\s,]+/} ref($include)?@{$include}:$include));
-my $excludetags = html_escape(join(" ", grep { !m/^subj:/i } map {split /[\s,]+/} ref($exclude)?@{$exclude}:$exclude));
-my $includesubj = html_escape(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } map {split /[\s,]+/} ref($include)?@{$include}:$include));
-my $excludesubj = html_escape(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } map {split /[\s,]+/} ref($exclude)?@{$exclude}:$exclude));
-my $vismindays = ($mindays == 0 ? "" : $mindays);
-my $vismaxdays = ($maxdays == -1 ? "" : $maxdays);
-
-my $sel_rmy = ($param{repeatmerged} ? " selected" : "");
-my $sel_rmn = ($param{repeatmerged} ? "" : " selected");
-my $sel_ordraw = ($ordering eq "raw" ? " selected" : "");
-my $sel_ordold = ($ordering eq "oldview" ? " selected" : "");
-my $sel_ordnor = ($ordering eq "normal" ? " selected" : "");
-my $sel_ordage = ($ordering eq "age" ? " selected" : "");
-
-my $chk_bugrev = ($bug_rev ? " checked" : "");
-my $chk_pendrev = ($pend_rev ? " checked" : "");
-my $chk_sevrev = ($sev_rev ? " checked" : "");
-
-print <<EOF;
-<tr><td>Only include bugs tagged with </td><td><input name=include value="$includetags"> or that have <input name=includesubj value="$includesubj"> in their subject</td></tr>
-<tr><td>Exclude bugs tagged with </td><td><input name=exclude value="$excludetags"> or that have <input name=excludesubj value="$excludesubj"> in their subject</td></tr>
-<tr><td>Only show bugs older than</td><td><input name=mindays value="$vismindays" size=5> days, and younger than <input name=maxdays value="$vismaxdays" size=5> days</td></tr>
-
-<tr><td>&nbsp;</td></tr>
-
-<tr><td>Merged bugs should be</td><td>
-<select name=repeatmerged>
-<option value=yes$sel_rmy>displayed separately</option>
-<option value=no$sel_rmn>combined</option>
-</select>
-<tr><td>Categorise bugs by</td><td>
-<select name=ordering>
-<option value=raw$sel_ordraw>bug number only</option>
-<option value=old$sel_ordold>status and severity</option>
-<option value=normal$sel_ordnor>status, severity and classification</option>
-<option value=age$sel_ordage>status, severity, classification, and age</option>
-EOF
-
-{
-my $any = 0;
-my $o = $param{"ordering"} || "";
-for my $n (keys %cats) {
-    next if ($n eq "normal" || $n eq "oldview");
-    next if defined $hidden{$n};
-    unless ($any) {
-        $any = 1;
-       print "<option disabled>------</option>\n";
-    }
-    my @names = map { ref($_) eq "HASH" ? $_->{"nam"} : $_ } @{$cats{$n}};
-    my $name;
-    if (@names == 1) { $name = $names[0]; }
-    else { $name = " and " . pop(@names); $name = join(", ", @names) . $name; }
+print qq(<h2 class="outstanding"><!--<a class="options" href="javascript:toggle(1)">-->Options<!--</a>--></h2>\n);
 
-    printf "<option value=\"%s\"%s>%s</option>\n",
-        $n, ($o eq $n ? " selected" : ""), $name;
-}
-}
-
-print "</select></td></tr>\n";
-
-printf "<tr><td>Order bugs by</td><td>%s</td></tr>\n",
-    pkg_htmlselectyesno("pend-rev", "outstanding bugs first", "done bugs first", $pend_rev);
-printf "<tr><td></td><td>%s</td></tr>\n",
-    pkg_htmlselectyesno("sev-rev", "highest severity first", "lowest severity first", $sev_rev);
-printf "<tr><td></td><td>%s</td></tr>\n",
-    pkg_htmlselectyesno("bug-rev", "oldest bugs first", "newest bugs first", $bug_rev);
-
-print <<EOF;
-<tr><td>&nbsp;</td></tr>
-<tr><td colspan=2><input value="Reload page" type="submit"> with new settings</td></tr>
-EOF
+print option_form(template => 'cgi/pkgreport_options',
+                 param    => \%param,
+                 form_options => $form_options,
+                 variables => $form_option_variables,
+                );
 
-print "</table></form></div>\n";
+# print "<h2 class=\"outstanding\"><a class=\"options\" href=\"javascript:toggle(1)\">Options</a></h2>\n";
+# print "<div id=\"a_1\">\n";
+# printf "<form action=\"%s\" method=POST>\n", myurl();
+# 
+# print "<table class=\"forms\">\n";
+# 
+# my ($checked_any, $checked_sui, $checked_ver) = ("", "", "");
+# if (defined $dist) {
+#   $checked_sui = "CHECKED";
+# } elsif (defined $version) {
+#   $checked_ver = "CHECKED";
+# } else {
+#   $checked_any = "CHECKED";
+# }
+# 
+# print "<tr><td>Show bugs applicable to</td>\n";
+# print "    <td><input id=\"b_1_1\" name=vt value=none type=radio onchange=\"enable(1);\" $checked_any>anything</td></tr>\n";
+# print "<tr><td></td>";
+# print "    <td><input id=\"b_1_2\" name=vt value=bysuite type=radio onchange=\"enable(1);\" $checked_sui>" . pkg_htmlselectsuite(1,2,1) . " for " . pkg_htmlselectarch(1,2,2) . "</td></tr>\n";
+# 
+# if (defined $pkg) {
+#     my $v = html_escape($version) || "";
+#     my $pkgsane = html_escape($pkg->[0]);
+#     print "<tr><td></td>";
+#     print "    <td><input id=\"b_1_3\" name=vt value=bypkg type=radio onchange=\"enable(1);\" $checked_ver>$pkgsane version <input id=\"b_1_3_1\" name=version value=\"$v\"></td></tr>\n";
+# } elsif (defined $src) {
+#     my $v = html_escape($version) || "";
+#     my $srcsane = html_escape($src->[0]);
+#     print "<tr><td></td>";
+#     print "    <td><input name=vt value=bysrc type=radio onchange=\"enable(1);\" $checked_ver>$srcsane version <input id=\"b_1_3_1\" name=version value=\"$v\"></td></tr>\n";
+# }
+# print "<tr><td>&nbsp;</td></tr>\n";
+# 
+# my $includetags = html_escape(join(" ", grep { !m/^subj:/i } map {split /[\s,]+/} ref($include)?@{$include}:$include));
+# my $excludetags = html_escape(join(" ", grep { !m/^subj:/i } map {split /[\s,]+/} ref($exclude)?@{$exclude}:$exclude));
+# my $includesubj = html_escape(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } map {split /[\s,]+/} ref($include)?@{$include}:$include));
+# my $excludesubj = html_escape(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } map {split /[\s,]+/} ref($exclude)?@{$exclude}:$exclude));
+# my $vismindays = ($mindays == 0 ? "" : $mindays);
+# my $vismaxdays = ($maxdays == -1 ? "" : $maxdays);
+# 
+# my $sel_rmy = ($param{repeatmerged} ? " selected" : "");
+# my $sel_rmn = ($param{repeatmerged} ? "" : " selected");
+# my $sel_ordraw = ($ordering eq "raw" ? " selected" : "");
+# my $sel_ordold = ($ordering eq "oldview" ? " selected" : "");
+# my $sel_ordnor = ($ordering eq "normal" ? " selected" : "");
+# my $sel_ordage = ($ordering eq "age" ? " selected" : "");
+# 
+# my $chk_bugrev = ($bug_rev ? " checked" : "");
+# my $chk_pendrev = ($pend_rev ? " checked" : "");
+# my $chk_sevrev = ($sev_rev ? " checked" : "");
+# 
+# print <<EOF;
+# <tr><td>Only include bugs tagged with </td><td><input name=include value="$includetags"> or that have <input name=includesubj value="$includesubj"> in their subject</td></tr>
+# <tr><td>Exclude bugs tagged with </td><td><input name=exclude value="$excludetags"> or that have <input name=excludesubj value="$excludesubj"> in their subject</td></tr>
+# <tr><td>Only show bugs older than</td><td><input name=mindays value="$vismindays" size=5> days, and younger than <input name=maxdays value="$vismaxdays" size=5> days</td></tr>
+# 
+# <tr><td>&nbsp;</td></tr>
+# 
+# <tr><td>Merged bugs should be</td><td>
+# <select name=repeatmerged>
+# <option value=yes$sel_rmy>displayed separately</option>
+# <option value=no$sel_rmn>combined</option>
+# </select>
+# <tr><td>Categorise bugs by</td><td>
+# <select name=ordering>
+# <option value=raw$sel_ordraw>bug number only</option>
+# <option value=old$sel_ordold>status and severity</option>
+# <option value=normal$sel_ordnor>status, severity and classification</option>
+# <option value=age$sel_ordage>status, severity, classification, and age</option>
+# EOF
+# 
+# {
+# my $any = 0;
+# my $o = $param{"ordering"} || "";
+# for my $n (keys %cats) {
+#     next if ($n eq "normal" || $n eq "oldview");
+#     next if defined $hidden{$n};
+#     unless ($any) {
+#         $any = 1;
+#      print "<option disabled>------</option>\n";
+#     }
+#     my @names = map { ref($_) eq "HASH" ? $_->{"nam"} : $_ } @{$cats{$n}};
+#     my $name;
+#     if (@names == 1) { $name = $names[0]; }
+#     else { $name = " and " . pop(@names); $name = join(", ", @names) . $name; }
+# 
+#     printf "<option value=\"%s\"%s>%s</option>\n",
+#         $n, ($o eq $n ? " selected" : ""), $name;
+# }
+# }
+# 
+# print "</select></td></tr>\n";
+# 
+# printf "<tr><td>Order bugs by</td><td>%s</td></tr>\n",
+#     pkg_htmlselectyesno("pend-rev", "outstanding bugs first", "done bugs first", $pend_rev);
+# printf "<tr><td></td><td>%s</td></tr>\n",
+#     pkg_htmlselectyesno("sev-rev", "highest severity first", "lowest severity first", $sev_rev);
+# printf "<tr><td></td><td>%s</td></tr>\n",
+#     pkg_htmlselectyesno("bug-rev", "oldest bugs first", "newest bugs first", $bug_rev);
+# 
+# print <<EOF;
+# <tr><td>&nbsp;</td></tr>
+# <tr><td colspan=2><input value="Reload page" type="submit"> with new settings</td></tr>
+# EOF
+# 
+# print "</table></form></div>\n";
 
 print "<hr>\n";
 print "<p>$tail_html";
 
 print "</body></html>\n";
 
-sub pkg_htmlindexentrystatus {
-    my $s = shift;
-    my %status = %{$s};
-
-    my $result = "";
-
-    my $showseverity;
-    if  ($status{severity} eq 'normal') {
-        $showseverity = '';
-    } elsif (isstrongseverity($status{severity})) {
-        $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
-    } else {
-        $showseverity = "Severity: <em>$status{severity}</em>;\n";
-    }
-
-    $result .= pkg_htmlpackagelinks($status{"package"}, 1);
-
-    my $showversions = '';
-    if (@{$status{found_versions}}) {
-        my @found = @{$status{found_versions}};
-        $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @found;
-    }
-    if (@{$status{fixed_versions}}) {
-        $showversions .= '; ' if length $showversions;
-        $showversions .= '<strong>fixed</strong>: ';
-        my @fixed = @{$status{fixed_versions}};
-        $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @fixed;
-    }
-    $result .= ' (<a href="'.
-        version_url($status{package},
-                    $status{found_versions},
-                    $status{fixed_versions},
-                   ).qq{">$showversions</a>)} if length $showversions;
-    $result .= ";\n";
-
-    $result .= $showseverity;
-    $result .= pkg_htmladdresslinks("Reported by: ", \&submitterurl,
-                                $status{originator});
-    $result .= ";\nOwned by: " . html_escape($status{owner})
-               if length $status{owner};
-    $result .= ";\nTags: <strong>" 
-                 . html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
-                 . "</strong>"
-                       if (length($status{tags}));
-
-    $result .= buglinklist(";\nMerged with ", ", ",
-        split(/ /,$status{mergedwith}));
-    $result .= buglinklist(";\nBlocked by ", ", ",
-        split(/ /,$status{blockedby}));
-    $result .= buglinklist(";\nBlocks ", ", ",
-        split(/ /,$status{blocks}));
-
-    if (length($status{done})) {
-        $result .= "<br><strong>Done:</strong> " . html_escape($status{done});
-        my $days = bug_archiveable(bug => $status{id},
-                                  status => \%status,
-                                  days_until => 1,
-                                 );
-        if ($days >= 0 and defined $status{location} and $status{location} ne 'archive') {
-            $result .= ";\n<strong>Can be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
-        }
-       elsif (defined $status{location} and $status{location} eq 'archived') {
-            $result .= ";\n<strong>Archived.</strong>";
-       }
-    }
-
-    unless (length($status{done})) {
-        if (length($status{forwarded})) {
-            $result .= ";\n<strong>Forwarded</strong> to "
-                       . join(', ',
-                             map {maybelink($_)}
-                             split /\,\s+/,$status{forwarded}
-                            );
-        }
-       # Check the age of the logfile
-       my ($days_last,$eng_last) = secs_to_english(time - $status{log_modified});
-        my ($days,$eng) = secs_to_english(time - $status{date});
-       
-        if ($days >= 7) {
-            my $font = "";
-            my $efont = "";
-            $font = "em" if ($days > 30);
-            $font = "strong" if ($days > 60);
-            $efont = "</$font>" if ($font);
-            $font = "<$font>" if ($font);
-
-            $result .= ";\n ${font}$eng old$efont";
-        }
-       if ($days_last > 7) {
-           my $font = "";
-            my $efont = "";
-            $font = "em" if ($days_last > 30);
-            $font = "strong" if ($days_last > 60);
-            $efont = "</$font>" if ($font);
-            $font = "<$font>" if ($font);
-
-            $result .= ";\n ${font}Modified $eng_last ago$efont";
-       }
-    }
-
-    $result .= ".";
-
-    return $result;
-}
-
-
-sub pkg_htmlizebugs {
-    $b = $_[0];
-    my @bugs = @$b;
-
-    my @status = ();
-    my %count;
-    my $header = '';
-    my $footer = "<h2 class=\"outstanding\">Summary</h2>\n";
-
-    my @dummy = ($gRemoveAge); #, @gSeverityList, @gSeverityDisplay);  #, $gHTMLExpireNote);
-
-    if (@bugs == 0) {
-        return "<HR><H2>No reports found!</H2></HR>\n";
-    }
-
-    if ( $bug_rev ) {
-        @bugs = sort {$b<=>$a} @bugs;
-    } else {
-        @bugs = sort {$a<=>$b} @bugs;
-    }
-    my %seenmerged;
-
-    my %common = (
-        'show_list_header' => 1,
-        'show_list_footer' => 1,
-    );
-
-    my %section = ();
-    # Make the include/exclude map
-    my %include;
-    my %exclude;
-    for my $include (make_list($param{include})) {
-        next unless defined $include;
-        my ($key,$value) = split /\s*:\s*/,$include,2;
-        unless (defined $value) {
-            $key = 'tags';
-            $value = $include;
-        }
-        push @{$include{$key}}, split /\s*,\s*/, $value;
-    }
-    for my $exclude (make_list($param{exclude})) {
-        next unless defined $exclude;
-        my ($key,$value) = split /\s*:\s*/,$exclude,2;
-        unless (defined $value) {
-            $key = 'tags';
-            $value = $exclude;
-        }
-        push @{$exclude{$key}}, split /\s*,\s*/, $value;
-    }
-
-    foreach my $bug (@bugs) {
-        my %status = %{get_bug_status(bug=>$bug,
-                                     (exists $param{dist}?(dist => $param{dist}):()),
-                                     bugusertags => \%bugusertags,
-                                     (exists $param{version}?(version => $param{version}):()),
-                                     (exists $param{arch}?(arch => $param{arch}):(arch => $config{default_architectures})),
-                                    )};
-        next unless %status;
-        next if bug_filter(bug => $bug,
-                          status => \%status,
-                          (exists $param{repeatmerged}?(repeat_merged => $param{repeatmerged}):()),
-                          seen_merged => \%seenmerged,
-                          (keys %include ? (include => \%include):()),
-                          (keys %exclude ? (exclude => \%exclude):()),
-                         );
-
-       my $html = sprintf "<li><a href=\"%s\">#%d: %s</a>\n<br>",
-            bug_url($bug), $bug, html_escape($status{subject});
-        $html .= pkg_htmlindexentrystatus(\%status) . "\n";
-       push @status, [ $bug, \%status, $html ];
-    }
-    if ($bug_order eq 'age') {
-        # MWHAHAHAHA
-        @status = sort {$a->[1]{log_modified} <=> $b->[1]{log_modified}} @status;
-    }
-    elsif ($bug_order eq 'agerev') {
-        @status = sort {$b->[1]{log_modified} <=> $a->[1]{log_modified}} @status;
-    }
-    for my $entry (@status) {
-        my $key = "";
-       for my $i (0..$#prior) {
-           my $v = get_bug_order_index($prior[$i], $entry->[1]);
-            $count{"g_${i}_${v}"}++;
-           $key .= "_$v";
-       }
-        $section{$key} .= $entry->[2];
-        $count{"_$key"}++;
-    }
-
-    my $result = "";
-    if ($ordering eq "raw") {
-        $result .= "<UL class=\"bugs\">\n" . join("", map( { $_->[ 2 ] } @status ) ) . "</UL>\n";
-    } else {
-        $header .= "<div class=\"msgreceived\">\n<ul>\n";
-       my @keys_in_order = ("");
-       for my $o (@order) {
-           push @keys_in_order, "X";
-           while ((my $k = shift @keys_in_order) ne "X") {
-               for my $k2 (@{$o}) {
-                   $k2+=0;
-                   push @keys_in_order, "${k}_${k2}";
-               }
-           }
-       }
-        for my $order (@keys_in_order) {
-            next unless defined $section{$order};
-           my @ttl = split /_/, $order; shift @ttl;
-           my $title = $title[0]->[$ttl[0]] . " bugs";
-           if ($#ttl > 0) {
-               $title .= " -- ";
-               $title .= join("; ", grep {($_ || "") ne ""}
-                       map { $title[$_]->[$ttl[$_]] } 1..$#ttl);
-           }
-           $title = html_escape($title);
-
-            my $count = $count{"_$order"};
-            my $bugs = $count == 1 ? "bug" : "bugs";
-
-            $header .= "<li><a href=\"#$order\">$title</a> ($count $bugs)</li>\n";
-            if ($common{show_list_header}) {
-                my $count = $count{"_$order"};
-                my $bugs = $count == 1 ? "bug" : "bugs";
-                $result .= "<H2 CLASS=\"outstanding\"><a name=\"$order\"></a>$title ($count $bugs)</H2>\n";
-            } else {
-                $result .= "<H2 CLASS=\"outstanding\">$title</H2>\n";
-            }
-            $result .= "<div class=\"msgreceived\">\n<UL class=\"bugs\">\n";
-           $result .= "\n\n\n\n";
-            $result .= $section{$order};
-           $result .= "\n\n\n\n";
-            $result .= "</UL>\n</div>\n";
-        } 
-        $header .= "</ul></div>\n";
-
-        $footer .= "<div class=\"msgreceived\">\n<ul>\n";
-        for my $i (0..$#prior) {
-            my $local_result = '';
-            foreach my $key ( @{$order[$i]} ) {
-                my $count = $count{"g_${i}_$key"};
-                next if !$count or !$title[$i]->[$key];
-                $local_result .= "<li>$count $title[$i]->[$key]</li>\n";
-            }
-            if ( $local_result ) {
-                $footer .= "<li>$names[$i]<ul>\n$local_result</ul></li>\n";
-            }
-        }
-        $footer .= "</ul>\n</div>\n";
-    }
-
-    $result = $header . $result if ( $common{show_list_header} );
-    $result .= $footer if ( $common{show_list_footer} );
-    return $result;
-}
-
-sub pkg_htmlpackagelinks {
-    my $pkgs = shift;
-    return unless defined $pkgs and $pkgs ne '';
-    my $strong = shift;
-    my @pkglist = splitpackages($pkgs);
-
-    $strong = 0;
-    my $openstrong  = $strong ? '<strong>' : '';
-    my $closestrong = $strong ? '</strong>' : '';
-
-    return 'Package' . (@pkglist > 1 ? 's' : '') . ': ' .
-           join(', ',
-                map {
-                    '<a class="submitter" href="' . munge_url($this,src=>[],package=>$_,newest=>[]) . '">' .
-                    $openstrong . html_escape($_) . $closestrong . '</a>'
-                } @pkglist
-           );
-}
-
-sub pkg_htmladdresslinks {
-     htmlize_addresslinks(@_,'submitter');
-}
-
-sub pkg_javascript {
-    return <<EOF ;
-<script type="text/javascript">
-<!--
-function pagemain() {
-       toggle(1);
-//     toggle(2);
-       enable(1);
-}
-
-function setCookie(name, value, expires, path, domain, secure) {
-  var curCookie = name + "=" + escape(value) +
-      ((expires) ? "; expires=" + expires.toGMTString() : "") +
-      ((path) ? "; path=" + path : "") +
-      ((domain) ? "; domain=" + domain : "") +
-      ((secure) ? "; secure" : "");
-  document.cookie = curCookie;
-}
-
-function save_cat_cookies() {
-  var cat = document.categories.categorisation.value;
-  var exp = new Date();
-  exp.setTime(exp.getTime() + 10 * 365 * 24 * 60 * 60 * 1000);
-  var oldexp = new Date();
-  oldexp.setTime(oldexp.getTime() - 1 * 365 * 24 * 60 * 60 * 1000);
-  var lev;
-  var done = 0;
-
-  var u = document.getElementById("users");
-  if (u != null) { u = u.value; }
-  if (u == "") { u = null; }
-  if (u != null) {
-      setCookie("cat" + cat + "_users", u, exp, "/");
-  } else {
-      setCookie("cat" + cat + "_users", "", oldexp, "/");
-  }
-
-  var bits = new Array("nam", "pri", "ttl", "ord");
-  for (var i = 0; i < 4; i++) {
-      for (var j = 0; j < bits.length; j++) {
-          var e = document.getElementById(bits[j] + i);
-         if (e) e = e.value;
-         if (e == null) { e = ""; }
-         if (j == 0 && e == "") { done = 1; }
-         if (done || e == "") {
-              setCookie("cat" + cat + "_" + bits[j] + i, "", oldexp, "/");
-         } else {
-              setCookie("cat" + cat + "_" + bits[j] + i, e, exp, "/");
-         }
-      }
-  }
-}
-
-function toggle(i) {
-        var a = document.getElementById("a_" + i);
-        if (a) {
-             if (a.style.display == "none") {
-                     a.style.display = "";
-             } else {
-                     a.style.display = "none";
-             }
-        }
-}
-
-function enable(x) {
-    for (var i = 1; ; i++) {
-        var a = document.getElementById("b_" + x + "_" + i);
-        if (a == null) break;
-        var ischecked = a.checked;
-        for (var j = 1; ; j++) {
-            var b = document.getElementById("b_" + x + "_"+ i + "_" + j);
-            if (b == null) break;
-            if (ischecked) {
-                b.disabled = false;
-            } else {
-                b.disabled = true;
-            }
-        }
-    }
-}
--->
-</script>
-EOF
-}
-
-sub pkg_htmlselectyesno {
-    my ($name, $n, $y, $default) = @_;
-    return sprintf('<select name="%s"><option value=no%s>%s</option><option value=yes%s>%s</option></select>', $name, ($default ? "" : " selected"), $n, ($default ? " selected" : ""), $y);
-}
-
-sub pkg_htmlselectsuite {
-    my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
-    my @suites = ("stable", "testing", "unstable", "experimental");
-    my %suiteaka = ("stable", "etch", "testing", "lenny", "unstable", "sid");
-    my $defaultsuite = "unstable";
-
-    my $result = sprintf '<select name=dist id="%s">', $id;
-    for my $s (@suites) {
-        $result .= sprintf '<option value="%s"%s>%s%s</option>',
-                $s, ($defaultsuite eq $s ? " selected" : ""),
-                $s, (defined $suiteaka{$s} ? " (" . $suiteaka{$s} . ")" : "");
-    }
-    $result .= '</select>';
-    return $result;
-}
-
-sub pkg_htmlselectarch {
-    my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
-    my @arches = qw(alpha amd64 arm hppa i386 ia64 m68k mips mipsel powerpc s390 sparc);
-
-    my $result = sprintf '<select name=arch id="%s">', $id;
-    $result .= '<option value="any">any architecture</option>';
-    for my $a (@arches) {
-        $result .= sprintf '<option value="%s">%s</option>', $a, $a;
-    }
-    $result .= '</select>';
-    return $result;
-}
-
-sub myurl {
-     return html_escape(pkg_url(map {exists $param{$_}?($_,$param{$_}):()}
-                            qw(archive repeatmerged mindays maxdays),
-                            qw(version dist arch pkg src tag maint submitter)
-                           )
-                   );
-}
-
-sub make_order_list {
-    my $vfull = shift;
-    my @x = ();
-
-    if ($vfull =~ m/^([^:]+):(.*)$/) {
-        my $v = $1;
-        for my $vv (split /,/, $2) {
-           push @x, "$v=$vv";
-       }
-    } else {
-       for my $v (split /,/, $vfull) {
-            next unless $v =~ m/.=./;
-            push @x, $v;
-        }
-    }
-    push @x, "";  # catch all
-    return @x;
-}
-
-sub get_bug_order_index {
-    my $order = shift;
-    my $status = shift;
-    my $pos = -1;
-
-    my %tags = ();
-    %tags = map { $_, 1 } split / /, $status->{"tags"}
-        if defined $status->{"tags"};
-
-    for my $el (@${order}) {
-       $pos++;
-        my $match = 1;
-        for my $item (split /[+]/, $el) {
-           my ($f, $v) = split /=/, $item, 2;
-           next unless (defined $f and defined $v);
-           my $isokay = 0;
-           $isokay = 1 if (defined $status->{$f} and $v eq $status->{$f});
-           $isokay = 1 if ($f eq "tag" && defined $tags{$v});
-           unless ($isokay) {
-               $match = 0;
-               last;
-           }
-        }
-        if ($match) {
-            return $pos;
-            last;
-        }
-    }
-    return $pos + 1;
-}
-
-sub buglinklist {
-    my ($prefix, $infix, @els) = @_;
-    return '' if not @els;
-    return $prefix . bug_linklist($infix,'submitter',@els);
-}
-
-
-# sets: my @names; my @prior; my @title; my @order;
-
-sub determine_ordering {
-    $cats{status}[0]{ord} = [ reverse @{$cats{status}[0]{ord}} ]
-        if ($pend_rev);
-    $cats{severity}[0]{ord} = [ reverse @{$cats{severity}[0]{ord}} ]
-        if ($sev_rev);
-
-    my $i;
-    if (defined $param{"pri0"}) {
-        my @c = ();
-        $i = 0;
-        while (defined $param{"pri$i"}) {
-            my $h = {};
-
-            my ($pri) = make_list($param{"pri$i"});
-            if ($pri =~ m/^([^:]*):(.*)$/) {
-              $h->{"nam"} = $1;  # overridden later if necesary
-              $h->{"pri"} = [ map { "$1=$_" } (split /,/, $2) ];
-            } else {
-              $h->{"pri"} = [ split /,/, $pri ];
-            }
-
-           ($h->{"nam"}) = make_list($param{"nam$i"})
-                if (defined $param{"nam$i"});
-            $h->{"ord"} = [ map {split /\s*,\s*/} make_list($param{"ord$i"}) ]
-                if (defined $param{"ord$i"});
-           $h->{"ttl"} = [ map {split /\s*,\s*/} make_list($param{"ttl$i"}) ]
-                if (defined $param{"ttl$i"});
-
-            push @c, $h;
-           $i++;
-        }
-        $cats{"_"} = [@c];
-        $ordering = "_";
-    }
-
-    $ordering = "normal" unless defined $cats{$ordering};
-
-    sub get_ordering {
-        my @res;
-       my $cats = shift;
-        my $o = shift;
-        for my $c (@{$cats->{$o}}) {
-            if (ref($c) eq "HASH") {
-                push @res, $c;
-            } else {
-                push @res, get_ordering($cats, $c);
-            }
-        }
-        return @res;
-    }
-    my @cats = get_ordering(\%cats, $ordering);
-
-    sub toenglish {
-        my $expr = shift;
-        $expr =~ s/[+]/ and /g;
-        $expr =~ s/[a-z]+=//g;
-        return $expr;
-    }
-    $i = 0;
-    for my $c (@cats) {
-       $i++;
-        push @prior, $c->{"pri"};
-       push @names, ($c->{"nam"} || "Bug attribute #" . $i);
-        if (defined $c->{"ord"}) {
-            push @order, $c->{"ord"};
-        } else {
-            push @order, [ 0..$#{$prior[-1]} ];
-        }
-        my @t = @{ $c->{"ttl"} } if defined $c->{ttl};
-       if (@t < $#{$prior[-1]}) {
-            push @t, map { toenglish($prior[-1][$_]) } @t..($#{$prior[-1]});
-       }
-       push @t, $c->{"def"} || "";
-        push @title, [@t];
-    }
-}
index b5766d7a52108e25c606f5f1885109daed217a38..1ca87b97b3c2be50bc5e7a903f44e5ea50e4c5f0 100755 (executable)
@@ -16,5 +16,13 @@ my $soap = Debbugs::SOAP::Server
 # soapy is stupid, and is using the 1999 schema; override it.
 *SOAP::XMLSchema1999::Serializer::as_base64Binary = \&SOAP::XMLSchema2001::Serializer::as_base64Binary;
 *SOAP::Serializer::as_anyURI       = \&SOAP::XMLSchema2001::Serializer::as_string;
-$soap-> handle;
+# to work around the serializer improperly using date/time stuff
+# (Nothing in Debbugs should be looked at as if it were date/time) we
+# kill off all of the date/time related bits in the serializer.
+my $typelookup = $soap->serializer()->{_typelookup};
+for my $key (keys %{$typelookup}) {
+     next unless /Month|Day|Year|date|time|duration/i;
+     delete $typelookup->{$key};
+}
+$soap->handle;
 
index 7e19e9e118c79435de57b76e7a505f0d6f1eba42..d1e8960b700b5fc9bbe877dc490a9c3d9eea0e37 100755 (executable)
@@ -14,9 +14,15 @@ BEGIN{
 
 use CGI::Simple;
 
-use CGI::Alert 'don@donarmstrong.com';
+# by default send this message nowhere
+use CGI::Alert q(nobody@example.com);
 
 use Debbugs::Config qw(:config);
+
+BEGIN{
+     $CGI::Alert::Maintainer = $config{maintainer};
+}
+
 use Debbugs::CGI qw(htmlize_packagelinks html_escape cgi_parameters munge_url);
 use Debbugs::Versions;
 use Debbugs::Versions::Dpkg;
@@ -31,7 +37,7 @@ my %img_types = (svg => 'image/svg+xml',
                 png => 'image/png',
                );
 
-my $q = new CGI::Simple;
+my $q = CGI::Simple->new();
 
 my %cgi_var = cgi_parameters(query   => $q,
                             single  => [qw(package format ignore_boring width height collapse info)],
@@ -118,7 +124,9 @@ my %sources;
 my $version = Debbugs::Versions->new(\&Debbugs::Versions::Dpkg::vercmp);
 foreach my $source (keys %sources) {
      my $srchash = substr $source, 0, 1;
-     my $version_fh = new IO::File "$config{version_packages_dir}/$srchash/$source", 'r';
+     next unless -e "$config{version_packages_dir}/$srchash/$source";
+     my $version_fh = IO::File->new("$config{version_packages_dir}/$srchash/$source", 'r') or
+         warn "Unable to open $config{version_packages_dir}/$srchash/$source for reading: $!";
      $version->load($version_fh);
 }
 # Here, we need to generate a short version to full version map
@@ -186,7 +194,7 @@ if ($cgi_var{collapse}) {
      # are in the same state as their parent, and are not in a suite
      foreach my $key (keys %reversed_nodes) {
          my ($short_version) = $key =~ m{/(.+)$};
-         if (not exists $version_to_dist{$short_version}
+         if (not exists $version_to_dist{$key}
              and @{$reversed_nodes{$key}} <= 1
              and defined $version->{parent}{$key}
              and $all_states{$key} eq $all_states{$version->{parent}{$key}}
@@ -241,8 +249,8 @@ foreach my $key (keys %all_states) {
                                          or $all_states{$key} eq 'absent');
      next if $cgi_var{ignore_boring} and not version_relevant($version,$key,\@interesting_versions);
      my @attributes = @{$state{$all_states{$key}}};
-     if (length $short_version and exists $version_to_dist{$short_version}) {
-         push @attributes, 'label="'.$key.'\n'."(".join(', ',@{$version_to_dist{$short_version}}).")\"";
+     if (exists $version_to_dist{$key}) {
+         push @attributes, 'label="'.$key.'\n'."(".join(', ',@{$version_to_dist{$key}}).")\"";
      }
      my $node_attributes = qq("$key" [).join(',',@attributes).qq(]\n);
      $dot .= $node_attributes;
@@ -278,13 +286,13 @@ $dot .= "}\n";
 my $temp_dir = tempdir(CLEANUP => 1);
 
 if (not defined $cgi_var{dot}) {
-     my $dot_fh = new IO::File "$temp_dir/temp.dot",'w' or
+     my $dot_fh = IO::File->new("$temp_dir/temp.dot",'w') or
          die "Unable to open $temp_dir/temp.dot for writing: $!";
      print {$dot_fh} $dot or die "Unable to print output to the dot file: $!";
      close $dot_fh or die "Unable to close the dot file: $!";
      system('dot','-T'.$cgi_var{format},"$temp_dir/temp.dot",'-o',"$temp_dir/temp.$cgi_var{format}") == 0
          or print "Content-Type: text\n\nDot failed." and die "Dot failed: $?";
-     my $img_fh = new IO::File "$temp_dir/temp.$cgi_var{format}", 'r' or
+     my $img_fh = IO::File->new("$temp_dir/temp.$cgi_var{format}", 'r') or
          die "Unable to open $temp_dir/temp.$cgi_var{format} for reading: $!";
      print "Content-Type: $img_types{$cgi_var{format}}\n\n";
      print <$img_fh>;
index 339779a187a5c600272e4f42174722004aeb4173..11c7df8ab9b73dbadb2368032a96f736ada4eadd 100644 (file)
 debbugs (2.4.2) UNRELEASED; urgency=low
 
-  * Anthony Towns:
-    - Add "package" command to service (control@) to limit the bugs that
-      the following commands apply to.
-
-  * Colin Watson:
-    - Add (slightly) fancy CGI decoding of message/* MIME types.
-    - CGI scripts now support multiple maintainers for a single package.
-    - Add support for an X-Debbugs-No-Ack: mail header to suppress
-      acknowledgements.
-    - Document how to deliver mail to debbugs via procmail and SpamAssassin.
-    - Implement new .status format that's extensible and easier to read; it
-      now lives in .summary rather than .status. Use debbugs-upgradestatus
-      to convert existing bug databases.
-    - Implement bug ownership, with new 'owner' and 'noowner' commands and
-      Owner: pseudo-header (closes: #133453).
-    - Install Debian configuration in the binary package's examples
-      directory (closes: #222118).
-    - New standalone SpamAssassin queue runner, spamscan.
-    - Allow # prefix on bug numbers in 'merge' command.
-    - Fix some ordering issues in old-style package pages and summaries.
-    - Add X-$gProject-PR-Message: headers to all mails sent by service.
-    - debbugsconfig creates required directories in $gSpoolDir
-      (closes: #222077).
-    - Decode RFC1522 mail headers for display in the web interface.
-      bugreport.cgi and pkgreport.cgi now output UTF-8.
-    - Properly support multiple submitter addresses on a single bug.
-    - Add a number of extra htmlsanit() calls to prevent cross-site
-      scripting attacks.
-
-  * Adam Heath:
-    - Rewrite filtering in cgi's common.pl, to make it completely generic.
-      Filtering can now work against any field.
-    - Rewrite grouping logic in cgi's common.pl, to make it completely
-      generic.  There is now no longer 2 nested loops, to do the grouping.
-      This makes adding new grouping levels simpler for the future.
-    - Add in a Table of Contents to pkgreport.cgi.
-    - Display how long until a resolved bug will be archived in
-      pkgreport.cgi.
-    - Add user-agent detection.  This currently doesn't change anything,
-      however.
-    - Add options show_list_(head|foot)er.
-
-  * Don Armstrong:
-
-    - Don't remove the maintainer address if the message was sent by the
-      maintainer and we're submitting to maintonly (closes: #140061)
-    - Use uri_escape to escape URI's before sending them back out so the
-      name of the file doesn't munge the query string. [#301606 pt. 1]
-    - call decode_rfc1522 on the filename returned so that encoded
-      filenames get decoded to something "reasonable." (closes: #301606)
-    - We now require URI::Escape
-    - Added apache.conf to examples, which is a sample apache
-      configuration file which implements the rewrite rules that are
-      currently used on bugs.debian.org. (closes: #222264)
-    - Change spamscan.in to work with SA 3.0; This is an incompatible
-      change with SA 2.6, and as such, we now Suggests: spamassassin 
-      (>=3.0) (closes: #290501)
-    - Update MTA to exim4 (closes: #228597) and add instructions on using
-      exim 4 (thanks to Marc Haber) (closes: #248335)
-    - Added per bug subscription support to debbugs, which relies on an
-      external MLM to actually deal with the requests; currently works with
-      eoc and sends messages to bugnum\@$gListDomain. (closes: #34071)
-    - Change bugreport.cgi to use Debbugs::Log and greately simplify the
-      process of outputing the bug log.
-    - All RFC1522 subject lines are decoded, both in the html information
-      and the message headers. All messages are converted to UTF-8 whereever
-      possible; all bugreport.cgi pages are now completely in UTF-8 to the
-      degree possible using Encode.pm (closes: #46848,#238984)
-    - Add a convert_to_utf8 function to Debbugs::Mime to make the above
-      possible; abstracts functionality that was already present in the
-      decode_rfc1522 fucntionality.
-    - Individual messages can now be downloaded from each bug report
-      (closes: #95373)
-    - Uninteresting headers are now hidden by default, can be renabled
-      with &trim=no (closes: #188561)
-    - Fix postfix instructions in README.mail (thanks to Jeff Teunissen)
-      (closes: #134166)
-    - Display old severity when changing severity (closes: #196947)
-    - All messages that originate from the BTS and either go to .log files
-      or out to users are now properly RFC1522 encoded. (closes: #306068)
-    - Add links to cloned bugs (closes: #217960) and forwarded records
-      that look like urls in the html records output by bugreport.cgi.
-    - Things that look like urls in message bodies are now linked
-      (closes: #168962)
-    - Add Debbugs::Mail module that has two important functions:
-      send_mail_message and encode_headers. All mail handling in service.in
-      and process.in now uses send_mail_message to send mail messages which
-      tries as hard as possible to send a message; if it fails, only
-      warnings are returned. This fixes bad addresses causing sendmail to
-      exit and destroying the bug log. (closes: #191306)
-    - Add rudimentary Test::More modules for testing Debbugs::Mime and
-      Debbugs::Mail.
-    - Allow X-debbugs-* to be set in pseudo headers. (closes: #179340)
-    - Obey X-Debbugs-No-Ack in control@ messages. (closes: #201825)
-    - Allow forwarded: and owner to be set at submit@ time.
-      (closes:#128320)
-    - Fix example rewrite rules to allow for #1234 and foo+bar@baz.com
-      (closes: #321925)
-    - Output proper charset for attachments (closes: #335813)
-    - Use MIME encodings to attach messages in close and done.
-      (closes: #136654)
-    - Add a forcemerge command to service.in to allow forcibly merging
-      bugs which are in the same package. (closes: #286792)
-    - Make all packages lowercase to support packages with uppercase
-      names, even though that's insane. (closes: #67067)
-    - Change acknowledged to close (closes: #61341) and indicate who
-      actually closed the bug (closes: #355968, #132274)
-    - Fix the documentation of clone to indicate that you need at least
-      one newID (closes: #276747)
-    - Use create_mime_message to send all of the mails which may contain
-      UTF8 material. (closes: #364026)
-    - Add links to Closes: text for closed bugs. (closes: #320986)
-    - Add X-$gProject-PR-Source: line (closes: #219230)
-    - Use the %cats data structure properly in pkgreport.cgi
-      (closes: #367514)  
-    - Document nnn.*@foobar addresses (closes: #188670)
-    - Support cloned bugs in control blocking (closes: #337329)
-    - Indicate which bugs are blocked after blocking (closes: #367496)
-    - Obey package for usertags (closes: #376528)
-    - Add link to subscribe to a bug (closes: #353260)
-    - Don't lc owner or forwarded at submit time (closes: #288384)
-    - Explain how to close bugs in the ack message (closes: #37605)
-    - Make the moreinfo ack more general (closes: #70810)
-    - Use RFC compliant dates in headers (closes: #362935)
-    - Add SOAP support (closes: #377520) Thanks to Raphael Hertzog.
-    - Split forwarded on commas for linking (closes: #367813,#473272)
-    - Don't display duplicate bugs (closes: #348116)
-    - Display links to archived bugs for all searches (closes: #53710)
-    - Link to blocked bugs in the bugreport.cgi output (closes: #326077)
-    - Don't ask for more bugs if there is no maintainer (closes: #355190)
-    - Stop refering to developers on the index page (closes: #355786)
-    - Change control@ stop regex and documentation to match eachother
-      (closes: #366093)
-    - Make it obvious when commands to control have failed
-      (closes: #344184)
-    - Fix javascript error in pkgreport.cgi (closes: #346043)
-    - When a bug can't be found in control@; indicate to user that it may
-      be archived. (closes: #153536)
-    - my_url in pkgreport.cgi now returns the complete url (closes: #378566)
-    - Document precisely how forwarded works (closes: #228049)
-    - Dissallow forwarded being set to a $gEmailDomain address
-      (closes: #397486)
-    - Fix broken sorting by usertags by forcing numeric (closes: #395027)
-    - Add support for hiding useless messages; thanks to Sune Vuorela.
-      (closes: #406020)
-    - Fix arrayrefs leaking into the myurl function (closes: #397344)
-    - List bugs being blocked (closes: #356680)
-    - Fix multiple submitters for a single bug in the index
-      (closes: #402362)
-    - Marking a bug as fixed now overrides a found at that exact version
-      (closes: #395865)
-    - When searching by source package, include the source package itself
-      in the list of packages to search for, even if there is no binary
-      package called that. (closes: #414825)
-    - Add link from singlemsg page to main page; remove useless links
-      (closes: #404806)
-    - Support usertagging cloned bugs (closes: #375697)
-    - List previous/new title when retitling, and show date of control
-      actions (closes: #127354)
-    - Add searching by owner (closes: #345407)
-    - Accept colon after package in control mails (closes: #319720)
-    - Make e-mail addresses case insensitive for searching
-      (closes: #89569)
-    - pkgindex.cgi limits its output with pagination (closes: #23018)
-    - lc submitter address for comparsion in pkgreport.cgi
-      (closes: #415628)
-    - Add quotes around retitle'd titles (closes: #419202)
-    - Don't automatically make categories hidden (closes: #415932)
-    - Don't duplicate ordering (closes: #415931)
-    - Make file locking portable (closes: #293277)
-    - Allow the package pages to be optional (closes: #234362)
-    - Fix package link code and link both packages (closes: #419553)
-    - Save leading space when we unmime (closes: #416321)
-    - Make the version regex correct (closes: #425614)
-    - Indicate the selected user (closes: #422934)
-    - Use source package for usertags where possible (closes: #415933)
-    - Add PR-Package header for control messages (closes: #414023)
-    - Fix double leading spaces of format=flowed messages
-      (closes: #428056)
-    - Don't doubly select users
-    - Implement versioning aware archiving support (closes: #339141)
-    - Split out packages so that you don't have to install the mail stuff
-      unless you want it.
-    - Only mail duplicated recipients once (closes: #172635)
-    - Indicate date of last activity (closes: #207065)
-    - Reorder title (closes: #265267)
-    - Reopen bugs when a bug is found with a version greater than any
-      fixed version (closes: #365352)
-    - Allow ordering bugs by last action (closes: #318898)
-    - Add notfixed/notfound commands (closes: #389634)
-    - Fix soapy insanity (closes: #422062)
-    - Add script to split index.db by severities (closes: #422062)
-    - Add bugspam.cgi with confirm string (closes: #348225)
-    - Allow selecting both archived and unarchived bugs (closes: #320175)
-    - Support intersecting sets of bugs (closes: #164421)
-    - Allow selecting the newest N bugs (closes: #84681)
-    - Add anchor links to specific messages (closes: #431450)
-    - Add missing newline after indicating what the user is (closes: #432466)
-    - Handle src/binary packages with the same name (but different src
-      packages) correctly. (closes: #435926)
-    - Make sendmail binary location configurable, and use flock instead of
-      fcntl. (closes: #260791)
-    - Make notfound/notfixed log verbiage more clear (closes: #434953)
-    - Verify submitter is a valid email according to RFC822
-      (closes: #182419)
-    - Indicate what message number a message is (closes: #462653,#454248)
-    - Fix casing of versions (closes: #441022)
-    - Output last-modified in bugreport.cgi (closes: #459709)
-    - Fix various html syntax errors in pkgreport.cgi (closes: #462322)
-    - Make search case insensitive (closes: #448861)
-    - Add the ability to return source/package mapping
-      (closes: #465332,#458822)
-    - Deal properly with \r line endings (closes: #467190)
-    - Distinguish between reports and followups (closes: #459866)
+  [ Anthony Towns ]
+  * Add "package" command to service (control@) to limit the bugs that
+    the following commands apply to.
+
+  [ Colin Watson ]
+  * Add (slightly) fancy CGI decoding of message/* MIME types.
+  * CGI scripts now support multiple maintainers for a single package.
+  * Add support for an X-Debbugs-No-Ack: mail header to suppress
+    acknowledgements.
+  * Document how to deliver mail to debbugs via procmail and SpamAssassin.
+  * Implement new .status format that's extensible and easier to read; it
+    now lives in .summary rather than .status. Use debbugs-upgradestatus
+    to convert existing bug databases.
+  * Implement bug ownership, with new 'owner' and 'noowner' commands and
+    Owner: pseudo-header (closes: #133453).
+  * Install Debian configuration in the binary package's examples
+    directory (closes: #222118).
+  * New standalone SpamAssassin queue runner, spamscan.
+  * Allow # prefix on bug numbers in 'merge' command.
+  * Fix some ordering issues in old-style package pages and summaries.
+  * Add X-$gProject-PR-Message: headers to all mails sent by service.
+  * debbugsconfig creates required directories in $gSpoolDir
+    (closes: #222077).
+  * Decode RFC1522 mail headers for display in the web interface.
+    bugreport.cgi and pkgreport.cgi now output UTF-8.
+  * Properly support multiple submitter addresses on a single bug.
+  * Add a number of extra htmlsanit() calls to prevent cross-site
+    scripting attacks.
+
+  [ Adam Heath ]
+  * Rewrite filtering in cgi's common.pl, to make it completely generic.
+    Filtering can now work against any field.
+  * Rewrite grouping logic in cgi's common.pl, to make it completely
+    generic.  There is now no longer 2 nested loops, to do the grouping.
+    This makes adding new grouping levels simpler for the future.
+  * Add in a Table of Contents to pkgreport.cgi.
+  * Display how long until a resolved bug will be archived in
+    pkgreport.cgi.
+  * Add user-agent detection.  This currently doesn't change anything,
+    however.
+  * Add options show_list_(head|foot)er.
+
+  [ Don Armstrong ]
+  * Don't remove the maintainer address if the message was sent by the
+    maintainer and we're submitting to maintonly (closes: #140061)
+  * Use uri_escape to escape URI's before sending them back out so the
+    name of the file doesn't munge the query string. [#301606 pt. 1]
+  * call decode_rfc1522 on the filename returned so that encoded
+    filenames get decoded to something "reasonable." (closes: #301606)
+  * We now require URI::Escape
+  * Added apache.conf to examples, which is a sample apache
+    configuration file which implements the rewrite rules that are
+    currently used on bugs.debian.org. (closes: #222264)
+  * Change spamscan.in to work with SA 3.0; This is an incompatible
+    change with SA 2.6, and as such, we now Suggests: spamassassin 
+    (>=3.0) (closes: #290501)
+  * Update MTA to exim4 (closes: #228597) and add instructions on using
+    exim 4 (thanks to Marc Haber) (closes: #248335)
+  * Added per bug subscription support to debbugs, which relies on an
+    external MLM to actually deal with the requests; currently works with
+    eoc and sends messages to bugnum\@$gListDomain. (closes: #34071)
+  * Change bugreport.cgi to use Debbugs::Log and greately simplify the
+    process of outputing the bug log.
+  * All RFC1522 subject lines are decoded, both in the html information
+    and the message headers. All messages are converted to UTF-8 whereever
+    possible; all bugreport.cgi pages are now completely in UTF-8 to the
+    degree possible using Encode.pm (closes: #46848,#238984)
+  * Add a convert_to_utf8 function to Debbugs::Mime to make the above
+    possible; abstracts functionality that was already present in the
+    decode_rfc1522 fucntionality.
+  * Individual messages can now be downloaded from each bug report
+    (closes: #95373)
+  * Uninteresting headers are now hidden by default, can be renabled
+    with &trim=no (closes: #188561)
+  * Fix postfix instructions in README.mail (thanks to Jeff Teunissen)
+    (closes: #134166)
+  * Display old severity when changing severity (closes: #196947)
+  * All messages that originate from the BTS and either go to .log files
+    or out to users are now properly RFC1522 encoded. (closes: #306068)
+  * Add links to cloned bugs (closes: #217960) and forwarded records
+    that look like urls in the html records output by bugreport.cgi.
+  * Things that look like urls in message bodies are now linked
+    (closes: #168962)
+  * Add Debbugs::Mail module that has two important functions:
+    send_mail_message and encode_headers. All mail handling in service.in
+    and process.in now uses send_mail_message to send mail messages which
+    tries as hard as possible to send a message; if it fails, only
+    warnings are returned. This fixes bad addresses causing sendmail to
+    exit and destroying the bug log. (closes: #191306)
+  * Add rudimentary Test::More modules for testing Debbugs::Mime and
+    Debbugs::Mail.
+  * Allow X-debbugs-* to be set in pseudo headers. (closes: #179340)
+  * Obey X-Debbugs-No-Ack in control@ messages. (closes: #201825)
+  * Allow forwarded: and owner to be set at submit@ time.
+    (closes:#128320)
+  * Fix example rewrite rules to allow for #1234 and foo+bar@baz.com
+    (closes: #321925)
+  * Output proper charset for attachments (closes: #335813)
+  * Use MIME encodings to attach messages in close and done.
+    (closes: #136654)
+  * Add a forcemerge command to service.in to allow forcibly merging
+    bugs which are in the same package. (closes: #286792)
+  * Make all packages lowercase to support packages with uppercase
+    names, even though that's insane. (closes: #67067)
+  * Change acknowledged to close (closes: #61341) and indicate who
+    actually closed the bug (closes: #355968, #132274)
+  * Fix the documentation of clone to indicate that you need at least
+    one newID (closes: #276747)
+  * Use create_mime_message to send all of the mails which may contain
+    UTF8 material. (closes: #364026)
+  * Add links to Closes: text for closed bugs. (closes: #320986)
+  * Add X-$gProject-PR-Source: line (closes: #219230)
+  * Use the %cats data structure properly in pkgreport.cgi
+    (closes: #367514)  
+  * Document nnn.*@foobar addresses (closes: #188670)
+  * Support cloned bugs in control blocking (closes: #337329)
+  * Indicate which bugs are blocked after blocking (closes: #367496)
+  * Obey package for usertags (closes: #376528)
+  * Add link to subscribe to a bug (closes: #353260)
+  * Don't lc owner or forwarded at submit time (closes: #288384)
+  * Explain how to close bugs in the ack message (closes: #37605)
+  * Make the moreinfo ack more general (closes: #70810)
+  * Use RFC compliant dates in headers (closes: #362935)
+  * Add SOAP support (closes: #377520) Thanks to Raphael Hertzog.
+  * Split forwarded on commas for linking (closes: #367813,#473272)
+  * Don't display duplicate bugs (closes: #348116)
+  * Display links to archived bugs for all searches (closes: #53710)
+  * Link to blocked bugs in the bugreport.cgi output (closes: #326077)
+  * Don't ask for more bugs if there is no maintainer (closes: #355190)
+  * Stop refering to developers on the index page (closes: #355786)
+  * Change control@ stop regex and documentation to match eachother
+    (closes: #366093)
+  * Make it obvious when commands to control have failed
+    (closes: #344184)
+  * Fix javascript error in pkgreport.cgi (closes: #346043)
+  * When a bug can't be found in control@; indicate to user that it may
+    be archived. (closes: #153536)
+  * my_url in pkgreport.cgi now returns the complete url (closes: #378566)
+  * Document precisely how forwarded works (closes: #228049)
+  * Dissallow forwarded being set to a $gEmailDomain address
+    (closes: #397486)
+  * Fix broken sorting by usertags by forcing numeric (closes: #395027)
+  * Add support for hiding useless messages; thanks to Sune Vuorela.
+    (closes: #406020)
+  * Fix arrayrefs leaking into the myurl function (closes: #397344)
+  * List bugs being blocked (closes: #356680)
+  * Fix multiple submitters for a single bug in the index
+    (closes: #402362)
+  * Marking a bug as fixed now overrides a found at that exact version
+    (closes: #395865)
+  * When searching by source package, include the source package itself
+    in the list of packages to search for, even if there is no binary
+    package called that. (closes: #414825)
+  * Add link from singlemsg page to main page; remove useless links
+    (closes: #404806)
+  * Support usertagging cloned bugs (closes: #375697)
+  * List previous/new title when retitling, and show date of control
+    actions (closes: #127354)
+  * Add searching by owner (closes: #345407)
+  * Accept colon after package in control mails (closes: #319720)
+  * Make e-mail addresses case insensitive for searching
+    (closes: #89569)
+  * pkgindex.cgi limits its output with pagination (closes: #23018)
+  * lc submitter address for comparsion in pkgreport.cgi
+    (closes: #415628)
+  * Add quotes around retitle'd titles (closes: #419202)
+  * Don't automatically make categories hidden (closes: #415932)
+  * Don't duplicate ordering (closes: #415931)
+  * Make file locking portable (closes: #293277)
+  * Allow the package pages to be optional (closes: #234362)
+  * Fix package link code and link both packages (closes: #419553)
+  * Save leading space when we unmime (closes: #416321)
+  * Make the version regex correct (closes: #425614)
+  * Indicate the selected user (closes: #422934)
+  * Use source package for usertags where possible (closes: #415933)
+  * Add PR-Package header for control messages (closes: #414023)
+  * Fix double leading spaces of format=flowed messages
+    (closes: #428056)
+  * Don't doubly select users
+  * Implement versioning aware archiving support (closes: #339141)
+  * Split out packages so that you don't have to install the mail stuff
+    unless you want it.
+  * Only mail duplicated recipients once (closes: #172635)
+  * Indicate date of last activity (closes: #207065)
+  * Reorder title (closes: #265267)
+  * Reopen bugs when a bug is found with a version greater than any
+    fixed version (closes: #365352)
+  * Allow ordering bugs by last action (closes: #318898)
+  * Add notfixed/notfound commands (closes: #389634)
+  * Fix soapy insanity (closes: #422062)
+  * Add script to split index.db by severities (closes: #422062)
+  * Add bugspam.cgi with confirm string (closes: #348225)
+  * Allow selecting both archived and unarchived bugs (closes: #320175)
+  * Support intersecting sets of bugs (closes: #164421)
+  * Allow selecting the newest N bugs (closes: #84681)
+  * Add anchor links to specific messages (closes: #431450)
+  * Add missing newline after indicating what the user is (closes: #432466)
+  * Handle src/binary packages with the same name (but different src
+    packages) correctly. (closes: #435926)
+  * Make sendmail binary location configurable, and use flock instead of
+    fcntl. (closes: #260791)
+  * Make notfound/notfixed log verbiage more clear (closes: #434953)
+  * Verify submitter is a valid email according to RFC822
+    (closes: #182419)
+  * Indicate what message number a message is (closes: #462653,#454248)
+  * Fix casing of versions (closes: #441022)
+  * Output last-modified in bugreport.cgi (closes: #459709)
+  * Fix various html syntax errors in pkgreport.cgi (closes: #462322)
+  * Make search case insensitive (closes: #448861)
+  * Add the ability to return source/package mapping
+    (closes: #465332,#458822)
+  * Deal properly with \r line endings (closes: #467190)
+  * Distinguish between reports and followups (closes: #459866)
+  * Allow for the archiving of bugs in removed packages (closes: #475622)
+  * Add Text::Template based templating system (closes: #36814)
+  * Add new uservalue feature to Debbugs::User
+  * Don't serialize things as date/time in soap (closes: #484789)
 
   
  -- Colin Watson <cjwatson@debian.org>  Fri, 20 Jun 2003 18:57:25 +0100
index ed42581fc96de888417f64974b4ccd7a3bd75cee..43f58cbceca29eca2d8b002e75c4701d8df34dcd 100644 (file)
@@ -2,14 +2,14 @@ html {
     color: #000; 
     background: #fefefe;
     font-family: serif;
-    margin: 1em;
+    margin: 0;
     border: 0;
     padding: 0;
     line-height: 120%;
 }
 
 body {
-    margin: 0;
+    margin: 10px;
     border: 0;
     padding: 0;
 }
@@ -120,6 +120,42 @@ pre.mime {
     color: #686868;
 }
 
+.buginfo p
+{ 
+  font-family: sans-serif;
+  font-size: 110%;
+  margin-bottom: 0px
+}
+
+.buginfo p + p
+{ 
+  margin: 0;
+  margin-top: 0px;
+  border: 0;
+}
+
+
+.pkginfo p
+{ font-family: sans-serif;
+  font-size: 110%;
+  margin-bottom: 0px
+}
+
+.pkginfo p + p
+{ 
+  margin: 0;
+  margin-top: 0px;
+  padding: 0;
+  border: 0;
+}
+
+
+.versiongraph
+{
+  float: right
+  
+}
+
 pre.tags {
     color: #a3a3a3;
     font-size: 90%;
@@ -162,6 +198,36 @@ li {
     list-style-type: square;
 }
 
+.shortbugstatus
+{ 
+  font-family: sans-serif;
+  
+  }
+
+.shortbugstatusextra
+{ font-family: sans-serif;
+  margin: 5px;
+  margin-top: 2px;
+  padding: 5px;
+  /* display: none; */
+  /* z-index: 1; */
+  /* position: absolute; */
+  left: 120px;
+  background-color: #ffffff;
+/*  border: #000 1px solid; */
+  position: static;
+  display: block;
+  border: 0;
+  }
+
+.shortbugstatusextra span
+{ margin: 0;
+  margin-top: 0px;
+  padding: 0;
+  border: 0;
+  display: block;
+  }
+
 .bugs li {
     margin-top: 5px;
 }
diff --git a/scripts/age-1 b/scripts/age-1
new file mode 100755 (executable)
index 0000000..cc2e72d
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+# $Id: age-1.in,v 1.3 2002/01/06 10:46:24 ajt Exp $
+set -e
+cd /var/lib/debbugs/spool/db-h
+test -f ./-3.log && rm ./-3.log
+test -f ./-2.log && mv ./-2.log ./-3.log
+test -f ./-1.log && mv ./-1.log ./-2.log
+#rm -f ../stamp.html
diff --git a/scripts/age-1.in b/scripts/age-1.in
deleted file mode 100755 (executable)
index cc2e72d..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-# $Id: age-1.in,v 1.3 2002/01/06 10:46:24 ajt Exp $
-set -e
-cd /var/lib/debbugs/spool/db-h
-test -f ./-3.log && rm ./-3.log
-test -f ./-2.log && mv ./-2.log ./-3.log
-test -f ./-1.log && mv ./-1.log ./-2.log
-#rm -f ../stamp.html
diff --git a/scripts/config b/scripts/config
new file mode 100644 (file)
index 0000000..4767f6a
--- /dev/null
@@ -0,0 +1,83 @@
+# -*- mode: cperl -*-
+# This is the template debbugs configuration file.
+# You *must* edit it in order for debbugs to work.
+# $Id: config.in,v 1.23 2005/07/17 19:07:01 cjwatson Exp $
+
+# Domains
+$gEmailDomain = "bugs.something";                      # e.g. bugs.debian.org
+$gListDomain = "lists.something";                      # e.g. lists.debian.org
+$gWebHost = "localhost";                               # e.g. www.debian.org
+$gWebHostBugDir = "Bugs";                              # e.g. Bugs
+# For now, don't change this one manually!
+$gWebDomain = "$gWebHost/$gWebHostBugDir";
+$gHTMLSuffix = ".html";
+$gCGIDomain = "$gWebDomain/cgi";                       # e.g. cgi.debian.org
+$gMirrors = "";                                                # comma separated list
+$gPackagePages = "packages.debian.org";                 # e.g. packages.debian.org
+$gSubscriptionDomain = "packages.something";           # e.g. packages.qa.debian.org
+
+# Project identification
+$gProject = "Something";                               # e.g. Debian
+$gProjectTitle = "Something DebBugs Test";             # e.g. Debian GNU/Linux
+# Person(s) responsible for this installation
+$gMaintainer = "Local DebBugs Owner";                  # e.g. Ian Jackson
+$gMaintainerWebpage = "http://localhost/~owner";       # e.g. http://www.debian.org/~iwj
+$gMaintainerEmail = "root\@something";                 # e.g. owner@bugs.debian.org
+$gUnknownMaintainerEmail = "$gMaintainerEmail";                # e.g. unknown-package@qa.debian.org
+
+# BTS mailing lists, at $gListDomain
+# if you don't want lists, set them all to $gMaintainerEmail
+# if you don't want that mail at all, filter it out somehow :)
+$gSubmitList = "bug-submit-list";              # e.g. debian-bugs-dist
+$gMaintList = "bug-maint-list";                        # e.g. debian-bugs-dist
+$gQuietList = "bug-quiet-list";                        # e.g. debian-bugs-dist
+$gForwardList = "bug-forward-list";            # e.g. debian-bugs-forwarded
+$gDoneList = "bug-done-list";                  # e.g. debian-bugs-closed
+$gRequestList = "bug-request-list";            # e.g. debian-bugs-dist
+$gSubmitterList = "bug-submitter-list";                # e.g. debian-bugs-dist
+$gControlList = "bug-control-list";            # e.g. debian-bugs-dist
+$gSummaryList = "bug-summary-list";            # e.g. debian-bugs-reports
+$gMirrorList = "bug-mirrors-list";             # sends to all mirrors
+
+# Various configurable options
+$gMailer = "exim";                             # valid: exim, qmail and sendmail
+$gBug = "bug";                                 # how to spell `bug'
+$gBugs = "bugs";                               # how to spell `bugs'
+$gRemoveAge = 28;                              # days after closed bugs are cleaned out,
+                                               # 0 disables
+$gSaveOldBugs = 1;                             # whether to archive such bugs
+$gDefaultSeverity = "normal";
+$gShowSeverities = "critical, grave, normal, minor, wishlist";
+@gStrongSeverities = ( 'critical', 'grave' );
+@gSeverityList = ( 'critical', 'grave', 'normal', 'wishlist' );
+%gSeverityDisplay = ( 'critical', "Critical $gBugs",
+                      'grave', "Grave $gBugs",
+                      'normal', "Normal $gBugs",
+                      'wishlist', "Wishlist items" );
+@gTags = ( 'patch', 'wontfix', 'moreinfo', 'unreproducible', 'fixed', 'stable' );
+
+# better don't change this
+$gBounceFroms = "^mailer|^da?emon|^post.*mast|^root|^wpuser|^mmdf|^smt.*|^mrgate|^vmmail|^mail.*system|^uucp|-maiser-|^mal\@|^mail.*agent|^tcpmail|^bitmail|^mailman";
+
+# Directories -- do _not_ change their locations.
+# They are currently hardcoded, variables are here for future expansion.
+$gConfigDir = "/etc/debbugs";                          # directory where this file is
+$gSpoolDir = "/var/lib/debbugs/spool";                 # working directory
+$gIncomingDir = "incoming";                            # unprocessed e-mails
+$gWebDir = "/var/lib/debbugs/www";                     # base location of web pages
+$gDocDir = "/var/lib/debbugs/www/txt";                 # location of text doc files
+
+# Required data files
+$gMaintainerFile = "$gConfigDir/Maintainers";
+$gMaintainerFileOverride = "$gConfigDir/Maintainers.override";
+$gPseudoDescFile = "$gConfigDir/pseudo-packages.description";
+$gPackageSource = "$gConfigDir/indices/sources";
+
+
+# Estraier Configuration
+%gSearchEstraier = (url  => 'http://localhost:1978/node/bts1',
+                   user => 'user',
+                   pass => 'pass',
+                  );
+
+1;
diff --git a/scripts/config.in b/scripts/config.in
deleted file mode 100644 (file)
index 4767f6a..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- mode: cperl -*-
-# This is the template debbugs configuration file.
-# You *must* edit it in order for debbugs to work.
-# $Id: config.in,v 1.23 2005/07/17 19:07:01 cjwatson Exp $
-
-# Domains
-$gEmailDomain = "bugs.something";                      # e.g. bugs.debian.org
-$gListDomain = "lists.something";                      # e.g. lists.debian.org
-$gWebHost = "localhost";                               # e.g. www.debian.org
-$gWebHostBugDir = "Bugs";                              # e.g. Bugs
-# For now, don't change this one manually!
-$gWebDomain = "$gWebHost/$gWebHostBugDir";
-$gHTMLSuffix = ".html";
-$gCGIDomain = "$gWebDomain/cgi";                       # e.g. cgi.debian.org
-$gMirrors = "";                                                # comma separated list
-$gPackagePages = "packages.debian.org";                 # e.g. packages.debian.org
-$gSubscriptionDomain = "packages.something";           # e.g. packages.qa.debian.org
-
-# Project identification
-$gProject = "Something";                               # e.g. Debian
-$gProjectTitle = "Something DebBugs Test";             # e.g. Debian GNU/Linux
-# Person(s) responsible for this installation
-$gMaintainer = "Local DebBugs Owner";                  # e.g. Ian Jackson
-$gMaintainerWebpage = "http://localhost/~owner";       # e.g. http://www.debian.org/~iwj
-$gMaintainerEmail = "root\@something";                 # e.g. owner@bugs.debian.org
-$gUnknownMaintainerEmail = "$gMaintainerEmail";                # e.g. unknown-package@qa.debian.org
-
-# BTS mailing lists, at $gListDomain
-# if you don't want lists, set them all to $gMaintainerEmail
-# if you don't want that mail at all, filter it out somehow :)
-$gSubmitList = "bug-submit-list";              # e.g. debian-bugs-dist
-$gMaintList = "bug-maint-list";                        # e.g. debian-bugs-dist
-$gQuietList = "bug-quiet-list";                        # e.g. debian-bugs-dist
-$gForwardList = "bug-forward-list";            # e.g. debian-bugs-forwarded
-$gDoneList = "bug-done-list";                  # e.g. debian-bugs-closed
-$gRequestList = "bug-request-list";            # e.g. debian-bugs-dist
-$gSubmitterList = "bug-submitter-list";                # e.g. debian-bugs-dist
-$gControlList = "bug-control-list";            # e.g. debian-bugs-dist
-$gSummaryList = "bug-summary-list";            # e.g. debian-bugs-reports
-$gMirrorList = "bug-mirrors-list";             # sends to all mirrors
-
-# Various configurable options
-$gMailer = "exim";                             # valid: exim, qmail and sendmail
-$gBug = "bug";                                 # how to spell `bug'
-$gBugs = "bugs";                               # how to spell `bugs'
-$gRemoveAge = 28;                              # days after closed bugs are cleaned out,
-                                               # 0 disables
-$gSaveOldBugs = 1;                             # whether to archive such bugs
-$gDefaultSeverity = "normal";
-$gShowSeverities = "critical, grave, normal, minor, wishlist";
-@gStrongSeverities = ( 'critical', 'grave' );
-@gSeverityList = ( 'critical', 'grave', 'normal', 'wishlist' );
-%gSeverityDisplay = ( 'critical', "Critical $gBugs",
-                      'grave', "Grave $gBugs",
-                      'normal', "Normal $gBugs",
-                      'wishlist', "Wishlist items" );
-@gTags = ( 'patch', 'wontfix', 'moreinfo', 'unreproducible', 'fixed', 'stable' );
-
-# better don't change this
-$gBounceFroms = "^mailer|^da?emon|^post.*mast|^root|^wpuser|^mmdf|^smt.*|^mrgate|^vmmail|^mail.*system|^uucp|-maiser-|^mal\@|^mail.*agent|^tcpmail|^bitmail|^mailman";
-
-# Directories -- do _not_ change their locations.
-# They are currently hardcoded, variables are here for future expansion.
-$gConfigDir = "/etc/debbugs";                          # directory where this file is
-$gSpoolDir = "/var/lib/debbugs/spool";                 # working directory
-$gIncomingDir = "incoming";                            # unprocessed e-mails
-$gWebDir = "/var/lib/debbugs/www";                     # base location of web pages
-$gDocDir = "/var/lib/debbugs/www/txt";                 # location of text doc files
-
-# Required data files
-$gMaintainerFile = "$gConfigDir/Maintainers";
-$gMaintainerFileOverride = "$gConfigDir/Maintainers.override";
-$gPseudoDescFile = "$gConfigDir/pseudo-packages.description";
-$gPackageSource = "$gConfigDir/indices/sources";
-
-
-# Estraier Configuration
-%gSearchEstraier = (url  => 'http://localhost:1978/node/bts1',
-                   user => 'user',
-                   pass => 'pass',
-                  );
-
-1;
diff --git a/scripts/db2html b/scripts/db2html
new file mode 100755 (executable)
index 0000000..f39ea98
--- /dev/null
@@ -0,0 +1,653 @@
+#!/usr/bin/perl
+# $Id: db2html.in,v 1.22 2004/04/19 10:03:53 cjwatson Exp $
+# usage: db2html [-diff] [-stampfile=<stampfile>] [-lastrun=<days>] <wwwbase>
+
+#load the necessary libraries/configuration
+$config_path = '/etc/debbugs';
+$lib_path = '/usr/lib/debbugs';
+
+require("$config_path/config");
+require("$config_path/text");
+require("$lib_path/errorlib");
+$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
+
+use POSIX qw(strftime tzset);
+$ENV{"TZ"} = 'UTC';
+tzset();
+
+#set current working directory
+chdir("$gSpoolDir") || die "chdir spool: $!\n";
+
+#setup variables
+$diff = 0;
+$stampfile = 'stamp.html';
+$tail_html = $gHTMLTail; 
+$expirynote_html = '';
+$expirynote_html = $gHTMLExpireNote if $gRemoveAge;
+$shorthead = ' Ref   * Package    Keywords/Subject                    Submitter';
+$shortindex = ''; 
+$amonths = -1;
+$indexunmatched = '';
+%displayshowpendings = ('pending','outstanding',
+                       'done','resolved',
+                       'forwarded','forwarded to upstream software authors');
+
+#set timestamp for html files
+$dtime = strftime "%a, %e %b %Y %T UTC", localtime;
+$tail_html =~ s/SUBSTITUTE_DTIME/$dtime/;
+
+#check for commandline switches
+while (@ARGV && $ARGV[0] =~ m/^-/) 
+{      if ($ARGV[0] eq '-diff') { $diff=1; }
+    elsif ($ARGV[0] =~ m/^-lastrun\=([0-9.]+)$/) { $lastrun= $1; undef $stampfile; }
+    elsif ($ARGV[0] =~ m/^-full$/) { undef $lastrun; undef $stampfile; }
+    elsif ($ARGV[0] =~ m/^-stampfile\=(\S+)$/) { $stampfile= $1; }
+    else { die "bad usage"; }
+    shift;
+}
+
+#check for remaing argument, only one...
+@ARGV==1 or die;
+$wwwbase= shift(@ARGV);
+
+#get starting time
+defined($startdate= time) || die "failed to get time: $!";
+
+$|=1;
+
+#if stamp file was given, 
+if (defined($stampfile)) 
+{      if (open(X,"< $stampfile")) 
+       {       $lastrun= -M X;
+        close(X);
+        printf "progress last run %.7f days\n",$lastrun;
+    } else { print "progress stamp file $stampfile: $! - full\n"; }
+}
+
+#only process file if greater than last run...
+if (defined($lastrun) && -M "db-h" > $lastrun) 
+{      $_= $gHTMLStamp;
+    s/SUBSTITUTE_DTIME/$dtime/o;
+    s/\<\!\-\-updateupdate\-\-\>.*\<\!\-\-\/updateupdate\-\-\>/check/;
+    &file('ix/zstamp.html','non',$_."</body></html>\n");
+       print "noremoves";
+#    print "db2html: no changes since last run\n";
+    exit 0;
+}
+
+#parse maintainer file
+open(MM,"$gMaintainerFile") || die "open $gMaintainerFile: $!";
+while(<MM>) 
+{      m/^(\S+)\s+(\S.*\S)\s*$/ || die "$gMaintainerFile: \`$_'";
+    ($a,$b)=($1,$2);
+    $a =~ y/A-Z/a-z/;
+    $maintainer{$a}= $b;
+}
+close(MM);
+
+#load all database files
+opendir(D,'db-h') || die "opendir db-h: $!";
+@dirs = grep(s#^#db-h/#,grep(/^\d+$/,readdir(D)));
+closedir(D);
+foreach my $dir (@dirs) {
+    opendir(D,$dir);
+    push @files, grep(/^-?\d+\.log$/,readdir(D));
+    closedir(D);
+}
+@files = sort { $a <=> $b } @files;
+
+for $pending (qw(pending done forwarded)) 
+{      for $severity (@showseverities) 
+       {       eval "\$index${pending}${severity}= \$iiindex${pending}${severity}= ''; 1;"
+            or die "reset \$index${pending}${severity}: $@";
+    }
+}
+
+for $f (@files) 
+{      next unless $f =~ m/^(-?\d+)\.log$/;
+    $ref= $1;
+       #((print STDERR "$ref\n"),
+       #next
+       #)
+       # unless $ref =~ m/^-/ || $ref =~ m/^124/;
+    &filelock("lock/$ref");
+    $preserveonly= defined($lastrun) && -M "db-h/".get_hashname($ref)."/$ref.log" > $lastrun;
+    if ($ref =~ m/^-\d$/) 
+       {       $week= $ref eq '-1' ? 'this week' :
+               $ref eq '-2' ? 'last week' :
+               $ref eq '-3' ? 'two weeks ago' :
+                              ($ref-1)." weeks ago";
+        $linkto= "ju/unmatched$ref";
+        $short= "junk, $week";
+        $descriptivehead=
+            "This includes messages sent to <code>done\@$gEmailDomain</code>\n".
+            "which did not have a $gBug reference number in the Subject line\n".
+            "or which contained an\n".
+            "unknown or out of date $gBug report number (these cause a warning\n".
+            "to be sent to the sender) and details about the messages\n".
+            "sent to <code>request@$gEmailDomain</code> (all of which".
+            "produce replies).\n";
+        $indexlink= "Messages not matched to a specific $gBug report - $week";
+        $data->{subject}= '';
+        $indexentry= '';
+        undef $tpack;
+        undef $tmaint;
+        undef $iiref;
+        $tpackfile= "pnone.html";
+        $indexpart= 'unmatched';
+    } else 
+       {
+       $data=readbug($ref);
+        $_= $data->{package}; y/A-Z/a-z/; $_= $` if m/[^-+._a-z0-9()]/;
+        $tpack= $_;
+        if ($data->{severity} eq '' || $data->{severity} eq 'normal') 
+               {       $showseverity= '';
+            $addseverity= $gDefaultSeverity;
+        } elsif (isstrongseverity($data->{severity})) 
+               {       $showseverity= "<strong>Severity: $data->{severity}</strong>;\n";
+            $addseverity= $data->{severity};
+        } else 
+               {       $showseverity= "Severity: <em>$data->{severity}</em>;\n";
+            $addseverity= $data->{severity};
+        }
+        $days= int(($startdate - $data->{date})/86400); close(S);
+        $indexlink= "#$ref: ".&sani($data->{subject});
+        $indexentry= '';
+        $packfile= length($tpack) ? "pa/l$tpack.html" : "pa/none.html";
+        $indexentry .= "Package: <A href=\"../$packfile\"><strong>".
+                        &sani($data->{package})."</strong></A>;\n"
+            if length($data->{package});
+        $indexentry .= $showseverity;
+        $indexentry .= "Reported by: ".&sani($data->{originator});
+        $indexentry .= ";\nOwned by: ".&sani($data->{owner})
+            if length($data->{owner});
+        $indexentry .= ";\nKeywords: ".&sani($data->{keywords})
+            if length($data->{keywords});
+        $linkto= $ref; $linkto =~ s,^..,$&/$&,;
+        @merged= split(/ /,$data->{mergedwith});
+        if (@merged) 
+               {       $mseparator= ";\nmerged with ";
+            for $m (@merged) 
+                       {       $mfile= $m; $mfile =~ s,^..,$&/$&,;
+                $indexentry .= $mseparator."<A href=\"../$mfile.html\">#$m</A>";
+                $mseparator= ",\n";
+            }
+        }
+        $daysold=$submitted='';
+        if (length($data->{done})) 
+               {       $indexentry .= ";\n<strong>Done:</strong> ".&sani($data->{done});
+            $indexpart= "done$addseverity";
+        } elsif (length($data->{forwarded})) 
+               {       $indexentry .= ";\n<strong>Forwarded</strong> to ".&sani($data->{forwarded});
+            $indexpart= "forwarded$addseverity";
+        } else 
+               {       $cmonths= int($days/30);
+            if ($cmonths != $amonths) 
+                       {       $msg= $cmonths == 0 ? "Submitted in the last month" :
+                       $cmonths == 1 ? "Over one month old" :
+                       $cmonths == 2 ? "Over two months old - attention is required" :
+                       "OVER $cmonths MONTHS OLD - ATTENTION IS REQUIRED";
+                $shortindex .= "</pre><h2>$msg:</h2><pre>\n$shorthead\n";
+                $amonths= $cmonths;
+            }
+            $pad= 6-length(sprintf("%d",$f));
+            $thissient=
+                ($pad>0 ? ' 'x$pad : '').
+                sprintf("<A href=\"../%s.html\">%d</A>",$linkto,$ref).
+                &sani(sprintf(" %-1.1s %-10.10s %-35.35s %-.25s\n",
+                                               $data->{severity},
+                        $data->{package},
+                        (length($data->{keywords}) ? $data->{keywords}.'/' : '').
+                        $data->{subject}, $data->{originator}));
+            $shortindex.= $thissient;
+            $sient{"$ref $data->{package}"}= $thissient;
+            if ($days >= 7) 
+                       {       $font= $days <= 30 ? '' :
+                       $days <= 60 ? 'em' :
+                    'strong';
+                $efont= length($font) ? "</$font>" : '';
+                $font= length($font) ? "<$font>" : '';
+                $daysold= "; $font$days days old$efont";
+            }
+            if ($preserveonly) {
+                $submitted = 'THIS IS A BUG IN THE BUG PROCESSOR';
+            } else {
+                $submitted = strftime "%a, %e %b %Y %T %Z", localtime($data->{date});
+            }
+            $submitted= "; dated $submitted";
+            $indexpart= "pending$addseverity";
+        }
+        $iiref= $ref;
+        $short= $ref; $short =~ s/^\d+/#$&/;
+        $tmaint= defined($maintainer{$tpack}) ? $maintainer{$tpack} : '(unknown)';
+        $qpackage= &sani($_);
+        $descriptivehead= $indexentry.$submitted.";\nMaintainer for $qpackage is\n".
+            '<A href="../ma/l'.&maintencoded($tmaint).'.html">'.&sani($tmaint).'</A>.';
+        $indexentry .= $daysold;
+        $indexentry .= ".";
+    }
+    $indexadd='';
+    $indexadd .= "<!--iid $iiref-->" if defined($iiref);
+    $indexadd .= "<li><A href=\"../$linkto.html\">".$indexlink."</A>";
+    $indexadd .=  "<br>\n".$indexentry if length($indexentry);
+    $indexadd .= "<!--/iid-->" if defined($iiref);
+    $indexadd .= "\n";
+    $estr= "\$index$indexpart = \$indexadd.\$index$indexpart; 1;";
+    eval($estr) || die "eval add to \$index$indexpart ($estr) failed: $@";
+       #print STDERR ">$estr|$indexadd<\n";
+    $indexadd= "<!--ii $iiref-->\n" if defined($iiref);
+    eval("\$iiindex$indexpart = \$indexadd.\$iiindex$indexpart; 1;") ||
+        die "eval add to \$iiindex$indexpart failed: $@";
+    if (defined($tmaint)) 
+       {       $countpermaint{$tmaint} += length($data->{done}) ? 0 : length($data->{forwarded}) ? 0 : 1;
+        eval("\$permaint${indexpart}{\$tmaint} .= \$indexadd; 1;") ||
+            die "eval add to \$permaint${indexpart}{\$tmaint} failed: $@";
+    }
+    if (defined($tpack)) 
+       {       $countperpack{$tpack} += length($data->{done}) ? 0 : length($data->{forwarded}) ? 0 : 1;
+        eval("\$perpack${indexpart}{\$tpack} .= \$indexadd; 1;") ||
+            die "eval add to \$perpack${indexpart}{\$tpack} failed: $@";
+    }
+    if ($preserveonly) { &preserve("$linkto.html"); &preserve("$linkto-b.html"); &unfilelock; next; }
+    my $hash = get_hashname($ref);
+    open(L,"db-h/$hash/$ref.log") || die "open db-h/$hash/$ref.log: $!";
+    $log='';
+    $boring=''; $xmessage= 0;
+    $normstate= 'kill-init';
+    $suppressnext= 0;
+    while(<L>) {
+        if (m/^\07$/) {
+            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
+                die "$ref ^G in state $normstate";
+            $normstate= 'incoming-recv';
+        } elsif (m/^\01$/) {
+            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
+                die "$ref ^A in state $normstate";
+            $normstate= 'autocheck';
+        } elsif (m/^\02$/) {
+            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
+                die "$ref ^B in state $normstate";
+            $normstate= 'recips';
+        } elsif (m/^\03$/) {
+            $normstate eq 'go' || $normstate eq 'go-nox' || $normstate eq 'html' ||
+                die "$ref ^C in state $normstate";
+            $this .= "</pre>\n" if $normstate eq 'go' || $normstate eq 'go-nox';
+            if ($normstate eq 'html') {
+                $xmessage++;
+                $this .= "  <em><A href=\"../$linkto-b.html#m$xmessage\">Full text</A>".
+                         " available.</em>";
+            }
+            if ($suppressnext && $normstate ne 'html') {
+                $ntis= $this; $ntis =~ s:\<pre\>:</A><pre>:i;
+                $boring .= "<hr><A name=\"m$xmessage\">\n$ntis\n";
+            } else {
+                $log = $this. "<hr>\n". $log;
+            }
+            $suppressnext= $normstate eq 'html';
+            $normstate= 'kill-end';
+        } elsif (m/^\05$/) {
+            $normstate eq 'kill-body' || die "^E in state $normstate";
+            $this .= "<pre>\n";
+            $normstate= 'go';
+        } elsif (m/^\06$/) {
+            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
+                die "$ref ^F in state $normstate";
+            $normstate= 'html'; $this= '';
+        } elsif ($normstate eq 'incoming-recv') {
+            $pl= $_; $pl =~ s/\n+$//;
+            m/^Received: \(at (\S+)\) by (\S+)\;/ ||
+                die "bad line \`$pl' in state incoming-recv";
+            $this = "<h2>Message received at ".&sani("$1\@$2").":</h2><br>\n".
+                    "<pre>\n".
+                    "$_";
+            $normstate= 'go';
+        } elsif ($normstate eq 'html') {
+            $this .= $_;
+        } elsif ($normstate eq 'go') {
+            s/^\030//;
+            $this .= &sani($_);
+        } elsif ($normstate eq 'go-nox') {
+            next if !s/^X//;
+            $this .= &sani($_);
+        } elsif ($normstate eq 'recips') {
+            if (m/^-t$/) {
+                $this = "<h2>Message sent:</h2><br>\n";
+            } else {
+                s/\04/, /g; s/\n$//;
+                $this = "<h2>Message sent to ".&sani($_).":</h2><br>\n";
+            }
+            $normstate= 'kill-body';
+        } elsif ($normstate eq 'autocheck') {
+            next if !m/^X-Debian-Bugs(-\w+)?: This is an autoforward from (\S+)/;
+            $normstate= 'autowait';
+            $this = "<h2>Message received at $2:</h2><br>\n";
+        } elsif ($normstate eq 'autowait') {
+            next if !m/^$/;
+            $normstate= 'go-nox';
+            $this .= "<pre>\n";
+        } else {
+            die "$ref state $normstate line \`$_'";
+        }
+    }
+    die "$ref state $normstate at end" unless $normstate eq 'kill-end';
+    close(L);
+    if (length($boring)) {
+        &file("$linkto-b.html",'non',
+              "<html><head><title>$gProject $gBug report logs - ".
+              "$short, boring messages</title>\n".
+              "<link rev=\"made\" href=\"mailto:$gMaintainerEmail)\">\n".
+              "</head>$gHTMLStart<h1>$gProject $gBugreport logs -".
+              "\n <A href=\"../$linkto.html\">$short</A>,".
+              " boring messages</h1>\n$boring\n<hr>\n".
+              $tail_html."</body></html>\n");
+    }
+    &file("$linkto.html",'non',
+          "<html><head><title>$gProject $gBug report logs - ".
+          "$short</title>\n".
+          "<link rev=\"made\" href=\"mailto:$gMaintainerEmail\">\n".
+          "</head>$gHTMLStart<h1>$gProject $gBug report logs -  $short<br>\n".
+          &sani($data->{subject})."</h1>".
+          "$descriptivehead\n".
+          "\n<hr>\n".
+          $log.
+          $tail_html."</body></html>\n");
+    &unfilelock;
+}
+
+sub maintsort {
+    $_= $_[0];
+    s/([^<>()]+) \(([^()<>]+)\)/$2 \<$1\>/;
+    
+    s/\s+/ /g;
+    s/^\s*//;
+    $email= s/ *\<[^<>()]+\>$//g ? $& : '';
+    $_= "$1 $_" if s/ (\S+)$//;
+    $_.= $email;
+    $_;
+}
+
+sub maintencoded {
+    return $maintencoded{$_[0]} if defined($maintencoded{$_[0]});
+    local ($input)= @_;
+    local ($todo,$encoded)= ($input);
+    while ($todo =~ m/\W/) {
+        $encoded.=$`.sprintf("-%02x_",unpack("C",$&));
+        $todo= $';
+    }
+    $encoded.= $todo;
+    $encoded =~ s/-2e_/\./g;
+    $encoded =~ s/^([^,]+)-20_-3c_(.*)-40_(.*)-3e_/$1,$2,$3,/;
+    $encoded =~ s/^(.*)-40_(.*)-20_-28_([^,]+)-29_$/,$1,$2,$3/;
+    $encoded =~ s/-20_/_/g;
+    $encoded =~ s/-([^_]+)_-/-$1/g;
+    $maintencoded{$input}= $encoded;
+}
+
+for $tmaint (keys %countpermaint) {
+    $_= $tmaint;
+    $after=$before=$sort2d=$sort2s=$sort1d=$sort1s='';
+    $after= "$&$after" if s/\s*\<[^<>()]+\>\s*$//;
+    $after= "$&$after" if s/\s*\)\s*$//;
+    $after= "$&$after" if s/\s*,.*$//;
+    $before.= $& if s/^.*\(\s*//;
+    $sort2d= $& if s/\S+$//;
+    $sort1d= $_;
+    while (s/^([^()<>]+)\. */$1 /) { };
+    s/\s+$//; y/A-Za-z/a-zA-Z/; $sort1s= $_;
+    $sort2s= $sort2d; $sort2s =~ y/A-Za-z/a-zA-Z/;
+    $maintsort{$tmaint}= $sort2s.' '.$sort1s.' '.$before.$sort1d.$sort2d.$after;
+    $maintdisplay{$tmaint}=
+        &sani($before).'<strong>'.&sani($sort1d.$sort2d).'</strong>'.&sani($after);
+}
+
+sub heading ($$) {
+    my ($pt,$sv) = @_;
+    return $displayshowseverities{$sv}.' - '.$displayshowpendings{$pt};
+}
+
+sub makeindex ($$$) {
+    my ($varprefix,$varsuffix,$tkey) = @_;
+    my ($pending,$severity,$anydone,$text);
+    $anydone= 0;
+    $text= '';
+    for $pending (qw(pending forwarded done)) {
+        for $severity (@showseverities) {
+            $estr= "\$value= \\${varprefix}${pending}${severity}${varsuffix}; 1;";
+#print STDERR $estr;
+            eval $estr
+                or die "eval get \$${varprefix}${pending}${severity} failed: $@";
+#print STDERR ">$$value<\n";
+            next unless length($$value);
+            $text.= "<hr>\n<h2>".&heading($pending,$severity).":</h2>\n".
+                    "(List of <A href=\"../si/$pending$severity.html\">all".
+                    " such $gBugs</A> is available.)\n<ul>\n".
+                    $$value.
+                    "</ul>\n";
+            $anydone=1 if $pending eq 'done';
+        }
+    }
+    $text.= $expirynote_html if $anydone;
+    return $text;
+}        
+
+&file("ix/full.html",'def',
+      $gFullIndex.
+      makeindex('$index',"",'').
+      "<hr>\n".
+      $tail_html."</body><html>\n");
+
+&file("ju/junk.html",'non',
+      $gJunkIndex.
+      "<hr>\n<h2>Junk (messages without a specific $gBug report number):</h2>\n".
+      "(\`this week' is everything since last Wednesday.)\n<ul>\n".
+      $indexunmatched.
+      "</ul><hr>\n".
+      $tail_html."</body><html>\n");
+
+$nobugs_html= "No reports are currently in this state.";
+$who_html= $gProject;
+$owner_addr= $gMaintainerEmail;
+$otherindex_html= "For other kinds of index or for other information about
+$gProject and the $gBug system, see the <A HREF=\"../../\">$gBug system top-level
+contents WWW page</A>.
+
+";
+
+for $pending (qw(pending forwarded done)) {
+    for $severity (@showseverities) {
+        eval "\$value= \\\$iiindex${pending}${severity}; 1;"
+            or die "eval get \$iiindex${pendingtype}${severity} failed: $@";
+        $value= \$nobugs_html if !length($$value);
+        $headstring= &heading($pending,$severity);
+        &file("si/$pending$severity.html",'ref',
+              "<html><head><title>$who_html $gBug reports: $headstring</title>\n".
+              "<link rev=\"made\" href=\"mailto:".&sani($owner_addr)."\">\n".
+              "</head>$gHTMLStart<h1>$who_html $gBug reports: $headstring</h1>\n".
+              $otherindex_html.
+              ($pending eq 'done' ? "<P>\n$expirynote_html" : '').
+              "<hr>\n<ul>\n".
+              $$value.
+              "</ul>\n<hr>\n".
+              $tail_html."</body></html>\n");
+    }
+}
+
+sub individualindexes ($\@&\%&&$$$$$&&) {
+    my ($filename,$keysref,$getfilenameref,$countref,$getdisplayref,
+        $getsimpledisplayref,$what,$caveat,$whatplural,$abbrev,$ihead,
+        $getxinforef,$getxindexref) = @_;
+    my ($itext,$i,$tkey,$sani,$count,$tfilename,$refto,$backnext,$xitext,$bugbugs);
+    $itext='';
+    for ($i=0; $i<=$#$keysref; $i++) {
+        $tkey= $$keysref[$i];
+        $tfilename= &$getfilenameref($tkey);
+        $sani= &$getsimpledisplayref($tkey);
+        $count= $$countref{$tkey};
+        $count= $count >= 1 ? "$count" : "no";
+        $bugbugs= $count == 1 ? "$gBug" : "$gBugs";
+        $xitext= &$getxindexref($tkey);
+        $xitext= length($xitext) ? "$count $bugbugs; $xitext"
+                                 : "$count outstanding $bugbugs";
+        $itext .= "<li><A href=\"../$tfilename\">".&$getdisplayref($tkey)."</A>"."\n".
+                  "  ($xitext)\n";
+        $backnext= '';
+        if ($i>0) {
+            $refto= $$keysref[$i-1];
+            $xitext= &$getxindexref($refto);
+            $xitext= " ($xitext)" if length($xitext);
+            $backnext .= "<br>\nPrevious $what in list, <A href=\"../".
+                         &$getfilenameref($refto)."\">".&$getdisplayref($refto)."</A>".
+                         "$xitext\n";
+        }
+        if ($i<$#$keysref) {
+            $refto= $$keysref[$i+1];
+            $xitext= &$getxindexref($refto);
+            $xitext= " ($xitext)" if length($xitext);
+            $backnext .= "<br>\nNext $what in list, <A href=\"../".
+                         &$getfilenameref($refto)."\">".&$getdisplayref($refto)."</A>".
+                         "$xitext\n";
+        }
+        &file($tfilename,'ref',
+              "<html><head><title>$gProject $gBug reports: $what $sani</title>\n".
+              "<link rev=\"made\" href=\"mailto:$gMaintainerEmail\">\n".
+              "</head>$gHTMLStart<h1>$gProject $gBug reports: $what $sani</h1>\n".
+              &$getxinforef($tkey).
+              $caveat.
+              "See the <A href=\"../$filename\">listing of $whatplural</A>.\n".
+              $backnext.
+              &makeindex("\$per${abbrev}","{\$tkey}",$tkey).
+              "<hr>\n".
+              $tail_html."</body></html>\n");
+    }
+    &file($filename,'non',
+          $ihead.
+          "<hr><ul>\n".
+          $itext.
+          "</ul><hr>\n".
+          $tail_html."</body></html>\n");
+}
+
+@maintainers= sort { $maintsort{$a} cmp $maintsort{$b}; } keys %countpermaint;
+individualindexes('ix/maintainers.html',
+                  @maintainers,
+                  sub { 'ma/l'.&maintencoded($_[0]).'.html'; },
+                  %countpermaint,
+                  sub { $maintdisplay{$_[0]}; },
+                  sub { &sani($_[0]); },
+                  'maintainer',
+                  "Note that there may be other reports filed under different
+                                 variations on the maintainer\'s name and email address.<P>",
+                  'maintainers',
+                  'maint',
+                  $gMaintIndex,
+                  sub { return ''; },
+                  sub { return ''; });
+
+@packages= sort keys %countperpack;
+individualindexes('ix/packages.html',
+                  @packages,
+                  sub { length($_[0]) ? "pa/l$_[0].html" : 'pa/none.html'; },
+                  %countperpack,
+                  sub { length($_[0]) ? $_[0] : 'not specified'; },
+                  sub { &sani(length($_[0]) ? $_[0] : 'not specified'); },
+                  'package',
+                  "Note that with multi-binary packages there may be other
+                                 reports filed under the different binary package names.<P>",
+                  'packages',
+                  'pack',
+                  $gPackageIndex,
+                  sub {
+                      return unless defined($maintainer{$_[0]});
+                      $tmaint= $maintainer{$_[0]};
+                      return "Maintainer for $_[0] is <A href=\"../ma/l".
+                             &maintencoded($tmaint).
+                             ".html\">".&sani($tmaint)."</A>.\n<p>\n";
+                  },
+                  sub {
+                      return unless defined($maintainer{$_[0]});
+                      $tmaint= $maintainer{$_[0]};
+                      return "<A href=\"../ma/l".
+                             &maintencoded($tmaint).
+                             ".html\">".&sani($tmaint)."</A>";
+                  });
+
+&file('ix/summary.html','non',
+      $gSummaryIndex.
+      "<hr><pre>\n".
+      $shortindex.
+      "</pre><hr>\n".
+      $tail_html."</body></html>\n");
+
+$bypackageindex='';
+for $k (map {$_->[0] }
+       sort { $a->[2] cmp $b->[2]  ||  $a->[1] <=> $b->[1] }
+       map { [$_, split(' ',$_,2)] } keys %sient)
+    { $bypackageindex.= $sient{$k}; }
+&file('ix/psummary.html','non',
+      $gPackageLog.
+      "<hr><pre>\n$shorthead\n".
+      $bypackageindex.
+      "</pre><hr>\n".
+      $tail_html."</body></html>\n");
+
+open(P,"$gPseudoDescFile") ||
+    die "$gPseudoDescFile: $!";
+$ppd=''; while(<P>) { s/\s*\n$//; $ppd.= &sani($_)."\n"; } close(P);
+&file('ix/pseudopackages.html','non',
+      $gPseudoIndex.
+      "<hr><pre>\n$ppd".
+      "</pre><hr>\n".
+      $tail_html."</body></html>\n");
+
+$_= $gHTMLStamp; s/SUBSTITUTE_DTIME/$dtime/o;
+
+&file('ix/zstamp.html','non',$_."</body></html>\n");
+
+sub notimestamp ($) {
+    $_= $_[0];
+    s/\<\!\-\-timestamp\-\-\>\n.*\n\<\!\-\-\/timestamp\-\-\>\n//;
+    return $_;
+}
+
+sub file {
+    local ($name,$ii,$file)= @_;
+    if ($diff) {
+        $cmppath= "$wwwbase/$name".($ii eq 'ref' ? '.ref' : '');
+        if (open(ORIG,"$cmppath")) {
+            undef $/; $orig= <ORIG>; $/= "\n";
+            close(ORIG);
+            if (&notimestamp($orig) eq &notimestamp($file)) {
+               print "preserve $name\n";
+               return;
+           }
+            defined($c= open(P,"-|")) or die "pipe/fork for diff: $!";
+            if (!$c) {
+                open(Q,"|diff -e $cmppath -") or die "pipe/fork II for diff: $!\n";
+                print Q $file or die "write orig to diff: $!\n";
+                close(Q); $?==0 || $?==256 or die "diff gave $?\n";
+                exit($?>>8);
+            }
+            undef $/; $difftxt= <P>; $/= "\n";
+            close(P); $?==0 || $?==256 or die "diff fork gave $?\n";
+            if ($?==0) {
+                print "preserve $name\n";
+                return;
+            }
+            $v= (split(/\n/,$difftxt));
+            print "diff $v $ii $name\n${difftxt}thatdiff $name\n"
+                or die "stdout (diff): $!";
+            return;
+        }
+    } 
+    $v= (split(/\n/,$file));
+    print "file $v $ii $name\n${file}thatfile $name\n" or die "stdout: $!";
+}
+
+sub preserve {
+    print "preserve $_[0]\n";
+}
+
+print "end\n";
+
+while ($u= $cleanups[$#cleanups]) { &$u; }
+exit 0;
diff --git a/scripts/db2html.in b/scripts/db2html.in
deleted file mode 100755 (executable)
index b45d2b9..0000000
+++ /dev/null
@@ -1,653 +0,0 @@
-#!/usr/bin/perl
-# $Id: db2html.in,v 1.22 2004/04/19 10:03:53 cjwatson Exp $
-# usage: db2html [-diff] [-stampfile=<stampfile>] [-lastrun=<days>] <wwwbase>
-
-#load the necessary libraries/configuration
-$config_path = '/etc/debbugs';
-$lib_path = '/usr/lib/debbugs';
-
-require("$config_path/config");
-require("$config_path/text");
-require("$lib_path/errorlib");
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-use POSIX qw(strftime tzset);
-$ENV{"TZ"} = 'UTC';
-tzset();
-
-#set current working directory
-chdir("$gSpoolDir") || die "chdir spool: $!\n";
-
-#setup variables
-$diff = 0;
-$stampfile = 'stamp.html';
-$tail_html = $gHTMLTail; 
-$expirynote_html = '';
-$expirynote_html = $gHTMLExpireNote if $gRemoveAge;
-$shorthead = ' Ref   * Package    Keywords/Subject                    Submitter';
-$shortindex = ''; 
-$amonths = -1;
-$indexunmatched = '';
-%displayshowpendings = ('pending','outstanding',
-                       'done','resolved',
-                       'forwarded','forwarded to upstream software authors');
-
-#set timestamp for html files
-$dtime = strftime "%a, %e %b %Y %T UTC", localtime;
-$tail_html =~ s/SUBSTITUTE_DTIME/$dtime/;
-
-#check for commandline switches
-while (@ARGV && $ARGV[0] =~ m/^-/) 
-{      if ($ARGV[0] eq '-diff') { $diff=1; }
-    elsif ($ARGV[0] =~ m/^-lastrun\=([0-9.]+)$/) { $lastrun= $1; undef $stampfile; }
-    elsif ($ARGV[0] =~ m/^-full$/) { undef $lastrun; undef $stampfile; }
-    elsif ($ARGV[0] =~ m/^-stampfile\=(\S+)$/) { $stampfile= $1; }
-    else { &quit("bad usage"); }
-    shift;
-}
-
-#check for remaing argument, only one...
-@ARGV==1 or die;
-$wwwbase= shift(@ARGV);
-
-#get starting time
-defined($startdate= time) || &quit("failed to get time: $!");
-
-$|=1;
-
-#if stamp file was given, 
-if (defined($stampfile)) 
-{      if (open(X,"< $stampfile")) 
-       {       $lastrun= -M X;
-        close(X);
-        printf "progress last run %.7f days\n",$lastrun;
-    } else { print "progress stamp file $stampfile: $! - full\n"; }
-}
-
-#only process file if greater than last run...
-if (defined($lastrun) && -M "db-h" > $lastrun) 
-{      $_= $gHTMLStamp;
-    s/SUBSTITUTE_DTIME/$dtime/o;
-    s/\<\!\-\-updateupdate\-\-\>.*\<\!\-\-\/updateupdate\-\-\>/check/;
-    &file('ix/zstamp.html','non',$_."</body></html>\n");
-       print "noremoves";
-#    print "db2html: no changes since last run\n";
-    exit 0;
-}
-
-#parse maintainer file
-open(MM,"$gMaintainerFile") || &quit("open $gMaintainerFile: $!");
-while(<MM>) 
-{      m/^(\S+)\s+(\S.*\S)\s*$/ || &quit("$gMaintainerFile: \`$_'");
-    ($a,$b)=($1,$2);
-    $a =~ y/A-Z/a-z/;
-    $maintainer{$a}= $b;
-}
-close(MM);
-
-#load all database files
-opendir(D,'db-h') || &quit("opendir db-h: $!");
-@dirs = grep(s#^#db-h/#,grep(/^\d+$/,readdir(D)));
-closedir(D);
-foreach my $dir (@dirs) {
-    opendir(D,$dir);
-    push @files, grep(/^-?\d+\.log$/,readdir(D));
-    closedir(D);
-}
-@files = sort { $a <=> $b } @files;
-
-for $pending (qw(pending done forwarded)) 
-{      for $severity (@showseverities) 
-       {       eval "\$index${pending}${severity}= \$iiindex${pending}${severity}= ''; 1;"
-            or &quit("reset \$index${pending}${severity}: $@");
-    }
-}
-
-for $f (@files) 
-{      next unless $f =~ m/^(-?\d+)\.log$/;
-    $ref= $1;
-       #((print STDERR "$ref\n"),
-       #next
-       #)
-       # unless $ref =~ m/^-/ || $ref =~ m/^124/;
-    &filelock("lock/$ref");
-    $preserveonly= defined($lastrun) && -M "db-h/".get_hashname($ref)."/$ref.log" > $lastrun;
-    if ($ref =~ m/^-\d$/) 
-       {       $week= $ref eq '-1' ? 'this week' :
-               $ref eq '-2' ? 'last week' :
-               $ref eq '-3' ? 'two weeks ago' :
-                              ($ref-1)." weeks ago";
-        $linkto= "ju/unmatched$ref";
-        $short= "junk, $week";
-        $descriptivehead=
-            "This includes messages sent to <code>done\@$gEmailDomain</code>\n".
-            "which did not have a $gBug reference number in the Subject line\n".
-            "or which contained an\n".
-            "unknown or out of date $gBug report number (these cause a warning\n".
-            "to be sent to the sender) and details about the messages\n".
-            "sent to <code>request@$gEmailDomain</code> (all of which".
-            "produce replies).\n";
-        $indexlink= "Messages not matched to a specific $gBug report - $week";
-        $data->{subject}= '';
-        $indexentry= '';
-        undef $tpack;
-        undef $tmaint;
-        undef $iiref;
-        $tpackfile= "pnone.html";
-        $indexpart= 'unmatched';
-    } else 
-       {
-       $data=readbug($ref);
-        $_= $data->{package}; y/A-Z/a-z/; $_= $` if m/[^-+._a-z0-9()]/;
-        $tpack= $_;
-        if ($data->{severity} eq '' || $data->{severity} eq 'normal') 
-               {       $showseverity= '';
-            $addseverity= $gDefaultSeverity;
-        } elsif (isstrongseverity($data->{severity})) 
-               {       $showseverity= "<strong>Severity: $data->{severity}</strong>;\n";
-            $addseverity= $data->{severity};
-        } else 
-               {       $showseverity= "Severity: <em>$data->{severity}</em>;\n";
-            $addseverity= $data->{severity};
-        }
-        $days= int(($startdate - $data->{date})/86400); close(S);
-        $indexlink= "#$ref: ".&sani($data->{subject});
-        $indexentry= '';
-        $packfile= length($tpack) ? "pa/l$tpack.html" : "pa/none.html";
-        $indexentry .= "Package: <A href=\"../$packfile\"><strong>".
-                        &sani($data->{package})."</strong></A>;\n"
-            if length($data->{package});
-        $indexentry .= $showseverity;
-        $indexentry .= "Reported by: ".&sani($data->{originator});
-        $indexentry .= ";\nOwned by: ".&sani($data->{owner})
-            if length($data->{owner});
-        $indexentry .= ";\nKeywords: ".&sani($data->{keywords})
-            if length($data->{keywords});
-        $linkto= $ref; $linkto =~ s,^..,$&/$&,;
-        @merged= split(/ /,$data->{mergedwith});
-        if (@merged) 
-               {       $mseparator= ";\nmerged with ";
-            for $m (@merged) 
-                       {       $mfile= $m; $mfile =~ s,^..,$&/$&,;
-                $indexentry .= $mseparator."<A href=\"../$mfile.html\">#$m</A>";
-                $mseparator= ",\n";
-            }
-        }
-        $daysold=$submitted='';
-        if (length($data->{done})) 
-               {       $indexentry .= ";\n<strong>Done:</strong> ".&sani($data->{done});
-            $indexpart= "done$addseverity";
-        } elsif (length($data->{forwarded})) 
-               {       $indexentry .= ";\n<strong>Forwarded</strong> to ".&sani($data->{forwarded});
-            $indexpart= "forwarded$addseverity";
-        } else 
-               {       $cmonths= int($days/30);
-            if ($cmonths != $amonths) 
-                       {       $msg= $cmonths == 0 ? "Submitted in the last month" :
-                       $cmonths == 1 ? "Over one month old" :
-                       $cmonths == 2 ? "Over two months old - attention is required" :
-                       "OVER $cmonths MONTHS OLD - ATTENTION IS REQUIRED";
-                $shortindex .= "</pre><h2>$msg:</h2><pre>\n$shorthead\n";
-                $amonths= $cmonths;
-            }
-            $pad= 6-length(sprintf("%d",$f));
-            $thissient=
-                ($pad>0 ? ' 'x$pad : '').
-                sprintf("<A href=\"../%s.html\">%d</A>",$linkto,$ref).
-                &sani(sprintf(" %-1.1s %-10.10s %-35.35s %-.25s\n",
-                                               $data->{severity},
-                        $data->{package},
-                        (length($data->{keywords}) ? $data->{keywords}.'/' : '').
-                        $data->{subject}, $data->{originator}));
-            $shortindex.= $thissient;
-            $sient{"$ref $data->{package}"}= $thissient;
-            if ($days >= 7) 
-                       {       $font= $days <= 30 ? '' :
-                       $days <= 60 ? 'em' :
-                    'strong';
-                $efont= length($font) ? "</$font>" : '';
-                $font= length($font) ? "<$font>" : '';
-                $daysold= "; $font$days days old$efont";
-            }
-            if ($preserveonly) {
-                $submitted = 'THIS IS A BUG IN THE BUG PROCESSOR';
-            } else {
-                $submitted = strftime "%a, %e %b %Y %T %Z", localtime($data->{date});
-            }
-            $submitted= "; dated $submitted";
-            $indexpart= "pending$addseverity";
-        }
-        $iiref= $ref;
-        $short= $ref; $short =~ s/^\d+/#$&/;
-        $tmaint= defined($maintainer{$tpack}) ? $maintainer{$tpack} : '(unknown)';
-        $qpackage= &sani($_);
-        $descriptivehead= $indexentry.$submitted.";\nMaintainer for $qpackage is\n".
-            '<A href="../ma/l'.&maintencoded($tmaint).'.html">'.&sani($tmaint).'</A>.';
-        $indexentry .= $daysold;
-        $indexentry .= ".";
-    }
-    $indexadd='';
-    $indexadd .= "<!--iid $iiref-->" if defined($iiref);
-    $indexadd .= "<li><A href=\"../$linkto.html\">".$indexlink."</A>";
-    $indexadd .=  "<br>\n".$indexentry if length($indexentry);
-    $indexadd .= "<!--/iid-->" if defined($iiref);
-    $indexadd .= "\n";
-    $estr= "\$index$indexpart = \$indexadd.\$index$indexpart; 1;";
-    eval($estr) || &quit("eval add to \$index$indexpart ($estr) failed: $@");
-       #print STDERR ">$estr|$indexadd<\n";
-    $indexadd= "<!--ii $iiref-->\n" if defined($iiref);
-    eval("\$iiindex$indexpart = \$indexadd.\$iiindex$indexpart; 1;") ||
-        &quit("eval add to \$iiindex$indexpart failed: $@");
-    if (defined($tmaint)) 
-       {       $countpermaint{$tmaint} += length($data->{done}) ? 0 : length($data->{forwarded}) ? 0 : 1;
-        eval("\$permaint${indexpart}{\$tmaint} .= \$indexadd; 1;") ||
-            &quit("eval add to \$permaint${indexpart}{\$tmaint} failed: $@");
-    }
-    if (defined($tpack)) 
-       {       $countperpack{$tpack} += length($data->{done}) ? 0 : length($data->{forwarded}) ? 0 : 1;
-        eval("\$perpack${indexpart}{\$tpack} .= \$indexadd; 1;") ||
-            &quit("eval add to \$perpack${indexpart}{\$tpack} failed: $@");
-    }
-    if ($preserveonly) { &preserve("$linkto.html"); &preserve("$linkto-b.html"); &unfilelock; next; }
-    my $hash = get_hashname($ref);
-    open(L,"db-h/$hash/$ref.log") || &quit("open db-h/$hash/$ref.log: $!");
-    $log='';
-    $boring=''; $xmessage= 0;
-    $normstate= 'kill-init';
-    $suppressnext= 0;
-    while(<L>) {
-        if (m/^\07$/) {
-            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
-                &quit("$ref ^G in state $normstate");
-            $normstate= 'incoming-recv';
-        } elsif (m/^\01$/) {
-            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
-                &quit("$ref ^A in state $normstate");
-            $normstate= 'autocheck';
-        } elsif (m/^\02$/) {
-            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
-                &quit("$ref ^B in state $normstate");
-            $normstate= 'recips';
-        } elsif (m/^\03$/) {
-            $normstate eq 'go' || $normstate eq 'go-nox' || $normstate eq 'html' ||
-                &quit("$ref ^C in state $normstate");
-            $this .= "</pre>\n" if $normstate eq 'go' || $normstate eq 'go-nox';
-            if ($normstate eq 'html') {
-                $xmessage++;
-                $this .= "  <em><A href=\"../$linkto-b.html#m$xmessage\">Full text</A>".
-                         " available.</em>";
-            }
-            if ($suppressnext && $normstate ne 'html') {
-                $ntis= $this; $ntis =~ s:\<pre\>:</A><pre>:i;
-                $boring .= "<hr><A name=\"m$xmessage\">\n$ntis\n";
-            } else {
-                $log = $this. "<hr>\n". $log;
-            }
-            $suppressnext= $normstate eq 'html';
-            $normstate= 'kill-end';
-        } elsif (m/^\05$/) {
-            $normstate eq 'kill-body' || &quit("^E in state $normstate");
-            $this .= "<pre>\n";
-            $normstate= 'go';
-        } elsif (m/^\06$/) {
-            $normstate eq 'kill-init' || $normstate eq 'kill-end' ||
-                &quit("$ref ^F in state $normstate");
-            $normstate= 'html'; $this= '';
-        } elsif ($normstate eq 'incoming-recv') {
-            $pl= $_; $pl =~ s/\n+$//;
-            m/^Received: \(at (\S+)\) by (\S+)\;/ ||
-                &quit("bad line \`$pl' in state incoming-recv");
-            $this = "<h2>Message received at ".&sani("$1\@$2").":</h2><br>\n".
-                    "<pre>\n".
-                    "$_";
-            $normstate= 'go';
-        } elsif ($normstate eq 'html') {
-            $this .= $_;
-        } elsif ($normstate eq 'go') {
-            s/^\030//;
-            $this .= &sani($_);
-        } elsif ($normstate eq 'go-nox') {
-            next if !s/^X//;
-            $this .= &sani($_);
-        } elsif ($normstate eq 'recips') {
-            if (m/^-t$/) {
-                $this = "<h2>Message sent:</h2><br>\n";
-            } else {
-                s/\04/, /g; s/\n$//;
-                $this = "<h2>Message sent to ".&sani($_).":</h2><br>\n";
-            }
-            $normstate= 'kill-body';
-        } elsif ($normstate eq 'autocheck') {
-            next if !m/^X-Debian-Bugs(-\w+)?: This is an autoforward from (\S+)/;
-            $normstate= 'autowait';
-            $this = "<h2>Message received at $2:</h2><br>\n";
-        } elsif ($normstate eq 'autowait') {
-            next if !m/^$/;
-            $normstate= 'go-nox';
-            $this .= "<pre>\n";
-        } else {
-            &quit("$ref state $normstate line \`$_'");
-        }
-    }
-    &quit("$ref state $normstate at end") unless $normstate eq 'kill-end';
-    close(L);
-    if (length($boring)) {
-        &file("$linkto-b.html",'non',
-              "<html><head><title>$gProject $gBug report logs - ".
-              "$short, boring messages</title>\n".
-              "<link rev=\"made\" href=\"mailto:$gMaintainerEmail)\">\n".
-              "</head>$gHTMLStart<h1>$gProject $gBugreport logs -".
-              "\n <A href=\"../$linkto.html\">$short</A>,".
-              " boring messages</h1>\n$boring\n<hr>\n".
-              $tail_html."</body></html>\n");
-    }
-    &file("$linkto.html",'non',
-          "<html><head><title>$gProject $gBug report logs - ".
-          "$short</title>\n".
-          "<link rev=\"made\" href=\"mailto:$gMaintainerEmail\">\n".
-          "</head>$gHTMLStart<h1>$gProject $gBug report logs -  $short<br>\n".
-          &sani($data->{subject})."</h1>".
-          "$descriptivehead\n".
-          "\n<hr>\n".
-          $log.
-          $tail_html."</body></html>\n");
-    &unfilelock;
-}
-
-sub maintsort {
-    $_= $_[0];
-    s/([^<>()]+) \(([^()<>]+)\)/$2 \<$1\>/;
-    
-    s/\s+/ /g;
-    s/^\s*//;
-    $email= s/ *\<[^<>()]+\>$//g ? $& : '';
-    $_= "$1 $_" if s/ (\S+)$//;
-    $_.= $email;
-    $_;
-}
-
-sub maintencoded {
-    return $maintencoded{$_[0]} if defined($maintencoded{$_[0]});
-    local ($input)= @_;
-    local ($todo,$encoded)= ($input);
-    while ($todo =~ m/\W/) {
-        $encoded.=$`.sprintf("-%02x_",unpack("C",$&));
-        $todo= $';
-    }
-    $encoded.= $todo;
-    $encoded =~ s/-2e_/\./g;
-    $encoded =~ s/^([^,]+)-20_-3c_(.*)-40_(.*)-3e_/$1,$2,$3,/;
-    $encoded =~ s/^(.*)-40_(.*)-20_-28_([^,]+)-29_$/,$1,$2,$3/;
-    $encoded =~ s/-20_/_/g;
-    $encoded =~ s/-([^_]+)_-/-$1/g;
-    $maintencoded{$input}= $encoded;
-}
-
-for $tmaint (keys %countpermaint) {
-    $_= $tmaint;
-    $after=$before=$sort2d=$sort2s=$sort1d=$sort1s='';
-    $after= "$&$after" if s/\s*\<[^<>()]+\>\s*$//;
-    $after= "$&$after" if s/\s*\)\s*$//;
-    $after= "$&$after" if s/\s*,.*$//;
-    $before.= $& if s/^.*\(\s*//;
-    $sort2d= $& if s/\S+$//;
-    $sort1d= $_;
-    while (s/^([^()<>]+)\. */$1 /) { };
-    s/\s+$//; y/A-Za-z/a-zA-Z/; $sort1s= $_;
-    $sort2s= $sort2d; $sort2s =~ y/A-Za-z/a-zA-Z/;
-    $maintsort{$tmaint}= $sort2s.' '.$sort1s.' '.$before.$sort1d.$sort2d.$after;
-    $maintdisplay{$tmaint}=
-        &sani($before).'<strong>'.&sani($sort1d.$sort2d).'</strong>'.&sani($after);
-}
-
-sub heading ($$) {
-    my ($pt,$sv) = @_;
-    return $displayshowseverities{$sv}.' - '.$displayshowpendings{$pt};
-}
-
-sub makeindex ($$$) {
-    my ($varprefix,$varsuffix,$tkey) = @_;
-    my ($pending,$severity,$anydone,$text);
-    $anydone= 0;
-    $text= '';
-    for $pending (qw(pending forwarded done)) {
-        for $severity (@showseverities) {
-            $estr= "\$value= \\${varprefix}${pending}${severity}${varsuffix}; 1;";
-#print STDERR $estr;
-            eval $estr
-                or &quit("eval get \$${varprefix}${pending}${severity} failed: $@");
-#print STDERR ">$$value<\n";
-            next unless length($$value);
-            $text.= "<hr>\n<h2>".&heading($pending,$severity).":</h2>\n".
-                    "(List of <A href=\"../si/$pending$severity.html\">all".
-                    " such $gBugs</A> is available.)\n<ul>\n".
-                    $$value.
-                    "</ul>\n";
-            $anydone=1 if $pending eq 'done';
-        }
-    }
-    $text.= $expirynote_html if $anydone;
-    return $text;
-}        
-
-&file("ix/full.html",'def',
-      $gFullIndex.
-      makeindex('$index',"",'').
-      "<hr>\n".
-      $tail_html."</body><html>\n");
-
-&file("ju/junk.html",'non',
-      $gJunkIndex.
-      "<hr>\n<h2>Junk (messages without a specific $gBug report number):</h2>\n".
-      "(\`this week' is everything since last Wednesday.)\n<ul>\n".
-      $indexunmatched.
-      "</ul><hr>\n".
-      $tail_html."</body><html>\n");
-
-$nobugs_html= "No reports are currently in this state.";
-$who_html= $gProject;
-$owner_addr= $gMaintainerEmail;
-$otherindex_html= "For other kinds of index or for other information about
-$gProject and the $gBug system, see the <A HREF=\"../../\">$gBug system top-level
-contents WWW page</A>.
-
-";
-
-for $pending (qw(pending forwarded done)) {
-    for $severity (@showseverities) {
-        eval "\$value= \\\$iiindex${pending}${severity}; 1;"
-            or &quit("eval get \$iiindex${pendingtype}${severity} failed: $@");
-        $value= \$nobugs_html if !length($$value);
-        $headstring= &heading($pending,$severity);
-        &file("si/$pending$severity.html",'ref',
-              "<html><head><title>$who_html $gBug reports: $headstring</title>\n".
-              "<link rev=\"made\" href=\"mailto:".&sani($owner_addr)."\">\n".
-              "</head>$gHTMLStart<h1>$who_html $gBug reports: $headstring</h1>\n".
-              $otherindex_html.
-              ($pending eq 'done' ? "<P>\n$expirynote_html" : '').
-              "<hr>\n<ul>\n".
-              $$value.
-              "</ul>\n<hr>\n".
-              $tail_html."</body></html>\n");
-    }
-}
-
-sub individualindexes ($\@&\%&&$$$$$&&) {
-    my ($filename,$keysref,$getfilenameref,$countref,$getdisplayref,
-        $getsimpledisplayref,$what,$caveat,$whatplural,$abbrev,$ihead,
-        $getxinforef,$getxindexref) = @_;
-    my ($itext,$i,$tkey,$sani,$count,$tfilename,$refto,$backnext,$xitext,$bugbugs);
-    $itext='';
-    for ($i=0; $i<=$#$keysref; $i++) {
-        $tkey= $$keysref[$i];
-        $tfilename= &$getfilenameref($tkey);
-        $sani= &$getsimpledisplayref($tkey);
-        $count= $$countref{$tkey};
-        $count= $count >= 1 ? "$count" : "no";
-        $bugbugs= $count == 1 ? "$gBug" : "$gBugs";
-        $xitext= &$getxindexref($tkey);
-        $xitext= length($xitext) ? "$count $bugbugs; $xitext"
-                                 : "$count outstanding $bugbugs";
-        $itext .= "<li><A href=\"../$tfilename\">".&$getdisplayref($tkey)."</A>"."\n".
-                  "  ($xitext)\n";
-        $backnext= '';
-        if ($i>0) {
-            $refto= $$keysref[$i-1];
-            $xitext= &$getxindexref($refto);
-            $xitext= " ($xitext)" if length($xitext);
-            $backnext .= "<br>\nPrevious $what in list, <A href=\"../".
-                         &$getfilenameref($refto)."\">".&$getdisplayref($refto)."</A>".
-                         "$xitext\n";
-        }
-        if ($i<$#$keysref) {
-            $refto= $$keysref[$i+1];
-            $xitext= &$getxindexref($refto);
-            $xitext= " ($xitext)" if length($xitext);
-            $backnext .= "<br>\nNext $what in list, <A href=\"../".
-                         &$getfilenameref($refto)."\">".&$getdisplayref($refto)."</A>".
-                         "$xitext\n";
-        }
-        &file($tfilename,'ref',
-              "<html><head><title>$gProject $gBug reports: $what $sani</title>\n".
-              "<link rev=\"made\" href=\"mailto:$gMaintainerEmail\">\n".
-              "</head>$gHTMLStart<h1>$gProject $gBug reports: $what $sani</h1>\n".
-              &$getxinforef($tkey).
-              $caveat.
-              "See the <A href=\"../$filename\">listing of $whatplural</A>.\n".
-              $backnext.
-              &makeindex("\$per${abbrev}","{\$tkey}",$tkey).
-              "<hr>\n".
-              $tail_html."</body></html>\n");
-    }
-    &file($filename,'non',
-          $ihead.
-          "<hr><ul>\n".
-          $itext.
-          "</ul><hr>\n".
-          $tail_html."</body></html>\n");
-}
-
-@maintainers= sort { $maintsort{$a} cmp $maintsort{$b}; } keys %countpermaint;
-individualindexes('ix/maintainers.html',
-                  @maintainers,
-                  sub { 'ma/l'.&maintencoded($_[0]).'.html'; },
-                  %countpermaint,
-                  sub { $maintdisplay{$_[0]}; },
-                  sub { &sani($_[0]); },
-                  'maintainer',
-                  "Note that there may be other reports filed under different
-                                 variations on the maintainer\'s name and email address.<P>",
-                  'maintainers',
-                  'maint',
-                  $gMaintIndex,
-                  sub { return ''; },
-                  sub { return ''; });
-
-@packages= sort keys %countperpack;
-individualindexes('ix/packages.html',
-                  @packages,
-                  sub { length($_[0]) ? "pa/l$_[0].html" : 'pa/none.html'; },
-                  %countperpack,
-                  sub { length($_[0]) ? $_[0] : 'not specified'; },
-                  sub { &sani(length($_[0]) ? $_[0] : 'not specified'); },
-                  'package',
-                  "Note that with multi-binary packages there may be other
-                                 reports filed under the different binary package names.<P>",
-                  'packages',
-                  'pack',
-                  $gPackageIndex,
-                  sub {
-                      return unless defined($maintainer{$_[0]});
-                      $tmaint= $maintainer{$_[0]};
-                      return "Maintainer for $_[0] is <A href=\"../ma/l".
-                             &maintencoded($tmaint).
-                             ".html\">".&sani($tmaint)."</A>.\n<p>\n";
-                  },
-                  sub {
-                      return unless defined($maintainer{$_[0]});
-                      $tmaint= $maintainer{$_[0]};
-                      return "<A href=\"../ma/l".
-                             &maintencoded($tmaint).
-                             ".html\">".&sani($tmaint)."</A>";
-                  });
-
-&file('ix/summary.html','non',
-      $gSummaryIndex.
-      "<hr><pre>\n".
-      $shortindex.
-      "</pre><hr>\n".
-      $tail_html."</body></html>\n");
-
-$bypackageindex='';
-for $k (map {$_->[0] }
-       sort { $a->[2] cmp $b->[2]  ||  $a->[1] <=> $b->[1] }
-       map { [$_, split(' ',$_,2)] } keys %sient)
-    { $bypackageindex.= $sient{$k}; }
-&file('ix/psummary.html','non',
-      $gPackageLog.
-      "<hr><pre>\n$shorthead\n".
-      $bypackageindex.
-      "</pre><hr>\n".
-      $tail_html."</body></html>\n");
-
-open(P,"$gPseudoDescFile") ||
-    &quit("$gPseudoDescFile: $!");
-$ppd=''; while(<P>) { s/\s*\n$//; $ppd.= &sani($_)."\n"; } close(P);
-&file('ix/pseudopackages.html','non',
-      $gPseudoIndex.
-      "<hr><pre>\n$ppd".
-      "</pre><hr>\n".
-      $tail_html."</body></html>\n");
-
-$_= $gHTMLStamp; s/SUBSTITUTE_DTIME/$dtime/o;
-
-&file('ix/zstamp.html','non',$_."</body></html>\n");
-
-sub notimestamp ($) {
-    $_= $_[0];
-    s/\<\!\-\-timestamp\-\-\>\n.*\n\<\!\-\-\/timestamp\-\-\>\n//;
-    return $_;
-}
-
-sub file {
-    local ($name,$ii,$file)= @_;
-    if ($diff) {
-        $cmppath= "$wwwbase/$name".($ii eq 'ref' ? '.ref' : '');
-        if (open(ORIG,"$cmppath")) {
-            undef $/; $orig= <ORIG>; $/= "\n";
-            close(ORIG);
-            if (&notimestamp($orig) eq &notimestamp($file)) {
-               print "preserve $name\n";
-               return;
-           }
-            defined($c= open(P,"-|")) or &quit("pipe/fork for diff: $!");
-            if (!$c) {
-                open(Q,"|diff -e $cmppath -") or die "pipe/fork II for diff: $!\n";
-                print Q $file or die "write orig to diff: $!\n";
-                close(Q); $?==0 || $?==256 or die "diff gave $?\n";
-                exit($?>>8);
-            }
-            undef $/; $difftxt= <P>; $/= "\n";
-            close(P); $?==0 || $?==256 or die "diff fork gave $?\n";
-            if ($?==0) {
-                print "preserve $name\n";
-                return;
-            }
-            $v= (split(/\n/,$difftxt));
-            print "diff $v $ii $name\n${difftxt}thatdiff $name\n"
-                or &quit("stdout (diff): $!");
-            return;
-        }
-    } 
-    $v= (split(/\n/,$file));
-    print "file $v $ii $name\n${file}thatfile $name\n" or &quit("stdout: $!");
-}
-
-sub preserve {
-    print "preserve $_[0]\n";
-}
-
-print "end\n";
-
-while ($u= $cleanups[$#cleanups]) { &$u; }
-exit 0;
diff --git a/scripts/errorlib b/scripts/errorlib
new file mode 100755 (executable)
index 0000000..a2e9016
--- /dev/null
@@ -0,0 +1,41 @@
+# -*- perl -*-
+
+use Mail::Address;
+use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522 getmailbody);
+use Debbugs::Packages qw(:all);
+use Debbugs::Common qw(:all);
+use Debbugs::Status qw(:all);
+use Carp;
+
+sub unlockreadbugmerge {
+    local ($rv) = @_;
+    &unfilelock if $rv >= 2;
+    &unfilelock if $rv >= 1;
+}
+
+%saniarray= ('<','lt', '>','gt', '&','amp', '"','quot');
+
+sub sani {
+    my ($in) = @_;
+    carp "You should be using HTML::Entities instead.";
+    $in =~ s/([<>&"])/$saniarray{$1}/g;
+    return $in;
+}
+
+sub get_addresses {
+       return
+           map { $_->address() }
+           map { Mail::Address->parse($_) } @_;
+}
+
+@severities= grep { not exists $gObsoleteSeverities{$_} } @gSeverityList;
+@showseverities= @severities;
+grep ($_= $_ eq '' ? $gDefaultSeverity : $_, @showseverities);
+%displayshowseverities= %gSeverityDisplay;
+
+# compatibility
+if (defined $gFowardList and not defined $gForwardList) {
+    $gForwardList = $gFowardList;
+}
+
+1;
diff --git a/scripts/errorlib.in b/scripts/errorlib.in
deleted file mode 100755 (executable)
index a2e9016..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- perl -*-
-
-use Mail::Address;
-use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522 getmailbody);
-use Debbugs::Packages qw(:all);
-use Debbugs::Common qw(:all);
-use Debbugs::Status qw(:all);
-use Carp;
-
-sub unlockreadbugmerge {
-    local ($rv) = @_;
-    &unfilelock if $rv >= 2;
-    &unfilelock if $rv >= 1;
-}
-
-%saniarray= ('<','lt', '>','gt', '&','amp', '"','quot');
-
-sub sani {
-    my ($in) = @_;
-    carp "You should be using HTML::Entities instead.";
-    $in =~ s/([<>&"])/$saniarray{$1}/g;
-    return $in;
-}
-
-sub get_addresses {
-       return
-           map { $_->address() }
-           map { Mail::Address->parse($_) } @_;
-}
-
-@severities= grep { not exists $gObsoleteSeverities{$_} } @gSeverityList;
-@showseverities= @severities;
-grep ($_= $_ eq '' ? $gDefaultSeverity : $_, @showseverities);
-%displayshowseverities= %gSeverityDisplay;
-
-# compatibility
-if (defined $gFowardList and not defined $gForwardList) {
-    $gForwardList = $gFowardList;
-}
-
-1;
diff --git a/scripts/expire b/scripts/expire
new file mode 100755 (executable)
index 0000000..d5149e9
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/perl
+# This script is part of debbugs, and is released
+# under the terms of the GPL version 2, or any later
+# version at your option.
+# See the file README and COPYING for more information.
+#
+# [Other people may have contributed to this file; their copyrights
+# should go here too.]
+# Copyright 2004 by Collin Watson <cjwatson@debian.org>
+# Copyright 2007 by Don Armstrong <don@donarmstrong.com>
+
+use Getopt::Long;
+use Pod::Usage;
+
+use warnings;
+use strict;
+
+=head1 NAME
+
+expire - Expires archiveable bugs by copying to archive or deleting
+
+=head1 SYNOPSIS
+
+ expire [options]
+
+ Options:
+  --debug, -d debugging level (Default 0)
+  --help, -h display this help
+  --man, -m display manual
+
+=head1 OPTIONS
+
+=over
+
+=item B<--debug, -d>
+
+Debug verbosity. (Default 0)
+
+=item B<--help, -h>
+
+Display brief useage information.
+
+=item B<--man, -m>
+
+Display this manual.
+
+=back
+
+=head1 EXAMPLES
+
+
+=cut
+
+my %options = (debug           => 0,
+              help            => 0,
+              man             => 0,
+              quick           => 0,
+              index_path      => undef,
+              );
+
+GetOptions(\%options,'debug|d+','help|h|?','man|m') or pod2usage(2);
+pod2usage(1) if $options{help};
+pod2usage(-verbose=>2) if $options{man};
+
+
+my $verbose = $options{debug};
+
+use Debbugs::Control qw(bug_archive);
+use Debbugs::Status qw(bug_archiveable);
+
+use Debbugs::Config qw(:config);
+use Debbugs::Common qw(:lock);
+
+# No $gRemoveAge means "never expire".
+exit 0 unless $config{remove_age};
+
+chdir($config{spool_dir}) || die "chdir $config{spool_dir} failed: $!\n";
+
+#get list of bugs (ie, status files)
+opendir(DIR,"db-h") or die "Unable to open dir db-h: $!";
+my @dirs = sort { $a cmp $b } grep(s,^,db-h/,, grep(m/^\d+$/,readdir(DIR)));
+close(DIR);
+my @list;
+foreach my $dir (@dirs) {
+    opendir(DIR,$dir);
+    push @list, sort { $a <=> $b } grep(s/\.summary$//,grep(m/^\d+\.summary$/,readdir(DIR)));
+    close(DIR);
+}
+
+my $bug;
+my $errors=0;
+our $exit_now = 0;
+#process each bug (ie, status file)
+my @bugs_to_archive = ();
+for my $bug (@list) {
+     # Weeeee.
+     print "Examining $bug\n" if $verbose;
+     next unless bug_archiveable(bug=>$bug);
+     push @bugs_to_archive,$bug;
+}
+
+$SIG{INT} = sub {$exit_now=1;};
+# At this point we want to block control
+if (not lockpid($config{spool_dir}.'/lock/expire.pid')) {
+     exit 1;
+}
+# We'll also double check that the bug can be archived
+for my $bug (@bugs_to_archive) {
+     last if $exit_now;
+     print "Reexamining $bug\n" if $verbose;
+     next unless bug_archiveable(bug=>$bug);
+     last if $exit_now;
+     print "Bug $bug can be archived: " if $verbose;
+     eval {
+         bug_archive(bug=>$bug,
+                    );
+         print "archived.\n" if $verbose;
+     };
+     if ($@) {
+         $errors=1;
+         print "failed.\n" if $verbose;
+         print STDERR "Unable to archive bug# $bug which I thought I could archive:\n$@\n";
+     }
+     last if $exit_now;
+}
+unlink($config{spool_dir}.'/lock/expire.pid');
+
+
+exit $errors;
diff --git a/scripts/expire.in b/scripts/expire.in
deleted file mode 100755 (executable)
index d5149e9..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/perl
-# This script is part of debbugs, and is released
-# under the terms of the GPL version 2, or any later
-# version at your option.
-# See the file README and COPYING for more information.
-#
-# [Other people may have contributed to this file; their copyrights
-# should go here too.]
-# Copyright 2004 by Collin Watson <cjwatson@debian.org>
-# Copyright 2007 by Don Armstrong <don@donarmstrong.com>
-
-use Getopt::Long;
-use Pod::Usage;
-
-use warnings;
-use strict;
-
-=head1 NAME
-
-expire - Expires archiveable bugs by copying to archive or deleting
-
-=head1 SYNOPSIS
-
- expire [options]
-
- Options:
-  --debug, -d debugging level (Default 0)
-  --help, -h display this help
-  --man, -m display manual
-
-=head1 OPTIONS
-
-=over
-
-=item B<--debug, -d>
-
-Debug verbosity. (Default 0)
-
-=item B<--help, -h>
-
-Display brief useage information.
-
-=item B<--man, -m>
-
-Display this manual.
-
-=back
-
-=head1 EXAMPLES
-
-
-=cut
-
-my %options = (debug           => 0,
-              help            => 0,
-              man             => 0,
-              quick           => 0,
-              index_path      => undef,
-              );
-
-GetOptions(\%options,'debug|d+','help|h|?','man|m') or pod2usage(2);
-pod2usage(1) if $options{help};
-pod2usage(-verbose=>2) if $options{man};
-
-
-my $verbose = $options{debug};
-
-use Debbugs::Control qw(bug_archive);
-use Debbugs::Status qw(bug_archiveable);
-
-use Debbugs::Config qw(:config);
-use Debbugs::Common qw(:lock);
-
-# No $gRemoveAge means "never expire".
-exit 0 unless $config{remove_age};
-
-chdir($config{spool_dir}) || die "chdir $config{spool_dir} failed: $!\n";
-
-#get list of bugs (ie, status files)
-opendir(DIR,"db-h") or die "Unable to open dir db-h: $!";
-my @dirs = sort { $a cmp $b } grep(s,^,db-h/,, grep(m/^\d+$/,readdir(DIR)));
-close(DIR);
-my @list;
-foreach my $dir (@dirs) {
-    opendir(DIR,$dir);
-    push @list, sort { $a <=> $b } grep(s/\.summary$//,grep(m/^\d+\.summary$/,readdir(DIR)));
-    close(DIR);
-}
-
-my $bug;
-my $errors=0;
-our $exit_now = 0;
-#process each bug (ie, status file)
-my @bugs_to_archive = ();
-for my $bug (@list) {
-     # Weeeee.
-     print "Examining $bug\n" if $verbose;
-     next unless bug_archiveable(bug=>$bug);
-     push @bugs_to_archive,$bug;
-}
-
-$SIG{INT} = sub {$exit_now=1;};
-# At this point we want to block control
-if (not lockpid($config{spool_dir}.'/lock/expire.pid')) {
-     exit 1;
-}
-# We'll also double check that the bug can be archived
-for my $bug (@bugs_to_archive) {
-     last if $exit_now;
-     print "Reexamining $bug\n" if $verbose;
-     next unless bug_archiveable(bug=>$bug);
-     last if $exit_now;
-     print "Bug $bug can be archived: " if $verbose;
-     eval {
-         bug_archive(bug=>$bug,
-                    );
-         print "archived.\n" if $verbose;
-     };
-     if ($@) {
-         $errors=1;
-         print "failed.\n" if $verbose;
-         print STDERR "Unable to archive bug# $bug which I thought I could archive:\n$@\n";
-     }
-     last if $exit_now;
-}
-unlink($config{spool_dir}.'/lock/expire.pid');
-
-
-exit $errors;
diff --git a/scripts/gen-indices b/scripts/gen-indices
new file mode 100755 (executable)
index 0000000..ca11546
--- /dev/null
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+# gen-indices generates bug index files, and is released
+# under the terms of the GPL version 2, or any later version, at your
+# option. See the file README and COPYING for more information.
+
+# Copyright (c) 2005/08/03 Anthony Towns
+# Copyright 2007, 2008 by Don Armstrong <don@donarmstrong.com>.
+
+use warnings;
+use strict;
+
+use DB_File;
+use MLDBM qw(DB_FILE Storable);
+use Fcntl qw/O_RDWR O_CREAT O_TRUNC/;
+use File::Copy;
+
+use Getopt::Long;
+use Pod::Usage;
+
+use File::stat;
+use List::Util qw(min);
+
+=head1 NAME
+
+gen-indices - Generates index files for the cgi scripts
+
+=head1 SYNOPSIS
+
+ gen-indices [options]
+
+ Options:
+  --index-path path to index location
+  --quick update changed bugs
+  --debug, -d debugging level (Default 0)
+  --help, -h display this help
+  --man, -m display manual
+
+=head1 OPTIONS
+
+=over
+
+=itme B<--quick>
+
+Only update changed bugs
+
+=item B<--debug, -d>
+
+Debug verbosity. (Default 0)
+
+=item B<--help, -h>
+
+Display brief useage information.
+
+=item B<--man, -m>
+
+Display this manual.
+
+=back
+
+=head1 EXAMPLES
+
+
+=cut
+
+# Use portable Storable images
+$MLDBM::DumpMeth=q(portable);
+
+
+my %options = (debug           => 0,
+              help            => 0,
+              man             => 0,
+              quick           => 0,
+              index_path      => undef,
+              );
+
+GetOptions(\%options,'quick!','index_path|index-path=s','debug|d+','help|h|?','man|m') or pod2usage(2);
+pod2usage(1) if $options{help};
+pod2usage(-verbose=>2) if $options{man};
+
+use Debbugs::Config qw(:config);
+use Debbugs::Common qw(getparsedaddrs getbugcomponent lockpid);
+use Debbugs::Status qw(readbug);
+use Debbugs::Log;
+
+chdir($config{spool_dir}) or die "chdir $config{spool_dir} failed: $!";
+
+my $verbose = $options{debug};
+my $indexdest = $options{index_path} || $config{spool_dir};
+
+my $initialdir = "db-h";
+my $suffix = "";
+
+if (defined $ARGV[0] and $ARGV[0] eq "archive") {
+    $initialdir = "archive";
+    $suffix = "-arc";
+}
+
+if (not lockpid($config{spool_dir}.'/lock/gen-indices')) {
+     if ($options{quick}) {
+         # If this is a quick run, just exit
+         print STDERR "Another gen-indices is running; stopping\n" if $verbose;
+         exit 0;
+     }
+     print STDERR "Another gen-indices is running; stopping\n";
+     exit 1;
+}
+
+# NB: The reverse index is special; it's used to clean up during updates to bugs
+my @indexes = ('package', 'tag', 'severity','owner','submitter-email','status','correspondent','reverse');
+my $indexes;
+my %slow_index = ();
+my %fast_index = ();
+if (not $options{quick}) {
+     # We'll trade memory for speed here if we're not doing a quick rebuild
+     for my $indexes (@indexes) {
+         $fast_index{$indexes} = {};
+     }
+     $indexes = \%fast_index;
+}
+else {
+     $indexes = \%slow_index;
+}
+my $time = undef;
+my $start_time = time;
+for my $i (@indexes) {
+       $slow_index{$i} = {};
+       if ($options{quick}) {
+            if (-e "$indexdest/by-$i${suffix}.idx") {
+                 system('cp','-a',"$indexdest/by-$i${suffix}.idx","$indexdest/by-$i${suffix}.idx.new") == 0
+                      or die "Error creating the new index";
+                 my $stat = stat("$indexdest/by-$i${suffix}.idx") or die "Unable to stat $indexdest/by-$i${suffix}.idx";
+                 $time = defined $time ? min($time,$stat->mtime) : $stat->mtime;
+            }
+            tie %{$slow_index{$i}}, MLDBM => "$indexdest/by-$i$suffix.idx.new",
+                 O_RDWR|O_CREAT, 0666
+                      or die "$0: can't create by-$i$suffix-idx.new: $!";
+       }
+       else {
+            tie %{$slow_index{$i}}, MLDBM => "$indexdest/by-$i$suffix.idx.new",
+                 O_RDWR|O_CREAT|O_TRUNC, 0666
+                      or die "$0: can't create by-$i$suffix-idx.new: $!";
+
+       }
+       $time = 0 if not defined $time;
+}
+
+sub addbugtoindex {
+     my ($index, $bug, @values) = @_;
+
+     if (exists $indexes->{reverse}{"$index $bug"}) {
+         # We do this insanity to work around a "feature" in MLDBM
+         for my $key (@{$indexes->{reverse}{"$index $bug"}}) {
+              my $temp = $indexes->{$index}{$key};
+              delete $temp->{$bug};
+              $indexes->{$index}{$key} = $temp;
+              $indexes->{$index}{"count $key"}--;
+         }
+         delete $indexes->{reverse}{"$index $bug"};
+     }
+     for my $key (@values) {
+         $indexes->{$index}->{"count $key"}++;
+         # We do this insanity to work around a "feature" in MLDBM
+         my $temp = $indexes->{$index}->{$key};
+         $temp->{$bug} = 1;
+         $indexes->{$index}->{$key} = $temp;
+     }
+     $indexes->{reverse}{"$index $bug"} = [@values];
+}
+
+sub emailfromrfc822 {
+       my $email = shift;
+       $email =~ s/\s*\(.*\)\s*//;
+       $email = $1 if ($email =~ m/<(.*)>/);
+       return $email;
+}
+
+my $cnt = 0;
+
+my @dirs = ($initialdir);
+while (my $dir = shift @dirs) {
+       printf "Doing dir %s ...\n", $dir if $verbose;
+
+       opendir(DIR, "$dir/.") or die "opendir $dir: $!";
+       my @subdirs = readdir(DIR);
+       closedir(DIR);
+
+       my @list = map { m/^(\d+)\.summary$/?($1):() } @subdirs;
+       push @dirs, map { m/^(\d+)$/ && -d "$dir/$1"?("$dir/$1"):() } @subdirs;
+
+       for my $bug (@list) {
+               print "Up to $cnt bugs...\n" if (++$cnt % 100 == 0 && $verbose);
+               my $stat = stat(getbugcomponent($bug,'summary',$initialdir));
+               if (not defined $stat) {
+                    print STDERR "Unable to stat $bug $!\n";
+                    next;
+               }
+               next if $stat->mtime < $time;
+               my $fdata = readbug($bug, $initialdir);
+               addbugtoindex("package", $bug, split /[\s,]+/, $fdata->{"package"});
+               addbugtoindex("tag", $bug, split /[\s,]+/, $fdata->{"keywords"});
+               addbugtoindex('submitter-email', $bug,
+                             map {lc($_->address)} getparsedaddrs($fdata->{originator}));
+               addbugtoindex("severity", $bug, $fdata->{"severity"});
+               addbugtoindex("owner", $bug,
+                             map {lc($_->address)} getparsedaddrs($fdata->{"owner"}));
+               # handle log entries
+               # do this in eval to avoid exploding on jacked logs
+               eval {
+                    my $log = Debbugs::Log->new(bug_num => $bug);
+                    while (my $record = $log->read_record()) {
+                         next unless $record->{type} eq 'incoming-recv';
+                         # we use a regex here, because a full mime parse will be slow.
+                         my ($from) = $record->{text} =~ /^From:\s+(.+?)^\S/ism;
+                         addbugtoindex('correspondent',$bug,
+                                       map {lc($_->address)} getparsedaddrs($from)
+                                      );
+                    }
+               };
+               if ($@) {
+                    print STDERR "Problem dealing with log of $bug: $@";
+               }
+          }
+}
+
+if (not $options{quick}) {
+     # put the fast index into the slow index
+     for my $key1 (keys %fast_index) {
+         for my $key2 (keys %{$fast_index{$key1}}) {
+              $slow_index{$key1}{$key2} = $fast_index{$key1}{$key2};
+         }
+         print "Dealt with index $key1\n" if $verbose;
+     }
+}
+
+
+for my $i (@indexes) {
+       untie %{$slow_index{$i}};
+       move("$indexdest/by-$i$suffix.idx.new", "$indexdest/by-$i$suffix.idx");
+       # We do this, because old versions of touch don't support -d '@epoch'
+       system('touch','-d',"1/1/1970 UTC + ${start_time}secs","$indexdest/by-$i$suffix.idx");
+}
+
+unlink($config{spool_dir}.'/lock/gen-indices')
diff --git a/scripts/gen-indices.in b/scripts/gen-indices.in
deleted file mode 100755 (executable)
index 11775e4..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/usr/bin/perl
-
-# Generates by-*.idx files for the CGI scripts
-# Copyright (c) 2005/08/03 Anthony Towns
-# GPL v2
-
-use DB_File;
-use MLDBM qw(DB_FILE Storable);
-use Fcntl qw/O_RDWR O_CREAT O_TRUNC/;
-use File::Copy;
-
-use Getopt::Long;
-use Pod::Usage;
-
-use warnings;
-use strict;
-
-use File::stat;
-use List::Util qw(min);
-
-=head1 NAME
-
-gen-indices - Generates index files for the cgi scripts
-
-=head1 SYNOPSIS
-
- gen-indices [options]
-
- Options:
-  --index-path path to index location
-  --quick update changed bugs
-  --debug, -d debugging level (Default 0)
-  --help, -h display this help
-  --man, -m display manual
-
-=head1 OPTIONS
-
-=over
-
-=itme B<--quick>
-
-Only update changed bugs
-
-=item B<--debug, -d>
-
-Debug verbosity. (Default 0)
-
-=item B<--help, -h>
-
-Display brief useage information.
-
-=item B<--man, -m>
-
-Display this manual.
-
-=back
-
-=head1 EXAMPLES
-
-
-=cut
-
-# Use portable Storable images
-$MLDBM::DumpMeth=q(portable);
-
-
-my %options = (debug           => 0,
-              help            => 0,
-              man             => 0,
-              quick           => 0,
-              index_path      => undef,
-              );
-
-GetOptions(\%options,'quick!','index_path|index-path=s','debug|d+','help|h|?','man|m') or pod2usage(2);
-pod2usage(1) if $options{help};
-pod2usage(-verbose=>2) if $options{man};
-
-use Debbugs::Config qw(:config);
-use Debbugs::Common qw(getparsedaddrs getbugcomponent lockpid);
-use Debbugs::Status qw(readbug);
-
-chdir($config{spool_dir}) or die "chdir $config{spool_dir} failed: $!";
-
-my $verbose = $options{debug};
-my $indexdest = $options{index_path} || $config{spool_dir};
-
-my $initialdir = "db-h";
-my $suffix = "";
-
-if (defined $ARGV[0] and $ARGV[0] eq "archive") {
-    $initialdir = "archive";
-    $suffix = "-arc";
-}
-
-if (not lockpid($config{spool_dir}.'/lock/gen-indices')) {
-     if ($options{quick}) {
-         # If this is a quick run, just exit
-         print STDERR "Another gen-indices is running; stopping\n" if $verbose;
-         exit 0;
-     }
-     print STDERR "Another gen-indices is running; stopping\n";
-     exit 1;
-}
-
-# NB: The reverse index is special; it's used to clean up during updates to bugs
-my @indexes = ('package', 'tag', 'severity','owner','submitter-email','status','reverse');
-my $indexes;
-my %slow_index = ();
-my %fast_index = ();
-if (not $options{quick}) {
-     # We'll trade memory for speed here if we're not doing a quick rebuild
-     for my $indexes (@indexes) {
-         $fast_index{$indexes} = {};
-     }
-     $indexes = \%fast_index;
-}
-else {
-     $indexes = \%slow_index;
-}
-my $time = undef;
-my $start_time = time;
-for my $i (@indexes) {
-       $slow_index{$i} = {};
-       if ($options{quick}) {
-            if (-e "$indexdest/by-$i${suffix}.idx") {
-                 system('cp','-a',"$indexdest/by-$i${suffix}.idx","$indexdest/by-$i${suffix}.idx.new") == 0
-                      or die "Error creating the new index";
-                 my $stat = stat("$indexdest/by-$i${suffix}.idx") or die "Unable to stat $indexdest/by-$i${suffix}.idx";
-                 $time = defined $time ? min($time,$stat->mtime) : $stat->mtime;
-            }
-            tie %{$slow_index{$i}}, MLDBM => "$indexdest/by-$i$suffix.idx.new",
-                 O_RDWR|O_CREAT, 0666
-                      or die "$0: can't create by-$i$suffix-idx.new: $!";
-       }
-       else {
-            tie %{$slow_index{$i}}, MLDBM => "$indexdest/by-$i$suffix.idx.new",
-                 O_RDWR|O_CREAT|O_TRUNC, 0666
-                      or die "$0: can't create by-$i$suffix-idx.new: $!";
-
-       }
-       $time = 0 if not defined $time;
-}
-
-sub addbugtoindex {
-     my ($index, $bug, @values) = @_;
-
-     if (exists $indexes->{reverse}{"$index $bug"}) {
-         # We do this insanity to work around a "feature" in MLDBM
-         for my $key (@{$indexes->{reverse}{"$index $bug"}}) {
-              my $temp = $indexes->{$index}{$key};
-              delete $temp->{$bug};
-              $indexes->{$index}{$key} = $temp;
-              $indexes->{$index}{"count $key"}--;
-         }
-         delete $indexes->{reverse}{"$index $bug"};
-     }
-     for my $key (@values) {
-         $indexes->{$index}->{"count $key"}++;
-         # We do this insanity to work around a "feature" in MLDBM
-         my $temp = $indexes->{$index}->{$key};
-         $temp->{$bug} = 1;
-         $indexes->{$index}->{$key} = $temp;
-     }
-     $indexes->{reverse}{"$index $bug"} = [@values];
-}
-
-sub emailfromrfc822 {
-       my $email = shift;
-       $email =~ s/\s*\(.*\)\s*//;
-       $email = $1 if ($email =~ m/<(.*)>/);
-       return $email;
-}
-
-my $cnt = 0;
-
-my @dirs = ($initialdir);
-while (my $dir = shift @dirs) {
-       printf "Doing dir %s ...\n", $dir if $verbose;
-
-       opendir(DIR, "$dir/.") or die "opendir $dir: $!";
-       my @subdirs = readdir(DIR);
-       closedir(DIR);
-
-       my @list = map { m/^(\d+)\.summary$/?($1):() } @subdirs;
-       push @dirs, map { m/^(\d+)$/ && -d "$dir/$1"?("$dir/$1"):() } @subdirs;
-
-       for my $bug (@list) {
-               print "Up to $cnt bugs...\n" if (++$cnt % 100 == 0 && $verbose);
-               my $stat = stat(getbugcomponent($bug,'summary',$initialdir));
-               if (not defined $stat) {
-                    print STDERR "Unable to stat $bug $!\n";
-                    next;
-               }
-               next if $stat->mtime < $time;
-               my $fdata = readbug($bug, $initialdir);
-               addbugtoindex("package", $bug, split /[\s,]+/, $fdata->{"package"});
-               addbugtoindex("tag", $bug, split /[\s,]+/, $fdata->{"keywords"});
-               addbugtoindex('submitter-email', $bug,
-                             map {lc($_->address)} getparsedaddrs($fdata->{originator}));
-               addbugtoindex("severity", $bug, $fdata->{"severity"});
-               addbugtoindex("owner", $bug,
-                             map {lc($_->address)} getparsedaddrs($fdata->{"owner"}));
-       }
-}
-
-if (not $options{quick}) {
-     # put the fast index into the slow index
-     for my $key1 (keys %fast_index) {
-         for my $key2 (keys %{$fast_index{$key1}}) {
-              $slow_index{$key1}{$key2} = $fast_index{$key1}{$key2};
-         }
-         print "Dealt with index $key1\n" if $verbose;
-     }
-}
-
-
-for my $i (@indexes) {
-       untie %{$slow_index{$i}};
-       move("$indexdest/by-$i$suffix.idx.new", "$indexdest/by-$i$suffix.idx");
-       # We do this, because old versions of touch don't support -d '@epoch'
-       system('touch','-d',"1/1/1970 UTC + ${start_time}secs","$indexdest/by-$i$suffix.idx");
-}
-
-unlink($config{spool_dir}.'/lock/gen-indices')
diff --git a/scripts/html-control b/scripts/html-control
new file mode 100755 (executable)
index 0000000..f3901df
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+# $Id: html-control.in,v 1.12 2004/10/26 14:00:05 cjwatson Exp $
+
+use POSIX qw(strftime tzset ENOENT);
+$ENV{"TZ"} = 'UTC';
+tzset();
+
+$config_path = '/etc/debbugs';
+$lib_path = '/usr/lib/debbugs';
+
+require("$config_path/config");
+require("$lib_path/errorlib");
+$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
+
+chdir("$gSpoolDir") || die "chdir spool: $!\n";
+#push(@INC,"$lib_path");
+
+&filelock("html.fcntl-lock");
+
+unlink("html-data.gz") || $!==&ENOENT or die "remove html-data.gz: $!";
+
+sub nonawful ($) {
+    rename("stamp.html.run","stamp.html") or warn "warning: put back stamp.html: $!";
+    die $_[0];
+}
+
+if (open(US,'updateseqs') && -f 'stamp.html') {
+    chop($lastmain=<US>);
+    chop($lastsub=<US>);
+    close(US);
+
+    $lastsub++;
+    $args= "-diff -stampfile=stamp.html.run";
+    rename("stamp.html","stamp.html.run") or die "rename stamp.html: $!";
+} else {
+    $lastsub=0;
+    $lastmain = strftime "%Y%m%d%H%M%S", localtime;
+    $args= '-full';
+    unlink('stamp.html') || $!==&ENOENT or die "excise stale stamp.html: $!";
+}
+
+open(X,">stamp.html.new") or die "stamp.html.new: $!";
+close(X) or die "close stamp.html.new: $!";
+
+open(US,'>updateseqs.new') || die "create updateseqs.new: $!";
+print(US "$lastmain\n$lastsub\n") || die "write updateseqs.new: $!";
+close(US) || die "close updateseqs.new: $!";
+rename('updateseqs.new','updateseqs') or nonawful("install updateseqs: $!");
+
+sub runshell ($&) {
+    my ($cmd,$errhref) = @_;
+    print "xx $cmd\n";
+    system $cmd;
+    !$? && !length($stderr) or &$errhref("$cmd failed - gave $? / $stderr");
+}
+
+$sequences="$lastmain $lastsub";
+$seqmid= $sequences; $seqmid =~ y/ /-/;
+open(MM,">html-data.mail") or nonawful("open html-data.mail: $!");
+if ( length( $gListDomain ) > 0 && length( $gMirrorList ) > 0 ) {
+print(MM <<END
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $gMirrorList\@$gListDomain
+Subject: $gProject $gBugs autoupdate 259012
+Message-ID: <handle.htmlup.$seqmid\@$gEmailDomain>
+X-$gProject-PR: update $sequences
+
+END
+      ) or nonawful("write html-data.mail header: $!");
+} else {
+print(MM <<END
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $gMaintainerEmail
+Subject: $gProject $gBugs autoupdate 259012
+Message-ID: <handle.htmlup.$seqmid\@$gEmailDomain>
+X-$gProject-PR: update $sequences
+
+END
+      ) or nonawful("write html-data.mail header: $!");
+}
+close(MM) or nonawful("close html-data.mail: $!");
+
+runshell("$lib_path/db2html $args 2>&1 >html-data $gWebDir/db",
+         sub { &nonawful; });
+runshell("$lib_path/html-install $gWebDir/db <html-data 2>&1",sub { &quit; });
+#runshell("gzip -9 html-data 2>&1",sub { &quit; });
+#runshell("btoa 2>&1 <html-data.gz >>html-data.mail",sub { &quit; });
+#runshell('2>&1 '.join(' ',('/usr/lib/sendmail','-f'."$gMaintainerEmail")).' -oem -oi -t <html-data.mail',
+#         sub { &quit; });
+
+rename("stamp.html.new","stamp.html") or die "install new stamp.html: $!";
+
+unlink("html-data") or warn "remove html-data: $!";
+#unlink("html-data.gz") or warn "remove html-data.gz: $!";
+#unlink("html-data.mail") or warn "remove html-data.mail: $!";
+unlink("stamp.html.run") || $!==&ENOENT or warn "remove stamp.html.run: $!";
+
+print "sequences $lastmain $lastsub\n";
+
+&unfilelock();
+exit(0);
diff --git a/scripts/html-control.in b/scripts/html-control.in
deleted file mode 100755 (executable)
index 5dd8e0d..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/perl
-# $Id: html-control.in,v 1.12 2004/10/26 14:00:05 cjwatson Exp $
-
-use POSIX qw(strftime tzset ENOENT);
-$ENV{"TZ"} = 'UTC';
-tzset();
-
-$config_path = '/etc/debbugs';
-$lib_path = '/usr/lib/debbugs';
-
-require("$config_path/config");
-require("$lib_path/errorlib");
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-chdir("$gSpoolDir") || die "chdir spool: $!\n";
-#push(@INC,"$lib_path");
-
-&filelock("html.fcntl-lock");
-
-unlink("html-data.gz") || $!==&ENOENT or &quit("remove html-data.gz: $!");
-
-sub nonawful ($) {
-    rename("stamp.html.run","stamp.html") or warn "warning: put back stamp.html: $!";
-    &quit($_[0]);
-}
-
-if (open(US,'updateseqs') && -f 'stamp.html') {
-    chop($lastmain=<US>);
-    chop($lastsub=<US>);
-    close(US);
-
-    $lastsub++;
-    $args= "-diff -stampfile=stamp.html.run";
-    rename("stamp.html","stamp.html.run") or &quit("rename stamp.html: $!");
-} else {
-    $lastsub=0;
-    $lastmain = strftime "%Y%m%d%H%M%S", localtime;
-    $args= '-full';
-    unlink('stamp.html') || $!==&ENOENT or &quit("excise stale stamp.html: $!");
-}
-
-open(X,">stamp.html.new") or &quit("stamp.html.new: $!");
-close(X) or &quit("close stamp.html.new: $!");
-
-open(US,'>updateseqs.new') || &quit("create updateseqs.new: $!");
-print(US "$lastmain\n$lastsub\n") || &quit("write updateseqs.new: $!");
-close(US) || &quit("close updateseqs.new: $!");
-rename('updateseqs.new','updateseqs') or nonawful("install updateseqs: $!");
-
-sub runshell ($&) {
-    my ($cmd,$errhref) = @_;
-    print "xx $cmd\n";
-    system $cmd;
-    !$? && !length($stderr) or &$errhref("$cmd failed - gave $? / $stderr");
-}
-
-$sequences="$lastmain $lastsub";
-$seqmid= $sequences; $seqmid =~ y/ /-/;
-open(MM,">html-data.mail") or nonawful("open html-data.mail: $!");
-if ( length( $gListDomain ) > 0 && length( $gMirrorList ) > 0 ) {
-print(MM <<END
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $gMirrorList\@$gListDomain
-Subject: $gProject $gBugs autoupdate 259012
-Message-ID: <handle.htmlup.$seqmid\@$gEmailDomain>
-X-$gProject-PR: update $sequences
-
-END
-      ) or nonawful("write html-data.mail header: $!");
-} else {
-print(MM <<END
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $gMaintainerEmail
-Subject: $gProject $gBugs autoupdate 259012
-Message-ID: <handle.htmlup.$seqmid\@$gEmailDomain>
-X-$gProject-PR: update $sequences
-
-END
-      ) or nonawful("write html-data.mail header: $!");
-}
-close(MM) or nonawful("close html-data.mail: $!");
-
-runshell("$lib_path/db2html $args 2>&1 >html-data $gWebDir/db",
-         sub { &nonawful; });
-runshell("$lib_path/html-install $gWebDir/db <html-data 2>&1",sub { &quit; });
-#runshell("gzip -9 html-data 2>&1",sub { &quit; });
-#runshell("btoa 2>&1 <html-data.gz >>html-data.mail",sub { &quit; });
-#runshell('2>&1 '.join(' ',('/usr/lib/sendmail','-f'."$gMaintainerEmail")).' -oem -oi -t <html-data.mail',
-#         sub { &quit; });
-
-rename("stamp.html.new","stamp.html") or &quit("install new stamp.html: $!");
-
-unlink("html-data") or warn "remove html-data: $!";
-#unlink("html-data.gz") or warn "remove html-data.gz: $!";
-#unlink("html-data.mail") or warn "remove html-data.mail: $!";
-unlink("stamp.html.run") || $!==&ENOENT or warn "remove stamp.html.run: $!";
-
-print "sequences $lastmain $lastsub\n";
-
-&unfilelock();
-exit(0);
diff --git a/scripts/html-install b/scripts/html-install
new file mode 100755 (executable)
index 0000000..bb6b04d
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/perl
+# $Id: html-install.in,v 1.4 2002/11/17 22:45:16 cjwatson Exp $
+# Takes 1 argument - directory tree to install into
+# Tree _must_ be synch'd with one used by db2html to generate file
+
+use POSIX;
+$config_path = '/etc/debbugs';
+
+require("$config_path/config");
+$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
+
+$dirtree= shift(@ARGV);
+defined($dirtree) or die 'usage';
+chdir $dirtree or die $!;
+
+$filenamere= '[0-9a-z]{2}/[0-9a-z][-+_:,.0-9a-zA-Z]*';
+
+opendir(D,".") or die " opendir: $!";
+while ($dir=readdir(D)) {
+    next if $dir =~ m/^\.\.?$/;
+    if (-f $dir) {
+        $remove{$dir}= 1;
+    } else {
+        opendir(E,"$dir") or die " opendir $dir: $!";
+        while ($_=readdir(E)) {
+            next if $_ =~ m/^\.\.?$/;
+            $remove{"$dir/$_"}= 1;
+        }
+        closedir(E) or die " closedir $dir: $!";
+        $rmdir{$dir}= 1;
+    }
+}
+closedir(D) or die " closedir: $!";
+
+while(<>) {
+    chomp;
+    if (m/^end$/) {
+       print "end, removing\n";
+        for $k (keys %remove) { unlink($k) || $!==&ENOENT or die "$k: $!"; }
+        for $k (keys %rmdir) { rmdir($k) || $!==&ENOTEMPTY || $!==EEXIST or die "$k: $!"; }
+        exit 0;
+    } elsif (s/^progress //) {
+        y/-+:._!#=,0-9a-zA-Z //cd;
+        print " progress $_\n";
+    } elsif (m/^preserve ($filenamere)$/o) {
+        delete $remove{$1};
+        delete $remove{"$1.ref"};
+        print " preserve $1\n";
+    } elsif (m/^(file|diff) (\d+) (ref|def|non) ($filenamere)$/o) {
+        $filediff= $1; $linestodo= $2; $ii= $3; $file= $4;
+        print " $filediff $ii $file\n";
+        delete $remove{$file};
+        delete $remove{"$file.ref"} if $ii eq 'ref';
+        $file =~ m,^(..)/, or die $file;
+        mkdir($1,0777) || $!==EEXIST or die $!;
+        $tranfile= $file;
+        $tranfile.= '.ref' if $ii eq 'ref';
+        open(DT,"> recv.tmp") or die $!;
+        if ($filediff eq 'diff') { print DT "r $tranfile\n" or die $!; }
+        $indata= 0;
+        while ($linestodo--) {
+            $z=<STDIN>;
+            if ($filediff eq 'diff') {
+                if ($indata) { $indata=0 if $incmd && m/^\.$/; }
+                elsif ($z =~ m/^[0-9,]+[ac]/) { $indata= 1; }
+                elsif ($z !~ m/^[0-9,]+[ds]/) { die "SECURITY $file >$z<"; }
+            }
+            print DT $z or die $!;
+        }
+        if ($filediff eq 'diff') { print DT "w new.tmp\nq\n" or die $!; }
+        close(DT) or die $!;
+        ($z=<STDIN>) eq "that$filediff $file\n" or die die "not confirmed >$z<";
+        if ($filediff eq 'diff') {
+            $q= `ed -s <recv.tmp 2>&1`;
+            length($q) || $? and die "ed $q $?";
+            rename("new.tmp","$tranfile") or die "$tranfile $!";
+            unlink("recv.tmp") or die $!;
+        } else {
+            rename("recv.tmp","$tranfile") or die "$tranfile $!";
+        }
+        if ($ii eq 'ref') {
+            open(I,"$tranfile") or die $!;
+            open(O,"> ref.tmp") or die $!;
+            while (<I>) {
+                if (m/^\<\!\-\-ii (\d+)\-\-\>$/) {
+                    defined($iival{$1}) or die "$tranfile $1";
+                    print O $iival{$1} or die $!;
+                } else {
+                    print O or die $!;
+                }
+            }
+            close(I) or die $!;
+            close(O) or die $!;
+            rename("ref.tmp","$file") or die $!;
+        } elsif ($ii eq 'def') {
+            open(I,"$file") or die $!;
+            undef $cdef; $ctext= '';
+            while (<I>) {
+                if (s/^\<\!\-\-iid (\d+)\-\-\>//) {
+                    defined($cdef) and die $file;
+                    $cdef= $1;
+                    $ctext= $_;
+                } elsif (s/\<\!\-\-\/iid\-\-\>\n$//) {
+                    defined($cdef) or die $file;
+                    $iival{$cdef}= $ctext.$_."\n";
+                    $ctext=''; undef $cdef;
+                } else {
+                    $ctext.= $_ if defined($cdef);
+                }
+            }
+        }
+    } elsif (m/^noremoves$/) {
+       print "noremoves\n";
+        exit 0;
+    } else {
+        die " huh ? $_";
+    }
+}
+
+die "eof $!";
diff --git a/scripts/html-install.in b/scripts/html-install.in
deleted file mode 100755 (executable)
index bb6b04d..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/perl
-# $Id: html-install.in,v 1.4 2002/11/17 22:45:16 cjwatson Exp $
-# Takes 1 argument - directory tree to install into
-# Tree _must_ be synch'd with one used by db2html to generate file
-
-use POSIX;
-$config_path = '/etc/debbugs';
-
-require("$config_path/config");
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-$dirtree= shift(@ARGV);
-defined($dirtree) or die 'usage';
-chdir $dirtree or die $!;
-
-$filenamere= '[0-9a-z]{2}/[0-9a-z][-+_:,.0-9a-zA-Z]*';
-
-opendir(D,".") or die " opendir: $!";
-while ($dir=readdir(D)) {
-    next if $dir =~ m/^\.\.?$/;
-    if (-f $dir) {
-        $remove{$dir}= 1;
-    } else {
-        opendir(E,"$dir") or die " opendir $dir: $!";
-        while ($_=readdir(E)) {
-            next if $_ =~ m/^\.\.?$/;
-            $remove{"$dir/$_"}= 1;
-        }
-        closedir(E) or die " closedir $dir: $!";
-        $rmdir{$dir}= 1;
-    }
-}
-closedir(D) or die " closedir: $!";
-
-while(<>) {
-    chomp;
-    if (m/^end$/) {
-       print "end, removing\n";
-        for $k (keys %remove) { unlink($k) || $!==&ENOENT or die "$k: $!"; }
-        for $k (keys %rmdir) { rmdir($k) || $!==&ENOTEMPTY || $!==EEXIST or die "$k: $!"; }
-        exit 0;
-    } elsif (s/^progress //) {
-        y/-+:._!#=,0-9a-zA-Z //cd;
-        print " progress $_\n";
-    } elsif (m/^preserve ($filenamere)$/o) {
-        delete $remove{$1};
-        delete $remove{"$1.ref"};
-        print " preserve $1\n";
-    } elsif (m/^(file|diff) (\d+) (ref|def|non) ($filenamere)$/o) {
-        $filediff= $1; $linestodo= $2; $ii= $3; $file= $4;
-        print " $filediff $ii $file\n";
-        delete $remove{$file};
-        delete $remove{"$file.ref"} if $ii eq 'ref';
-        $file =~ m,^(..)/, or die $file;
-        mkdir($1,0777) || $!==EEXIST or die $!;
-        $tranfile= $file;
-        $tranfile.= '.ref' if $ii eq 'ref';
-        open(DT,"> recv.tmp") or die $!;
-        if ($filediff eq 'diff') { print DT "r $tranfile\n" or die $!; }
-        $indata= 0;
-        while ($linestodo--) {
-            $z=<STDIN>;
-            if ($filediff eq 'diff') {
-                if ($indata) { $indata=0 if $incmd && m/^\.$/; }
-                elsif ($z =~ m/^[0-9,]+[ac]/) { $indata= 1; }
-                elsif ($z !~ m/^[0-9,]+[ds]/) { die "SECURITY $file >$z<"; }
-            }
-            print DT $z or die $!;
-        }
-        if ($filediff eq 'diff') { print DT "w new.tmp\nq\n" or die $!; }
-        close(DT) or die $!;
-        ($z=<STDIN>) eq "that$filediff $file\n" or die die "not confirmed >$z<";
-        if ($filediff eq 'diff') {
-            $q= `ed -s <recv.tmp 2>&1`;
-            length($q) || $? and die "ed $q $?";
-            rename("new.tmp","$tranfile") or die "$tranfile $!";
-            unlink("recv.tmp") or die $!;
-        } else {
-            rename("recv.tmp","$tranfile") or die "$tranfile $!";
-        }
-        if ($ii eq 'ref') {
-            open(I,"$tranfile") or die $!;
-            open(O,"> ref.tmp") or die $!;
-            while (<I>) {
-                if (m/^\<\!\-\-ii (\d+)\-\-\>$/) {
-                    defined($iival{$1}) or die "$tranfile $1";
-                    print O $iival{$1} or die $!;
-                } else {
-                    print O or die $!;
-                }
-            }
-            close(I) or die $!;
-            close(O) or die $!;
-            rename("ref.tmp","$file") or die $!;
-        } elsif ($ii eq 'def') {
-            open(I,"$file") or die $!;
-            undef $cdef; $ctext= '';
-            while (<I>) {
-                if (s/^\<\!\-\-iid (\d+)\-\-\>//) {
-                    defined($cdef) and die $file;
-                    $cdef= $1;
-                    $ctext= $_;
-                } elsif (s/\<\!\-\-\/iid\-\-\>\n$//) {
-                    defined($cdef) or die $file;
-                    $iival{$cdef}= $ctext.$_."\n";
-                    $ctext=''; undef $cdef;
-                } else {
-                    $ctext.= $_ if defined($cdef);
-                }
-            }
-        }
-    } elsif (m/^noremoves$/) {
-       print "noremoves\n";
-        exit 0;
-    } else {
-        die " huh ? $_";
-    }
-}
-
-die "eof $!";
diff --git a/scripts/mailsummary b/scripts/mailsummary
new file mode 100755 (executable)
index 0000000..1ed2e0b
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+# $Id: mailsummary.in,v 1.11 2003/04/28 23:51:15 cjwatson Exp $
+
+$config_path = '/etc/debbugs';
+$lib_path = '/usr/lib/debbugs';
+
+require("$config_path/config");
+require("$lib_path/errorlib");
+$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
+
+chdir("$gSpoolDir") || die "chdir spool: $!\n";
+
+#open(DEBUG,">&4");
+
+if ($ARGV[0] eq 'undone') {
+    $vdef= "(no outstanding $gBug reports on file, or problem running script)\n";
+    $subject= "Unanswered problem reports by date";
+    $intro=
+"The following problem reports have not yet been marked as `taken up\' by a
+message to done\@$gEmailDomain or or `forwarded\' by a
+message to forwarded\@$gEmailDomain."
+    ;
+} elsif ($ARGV[0] eq 'bymaint') {
+    $vdef= "(no outstanding $gBug reports on file, or problem running script)\n";
+    $subject= "Unanswered problem reports by maintainer and package";
+    $intro=
+"The following problem reports have not yet been marked as `taken up\' by a
+message to done\@$gEmailDomain or or `forwarded\' by a
+message to forwarded\@$gEmailDomain.
+The maintainer listed against each package is derived from the Maintainer
+field of the package found in the development tree; there is an override file
+that can be amended to get the right results if you have taken over a package
+and do not expect to issue a new version soon.
+
+Variant versions of the Maintainer field for the same actual package
+maintainer will be listed separately.
+
+Maintainers with few outstanding $gBugs appear first, to avoid those with few
+$gBugs being lost deep in the message.
+"
+    ;
+} elsif ($ARGV[0] eq 'veryold') {
+    $vdef= '';
+    $subject= "Overdue problem reports by age";
+    $intro=
+"The following problem reports are very old but have not yet been marked
+as `taken up\' by a message to done\@$gEmailDomain as forwarded
+to a developer by CCing a message to forwarded\@$gEmailDomain.
+Please help ensure that these $gBugs are dealt with quickly, even if you
+are not the package maintainer in question.  (NB a full list of outstanding
+$gBug reports is posted periodically - this is a partial list only!)
+"
+} else {
+    die "urgk, wrong argument @ARGV";
+}
+
+$v=`$lib_path/summary $ARGV[0]`; $? && die "undone failed $?: $!\n";
+
+$v= $vdef if $v eq '';
+exit 0 if $v eq '';
+
+open(D, '| '.join(' ',('/usr/lib/sendmail','-f'.$gMaintainerEmail)).' -odq -oem -oi -t') ||
+    die "start sendmail: $!";
+
+print D <<END || die "complete sendmail";
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $gSummaryList\@$gListDomain
+Subject: $subject
+
+$intro
+$v
+Every Tuesday, the listing by package maintainer is posted.
+Every Friday, the listing by age of the report is posted.
+
+Please see the documentation for more information about how to
+use the $gBug tracking system.  It is available on the WWW at
+<A HREF=\"http://$gWebDomain/txt/\">$gWebDomain/txt</A>
+END
+
+close(D);
+$? && die "sendmail failed $?: $!\n";
+
+print length($v)," bytes of summary posted.\n";
diff --git a/scripts/mailsummary.in b/scripts/mailsummary.in
deleted file mode 100755 (executable)
index 1ed2e0b..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/perl
-# $Id: mailsummary.in,v 1.11 2003/04/28 23:51:15 cjwatson Exp $
-
-$config_path = '/etc/debbugs';
-$lib_path = '/usr/lib/debbugs';
-
-require("$config_path/config");
-require("$lib_path/errorlib");
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-chdir("$gSpoolDir") || die "chdir spool: $!\n";
-
-#open(DEBUG,">&4");
-
-if ($ARGV[0] eq 'undone') {
-    $vdef= "(no outstanding $gBug reports on file, or problem running script)\n";
-    $subject= "Unanswered problem reports by date";
-    $intro=
-"The following problem reports have not yet been marked as `taken up\' by a
-message to done\@$gEmailDomain or or `forwarded\' by a
-message to forwarded\@$gEmailDomain."
-    ;
-} elsif ($ARGV[0] eq 'bymaint') {
-    $vdef= "(no outstanding $gBug reports on file, or problem running script)\n";
-    $subject= "Unanswered problem reports by maintainer and package";
-    $intro=
-"The following problem reports have not yet been marked as `taken up\' by a
-message to done\@$gEmailDomain or or `forwarded\' by a
-message to forwarded\@$gEmailDomain.
-The maintainer listed against each package is derived from the Maintainer
-field of the package found in the development tree; there is an override file
-that can be amended to get the right results if you have taken over a package
-and do not expect to issue a new version soon.
-
-Variant versions of the Maintainer field for the same actual package
-maintainer will be listed separately.
-
-Maintainers with few outstanding $gBugs appear first, to avoid those with few
-$gBugs being lost deep in the message.
-"
-    ;
-} elsif ($ARGV[0] eq 'veryold') {
-    $vdef= '';
-    $subject= "Overdue problem reports by age";
-    $intro=
-"The following problem reports are very old but have not yet been marked
-as `taken up\' by a message to done\@$gEmailDomain as forwarded
-to a developer by CCing a message to forwarded\@$gEmailDomain.
-Please help ensure that these $gBugs are dealt with quickly, even if you
-are not the package maintainer in question.  (NB a full list of outstanding
-$gBug reports is posted periodically - this is a partial list only!)
-"
-} else {
-    die "urgk, wrong argument @ARGV";
-}
-
-$v=`$lib_path/summary $ARGV[0]`; $? && die "undone failed $?: $!\n";
-
-$v= $vdef if $v eq '';
-exit 0 if $v eq '';
-
-open(D, '| '.join(' ',('/usr/lib/sendmail','-f'.$gMaintainerEmail)).' -odq -oem -oi -t') ||
-    die "start sendmail: $!";
-
-print D <<END || die "complete sendmail";
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $gSummaryList\@$gListDomain
-Subject: $subject
-
-$intro
-$v
-Every Tuesday, the listing by package maintainer is posted.
-Every Friday, the listing by age of the report is posted.
-
-Please see the documentation for more information about how to
-use the $gBug tracking system.  It is available on the WWW at
-<A HREF=\"http://$gWebDomain/txt/\">$gWebDomain/txt</A>
-END
-
-close(D);
-$? && die "sendmail failed $?: $!\n";
-
-print length($v)," bytes of summary posted.\n";
diff --git a/scripts/process b/scripts/process
new file mode 100755 (executable)
index 0000000..197ae8d
--- /dev/null
@@ -0,0 +1,1171 @@
+#!/usr/bin/perl
+# $Id: process.in,v 1.109 2006/02/09 22:02:04 don Exp $
+#
+# Usage: process nn
+# Temps:  incoming/Pnn
+
+use warnings;
+use strict;
+
+use POSIX qw(strftime);
+
+use IO::File;
+
+use MIME::Parser;
+use Debbugs::MIME qw(decode_rfc1522 create_mime_message getmailbody);
+use Debbugs::Mail qw(send_mail_message encode_headers);
+use Debbugs::Packages qw(getpkgsrc);
+use Debbugs::User qw(read_usertags write_usertags);
+use Debbugs::Common qw(:lock get_hashname);
+use Debbugs::Status qw(writebug isstrongseverity lockreadbugmerge lockreadbug);
+
+use Debbugs::CGI qw(html_escape bug_url);
+
+use Debbugs::Log qw(:misc);
+
+use Debbugs::Text qw(:templates);
+
+use Debbugs::Status qw(:versions);
+use Debbugs::Config qw(:globals :config);
+
+chdir( "$gSpoolDir" ) || die "chdir spool: $!\n";
+
+#open(DEBUG,"> /tmp/debbugs.debug");
+umask(002);
+open DEBUG, ">/dev/null";
+
+my $intdate = time or die "failed to get time: $!";
+
+$_=shift;
+m/^([BMQFDUL])(\d*)\.\d+$/ or die "bad argument: $_";
+my $codeletter= $1;
+my $tryref= length($2) ? $2 : -1;
+my $nn= $_;
+
+if (!rename("incoming/G$nn","incoming/P$nn")) 
+{
+    $_=$!.'';  m/no such file or directory/i && exit 0;
+    die "renaming to lock: $!";
+}
+
+my $baddress= 'submit' if $codeletter eq 'B';
+$baddress= 'maintonly' if $codeletter eq 'M';
+$baddress= 'quiet' if $codeletter eq 'Q';
+$baddress= 'forwarded' if $codeletter eq 'F';
+$baddress= 'done' if $codeletter eq 'D';
+$baddress= 'submitter' if $codeletter eq 'U';
+bug_list_forward($nn) if $codeletter eq 'L';
+$baddress || die "bad codeletter $codeletter";
+my $baddressroot= $baddress;
+$baddress= "$tryref-$baddress" if $tryref>=0;
+
+open(M,"incoming/P$nn");
+my @log=<M>;
+close(M);
+
+my @msg = @log;
+chomp @msg;
+
+print DEBUG "###\n",join("##\n",@msg),"\n###\n";
+
+my $tdate = strftime "%a, %d %h %Y %T +0000", gmtime;
+my $fwd= <<END;
+Received: via spool by $baddress\@$gEmailDomain id=$nn
+          (code $codeletter ref $tryref); $tdate
+END
+
+# header and decoded body respectively
+my (@headerlines, @bodylines);
+
+# whether maintainer addresses have been checked
+our $maintainerschecked = 0;
+#maintainer address for this message
+our @maintaddrs;
+# other src addresses
+our @addsrcaddrs;
+our @resentccs;
+our @bccs;
+
+my $resentccexplain='';
+
+# whether there's a new reference with this email
+our $newref = 0;
+
+our $brokenness = '';
+
+my $parser = new MIME::Parser;
+mkdir "$gSpoolDir/mime.tmp", 0777;
+$parser->output_under("$gSpoolDir/mime.tmp");
+my $entity = eval { $parser->parse_data(join('',@log)) };
+
+my $i;
+if ($entity and $entity->head->tags) {
+    @headerlines = @{$entity->head->header};
+    chomp @headerlines;
+
+    my $entity_body = getmailbody($entity);
+    @bodylines = map {s/\r?\n$//; $_;}
+        $entity_body ? $entity_body->as_lines() : ();
+
+    # set $i to beginning of encoded body data, so we can dump it out
+    # verbatim later
+    $i = 0;
+    ++$i while $msg[$i] =~ /./;
+} else {
+    # Legacy pre-MIME code, kept around in case MIME::Parser fails.
+    for ($i = 0; $i <= $#msg; $i++) {
+       $_ = $msg[$i];
+       last unless length($_);
+       while ($msg[$i+1] =~ m/^\s/) {
+           $i++;
+           $_ .= "\n".$msg[$i];
+       }
+       push @headerlines, $_;
+    }
+
+    @bodylines = @msg[$i..$#msg];
+}
+
+my %header;
+
+for my $hdr (@headerlines) {
+    $hdr = decode_rfc1522($hdr);
+    $_ = $hdr;
+    s/\n\s/ /g;
+    &finish if m/^x-loop: (\S+)$/i && $1 eq "$gMaintainerEmail";
+    my $ins = !m/^subject:/i && !m/^reply-to:/i && !m/^return-path:/i
+           && !m/^From / && !m/^X-Debbugs-/i;
+    $fwd .= $hdr."\n" if $ins;
+    # print DEBUG ">$_<\n";
+    if (s/^(\S+):\s*//) {
+       my $v = lc $1;
+       print DEBUG ">$v=$_<\n";
+       $header{$v} = $_;
+    } else {
+       print DEBUG "!>$_<\n";
+    }
+}
+$header{'message-id'} = '' if not defined $header{'message-id'};
+
+# remove blank lines
+shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
+
+# Strip off RFC2440-style PGP clearsigning.
+if (@bodylines and $bodylines[0] =~ /^-----BEGIN PGP SIGNED/) {
+    shift @bodylines while @bodylines and length $bodylines[0];
+    shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
+    for my $findsig (0 .. $#bodylines) {
+       if ($bodylines[$findsig] =~ /^-----BEGIN PGP SIGNATURE/) {
+           $#bodylines = $findsig - 1;
+           last;
+       }
+    }
+    map { s/^- // } @bodylines;
+}
+
+#psuedoheaders
+my %pheader;
+# extract pseudo-headers
+for my $phline (@bodylines)
+{
+    last if $phline !~ m/^([\w-]+):\s*(\S.*)/;
+    my ($fn, $fv) = ($1, $2);
+    $fv =~ s/\s*$//;
+    print DEBUG ">$fn|$fv|\n";
+    $fn = lc $fn;
+    # Don't lc owner or forwarded
+    $fv = lc $fv unless $fn =~ /^(?:owner|forwarded|usertags|version|source-version)$/;
+    $pheader{$fn} = $fv;
+    print DEBUG ">$fn~$fv<\n";
+}
+
+# Allow pseudo headers to set x-debbugs- stuff [#179340]
+for my $key (grep /X-Debbugs-.*/i, keys %pheader) {
+     $header{$key} = $pheader{$key} if not exists $header{$key};
+}
+
+$fwd .= join("\n",@msg[$i..$#msg]);
+
+print DEBUG "***\n$fwd\n***\n";
+
+if (defined $header{'resent-from'} && !defined $header{'from'}) {
+    $header{'from'} = $header{'resent-from'};
+}
+defined($header{'from'}) || die "no From header";
+
+my $replyto = $header{'reply-to'};
+$replyto = '' unless defined $replyto;
+$replyto =~ s/^ +//;
+$replyto =~ s/ +$//;
+unless (length $replyto) {
+    $replyto = $header{'from'};
+}
+
+my $subject = '(no subject)';
+if (!defined($header{'subject'})) 
+{
+       $brokenness.= fill_template('mail/process_broken_subject');
+
+} else { 
+    $subject= $header{'subject'}; 
+}
+
+my $ref=-1;
+$subject =~ s/^Re:\s*//i; $_= $subject."\n";
+if ($tryref < 0 && m/^Bug ?\#(\d+)\D/i) {
+    $tryref= $1+0; 
+}
+my $data;
+if ($tryref >= 0) 
+{
+     my $bfound;
+    ($bfound, $data)= &lockreadbugmerge($tryref);
+    if ($bfound) { 
+        $ref= $tryref; 
+    } else {
+        &htmllog("Reply","sent", $replyto,"Unknown problem report number <code>$tryref</code>.");
+        &sendmessage(create_mime_message(
+          [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+          To            => $replyto,
+          Subject       => "Unknown problem report $gBug#$tryref ($subject)",
+          'Message-ID'  => "<handler.x.$nn.unknown\@$gEmailDomain>",
+          'In-Reply-To' => $header{'message-id'},
+          References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+          Precedence    => 'bulk',
+          "X-$gProject-PR-Message" => 'error',
+         ],message_body_template('process_unknown_bug_number',
+                                 {subject => $subject,
+                                  date    => $header{date},
+                                  baddress => $baddress,
+                                  tryref   => $tryref,
+                                  messageid => $header{'message-id'},
+                                 },
+                                )),'');
+        &appendlog;
+        &finish;
+    }
+} else { 
+    &filelock('lock/-1'); 
+}
+
+# Attempt to determine which source package this is
+my $source_pr_header = '';
+my $source_package = '';
+if (defined $pheader{source}) {
+     $source_package = $pheader{source};
+}
+elsif (defined $data->{package} or defined $pheader{package}) {
+     my $pkg_src = getpkgsrc();
+     $source_package = $pkg_src->{defined $data->{package}?$data->{package}:$pheader{package}};
+}
+$source_pr_header = "X-$gProject-PR-Source: $source_package\n"
+     if defined $source_package and length $source_package;
+
+# Done and Forwarded Bugs
+if ($codeletter eq 'D' || $codeletter eq 'F') 
+{
+    if ($replyto =~ m/$gBounceFroms/o ||
+        $header{'from'} =~ m/$gBounceFroms/o)
+    {
+        print STDERR "bounce detected !  Mwaap! Mwaap!";
+        exit 1;
+    }
+    my $markedby= $header{'from'} eq $replyto ? $replyto :
+               "$header{'from'} (reply to $replyto)";
+    my @generalcc;
+    my $receivedat;
+    my $markaswhat;
+    my $set_forwarded;
+    my $generalcc;
+    my $set_done;
+    if ($codeletter eq 'F') { # Forwarded
+        (&appendlog,&finish) if defined $data->{forwarded} and length($data->{forwarded});
+        $receivedat= "forwarded\@$gEmailDomain";
+        $markaswhat= 'forwarded';
+        $set_forwarded= $header{'to'};
+       # Dissallow forwarded being set to this bug tracking system
+       if (defined $set_forwarded and $set_forwarded =~ /\Q$gEmailDomain\E/) {
+            undef $set_forwarded;
+       }
+       if ( length( $gListDomain ) > 0 && length( $gForwardList ) > 0 ) {
+           push @generalcc, "$gForwardList\@$gListDomain";
+           $generalcc= "$gForwardList\@$gListDomain";
+       } else { 
+           $generalcc=''; 
+        }
+    } else { # Done
+        if (defined $data->{done} and length($data->{done}) and
+                not defined $pheader{'source-version'} and
+                not defined $pheader{'version'}) {
+            &appendlog;
+            &finish;
+        }
+        $receivedat= "done\@$gEmailDomain";
+        $markaswhat= 'done';
+        $set_done= $header{'from'};
+       if ( length( $gListDomain ) > 0 && length( $gDoneList ) > 0 ) {
+            $generalcc= "$gDoneList\@$gListDomain";
+           push @generalcc, "$gDoneList\@$gListDomain";
+       } else { 
+           $generalcc=''; 
+       }
+    }
+    if (defined $gStrongList and isstrongseverity($data->{severity})) {
+        $generalcc = join ', ', $generalcc, "$gStrongList\@$gListDomain";
+       push @generalcc,"$gStrongList\@$gListDomain";
+    }
+    if ($ref<0) {
+       &htmllog("Warning","sent",$replyto,"Message ignored.");
+       &sendmessage(create_mime_message(
+          [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+          To            => $replyto,
+          Subject       => "Message with no $gBug number ignored by $receivedat ($subject)",
+          'Message-ID'  => "<handler.x.$nn.warnignore\@$gEmailDomain>",
+          'In-Reply-To' => $header{'message-id'},
+          References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+          Precedence    => 'bulk',
+          "X-$gProject-PR-Message" => 'error',
+         ],message_body_template('mail/process_no_bug_number',
+                                 {subject => $subject,
+                                  date    => $header{date},
+                                  markaswhat => $markaswhat,
+                                  receivedat => $receivedat,
+                                  messageid => $header{'message-id'},
+                                 },
+                                )),'');
+       &appendlog;
+       &finish;
+    }
+
+    &checkmaintainers;
+
+    my @noticecc = grep($_ ne $replyto,@maintaddrs);
+    my $noticeccval.= join(', ', grep($_ ne $replyto,@maintaddrs));
+    $noticeccval =~ s/\s+\n\s+/ /g; 
+    $noticeccval =~ s/^\s+/ /; $noticeccval =~ s/\s+$//;
+
+    my @process= ($ref,split(/ /,$data->{mergedwith}));
+    my $orgref= $ref;
+
+    for $ref (@process) {
+       if ($ref != $orgref) {
+           &unfilelock;
+           $data = &lockreadbug($ref)
+               || die "huh ? $ref from $orgref out of ".join(' ',@process);
+       }
+        $data->{done}= $set_done if defined($set_done);
+        $data->{forwarded}= $set_forwarded if defined($set_forwarded);
+        if ($codeletter eq 'D') {
+            $data->{keywords} = join ' ', grep $_ ne 'pending',
+                                     split ' ', $data->{keywords};
+            if (defined $pheader{'source-version'}) {
+                if ($pheader{'source-version'} !~ m/^$config{package_version_re}$/) {
+                     $brokenness .= fill_template('mail/invalid_version',
+                                                  {version => $pheader{'source-version'}},
+                                                 );
+                }
+                else {
+                     addfixedversions($data, $pheader{source}, $pheader{'source-version'}, '');
+                }
+           } elsif (defined $pheader{version}) {
+                if ($pheader{version} !~ m/^$config{package_version_re}$/) {
+                     $brokenness .= fill_template('mail/invalid_version',
+                                                  {version => $pheader{version}},
+                                                 );
+                }
+                else {
+                     addfixedversions($data, $pheader{package}, $pheader{version}, '');
+                }
+           }
+        }
+
+       # Add bug mailing list to $generalbcc as appropriate
+       # This array is used to specify bcc in the cases where we're using create_mime_message.
+       my @generalbcc = (@generalcc,@addsrcaddrs,"bugs=$ref\@$gListDomain");
+       my $generalbcc = join(', ', $generalcc, @addsrcaddrs,"bugs=$ref\@$gListDomain");
+       $generalbcc =~ s/\s+\n\s+/ /g;
+       $generalbcc =~ s/^\s+/ /; $generalbcc =~ s/\s+$//;
+       if (length $generalbcc) {$generalbcc = "Bcc: $generalbcc\n"};
+
+       writebug($ref, $data);
+
+       my $hash = get_hashname($ref);
+        open(O,"db-h/$hash/$ref.report") || die "read original report: $!";
+        my $orig_report= join('',<O>); close(O);
+        if ($codeletter eq 'F') {
+           &htmllog("Reply","sent",$replyto,"You have marked $gBug as forwarded.");
+            &sendmessage(create_mime_message(
+            ["X-Loop"      => "$gMaintainerEmail",
+             From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+              To            => "$replyto",
+              Subject       => "$gBug#$ref: marked as forwarded ($data->{subject})",
+              "Message-ID"  => "<header.$ref.$nn.ackfwdd\@$gEmailDomain>",
+              "In-Reply-To" => $header{'message-id'},
+              References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+              Precedence    => 'bulk',
+              "X-$gProject-PR-Message"  => "forwarded $ref",
+              "X-$gProject-PR-Package"  => $data->{package},
+              "X-$gProject-PR-Keywords" => $data->{keywords},
+             # Only have a X-$gProject-PR-Source when we know the source package
+             (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
+             ],message_body_template('mail/process_mark_as_forwarded',
+                                    {date => $header{date},
+                                     messageid => $header{'message-id'},
+                                     data      => $data,
+                                    },
+                                   ),
+            [join("\n",@msg)]),'',[$replyto,@generalbcc,@noticecc],1);
+        } else {
+           &htmllog("Reply","sent",$replyto,"You have taken responsibility.");
+            &sendmessage(create_mime_message(
+            ["X-Loop"      => "$gMaintainerEmail",
+             From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+              To            => $replyto,
+              Subject       => "$gBug#$ref: marked as done ($data->{subject})",
+              "Message-ID"  => "<handler.$ref.$nn.ackdone\@$gEmailDomain>",
+              "In-Reply-To" => $header{'message-id'},
+              References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+              Precedence    => 'bulk',
+              "X-$gProject-PR-Message"  => "closed $ref",
+              "X-$gProject-PR-Package"  => $data->{package},
+              "X-$gProject-PR-Keywords" => $data->{keywords},
+             # Only have a X-$gProject-PR-Source when we know the source package
+             (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
+             ],message_body_template('mail/process_mark_as_done',
+                                    {date => $header{date},
+                                     messageid => $header{'message-id'},
+                                     subject   => $header{subject},
+                                     data      => $data,
+                                    },
+                                   ),
+            [$orig_report,join("\n",@msg)]),'',[$replyto,@generalbcc,@noticecc],1);
+            &htmllog("Notification","sent",$data->{originator},
+               "$gBug acknowledged by developer.");
+            &sendmessage(create_mime_message(
+            ["X-Loop"      => "$gMaintainerEmail",
+             From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+              To            => "$data->{originator}",
+              Subject       => "$gBug#$ref closed by $markedby ($header{'subject'})",
+              "Message-ID"  => "<handler.$ref.$nn.notifdone\@$gEmailDomain>",
+              "In-Reply-To" => "$data->{msgid}",
+              References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+              "X-$gProject-PR-Message"  => "they-closed $ref",
+              "X-$gProject-PR-Package"  => "$data->{package}",
+              "X-$gProject-PR-Keywords" => "$data->{keywords}",
+             # Only have a X-$gProject-PR-Source when we know the source package
+             (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
+              "Reply-To"                => "$ref\@$gEmailDomain",
+              "Content-Type"            => 'text/plain; charset="utf-8"',
+             ],message_body_template('mail/process_your_bug_done',
+                                    {data      => $data,
+                                     markedby  => $markedby,
+                                     messageid => $header{'message-id'},
+                                     subject   => $header{subject},
+                                    },
+                                   ),
+            [join("\n",@msg),$orig_report]),'',undef,1);
+        }
+       &appendlog;
+    }
+    &finish;
+}
+
+if ($ref<0) { # new bug report
+    if ($codeletter eq 'U') { # -submitter
+        &htmllog("Warning","sent",$replyto,"Message not forwarded.");
+       &sendmessage(create_mime_message(
+          [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+          To            => $replyto,
+          Subject       => "Message with no $gBug number cannot be sent to submitter! ($subject)",
+          'Message-ID'  => "<handler.x.$nn.nonumnosub\@$gEmailDomain>",
+          'In-Reply-To' => $header{'message-id'},
+          References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+          Precedence    => 'bulk',
+          "X-$gProject-PR-Message" => 'error',
+         ],message_body_template('mail/process_no_bug_number',
+                                 {subject => $subject,
+                                  date    => $header{date},
+                                  markaswhat => 'submitter',
+                                  receivedat => "$baddress\@$gEmailDomain",
+                                  messageid => $header{'message-id'},
+                                 },
+                                )),'');
+       &appendlog;
+       &finish;
+    }
+
+    $data->{found_versions} = [];
+    $data->{fixed_versions} = [];
+
+    if (defined $pheader{source}) {
+        $data->{package} = $pheader{source};
+    } elsif (defined $pheader{package}) {
+        $data->{package} = $pheader{package};
+    } elsif (defined $config{default_package}) {
+       $data->{package} = $config{default_package},
+    }
+    else {
+       &htmllog("Warning","sent",$replyto,"Message not forwarded.");
+       my $body = message_body_template('mail/process_no_package',
+                                       );
+        &sendmessage(create_mime_message(
+                       ["X-Loop"      => "$gMaintainerEmail",
+                       From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+                        To            => $replyto,
+                        Subject       => "Message with no Package: tag cannot be processed! ($subject)",
+                        "Message-ID"  => "<handler.x.$nn.nonumnosub\@$gEmailDomain>",
+                        "In-Reply-To" => $header{'message-id'},
+                        References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
+                        Precedence    => 'bulk',
+                        "X-$gProject-PR-Message" => 'error'
+                      ],
+          message_body_template('mail/process_no_package',
+                                {date => $header{date},
+                                 subject => $subject,
+                                 messageid => $header{'message-id'},
+                                 baddress => $baddress,
+                                },
+                               ),[join("\n", @msg)]), '',undef,1);
+       &appendlog;
+       &finish;
+    }
+
+    if (defined $config{default_package}) {
+        &checkmaintainers;
+        # if there are no maintainers for this package, assign it to the default package
+        if (not @maintaddrs) {
+             $data->{package} = $config{default_package};
+             $brokenness.= fill_template('mail/process_default_package_selected',
+                                         {old_package => $pheader{source} || $pheader{package} || 'No package',
+                                          new_package => $data->{package},
+                                         }
+                                        );
+             # force the maintainers to be rechecked
+             $maintainerschecked = 0;
+             &checkmaintainers;
+        }
+    }
+
+    $data->{keywords}= '';
+    if (defined($pheader{'keywords'})) {
+        $data->{keywords}= $pheader{'keywords'};
+    } elsif (defined($pheader{'tags'})) {
+        $data->{keywords}= $pheader{'tags'};
+    }
+    if (length($data->{keywords})) {
+        my @kws;
+        my %gkws = map { ($_, 1) } @gTags;
+        foreach my $kw (sort split(/[,\s]+/, lc($data->{keywords}))) {
+            push @kws, $kw if (defined $gkws{$kw});
+        }
+        $data->{keywords} = join(" ", @kws);
+    }
+    $data->{severity}= '';
+    if (defined($pheader{'severity'}) || defined($pheader{'priority'})) {
+       $data->{severity}= $pheader{'severity'};
+       $data->{severity}= $pheader{'priority'} unless ($data->{severity});
+       $data->{severity} =~ s/^\s*(.+)\s*$/$1/;
+
+       if (!grep($_ eq $data->{severity}, @gSeverityList, "$gDefaultSeverity")) {
+            $brokenness.= fill_template('mail/invalid_severity',
+                                       {severity=>$data->{severity}}
+                                      );
+            $data->{severity}= '';
+        }
+    }
+    if (defined($pheader{owner})) {
+        $data->{owner}= $pheader{owner};
+    }
+    if (defined($pheader{forwarded})) {
+       $data->{'forwarded-to'} = $pheader{forwarded};
+    }
+    &filelock("nextnumber.lock");
+    open(N,"nextnumber") || die "nextnumber: read: $!";
+    my $nextnumber=<N>; $nextnumber =~ s/\n$// || die "nextnumber bad format";
+    $ref= $nextnumber+0;  $nextnumber += 1;  $newref=1;
+    &overwrite('nextnumber', "$nextnumber\n");
+    &unfilelock;
+    my $hash = get_hashname($ref);
+    &overwrite("db-h/$hash/$ref.log",'');
+    $data->{originator} = $replyto;
+    $data->{date} = $intdate;
+    $data->{subject} = $subject;
+    $data->{msgid} = $header{'message-id'};
+    writebug($ref, $data);
+    # Deal with usertags
+    if (exists $pheader{usertags}) {
+        my $user = $replyto;
+        $user = $pheader{user} if exists $pheader{user};
+        $user =~ s/,.*//;
+        $user =~ s/^.*<(.*)>.*$/$1/;
+        $user =~ s/[(].*[)]//;
+        $user =~ s/^\s*(\S+)\s+.*$/$1/;
+        if ($user ne '' and Debbugs::User::is_valid_user($user)) {
+             $pheader{usertags} =~ s/(?:^\s+|\s+$)//g;
+             my %user_tags;
+             read_usertags(\%user_tags,$user);
+             for my $tag (split /[,\s]+/, $pheader{usertags}) {
+                  if ($tag =~ /^[a-zA-Z0-9.+\@-]+/) {
+                       my %bugs_with_tag; 
+                       @bugs_with_tag{@{$user_tags{$tag}||[]}} = (1) x @{$user_tags{$tag}||[]};
+                       $bugs_with_tag{$ref} = 1;
+                       $user_tags{$tag} = [keys %bugs_with_tag];
+                  }
+             }
+             write_usertags(\%user_tags,$user);
+        }
+        else {
+             $brokenness .= fill_template('mail/invalid_user',
+                                          {user => $user}
+                                         );
+        }
+    }
+    &overwrite("db-h/$hash/$ref.report",
+               join("\n",@msg)."\n");
+}
+
+&checkmaintainers;
+
+print DEBUG "maintainers >".join(' ',@maintaddrs)."<\n";
+
+my $orgsender= defined($header{'sender'}) ? "Original-Sender: $header{'sender'}\n" : '';
+my $newsubject= $subject;  $newsubject =~ s/^$gBug#$ref:*\s*//;
+
+my $xcchdr= $header{ 'x-debbugs-cc' } || '';
+if ($xcchdr =~ m/\S/) {
+    push(@resentccs,$xcchdr);
+    $resentccexplain.= fill_template('mail/xdebbugscc',
+                                    {xcchdr => $xcchdr},
+                                   );
+}
+
+if (@maintaddrs && ($codeletter eq 'B' || $codeletter eq 'M')) {
+    push(@resentccs,@maintaddrs);
+    $resentccexplain.= fill_template('mail/maintainercc',
+                                    {maintaddrs => \@maintaddrs,
+                                    },
+                                   );
+}
+
+@bccs = @addsrcaddrs;
+if (defined $gStrongList and isstrongseverity($data->{severity})) {
+    push @bccs, "$gStrongList\@$gListDomain";
+}
+
+# Send mail to the per bug list subscription too
+push @bccs, "bugs=$ref\@$gListDomain";
+
+if (defined $pheader{source}) {
+    # Prefix source versions with the name of the source package. They
+    # appear that way in version trees so that we can deal with binary
+    # packages moving from one source package to another.
+    if (defined $pheader{'source-version'}) {
+        if ($pheader{'source-version'} !~ m/^$config{package_version_re}$/) {
+             $brokenness .= fill_template('mail/invalid_version',
+                                          {version => $pheader{'source-version'}},
+                                         );
+        }
+        else {
+             addfoundversions($data, $pheader{source}, $pheader{'source-version'}, '');
+        }
+    } elsif (defined $pheader{version}) {
+        if ($pheader{version} !~ m/^$config{package_version_re}$/) {
+             $brokenness .= fill_template('mail/invalid_version',
+                                          {version => $pheader{version}},
+                                         );
+        }
+        else {
+             addfoundversions($data, $pheader{source}, $pheader{version}, '');
+        }
+    }
+    writebug($ref, $data);
+} elsif (defined $pheader{package}) {
+    # TODO: could handle Source-Version: by looking up the source package?
+     if (defined $pheader{version}) {
+         if ($pheader{version} !~ m/^$config{package_version_re}$/) {
+              $brokenness .= fill_template('mail/invalid_version',
+                                           {version => $pheader{version}},
+                                          );
+         }
+         else {
+              addfoundversions($data, $pheader{package}, $pheader{version}, 'binary');
+         }
+     }
+     writebug($ref, $data);
+}
+
+my $veryquiet= $codeletter eq 'Q';
+if ($codeletter eq 'M' && !@maintaddrs) {
+    $veryquiet= 1;
+    $brokenness.= fill_template('mail/invalid_maintainer',
+                               {},
+                              );
+}
+
+my $resentccval.= join(', ',@resentccs);
+$resentccval =~ s/\s+\n\s+/ /g; $resentccval =~ s/^\s+/ /; $resentccval =~ s/\s+$//;
+my $resentcc = '';
+if (length($resentccval)) { 
+    $resentcc= "Resent-CC: $resentccval\n"; 
+}
+
+if ($codeletter eq 'U') { # sent to -submitter
+    &htmllog("Message", "sent on", $data->{originator}, "$gBug#$ref.");
+    &sendmessage(<<END,[$data->{originator},@resentccs],[@bccs]);
+Subject: $gBug#$ref: $newsubject
+Reply-To: $replyto, $ref-quiet\@$gEmailDomain
+${orgsender}Resent-To: $data->{originator}
+${resentcc}Resent-Date: $tdate
+Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
+Resent-Sender: $gMaintainerEmail
+X-$gProject-PR-Message: report $ref
+X-$gProject-PR-Package: $data->{package}
+X-$gProject-PR-Keywords: $data->{keywords}
+${source_pr_header}$fwd
+END
+} elsif ($codeletter eq 'B') { # Sent to submit
+    my $report_followup = $newref ? 'report' : 'followup';
+    &htmllog($newref ? "Report" : "Information", "forwarded",
+             join(', ',"$gSubmitList\@$gListDomain",@resentccs),
+             "<code>$gBug#$ref</code>".
+             (length($data->{package})? "; Package <code>".html_escape($data->{package})."</code>" : '').
+             ".");
+    &sendmessage(<<END,["$gSubmitList\@$gListDomain",@resentccs],[@bccs]);
+Subject: $gBug#$ref: $newsubject
+Reply-To: $replyto, $ref\@$gEmailDomain
+Resent-From: $header{'from'}
+${orgsender}Resent-To: $gSubmitList\@$gListDomain
+${resentcc}Resent-Date: $tdate
+Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
+Resent-Sender: $gMaintainerEmail
+X-$gProject-PR-Message: $report_followup $ref
+X-$gProject-PR-Package: $data->{package}
+X-$gProject-PR-Keywords: $data->{keywords}
+${source_pr_header}$fwd
+END
+} elsif (@resentccs or @bccs) { # Quiet or Maintainer
+    # D and F done far earlier; B just done - so this must be M or Q
+    # We preserve whichever it was in the Reply-To (possibly adding
+    # the $gBug#).
+    my $report_followup = $newref ? 'report' : 'followup';
+    if (@resentccs) {
+        &htmllog($newref ? "Report" : "Information", "forwarded",
+                 $resentccval,
+                 "<code>$gBug#$ref</code>".
+                 (length($data->{package}) ? "; Package <code>".html_escape($data->{package})."</code>" : '').
+                 ".");
+    } else {
+        &htmllog($newref ? "Report" : "Information", "stored",
+                 "",
+                 "<code>$gBug#$ref</code>".
+                 (length($data->{package}) ? "; Package <code>".html_escape($data->{package})."</code>" : '').
+                 ".");
+    }
+    &sendmessage(<<END,[@resentccs],[@bccs]);
+Subject: $gBug#$ref: $newsubject
+Reply-To: $replyto, $ref-$baddressroot\@$gEmailDomain
+Resent-From: $header{'from'}
+${orgsender}Resent-To: $resentccval
+Resent-Date: $tdate
+Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
+Resent-Sender: $gMaintainerEmail
+X-$gProject-PR-Message: $report_followup $ref
+X-$gProject-PR-Package: $data->{package}
+X-$gProject-PR-Keywords: $data->{keywords}
+${source_pr_header}$fwd
+END
+}
+
+my $htmlbreak= length($brokenness) ? "<p>\n".html_escape($brokenness)."\n<p>\n" : '';
+$htmlbreak =~ s/\n\n/\n<P>\n\n/g;
+if (length($resentccval)) {
+    $htmlbreak = "  Copy sent to <code>".html_escape($resentccval)."</code>.".
+        $htmlbreak;
+}
+
+# Should we send an ack out?
+if (not exists $header{'x-debbugs-no-ack'} and
+    ($newref or
+     ($codeletter ne 'U' and
+      (not defined $header{precedence} or
+       $header{'precedence'} !~ /\b(?:bulk|junk|list)\b/
+      )
+     )
+    )
+   ){
+
+     # figure out forward explanation
+     my $forwardexplain = '';
+     my $thanks = '';
+     my $extra_vars;
+     # will contain info and -info in moreinfo messages
+     my $info = '';
+     my $infod = '';
+     # temporary headers
+     my %t_h;
+     if ($newref) {
+         &htmllog("Acknowledgement","sent",$replyto,
+                  ($veryquiet ?
+                   "New $gBug report received and filed, but not forwarded." :
+                   "New $gBug report received and forwarded."). $htmlbreak);
+         $thanks = fill_template('mail/process_ack_thanks_new');
+     }
+     else {
+         &htmllog("Acknowledgement","sent",$replyto,
+                  ($veryquiet ? "Extra info received and filed, but not forwarded." :
+                   $codeletter eq 'M' ? "Extra info received and forwarded to maintainer." :
+                   "Extra info received and forwarded to list."). $htmlbreak);
+         $thanks = fill_template('mail/process_ack_thanks_additional');
+         $info = 'info';
+         $infod = '-info';
+     }
+     if ($veryquiet) {
+         $forwardexplain = fill_template('mail/forward_veryquiet',
+                                        );
+         # these are the headers that quiet messages override
+         $t_h{messageid}  = "<handler.$ref.$nn.ack${info}quiet\@$gEmailDomain>";
+         $t_h{pr_message} = "ack${infod}-quiet $ref";
+         $t_h{reply_to}   = "$ref-quiet\@$gEmailDomain";
+         $extra_vars->{refreplyto} = "$ref-quiet\@$gEmailDomain";
+         $t_h{subject}    = length($info)?
+              "$gBug#$ref: Info received and FILED only ($subject)":
+              "$gBug#$ref: Acknowledgement of QUIET report ($subject)";
+     }
+     elsif ($codeletter eq 'M') {
+         $forwardexplain = fill_template('mail/forward_maintonly',
+                                        );
+         # these are the headers that maintonly messages override
+         $t_h{messageid}  = "<handler.$ref.$nn.ack{$info}maintonly\@$gEmailDomain>";
+         $t_h{pr_message} = "ack${infod}-maintonly $ref";
+         $t_h{reply_to}   = "$ref-maintonly\@$gEmailDomain";
+         $extra_vars->{refreplyto} = "$ref-maintonly\@$gEmailDomain";
+         $t_h{subject}    = length($info)?
+              "$gBug#$ref: Info received for maintainer only ($subject)":
+              "$gBug#$ref: Acknowledgement of maintainer-only report ($subject)";
+     }
+     else {
+         $forwardexplain = fill_template('mail/forward_normal',
+                                        );
+         $t_h{messageid}  = "<handler.$ref.$nn.ack${info}\@$gEmailDomain>";
+         $t_h{pr_message} = "ack${infod} $ref";
+         $t_h{reply_to}   = "$ref\@$gEmailDomain";
+         $extra_vars->{refreplyto} = "$ref\@$gEmailDomain";
+         $t_h{subject}    = (defined $info and length($info))?
+              "$gBug#$ref: Info received ($subject)" :
+              "$gBug#$ref: Acknowledgement ($subject)";
+     }
+     my $body = message_body_template('mail/process_ack',
+                                     {forwardexplain  => $forwardexplain,
+                                      resentccexplain => $resentccexplain,
+                                      thanks          => $thanks,
+                                      %{$extra_vars}
+                                     }
+                                    );
+     &sendmessage(create_mime_message(
+                      ["X-Loop"      => "$gMaintainerEmail",
+                       From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
+                       To            => $replyto,
+                       Subject       => $t_h{subject},
+                       "Message-ID"  => $t_h{messageid},
+                       "In-Reply-To" => $header{'message-id'},
+                        References    => $header{'message-id'},
+                        Precedence    => 'bulk',
+                       "X-$gProject-PR-Message"  => $t_h{pr_message} || "ack $ref",
+                       "X-$gProject-PR-Package"  => $data->{package},
+                       "X-$gProject-PR-Keywords" => $data->{keywords},
+                       # Only have a X-$gProject-PR-Source when we know the source package
+                       (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
+                       "Reply-To"                => $t_h{reply_to} || "$ref\@$gEmailDomain",
+                      ],$body,[]), '',undef,1);
+}
+
+&appendlog;
+&finish;
+
+sub overwrite {
+    my ($f,$v) = @_;
+    open(NEW,">$f.new") || die "$f.new: create: $!";
+    print(NEW "$v") || die "$f.new: write: $!";
+    close(NEW) || die "$f.new: close: $!";
+    rename("$f.new","$f") || die "rename $f.new to $f: $!";
+}
+
+sub appendlog {
+    my $hash = get_hashname($ref);
+    if (!open(AP,">>db-h/$hash/$ref.log")) {
+        print DEBUG "failed open log<\n";
+        print DEBUG "failed open log err $!<\n";
+        die "opening db-h/$hash/$ref.log (li): $!";
+    }
+    print(AP "\7\n",escape_log(@log),"\n\3\n") || die "writing db-h/$hash/$ref.log (li): $!";
+    close(AP) || die "closing db-h/$hash/$ref.log (li): $!";
+}
+
+sub finish {
+    my ($exit) = @_;
+    $exit ||= 0;
+    utime(time,time,"db");
+    # cleanups are run in an end block now.
+    #my ($u);
+    #while ($u= $cleanups[$#cleanups]) { &$u; }
+    unlink("incoming/P$nn") || die "unlinking incoming/P$nn: $!";
+    exit $exit;
+}
+
+die "wot no exit";
+
+sub htmllog {
+    my ($whatobj,$whatverb,$where,$desc) = @_;
+    my $hash = get_hashname($ref);
+    open(AP,">>db-h/$hash/$ref.log") || die "opening db-h/$hash/$ref.log (lh): $!";
+    print(AP
+          "\6\n".
+          "<strong>$whatobj $whatverb</strong>".
+          ($where eq '' ? "" : " to <code>".html_escape($where)."</code>").
+          ":<br>\n". $desc.
+          "\n\3\n") || die "writing db-h/$hash/$ref.log (lh): $!";
+    close(AP) || die "closing db-h/$hash/$ref.log (lh): $!";
+}    
+
+sub stripbccs {
+    my $msg = shift;
+    my $ret = '';
+    my $bcc = 0;
+    while ($msg =~ s/(.*\n)//) {
+       local $_ = $1;
+       if (/^$/) {
+           $ret .= $_;
+           last;
+       }
+       if ($bcc) {
+           # strip continuation lines too
+           next if /^\s/;
+           $bcc = 0;
+       }
+       if (/^Bcc:/i) {
+           $bcc = 1;
+       } else {
+           $ret .= $_;
+       }
+    }
+    return $ret . $msg;
+}
+
+=head2 send_message
+
+     send_message($the_message,\@recipients,\@bcc,$do_not_encode)
+
+The first argument is the scalar message, the second argument is the
+arrayref of recipients, the third is the arrayref of Bcc:'ed
+recipients.
+
+The final argument turns off header encoding and the addition of the
+X-Loop header if true, defaults to false.
+
+=cut
+
+
+sub sendmessage {
+    my ($msg,$recips,$bcc,$no_encode) = @_;
+    if (not defined $recips or (!ref($recips) && $recips eq '')
+       or @$recips == 0) {
+       $recips = ['-t'];
+    }
+    # This is suboptimal. The right solution is to send headers
+    # separately from the rest of the message and encode them rather
+    # than doing this.
+    $msg = "X-Loop: $gMaintainerEmail\n" . $msg unless $no_encode;
+    # The original message received is written out in appendlog, so
+    # before writing out the other messages we've sent out, we need to
+    # RFC1522 encode the header.
+    $msg = encode_headers($msg) unless $no_encode;
+
+    my $hash = get_hashname($ref);
+    #save email to the log
+    open(AP,">>db-h/$hash/$ref.log") || die "opening db-h/$hash/$ref.log (lo): $!";
+    print(AP "\2\n",join("\4",@$recips),"\n\5\n",
+          escape_log(stripbccs($msg)),"\n\3\n") ||
+        die "writing db-h/$hash/$ref.log (lo): $!";
+    close(AP) || die "closing db-h/$hash/$ref.log (lo): $!";
+
+    if (ref($bcc)) {
+        shift @$recips if $recips->[0] eq '-t';
+        push @$recips, @$bcc;
+    }
+
+    send_mail_message(message        => $msg,
+                     # Because we encode the headers above, we do not want to encode them here
+                     encode_headers => 0,
+                     recipients     => $recips);
+}
+
+=head2 message_body_template
+
+     message_body_template('mail/ack',{ref=>'foo'});
+
+Creates a message body using a template
+
+=cut
+
+sub message_body_template{
+     my ($template,$extra_var) = @_;
+     $extra_var ||={};
+     my $body = fill_template($template,$extra_var);
+     return fill_template('mail/message_body',
+                         {%{$extra_var},
+                          body => $body,
+                         },
+                        );
+}
+
+=head2 fill_template
+
+     fill_template('mail/foo',{foo=>'bar'});
+
+Calls fill_in_template with a default set of variables and any extras
+added in.
+
+=cut
+
+sub fill_template{
+     my ($template,$extra_var) = @_;
+     $extra_var ||={};
+     my $variables = {config => \%config,
+                     defined($ref)?(ref    => $ref):(),
+                     defined($data)?(data  => $data):(),
+                     %{$extra_var},
+                    };
+     my $hole_var = {'&bugurl' =>
+                    sub{"$_[0]: ".
+                             'http://'.$config{cgi_domain}.'/'.
+                                  Debbugs::CGI::bug_links(bug=>$_[0],
+                                                          links_only => 1,
+                                                         );
+                   }
+                   };
+     return fill_in_template(template => $template,
+                            variables => $variables,
+                            hole_var  => $hole_var,
+                           );
+}
+
+
+sub checkmaintainers {
+    return if $maintainerschecked++;
+    return if !length($data->{package});
+    my %maintainerof;
+    open(MAINT,"$gMaintainerFile") || die die "maintainers open: $!";
+    while (<MAINT>) {
+       m/^\n$/ && next;
+       m/^\s*$/ && next;
+        m/^(\S+)\s+(\S.*\S)\s*\n$/ || die "maintainers bogus \`$_'";
+        $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
+       # use the package which is normalized to lower case; we do this because we lc the pseudo headers.
+        $maintainerof{$a}= $2;
+    }
+    close(MAINT);
+    open(MAINT,"$gMaintainerFileOverride") || die die "maintainers.override open: $!";
+    while (<MAINT>) {
+       m/^\n$/ && next;
+       m/^\s*$/ && next;
+        m/^(\S+)\s+(\S.*\S)\s*\n$/ || die "maintainers.override bogus \`$_'";
+        $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
+       # use the package which is normalized to lower case; we do this because we lc the pseudo headers.
+        $maintainerof{$a}= $2;
+    }
+    close(MAINT);
+    my %pkgsrc;
+    open(SOURCES,"$gPackageSource") || die "pkgsrc open: $!";
+    while (<SOURCES>) {
+        next unless m/^(\S+)\s+\S+\s+(\S.*\S)\s*$/;
+       ($a,$b)=($1,$2);
+       $a =~ y/A-Z/a-z/;
+       $pkgsrc{$a} = $b;
+    }
+    close(SOURCES);
+    my $anymaintfound=0; my $anymaintnotfound=0;
+    for my $p (split(m/[ \t?,():]+/,$data->{package})) {
+        $p =~ y/A-Z/a-z/;
+       $p =~ /([a-z0-9.+-]+)/;
+       $p = $1;
+       next unless defined $p;
+       if (defined $gSubscriptionDomain) {
+           if (defined($pkgsrc{$p})) {
+               push @addsrcaddrs, "$pkgsrc{$p}\@$gSubscriptionDomain";
+           } else {
+               push @addsrcaddrs, "$p\@$gSubscriptionDomain";
+           }
+       }
+        if (defined($maintainerof{$p})) {
+           print DEBUG "maintainer add >$p|$maintainerof{$p}<\n";
+            my $addmaint= $maintainerof{$p};
+            push(@maintaddrs,$addmaint) unless
+                $addmaint eq $replyto || grep($_ eq $addmaint, @maintaddrs);
+            $anymaintfound++;
+        } else {
+           print DEBUG "maintainer none >$p<\n";
+           push(@maintaddrs,$gUnknownMaintainerEmail) unless $anymaintnotfound;
+            $anymaintnotfound++;
+            last;
+        }
+    }
+
+    if (defined $data->{owner} and length $data->{owner}) {
+        print DEBUG "owner add >$data->{package}|$data->{owner}<\n";
+        my $addmaint = $data->{owner};
+        push(@maintaddrs, $addmaint) unless
+            $addmaint eq $replyto or grep($_ eq $addmaint, @maintaddrs);
+    }
+}
+
+=head2 bug_list_forward
+
+     bug_list_forward($spool_filename) if $codeletter eq 'L';
+
+
+Given the spool file, will forward a bug to the per bug mailing list
+subscription system.
+
+=cut
+
+sub bug_list_forward{
+     my ($bug_fn) = @_;
+     # Read the bug information and package information for passing to
+     # the mailing list
+     my ($bug_number) = $bug_fn =~ /^L(\d+)\./;
+     my ($bfound, $data)= lockreadbugmerge($bug_number);
+     my $bug_fh = IO::File->new("incoming/P$bug_fn",'r') or die "Unable to open incoming/P$bug_fn $!";
+
+     local $/ = undef;
+     my $bug_message = <$bug_fh>;
+     my ($bug_address) = $bug_message =~ /^Received: \(at ([^\)]+)\) by/;
+     my ($envelope_from) = $bug_message =~ s/\nFrom\s+([^\s]+)[^\n]+\n/\n/;
+     if (not defined $envelope_from) {
+         # Try to use the From: header or something to set it 
+          ($envelope_from) = $bug_message =~ /\nFrom:\s+(.+?)\n/;
+         # Kludgy, and should really be using a full scale header
+         # parser to do this.
+         $envelope_from =~ s/^.+?<([^>]+)>.+$/$1/;
+     }
+     my ($header,$body) = split /\n\n/, $bug_message, 2;
+     # Add X-$gProject-PR-Message: list bug_number, package name, and bug title headers
+     $header .= qq(\nX-$gProject-PR-Message: list $bug_number\n).
+         qq(X-$gProject-PR-Package: $data->{package}\n).
+              qq(X-$gProject-PR-Title: $data->{subject})
+              if defined $data;
+     print STDERR "Tried to loop me with $envelope_from\n"
+         and exit 1 if $envelope_from =~ /\Q$gListDomain\E|\Q$gEmailDomain\E/;
+     print DEBUG $envelope_from,qq(\n);
+     # If we don't have a bug address, something has gone horribly wrong.
+     print STDERR "Doesn't match: $bug_address\n" and exit 1 unless defined $bug_address;
+     $bug_address =~ s/\@.+//;
+     print DEBUG "Sending message to bugs=$bug_address\@$gListDomain\n";
+     print DEBUG $header.qq(\n\n).$body;
+     send_mail_message(message        => $header.qq(\n\n).$body,
+                      recipients     => ["bugs=$bug_address\@$gListDomain"],
+                      envelope_from  => $envelope_from,
+                      encode_headers => 0,
+                     );
+     unlink("incoming/P$bug_fn") || die "unlinking incoming/P$bug_fn: $!";
+     exit 0;
+}
diff --git a/scripts/process.in b/scripts/process.in
deleted file mode 100755 (executable)
index e17127b..0000000
+++ /dev/null
@@ -1,1169 +0,0 @@
-#!/usr/bin/perl
-# $Id: process.in,v 1.109 2006/02/09 22:02:04 don Exp $
-#
-# Usage: process nn
-# Temps:  incoming/Pnn
-
-use warnings;
-use strict;
-
-use POSIX qw(strftime);
-
-use IO::File;
-
-use MIME::Parser;
-use Debbugs::MIME qw(decode_rfc1522 create_mime_message getmailbody);
-use Debbugs::Mail qw(send_mail_message encode_headers);
-use Debbugs::Packages qw(getpkgsrc);
-use Debbugs::User qw(read_usertags write_usertags);
-use Debbugs::Common qw(:lock get_hashname);
-use Debbugs::Status qw(writebug isstrongseverity lockreadbugmerge lockreadbug);
-
-use Debbugs::CGI qw(html_escape bug_url);
-
-use Debbugs::Log qw(:misc);
-
-use Debbugs::Text qw(:templates);
-
-use Debbugs::Status qw(:versions);
-use Debbugs::Config qw(:globals :config);
-
-chdir( "$gSpoolDir" ) || die "chdir spool: $!\n";
-
-#open(DEBUG,"> /tmp/debbugs.debug");
-umask(002);
-open DEBUG, ">/dev/null";
-
-my $intdate = time or quit("failed to get time: $!");
-
-$_=shift;
-m/^([BMQFDUL])(\d*)\.\d+$/ or quit("bad argument: $_");
-my $codeletter= $1;
-my $tryref= length($2) ? $2 : -1;
-my $nn= $_;
-
-if (!rename("incoming/G$nn","incoming/P$nn")) 
-{
-    $_=$!.'';  m/no such file or directory/i && exit 0;
-    &quit("renaming to lock: $!");
-}
-
-my $baddress= 'submit' if $codeletter eq 'B';
-$baddress= 'maintonly' if $codeletter eq 'M';
-$baddress= 'quiet' if $codeletter eq 'Q';
-$baddress= 'forwarded' if $codeletter eq 'F';
-$baddress= 'done' if $codeletter eq 'D';
-$baddress= 'submitter' if $codeletter eq 'U';
-bug_list_forward($nn) if $codeletter eq 'L';
-$baddress || &quit("bad codeletter $codeletter");
-my $baddressroot= $baddress;
-$baddress= "$tryref-$baddress" if $tryref>=0;
-
-open(M,"incoming/P$nn");
-my @log=<M>;
-close(M);
-
-my @msg = @log;
-chomp @msg;
-
-print DEBUG "###\n",join("##\n",@msg),"\n###\n";
-
-my $tdate = strftime "%a, %d %h %Y %T +0000", gmtime;
-my $fwd= <<END;
-Received: via spool by $baddress\@$gEmailDomain id=$nn
-          (code $codeletter ref $tryref); $tdate
-END
-
-# header and decoded body respectively
-my (@headerlines, @bodylines);
-
-# whether maintainer addresses have been checked
-our $maintainerschecked = 0;
-#maintainer address for this message
-our @maintaddrs;
-# other src addresses
-our @addsrcaddrs;
-our @resentccs;
-our @bccs;
-
-my $resentccexplain='';
-
-# whether there's a new reference with this email
-our $newref = 0;
-
-our $brokenness = '';
-
-my $parser = new MIME::Parser;
-mkdir "$gSpoolDir/mime.tmp", 0777;
-$parser->output_under("$gSpoolDir/mime.tmp");
-my $entity = eval { $parser->parse_data(join('',@log)) };
-
-my $i;
-if ($entity and $entity->head->tags) {
-    @headerlines = @{$entity->head->header};
-    chomp @headerlines;
-
-    my $entity_body = getmailbody($entity);
-    @bodylines = map {s/\r?\n$//; $_;}
-        $entity_body ? $entity_body->as_lines() : ();
-
-    # set $i to beginning of encoded body data, so we can dump it out
-    # verbatim later
-    $i = 0;
-    ++$i while $msg[$i] =~ /./;
-} else {
-    # Legacy pre-MIME code, kept around in case MIME::Parser fails.
-    for ($i = 0; $i <= $#msg; $i++) {
-       $_ = $msg[$i];
-       last unless length($_);
-       while ($msg[$i+1] =~ m/^\s/) {
-           $i++;
-           $_ .= "\n".$msg[$i];
-       }
-       push @headerlines, $_;
-    }
-
-    @bodylines = @msg[$i..$#msg];
-}
-
-my %header;
-
-for my $hdr (@headerlines) {
-    $hdr = decode_rfc1522($hdr);
-    $_ = $hdr;
-    s/\n\s/ /g;
-    &finish if m/^x-loop: (\S+)$/i && $1 eq "$gMaintainerEmail";
-    my $ins = !m/^subject:/i && !m/^reply-to:/i && !m/^return-path:/i
-           && !m/^From / && !m/^X-Debbugs-/i;
-    $fwd .= $hdr."\n" if $ins;
-    # print DEBUG ">$_<\n";
-    if (s/^(\S+):\s*//) {
-       my $v = lc $1;
-       print DEBUG ">$v=$_<\n";
-       $header{$v} = $_;
-    } else {
-       print DEBUG "!>$_<\n";
-    }
-}
-$header{'message-id'} = '' if not defined $header{'message-id'};
-
-# remove blank lines
-shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
-
-# Strip off RFC2440-style PGP clearsigning.
-if (@bodylines and $bodylines[0] =~ /^-----BEGIN PGP SIGNED/) {
-    shift @bodylines while @bodylines and length $bodylines[0];
-    shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
-    for my $findsig (0 .. $#bodylines) {
-       if ($bodylines[$findsig] =~ /^-----BEGIN PGP SIGNATURE/) {
-           $#bodylines = $findsig - 1;
-           last;
-       }
-    }
-    map { s/^- // } @bodylines;
-}
-
-#psuedoheaders
-my %pheader;
-# extract pseudo-headers
-for my $phline (@bodylines)
-{
-    last if $phline !~ m/^([\w-]+):\s*(\S.*)/;
-    my ($fn, $fv) = ($1, $2);
-    $fv =~ s/\s*$//;
-    print DEBUG ">$fn|$fv|\n";
-    $fn = lc $fn;
-    # Don't lc owner or forwarded
-    $fv = lc $fv unless $fn =~ /^(?:owner|forwarded|usertags|version|source-version)$/;
-    $pheader{$fn} = $fv;
-    print DEBUG ">$fn~$fv<\n";
-}
-
-# Allow pseudo headers to set x-debbugs- stuff [#179340]
-for my $key (grep /X-Debbugs-.*/i, keys %pheader) {
-     $header{$key} = $pheader{$key} if not exists $header{$key};
-}
-
-$fwd .= join("\n",@msg[$i..$#msg]);
-
-print DEBUG "***\n$fwd\n***\n";
-
-if (defined $header{'resent-from'} && !defined $header{'from'}) {
-    $header{'from'} = $header{'resent-from'};
-}
-defined($header{'from'}) || &quit("no From header");
-
-my $replyto = $header{'reply-to'};
-$replyto = '' unless defined $replyto;
-$replyto =~ s/^ +//;
-$replyto =~ s/ +$//;
-unless (length $replyto) {
-    $replyto = $header{'from'};
-}
-
-my $subject = '(no subject)';
-if (!defined($header{'subject'})) 
-{
-       $brokenness.= fill_template('mail/process_broken_subject');
-
-} else { 
-    $subject= $header{'subject'}; 
-}
-
-my $ref=-1;
-$subject =~ s/^Re:\s*//i; $_= $subject."\n";
-if ($tryref < 0 && m/^Bug ?\#(\d+)\D/i) {
-    $tryref= $1+0; 
-}
-my $data;
-if ($tryref >= 0) 
-{
-     my $bfound;
-    ($bfound, $data)= &lockreadbugmerge($tryref);
-    if ($bfound) { 
-        $ref= $tryref; 
-    } else {
-        &htmllog("Reply","sent", $replyto,"Unknown problem report number <code>$tryref</code>.");
-        &sendmessage(create_mime_message(
-          [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-          To            => $replyto,
-          Subject       => "Unknown problem report $gBug#$tryref ($subject)",
-          'Message-ID'  => "<handler.x.$nn.unknown\@$gEmailDomain>",
-          'In-Reply-To' => $header{'message-id'},
-          References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-          Precedence    => 'bulk',
-          "X-$gProject-PR-Message" => 'error',
-         ],message_body_template('process_unknown_bug_number',
-                                 {subject => $subject,
-                                  date    => $header{date},
-                                  baddress => $baddress,
-                                  tryref   => $tryref,
-                                  messageid => $header{'message-id'},
-                                 },
-                                )),'');
-        &appendlog;
-        &finish;
-    }
-} else { 
-    &filelock('lock/-1'); 
-}
-
-# Attempt to determine which source package this is
-my $source_pr_header = '';
-my $source_package = '';
-if (defined $pheader{source}) {
-     $source_package = $pheader{source};
-}
-elsif (defined $data->{package} or defined $pheader{package}) {
-     my $pkg_src = getpkgsrc();
-     $source_package = $pkg_src->{defined $data->{package}?$data->{package}:$pheader{package}};
-}
-$source_pr_header = "X-$gProject-PR-Source: $source_package\n"
-     if defined $source_package and length $source_package;
-
-# Done and Forwarded Bugs
-if ($codeletter eq 'D' || $codeletter eq 'F') 
-{
-    if ($replyto =~ m/$gBounceFroms/o ||
-        $header{'from'} =~ m/$gBounceFroms/o)
-    {
-        print STDERR "bounce detected !  Mwaap! Mwaap!";
-        exit 1;
-    }
-    my $markedby= $header{'from'} eq $replyto ? $replyto :
-               "$header{'from'} (reply to $replyto)";
-    my @generalcc;
-    my $receivedat;
-    my $markaswhat;
-    my $set_forwarded;
-    my $generalcc;
-    my $set_done;
-    if ($codeletter eq 'F') { # Forwarded
-        (&appendlog,&finish) if defined $data->{forwarded} and length($data->{forwarded});
-        $receivedat= "forwarded\@$gEmailDomain";
-        $markaswhat= 'forwarded';
-        $set_forwarded= $header{'to'};
-       # Dissallow forwarded being set to this bug tracking system
-       if (defined $set_forwarded and $set_forwarded =~ /\Q$gEmailDomain\E/) {
-            undef $set_forwarded;
-       }
-       if ( length( $gListDomain ) > 0 && length( $gForwardList ) > 0 ) {
-           push @generalcc, "$gForwardList\@$gListDomain";
-           $generalcc= "$gForwardList\@$gListDomain";
-       } else { 
-           $generalcc=''; 
-        }
-    } else { # Done
-        if (defined $data->{done} and length($data->{done}) and
-                not defined $pheader{'source-version'} and
-                not defined $pheader{'version'}) {
-            &appendlog;
-            &finish;
-        }
-        $receivedat= "done\@$gEmailDomain";
-        $markaswhat= 'done';
-        $set_done= $header{'from'};
-       if ( length( $gListDomain ) > 0 && length( $gDoneList ) > 0 ) {
-            $generalcc= "$gDoneList\@$gListDomain";
-           push @generalcc, "$gDoneList\@$gListDomain";
-       } else { 
-           $generalcc=''; 
-       }
-    }
-    if (defined $gStrongList and isstrongseverity($data->{severity})) {
-        $generalcc = join ', ', $generalcc, "$gStrongList\@$gListDomain";
-       push @generalcc,"$gStrongList\@$gListDomain";
-    }
-    if ($ref<0) {
-       &htmllog("Warning","sent",$replyto,"Message ignored.");
-       &sendmessage(create_mime_message(
-          [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-          To            => $replyto,
-          Subject       => "Message with no $gBug number ignored by $receivedat ($subject)",
-          'Message-ID'  => "<handler.x.$nn.warnignore\@$gEmailDomain>",
-          'In-Reply-To' => $header{'message-id'},
-          References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-          Precedence    => 'bulk',
-          "X-$gProject-PR-Message" => 'error',
-         ],message_body_template('mail/process_no_bug_number',
-                                 {subject => $subject,
-                                  date    => $header{date},
-                                  markaswhat => $markaswhat,
-                                  receivedat => $receivedat,
-                                  messageid => $header{'message-id'},
-                                 },
-                                )),'');
-       &appendlog;
-       &finish;
-    }
-
-    &checkmaintainers;
-
-    my @noticecc = grep($_ ne $replyto,@maintaddrs);
-    my $noticeccval.= join(', ', grep($_ ne $replyto,@maintaddrs));
-    $noticeccval =~ s/\s+\n\s+/ /g; 
-    $noticeccval =~ s/^\s+/ /; $noticeccval =~ s/\s+$//;
-
-    my @process= ($ref,split(/ /,$data->{mergedwith}));
-    my $orgref= $ref;
-
-    for $ref (@process) {
-       if ($ref != $orgref) {
-           &unfilelock;
-           $data = &lockreadbug($ref)
-               || die "huh ? $ref from $orgref out of ".join(' ',@process);
-       }
-        $data->{done}= $set_done if defined($set_done);
-        $data->{forwarded}= $set_forwarded if defined($set_forwarded);
-        if ($codeletter eq 'D') {
-            $data->{keywords} = join ' ', grep $_ ne 'pending',
-                                     split ' ', $data->{keywords};
-            if (defined $pheader{'source-version'}) {
-                if ($pheader{'source-version'} !~ m/^$config{package_version_re}$/) {
-                     $brokenness .= fill_template('mail/invalid_version',
-                                                  {version => $pheader{'source-version'}},
-                                                 );
-                }
-                else {
-                     addfixedversions($data, $pheader{source}, $pheader{'source-version'}, '');
-                }
-           } elsif (defined $pheader{version}) {
-                if ($pheader{version} !~ m/^$config{package_version_re}$/) {
-                     $brokenness .= fill_template('mail/invalid_version',
-                                                  {version => $pheader{version}},
-                                                 );
-                }
-                else {
-                     addfixedversions($data, $pheader{package}, $pheader{version}, '');
-                }
-           }
-        }
-
-       # Add bug mailing list to $generalbcc as appropriate
-       # This array is used to specify bcc in the cases where we're using create_mime_message.
-       my @generalbcc = (@generalcc,@addsrcaddrs,"bugs=$ref\@$gListDomain");
-       my $generalbcc = join(', ', $generalcc, @addsrcaddrs,"bugs=$ref\@$gListDomain");
-       $generalbcc =~ s/\s+\n\s+/ /g;
-       $generalbcc =~ s/^\s+/ /; $generalbcc =~ s/\s+$//;
-       if (length $generalbcc) {$generalbcc = "Bcc: $generalbcc\n"};
-
-       writebug($ref, $data);
-
-       my $hash = get_hashname($ref);
-        open(O,"db-h/$hash/$ref.report") || &quit("read original report: $!");
-        my $orig_report= join('',<O>); close(O);
-        if ($codeletter eq 'F') {
-           &htmllog("Reply","sent",$replyto,"You have marked $gBug as forwarded.");
-            &sendmessage(create_mime_message(
-            ["X-Loop"      => "$gMaintainerEmail",
-             From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-              To            => "$replyto",
-              Subject       => "$gBug#$ref: marked as forwarded ($data->{subject})",
-              "Message-ID"  => "<header.$ref.$nn.ackfwdd\@$gEmailDomain>",
-              "In-Reply-To" => $header{'message-id'},
-              References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-              Precedence    => 'bulk',
-              "X-$gProject-PR-Message"  => "forwarded $ref",
-              "X-$gProject-PR-Package"  => $data->{package},
-              "X-$gProject-PR-Keywords" => $data->{keywords},
-             # Only have a X-$gProject-PR-Source when we know the source package
-             (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
-             ],message_body_template('mail/process_mark_as_forwarded',
-                                    {date => $header{date},
-                                     messageid => $header{'message-id'},
-                                     data      => $data,
-                                    },
-                                   ),
-            [join("\n",@msg)]),'',[$replyto,@generalbcc,@noticecc],1);
-        } else {
-           &htmllog("Reply","sent",$replyto,"You have taken responsibility.");
-            &sendmessage(create_mime_message(
-            ["X-Loop"      => "$gMaintainerEmail",
-             From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-              To            => $replyto,
-              Subject       => "$gBug#$ref: marked as done ($data->{subject})",
-              "Message-ID"  => "<handler.$ref.$nn.ackdone\@$gEmailDomain>",
-              "In-Reply-To" => $header{'message-id'},
-              References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-              Precedence    => 'bulk',
-              "X-$gProject-PR-Message"  => "closed $ref",
-              "X-$gProject-PR-Package"  => $data->{package},
-              "X-$gProject-PR-Keywords" => $data->{keywords},
-             # Only have a X-$gProject-PR-Source when we know the source package
-             (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
-             ],message_body_template('mail/process_mark_as_done',
-                                    {date => $header{date},
-                                     messageid => $header{'message-id'},
-                                     subject   => $header{subject},
-                                     data      => $data,
-                                    },
-                                   ),
-            [$orig_report,join("\n",@msg)]),'',[$replyto,@generalbcc,@noticecc],1);
-            &htmllog("Notification","sent",$data->{originator},
-               "$gBug acknowledged by developer.");
-            &sendmessage(create_mime_message(
-            ["X-Loop"      => "$gMaintainerEmail",
-             From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-              To            => "$data->{originator}",
-              Subject       => "$gBug#$ref closed by $markedby ($header{'subject'})",
-              "Message-ID"  => "<handler.$ref.$nn.notifdone\@$gEmailDomain>",
-              "In-Reply-To" => "$data->{msgid}",
-              References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-              "X-$gProject-PR-Message"  => "they-closed $ref",
-              "X-$gProject-PR-Package"  => "$data->{package}",
-              "X-$gProject-PR-Keywords" => "$data->{keywords}",
-             # Only have a X-$gProject-PR-Source when we know the source package
-             (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
-              "Reply-To"                => "$ref\@$gEmailDomain",
-              "Content-Type"            => 'text/plain; charset="utf-8"',
-             ],message_body_template('mail/process_your_bug_done',
-                                    {data      => $data,
-                                     markedby  => $markedby,
-                                     messageid => $header{'message-id'},
-                                     subject   => $header{subject},
-                                    },
-                                   ),
-            [join("\n",@msg),$orig_report]),'',undef,1);
-        }
-       &appendlog;
-    }
-    &finish;
-}
-
-if ($ref<0) { # new bug report
-    if ($codeletter eq 'U') { # -submitter
-        &htmllog("Warning","sent",$replyto,"Message not forwarded.");
-       &sendmessage(create_mime_message(
-          [From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-          To            => $replyto,
-          Subject       => "Message with no $gBug number cannot be sent to submitter! ($subject)",
-          'Message-ID'  => "<handler.x.$nn.nonumnosub\@$gEmailDomain>",
-          'In-Reply-To' => $header{'message-id'},
-          References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-          Precedence    => 'bulk',
-          "X-$gProject-PR-Message" => 'error',
-         ],message_body_template('mail/process_no_bug_number',
-                                 {subject => $subject,
-                                  date    => $header{date},
-                                  markaswhat => 'submitter',
-                                  receivedat => "$baddress\@$gEmailDomain",
-                                  messageid => $header{'message-id'},
-                                 },
-                                )),'');
-       &appendlog;
-       &finish;
-    }
-
-    $data->{found_versions} = [];
-    $data->{fixed_versions} = [];
-
-    if (defined $pheader{source}) {
-        $data->{package} = $pheader{source};
-    } elsif (defined $pheader{package}) {
-        $data->{package} = $pheader{package};
-    } elsif (defined $config{default_package}) {
-       $data->{package} = $config{default_package},
-    }
-    else {
-       &htmllog("Warning","sent",$replyto,"Message not forwarded.");
-       my $body = message_body_template('mail/process_no_package',
-                                       );
-        &sendmessage(create_mime_message(
-                       ["X-Loop"      => "$gMaintainerEmail",
-                       From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-                        To            => $replyto,
-                        Subject       => "Message with no Package: tag cannot be processed! ($subject)",
-                        "Message-ID"  => "<handler.x.$nn.nonumnosub\@$gEmailDomain>",
-                        "In-Reply-To" => $header{'message-id'},
-                        References    => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}),
-                        Precedence    => 'bulk',
-                        "X-$gProject-PR-Message" => 'error'
-                      ],
-          message_body_template('mail/process_no_package',
-                                {date => $header{date},
-                                 subject => $subject,
-                                 messageid => $header{'message-id'},
-                                 baddress => $baddress,
-                                },
-                               ),[join("\n", @msg)]), '',undef,1);
-       &appendlog;
-       &finish;
-    }
-
-    if (defined $config{default_package}) {
-        &checkmaintainers;
-        # if there are no maintainers for this package, assign it to the default package
-        if (not @maintaddrs) {
-             $data->{package} = $config{default_package};
-             $brokenness.= fill_template('mail/process_default_package_selected',
-                                         {old_package => $pheader{source} || $pheader{package} || 'No package',
-                                          new_package => $data->{package},
-                                         }
-                                        );
-             # force the maintainers to be rechecked
-             $maintainerschecked = 0;
-             &checkmaintainers;
-        }
-    }
-
-    $data->{keywords}= '';
-    if (defined($pheader{'keywords'})) {
-        $data->{keywords}= $pheader{'keywords'};
-    } elsif (defined($pheader{'tags'})) {
-        $data->{keywords}= $pheader{'tags'};
-    }
-    if (length($data->{keywords})) {
-        my @kws;
-        my %gkws = map { ($_, 1) } @gTags;
-        foreach my $kw (sort split(/[,\s]+/, lc($data->{keywords}))) {
-            push @kws, $kw if (defined $gkws{$kw});
-        }
-        $data->{keywords} = join(" ", @kws);
-    }
-    $data->{severity}= '';
-    if (defined($pheader{'severity'}) || defined($pheader{'priority'})) {
-       $data->{severity}= $pheader{'severity'};
-       $data->{severity}= $pheader{'priority'} unless ($data->{severity});
-       $data->{severity} =~ s/^\s*(.+)\s*$/$1/;
-
-       if (!grep($_ eq $data->{severity}, @gSeverityList, "$gDefaultSeverity")) {
-            $brokenness.= fill_template('mail/invalid_severity',
-                                       {severity=>$data->{severity}}
-                                      );
-            $data->{severity}= '';
-        }
-    }
-    if (defined($pheader{owner})) {
-        $data->{owner}= $pheader{owner};
-    }
-    if (defined($pheader{forwarded})) {
-       $data->{'forwarded-to'} = $pheader{forwarded};
-    }
-    &filelock("nextnumber.lock");
-    open(N,"nextnumber") || &quit("nextnumber: read: $!");
-    my $nextnumber=<N>; $nextnumber =~ s/\n$// || &quit("nextnumber bad format");
-    $ref= $nextnumber+0;  $nextnumber += 1;  $newref=1;
-    &overwrite('nextnumber', "$nextnumber\n");
-    &unfilelock;
-    my $hash = get_hashname($ref);
-    &overwrite("db-h/$hash/$ref.log",'');
-    $data->{originator} = $replyto;
-    $data->{date} = $intdate;
-    $data->{subject} = $subject;
-    $data->{msgid} = $header{'message-id'};
-    writebug($ref, $data);
-    # Deal with usertags
-    if (exists $pheader{usertags}) {
-        my $user = $replyto;
-        $user = $pheader{user} if exists $pheader{user};
-        $user =~ s/,.*//;
-        $user =~ s/^.*<(.*)>.*$/$1/;
-        $user =~ s/[(].*[)]//;
-        $user =~ s/^\s*(\S+)\s+.*$/$1/;
-        if ($user ne '' and Debbugs::User::is_valid_user($user)) {
-             $pheader{usertags} =~ s/(?:^\s+|\s+$)//g;
-             my %user_tags;
-             read_usertags(\%user_tags,$user);
-             for my $tag (split /[,\s]+/, $pheader{usertags}) {
-                  if ($tag =~ /^[a-zA-Z0-9.+\@-]+/) {
-                       my %bugs_with_tag; 
-                       @bugs_with_tag{@{$user_tags{$tag}||[]}} = (1) x @{$user_tags{$tag}||[]};
-                       $bugs_with_tag{$ref} = 1;
-                       $user_tags{$tag} = [keys %bugs_with_tag];
-                  }
-             }
-             write_usertags(\%user_tags,$user);
-        }
-        else {
-             $brokenness .= fill_template('mail/invalid_user',
-                                          {user => $user}
-                                         );
-        }
-    }
-    &overwrite("db-h/$hash/$ref.report",
-               join("\n",@msg)."\n");
-}
-
-&checkmaintainers;
-
-print DEBUG "maintainers >".join(' ',@maintaddrs)."<\n";
-
-my $orgsender= defined($header{'sender'}) ? "Original-Sender: $header{'sender'}\n" : '';
-my $newsubject= $subject;  $newsubject =~ s/^$gBug#$ref:*\s*//;
-
-my $xcchdr= $header{ 'x-debbugs-cc' } || '';
-if ($xcchdr =~ m/\S/) {
-    push(@resentccs,$xcchdr);
-    $resentccexplain.= fill_template('mail/xdebbugscc',
-                                    {xcchdr => $xcchdr},
-                                   );
-}
-
-if (@maintaddrs && ($codeletter eq 'B' || $codeletter eq 'M')) {
-    push(@resentccs,@maintaddrs);
-    $resentccexplain.= fill_template('mail/maintainercc',
-                                    {maintaddrs => \@maintaddrs,
-                                    },
-                                   );
-}
-
-@bccs = @addsrcaddrs;
-if (defined $gStrongList and isstrongseverity($data->{severity})) {
-    push @bccs, "$gStrongList\@$gListDomain";
-}
-
-# Send mail to the per bug list subscription too
-push @bccs, "bugs=$ref\@$gListDomain";
-
-if (defined $pheader{source}) {
-    # Prefix source versions with the name of the source package. They
-    # appear that way in version trees so that we can deal with binary
-    # packages moving from one source package to another.
-    if (defined $pheader{'source-version'}) {
-        if ($pheader{'source-version'} !~ m/^$config{package_version_re}$/) {
-             $brokenness .= fill_template('mail/invalid_version',
-                                          {version => $pheader{'source-version'}},
-                                         );
-        }
-        else {
-             addfoundversions($data, $pheader{source}, $pheader{'source-version'}, '');
-        }
-    } elsif (defined $pheader{version}) {
-        if ($pheader{version} !~ m/^$config{package_version_re}$/) {
-             $brokenness .= fill_template('mail/invalid_version',
-                                          {version => $pheader{version}},
-                                         );
-        }
-        else {
-             addfoundversions($data, $pheader{source}, $pheader{version}, '');
-        }
-    }
-    writebug($ref, $data);
-} elsif (defined $pheader{package}) {
-    # TODO: could handle Source-Version: by looking up the source package?
-     if (defined $pheader{version}) {
-         if ($pheader{version} !~ m/^$config{package_version_re}$/) {
-              $brokenness .= fill_template('mail/invalid_version',
-                                           {version => $pheader{version}},
-                                          );
-         }
-         else {
-              addfoundversions($data, $pheader{package}, $pheader{version}, 'binary');
-         }
-     }
-     writebug($ref, $data);
-}
-
-my $veryquiet= $codeletter eq 'Q';
-if ($codeletter eq 'M' && !@maintaddrs) {
-    $veryquiet= 1;
-    $brokenness.= fill_template('mail/invalid_maintainer',
-                               {},
-                              );
-}
-
-my $resentccval.= join(', ',@resentccs);
-$resentccval =~ s/\s+\n\s+/ /g; $resentccval =~ s/^\s+/ /; $resentccval =~ s/\s+$//;
-my $resentcc = '';
-if (length($resentccval)) { 
-    $resentcc= "Resent-CC: $resentccval\n"; 
-}
-
-if ($codeletter eq 'U') { # sent to -submitter
-    &htmllog("Message", "sent on", $data->{originator}, "$gBug#$ref.");
-    &sendmessage(<<END,[$data->{originator},@resentccs],[@bccs]);
-Subject: $gBug#$ref: $newsubject
-Reply-To: $replyto, $ref-quiet\@$gEmailDomain
-${orgsender}Resent-To: $data->{originator}
-${resentcc}Resent-Date: $tdate
-Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
-Resent-Sender: $gMaintainerEmail
-X-$gProject-PR-Message: report $ref
-X-$gProject-PR-Package: $data->{package}
-X-$gProject-PR-Keywords: $data->{keywords}
-${source_pr_header}$fwd
-END
-} elsif ($codeletter eq 'B') { # Sent to submit
-    my $report_followup = $newref ? 'report' : 'followup';
-    &htmllog($newref ? "Report" : "Information", "forwarded",
-             join(', ',"$gSubmitList\@$gListDomain",@resentccs),
-             "<code>$gBug#$ref</code>".
-             (length($data->{package})? "; Package <code>".html_escape($data->{package})."</code>" : '').
-             ".");
-    &sendmessage(<<END,["$gSubmitList\@$gListDomain",@resentccs],[@bccs]);
-Subject: $gBug#$ref: $newsubject
-Reply-To: $replyto, $ref\@$gEmailDomain
-Resent-From: $header{'from'}
-${orgsender}Resent-To: $gSubmitList\@$gListDomain
-${resentcc}Resent-Date: $tdate
-Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
-Resent-Sender: $gMaintainerEmail
-X-$gProject-PR-Message: $report_followup $ref
-X-$gProject-PR-Package: $data->{package}
-X-$gProject-PR-Keywords: $data->{keywords}
-${source_pr_header}$fwd
-END
-} elsif (@resentccs or @bccs) { # Quiet or Maintainer
-    # D and F done far earlier; B just done - so this must be M or Q
-    # We preserve whichever it was in the Reply-To (possibly adding
-    # the $gBug#).
-    my $report_followup = $newref ? 'report' : 'followup';
-    if (@resentccs) {
-        &htmllog($newref ? "Report" : "Information", "forwarded",
-                 $resentccval,
-                 "<code>$gBug#$ref</code>".
-                 (length($data->{package}) ? "; Package <code>".html_escape($data->{package})."</code>" : '').
-                 ".");
-    } else {
-        &htmllog($newref ? "Report" : "Information", "stored",
-                 "",
-                 "<code>$gBug#$ref</code>".
-                 (length($data->{package}) ? "; Package <code>".html_escape($data->{package})."</code>" : '').
-                 ".");
-    }
-    &sendmessage(<<END,[@resentccs],[@bccs]);
-Subject: $gBug#$ref: $newsubject
-Reply-To: $replyto, $ref-$baddressroot\@$gEmailDomain
-Resent-From: $header{'from'}
-${orgsender}Resent-To: $resentccval
-Resent-Date: $tdate
-Resent-Message-ID: <handler.$ref.$nn\@$gEmailDomain>
-Resent-Sender: $gMaintainerEmail
-X-$gProject-PR-Message: $report_followup $ref
-X-$gProject-PR-Package: $data->{package}
-X-$gProject-PR-Keywords: $data->{keywords}
-${source_pr_header}$fwd
-END
-}
-
-my $htmlbreak= length($brokenness) ? "<p>\n".html_escape($brokenness)."\n<p>\n" : '';
-$htmlbreak =~ s/\n\n/\n<P>\n\n/g;
-if (length($resentccval)) {
-    $htmlbreak = "  Copy sent to <code>".html_escape($resentccval)."</code>.".
-        $htmlbreak;
-}
-
-# Should we send an ack out?
-if (not exists $header{'x-debbugs-no-ack'} and
-    ($newref or
-     ($codeletter ne 'U' and
-      (not defined $header{precedence} or
-       $header{'precedence'} !~ /\b(?:bulk|junk|list)\b/
-      )
-     )
-    )
-   ){
-
-     # figure out forward explanation
-     my $forwardexplain = '';
-     my $thanks = '';
-     my $extra_vars;
-     # will contain info and -info in moreinfo messages
-     my $info = '';
-     my $infod = '';
-     # temporary headers
-     my %t_h;
-     if ($newref) {
-         &htmllog("Acknowledgement","sent",$replyto,
-                  ($veryquiet ?
-                   "New $gBug report received and filed, but not forwarded." :
-                   "New $gBug report received and forwarded."). $htmlbreak);
-         $thanks = fill_template('mail/process_ack_thanks_new');
-     }
-     else {
-         &htmllog("Acknowledgement","sent",$replyto,
-                  ($veryquiet ? "Extra info received and filed, but not forwarded." :
-                   $codeletter eq 'M' ? "Extra info received and forwarded to maintainer." :
-                   "Extra info received and forwarded to list."). $htmlbreak);
-         $thanks = fill_template('mail/process_ack_thanks_additional');
-         $info = 'info';
-         $infod = '-info';
-     }
-     if ($veryquiet) {
-         $forwardexplain = fill_template('mail/forward_veryquiet',
-                                        );
-         # these are the headers that quiet messages override
-         $t_h{messageid}  = "<handler.$ref.$nn.ack${info}quiet\@$gEmailDomain>";
-         $t_h{pr_message} = "ack${infod}-quiet $ref";
-         $t_h{reply_to}   = "$ref-quiet\@$gEmailDomain";
-         $extra_vars->{refreplyto} = "$ref-quiet\@$gEmailDomain";
-         $t_h{subject}    = length($info)?
-              "$gBug#$ref: Info received and FILED only ($subject)":
-              "$gBug#$ref: Acknowledgement of QUIET report ($subject)";
-     }
-     elsif ($codeletter eq 'M') {
-         $forwardexplain = fill_template('mail/forward_maintonly',
-                                        );
-         # these are the headers that maintonly messages override
-         $t_h{messageid}  = "<handler.$ref.$nn.ack{$info}maintonly\@$gEmailDomain>";
-         $t_h{pr_message} = "ack${infod}-maintonly $ref";
-         $t_h{reply_to}   = "$ref-maintonly\@$gEmailDomain";
-         $extra_vars->{refreplyto} = "$ref-maintonly\@$gEmailDomain";
-         $t_h{subject}    = length($info)?
-              "$gBug#$ref: Info received for maintainer only ($subject)":
-              "$gBug#$ref: Acknowledgement of maintainer-only report ($subject)";
-     }
-     else {
-         $forwardexplain = fill_template('mail/forward_normal',
-                                        );
-         $t_h{messageid}  = "<handler.$ref.$nn.ack${info}\@$gEmailDomain>";
-         $t_h{pr_message} = "ack${infod} $ref";
-         $t_h{reply_to}   = "$ref\@$gEmailDomain";
-         $extra_vars->{refreplyto} = "$ref\@$gEmailDomain";
-         $t_h{subject}    = (defined $info and length($info))?
-              "$gBug#$ref: Info received ($subject)" :
-              "$gBug#$ref: Acknowledgement ($subject)";
-     }
-     my $body = message_body_template('mail/process_ack',
-                                     {forwardexplain  => $forwardexplain,
-                                      resentccexplain => $resentccexplain,
-                                      thanks          => $thanks,
-                                      %{$extra_vars}
-                                     }
-                                    );
-     &sendmessage(create_mime_message(
-                      ["X-Loop"      => "$gMaintainerEmail",
-                       From          => "$gMaintainerEmail ($gProject $gBug Tracking System)",
-                       To            => $replyto,
-                       Subject       => $t_h{subject},
-                       "Message-ID"  => $t_h{messageid},
-                       "In-Reply-To" => $header{'message-id'},
-                        References    => $header{'message-id'},
-                        Precedence    => 'bulk',
-                       "X-$gProject-PR-Message"  => $t_h{pr_message} || "ack $ref",
-                       "X-$gProject-PR-Package"  => $data->{package},
-                       "X-$gProject-PR-Keywords" => $data->{keywords},
-                       # Only have a X-$gProject-PR-Source when we know the source package
-                       (defined($source_package) and length($source_package))?("X-$gProject-PR-Source" => $source_package):(),
-                       "Reply-To"                => $t_h{reply_to} || "$ref\@$gEmailDomain",
-                      ],$body,[]), '',undef,1);
-}
-
-&appendlog;
-&finish;
-
-sub overwrite {
-    my ($f,$v) = @_;
-    open(NEW,">$f.new") || &quit("$f.new: create: $!");
-    print(NEW "$v") || &quit("$f.new: write: $!");
-    close(NEW) || &quit("$f.new: close: $!");
-    rename("$f.new","$f") || &quit("rename $f.new to $f: $!");
-}
-
-sub appendlog {
-    my $hash = get_hashname($ref);
-    if (!open(AP,">>db-h/$hash/$ref.log")) {
-        print DEBUG "failed open log<\n";
-        print DEBUG "failed open log err $!<\n";
-        &quit("opening db-h/$hash/$ref.log (li): $!");
-    }
-    print(AP "\7\n",escape_log(@log),"\n\3\n") || &quit("writing db-h/$hash/$ref.log (li): $!");
-    close(AP) || &quit("closing db-h/$hash/$ref.log (li): $!");
-}
-
-sub finish {
-    my ($exit) = @_;
-    $exit ||= 0;
-    utime(time,time,"db");
-    # cleanups are run in an end block now.
-    #my ($u);
-    #while ($u= $cleanups[$#cleanups]) { &$u; }
-    unlink("incoming/P$nn") || &quit("unlinking incoming/P$nn: $!");
-    exit $exit;
-}
-
-&quit("wot no exit");
-
-sub htmllog {
-    my ($whatobj,$whatverb,$where,$desc) = @_;
-    my $hash = get_hashname($ref);
-    open(AP,">>db-h/$hash/$ref.log") || &quit("opening db-h/$hash/$ref.log (lh): $!");
-    print(AP
-          "\6\n".
-          "<strong>$whatobj $whatverb</strong>".
-          ($where eq '' ? "" : " to <code>".html_escape($where)."</code>").
-          ":<br>\n". $desc.
-          "\n\3\n") || &quit("writing db-h/$hash/$ref.log (lh): $!");
-    close(AP) || &quit("closing db-h/$hash/$ref.log (lh): $!");
-}    
-
-sub stripbccs {
-    my $msg = shift;
-    my $ret = '';
-    my $bcc = 0;
-    while ($msg =~ s/(.*\n)//) {
-       local $_ = $1;
-       if (/^$/) {
-           $ret .= $_;
-           last;
-       }
-       if ($bcc) {
-           # strip continuation lines too
-           next if /^\s/;
-           $bcc = 0;
-       }
-       if (/^Bcc:/i) {
-           $bcc = 1;
-       } else {
-           $ret .= $_;
-       }
-    }
-    return $ret . $msg;
-}
-
-=head2 send_message
-
-     send_message($the_message,\@recipients,\@bcc,$do_not_encode)
-
-The first argument is the scalar message, the second argument is the
-arrayref of recipients, the third is the arrayref of Bcc:'ed
-recipients.
-
-The final argument turns off header encoding and the addition of the
-X-Loop header if true, defaults to false.
-
-=cut
-
-
-sub sendmessage {
-    my ($msg,$recips,$bcc,$no_encode) = @_;
-    if (not defined $recips or (!ref($recips) && $recips eq '')
-       or @$recips == 0) {
-       $recips = ['-t'];
-    }
-    # This is suboptimal. The right solution is to send headers
-    # separately from the rest of the message and encode them rather
-    # than doing this.
-    $msg = "X-Loop: $gMaintainerEmail\n" . $msg unless $no_encode;
-    # The original message received is written out in appendlog, so
-    # before writing out the other messages we've sent out, we need to
-    # RFC1522 encode the header.
-    $msg = encode_headers($msg) unless $no_encode;
-
-    my $hash = get_hashname($ref);
-    #save email to the log
-    open(AP,">>db-h/$hash/$ref.log") || &quit("opening db-h/$hash/$ref.log (lo): $!");
-    print(AP "\2\n",join("\4",@$recips),"\n\5\n",
-          escape_log(stripbccs($msg)),"\n\3\n") ||
-        &quit("writing db-h/$hash/$ref.log (lo): $!");
-    close(AP) || &quit("closing db-h/$hash/$ref.log (lo): $!");
-
-    if (ref($bcc)) {
-        shift @$recips if $recips->[0] eq '-t';
-        push @$recips, @$bcc;
-    }
-
-    send_mail_message(message        => $msg,
-                     # Because we encode the headers above, we do not want to encode them here
-                     encode_headers => 0,
-                     recipients     => $recips);
-}
-
-=head2 message_body_template
-
-     message_body_template('mail/ack',{ref=>'foo'});
-
-Creates a message body using a template
-
-=cut
-
-sub message_body_template{
-     my ($template,$extra_var) = @_;
-     $extra_var ||={};
-     my $body = fill_template($template,$extra_var);
-     return fill_template('mail/message_body',
-                         {%{$extra_var},
-                          body => $body,
-                         },
-                        );
-}
-
-=head2 fill_template
-
-     fill_template('mail/foo',{foo=>'bar'});
-
-Calls fill_in_template with a default set of variables and any extras
-added in.
-
-=cut
-
-sub fill_template{
-     my ($template,$extra_var) = @_;
-     $extra_var ||={};
-     my $variables = {config => \%config,
-                     defined($ref)?(ref    => $ref):(),
-                     defined($data)?(data  => $data):(),
-                     %{$extra_var},
-                    };
-     my $hole_var = {'&bugurl' =>
-                    sub{"$_[0]: ".
-                             'http://'.$config{cgi_domain}.'/'.
-                                  Debbugs::CGI::bug_url($_[0]);
-                   }
-                   };
-     return fill_in_template(template => $template,
-                            variables => $variables,
-                            hole_var  => $hole_var,
-                           );
-}
-
-
-sub checkmaintainers {
-    return if $maintainerschecked++;
-    return if !length($data->{package});
-    my %maintainerof;
-    open(MAINT,"$gMaintainerFile") || die &quit("maintainers open: $!");
-    while (<MAINT>) {
-       m/^\n$/ && next;
-       m/^\s*$/ && next;
-        m/^(\S+)\s+(\S.*\S)\s*\n$/ || &quit("maintainers bogus \`$_'");
-        $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
-       # use the package which is normalized to lower case; we do this because we lc the pseudo headers.
-        $maintainerof{$a}= $2;
-    }
-    close(MAINT);
-    open(MAINT,"$gMaintainerFileOverride") || die &quit("maintainers.override open: $!");
-    while (<MAINT>) {
-       m/^\n$/ && next;
-       m/^\s*$/ && next;
-        m/^(\S+)\s+(\S.*\S)\s*\n$/ || &quit("maintainers.override bogus \`$_'");
-        $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
-       # use the package which is normalized to lower case; we do this because we lc the pseudo headers.
-        $maintainerof{$a}= $2;
-    }
-    close(MAINT);
-    my %pkgsrc;
-    open(SOURCES,"$gPackageSource") || &quit("pkgsrc open: $!");
-    while (<SOURCES>) {
-        next unless m/^(\S+)\s+\S+\s+(\S.*\S)\s*$/;
-       ($a,$b)=($1,$2);
-       $a =~ y/A-Z/a-z/;
-       $pkgsrc{$a} = $b;
-    }
-    close(SOURCES);
-    my $anymaintfound=0; my $anymaintnotfound=0;
-    for my $p (split(m/[ \t?,():]+/,$data->{package})) {
-        $p =~ y/A-Z/a-z/;
-       $p =~ /([a-z0-9.+-]+)/;
-       $p = $1;
-       next unless defined $p;
-       if (defined $gSubscriptionDomain) {
-           if (defined($pkgsrc{$p})) {
-               push @addsrcaddrs, "$pkgsrc{$p}\@$gSubscriptionDomain";
-           } else {
-               push @addsrcaddrs, "$p\@$gSubscriptionDomain";
-           }
-       }
-        if (defined($maintainerof{$p})) {
-           print DEBUG "maintainer add >$p|$maintainerof{$p}<\n";
-            my $addmaint= $maintainerof{$p};
-            push(@maintaddrs,$addmaint) unless
-                $addmaint eq $replyto || grep($_ eq $addmaint, @maintaddrs);
-            $anymaintfound++;
-        } else {
-           print DEBUG "maintainer none >$p<\n";
-           push(@maintaddrs,$gUnknownMaintainerEmail) unless $anymaintnotfound;
-            $anymaintnotfound++;
-            last;
-        }
-    }
-
-    if (defined $data->{owner} and length $data->{owner}) {
-        print DEBUG "owner add >$data->{package}|$data->{owner}<\n";
-        my $addmaint = $data->{owner};
-        push(@maintaddrs, $addmaint) unless
-            $addmaint eq $replyto or grep($_ eq $addmaint, @maintaddrs);
-    }
-}
-
-=head2 bug_list_forward
-
-     bug_list_forward($spool_filename) if $codeletter eq 'L';
-
-
-Given the spool file, will forward a bug to the per bug mailing list
-subscription system.
-
-=cut
-
-sub bug_list_forward{
-     my ($bug_fn) = @_;
-     # Read the bug information and package information for passing to
-     # the mailing list
-     my ($bug_number) = $bug_fn =~ /^L(\d+)\./;
-     my ($bfound, $data)= lockreadbugmerge($bug_number);
-     my $bug_fh = IO::File->new("incoming/P$bug_fn",'r') or die "Unable to open incoming/P$bug_fn $!";
-
-     local $/ = undef;
-     my $bug_message = <$bug_fh>;
-     my ($bug_address) = $bug_message =~ /^Received: \(at ([^\)]+)\) by/;
-     my ($envelope_from) = $bug_message =~ s/\nFrom\s+([^\s]+)[^\n]+\n/\n/;
-     if (not defined $envelope_from) {
-         # Try to use the From: header or something to set it 
-          ($envelope_from) = $bug_message =~ /\nFrom:\s+(.+?)\n/;
-         # Kludgy, and should really be using a full scale header
-         # parser to do this.
-         $envelope_from =~ s/^.+?<([^>]+)>.+$/$1/;
-     }
-     my ($header,$body) = split /\n\n/, $bug_message, 2;
-     # Add X-$gProject-PR-Message: list bug_number, package name, and bug title headers
-     $header .= qq(\nX-$gProject-PR-Message: list $bug_number\n).
-         qq(X-$gProject-PR-Package: $data->{package}\n).
-              qq(X-$gProject-PR-Title: $data->{subject})
-              if defined $data;
-     print STDERR "Tried to loop me with $envelope_from\n"
-         and exit 1 if $envelope_from =~ /\Q$gListDomain\E|\Q$gEmailDomain\E/;
-     print DEBUG $envelope_from,qq(\n);
-     # If we don't have a bug address, something has gone horribly wrong.
-     print STDERR "Doesn't match: $bug_address\n" and exit 1 unless defined $bug_address;
-     $bug_address =~ s/\@.+//;
-     print DEBUG "Sending message to bugs=$bug_address\@$gListDomain\n";
-     print DEBUG $header.qq(\n\n).$body;
-     send_mail_message(message        => $header.qq(\n\n).$body,
-                      recipients     => ["bugs=$bug_address\@$gListDomain"],
-                      envelope_from  => $envelope_from,
-                      encode_headers => 0,
-                     );
-     unlink("incoming/P$bug_fn") || &quit("unlinking incoming/P$bug_fn: $!");
-     exit 0;
-}
diff --git a/scripts/processall b/scripts/processall
new file mode 100755 (executable)
index 0000000..2606b26
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+# $Id: processall.in,v 1.13 2005/10/06 03:46:13 ajt Exp $
+#
+# Usage: processall
+#
+# Uses up: incoming/I<code><bugnum>.nn
+# Temps:   incoming/[GP].nn
+# Creates: incoming/E.nn
+# Stop:    stop
+
+use warnings;
+use strict;
+
+
+use Debbugs::Config qw(:globals);
+use Debbugs::Common qw(:lock);
+
+my $lib_path = $gLibPath;
+
+use File::Path;
+
+chdir( $gSpoolDir ) || die "chdir spool: $!\n";
+
+#open(DEBUG,">&4");
+
+umask(002);
+
+$|=1;
+my %fudged;
+my @ids;
+
+my $ndone = 0;
+&filelock('incoming-cleaner');
+for (;;) {
+    if (-f 'stop') {
+        print(STDERR "stop file created\n") || die $!;
+        last;
+    }
+    if (!@ids) {
+        opendir(DIR,"incoming") || die $!;
+        while ( defined( $_= readdir(DIR) )) { push(@ids,$_) if s/^I//; }
+        last unless @ids;
+        @ids= sort(@ids);
+    }
+    stat("$gMaintainerFile") || die "stat $gMaintainerFile: $!\n";
+    my $nf= @ids;
+    my $id= shift(@ids);
+    unless (rename("incoming/I$id","incoming/G$id")) {
+        if ($fudged{$id}) {
+            die "$id already fudged once! $!\n";
+        }
+        $fudged{$id}= 1;
+        next;
+    }
+    my $c;
+    if ($id =~ m/^[RC]/) {
+        print(STDOUT "[$nf] $id service ...") || die $!;
+        defined($c=fork) || die $!;
+        if (!$c) { exec("$lib_path/service",$id); die "unable to execute $lib_path/service: $!"; }
+    } elsif ($id =~ m/^[BMQFDUL]/) {
+        print(STDOUT "[$nf] $id process ...") || die $!;
+        defined($c=fork) || die $!;
+        if (!$c) { exec("$lib_path/process",$id); die "unable to execute $lib_path/process: $!"; }
+    } else {
+        die "bad name $id";
+    }
+    my $cc=waitpid($c,0); $cc == $c || die "$cc $c $!";
+    my $status=$?;
+    if ($status) {
+        print(STDERR "$id: process failed ($status $!) - now in [PG]$id\n") || die $!;
+    }
+    print(STDOUT " done\n") || die $!;
+    rmtree("$gSpoolDir/mime.tmp",0,1);
+    $ndone++;
+}
+
+
+system("$lib_path/gen-indices",'--quick') == 0 or print STDERR "gen-indices failed\n";
+
+if (@gPostProcessall) {
+    system @gPostProcessall == 0 or print STDERR "\@gPostProcessall failed: ".join(' ',@gPostProcessall)."\n";
+}
+
+
+
+&unfilelock;
+
+exit(0);
diff --git a/scripts/processall.in b/scripts/processall.in
deleted file mode 100755 (executable)
index 2606b26..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/perl
-# $Id: processall.in,v 1.13 2005/10/06 03:46:13 ajt Exp $
-#
-# Usage: processall
-#
-# Uses up: incoming/I<code><bugnum>.nn
-# Temps:   incoming/[GP].nn
-# Creates: incoming/E.nn
-# Stop:    stop
-
-use warnings;
-use strict;
-
-
-use Debbugs::Config qw(:globals);
-use Debbugs::Common qw(:lock);
-
-my $lib_path = $gLibPath;
-
-use File::Path;
-
-chdir( $gSpoolDir ) || die "chdir spool: $!\n";
-
-#open(DEBUG,">&4");
-
-umask(002);
-
-$|=1;
-my %fudged;
-my @ids;
-
-my $ndone = 0;
-&filelock('incoming-cleaner');
-for (;;) {
-    if (-f 'stop') {
-        print(STDERR "stop file created\n") || die $!;
-        last;
-    }
-    if (!@ids) {
-        opendir(DIR,"incoming") || die $!;
-        while ( defined( $_= readdir(DIR) )) { push(@ids,$_) if s/^I//; }
-        last unless @ids;
-        @ids= sort(@ids);
-    }
-    stat("$gMaintainerFile") || die "stat $gMaintainerFile: $!\n";
-    my $nf= @ids;
-    my $id= shift(@ids);
-    unless (rename("incoming/I$id","incoming/G$id")) {
-        if ($fudged{$id}) {
-            die "$id already fudged once! $!\n";
-        }
-        $fudged{$id}= 1;
-        next;
-    }
-    my $c;
-    if ($id =~ m/^[RC]/) {
-        print(STDOUT "[$nf] $id service ...") || die $!;
-        defined($c=fork) || die $!;
-        if (!$c) { exec("$lib_path/service",$id); die "unable to execute $lib_path/service: $!"; }
-    } elsif ($id =~ m/^[BMQFDUL]/) {
-        print(STDOUT "[$nf] $id process ...") || die $!;
-        defined($c=fork) || die $!;
-        if (!$c) { exec("$lib_path/process",$id); die "unable to execute $lib_path/process: $!"; }
-    } else {
-        die "bad name $id";
-    }
-    my $cc=waitpid($c,0); $cc == $c || die "$cc $c $!";
-    my $status=$?;
-    if ($status) {
-        print(STDERR "$id: process failed ($status $!) - now in [PG]$id\n") || die $!;
-    }
-    print(STDOUT " done\n") || die $!;
-    rmtree("$gSpoolDir/mime.tmp",0,1);
-    $ndone++;
-}
-
-
-system("$lib_path/gen-indices",'--quick') == 0 or print STDERR "gen-indices failed\n";
-
-if (@gPostProcessall) {
-    system @gPostProcessall == 0 or print STDERR "\@gPostProcessall failed: ".join(' ',@gPostProcessall)."\n";
-}
-
-
-
-&unfilelock;
-
-exit(0);
diff --git a/scripts/rebuild b/scripts/rebuild
new file mode 100755 (executable)
index 0000000..fd1b927
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/perl -w
+# $Id: rebuild.in,v 1.13 2003/08/23 15:12:57 cjwatson Exp $
+
+# Load modules and set environment
+use File::Copy;
+$config_path = '/etc/debbugs';
+$lib_path = '/usr/lib/debbugs';
+
+require("$config_path/config");
+require("$lib_path/errorlib");
+use vars qw($gSpoolDir);
+
+$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
+
+chdir("$gSpoolDir") || die "chdir spool: $!\n";
+
+#global variables
+$debug = 0;
+
+@ARGV==0 and die  "no archive given on the commandline" ;
+my $archive = shift(@ARGV);
+my $index = "index.$archive";
+$index = 'index.db' if $archive eq 'db-h';
+open IDXFILE, "> $index" or die  "trying to reset index file: $!" ;
+
+#get list of bugs (ie, status files)
+my @files;
+for ($subdir=0; $subdir<100; $subdir++ )
+{
+    my $path = sprintf( "$archive/%.2d", $subdir );
+    opendir(DIR,$path) || next; 
+    my @list= grep(m/^\d+\.summary$/,readdir(DIR));
+    closedir DIR;
+    grep(s/\.summary$//,@list);
+    push @files, @list;
+}
+
+@files = sort { $a <=> $b } @files;
+
+#process each bug (ie, status file)
+for my $ref (@files) 
+{
+    print STDERR "$ref considering\n" if $debug;
+    my $data = readbug($ref, $archive);
+    $data->{severity} =~ y/A-Z/a-z/;
+
+    (my $pkglist = $data->{package}) =~ s/[,\s]+/,/g;
+    $pkglist =~ s/^,+//;
+    $pkglist =~ s/,+$//;
+
+    my $whendone = 'open';
+    $whendone = 'forwarded' if length $data->{forwarded};
+    $whendone = 'done' if length $data->{done};
+
+    printf IDXFILE "%s %d %d %s [%s] %s %s\n",
+       $pkglist, $ref, $data->{date}, $whendone, $data->{originator},
+       $data->{severity}, $data->{keywords};
+}
+
+close IDXFILE;
diff --git a/scripts/rebuild.in b/scripts/rebuild.in
deleted file mode 100755 (executable)
index 6c98f25..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/perl -w
-# $Id: rebuild.in,v 1.13 2003/08/23 15:12:57 cjwatson Exp $
-
-# Load modules and set environment
-use File::Copy;
-$config_path = '/etc/debbugs';
-$lib_path = '/usr/lib/debbugs';
-
-require("$config_path/config");
-require("$lib_path/errorlib");
-use vars qw($gSpoolDir);
-
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-chdir("$gSpoolDir") || die "chdir spool: $!\n";
-
-#global variables
-$debug = 0;
-
-@ARGV==0 and &quit( "no archive given on the commandline" );
-my $archive = shift(@ARGV);
-my $index = "index.$archive";
-$index = 'index.db' if $archive eq 'db-h';
-open IDXFILE, "> $index" or &quit( "trying to reset index file: $!" );
-
-#get list of bugs (ie, status files)
-my @files;
-for ($subdir=0; $subdir<100; $subdir++ )
-{
-    my $path = sprintf( "$archive/%.2d", $subdir );
-    opendir(DIR,$path) || next; 
-    my @list= grep(m/^\d+\.summary$/,readdir(DIR));
-    closedir DIR;
-    grep(s/\.summary$//,@list);
-    push @files, @list;
-}
-
-@files = sort { $a <=> $b } @files;
-
-#process each bug (ie, status file)
-for my $ref (@files) 
-{
-    print STDERR "$ref considering\n" if $debug;
-    my $data = readbug($ref, $archive);
-    $data->{severity} =~ y/A-Z/a-z/;
-
-    (my $pkglist = $data->{package}) =~ s/[,\s]+/,/g;
-    $pkglist =~ s/^,+//;
-    $pkglist =~ s/,+$//;
-
-    my $whendone = 'open';
-    $whendone = 'forwarded' if length $data->{forwarded};
-    $whendone = 'done' if length $data->{done};
-
-    printf IDXFILE "%s %d %d %s [%s] %s %s\n",
-       $pkglist, $ref, $data->{date}, $whendone, $data->{originator},
-       $data->{severity}, $data->{keywords};
-}
-
-close IDXFILE;
diff --git a/scripts/receive b/scripts/receive
new file mode 100755 (executable)
index 0000000..eb101a4
--- /dev/null
@@ -0,0 +1,147 @@
+#!/usr/bin/perl
+# $Id: receive.in,v 1.17 2005/07/24 18:42:41 don Exp $
+# usage: mail is piped directly into program
+
+#set umask in order to have group-writable incoming/*
+#umask(002);
+
+use Debbugs::Config qw(:globals :text);
+my $lib_path = $gLibPath;
+
+$ENV{'PATH'} = '/usr/lib/debbugs:'.$ENV{'PATH'};
+
+#set source of mail delivery
+#sets any prefix needed to get mailer to add it to error mail
+if ( $gMailer eq 'exim' ) 
+{      $gBadEmailPrefix = '';
+       $_ = $ENV{'LOCAL_PART'};
+} elsif ( $gMailer eq 'qmail' )
+{      $gBadEmailPrefix = '//';
+       $_ = $ENV{'DEFAULT'};
+#      $_ = $ENV{'RECIPIENT'};
+#      s/^\w+-bugs--?//;
+} else 
+{      $gBadEmailPrefix = '';
+       $_ = $ARGV[0];
+       s/\>//;
+       s/\<//;
+}
+
+#remove everything from @ to end of line
+s/\@.*$//;
+
+#convert remaining upper case to lower case
+y/A-Z/a-z/;
+
+#set up to determine command
+%withbugaddressmap= ('-submit',     'B',
+                   '',            'B',
+                   '-maintonly',  'M',
+                   '-quiet',      'Q',
+                   '-forwarded',  'F',
+                   '-done',       'D',
+                   '-close',      'D',
+                  '-request',    'R',
+                   '-submitter',  'U',
+                  # Used for bug subscription
+                  #'-list-nothing-will-match-this', 'L',
+                   );
+
+%withpkgaddressmap= ('-request',     'R');
+
+%withoutaddressmap= ('submit',     'B',
+                      'bugs',       'B',
+                      'maintonly',  'M',
+                      'quiet',      'Q',
+                      'forwarded',  'F',
+                      'done',       'D',
+                      'close',      'D',
+                      'request',    'R',
+                      'submitter',  'U',
+                      'control',    'C');
+
+#determine command
+if (s/^(\d{1,9})\b//) {
+    $bugnumber= $1;
+    if (not exists $withbugaddressmap{$_} and
+/-(?:(?:un)?subscribe|subhelp|help|ignore|(?:sub(?:yes|approve|reject)
+ |unsubyes|bounce|probe|approve|reject|
+ setlistyes|setlistsilentyes).*)/x
+       ) {
+        $map = 'L';
+    }
+    else {
+        $map= $withbugaddressmap{$_};
+    }
+    $addrrec= "$bugnumber$_";
+} elsif (s/^(\w+)-//) {
+    $bugnumber= $1;
+    $map= $withpkgaddressmap{"-$_"};
+    $addrrec= "$bugnumber-$_";
+} else {
+    $bugnumber= '';
+    $map= $withoutaddressmap{$_};
+    $addrrec= "$_";
+}
+
+#print no command received
+if (!defined($map)) {
+    print STDERR <<ENDTEXT;
+$gBadEmailPrefix
+$gBadEmailPrefix Unknown $gBug service address $_\@$gEmailDomain.
+$gBadEmailPrefix Recognised addresses are:
+$gBadEmailPrefix
+$gBadEmailPrefix     General:       Read $gBug# in Subject:    $gBug# is NNNN:
+$gBadEmailPrefix
+$gBadEmailPrefix      request        submit  $gBug             NNNN  NNNN-submit
+$gBadEmailPrefix      control        maintonly                NNNN-maintonly
+$gBadEmailPrefix      owner          quiet                    NNNN-quiet
+$gBadEmailPrefix      postmaster     forwarded                NNNN-forwarded
+$gBadEmailPrefix                     done  close              NNNN-done  NNNN-close
+$gBadEmailPrefix                     submitter                NNNN-submitter
+$gBadEmailPrefix
+$gBadEmailPrefix (all \@$gEmailDomain.)
+$gBadEmailPrefix
+$gBadEmailPrefix For instructions via the WWW see:
+$gBadEmailPrefix   http://$gWebDomain/
+$gBadEmailPrefix   http://$gWebDomain/Reporting$gHTMLSuffix
+$gBadEmailPrefix   http://$gWebDomain/Developer$gHTMLSuffix
+$gBadEmailPrefix   http://$gWebDomain/Access$gHTMLSuffix
+$gTextInstructions
+$gBadEmailPrefix For details of how to access $gBug report logs by email:
+$gBadEmailPrefix   send \`request\@$gEmailDomain' the word \`help'
+$gBadEmailPrefix
+ENDTEXT
+    exit(100);
+}
+
+@months=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+($sec,$min,$hour,$mday,$mon,$year)= gmtime(time);
+
+$queue= "$map$bugnumber";
+
+chdir("$gSpoolDir/incoming") || &failure("chdir to spool: $!");
+
+$id= time.$$;
+open(FILE,">T.$id") || &failure("open temporary file: $!");
+printf(FILE "Received: (at %s) by $gEmailDomain; %d %s %d %02d:%02d:%02d +0000\n",
+       $addrrec, $mday,$months[$mon],$year+1900, $hour,$min,$sec) ||
+    &failure("write header to temporary file: $!");
+while(<STDIN>) { print(FILE) || &failure("write temporary file: $!"); }
+close(FILE) || &failure("close temporary file: $!");
+
+my $prefix;
+if ($gSpamScan) {
+    $prefix = 'S';
+} else {
+    $prefix = 'I';
+}
+rename("T.$id","$prefix$queue.$id") || &failure("rename spool message: $!");
+
+exit(0);
+
+sub failure {
+    length($id) && unlink("T.$id");
+    print STDERR "bugs receive failure: @_\n";
+    exit(75); # EX_TEMPFAIL
+}
diff --git a/scripts/receive.in b/scripts/receive.in
deleted file mode 100755 (executable)
index eb101a4..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/perl
-# $Id: receive.in,v 1.17 2005/07/24 18:42:41 don Exp $
-# usage: mail is piped directly into program
-
-#set umask in order to have group-writable incoming/*
-#umask(002);
-
-use Debbugs::Config qw(:globals :text);
-my $lib_path = $gLibPath;
-
-$ENV{'PATH'} = '/usr/lib/debbugs:'.$ENV{'PATH'};
-
-#set source of mail delivery
-#sets any prefix needed to get mailer to add it to error mail
-if ( $gMailer eq 'exim' ) 
-{      $gBadEmailPrefix = '';
-       $_ = $ENV{'LOCAL_PART'};
-} elsif ( $gMailer eq 'qmail' )
-{      $gBadEmailPrefix = '//';
-       $_ = $ENV{'DEFAULT'};
-#      $_ = $ENV{'RECIPIENT'};
-#      s/^\w+-bugs--?//;
-} else 
-{      $gBadEmailPrefix = '';
-       $_ = $ARGV[0];
-       s/\>//;
-       s/\<//;
-}
-
-#remove everything from @ to end of line
-s/\@.*$//;
-
-#convert remaining upper case to lower case
-y/A-Z/a-z/;
-
-#set up to determine command
-%withbugaddressmap= ('-submit',     'B',
-                   '',            'B',
-                   '-maintonly',  'M',
-                   '-quiet',      'Q',
-                   '-forwarded',  'F',
-                   '-done',       'D',
-                   '-close',      'D',
-                  '-request',    'R',
-                   '-submitter',  'U',
-                  # Used for bug subscription
-                  #'-list-nothing-will-match-this', 'L',
-                   );
-
-%withpkgaddressmap= ('-request',     'R');
-
-%withoutaddressmap= ('submit',     'B',
-                      'bugs',       'B',
-                      'maintonly',  'M',
-                      'quiet',      'Q',
-                      'forwarded',  'F',
-                      'done',       'D',
-                      'close',      'D',
-                      'request',    'R',
-                      'submitter',  'U',
-                      'control',    'C');
-
-#determine command
-if (s/^(\d{1,9})\b//) {
-    $bugnumber= $1;
-    if (not exists $withbugaddressmap{$_} and
-/-(?:(?:un)?subscribe|subhelp|help|ignore|(?:sub(?:yes|approve|reject)
- |unsubyes|bounce|probe|approve|reject|
- setlistyes|setlistsilentyes).*)/x
-       ) {
-        $map = 'L';
-    }
-    else {
-        $map= $withbugaddressmap{$_};
-    }
-    $addrrec= "$bugnumber$_";
-} elsif (s/^(\w+)-//) {
-    $bugnumber= $1;
-    $map= $withpkgaddressmap{"-$_"};
-    $addrrec= "$bugnumber-$_";
-} else {
-    $bugnumber= '';
-    $map= $withoutaddressmap{$_};
-    $addrrec= "$_";
-}
-
-#print no command received
-if (!defined($map)) {
-    print STDERR <<ENDTEXT;
-$gBadEmailPrefix
-$gBadEmailPrefix Unknown $gBug service address $_\@$gEmailDomain.
-$gBadEmailPrefix Recognised addresses are:
-$gBadEmailPrefix
-$gBadEmailPrefix     General:       Read $gBug# in Subject:    $gBug# is NNNN:
-$gBadEmailPrefix
-$gBadEmailPrefix      request        submit  $gBug             NNNN  NNNN-submit
-$gBadEmailPrefix      control        maintonly                NNNN-maintonly
-$gBadEmailPrefix      owner          quiet                    NNNN-quiet
-$gBadEmailPrefix      postmaster     forwarded                NNNN-forwarded
-$gBadEmailPrefix                     done  close              NNNN-done  NNNN-close
-$gBadEmailPrefix                     submitter                NNNN-submitter
-$gBadEmailPrefix
-$gBadEmailPrefix (all \@$gEmailDomain.)
-$gBadEmailPrefix
-$gBadEmailPrefix For instructions via the WWW see:
-$gBadEmailPrefix   http://$gWebDomain/
-$gBadEmailPrefix   http://$gWebDomain/Reporting$gHTMLSuffix
-$gBadEmailPrefix   http://$gWebDomain/Developer$gHTMLSuffix
-$gBadEmailPrefix   http://$gWebDomain/Access$gHTMLSuffix
-$gTextInstructions
-$gBadEmailPrefix For details of how to access $gBug report logs by email:
-$gBadEmailPrefix   send \`request\@$gEmailDomain' the word \`help'
-$gBadEmailPrefix
-ENDTEXT
-    exit(100);
-}
-
-@months=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
-($sec,$min,$hour,$mday,$mon,$year)= gmtime(time);
-
-$queue= "$map$bugnumber";
-
-chdir("$gSpoolDir/incoming") || &failure("chdir to spool: $!");
-
-$id= time.$$;
-open(FILE,">T.$id") || &failure("open temporary file: $!");
-printf(FILE "Received: (at %s) by $gEmailDomain; %d %s %d %02d:%02d:%02d +0000\n",
-       $addrrec, $mday,$months[$mon],$year+1900, $hour,$min,$sec) ||
-    &failure("write header to temporary file: $!");
-while(<STDIN>) { print(FILE) || &failure("write temporary file: $!"); }
-close(FILE) || &failure("close temporary file: $!");
-
-my $prefix;
-if ($gSpamScan) {
-    $prefix = 'S';
-} else {
-    $prefix = 'I';
-}
-rename("T.$id","$prefix$queue.$id") || &failure("rename spool message: $!");
-
-exit(0);
-
-sub failure {
-    length($id) && unlink("T.$id");
-    print STDERR "bugs receive failure: @_\n";
-    exit(75); # EX_TEMPFAIL
-}
diff --git a/scripts/service b/scripts/service
new file mode 100755 (executable)
index 0000000..df289c9
--- /dev/null
@@ -0,0 +1,1749 @@
+#!/usr/bin/perl
+# $Id: service.in,v 1.118 2005/10/19 01:22:14 don Exp $
+#
+# Usage: service <code>.nn
+# Temps:  incoming/P<code>.nn
+
+use warnings;
+use strict;
+
+
+use Debbugs::Config qw(:globals :config);
+
+use File::Copy;
+use MIME::Parser;
+
+use Params::Validate qw(:types validate_with);
+
+use Debbugs::Common qw(:util :quit :misc :lock);
+
+use Debbugs::Status qw(:read :status :write :versions);
+
+use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522);
+use Debbugs::Mail qw(send_mail_message);
+use Debbugs::User;
+use Debbugs::Recipients qw(:all);
+use HTML::Entities qw(encode_entities);
+use Debbugs::Versions::Dpkg;
+
+use Debbugs::Status qw(splitpackages);
+
+use Debbugs::CGI qw(html_escape);
+use Debbugs::Control qw(:archive :log :owner);
+use Debbugs::Log qw(:misc);
+use Debbugs::Text qw(:templates);
+
+use Mail::RFC822::Address;
+
+chdir($config{spool_dir}) or
+     die "Unable to chdir to spool_dir '$config{spool_dir}': $!";
+
+my $debug = 0;
+umask(002);
+
+my ($nn,$control) = $ARGV[0] =~ m/^(([RC])\.\d+)$/;
+if (not defined $control or not defined $nn) {
+     die "Bad argument to service.in";
+}
+if (!rename("incoming/G$nn","incoming/P$nn")) {
+    defined $! and $! =~ m/no such file or directory/i and exit 0;
+    die "Failed to rename incoming/G$nn to incoming/P$nn: $!";
+}
+
+my $log_fh = IO::File->new("incoming/P$nn",'r') or
+     die "Unable to open incoming/P$nn for reading: $!";
+my @log=<$log_fh>;
+my @msg=@log;
+close($log_fh);
+
+chomp @msg;
+
+print "###\n",join("##\n",@msg),"\n###\n" if $debug;
+
+# Bug numbers to send e-mail to, hash so that we don't send to the
+# same bug twice.
+my (%bug_affected);
+
+my (@headerlines,@bodylines);
+
+my $parse_output = Debbugs::MIME::parse(join('',@log));
+@headerlines = @{$parse_output->{header}};
+@bodylines = @{$parse_output->{body}};
+
+my %header;
+for (@headerlines) {
+    $_ = decode_rfc1522($_);
+    s/\n\s/ /g;
+    print ">$_<\n" if $debug;
+    if (s/^(\S+):\s*//) {
+       my $v = lc $1;
+       print ">$v=$_<\n" if $debug;
+       $header{$v} = $_;
+    } else {
+       print "!>$_<\n" if $debug;
+    }
+}
+$header{'message-id'} ||= '';
+
+grep(s/\s+$//,@bodylines);
+
+print "***\n",join("\n",@bodylines),"\n***\n" if $debug;
+
+if (defined $header{'resent-from'} && !defined $header{'from'}) {
+    $header{'from'} = $header{'resent-from'};
+}
+
+defined($header{'from'}) || die "no From header";
+
+delete $header{'reply-to'} 
+       if ( defined($header{'reply-to'}) && $header{'reply-to'} =~ m/^\s*$/ );
+
+my $replyto;
+if ( defined($header{'reply-to'}) && $header{'reply-to'} ne "" ) {
+    $replyto = $header{'reply-to'};
+} else {
+    $replyto = $header{'from'};
+}
+
+# This is an error counter which should be incremented every time there is an error.
+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";
+print {$transcript} "Processing commands for $controlrequestaddr:\n\n";
+
+# debug level
+my $dl = 0;
+my $state= 'idle';
+my $lowstate= 'idle';
+my $mergelowstate= 'idle';
+my $midix=0;
+
+my $user = $replyto;
+$user =~ s/,.*//;
+$user =~ s/^.*<(.*)>.*$/$1/;
+$user =~ s/[(].*[)]//;
+$user =~ s/^\s*(\S+)\s+.*$/$1/;
+$user = "" unless (Debbugs::User::is_valid_user($user));
+my $indicated_user = 0;
+
+my $quickabort = 0;
+
+
+if (@gExcludeFromControl and grep {$replyto =~ m/\Q$_\E/} @gExcludeFromControl) {
+       print {$transcript} fill_template('mail/excluded_from_control');
+       $quickabort = 1;
+}
+
+my %limit_pkgs = ();
+my %clonebugs = ();
+my %bcc = ();
+
+
+my @bcc;
+sub addbcc {
+    push @bcc, $_[0] unless grep { $_ eq $_[0] } @bcc;
+}
+
+our $data;
+our $message;
+our $extramessage;
+our $ref;
+
+our $mismatch;
+our $action;
+
+
+# recipients of mail
+my %recipients;
+# affected_packages
+my %affected_packages;
+my $ok = 0;
+my $unknowns = 0;
+my $procline=0;
+for ($procline=0; $procline<=$#bodylines; $procline++) {
+    my $noriginator;
+    my $newsubmitter;
+    my $oldsubmitter;
+    my $newowner;
+    $state eq 'idle' || print "state: $state ?\n";
+    $lowstate eq 'idle' || print "lowstate: $lowstate ?\n";
+    $mergelowstate eq 'idle' || print "mergelowstate: $mergelowstate ?\n";
+    if ($quickabort) {
+         print {$transcript} "Stopping processing here.\n\n";
+        last;
+    }
+    $_= $bodylines[$procline]; s/\s+$//;
+    next unless m/\S/;
+    print {$transcript} "> $_\n";
+    next if m/^\s*\#/;
+    $action= '';
+    if (m/^stop\s*$/i || m/^quit\s*$/i || m/^--\s*$/ || m/^thank(?:s|\s*you)?\s*$/i || m/^kthxbye\s*$/i) {
+       print {$transcript} "Stopping processing here.\n\n";
+        last;
+    } elsif (m/^debug\s+(\d+)$/i && $1 >= 0 && $1 <= 1000) {
+        $dl= $1+0;
+        print {$transcript} "Debug level $dl.\n\n";
+    } elsif (m/^(send|get)\s+\#?(\d{2,})$/i) {
+        $ref= $2+0;
+        &sendlynxdoc("bugreport.cgi?bug=$ref","logs for $gBug#$ref");
+    } elsif (m/^send-detail\s+\#?(\d{2,})$/i) {
+       $ref= $1+0;
+       &sendlynxdoc("bugreport.cgi?bug=$ref&boring=yes",
+                    "detailed logs for $gBug#$ref");
+    } elsif (m/^index(\s+full)?$/i) {
+       print {$transcript} "This BTS function is currently disabled, sorry.\n\n";
+       $errors++;
+       $ok++; # well, it's not really ok, but it fixes #81224 :)
+    } elsif (m/^index-summary\s+by-package$/i) {
+       print {$transcript} "This BTS function is currently disabled, sorry.\n\n";
+       $errors++;
+       $ok++; # well, it's not really ok, but it fixes #81224 :)
+    } elsif (m/^index-summary(\s+by-number)?$/i) {
+       print {$transcript} "This BTS function is currently disabled, sorry.\n\n";
+       $errors++;
+       $ok++; # well, it's not really ok, but it fixes #81224 :)
+    } elsif (m/^index(\s+|-)pack(age)?s?$/i) {
+       &sendlynxdoc("pkgindex.cgi?indexon=pkg",'index of packages');
+    } elsif (m/^index(\s+|-)maints?$/i) {
+       &sendlynxdoc("pkgindex.cgi?indexon=maint",'index of maintainers');
+    } elsif (m/^index(\s+|-)maint\s+(\S+)$/i) {
+       my $maint = $2;
+       &sendlynxdoc("pkgreport.cgi?maint=" . urlsanit($maint),
+                    "$gBug list for maintainer \`$maint'");
+        $ok++;
+    } elsif (m/^index(\s+|-)pack(age)?s?\s+(\S.*\S)$/i) {
+       my $package = $+;
+       &sendlynxdoc("pkgreport.cgi?pkg=" . urlsanit($package),
+                    "$gBug list for package $package");
+        $ok++;
+    } elsif (m/^send-unmatched(\s+this|\s+-?0)?$/i) {
+       print {$transcript} "This BTS function is currently disabled, sorry.\n\n";
+       $errors++;
+       $ok++; # well, it's not really ok, but it fixes #81224 :)
+    } elsif (m/^send-unmatched\s+(last|-1)$/i) {
+       print {$transcript} "This BTS function is currently disabled, sorry.\n\n";
+       $errors++;
+       $ok++; # well, it's not really ok, but it fixes #81224 :)
+    } elsif (m/^send-unmatched\s+(old|-2)$/i) {
+       print {$transcript} "This BTS function is currently disabled, sorry.\n\n";
+       $errors++;
+       $ok++; # well, it's not really ok, but it fixes #81224 :)
+    } elsif (m/^getinfo\s+([\w.-]+)$/i) {
+        # the following is basically a Debian-specific kludge, but who cares
+        my $req = $1;
+       if ($req =~ /^maintainers$/i && -f "$gConfigDir/Maintainers") {
+           &sendinfo("local", "$gConfigDir/Maintainers", "Maintainers file");
+       } elsif ($req =~ /^override\.(\w+)\.([\w.-]+)$/i) {
+           $req =~ s/.gz$//;
+           &sendinfo("ftp.d.o", "$req", "override file for $2 part of $1 distribution");
+       } elsif ($req =~ /^pseudo-packages\.(description|maintainers)$/i && -f "$gConfigDir/$req") {
+           &sendinfo("local", "$gConfigDir/$req", "$req file");
+       } else {
+           print {$transcript} "Info file $req does not exist.\n\n";
+       }
+    } elsif (m/^help/i) {
+        &sendhelp;
+        print {$transcript} "\n";
+        $ok++;
+    } elsif (m/^refcard/i) {
+        &sendtxthelp("bug-mailserver-refcard.txt","mail servers' reference card");
+    } 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
+to send them to you.
+soon: MAILINGLISTS_TEXT
+END
+    } elsif (m/^unsubscribe/i) {
+        print {$transcript} <<END;
+soon: UNSUBSCRIBE_TEXT
+soon: MAILINGLISTS_TEXT
+END
+    } elsif (m/^user\s+(\S+)\s*$/i) {
+        my $newuser = $1;
+       if (Debbugs::User::is_valid_user($newuser)) {
+           my $olduser = ($user ne "" ? " (was $user)" : "");
+            print {$transcript} "Setting user to $newuser$olduser.\n";
+           $user = $newuser;
+           $indicated_user = 1;
+       } else {
+           print {$transcript} "Selected user id ($newuser) invalid, sorry\n";
+           $errors++;
+           $user = "";
+           $indicated_user = 1;
+       }
+    } elsif (m/^usercategory\s+(\S+)(\s+\[hidden\])?\s*$/i) {
+        $ok++;
+       my $catname = $1;
+       my $hidden = ($2 ne "");
+
+        my $prefix = "";
+        my @cats;
+        my $bad = 0;
+       my $catsec = 0;
+       if ($user eq "") {
+           print {$transcript} "No valid user selected\n";
+           $errors++;
+           next;
+        }
+       if (not $indicated_user and defined $user) {
+            print {$transcript} "User is $user\n";
+            $indicated_user = 1;
+       }
+       my @ords = ();
+       while (++$procline <= $#bodylines) {
+            unless ($bodylines[$procline] =~ m/^\s*([*+])\s*(\S.*)$/) {
+                $procline--;
+                last;
+            }
+            print {$transcript} "> $bodylines[$procline]\n";
+            next if $bad;
+            my ($o, $txt) = ($1, $2);
+            if ($#cats == -1 && $o eq "+") {
+                print {$transcript} "User defined category specification must start with a category name. Skipping.\n\n";
+               $errors++;
+                $bad = 1;
+                next;
+            }
+            if ($o eq "+") {
+               unless (ref($cats[-1]) eq "HASH") {
+                   $cats[-1] = { "nam" => $cats[-1], 
+                                 "pri" => [], "ttl" => [] };
+               }
+               $catsec++;
+               my ($desc, $ord, $op);
+                if ($txt =~ m/^(.*\S)\s*\[((\d+):\s*)?\]\s*$/) {
+                    $desc = $1; $ord = $3; $op = "";
+                } elsif ($txt =~ m/^(.*\S)\s*\[((\d+):\s*)?(\S+)\]\s*$/) {
+                    $desc = $1; $ord = $3; $op = $4;
+                } elsif ($txt =~ m/^([^[\s]+)\s*$/) {
+                    $desc = ""; $op = $1;
+                } else {
+                    print {$transcript} "Unrecognised syntax for category section. Skipping.\n\n";
+                   $errors++;
+                    $bad = 1;
+                    next;
+                }
+               $ord = 999 unless defined $ord;
+
+               if ($op) {
+                    push @{$cats[-1]->{"pri"}}, $prefix . $op;
+                   push @{$cats[-1]->{"ttl"}}, $desc;
+                   push @ords, "$ord $catsec";
+               } else {
+                   $cats[-1]->{"def"} = $desc;
+                   push @ords, "$ord DEF";
+                   $catsec--;
+               }
+               @ords = sort { my ($a1, $a2, $b1, $b2) = split / /, "$a $b";
+                              $a1 <=> $b1 || $a2 <=> $b2; } @ords;
+               $cats[-1]->{"ord"} = [map { m/^.* (\S+)/; $1 eq "DEF" ? $catsec + 1 : $1 } @ords];
+            } elsif ($o eq "*") {
+               $catsec = 0;
+                my ($name);
+                if ($txt =~ m/^(.*\S)(\s*\[(\S+)\])\s*$/) {
+                    $name = $1; $prefix = $3;
+                } else {
+                    $name = $txt; $prefix = "";
+                }
+                push @cats, $name;
+            }
+        }
+        # XXX: got @cats, now do something with it
+       my $u = Debbugs::User::get_user($user);
+       if (@cats) {
+           print {$transcript} "Added usercategory $catname.\n\n";
+           $u->{"categories"}->{$catname} = [ @cats ];
+           if (not $hidden) {
+                push @{$u->{visible_cats}},$catname;
+           }
+       } else {
+           print {$transcript} "Removed usercategory $catname.\n\n";
+           delete $u->{"categories"}->{$catname};
+           @{$u->{visible_cats}} = grep {$_ ne $catname} @{$u->{visible_cats}};
+       }
+       $u->write();
+    } elsif (m/^usertags?\s+\#?(-?\d+)\s+(([=+-])\s*)?(\S.*)?$/i) {
+       $ok++;
+       $ref = $1;
+       my $addsubcode = $3 || "+";
+       my $tags = $4;
+       if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
+            $ref = $clonebugs{$ref};
+        }
+       if ($user eq "") {
+           print {$transcript} "No valid user selected\n";
+           $errors++;
+           $indicated_user = 1;
+        } elsif (&setbug) {
+           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 = ();
+           my %chtags;
+           for my $t (split /[,\s]+/, $tags) {
+               if ($t =~ m/^[a-zA-Z0-9.+\@-]+$/) {
+                   $chtags{$t} = 1;
+               } else {
+                   push @badtags, $t;
+               }
+           }
+           if (@badtags) {
+                print {$transcript} "Ignoring illegal tag/s: ".join(', ', @badtags).".\nPlease use only alphanumerics, at, dot, plus and dash.\n";
+               $errors++;
+           }
+            for my $t (keys %chtags) {
+               $ut{$t} = [] unless defined $ut{$t};
+           }
+           for my $t (keys %ut) {
+               my %res = map { ($_, 1) } @{$ut{$t}};
+               push @oldtags, $t if defined $res{$ref};
+               my $addop = ($addsubcode eq "+" or $addsubcode eq "=");
+               my $del = (defined $chtags{$t} ? $addsubcode eq "-" 
+                                              : $addsubcode eq "=");
+               $res{$ref} = 1 if ($addop && defined $chtags{$t});
+               delete $res{$ref} if ($del);
+               push @newtags, $t if defined $res{$ref};
+               $ut{$t} = [ sort { $a <=> $b } (keys %res) ];
+           }
+           if (@oldtags == 0) {
+               print {$transcript} "There were no usertags set.\n";
+           } else {
+               print {$transcript} "Usertags were: " . join(" ", @oldtags) . ".\n";
+           }
+           print {$transcript} "Usertags are now: " . join(" ", @newtags) . ".\n";
+           Debbugs::User::write_usertags(\%ut, $user);
+       }
+    } elsif (!$control) {
+        print {$transcript} <<END;
+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;
+       $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},
+                                 );
+                   $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+(\S+)(?:\s+(\d.*))?$/i) {
+        $ok++;
+        $ref= $1;
+       my $newpackage= $2;
+       $bug_affected{$ref}=1;
+        my $version= $3;
+       $newpackage =~ y/A-Z/a-z/;
+        if (&setbug) {
+            if (length($data->{package})) {
+                $action= "$gBug reassigned from package \`$data->{package}'".
+                         " to \`$newpackage'.";
+            } else {
+                $action= "$gBug assigned to package \`$newpackage'.";
+            }
+            do {
+               $affected_packages{$data->{package}} = 1;
+                add_recipients(data => $data, recipients => \%recipients);
+                $data->{package}= $newpackage;
+                $data->{found_versions}= [];
+                $data->{fixed_versions}= [];
+                # TODO: what if $newpackage is a source package?
+                addfoundversions($data, $data->{package}, $version, 'binary');
+                add_recipients(data => $data, recipients => \%recipients);
+            } while (&getnextbug);
+        }
+    } elsif (m/^reopen\s+\#?(-?\d+)$/i ? ($noriginator='', 1) :
+             m/^reopen\s+\#?(-?\d+)\s+\=$/i ? ($noriginator='', 1) :
+             m/^reopen\s+\#?(-?\d+)\s+\!$/i ? ($noriginator=$replyto, 1) :
+             m/^reopen\s+\#?(-?\d+)\s+(\S.*\S)$/i ? ($noriginator=$2, 1) : 0) {
+        $ok++;
+        $ref= $1;
+       $bug_affected{$ref}=1;
+        if (&setbug) {
+            if (@{$data->{fixed_versions}}) {
+                print {$transcript} "'reopen' may be inappropriate when a bug has been closed with a version;\nyou may need to use 'found' to remove fixed versions.\n";
+            }
+            if (!length($data->{done})) {
+                print {$transcript} "$gBug is already open, cannot reopen.\n\n";
+                &nochangebug;
+            } else {
+                $action=
+                    $noriginator eq '' ? "$gBug reopened, originator not changed." :
+                        "$gBug reopened, originator set to $noriginator.";
+                do {
+                   $affected_packages{$data->{package}} = 1;
+                    add_recipients(data => $data, recipients => \%recipients);
+                    $data->{originator}= $noriginator eq '' ?  $data->{originator} : $noriginator;
+                    $data->{fixed_versions}= [];
+                    $data->{done}= '';
+                } while (&getnextbug);
+            }
+        }
+    } elsif (m{^found\s+\#?(-?\d+)
+              (?:\s+((?:$config{package_name_re}\/)?
+                   $config{package_version_re}))?$}ix) {
+        $ok++;
+        $ref= $1;
+        my $version= $2;
+        if (&setbug) {
+            if (!length($data->{done}) and not defined($version)) {
+                print {$transcript} "$gBug is already open, cannot reopen.\n\n";
+               $errors++;
+                &nochangebug;
+            } else {
+                $action=
+                    defined($version) ?
+                        "$gBug marked as found in version $version." :
+                        "$gBug reopened.";
+                do {
+                    $affected_packages{$data->{package}} = 1;
+                   add_recipients(data => $data, recipients => \%recipients);
+                    # The 'done' field gets a bit weird with version
+                    # tracking, because a bug may be closed by multiple
+                    # people in different branches. Until we have something
+                    # more flexible, we set it every time a bug is fixed,
+                    # and clear it when a bug is found in a version greater
+                   # than any version in which the bug is fixed or when
+                   # a bug is found and there is no fixed version
+                   if (defined $version) {
+                       my ($version_only) = $version =~ m{([^/]+)$};
+                        addfoundversions($data, $data->{package}, $version, 'binary');
+                       my @fixed_order = sort {Debbugs::Versions::Dpkg::vercmp($a,$b);}
+                            map {s{.+/}{}; $_;} @{$data->{fixed_versions}};
+                       if (not @fixed_order or (Debbugs::Versions::Dpkg::vercmp($version_only,$fixed_order[-1]) >= 0)) {
+                            $action = "$gBug marked as found in version $version and reopened."
+                                 if length $data->{done};
+                            $data->{done} = '';
+                       }
+                    } else {
+                        # Versionless found; assume old-style "not fixed at
+                        # all".
+                        $data->{fixed_versions} = [];
+                        $data->{done} = '';
+                    }
+                } while (&getnextbug);
+            }
+        }
+    } elsif (m[^notfound\s+\#?(-?\d+)\s+
+              ((?:$config{package_name_re}\/)?
+                   \S+)\s*$]ix) {
+        $ok++;
+        $ref= $1;
+        my $version= $2;
+        if (&setbug) {
+            $action= "$gBug no longer marked as found in version $version.";
+            if (length($data->{done})) {
+                $extramessage= "(By the way, this $gBug is currently marked as done.)\n";
+            }
+            do {
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data, recipients => \%recipients);
+                removefoundversions($data, $data->{package}, $version, 'binary');
+            } while (&getnextbug);
+       }
+   }
+    elsif (m[^fixed\s+\#?(-?\d+)\s+
+            ((?:$config{package_name_re}\/)?
+                 $config{package_version_re})\s*$]ix) {
+        $ok++;
+        $ref= $1;
+        my $version= $2;
+        if (&setbug) {
+            $action=
+                 defined($version) ?
+                      "$gBug marked as fixed in version $version." :
+                           "$gBug reopened.";
+                do {
+                    $affected_packages{$data->{package}} = 1;
+                   add_recipients(data => $data, recipients => \%recipients);
+                    addfixedversions($data, $data->{package}, $version, 'binary');
+              } while (&getnextbug);
+       }
+   }
+    elsif (m[^notfixed\s+\#?(-?\d+)\s+
+            ((?:$config{package_name_re}\/)?
+                 \S+)\s*$]ix) {
+        $ok++;
+        $ref= $1;
+        my $version= $2;
+        if (&setbug) {
+            $action=
+                 defined($version) ?
+                      "$gBug no longer marked as fixed in version $version." :
+                           "$gBug reopened.";
+                do {
+                    $affected_packages{$data->{package}} = 1;
+                   add_recipients(data => $data, recipients => \%recipients);
+                    removefixedversions($data, $data->{package}, $version, 'binary');
+              } while (&getnextbug);
+       }
+   }
+    elsif (m/^submitter\s+\#?(-?\d+)\s+\!$/i ? ($newsubmitter=$replyto, 1) :
+             m/^submitter\s+\#?(-?\d+)\s+(\S.*\S)$/i ? ($newsubmitter=$2, 1) : 0) {
+        $ok++;
+        $ref= $1;
+       $bug_affected{$ref}=1;
+        if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
+            $ref = $clonebugs{$ref};
+        }
+       if (not Mail::RFC822::Address::valid($newsubmitter)) {
+            transcript("$newsubmitter is not a valid e-mail address; not changing submitter\n");
+            $errors++;
+       }
+        elsif (&getbug) {
+            if (&checkpkglimit) {
+                &foundbug;
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data, recipients => \%recipients);
+                $oldsubmitter= $data->{originator};
+                $data->{originator}= $newsubmitter;
+                $action= "Changed $gBug submitter from $oldsubmitter to $newsubmitter.";
+                &savebug;
+                print {$transcript} "$action\n";
+                if (length($data->{done})) {
+                    print {$transcript} "(By the way, that $gBug is currently marked as done.)\n";
+                }
+                print {$transcript} "\n";
+                $message= <<END;
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $oldsubmitter
+Subject: $gBug#$ref submitter address changed
+         ($header{'subject'})
+References: $header{'message-id'} $data->{msgid}
+In-Reply-To: $data->{msgid}
+Message-ID: <handler.$ref.$nn.newsubmitter.$midix\@$gEmailDomain>
+Reply-To: $ref\@$gEmailDomain
+X-$gProject-PR-Message: submitter-changed $ref
+
+The submitter address recorded for your $gBug report
+#$ref: $data->{subject}
+has been changed.
+
+The old submitter address for this report was
+$oldsubmitter.
+The new submitter address is
+$newsubmitter.
+
+This change was made by
+$replyto.
+If it was incorrect, please contact them directly.
+
+$gMaintainer
+(administrator, $gProject $gBugs database)
+
+END
+                &sendmailmessage($message,$oldsubmitter);
+            } else {
+                &cancelbug;
+            }
+        } else {
+            &notfoundbug;
+        }
+    } elsif (m/^forwarded\s+\#?(-?\d+)\s+(\S.*\S)$/i) {
+        $ok++;
+        $ref= $1;
+       my $whereto= $2;
+       $bug_affected{$ref}=1;
+        if (&setbug) {
+            if (length($data->{forwarded})) {
+    $action= "Forwarded-to-address changed from $data->{forwarded} to $whereto.";
+            } else {
+    $action= "Noted your statement that $gBug has been forwarded to $whereto.";
+            }
+            if (length($data->{done})) {
+                $extramessage= "(By the way, this $gBug is currently marked as done.)\n";
+            }
+            do {
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data,
+                              recipients => \%recipients,
+                              actions_taken => {forwarded => 1},
+                             );
+               $data->{forwarded}= $whereto;
+            } while (&getnextbug);
+        }
+    } elsif (m/^notforwarded\s+\#?(-?\d+)$/i) {
+        $ok++;
+        $ref= $1;
+       $bug_affected{$ref}=1;
+        if (&setbug) {
+            if (!length($data->{forwarded})) {
+                print {$transcript} "$gBug is not marked as having been forwarded.\n\n";
+                &nochangebug;
+            } else {
+    $action= "Removed annotation that $gBug had been forwarded to $data->{forwarded}.";
+                do {
+                    $affected_packages{$data->{package}} = 1;
+                   add_recipients(data => $data, recipients => \%recipients);
+                    $data->{forwarded}= '';
+                } while (&getnextbug);
+            }
+        }
+    } elsif (m/^severity\s+\#?(-?\d+)\s+([-0-9a-z]+)$/i ||
+       m/^priority\s+\#?(-?\d+)\s+([-0-9a-z]+)$/i) {
+        $ok++;
+        $ref= $1;
+       $bug_affected{$ref}=1;
+        my $newseverity= $2;
+        if (!grep($_ eq $newseverity, @gSeverityList, "$gDefaultSeverity")) {
+            print {$transcript} "Severity level \`$newseverity' is not known.\n".
+                 "Recognized are: $gShowSeverities.\n\n";
+           $errors++;
+        } elsif (exists $gObsoleteSeverities{$newseverity}) {
+            print {$transcript} "Severity level \`$newseverity' is obsolete. " .
+                "Use $gObsoleteSeverities{$newseverity} instead.\n\n";
+               $errors++;
+        } elsif (&setbug) {
+            my $printseverity= $data->{severity};
+            $printseverity= "$gDefaultSeverity" if $printseverity eq '';
+           $action= "Severity set to \`$newseverity' from \`$printseverity'";
+           do {
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data, recipients => \%recipients);
+                if (defined $gStrongList and isstrongseverity($newseverity)) {
+                    addbcc("$gStrongList\@$gListDomain");
+                }
+                $data->{severity}= $newseverity;
+            } while (&getnextbug);
+        }
+    } elsif (m/^tags?\s+\#?(-?\d+)\s+(([=+-])\s*)?(\S.*)?$/i) {
+       $ok++;
+       $ref = $1;
+       my $addsubcode = $3;
+       my $tags = $4;
+       $bug_affected{$ref}=1;
+       my $addsub = "add";
+       if (defined $addsubcode) {
+           $addsub = "sub" if ($addsubcode eq "-");
+           $addsub = "add" if ($addsubcode eq "+");
+           $addsub = "set" if ($addsubcode eq "=");
+       }
+       my @okaytags = ();
+       my @badtags = ();
+       foreach my $t (split /[\s,]+/, $tags) {
+           if (!grep($_ eq $t, @gTags)) {
+               push @badtags, $t;
+           } else {
+               push @okaytags, $t;
+           }
+       }
+       if (@badtags) {
+            print {$transcript} "Unknown tag/s: ".join(', ', @badtags).".\n".
+                "Recognized are: ".join(' ', @gTags).".\n\n";
+           $errors++;
+       }
+       if (&setbug) {
+           if ($data->{keywords} eq '') {
+               print {$transcript} "There were no tags set.\n";
+           } else {
+               print {$transcript} "Tags were: $data->{keywords}\n";
+           }
+           if ($addsub eq "set") {
+               $action= "Tags set to: " . join(", ", @okaytags);
+           } elsif ($addsub eq "add") {
+               $action= "Tags added: " . join(", ", @okaytags);
+           } elsif ($addsub eq "sub") {
+               $action= "Tags removed: " . join(", ", @okaytags);
+           }
+           do {
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data, recipients => \%recipients);
+               $data->{keywords} = '' if ($addsub eq "set");
+               # Allow removing obsolete tags.
+               if ($addsub eq "sub") {
+                   foreach my $t (@badtags) {
+                       $data->{keywords} = join ' ', grep $_ ne $t, 
+                           split ' ', $data->{keywords};
+                   }
+               }
+               # Now process all other additions and subtractions.
+               foreach my $t (@okaytags) {
+                   $data->{keywords} = join ' ', grep $_ ne $t, 
+                       split ' ', $data->{keywords};
+                   $data->{keywords} = "$t $data->{keywords}" unless($addsub eq "sub");
+               }
+               $data->{keywords} =~ s/\s*$//;
+            } while (&getnextbug);
+       }
+    } elsif (m/^(un)?block\s+\#?(-?\d+)\s+(by|with)\s+(\S.*)?$/i) {
+       $ok++;
+       my $bugnum = $2; my $blockers = $4;
+       my $addsub = "add";
+       $addsub = "sub" if ($1 eq "un");
+       if ($bugnum =~ m/^-\d+$/ && defined $clonebugs{$bugnum}) {
+            $bugnum = $clonebugs{$bugnum};
+       }
+
+       my @okayblockers;
+       my @badblockers;
+       foreach my $b (split /[\s,]+/, $blockers) {
+           $b=~s/^\#//;
+           if ($b=~/[0-9]+/) {
+               $ref=$b;
+               if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
+                    $ref = $clonebugs{$ref};
+               }
+               if (&getbug) {
+                   &foundbug;
+                   push @okayblockers, $ref;
+
+                   # add to the list all bugs that are merged with $b,
+                   # because all of their data must be kept in sync
+                   my @thisbugmergelist= split(/ /,$data->{mergedwith});
+                   &cancelbug;
+
+                   foreach $ref (@thisbugmergelist) {
+                       if (&getbug) {
+                          push @okayblockers, $ref;
+                          &cancelbug;
+                       }
+                   }
+               }
+               else {
+                   &notfoundbug;
+                    push @badblockers, $ref;
+               }
+           }
+           else {
+                push @badblockers, $b;
+           }
+       }
+       if (@badblockers) {
+            print {$transcript} "Unknown blocking bug/s: ".join(', ', @badblockers).".\n";
+           $errors++;
+       }
+       
+       $ref=$bugnum;
+       if (&setbug) {
+           if ($data->{blockedby} eq '') {
+               print {$transcript} "Was not blocked by any bugs.\n";
+           } else {
+               print {$transcript} "Was blocked by: $data->{blockedby}\n";
+           }
+           if ($addsub eq "set") {
+               $action= "Blocking bugs of $bugnum set to: " . join(", ", @okayblockers);
+           } elsif ($addsub eq "add") {
+               $action= "Blocking bugs of $bugnum added: " . join(", ", @okayblockers);
+           } elsif ($addsub eq "sub") {
+               $action= "Blocking bugs of $bugnum removed: " . join(", ", @okayblockers);
+           }
+           my %removedblocks;
+           my %addedblocks;
+           do {
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data, recipients => \%recipients);
+               my @oldblockerlist = split ' ', $data->{blockedby};
+               $data->{blockedby} = '' if ($addsub eq "set");
+               foreach my $b (@okayblockers) {
+                       $data->{blockedby} = manipset($data->{blockedby}, $b,
+                               ($addsub ne "sub"));
+               }
+
+               foreach my $b (@oldblockerlist) {
+                       if (! grep { $_ eq $b } split ' ', $data->{blockedby}) {
+                               push @{$removedblocks{$b}}, $ref;
+                       }
+               }
+               foreach my $b (split ' ', $data->{blockedby}) {
+                       if (! grep { $_ eq $b } @oldblockerlist) {
+                               push @{$addedblocks{$b}}, $ref;
+                       }
+               }
+            } while (&getnextbug);
+
+           # Now that the blockedby data is updated, change blocks data
+           # to match the changes.
+           foreach $ref (keys %addedblocks) {
+               if (&getbug) {
+                   foreach my $b (@{$addedblocks{$ref}}) {
+                       $data->{blocks} = manipset($data->{blocks}, $b, 1);
+                   }
+                   &savebug;
+                }
+           }
+           foreach $ref (keys %removedblocks) {
+               if (&getbug) {
+                   foreach my $b (@{$removedblocks{$ref}}) {
+                       $data->{blocks} = manipset($data->{blocks}, $b, 0);
+                   }
+                   &savebug;
+                }
+           }
+       }
+    } elsif (m/^retitle\s+\#?(-?\d+)\s+(\S.*\S)\s*$/i) {
+        $ok++;
+        $ref= $1; my $newtitle= $2;
+       $bug_affected{$ref}=1;
+       if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
+           $ref = $clonebugs{$ref};
+       }
+        if (&getbug) {
+            if (&checkpkglimit) {
+                &foundbug;
+                $affected_packages{$data->{package}} = 1;
+               add_recipients(data => $data, recipients => \%recipients);
+               my $oldtitle = $data->{subject};
+                $data->{subject}= $newtitle;
+                $action= "Changed $gBug title to `$newtitle' from `$oldtitle'.";
+                &savebug;
+                print {$transcript} "$action\n";
+                if (length($data->{done})) {
+                    print {$transcript} "(By the way, that $gBug is currently marked as done.)\n";
+                }
+                print {$transcript} "\n";
+            } else {
+                &cancelbug;
+            }
+        } else {
+            &notfoundbug;
+        }
+    } 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);
+                   $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);
+           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);
+               @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);
+               @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);
+               @{$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;
+               print {$transcript} "Ignoring bugs not assigned to: " .
+                       join(" ", keys(%limit_pkgs)) . "\n\n";
+       } else {
+               %limit_pkgs = ();
+               print {$transcript} "Not ignoring any bugs.\n\n";
+       }
+    } elsif (m/^owner\s+\#?(-?\d+)\s+((?:\S.*\S)|\!)\s*$/i) {
+       $ok++;
+        $ref = $1;
+       my $newowner = $2;
+       if ($newowner eq '!') {
+           $newowner = $replyto;
+       }
+       $bug_affected{$ref} = 1;
+       eval {
+           owner(bug          => $ref,
+                 transcript   => $transcript,
+                 ($dl > 0 ? (debug => $transcript):()),
+                 requester    => $header{from},
+                 request_addr => $controlrequestaddr,
+                 message      => \@log,
+                 recipients   => \%recipients,
+                 owner        => $newowner,
+                );
+       };
+    } elsif (m/^noowner\s+\#?(-?\d+)\s*$/i) {
+        $ok++;
+        $ref = $1;
+       $bug_affected{$ref} = 1;
+       eval {
+           owner(bug          => $ref,
+                 transcript   => $transcript,
+                 ($dl > 0 ? (debug => $transcript):()),
+                 requester    => $header{from},
+                 request_addr => $controlrequestaddr,
+                 message      => \@log,
+                 recipients   => \%recipients,
+                 owner        => undef,
+                );
+       };
+       if ($@) {
+           $errors++;
+           print {$transcript} "Failed to mark $ref as not having an owner: $@";
+       }
+    } elsif (m/^unarchive\s+#?(\d+)$/i) {
+        $ok++;
+        $ref = $1;
+        $bug_affected{$ref} = 1;
+        eval {
+             bug_unarchive(bug        => $ref,
+                           transcript => $transcript,
+                           ($dl > 0 ? (debug => $transcript):()),
+                           affected_bugs => \%bug_affected,
+                           requester => $header{from},
+                           request_addr => $controlrequestaddr,
+                           message => \@log,
+                           recipients => \%recipients,
+                          );
+        };
+        if ($@) {
+             $errors++;
+        }
+    } elsif (m/^archive\s+#?(\d+)$/i) {
+        $ok++;
+        $ref = $1;
+        $bug_affected{$ref} = 1;
+        eval {
+             bug_archive(bug => $ref,
+                         transcript => $transcript,
+                         ($dl > 0 ? (debug => $transcript):()),
+                         ignore_time => 1,
+                         archive_unarchived => 0,
+                         affected_bugs => \%bug_affected,
+                         requester => $header{from},
+                         request_addr => $controlrequestaddr,
+                         message => \@log,
+                         recipients => \%recipients,
+                        );
+        };
+        if ($@) {
+             $errors++;
+        }
+    } else {
+        print {$transcript} "Unknown command or malformed arguments to command.\n\n";
+       $errors++;
+        if (++$unknowns >= 5) {
+            print {$transcript} "Too many unknown commands, stopping here.\n\n";
+            last;
+        }
+    }
+}
+if ($procline>$#bodylines) {
+    print {$transcript} ">\nEnd of message, stopping processing here.\n\n";
+}
+if (!$ok && !$quickabort) {
+    $errors++;
+    print {$transcript} "No commands successfully parsed; sending the help text(s).\n";
+    &sendhelp;
+    print {$transcript} "\n";
+}
+
+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 $repliedshow= join(', ',$replyto,
+                     determine_recipients(recipients => \%recipients,
+                                          cc => 1,
+                                          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,
+                exists $header{'x-debbugs-no-ack'}?():$replyto,
+                make_list(values %{{determine_recipients(recipients => \%recipients,
+                                                         address_only => 1,
+                                                        )}}
+                         ),
+               );
+
+unlink("incoming/P$nn") || die "unlinking incoming/P$nn: $!";
+
+sub sendmailmessage {
+    my ($message,@recips) = @_;
+    $message = "X-Loop: $gMaintainerEmail\n" . $message;
+    send_mail_message(message    => $message,
+                     recipients => \@recips,
+                    );
+    $midix++;
+}
+
+sub fill_template{
+     my ($template,$extra_var) = @_;
+     $extra_var ||={};
+     my $variables = {config => \%config,
+                     defined($ref)?(ref    => $ref):(),
+                     defined($data)?(data  => $data):(),
+                     %{$extra_var},
+                    };
+     my $hole_var = {'&bugurl' =>
+                    sub{"$_[0]: ".
+                             'http://'.$config{cgi_domain}.'/'.
+                                  Debbugs::CGI::bug_url($_[0]);
+                   }
+                   };
+     return fill_in_template(template => $template,
+                            variables => $variables,
+                            hole_var  => $hole_var,
+                           );
+}
+
+=head2 message_body_template
+
+     message_body_template('mail/ack',{ref=>'foo'});
+
+Creates a message body using a template
+
+=cut
+
+sub message_body_template{
+     my ($template,$extra_var) = @_;
+     $extra_var ||={};
+     my $body = fill_template($template,$extra_var);
+     return fill_template('mail/message_body',
+                         {%{$extra_var},
+                          body => $body,
+                         },
+                        );
+}
+
+sub sendhelp {
+        &sendtxthelpraw("bug-log-mailserver.txt","instructions for request\@$gEmailDomain");
+        &sendtxthelpraw("bug-maint-mailcontrol.txt","instructions for control\@$gEmailDomain")
+            if $control;
+}
+
+#sub unimplemented {
+#    print {$transcript} "Sorry, command $_[0] not yet implemented.\n\n";
+#}
+
+sub checkmatch {
+    my ($string,$mvarname,$svarvalue,@newmergelist) = @_;
+    my ($mvarvalue);
+    if (@newmergelist) {
+        eval "\$mvarvalue= \$$mvarname";
+        print {$transcript} "D| checkmatch \`$string' /$mvarname/$mvarvalue/$svarvalue/\n"
+            if $dl;
+        $mismatch .=
+            "Values for \`$string' don't match:\n".
+            " #$newmergelist[0] has \`$mvarvalue';\n".
+            " #$ref has \`$svarvalue'\n"
+            if $mvarvalue ne $svarvalue;
+    } else {
+        print {$transcript} "D| setupmatch \`$string' /$mvarname/$svarvalue/\n"
+             if $dl;
+        eval "\$$mvarname= \$svarvalue";
+    }
+}
+
+sub checkpkglimit {
+    if (keys %limit_pkgs and not defined $limit_pkgs{$data->{package}}) {
+        print {$transcript} "$gBug number $ref belongs to package $data->{package}, skipping.\n\n";
+        $errors++;
+        return 0;
+    }
+    return 1;
+}
+
+sub manipset {
+    my $list = shift;
+    my $elt = shift;
+    my $add = shift;
+
+    my %h = map { $_ => 1 } split ' ', $list;
+    if ($add) {
+        $h{$elt}=1;
+    }
+    else {
+       delete $h{$elt};
+    }
+    return join ' ', sort keys %h;
+}
+
+# High-level bug manipulation calls
+# Do announcements themselves
+#
+# Possible calling sequences:
+#    setbug (returns 0)
+#    
+#    setbug (returns 1)
+#    &transcript(something)
+#    nochangebug
+#
+#    setbug (returns 1)
+#    $action= (something)
+#    do {
+#      (modify s_* variables)
+#    } while (getnextbug);
+
+our $manybugs;
+
+sub nochangebug {
+    &dlen("nochangebug");
+    $state eq 'single' || $state eq 'multiple' || die "$state ?";
+    &cancelbug;
+    &endmerge if $manybugs;
+    $state= 'idle';
+    &dlex("nochangebug");
+}
+
+our $sref;
+our @thisbugmergelist;
+
+sub setbug {
+    &dlen("setbug $ref");
+    if ($ref =~ m/^-\d+/) {
+        if (!defined $clonebugs{$ref}) {
+            &notfoundbug;
+            &dlex("setbug => noclone");
+            return 0;
+        }
+        $ref = $clonebugs{$ref};
+    }
+    $state eq 'idle' || die "$state ?";
+    if (!&getbug) {
+        &notfoundbug;
+        &dlex("setbug => 0s");
+        return 0;
+    }
+
+    if (!&checkpkglimit) {
+        &cancelbug;
+        return 0;
+    }
+
+    @thisbugmergelist= split(/ /,$data->{mergedwith});
+    if (!@thisbugmergelist) {
+        &foundbug;
+        $manybugs= 0;
+        $state= 'single';
+        $sref=$ref;
+        &dlex("setbug => 1s");
+        return 1;
+    }
+    &cancelbug;
+    &getmerge;
+    $manybugs= 1;
+    if (!&getbug) {
+        &notfoundbug;
+        &endmerge;
+        &dlex("setbug => 0mc");
+        return 0;
+    }
+    &foundbug;
+    $state= 'multiple'; $sref=$ref;
+    &dlex("setbug => 1m");
+    return 1;
+}
+
+sub getnextbug {
+    &dlen("getnextbug");
+    $state eq 'single' || $state eq 'multiple' || die "$state ?";
+    &savebug;
+    if (!$manybugs || !@thisbugmergelist) {
+        length($action) || die;
+        print {$transcript} "$action\n$extramessage\n";
+        &endmerge if $manybugs;
+        $state= 'idle';
+        &dlex("getnextbug => 0");
+        return 0;
+    }
+    $ref= shift(@thisbugmergelist);
+    &getbug || die "bug $ref disappeared";
+    &foundbug;
+    &dlex("getnextbug => 1");
+    return 1;
+}
+
+# Low-level bug-manipulation calls
+# Do no announcements
+#
+#    getbug (returns 0)
+#
+#    getbug (returns 1)
+#    cancelbug
+#
+#    getmerge
+#    $action= (something)
+#    getbug (returns 1)
+#    savebug/cancelbug
+#    getbug (returns 1)
+#    savebug/cancelbug
+#    [getbug (returns 0)]
+#    &transcript("$action\n\n")
+#    endmerge
+
+sub notfoundbug { print {$transcript} "$gBug number $ref not found. (Is it archived?)\n\n"; }
+sub foundbug { print {$transcript} "$gBug#$ref: $data->{subject}\n"; }
+
+sub getmerge {
+    &dlen("getmerge");
+    $mergelowstate eq 'idle' || die "$mergelowstate ?";
+    &filelock('lock/merge');
+    $mergelowstate='locked';
+    &dlex("getmerge");
+}
+
+sub endmerge {
+    &dlen("endmerge");
+    $mergelowstate eq 'locked' || die "$mergelowstate ?";
+    &unfilelock;
+    $mergelowstate='idle';
+    &dlex("endmerge");
+}
+
+sub getbug {
+    &dlen("getbug $ref");
+    $lowstate eq 'idle' || die "$state ?";
+    # Only use unmerged bugs here
+    if (($data = &lockreadbug($ref,'db-h'))) {
+        $sref= $ref;
+        $lowstate= "open";
+        &dlex("getbug => 1");
+        $extramessage='';
+        return 1;
+    }
+    $lowstate= 'idle';
+    &dlex("getbug => 0");
+    return 0;
+}
+
+sub cancelbug {
+    &dlen("cancelbug");
+    $lowstate eq 'open' || die "$state ?";
+    &unfilelock;
+    $lowstate= 'idle';
+    &dlex("cancelbug");
+}
+
+sub savebug {
+    &dlen("savebug $ref");
+    $lowstate eq 'open' || die "$lowstate ?";
+    length($action) || die;
+    $ref == $sref || die "read $sref but saving $ref ?";
+    append_action_to_log(bug => $ref,
+                        action => $action,
+                        requester => $header{from},
+                        request_addr => $controlrequestaddr,
+                        message => \@log,
+                        get_lock => 0,
+                       );
+    unlockwritebug($ref, $data);
+    $lowstate= "idle";
+    &dlex("savebug");
+}
+
+sub dlen {
+    return if !$dl;
+    print {$transcript} "C> @_ ($state $lowstate $mergelowstate)\n";
+}
+
+sub dlex {
+    return if !$dl;
+    print {$transcript} "R> @_ ($state $lowstate $mergelowstate)\n";
+}
+
+sub urlsanit {
+    my $url = shift;
+    $url =~ s/%/%25/g;
+    $url =~ s/\+/%2b/g;
+    my %saniarray = ('<','lt', '>','gt', '&','amp', '"','quot');
+    $url =~ s/([<>&"])/\&$saniarray{$1};/g;
+    return $url;
+}
+
+sub sendlynxdoc {
+    &sendlynxdocraw;
+    print {$transcript} "\n";
+    $ok++;
+}
+
+sub sendtxthelp {
+    &sendtxthelpraw;
+    print {$transcript} "\n";
+    $ok++;
+}
+
+
+our $doc;
+sub sendtxthelpraw {
+    my ($relpath,$description) = @_;
+    $doc='';
+    open(D,"$gDocDir/$relpath") || die "open doc file $relpath: $!";
+    while(<D>) { $doc.=$_; }
+    close(D);
+    print {$transcript} "Sending $description in separate message.\n";
+    &sendmailmessage(<<END.$doc,$replyto);
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $replyto
+Subject: $gProject $gBug help: $description
+References: $header{'message-id'}
+In-Reply-To: $header{'message-id'}
+Message-ID: <handler.s.$nn.help.$midix\@$gEmailDomain>
+Precedence: bulk
+X-$gProject-PR-Message: doc-text $relpath
+
+END
+    $ok++;
+}
+
+sub sendlynxdocraw {
+    my ($relpath,$description) = @_;
+    $doc='';
+    open(L,"lynx -nolist -dump http://$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/) {
+        print {$transcript} "Information ($description) is not available -\n".
+            "perhaps the $gBug does not exist or is not on the WWW yet.\n";
+         $ok++;
+    } elsif ($?) {
+        print {$transcript} "Error getting $description (code $? $!):\n$doc\n";
+    } else {
+        print {$transcript} "Sending $description.\n";
+        &sendmailmessage(<<END.$doc,$replyto);
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $replyto
+Subject: $gProject $gBugs information: $description
+References: $header{'message-id'}
+In-Reply-To: $header{'message-id'}
+Message-ID: <handler.s.$nn.info.$midix\@$gEmailDomain>
+Precedence: bulk
+X-$gProject-PR-Message: doc-html $relpath
+
+END
+         $ok++;
+    }
+}
+
+
+sub sendinfo {
+    my ($wherefrom,$path,$description) = @_;
+    if ($wherefrom eq "ftp.d.o") {
+      $doc = `lynx -nolist -dump http://ftp.debian.org/debian/indices/$path.gz 2>&1 | gunzip -cf` or die "fork for lynx/gunzip: $!";
+      $! = 0;
+      if ($? == 255 && $doc =~ m/^\n*lynx: Can\'t access start file/) {
+          print {$transcript} "$description is not available.\n";
+          $ok++; return;
+      } elsif ($?) {
+          print {$transcript} "Error getting $description (code $? $!):\n$doc\n";
+          return;
+      }
+    } elsif ($wherefrom eq "local") {
+      open P, "$path";
+      $doc = do { local $/; <P> };
+      close P;
+    } else {
+      print {$transcript} "internal errror: info files location unknown.\n";
+      $ok++; return;
+    }
+    print {$transcript} "Sending $description.\n";
+    &sendmailmessage(<<END.$doc,$replyto);
+From: $gMaintainerEmail ($gProject $gBug Tracking System)
+To: $replyto
+Subject: $gProject $gBugs information: $description
+References: $header{'message-id'}
+In-Reply-To: $header{'message-id'}
+Message-ID: <handler.s.$nn.info.$midix\@$gEmailDomain>
+Precedence: bulk
+X-$gProject-PR-Message: getinfo
+
+$description follows:
+
+END
+    $ok++;
+    print {$transcript} "\n";
+}
diff --git a/scripts/service.in b/scripts/service.in
deleted file mode 100755 (executable)
index 05925d2..0000000
+++ /dev/null
@@ -1,1818 +0,0 @@
-#!/usr/bin/perl
-# $Id: service.in,v 1.118 2005/10/19 01:22:14 don Exp $
-#
-# Usage: service <code>.nn
-# Temps:  incoming/P<code>.nn
-
-use File::Copy;
-use MIME::Parser;
-use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522);
-use Debbugs::Mail qw(send_mail_message);
-use Debbugs::User;
-use HTML::Entities qw(encode_entities);
-use Debbugs::Versions::Dpkg;
-
-use Debbugs::Config qw(:globals :config);
-use Debbugs::CGI qw(html_escape);
-use Debbugs::Control qw(:archive :log);
-use Debbugs::Log qw(:misc);
-use Debbugs::Text qw(:templates);
-
-use Mail::RFC822::Address;
-
-$lib_path = $gLibPath;
-require "$lib_path/errorlib";
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-chdir("$gSpoolDir") || die "chdir spool: $!\n";
-
-# open(DEBUG,">&4");
-open DEBUG, ">/dev/null";
-$debug = 0;
-umask(002);
-
-$_=shift;
-m/^[RC]\.\d+$/ || &quit("bad argument");
-$control= m/C/;
-$nn= $_;
-if (!rename("incoming/G$nn","incoming/P$nn")) {
-    $_=$!.'';  m/no such file or directory/i && exit 0;
-    &quit("renaming to lock: $!");
-}    
-
-open(M,"incoming/P$nn");
-@log=<M>;
-@msg=@log;
-close(M);
-
-chomp @msg;
-
-print "###\n",join("##\n",@msg),"\n###\n" if $debug;
-
-my $parser = new MIME::Parser;
-mkdir "$gSpoolDir/mime.tmp", 0777;
-$parser->output_under("$gSpoolDir/mime.tmp");
-my $entity = eval { $parser->parse_data(join('',@log)) };
-
-# header and decoded body respectively
-my (@headerlines, @bodylines);
-# Bug numbers to send e-mail to, hash so that we don't send to the
-# same bug twice.
-my (%bug_affected);
-
-if ($entity and $entity->head->tags) {
-    # Use map instead of chomp to also kill \r.
-    @headerlines = map {s/\r?\n?$//; $_;}
-        @{$entity->head->header};
-
-    my $entity_body = getmailbody($entity);
-    @bodylines = map {s/\r?\n$//; $_;}
-        $entity_body ? $entity_body->as_lines() : ();
-} else {
-    # Legacy pre-MIME code, kept around in case MIME::Parser fails.
-    my $i;
-    for ($i = 0; $i <= $#msg; $i++) {
-       $_ = $msg[$i];
-       last unless length($_);
-       while ($msg[$i+1] =~ m/^\s/) {
-           $i++;
-           $_ .= "\n".$msg[$i];
-       }
-       push @headerlines, $_;
-    }
-
-    @bodylines = @msg[$i..$#msg];
-}
-
-for (@headerlines) {
-    $_ = decode_rfc1522($_);
-    s/\n\s/ /g;
-    print ">$_<\n" if $debug;
-    if (s/^(\S+):\s*//) {
-       my $v = lc $1;
-       print ">$v=$_<\n" if $debug;
-       $header{$v} = $_;
-    } else {
-       print "!>$_<\n" if $debug;
-    }
-}
-
-# Strip off RFC2440-style PGP clearsigning.
-if (@bodylines and $bodylines[0] =~ /^-----BEGIN PGP SIGNED/) {
-    shift @bodylines while @bodylines and length $bodylines[0];
-    shift @bodylines while @bodylines and $bodylines[0] !~ /\S/;
-    for my $findsig (0 .. $#bodylines) {
-       if ($bodylines[$findsig] =~ /^-----BEGIN PGP SIGNATURE/) {
-           $#bodylines = $findsig - 1;
-           last;
-       }
-    }
-    map { s/^- // } @bodylines;
-}
-
-grep(s/\s+$//,@bodylines);
-
-print "***\n",join("\n",@bodylines),"\n***\n" if $debug;
-
-if (defined $header{'resent-from'} && !defined $header{'from'}) {
-    $header{'from'} = $header{'resent-from'};
-}
-
-defined($header{'from'}) || &quit("no From header");
-
-delete $header{'reply-to'} 
-       if ( defined($header{'reply-to'}) && $header{'reply-to'} =~ m/^\s*$/ );
-
-if ( defined($header{'reply-to'}) && $header{'reply-to'} ne "" ) {
-    $replyto = $header{'reply-to'};
-} else {
-    $replyto = $header{'from'};
-}
-
-# This is an error counter which should be incremented every time there is an error.
-my $errors = 0;
-$controlrequestaddr= $control ? "control\@$gEmailDomain" : "request\@$gEmailDomain";
-$transcript='';
-&transcript("Processing commands for $controlrequestaddr:\n\n");
-
-$dl= 0;
-$state= 'idle';
-$lowstate= 'idle';
-$mergelowstate= 'idle';
-$midix=0;    
-$extras="";
-
-my $user = $replyto;
-$user =~ s/,.*//;
-$user =~ s/^.*<(.*)>.*$/$1/;
-$user =~ s/[(].*[)]//;
-$user =~ s/^\s*(\S+)\s+.*$/$1/;
-$user = "" unless (Debbugs::User::is_valid_user($user));
-my $indicated_user = 0;
-
-my $quickabort = 0;
-
-my $fuckheads = "(" . join("|", @gExcludeFromControl) . ")";
-if (@gExcludeFromControl and $replyto =~ m/$fuckheads/) {
-       &transcript(fill_template('mail/excluded_from_control'));
-       $quickabort = 1;
-}
-
-my %limit_pkgs = ();
-my %clonebugs = ();
-my @bcc = ();
-
-sub addbcc {
-    push @bcc, $_[0] unless grep { $_ eq $_[0] } @bcc;
-}
-
-for ($procline=0; $procline<=$#bodylines; $procline++) {
-    $state eq 'idle' || print "$state ?\n";
-    $lowstate eq 'idle' || print "$lowstate ?\n";
-    $mergelowstate eq 'idle' || print "$mergelowstate ?\n";
-    if ($quickabort) {
-         &transcript("Stopping processing here.\n\n");
-        last;
-    }
-    $_= $bodylines[$procline]; s/\s+$//;
-    next unless m/\S/;
-    &transcript("> $_\n");
-    next if m/^\s*\#/;
-    $action= '';
-    if (m/^stop\s*$/i || m/^quit\s*$/i || m/^--\s*$/ || m/^thank(?:s|\s*you)?\s*$/i || m/^kthxbye\s*$/i) {
-       &transcript("Stopping processing here.\n\n");
-        last;
-    } elsif (m/^debug\s+(\d+)$/i && $1 >= 0 && $1 <= 1000) {
-        $dl= $1+0;
-        &transcript("Debug level $dl.\n\n");
-    } elsif (m/^(send|get)\s+\#?(\d{2,})$/i) {
-        $ref= $2+0;
-        &sendlynxdoc("bugreport.cgi?bug=$ref","logs for $gBug#$ref");
-    } elsif (m/^send-detail\s+\#?(\d{2,})$/i) {
-       $ref= $1+0;
-       &sendlynxdoc("bugreport.cgi?bug=$ref&boring=yes",
-                    "detailed logs for $gBug#$ref");
-    } elsif (m/^index(\s+full)?$/i) {
-       &transcript("This BTS function is currently disabled, sorry.\n\n");
-       $errors++;
-       $ok++; # well, it's not really ok, but it fixes #81224 :)
-    } elsif (m/^index-summary\s+by-package$/i) {
-       &transcript("This BTS function is currently disabled, sorry.\n\n");
-       $errors++;
-       $ok++; # well, it's not really ok, but it fixes #81224 :)
-    } elsif (m/^index-summary(\s+by-number)?$/i) {
-       &transcript("This BTS function is currently disabled, sorry.\n\n");
-       $errors++;
-       $ok++; # well, it's not really ok, but it fixes #81224 :)
-    } elsif (m/^index(\s+|-)pack(age)?s?$/i) {
-       &sendlynxdoc("pkgindex.cgi?indexon=pkg",'index of packages');
-    } elsif (m/^index(\s+|-)maints?$/i) {
-       &sendlynxdoc("pkgindex.cgi?indexon=maint",'index of maintainers');
-    } elsif (m/^index(\s+|-)maint\s+(\S+)$/i) {
-       $maint = $2;
-       &sendlynxdoc("pkgreport.cgi?maint=" . urlsanit($maint),
-                    "$gBug list for maintainer \`$maint'");
-        $ok++;
-    } elsif (m/^index(\s+|-)pack(age)?s?\s+(\S.*\S)$/i) {
-       $package = $+;
-       &sendlynxdoc("pkgreport.cgi?pkg=" . urlsanit($package),
-                    "$gBug list for package $package");
-        $ok++;
-    } elsif (m/^send-unmatched(\s+this|\s+-?0)?$/i) {
-       &transcript("This BTS function is currently disabled, sorry.\n\n");
-       $errors++;
-       $ok++; # well, it's not really ok, but it fixes #81224 :)
-    } elsif (m/^send-unmatched\s+(last|-1)$/i) {
-       &transcript("This BTS function is currently disabled, sorry.\n\n");
-       $errors++;
-       $ok++; # well, it's not really ok, but it fixes #81224 :)
-    } elsif (m/^send-unmatched\s+(old|-2)$/i) {
-       &transcript("This BTS function is currently disabled, sorry.\n\n");
-       $errors++;
-       $ok++; # well, it's not really ok, but it fixes #81224 :)
-    } elsif (m/^getinfo\s+([\w-.]+)$/i) {
-        # the following is basically a Debian-specific kludge, but who cares
-        $req = $1;
-       if ($req =~ /^maintainers$/i && -f "$gConfigDir/Maintainers") {
-           &sendinfo("local", "$gConfigDir/Maintainers", "Maintainers file");
-       } elsif ($req =~ /^override\.(\w+)\.([\w-.]+)$/i) {
-           $req =~ s/.gz$//;
-           &sendinfo("ftp.d.o", "$req", "override file for $2 part of $1 distribution");
-       } elsif ($req =~ /^pseudo-packages\.(description|maintainers)$/i && -f "$gConfigDir/$req") {
-           &sendinfo("local", "$gConfigDir/$req", "$req file");
-       } else {
-           &transcript("Info file $req does not exist.\n\n");
-       }
-    } elsif (m/^help/i) {
-        &sendhelp;
-        &transcript("\n");
-        $ok++;
-    } elsif (m/^refcard/i) {
-        &sendtxthelp("bug-mailserver-refcard.txt","mail servers' reference card");
-    } elsif (m/^subscribe/i) {
-        &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
-to send them to you.
-soon: MAILINGLISTS_TEXT
-END
-    } elsif (m/^unsubscribe/i) {
-        &transcript(<<END);
-soon: UNSUBSCRIBE_TEXT
-soon: MAILINGLISTS_TEXT
-END
-    } elsif (m/^user\s+(\S+)\s*$/i) {
-        my $newuser = $1;
-       if (Debbugs::User::is_valid_user($newuser)) {
-           my $olduser = ($user ne "" ? " (was $user)" : "");
-            &transcript("Setting user to $newuser$olduser.\n");
-           $user = $newuser;
-           $indicated_user = 1;
-       } else {
-           &transcript("Selected user id ($newuser) invalid, sorry\n");
-           $errors++;
-           $user = "";
-           $indicated_user = 1;
-       }
-    } elsif (m/^usercategory\s+(\S+)(\s+\[hidden\])?\s*$/i) {
-        $ok++;
-       my $catname = $1;
-       my $hidden = ($2 ne "");
-
-        my $prefix = "";
-        my @cats;
-        my $bad = 0;
-       my $catsec = 0;
-       if ($user eq "") {
-           &transcript("No valid user selected\n");
-           $errors++;
-           next;
-        }
-       if (not $indicated_user and defined $user) {
-            &transcript("User is $user\n");
-            $indicated_user = 1;
-       }
-       while (++$procline <= $#bodylines) {
-            unless ($bodylines[$procline] =~ m/^\s*([*+])\s*(\S.*)$/) {
-                $procline--;
-                last;
-            }
-            &transcript("> $bodylines[$procline]\n");
-            next if $bad;
-            my ($o, $txt) = ($1, $2);
-            if ($#cats == -1 && $o eq "+") {
-                &transcript("User defined category specification must start with a category name. Skipping.\n\n");
-               $errors++;
-                $bad = 1;
-                next;
-            }
-            if ($o eq "+") {
-               unless (ref($cats[-1]) eq "HASH") {
-                   $cats[-1] = { "nam" => $cats[-1], 
-                                 "pri" => [], "ttl" => [] };
-               }
-               $catsec++;
-               my ($desc, $ord, $op);
-                if ($txt =~ m/^(.*\S)\s*\[((\d+):\s*)?\]\s*$/) {
-                    $desc = $1; $ord = $3; $op = "";
-                } elsif ($txt =~ m/^(.*\S)\s*\[((\d+):\s*)?(\S+)\]\s*$/) {
-                    $desc = $1; $ord = $3; $op = $4;
-                } elsif ($txt =~ m/^([^[\s]+)\s*$/) {
-                    $desc = ""; $op = $1;
-                } else {
-                    &transcript("Unrecognised syntax for category section. Skipping.\n\n");
-                   $errors++;
-                    $bad = 1;
-                    next;
-                }
-               $ord = 999 unless defined $ord;
-
-               if ($op) {
-                    push @{$cats[-1]->{"pri"}}, $prefix . $op;
-                   push @{$cats[-1]->{"ttl"}}, $desc;
-                   push @ords, "$ord $catsec";
-               } else {
-                   @cats[-1]->{"def"} = $desc;
-                   push @ords, "$ord DEF";
-                   $catsec--;
-               }
-               @ords = sort { my ($a1, $a2, $b1, $b2) = split / /, "$a $b";
-                              $a1 <=> $b1 || $a2 <=> $b2; } @ords;
-               $cats[-1]->{"ord"} = [map { m/^.* (\S+)/; $1 eq "DEF" ? $catsec + 1 : $1 } @ords];
-            } elsif ($o eq "*") {
-               $catsec = 0;
-                my ($name);
-                if ($txt =~ m/^(.*\S)(\s*\[(\S+)\])\s*$/) {
-                    $name = $1; $prefix = $3;
-                } else {
-                    $name = $txt; $prefix = "";
-                }
-                push @cats, $name;
-            }
-        }
-        # XXX: got @cats, now do something with it
-       my $u = Debbugs::User::get_user($user);
-       if (@cats) {
-           &transcript("Added usercategory $catname.\n\n");
-           $u->{"categories"}->{$catname} = [ @cats ];
-           if (not $hidden) {
-                push @{$u->{visible_cats}},$catname;
-           }
-       } else {
-           &transcript("Removed usercategory $catname.\n\n");
-           delete $u->{"categories"}->{$catname};
-           @{$u->{visible_cats}} = grep {$_ ne $catname} @{$u->{visible_cats}};
-       }
-       $u->write();
-    } elsif (m/^usertags?\s+\#?(-?\d+)\s+(([=+-])\s*)?(\S.*)?$/i) {
-       $ok++;
-       $ref = $1; $addsubcode = $3 || "+"; $tags = $4;
-       if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
-            $ref = $clonebugs{$ref};
-        }
-       if ($user eq "") {
-           &transcript("No valid user selected\n");
-           $errors++;
-           $indicated_user = 1;
-        } elsif (&setbug) {
-           if (not $indicated_user and defined $user) {
-                &transcript("User is $user\n");
-                $indicated_user = 1;
-           }
-           &nochangebug;
-           my %ut;
-           Debbugs::User::read_usertags(\%ut, $user);
-            my @oldtags = (); my @newtags = (); my @badtags = ();
-           my %chtags;
-           for my $t (split /[,\s]+/, $tags) {
-               if ($t =~ m/^[a-zA-Z0-9.+\@-]+$/) {
-                   $chtags{$t} = 1;
-               } else {
-                   push @badtags, $t;
-               }
-           }
-           if (@badtags) {
-                &transcript("Ignoring illegal tag/s: ".join(', ', @badtags).".\nPlease use only alphanumerics, at, dot, plus and dash.\n");
-               $errors++;
-           }
-            for my $t (keys %chtags) {
-               $ut{$t} = [] unless defined $ut{$t};
-           }
-           for my $t (keys %ut) {
-               my %res = map { ($_, 1) } @{$ut{$t}};
-               push @oldtags, $t if defined $res{$ref};
-               my $addop = ($addsubcode eq "+" or $addsubcode eq "=");
-               my $del = (defined $chtags{$t} ? $addsubcode eq "-" 
-                                              : $addsubcode eq "=");
-               $res{$ref} = 1 if ($addop && defined $chtags{$t});
-               delete $res{$ref} if ($del);
-               push @newtags, $t if defined $res{$ref};
-               $ut{$t} = [ sort { $a <=> $b } (keys %res) ];
-           }
-           if (@oldtags == 0) {
-               &transcript("There were no usertags set.\n");
-           } else {
-               &transcript("Usertags were: " . join(" ", @oldtags) . ".\n");
-           }
-           &transcript("Usertags are now: " . join(" ", @newtags) . ".\n");
-           Debbugs::User::write_usertags(\%ut, $user);
-       }
-    } elsif (!$control) {
-        &transcript(<<END);
-Unknown command or malformed arguments to command.
-(Use control\@$gEmailDomain to manipulate reports.)
-
-END
-       $errors++;
-        if (++$unknowns >= 3) {
-            &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;
-       $bug_affected{$ref}=1;
-       $version= $2;
-       if (&setbug) {
-           &transcript("'close' is deprecated; see http://$gWebDomain/Developer$gHTMLSuffix#closing.\n");
-           if (length($data->{done}) and not defined($version)) {
-               &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 {
-                    &addmaintainers($data);
-                                       if ( length( $gDoneList ) > 0 && length( $gListDomain ) >
-                                       0 ) { &addccaddress("$gDoneList\@$gListDomain"); }
-                    $data->{done}= $replyto;
-                    my @keywords= split ' ', $data->{keywords};
-                    if (grep $_ eq 'pending', @keywords) {
-                        $extramessage= "Removed pending tag.\n";
-                        $data->{keywords}= join ' ', grep $_ ne 'pending',
-                                                @keywords;
-                    }
-                    addfixedversions($data, $data->{package}, $version, 'binary');
-
-                   $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+(\S+)(?:\s+(\d.*))?$/i) {
-        $ok++;
-        $ref= $1; $newpackage= $2;
-       $bug_affected{$ref}=1;
-        $version= $3;
-       $newpackage =~ y/A-Z/a-z/;
-        if (&setbug) {
-            if (length($data->{package})) {
-                $action= "$gBug reassigned from package \`$data->{package}'".
-                         " to \`$newpackage'.";
-            } else {
-                $action= "$gBug assigned to package \`$newpackage'.";
-            }
-            do {
-                &addmaintainers($data);
-                $data->{package}= $newpackage;
-                $data->{found_versions}= [];
-                $data->{fixed_versions}= [];
-                # TODO: what if $newpackage is a source package?
-                addfoundversions($data, $data->{package}, $version, 'binary');
-                &addmaintainers($data);
-            } while (&getnextbug);
-        }
-    } elsif (m/^reopen\s+\#?(-?\d+)$/i ? ($noriginator='', 1) :
-             m/^reopen\s+\#?(-?\d+)\s+\=$/i ? ($noriginator='', 1) :
-             m/^reopen\s+\#?(-?\d+)\s+\!$/i ? ($noriginator=$replyto, 1) :
-             m/^reopen\s+\#?(-?\d+)\s+(\S.*\S)$/i ? ($noriginator=$2, 1) : 0) {
-        $ok++;
-        $ref= $1;
-       $bug_affected{$ref}=1;
-        if (&setbug) {
-            if (@{$data->{fixed_versions}}) {
-                &transcript("'reopen' may be inappropriate when a bug has been closed with a version;\nyou may need to use 'found' to remove fixed versions.\n");
-            }
-            if (!length($data->{done})) {
-                &transcript("$gBug is already open, cannot reopen.\n\n");
-                &nochangebug;
-            } else {
-                $action=
-                    $noriginator eq '' ? "$gBug reopened, originator not changed." :
-                        "$gBug reopened, originator set to $noriginator.";
-                do {
-                    &addmaintainers($data);
-                    $data->{originator}= $noriginator eq '' ?  $data->{originator} : $noriginator;
-                    $data->{fixed_versions}= [];
-                    $data->{done}= '';
-                } while (&getnextbug);
-            }
-        }
-    } elsif (m{^found\s+\#?(-?\d+)
-              (?:\s+((?:$config{package_name_re}\/)?
-                   $config{package_version_re}))?$}ix) {
-        $ok++;
-        $ref= $1;
-        $version= $2;
-        if (&setbug) {
-            if (!length($data->{done}) and not defined($version)) {
-                &transcript("$gBug is already open, cannot reopen.\n\n");
-               $errors++;
-                &nochangebug;
-            } else {
-                $action=
-                    defined($version) ?
-                        "$gBug marked as found in version $version." :
-                        "$gBug reopened.";
-                do {
-                    &addmaintainers($data);
-                    # The 'done' field gets a bit weird with version
-                    # tracking, because a bug may be closed by multiple
-                    # people in different branches. Until we have something
-                    # more flexible, we set it every time a bug is fixed,
-                    # and clear it when a bug is found in a version greater
-                   # than any version in which the bug is fixed or when
-                   # a bug is found and there is no fixed version
-                   if (defined $version) {
-                       my ($version_only) = $version =~ m{([^/]+)$};
-                        addfoundversions($data, $data->{package}, $version, 'binary');
-                       my @fixed_order = sort {Debbugs::Versions::Dpkg::vercmp($a,$b);}
-                            map {s{.+/}{}; $_;} @{$data->{fixed_versions}};
-                       if (not @fixed_order or (Debbugs::Versions::Dpkg::vercmp($version_only,$fixed_order[-1]) >= 0)) {
-                            $action = "$gBug marked as found in version $version and reopened."
-                                 if length $data->{done};
-                            $data->{done} = '';
-                       }
-                    } else {
-                        # Versionless found; assume old-style "not fixed at
-                        # all".
-                        $data->{fixed_versions} = [];
-                        $data->{done} = '';
-                    }
-                } while (&getnextbug);
-            }
-        }
-    } elsif (m[^notfound\s+\#?(-?\d+)\s+
-              ((?:$config{package_name_re}\/)?
-                   \S+)\s*$]ix) {
-        $ok++;
-        $ref= $1;
-        $version= $2;
-        if (&setbug) {
-            $action= "$gBug no longer marked as found in version $version.";
-            if (length($data->{done})) {
-                $extramessage= "(By the way, this $gBug is currently marked as done.)\n";
-            }
-            do {
-                &addmaintainers($data);
-                removefoundversions($data, $data->{package}, $version, 'binary');
-            } while (&getnextbug);
-       }
-   }
-    elsif (m[^fixed\s+\#?(-?\d+)\s+
-            ((?:$config{package_name_re}\/)?
-                 $config{package_version_re})\s*$]ix) {
-        $ok++;
-        $ref= $1;
-        $version= $2;
-        if (&setbug) {
-            $action=
-                 defined($version) ?
-                      "$gBug marked as fixed in version $version." :
-                           "$gBug reopened.";
-                do {
-                    &addmaintainers($data);
-                    addfixedversions($data, $data->{package}, $version, 'binary');
-              } while (&getnextbug);
-       }
-   }
-    elsif (m[^notfixed\s+\#?(-?\d+)\s+
-            ((?:$config{package_name_re}\/)?
-                 \S+)\s*$]ix) {
-        $ok++;
-        $ref= $1;
-        $version= $2;
-        if (&setbug) {
-            $action=
-                 defined($version) ?
-                      "$gBug no longer marked as fixed in version $version." :
-                           "$gBug reopened.";
-                do {
-                    &addmaintainers($data);
-                    removefixedversions($data, $data->{package}, $version, 'binary');
-              } while (&getnextbug);
-       }
-   }
-    elsif (m/^submitter\s+\#?(-?\d+)\s+\!$/i ? ($newsubmitter=$replyto, 1) :
-             m/^submitter\s+\#?(-?\d+)\s+(\S.*\S)$/i ? ($newsubmitter=$2, 1) : 0) {
-        $ok++;
-        $ref= $1;
-       $bug_affected{$ref}=1;
-        if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
-            $ref = $clonebugs{$ref};
-        }
-       if (not Mail::RFC822::Address::valid($newsubmitter)) {
-            transcript("$newsubmitter is not a valid e-mail address; not changing submitter\n");
-            $errors++;
-       }
-        elsif (&getbug) {
-            if (&checkpkglimit) {
-                &foundbug;
-                &addmaintainers($data);
-                $oldsubmitter= $data->{originator};
-                $data->{originator}= $newsubmitter;
-                $action= "Changed $gBug submitter from $oldsubmitter to $newsubmitter.";
-                &savebug;
-                &transcript("$action\n");
-                if (length($data->{done})) {
-                    &transcript("(By the way, that $gBug is currently marked as done.)\n");
-                }
-                &transcript("\n");
-                $message= <<END;
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $oldsubmitter
-Subject: $gBug#$ref submitter address changed
-         ($header{'subject'})
-References: $header{'message-id'} $data->{msgid}
-In-Reply-To: $data->{msgid}
-Message-ID: <handler.$ref.$nn.newsubmitter.$midix\@$gEmailDomain>
-Reply-To: $ref\@$gEmailDomain
-X-$gProject-PR-Message: submitter-changed $ref
-
-The submitter address recorded for your $gBug report
-#$ref: $data->{subject}
-has been changed.
-
-The old submitter address for this report was
-$oldsubmitter.
-The new submitter address is
-$newsubmitter.
-
-This change was made by
-$replyto.
-If it was incorrect, please contact them directly.
-
-$gMaintainer
-(administrator, $gProject $gBugs database)
-
-END
-                &sendmailmessage($message,$oldsubmitter);
-            } else {
-                &cancelbug;
-            }
-        } else {
-            &notfoundbug;
-        }
-    } elsif (m/^forwarded\s+\#?(-?\d+)\s+(\S.*\S)$/i) {
-        $ok++;
-        $ref= $1; $whereto= $2;
-       $bug_affected{$ref}=1;
-        if (&setbug) {
-            if (length($data->{forwarded})) {
-    $action= "Forwarded-to-address changed from $data->{forwarded} to $whereto.";
-            } else {
-    $action= "Noted your statement that $gBug has been forwarded to $whereto.";
-            }
-            if (length($data->{done})) {
-                $extramessage= "(By the way, this $gBug is currently marked as done.)\n";
-            }
-            do {
-                &addmaintainers($data);
-               if (length($gForwardList)>0 && length($gListDomain)>0 ) {
-                    &addccaddress("$gForwardList\@$gListDomain"); 
-               }
-                $data->{forwarded}= $whereto;
-            } while (&getnextbug);
-        }
-    } elsif (m/^notforwarded\s+\#?(-?\d+)$/i) {
-        $ok++;
-        $ref= $1;
-       $bug_affected{$ref}=1;
-        if (&setbug) {
-            if (!length($data->{forwarded})) {
-                &transcript("$gBug is not marked as having been forwarded.\n\n");
-                &nochangebug;
-            } else {
-    $action= "Removed annotation that $gBug had been forwarded to $data->{forwarded}.";
-                do {
-                    &addmaintainers($data);
-                    $data->{forwarded}= '';
-                } while (&getnextbug);
-            }
-        }
-    } elsif (m/^severity\s+\#?(-?\d+)\s+([-0-9a-z]+)$/i ||
-       m/^priority\s+\#?(-?\d+)\s+([-0-9a-z]+)$/i) {
-        $ok++;
-        $ref= $1;
-       $bug_affected{$ref}=1;
-        $newseverity= $2;
-        if (!grep($_ eq $newseverity, @gSeverityList, "$gDefaultSeverity")) {
-            &transcript("Severity level \`$newseverity' is not known.\n".
-                       "Recognized are: $gShowSeverities.\n\n");
-           $errors++;
-        } elsif (exists $gObsoleteSeverities{$newseverity}) {
-            &transcript("Severity level \`$newseverity' is obsolete. " .
-                        "Use $gObsoleteSeverities{$newseverity} instead.\n\n");
-               $errors++;
-        } elsif (&setbug) {
-            $printseverity= $data->{severity};
-            $printseverity= "$gDefaultSeverity" if $printseverity eq '';
-           $action= "Severity set to \`$newseverity' from \`$printseverity'";
-           do {
-                &addmaintainers($data);
-                if (defined $gStrongList and isstrongseverity($newseverity)) {
-                    addbcc("$gStrongList\@$gListDomain");
-                }
-                $data->{severity}= $newseverity;
-            } while (&getnextbug);
-        }
-    } elsif (m/^tags?\s+\#?(-?\d+)\s+(([=+-])\s*)?(\S.*)?$/i) {
-       $ok++;
-       $ref = $1; $addsubcode = $3; $tags = $4;
-       $bug_affected{$ref}=1;
-       $addsub = "add";
-       if (defined $addsubcode) {
-           $addsub = "sub" if ($addsubcode eq "-");
-           $addsub = "add" if ($addsubcode eq "+");
-           $addsub = "set" if ($addsubcode eq "=");
-       }
-       my @okaytags = ();
-       my @badtags = ();
-       foreach my $t (split /[\s,]+/, $tags) {
-           if (!grep($_ eq $t, @gTags)) {
-               push @badtags, $t;
-           } else {
-               push @okaytags, $t;
-           }
-       }
-       if (@badtags) {
-            &transcript("Unknown tag/s: ".join(', ', @badtags).".\n".
-                       "Recognized are: ".join(' ', @gTags).".\n\n");
-           $errors++;
-       }
-       if (&setbug) {
-           if ($data->{keywords} eq '') {
-               &transcript("There were no tags set.\n");
-           } else {
-               &transcript("Tags were: $data->{keywords}\n");
-           }
-           if ($addsub eq "set") {
-               $action= "Tags set to: " . join(", ", @okaytags);
-           } elsif ($addsub eq "add") {
-               $action= "Tags added: " . join(", ", @okaytags);
-           } elsif ($addsub eq "sub") {
-               $action= "Tags removed: " . join(", ", @okaytags);
-           }
-           do {
-                &addmaintainers($data);
-               $data->{keywords} = '' if ($addsub eq "set");
-               # Allow removing obsolete tags.
-               if ($addsub eq "sub") {
-                   foreach my $t (@badtags) {
-                       $data->{keywords} = join ' ', grep $_ ne $t, 
-                           split ' ', $data->{keywords};
-                   }
-               }
-               # Now process all other additions and subtractions.
-               foreach my $t (@okaytags) {
-                   $data->{keywords} = join ' ', grep $_ ne $t, 
-                       split ' ', $data->{keywords};
-                   $data->{keywords} = "$t $data->{keywords}" unless($addsub eq "sub");
-               }
-               $data->{keywords} =~ s/\s*$//;
-            } while (&getnextbug);
-       }
-    } elsif (m/^(un)?block\s+\#?(-?\d+)\s+(by|with)\s+(\S.*)?$/i) {
-       $ok++;
-       my $bugnum = $2; my $blockers = $4;
-       $addsub = "add";
-       $addsub = "sub" if ($1 eq "un");
-       if ($bugnum =~ m/^-\d+$/ && defined $clonebugs{$bugnum}) {
-            $bugnum = $clonebugs{$bugnum};
-       }
-
-       my @okayblockers;
-       my @badblockers;
-       foreach my $b (split /[\s,]+/, $blockers) {
-           $b=~s/^\#//;
-           if ($b=~/[0-9]+/) {
-               $ref=$b;
-               if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
-                    $ref = $clonebugs{$ref};
-               }
-               if (&getbug) {
-                   &foundbug;
-                   push @okayblockers, $ref;
-
-                   # add to the list all bugs that are merged with $b,
-                   # because all of their data must be kept in sync
-                   @thisbugmergelist= split(/ /,$data->{mergedwith});
-                   &cancelbug;
-
-                   foreach $ref (@thisbugmergelist) {
-                       if (&getbug) {
-                          push @okayblockers, $ref;
-                          &cancelbug;
-                       }
-                   }
-               }
-               else {
-                   &notfoundbug;
-                    push @badblockers, $ref;
-               }
-           }
-           else {
-                push @badblockers, $b;
-           }
-       }
-       if (@badblockers) {
-            &transcript("Unknown blocking bug/s: ".join(', ', @badblockers).".\n");
-           $errors++;
-       }
-       
-       $ref=$bugnum;
-       if (&setbug) {
-           if ($data->{blockedby} eq '') {
-               &transcript("Was not blocked by any bugs.\n");
-           } else {
-               &transcript("Was blocked by: $data->{blockedby}\n");
-           }
-           if ($addsub eq "set") {
-               $action= "Blocking bugs of $bugnum set to: " . join(", ", @okayblockers);
-           } elsif ($addsub eq "add") {
-               $action= "Blocking bugs of $bugnum added: " . join(", ", @okayblockers);
-           } elsif ($addsub eq "sub") {
-               $action= "Blocking bugs of $bugnum removed: " . join(", ", @okayblockers);
-           }
-           my %removedblocks;
-           my %addedblocks;
-           do {
-                &addmaintainers($data);
-               my @oldblockerlist = split ' ', $data->{blockedby};
-               $data->{blockedby} = '' if ($addsub eq "set");
-               foreach my $b (@okayblockers) {
-                       $data->{blockedby} = manipset($data->{blockedby}, $b,
-                               ($addsub ne "sub"));
-               }
-
-               foreach my $b (@oldblockerlist) {
-                       if (! grep { $_ eq $b } split ' ', $data->{blockedby}) {
-                               push @{$removedblocks{$b}}, $ref;
-                       }
-               }
-               foreach my $b (split ' ', $data->{blockedby}) {
-                       if (! grep { $_ eq $b } @oldblockerlist) {
-                               push @{$addedblocks{$b}}, $ref;
-                       }
-               }
-            } while (&getnextbug);
-
-           # Now that the blockedby data is updated, change blocks data
-           # to match the changes.
-           foreach $ref (keys %addedblocks) {
-               if (&getbug) {
-                   foreach my $b (@{$addedblocks{$ref}}) {
-                       $data->{blocks} = manipset($data->{blocks}, $b, 1);
-                   }
-                   &savebug;
-                }
-           }
-           foreach $ref (keys %removedblocks) {
-               if (&getbug) {
-                   foreach my $b (@{$removedblocks{$ref}}) {
-                       $data->{blocks} = manipset($data->{blocks}, $b, 0);
-                   }
-                   &savebug;
-                }
-           }
-       }
-    } elsif (m/^retitle\s+\#?(-?\d+)\s+(\S.*\S)\s*$/i) {
-        $ok++;
-        $ref= $1; $newtitle= $2;
-       $bug_affected{$ref}=1;
-       if ($ref =~ m/^-\d+$/ && defined $clonebugs{$ref}) {
-           $ref = $clonebugs{$ref};
-       }
-        if (&getbug) {
-            if (&checkpkglimit) {
-                &foundbug;
-                &addmaintainers($data);
-               my $oldtitle = $data->{subject};
-                $data->{subject}= $newtitle;
-                $action= "Changed $gBug title to `$newtitle' from `$oldtitle'.";
-                &savebug;
-                &transcript("$action\n");
-                if (length($data->{done})) {
-                    &transcript("(By the way, that $gBug is currently marked as done.)\n");
-                }
-                &transcript("\n");
-            } else {
-                &cancelbug;
-            }
-        } else {
-            &notfoundbug;
-        }
-    } elsif (m/^unmerge\s+\#?(-?\d+)$/i) {
-       $ok++;
-       $ref= $1;
-       $bug_affected{$ref} = 1;
-       if (&setbug) {
-           if (!length($data->{mergedwith})) {
-               &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).";
-               @newmergelist= split(/ /,$data->{mergedwith});
-                $discref= $ref;
-               @bug_affected{@newmergelist} = 1 x @newmergelist;
-                do {
-                    &addmaintainers($data);
-                   $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))) {
-            &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;
-            &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);
-           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)) {
-               &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";
-                &addmaintainers($data);
-               @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;
-           }
-           &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;
-       &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))) {
-            &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;
-            &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}) {
-               &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";
-                &addmaintainers($data);
-               @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);
-               @{$data}{@field_list} = @{$master_bug_data}{@field_list};
-               &savebug;
-           }
-           &transcript("$action\n\n");
-       }
-        &endmerge;
-    } elsif (m/^clone\s+#?(\d+)\s+((-\d+\s+)*-\d+)\s*$/i) {
-       $ok++;
-
-       $origref = $1;
-       @newclonedids = split /\s+/, $2;
-       $newbugsneeded = scalar(@newclonedids);
-
-       $ref = $origref;
-       $bug_affected{$ref} = 1;
-       if (&setbug) {
-           if (length($data->{mergedwith})) {
-               &transcript("$gBug is marked as being merged with others. Use an existing clone.\n\n");
-               $errors++;
-               &nochangebug;
-           } else {
-               &filelock("nextnumber.lock");
-               open(N,"nextnumber") || &quit("nextnumber: read: $!");
-               $v=<N>; $v =~ s/\n$// || &quit("nextnumber bad format");
-               $firstref= $v+0;  $v += $newbugsneeded;
-               open(NN,">nextnumber"); print NN "$v\n"; close(NN);
-               &unfilelock;
-
-               $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 $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;
-               &transcript("Ignoring bugs not assigned to: " . 
-                       join(" ", keys(%limit_pkgs)) . "\n\n");
-       } else {
-               %limit_pkgs = ();
-               &transcript("Not ignoring any bugs.\n\n");
-       }
-    } elsif (m/^owner\s+\#?(-?\d+)\s+!$/i ? ($newowner = $replyto, 1) :
-             m/^owner\s+\#?(-?\d+)\s+(\S.*\S)$/i ? ($newowner = $2, 1) : 0) {
-        $ok++;
-        $ref = $1;
-       $bug_affected{$ref} = 1;
-        if (&setbug) {
-            if (length $data->{owner}) {
-                $action = "Owner changed from $data->{owner} to $newowner.";
-            } else {
-                $action = "Owner recorded as $newowner.";
-            }
-            if (length $data->{done}) {
-                $extramessage = "(By the way, this $gBug is currently " .
-                                "marked as done.)\n";
-            }
-            do {
-                &addmaintainers($data);
-                $data->{owner} = $newowner;
-            } while (&getnextbug);
-        }
-    } elsif (m/^noowner\s+\#?(-?\d+)$/i) {
-        $ok++;
-        $ref = $1;
-       $bug_affected{$ref} = 1;
-        if (&setbug) {
-            if (length $data->{owner}) {
-                $action = "Removed annotation that $gBug was owned by " .
-                          "$data->{owner}.";
-                do {
-                    &addmaintainers($data);
-                    $data->{owner} = '';
-                } while (&getnextbug);
-            } else {
-                &transcript("$gBug is not marked as having an owner.\n\n");
-                &nochangebug;
-            }
-        }
-    } elsif (m/^unarchive\s+#?(\d+)$/i) {
-        $ok++;
-        $ref = $1;
-        $bug_affected{$ref} = 1;
-        my $transcript;
-        eval {
-             bug_unarchive(bug        => $ref,
-                           transcript => \$transcript,
-                           affected_bugs => \%bug_affected,
-                           requester => $header{from},
-                           request_addr => $controlrequestaddr,
-                           message => \@log,
-                          );
-        };
-        if ($@) {
-             $errors++;
-        }
-        transcript($transcript."\n");
-    } elsif (m/^archive\s+#?(\d+)$/i) {
-        $ok++;
-        $ref = $1;
-        $bug_affected{$ref} = 1;
-        if (&setbug) {
-             if (exists $data->{unarchived}) {
-                  my $transcript;
-                  nochangebug();
-                  eval {
-                       bug_archive(bug => $ref,
-                                   transcript => \$transcript,
-                                   ignore_time => 1,
-                                   affected_bugs => \%bug_affected,
-                                   requester => $header{from},
-                                   request_addr => $controlrequestaddr,
-                                   message => \@log,
-                                  );
-                  };
-                  if ($@) {
-                       $errors++;
-                  }
-                  transcript($transcript."\n");
-             }
-             else {
-                  transcript("$gBug $ref has not been archived previously\n\n");
-                  nochangebug();
-                  $errors++;
-             }
-        }
-    } else {
-        &transcript("Unknown command or malformed arguments to command.\n\n");
-       $errors++;
-        if (++$unknowns >= 5) {
-            &transcript("Too many unknown commands, stopping here.\n\n");
-            last;
-        }
-    }
-}
-if ($procline>$#bodylines) {
-    &transcript(">\nEnd of message, stopping processing here.\n\n");
-}
-if (!$ok && !quickabort) {
-    $errors++;
-    &transcript("No commands successfully parsed; sending the help text(s).\n");
-    &sendhelp;
-    &transcript("\n");
-}
-
-&transcript("MC\n") if $dl>1;
-@maintccs= ();
-for $maint (keys %maintccreasons) {
-&transcript("MM|$maint|\n") if $dl>1;
-    next if $maint eq $replyto;
-    $reasonstring= '';
-    $reasonsref= $maintccreasons{$maint};
-&transcript("MY|$maint|\n") if $dl>2;
-    for $p (sort keys %$reasonsref) {
-&transcript("MP|$p|\n") if $dl>2;
-        $reasonstring.= ', ' if length($reasonstring);
-        $reasonstring.= $p.' ' if length($p);
-        $reasonstring.= join(' ',map("#$_",sort keys %{$$reasonsref{$p}}));
-    }
-    if (length($reasonstring) > 40) {
-       (substr $reasonstring, 37) = "...";
-    }
-    $reasonstring = "" if (!defined($reasonstring));
-    push(@maintccs,"$maint ($reasonstring)");
-    push(@maintccaddrs,"$maint");
-}
-
-$maintccs = ""; 
-if (@maintccs) {
-    &transcript("MC|@maintccs|\n") if $dl>2;
-    $maintccs .= "Cc: " . join(",\n    ",@maintccs) . "\n";
-}
-
-my %packagepr;
-for my $maint (keys %maintccreasons) {
-     for my $package (keys %{$maintccreasons{$maint}}) {
-         next unless length $package;
-         $packagepr{$package} = 1;
-     }
-}
-my $packagepr = '';
-$packagepr = "X-${gProject}-PR-Package: " . join(keys %packagepr) . "\n" if keys %packagepr;
-
-# Add Bcc's to subscribed bugs
-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)":'';
-
-$reply= <<END;
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $replyto
-${maintccs}Subject: Processed${error_text}: $header{'subject'}
-In-Reply-To: $header{'message-id'}
-References: $header{'message-id'}
-Message-ID: <handler.s.$nn.transcript\@$gEmailDomain>
-Precedence: bulk
-${packagepr}X-$gProject-PR-Message: transcript
-
-${transcript}Please contact me if you need assistance.
-
-$gMaintainer
-(administrator, $gProject $gBugs database)
-$extras
-END
-
-$repliedshow= join(', ',$replyto,@maintccaddrs);
-# -1 is the service.in log
-&filelock("lock/-1");
-open(AP,">>db-h/-1.log") || &quit("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") || &quit("writing db-h/-1.log: $!");
-close(AP) || &quit("open db-h/-1.log: $!");
-&unfilelock;
-utime(time,time,"db-h");
-
-&sendmailmessage($reply,exists $header{'x-debbugs-no-ack'}?():$replyto,@maintccaddrs,@bcc);
-
-unlink("incoming/P$nn") || &quit("unlinking incoming/P$nn: $!");
-
-sub sendmailmessage {
-    local ($message,@recips) = @_;
-    $message = "X-Loop: $gMaintainerEmail\n" . $message;
-    send_mail_message(message    => $message,
-                     recipients => \@recips,
-                    );
-    $midix++;
-}
-
-sub fill_template{
-     my ($template,$extra_var) = @_;
-     $extra_var ||={};
-     my $variables = {config => \%config,
-                     defined($ref)?(ref    => $ref):(),
-                     defined($data)?(data  => $data):(),
-                     %{$extra_var},
-                    };
-     my $hole_var = {'&bugurl' =>
-                    sub{"$_[0]: ".
-                             'http://'.$config{cgi_domain}.'/'.
-                                  Debbugs::CGI::bug_url($_[0]);
-                   }
-                   };
-     return fill_in_template(template => $template,
-                            variables => $variables,
-                            hole_var  => $hole_var,
-                           );
-}
-
-=head2 message_body_template
-
-     message_body_template('mail/ack',{ref=>'foo'});
-
-Creates a message body using a template
-
-=cut
-
-sub message_body_template{
-     my ($template,$extra_var) = @_;
-     $extra_var ||={};
-     my $body = fill_template($template,$extra_var);
-     return fill_template('mail/message_body',
-                         {%{$extra_var},
-                          body => $body,
-                         },
-                        );
-}
-
-sub sendhelp {
-        &sendtxthelpraw("bug-log-mailserver.txt","instructions for request\@$gEmailDomain");
-        &sendtxthelpraw("bug-maint-mailcontrol.txt","instructions for control\@$gEmailDomain")
-            if $control;
-}
-
-#sub unimplemented {
-#    &transcript("Sorry, command $_[0] not yet implemented.\n\n");
-#}
-
-sub checkmatch {
-    local ($string,$mvarname,$svarvalue,@newmergelist) = @_;
-    local ($mvarvalue);
-    if (@newmergelist) {
-        eval "\$mvarvalue= \$$mvarname";
-        &transcript("D| checkmatch \`$string' /$mvarname/$mvarvalue/$svarvalue/\n")
-            if $dl;
-        $mismatch .=
-            "Values for \`$string' don't match:\n".
-            " #$newmergelist[0] has \`$mvarvalue';\n".
-            " #$ref has \`$svarvalue'\n"
-            if $mvarvalue ne $svarvalue;
-    } else {
-        &transcript("D| setupmatch \`$string' /$mvarname/$svarvalue/\n")
-            if $dl;
-        eval "\$$mvarname= \$svarvalue";
-    }
-}
-
-sub checkpkglimit {
-    if (keys %limit_pkgs and not defined $limit_pkgs{$data->{package}}) {
-        &transcript("$gBug number $ref belongs to package $data->{package}, skipping.\n\n");
-        $errors++;
-        return 0;
-    }
-    return 1;
-}
-
-sub manipset {
-    my $list = shift;
-    my $elt = shift;
-    my $add = shift;
-
-    my %h = map { $_ => 1 } split ' ', $list;
-    if ($add) {
-        $h{$elt}=1;
-    }
-    else {
-       delete $h{$elt};
-    }
-    return join ' ', sort keys %h;
-}
-
-# High-level bug manipulation calls
-# Do announcements themselves
-#
-# Possible calling sequences:
-#    setbug (returns 0)
-#    
-#    setbug (returns 1)
-#    &transcript(something)
-#    nochangebug
-#
-#    setbug (returns 1)
-#    $action= (something)
-#    do {
-#      (modify s_* variables)
-#    } while (getnextbug);
-
-sub nochangebug {
-    &dlen("nochangebug");
-    $state eq 'single' || $state eq 'multiple' || die "$state ?";
-    &cancelbug;
-    &endmerge if $manybugs;
-    $state= 'idle';
-    &dlex("nochangebug");
-}
-
-sub setbug {
-    &dlen("setbug $ref");
-    if ($ref =~ m/^-\d+/) {
-        if (!defined $clonebugs{$ref}) {
-            &notfoundbug;
-            &dlex("setbug => noclone");
-            return 0;
-        }
-        $ref = $clonebugs{$ref};
-    }
-    $state eq 'idle' || die "$state ?";
-    if (!&getbug) {
-        &notfoundbug;
-        &dlex("setbug => 0s");
-        return 0;
-    }
-
-    if (!&checkpkglimit) {
-        &cancelbug;
-        return 0;
-    }
-
-    @thisbugmergelist= split(/ /,$data->{mergedwith});
-    if (!@thisbugmergelist) {
-        &foundbug;
-        $manybugs= 0;
-        $state= 'single';
-        $sref=$ref;
-        &dlex("setbug => 1s");
-        return 1;
-    }
-    &cancelbug;
-    &getmerge;
-    $manybugs= 1;
-    if (!&getbug) {
-        &notfoundbug;
-        &endmerge;
-        &dlex("setbug => 0mc");
-        return 0;
-    }
-    &foundbug;
-    $state= 'multiple'; $sref=$ref;
-    &dlex("setbug => 1m");
-    return 1;
-}
-
-sub getnextbug {
-    &dlen("getnextbug");
-    $state eq 'single' || $state eq 'multiple' || die "$state ?";
-    &savebug;
-    if (!$manybugs || !@thisbugmergelist) {
-        length($action) || die;
-        &transcript("$action\n$extramessage\n");
-        &endmerge if $manybugs;
-        $state= 'idle';
-        &dlex("getnextbug => 0");
-        return 0;
-    }
-    $ref= shift(@thisbugmergelist);
-    &getbug || die "bug $ref disappeared";
-    &foundbug;
-    &dlex("getnextbug => 1");
-    return 1;
-}
-
-# Low-level bug-manipulation calls
-# Do no announcements
-#
-#    getbug (returns 0)
-#
-#    getbug (returns 1)
-#    cancelbug
-#
-#    getmerge
-#    $action= (something)
-#    getbug (returns 1)
-#    savebug/cancelbug
-#    getbug (returns 1)
-#    savebug/cancelbug
-#    [getbug (returns 0)]
-#    &transcript("$action\n\n")
-#    endmerge
-
-sub notfoundbug { &transcript("$gBug number $ref not found. (Is it archived?)\n\n"); }
-sub foundbug { &transcript("$gBug#$ref: $data->{subject}\n"); }
-
-sub getmerge {
-    &dlen("getmerge");
-    $mergelowstate eq 'idle' || die "$mergelowstate ?";
-    &filelock('lock/merge');
-    $mergelowstate='locked';
-    &dlex("getmerge");
-}
-
-sub endmerge {
-    &dlen("endmerge");
-    $mergelowstate eq 'locked' || die "$mergelowstate ?";
-    &unfilelock;
-    $mergelowstate='idle';
-    &dlex("endmerge");
-}
-
-sub getbug {
-    &dlen("getbug $ref");
-    $lowstate eq 'idle' || die "$state ?";
-    # Only use unmerged bugs here
-    if (($data = &lockreadbug($ref,'db-h'))) {
-        $sref= $ref;
-        $lowstate= "open";
-        &dlex("getbug => 1");
-        $extramessage='';
-        return 1;
-    }
-    $lowstate= 'idle';
-    &dlex("getbug => 0");
-    return 0;
-}
-
-sub cancelbug {
-    &dlen("cancelbug");
-    $lowstate eq 'open' || die "$state ?";
-    &unfilelock;
-    $lowstate= 'idle';
-    &dlex("cancelbug");
-}
-
-sub savebug {
-    &dlen("savebug $ref");
-    $lowstate eq 'open' || die "$lowstate ?";
-    length($action) || die;
-    $ref == $sref || die "read $sref but saving $ref ?";
-    append_action_to_log(bug => $ref,
-                        action => $action,
-                        requester => $header{from},
-                        request_addr => $controlrequestaddr,
-                        message => \@log,
-                        get_lock => 0,
-                       );
-    unlockwritebug($ref, $data);
-    $lowstate= "idle";
-    &dlex("savebug");
-}
-
-sub dlen {
-    return if !$dl;
-    &transcript("C> @_ ($state $lowstate $mergelowstate)\n");
-}
-
-sub dlex {
-    return if !$dl;
-    &transcript("R> @_ ($state $lowstate $mergelowstate)\n");
-}
-
-sub transcript {
-    print $_[0] if $debug;
-    $transcript.= $_[0];
-}
-
-sub urlsanit {
-    my $url = shift;
-    $url =~ s/%/%25/g;
-    $url =~ s/\+/%2b/g;
-    my %saniarray = ('<','lt', '>','gt', '&','amp', '"','quot');
-    $url =~ s/([<>&"])/\&$saniarray{$1};/g;
-    return $url;
-}
-
-sub sendlynxdoc {
-    &sendlynxdocraw;
-    &transcript("\n");
-    $ok++;
-}
-
-sub sendtxthelp {
-    &sendtxthelpraw;
-    &transcript("\n");
-    $ok++;
-}
-
-sub sendtxthelpraw {
-    local ($relpath,$description) = @_;
-    $doc='';
-    open(D,"$gDocDir/$relpath") || &quit("open doc file $relpath: $!");
-    while(<D>) { $doc.=$_; }
-    close(D);
-    &transcript("Sending $description in separate message.\n");
-    &sendmailmessage(<<END.$doc,$replyto);
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $replyto
-Subject: $gProject $gBug help: $description
-References: $header{'message-id'}
-In-Reply-To: $header{'message-id'}
-Message-ID: <handler.s.$nn.help.$midix\@$gEmailDomain>
-Precedence: bulk
-X-$gProject-PR-Message: doc-text $relpath
-
-END
-    $ok++;
-}
-
-sub sendlynxdocraw {
-    local ($relpath,$description) = @_;
-    $doc='';
-    open(L,"lynx -nolist -dump http://$gCGIDomain/\Q$relpath\E 2>&1 |") || &quit("fork for lynx: $!");
-    while(<L>) { $doc.=$_; }
-    $!=0; close(L);
-    if ($? == 255 && $doc =~ m/^\n*lynx: Can\'t access start file/) {
-        &transcript("Information ($description) is not available -\n".
-                    "perhaps the $gBug does not exist or is not on the WWW yet.\n");
-         $ok++;
-    } elsif ($?) {
-        &transcript("Error getting $description (code $? $!):\n$doc\n");
-    } else {
-        &transcript("Sending $description.\n");
-        &sendmailmessage(<<END.$doc,$replyto);
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $replyto
-Subject: $gProject $gBugs information: $description
-References: $header{'message-id'}
-In-Reply-To: $header{'message-id'}
-Message-ID: <handler.s.$nn.info.$midix\@$gEmailDomain>
-Precedence: bulk
-X-$gProject-PR-Message: doc-html $relpath
-
-END
-         $ok++;
-    }
-}
-
-sub addccaddress {
-    my ($cca) = @_;
-    $maintccreasons{$cca}{''}{$ref}= 1;
-}
-
-sub addmaintainers {
-    # Data structure is:
-    #   maintainer email address &c -> assoc of packages -> assoc of bug#'s
-    my $data = shift;
-    my ($p, $addmaint);
-    &ensuremaintainersloaded;
-    $anymaintfound=0; $anymaintnotfound=0;
-    for $p (split(m/[ \t?,():]+/, $data->{package})) {
-       $p =~ y/A-Z/a-z/;
-       $p =~ /([a-z0-9.+-]+)/;
-       $p = $1;
-       next unless defined $p;
-       if (defined $gSubscriptionDomain) {
-           if (defined($pkgsrc{$p})) {
-               addbcc("$pkgsrc{$p}\@$gSubscriptionDomain");
-           } else {
-               addbcc("$p\@$gSubscriptionDomain");
-           }
-       }
-        if (defined $data->{severity} and defined $gStrongList and
-                isstrongseverity($data->{severity})) {
-            addbcc("$gStrongList\@$gListDomain");
-        }
-        if (defined($maintainerof{$p})) {
-           $addmaint= $maintainerof{$p};
-           &transcript("MR|$addmaint|$p|$ref|\n") if $dl>2;
-            $maintccreasons{$addmaint}{$p}{$ref}= 1;
-           print "maintainer add >$p|$addmaint<\n" if $debug;
-        } else { 
-           print "maintainer none >$p<\n" if $debug; 
-           &transcript("Warning: Unknown package '$p'\n");
-           &transcript("MR|unknown-package|$p|$ref|\n") if $dl>2;
-            $maintccreasons{$gUnknownMaintainerEmail}{$p}{$ref}= 1;
-       }
-    }
-
-    if (length $data->{owner}) {
-        $addmaint = $data->{owner};
-        &transcript("MO|$addmaint|$data->{package}|$ref|\n") if $dl>2;
-        $maintccreasons{$addmaint}{$data->{package}}{$ref} = 1;
-        print "owner add >$data->{package}|$addmaint<\n" if $debug;
-    }
-}
-
-sub ensuremaintainersloaded {
-    my ($a,$b);
-    return if $maintainersloaded++;
-    open(MAINT,"$gMaintainerFile") || die &quit("maintainers open: $!");
-    while (<MAINT>) {
-       m/^\n$/ && next;
-       m/^\s*$/ && next;
-        m/^(\S+)\s+(\S.*\S)\s*\n$/ || &quit("maintainers bogus \`$_'");
-        $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
-        $maintainerof{$a}= $2;
-    }
-    close(MAINT);
-    open(MAINT,"$gMaintainerFileOverride") || die &quit("maintainers.override open: $!");
-    while (<MAINT>) {
-        m/^\n$/ && next;
-        m/^\s*$/ && next;
-        m/^(\S+)\s+(\S.*\S)\s*\n$/ || &quit("maintainers.override bogus \`$_'");
-        $a= $1; $b= $2; $a =~ y/A-Z/a-z/;
-        $maintainerof{$a}= $2;
-    }
-
-    open(SOURCES, "$gPackageSource") || &quit("pkgsrc open: $!");
-    while (<SOURCES>) {
-       next unless m/^(\S+)\s+\S+\s+(\S.*\S)\s*$/;
-       my ($a, $b) = ($1, $2);
-       $pkgsrc{lc($a)} = $b;
-    }
-    close(SOURCES);
-}
-
-sub sendinfo {
-    local ($wherefrom,$path,$description) = @_;
-    if ($wherefrom eq "ftp.d.o") {
-      $doc = `lynx -nolist -dump http://ftp.debian.org/debian/indices/$path.gz 2>&1 | gunzip -cf` or &quit("fork for lynx/gunzip: $!");
-      $! = 0;
-      if ($? == 255 && $doc =~ m/^\n*lynx: Can\'t access start file/) {
-          &transcript("$description is not available.\n");
-          $ok++; return;
-      } elsif ($?) {
-          &transcript("Error getting $description (code $? $!):\n$doc\n");
-          return;
-      }
-    } elsif ($wherefrom eq "local") {
-      open P, "$path";
-      $doc = do { local $/; <P> };
-      close P;
-    } else {
-      &transcript("internal errror: info files location unknown.\n");
-      $ok++; return;
-    }
-    &transcript("Sending $description.\n");
-    &sendmailmessage(<<END.$doc,$replyto);
-From: $gMaintainerEmail ($gProject $gBug Tracking System)
-To: $replyto
-Subject: $gProject $gBugs information: $description
-References: $header{'message-id'}
-In-Reply-To: $header{'message-id'}
-Message-ID: <handler.s.$nn.info.$midix\@$gEmailDomain>
-Precedence: bulk
-X-$gProject-PR-Message: getinfo
-
-$description follows:
-
-END
-    $ok++;
-    &transcript("\n");
-}
diff --git a/scripts/spamscan b/scripts/spamscan
new file mode 100755 (executable)
index 0000000..9114b83
--- /dev/null
@@ -0,0 +1,325 @@
+#! /usr/bin/perl
+# $Id: spamscan.in,v 1.8 2005/02/01 07:54:01 blarson Exp $
+#
+# Usage: spamscan
+#
+# Performs SpamAssassin checks on a message before allowing it through to
+# the main incoming queue.
+#
+# Uses up: incoming/S<code><bugnum>.nn
+# Temps:   incoming/R.nn
+# Creates: incoming/I.nn
+# Stop:    spamscan-stop
+
+use warnings;
+use strict;
+
+use threads;
+use threads::shared;
+
+use Debbugs::Config qw(:config);
+
+use Debbugs::Common qw(:lock);
+
+use Mail::CrossAssassin;
+use Socket;
+use IO::Handle;
+use IPC::Open2;
+
+
+exit unless $config{spam_scan};
+
+chdir $config{spool_dir} or die "chdir spool: $!\n";
+
+umask 002;
+
+eval {
+    filelock('incoming-spamscan');
+};
+exit if $@;
+
+my %spamseen : shared = ();
+my @ids : shared = ();
+my %fudged : shared = ();
+my $spamscan_stop : shared = 0;
+my $cross_key : shared;
+my @cross_return : shared;
+my $cross_tid : shared;
+my $print_lock : shared;
+my $assassinated_lock : shared;
+my $crossassassinated_lock : shared;
+my $threadsrunning : shared = 0;
+
+# flush output immediately
+$| = 1;
+
+sub lprint ($) {
+    lock $print_lock;
+    print $_[0];
+}
+
+my $user_prefs = "$ENV{HOME}/.spamassassin/user_prefs";
+my $user_prefs_time;
+if (-e $user_prefs) {
+    $user_prefs_time = (stat $user_prefs)[9];
+} else {
+    die "$user_prefs not found";
+}
+
+# This thread handles the updating and querying of the crossassassin db
+sub cross {
+    ca_init('\b\d{3,8}(?:-(?:close|done|forwarded|maintonly|submitter|quiet|subscribe))?\@'.$config{email_domain}, $config{spam_crossassassin_db});
+    my $mytid = threads->self->tid();
+crosscheck:
+    while ($spamscan_stop <= 1) {
+       my ($ck, $ct);
+       {
+           lock $cross_key unless($cross_key);
+           until ($cross_key) {
+               last crosscheck if $spamscan_stop > 1;
+               lprint "{$mytid} cross waiting\n";
+               cond_timedwait $cross_key, (time() + 30);
+           }
+           last crosscheck if ($spamscan_stop > 1);
+           $ck = $cross_key;
+           $ct = $cross_tid;
+           undef $cross_key;
+       }
+       unless ($ck) {
+           lprint "{$mytid} Cross nothing\n";
+           sleep 1;
+           next crosscheck;
+       }
+       lprint "{$mytid} Cross{$ct}: $ck\n";
+       {
+           lock @cross_return;
+           $cross_return[$ct] = ca_set($ck);
+           cond_signal @cross_return;
+       }
+    }
+}
+
+# multiple threads handle spamassassin
+sub sa {
+    {
+       lock $threadsrunning;
+       $threadsrunning++;
+    }
+    my $mytid = threads->self->tid();
+    sleep $mytid + 3;
+    return if $spamscan_stop;
+    my ($sain, $saout);
+
+    my $pid = open2($saout, $sain, "/usr/lib/debbugs/spamscan-sa");
+       lprint "{$mytid} forked $pid\n";
+       my $messages_handled=0;
+pp:    until ($spamscan_stop) {
+           my ($id, $nf);
+           lprint "{$mytid} $messages_handled messages handled\n";
+           $messages_handled++;
+getid:     for (;;) {
+               {
+                   lock @ids;
+                   $nf = @ids;
+                   $id = shift @ids;
+                   last getid if $nf;
+                   cond_timedwait @ids, (time() + 30);
+                   last pp if $spamscan_stop;
+                   $nf = @ids;
+                   $id = shift @ids;
+                   last getid if $nf;
+               }
+               lprint "{$mytid} Waiting for spam to process\n";
+               sleep 1;
+           }
+           print $sain "$id\n$nf\n";
+           lprint "{$mytid} $id is $nf\n";
+           my $keys = <$saout>;
+           unless (defined $keys) {
+               lprint "{$mytid} Could not get keys: $!\n";
+               last pp;
+           }
+           chomp $keys;
+           my $messageid = <$saout>;
+           unless (defined($messageid)) {
+               lprint "{$mytid} Could not read messageid: $!\n";
+               last pp;
+           }
+           chomp $messageid;
+           lprint "{$mytid} $id $keys\n";
+           my $ca_score;
+crosskey:   for (;;) {
+               {
+                   lock $cross_key;
+                   unless ($cross_key) {
+                       $cross_tid = $mytid;
+                       $cross_key = $keys;
+                       cond_signal $cross_key;
+                       last crosskey;
+                   }
+               }
+               lprint "{$mytid} zzz...\n";
+               select undef, undef, undef, 0.1;
+           }
+crossret:   for (;;) {
+               {
+                   lock @cross_return;
+                   if ($cross_return[$mytid]) {
+                       $ca_score = $cross_return[$mytid];
+                       undef $cross_return[$mytid];
+                       last crossret;
+                   }
+               }
+               lprint "{$mytid} z z z...\n";
+               select undef, undef, undef, 0.1;
+           }
+           lprint "{$mytid} $id: ca_score: $ca_score\n";
+           my $seen = $spamseen{$messageid};
+           $seen = '' unless $seen;
+           unless(print $sain "$ca_score\n$seen\n") {
+               lprint "{$mytid} Could not send ca_score: $!\n";
+               last pp;
+           }
+           my $todo = <$saout>;
+           unless (defined($todo)) {
+               lprint "{$mytid} Could not read todo: $!\n";
+               last pp;
+           }
+           chomp $todo;
+           my $nseen;
+           if ($todo == 1) {
+               lock $assassinated_lock;
+               print $sain "$todo\n";
+               $nseen = <$saout>;
+           } elsif ($todo == 2) {
+               lock $crossassassinated_lock;
+               print $sain "$todo\n";
+               $nseen = <$saout>;
+           } else {
+               print $sain "$todo\n";
+               $nseen = <$saout>;
+           }
+           unless(defined($nseen)) {
+               lprint "{$mytid} Could not read seen: $!\n";
+               start_sa() if (scalar(@ids) > ($threadsrunning * $config{spam_spams_per_thread})
+                   && $threadsrunning < $config{spam_max_threads});
+               last pp;
+           }
+           chomp $nseen;
+           $spamseen{$messageid} = $nseen if ($nseen);
+           my $out = <$saout>;
+           unless(defined($out)) {
+               lprint "{$mytid} Could not read out: $!\n";
+               last pp;
+           }
+           chomp $out;
+           $out =~ tr/\r/\n/;
+           lprint $out;
+       }
+       {
+           lock $threadsrunning;
+           $threadsrunning--;
+       }
+        close $sain;
+        close $saout;
+       waitpid($pid,0);
+}
+
+my @sa_threads;
+sub start_sa {
+    my $s = threads->create(\&sa)
+       or die "Could not start sa threads: $!";
+    $s->detach;
+    push @sa_threads, $s;
+}
+
+my $cross_thread = threads->create(\&cross)
+    or die "Could not start cross thread: $!";
+$cross_thread->detach;
+start_sa;
+# start_sa;
+
+my $stopafter = time() + $config{spam_keep_running};
+
+for (;;) {
+    alarm 180;
+    if (-f 'spamscan-stop') {
+       lprint "spamscan-stop file created\n";
+       last;
+    }
+    if ($user_prefs_time != (stat $user_prefs)[9]) {
+       # stop and wait to be re-invoked from cron
+       lprint "File $user_prefs changed\n";
+       last;
+    }
+
+    unless (@ids) {
+       if (time() > $stopafter) {
+           lprint "KeepRunning timer expired\n";
+           last;
+       }
+        my @i;
+       opendir DIR, 'incoming' or die "opendir incoming: $!";
+       while (defined($_ = readdir DIR)) {
+           push @i, $1 if /^S(.*)/;
+       }
+       unless (@i) {
+           lprint "No more spam to process\n";
+           last;
+       }
+       @i = sort {(split(/\./,$a))[1] <=> (split(/\./,$b))[1]} @i;
+       my $m = @i;
+       lprint "Messages to process: $m\n";
+       lock @ids;
+       push @ids, @i;
+       cond_broadcast @ids;
+    }
+    start_sa if (scalar(@ids) > (($threadsrunning - 1) * $config{spam_spams_per_thread})
+                && $threadsrunning < $config{spam_max_threads});
+    sleep 30;
+}
+
+alarm 180;
+
+# wait for the spamassasin threads
+$spamscan_stop = 1;
+{
+    lock @ids;
+    cond_broadcast @ids;
+}
+
+while (my $t = shift @sa_threads) {
+    my $tid = $t->tid;
+    lprint "{} waiting for thread $tid\n";
+    my $max_wait = 60;
+    while ($t->is_running and --$max_wait > 0) {
+        sleep 1;
+    }
+#    $t->join;
+}
+
+# wait for the crossassasin thread
+$spamscan_stop = 2;
+{
+    lprint "{} waiting for cross thread\n";
+    lock $cross_key;
+    $cross_key = 1;
+    cond_signal $cross_key;
+}
+my $max_wait = 60;
+while ($cross_thread->is_running and --$max_wait > 0) {
+    sleep 1;
+}
+#$cross_thread->join;
+
+END{
+   foreach my $thread (threads->list()){
+      $thread->join;
+   }
+}
+
+&unfilelock;
+
+
+
+#exit 0;
diff --git a/scripts/spamscan.in b/scripts/spamscan.in
deleted file mode 100755 (executable)
index 9114b83..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-#! /usr/bin/perl
-# $Id: spamscan.in,v 1.8 2005/02/01 07:54:01 blarson Exp $
-#
-# Usage: spamscan
-#
-# Performs SpamAssassin checks on a message before allowing it through to
-# the main incoming queue.
-#
-# Uses up: incoming/S<code><bugnum>.nn
-# Temps:   incoming/R.nn
-# Creates: incoming/I.nn
-# Stop:    spamscan-stop
-
-use warnings;
-use strict;
-
-use threads;
-use threads::shared;
-
-use Debbugs::Config qw(:config);
-
-use Debbugs::Common qw(:lock);
-
-use Mail::CrossAssassin;
-use Socket;
-use IO::Handle;
-use IPC::Open2;
-
-
-exit unless $config{spam_scan};
-
-chdir $config{spool_dir} or die "chdir spool: $!\n";
-
-umask 002;
-
-eval {
-    filelock('incoming-spamscan');
-};
-exit if $@;
-
-my %spamseen : shared = ();
-my @ids : shared = ();
-my %fudged : shared = ();
-my $spamscan_stop : shared = 0;
-my $cross_key : shared;
-my @cross_return : shared;
-my $cross_tid : shared;
-my $print_lock : shared;
-my $assassinated_lock : shared;
-my $crossassassinated_lock : shared;
-my $threadsrunning : shared = 0;
-
-# flush output immediately
-$| = 1;
-
-sub lprint ($) {
-    lock $print_lock;
-    print $_[0];
-}
-
-my $user_prefs = "$ENV{HOME}/.spamassassin/user_prefs";
-my $user_prefs_time;
-if (-e $user_prefs) {
-    $user_prefs_time = (stat $user_prefs)[9];
-} else {
-    die "$user_prefs not found";
-}
-
-# This thread handles the updating and querying of the crossassassin db
-sub cross {
-    ca_init('\b\d{3,8}(?:-(?:close|done|forwarded|maintonly|submitter|quiet|subscribe))?\@'.$config{email_domain}, $config{spam_crossassassin_db});
-    my $mytid = threads->self->tid();
-crosscheck:
-    while ($spamscan_stop <= 1) {
-       my ($ck, $ct);
-       {
-           lock $cross_key unless($cross_key);
-           until ($cross_key) {
-               last crosscheck if $spamscan_stop > 1;
-               lprint "{$mytid} cross waiting\n";
-               cond_timedwait $cross_key, (time() + 30);
-           }
-           last crosscheck if ($spamscan_stop > 1);
-           $ck = $cross_key;
-           $ct = $cross_tid;
-           undef $cross_key;
-       }
-       unless ($ck) {
-           lprint "{$mytid} Cross nothing\n";
-           sleep 1;
-           next crosscheck;
-       }
-       lprint "{$mytid} Cross{$ct}: $ck\n";
-       {
-           lock @cross_return;
-           $cross_return[$ct] = ca_set($ck);
-           cond_signal @cross_return;
-       }
-    }
-}
-
-# multiple threads handle spamassassin
-sub sa {
-    {
-       lock $threadsrunning;
-       $threadsrunning++;
-    }
-    my $mytid = threads->self->tid();
-    sleep $mytid + 3;
-    return if $spamscan_stop;
-    my ($sain, $saout);
-
-    my $pid = open2($saout, $sain, "/usr/lib/debbugs/spamscan-sa");
-       lprint "{$mytid} forked $pid\n";
-       my $messages_handled=0;
-pp:    until ($spamscan_stop) {
-           my ($id, $nf);
-           lprint "{$mytid} $messages_handled messages handled\n";
-           $messages_handled++;
-getid:     for (;;) {
-               {
-                   lock @ids;
-                   $nf = @ids;
-                   $id = shift @ids;
-                   last getid if $nf;
-                   cond_timedwait @ids, (time() + 30);
-                   last pp if $spamscan_stop;
-                   $nf = @ids;
-                   $id = shift @ids;
-                   last getid if $nf;
-               }
-               lprint "{$mytid} Waiting for spam to process\n";
-               sleep 1;
-           }
-           print $sain "$id\n$nf\n";
-           lprint "{$mytid} $id is $nf\n";
-           my $keys = <$saout>;
-           unless (defined $keys) {
-               lprint "{$mytid} Could not get keys: $!\n";
-               last pp;
-           }
-           chomp $keys;
-           my $messageid = <$saout>;
-           unless (defined($messageid)) {
-               lprint "{$mytid} Could not read messageid: $!\n";
-               last pp;
-           }
-           chomp $messageid;
-           lprint "{$mytid} $id $keys\n";
-           my $ca_score;
-crosskey:   for (;;) {
-               {
-                   lock $cross_key;
-                   unless ($cross_key) {
-                       $cross_tid = $mytid;
-                       $cross_key = $keys;
-                       cond_signal $cross_key;
-                       last crosskey;
-                   }
-               }
-               lprint "{$mytid} zzz...\n";
-               select undef, undef, undef, 0.1;
-           }
-crossret:   for (;;) {
-               {
-                   lock @cross_return;
-                   if ($cross_return[$mytid]) {
-                       $ca_score = $cross_return[$mytid];
-                       undef $cross_return[$mytid];
-                       last crossret;
-                   }
-               }
-               lprint "{$mytid} z z z...\n";
-               select undef, undef, undef, 0.1;
-           }
-           lprint "{$mytid} $id: ca_score: $ca_score\n";
-           my $seen = $spamseen{$messageid};
-           $seen = '' unless $seen;
-           unless(print $sain "$ca_score\n$seen\n") {
-               lprint "{$mytid} Could not send ca_score: $!\n";
-               last pp;
-           }
-           my $todo = <$saout>;
-           unless (defined($todo)) {
-               lprint "{$mytid} Could not read todo: $!\n";
-               last pp;
-           }
-           chomp $todo;
-           my $nseen;
-           if ($todo == 1) {
-               lock $assassinated_lock;
-               print $sain "$todo\n";
-               $nseen = <$saout>;
-           } elsif ($todo == 2) {
-               lock $crossassassinated_lock;
-               print $sain "$todo\n";
-               $nseen = <$saout>;
-           } else {
-               print $sain "$todo\n";
-               $nseen = <$saout>;
-           }
-           unless(defined($nseen)) {
-               lprint "{$mytid} Could not read seen: $!\n";
-               start_sa() if (scalar(@ids) > ($threadsrunning * $config{spam_spams_per_thread})
-                   && $threadsrunning < $config{spam_max_threads});
-               last pp;
-           }
-           chomp $nseen;
-           $spamseen{$messageid} = $nseen if ($nseen);
-           my $out = <$saout>;
-           unless(defined($out)) {
-               lprint "{$mytid} Could not read out: $!\n";
-               last pp;
-           }
-           chomp $out;
-           $out =~ tr/\r/\n/;
-           lprint $out;
-       }
-       {
-           lock $threadsrunning;
-           $threadsrunning--;
-       }
-        close $sain;
-        close $saout;
-       waitpid($pid,0);
-}
-
-my @sa_threads;
-sub start_sa {
-    my $s = threads->create(\&sa)
-       or die "Could not start sa threads: $!";
-    $s->detach;
-    push @sa_threads, $s;
-}
-
-my $cross_thread = threads->create(\&cross)
-    or die "Could not start cross thread: $!";
-$cross_thread->detach;
-start_sa;
-# start_sa;
-
-my $stopafter = time() + $config{spam_keep_running};
-
-for (;;) {
-    alarm 180;
-    if (-f 'spamscan-stop') {
-       lprint "spamscan-stop file created\n";
-       last;
-    }
-    if ($user_prefs_time != (stat $user_prefs)[9]) {
-       # stop and wait to be re-invoked from cron
-       lprint "File $user_prefs changed\n";
-       last;
-    }
-
-    unless (@ids) {
-       if (time() > $stopafter) {
-           lprint "KeepRunning timer expired\n";
-           last;
-       }
-        my @i;
-       opendir DIR, 'incoming' or die "opendir incoming: $!";
-       while (defined($_ = readdir DIR)) {
-           push @i, $1 if /^S(.*)/;
-       }
-       unless (@i) {
-           lprint "No more spam to process\n";
-           last;
-       }
-       @i = sort {(split(/\./,$a))[1] <=> (split(/\./,$b))[1]} @i;
-       my $m = @i;
-       lprint "Messages to process: $m\n";
-       lock @ids;
-       push @ids, @i;
-       cond_broadcast @ids;
-    }
-    start_sa if (scalar(@ids) > (($threadsrunning - 1) * $config{spam_spams_per_thread})
-                && $threadsrunning < $config{spam_max_threads});
-    sleep 30;
-}
-
-alarm 180;
-
-# wait for the spamassasin threads
-$spamscan_stop = 1;
-{
-    lock @ids;
-    cond_broadcast @ids;
-}
-
-while (my $t = shift @sa_threads) {
-    my $tid = $t->tid;
-    lprint "{} waiting for thread $tid\n";
-    my $max_wait = 60;
-    while ($t->is_running and --$max_wait > 0) {
-        sleep 1;
-    }
-#    $t->join;
-}
-
-# wait for the crossassasin thread
-$spamscan_stop = 2;
-{
-    lprint "{} waiting for cross thread\n";
-    lock $cross_key;
-    $cross_key = 1;
-    cond_signal $cross_key;
-}
-my $max_wait = 60;
-while ($cross_thread->is_running and --$max_wait > 0) {
-    sleep 1;
-}
-#$cross_thread->join;
-
-END{
-   foreach my $thread (threads->list()){
-      $thread->join;
-   }
-}
-
-&unfilelock;
-
-
-
-#exit 0;
diff --git a/scripts/summary b/scripts/summary
new file mode 100755 (executable)
index 0000000..5d2b03f
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+# $Id: summary.in,v 1.11 2004/04/17 17:31:04 cjwatson Exp $
+
+$config_path = '/etc/debbugs';
+$lib_path = '/usr/lib/debbugs';
+
+require("$config_path/config");
+require("$lib_path/errorlib");
+$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
+
+chdir("$gSpoolDir") || die "chdir spool: $!\n";
+
+#open(DEBUG,">&4");
+
+$mode= shift(@ARGV);
+
+open(M,"$gMaintainerFile") || die "open $gMaintainerFile: $!";
+while (<M>) {
+    m/^(\S+)\s+(\S.*\S)\s*$/ || warn "$_ ?";
+    ($a,$b)=($1,$2);
+    $a =~ y/A-Z/a-z/;
+    $maintainer{$a}= $b;
+}
+close(M);
+open(M,"$gMaintainerFileOverride") || die "open $gMaintainerFileOverride: $!";
+while (<M>) {
+    m/^(\S+)\s+(\S.*\S)\s*$/ || warn "$_ ?";
+    ($a,$b)=($1,$2);
+    $a =~ y/A-Z/a-z/;
+    $maintainer{$a}= $b;
+}
+close(M);
+               
+
+defined($startdate= time) || die "failed to get time: $!";
+
+opendir(DIR,"db-h") || die "opendir db-h: $!\n";
+@dirs = grep(s,^,db-h/,, grep(m/^\d+$/,readdir(DIR)));
+closedir(DIR);
+foreach my $dir (@dirs) {
+        opendir(DIR,$dir);
+        push @list, grep(s/\.status$//,grep(m/^\d+\.status$/,readdir(DIR)));
+        closedir(DIR);
+}
+@list = sort { $a <=> $b } @list;
+
+$head= $mode eq 'bymaint'
+    ? ' Package     Ref    Subject'
+    : ' Ref   Package    Keywords/Subject               Package maintainer';
+$amonths=-1;
+
+while (length($f=shift(@list))) {
+    if (!($data = lockreadbug($f))) { next; }
+    $_= $data->{package}; y/A-Z/a-z/; $_= $` if m/[^-+._a-z0-9]/;
+    $data->{maintainer}=
+        defined($maintainer{$_}) ? $maintainer{$_} :
+        length($_) ? "(unknown -- \`$_')" :
+        "(unknown)";
+    if ($mode eq 'undone' || $mode eq 'veryold') {
+        &unfilelock;
+        next if length($data->{done}) || length($data->{forwarded});
+        $cmonths= int(($startdate - $data->{date})/2592000); # 3600*24*30 (30 days)
+        next if $mode eq 'veryold' && $cmonths < 2;
+        if ($cmonths != $amonths) {
+            $msg= $cmonths == 0 ? "Submitted in the last month" :
+                  $cmonths == 1 ? "Over one month old" :
+                  $cmonths == 2 ? "Over two months old - attention is required" :
+                                  "OVER $cmonths MONTHS OLD - ATTENTION IS REQUIRED";
+            print "\n$msg:\n$head\n";
+            $amonths= $cmonths;
+        }
+        printf("%6d %-10.10s %-30.30s %-.31s\n", $f, $data->{package},
+               (length($data->{keywords}) ? $data->{keywords}.'/' : '').$data->{subject},
+               $data->{maintainer}) || die "output undone: $!";
+    } elsif ($mode eq 'bymaint') {
+        &unfilelock;
+        next if length($data->{done}) || length($data->{forwarded});
+        $string{$f}=
+            sprintf(" %-10.10s %6d  %-.59s\n", $data->{package}, $f, $data->{subject});
+        $data->{maintainer}= "(unknown)" if $data->{maintainer} =~ m/^\(unknown \-\-/;
+        $maintainercnt{$data->{maintainer}}++;
+        $maintainerlist{$data->{maintainer}}.= " $f";
+    } else {
+        die "badmode $mode";
+    }
+}
+
+if ($mode eq 'bymaint') {
+    print("$head\n") || die "output head: $!";
+    for $m (sort { $maintainercnt{$a} <=> $maintainercnt{$b} } keys %maintainercnt) {
+        printf("\n%s (%d $gBugs):\n",$m,$maintainercnt{$m})
+            || die "output mainthead: $!";
+        for $i (sort { $string{$a} cmp $string{$b} } split(/ /,$maintainerlist{$m})) {
+            printf($string{$i}) || die "output 1bymaint: $!";
+        }
+    }
+}
+
+close(STDOUT) || die "close stdout: $!";
diff --git a/scripts/summary.in b/scripts/summary.in
deleted file mode 100755 (executable)
index a1be697..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/perl
-# $Id: summary.in,v 1.11 2004/04/17 17:31:04 cjwatson Exp $
-
-$config_path = '/etc/debbugs';
-$lib_path = '/usr/lib/debbugs';
-
-require("$config_path/config");
-require("$lib_path/errorlib");
-$ENV{'PATH'} = $lib_path.':'.$ENV{'PATH'};
-
-chdir("$gSpoolDir") || die "chdir spool: $!\n";
-
-#open(DEBUG,">&4");
-
-$mode= shift(@ARGV);
-
-open(M,"$gMaintainerFile") || &quit("open $gMaintainerFile: $!");
-while (<M>) {
-    m/^(\S+)\s+(\S.*\S)\s*$/ || warn "$_ ?";
-    ($a,$b)=($1,$2);
-    $a =~ y/A-Z/a-z/;
-    $maintainer{$a}= $b;
-}
-close(M);
-open(M,"$gMaintainerFileOverride") || &quit("open $gMaintainerFileOverride: $!");
-while (<M>) {
-    m/^(\S+)\s+(\S.*\S)\s*$/ || warn "$_ ?";
-    ($a,$b)=($1,$2);
-    $a =~ y/A-Z/a-z/;
-    $maintainer{$a}= $b;
-}
-close(M);
-               
-
-defined($startdate= time) || &quit("failed to get time: $!");
-
-opendir(DIR,"db-h") || &quit("opendir db-h: $!\n");
-@dirs = grep(s,^,db-h/,, grep(m/^\d+$/,readdir(DIR)));
-closedir(DIR);
-foreach my $dir (@dirs) {
-        opendir(DIR,$dir);
-        push @list, grep(s/\.status$//,grep(m/^\d+\.status$/,readdir(DIR)));
-        closedir(DIR);
-}
-@list = sort { $a <=> $b } @list;
-
-$head= $mode eq 'bymaint'
-    ? ' Package     Ref    Subject'
-    : ' Ref   Package    Keywords/Subject               Package maintainer';
-$amonths=-1;
-
-while (length($f=shift(@list))) {
-    if (!($data = lockreadbug($f))) { next; }
-    $_= $data->{package}; y/A-Z/a-z/; $_= $` if m/[^-+._a-z0-9]/;
-    $data->{maintainer}=
-        defined($maintainer{$_}) ? $maintainer{$_} :
-        length($_) ? "(unknown -- \`$_')" :
-        "(unknown)";
-    if ($mode eq 'undone' || $mode eq 'veryold') {
-        &unfilelock;
-        next if length($data->{done}) || length($data->{forwarded});
-        $cmonths= int(($startdate - $data->{date})/2592000); # 3600*24*30 (30 days)
-        next if $mode eq 'veryold' && $cmonths < 2;
-        if ($cmonths != $amonths) {
-            $msg= $cmonths == 0 ? "Submitted in the last month" :
-                  $cmonths == 1 ? "Over one month old" :
-                  $cmonths == 2 ? "Over two months old - attention is required" :
-                                  "OVER $cmonths MONTHS OLD - ATTENTION IS REQUIRED";
-            print "\n$msg:\n$head\n";
-            $amonths= $cmonths;
-        }
-        printf("%6d %-10.10s %-30.30s %-.31s\n", $f, $data->{package},
-               (length($data->{keywords}) ? $data->{keywords}.'/' : '').$data->{subject},
-               $data->{maintainer}) || &quit("output undone: $!");
-    } elsif ($mode eq 'bymaint') {
-        &unfilelock;
-        next if length($data->{done}) || length($data->{forwarded});
-        $string{$f}=
-            sprintf(" %-10.10s %6d  %-.59s\n", $data->{package}, $f, $data->{subject});
-        $data->{maintainer}= "(unknown)" if $data->{maintainer} =~ m/^\(unknown \-\-/;
-        $maintainercnt{$data->{maintainer}}++;
-        $maintainerlist{$data->{maintainer}}.= " $f";
-    } else {
-        &quit("badmode $mode");
-    }
-}
-
-if ($mode eq 'bymaint') {
-    print("$head\n") || &quit("output head: $!");
-    for $m (sort { $maintainercnt{$a} <=> $maintainercnt{$b} } keys %maintainercnt) {
-        printf("\n%s (%d $gBugs):\n",$m,$maintainercnt{$m})
-            || &quit("output mainthead: $!");
-        for $i (sort { $string{$a} cmp $string{$b} } split(/ /,$maintainerlist{$m})) {
-            printf($string{$i}) || &quit("output 1bymaint: $!");
-        }
-    }
-}
-
-close(STDOUT) || &quit("close stdout: $!");
diff --git a/scripts/text b/scripts/text
new file mode 100644 (file)
index 0000000..415aba0
--- /dev/null
@@ -0,0 +1,342 @@
+# -*- mode: cperl -*-
+
+use Debbugs::Config qw(:globals);
+
+############################################################################
+#  Here is a blurb to point people to ftp archive of directions.  It is
+#  used by the receive script when bouncing a badly formatted email
+#
+# $gTextInstructions = "$gBadEmailPrefix
+# $gBadEmailPrefix Instructions are available from ftp.debian.org in /debian
+# $gBadEmailPrefix and at all Debian mirror sites, in the files:
+# $gBadEmailPrefix  doc/bug-reporting.txt
+# $gBadEmailPrefix  doc/bug-log-access.txt
+# $gBadEmailPrefix  doc/bug-maint-info.txt
+# $gBadEmailPrefix";
+############################################################################
+$gBadEmailPrefix = '' unless defined $gBadEmailPrefix;
+$gTextInstructions = "$gBadEmailPrefix";
+
+
+############################################################################
+# Here is a blurb for any mirrors of the web site.  Here's a sample:
+#
+#$gHTMLCopies = "<p>Copies of the logs are available on the World Wide Web at<BR>
+# <A HREF=\"http://mirror1.domain\"><CODE>http://mirror1.domain</CODE></A><BR>
+# <A HREF=\"http://mirror2.domain\"><CODE>http://mirror2.domain</CODE></A>";
+############################################################################
+$gHTMLCopies = "";
+
+
+############################################################################
+# notice other links you want to note, like your list archives or project
+# home page.
+#
+#$gHTMLOtherPages = "Other Links of note:<BR>
+#      <A HREF=\"http://www.debian.org/\">The Debian Project</A><BR>
+#      <A HREF=\"http://another.domain\">Description of URL</A>";
+############################################################################
+$gHTMLOtherPages = "";
+
+
+############################################################################
+# list of other links you want to note, like your list archives or project
+# home page.  Some pages already have links in a list, this adds them to 
+# the end of the list.
+#
+#$gHTMLOtherPageList = "<LI><A HREF=\"http://www.debian.org/\">
+#      The Debian Project</A>
+#      <LI><A HREF=\"http://another.domain\">Description of URL</A>";
+############################################################################
+$gHTMLOtherPageList = "";
+
+
+############################################################################
+# gives explanation of bad maintainer situation and instructions on how to
+# correct.
+############################################################################
+$gBadMaintHtml = "";
+
+
+############################################################################
+# give directions here for how to find the proper title for Package:
+# pseudo header line.
+############################################################################
+$gHTMLFindPackage = "";
+
+
+############################################################################
+# If you have pseudo packages, place a blurb here.  For example:
+# $gHTMLPseudoDesc = "<p>There are some pseudo-packages available for putting in
+# the <CODE>Package</CODE> line when reporting a $gBug in something other than an
+# actual $gProject software package.  There is 
+# <A HREF="db/ix/pseudopackages.html"> a list of these</A> on the $gBugs WWW 
+# pages.";
+############################################################################
+$gHTMLPseudoDesc = "";
+
+
+############################################################################
+# List any extra information you would like included in bug reports. For
+# example:
+# $gXtraBugInfo = "<li>What kernel version you're using (type
+# <code>uname -a</code>), your shared C library (type <code>ls -l
+# /lib/libc.so.6</code> or <code>dpkg -s libc6 | grep ^Version</code>), and
+# any other details about your Debian system, if it seems appropriate.
+# For example, if you had a problem with a Perl script, you would want to
+# provide the version of the `perl' binary (type <code>perl -v</code> or
+# <code>dpkg -s perl-5.005 | grep ^Version:</code>).";
+############################################################################
+$gXtraBugInfo = "";
+
+
+############################################################################
+# List any extra information you would like about reporting bugs
+############################################################################
+$gXtraReportingInfo = "";
+
+
+############################################################################
+# Process used by system to create Maintainers index file
+############################################################################
+$gCreateMaintainers = "";
+
+
+###########################################################################
+# You shouldn't have to modify anything below here unless it's for personal
+# preference.  Be very careful and don't touch unless you *know* what
+# you're doing.  Much of the stuff has hardcoded duplicates elsewhere.
+
+
+############################################################################
+# Description of the severities
+############################################################################
+$gHTMLSeverityDesc = "<DT><CODE>critical</CODE>
+       <DD>makes unrelated software on the system (or the whole system) break,
+       or causes serious data loss, or introduces a security hole on systems 
+       where you install the package.
+
+       <DT><CODE>grave</CODE>
+       <DD>makes the package in question unusable or mostly so, or causes data
+       loss, or introduces a security hole allowing access to the accounts of
+       users who use the package.
+
+       <DT><CODE>normal</CODE>
+       <DD>the default value, for normal $gBugs.
+
+       <DT><CODE>wishlist</CODE>
+       <DD>for any feature request, and also for any $gBugs that are very 
+       difficult to fix due to major design considerations.";
+
+############################################################################
+# Description of the tags
+############################################################################
+$gHTMLTagDesc = "
+<dt><code>patch</code>
+  <dd>A patch or some other easy procedure for fixing the $gBug is included in
+  the $gBug logs. If there\'s a patch, but it doesn\'t resolve the $gBug
+  adequately or causes some other problems, this tag should not be used.
+
+<dt><code>wontfix</code>
+  <dd>This $gBug won\'t be fixed. Possibly because this is a choice between two
+  arbitrary ways of doing things and the maintainer and submitter prefer
+  different ways of doing things, possibly because changing the behaviour
+  will cause other, worse, problems for others, or possibly for other
+  reasons.
+
+<dt><code>moreinfo</code>
+  <dd>This $gBug can\'t be addressed until more information is provided by the
+  submitter. The $gBug will be closed if the submitter doesn\'t provide more
+  information in a reasonable (few months) timeframe. This is for $gBugs like
+  \"It doesn\'t work\". What doesn\'t work?
+
+<dt><code>unreproducible</code>
+  <dd>This $gBug can\'t be reproduced on the maintainer\'s system.  Assistance
+  from third parties is needed in diagnosing the cause of the problem.
+
+<dt><code>fixed</code>
+  <dd>This $gBug is fixed or worked around, but there\'s still an issue that
+  needs to be resolved.
+
+<dt><code>stable</code>
+  <dd>This $gBug affects the stable distribution in particular.  This is only
+  intended to be used for ease in identifying release critical $gBugs that
+  affect the stable distribution.  It\'ll be replaced eventually with
+  something a little more flexible, probably.
+";
+
+############################################################################
+# shows up at the start of (most) html pages.
+############################################################################
+$gHTMLStart = "<BODY>";
+
+############################################################################
+# shows up at the end of (most) html pages.
+############################################################################
+$gHTMLTail = "
+ <ADDRESS>$gMaintainer &lt;<A HREF=\"mailto:$gMaintainerEmail\">$gMaintainerEmail</A>&gt;.
+ Last modified:
+ <!--timestamp-->
+ SUBSTITUTE_DTIME
+ <!--timestamp-->
+         
+ <P>
+ <A HREF=\"http://$gWebDomain/\">Debian $gBug tracking system</A><BR>
+ Copyright (C) 1999 Darren O. Benham,
+ 1997,2003 nCipher Corporation Ltd,
+ 1994-97 Ian Jackson.
+ </ADDRESS>
+";
+
+############################################################################
+# Message on when reports are purged.
+############################################################################
+$gHTMLExpireNote = "(Closed $gBugs are archived $gRemoveAge days after the last related message is received.)";
+
+############################################################################
+# Makeup of the stamp page
+############################################################################
+$gHTMLStamp = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBugs - timestamp page</TITLE>
+       <LINK REV=\"made\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>Is this $gBug log or mirror up to date?</H1>
+
+       Unlike all the other $gBug pages, this small timestamp page is updated every
+       time the update check job runs.  If the timestamp here is recent it\'s
+       likely that the mirror in which you\'re reading it is up to date.
+       <P>
+       The last
+       <!--updateupdate-->update<!--/updateupdate-->
+       was at 
+       <STRONG><!--updatetime-->SUBSTITUTE_DTIME<!--/updatetime--></STRONG>;
+       The logs are usually checked every hour and updated if necessary.
+       <P>
+       For the $gBug index or for other information about $gProject and the $gBug 
+       system, see the <A HREF=\"../../\">$gBug system main contents page</A>.
+
+       <HR>
+       <ADDRESS>
+       <A HREF=\"mailto:$gMaintainerEmail\">$gMaintainerEmail</A>,
+       through the <A HREF=\"../../\">$gProject $gBug database</a>
+       </ADDRESS>
+       <!--version 1.0-4.3-->";
+
+############################################################################
+# Makeup of the indices pages
+############################################################################
+$gFullIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBugs - full index</TITLE>
+       <LINK REV=\"make\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug report logs - index</H1>
+
+       This index gives access to $gBugs sent to <CODE>submit\@$gEmailDomain</CODE>
+       but not yet marked as done, and to $gBugs marked as done but not yet purged
+       from the database (this happens $gRemoveAge days after the last message relating to
+       the report).
+       <P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+
+       ";
+
+$gJunkIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBug reports - Junk</TITLE>
+       <LINK REV=\"made\" HREF=\"$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug reports - Junk</H1>
+
+       This is the index page for logs of messages not associated with a specific
+       $gBug report.
+       <P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+
+       ";
+
+$gMaintIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBug reports by maintainer</TITLE>
+       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug reports by maintainer</H1>
+
+       This page lists the package maintainers against whose packages there are
+       outstanding, forwarded or recently-closed $gBug reports.  A maintainer who
+       has several versions of their email address in the <CODE>Maintainer</CODE>
+       package control file field may appear several times.<P>
+       If the maintainers information here is not accurate, please see 
+       <A HREF=\"../../Developer.html#maintincorrect\">the developers\'
+       instructions</A> to find how this can happen and what to do about it. <P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+
+       ";
+
+$gPackageIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBug reports by package</TITLE>
+       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug reports by package</H1>
+
+       This page lists the package against which there are outstanding, forwarded or
+       recently-closed $gBug reports.  A multi-binary package may appear several
+       times, once for each binary package name and once for the source package
+       name (if it is different).<P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+
+       ";
+
+$gSummaryIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBug report logs - summary index</TITLE>
+       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug report logs - summary index</H1>
+
+       This summary index briefly lists $gBugs sent to <CODE>submit\@$gEmailDomain
+       </CODE> but not yet marked as done, or as forwarded to an upstream author.  
+       Here they are sorted by reference number (and therefore by submission date, 
+       too).<P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+       <P>The * column lists the first letter of the severity of the $gBug.
+
+
+       ";
+
+$gPackageLog = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBug report logs - index by package</TITLE>
+       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug report logs - index by package</H1>
+
+       This summary index briefly lists $gBugs sent to <CODE>submit\@$gEmailDomain
+       </CODE> but not yet marked as done, or as forwarded to an upstream author.  
+       Here they are sorted by package name.<P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+
+       ";
+
+$gPseudoIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+       <HTML><HEAD><TITLE>$gProject $gBug report pseudo-packages</TITLE>
+       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
+       </HEAD>$gHTMLStart<H1>$gProject $gBug report pseudo-packages</H1>
+
+       This page lists the pseudo-packages available for use in the
+       <CODE>Package:</CODE> line in $gBug reports.<P>
+
+       See the <A HREF=\"../../Reporting.html\">instructions for reporting a
+       $gBug</A> for details of how to specify a <CODE>Package:</CODE> line.<P>
+       For other kinds of indices or for other information about $gProject and
+       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
+       page</A>.
+
+
+       ";
diff --git a/scripts/text.in b/scripts/text.in
deleted file mode 100644 (file)
index 415aba0..0000000
+++ /dev/null
@@ -1,342 +0,0 @@
-# -*- mode: cperl -*-
-
-use Debbugs::Config qw(:globals);
-
-############################################################################
-#  Here is a blurb to point people to ftp archive of directions.  It is
-#  used by the receive script when bouncing a badly formatted email
-#
-# $gTextInstructions = "$gBadEmailPrefix
-# $gBadEmailPrefix Instructions are available from ftp.debian.org in /debian
-# $gBadEmailPrefix and at all Debian mirror sites, in the files:
-# $gBadEmailPrefix  doc/bug-reporting.txt
-# $gBadEmailPrefix  doc/bug-log-access.txt
-# $gBadEmailPrefix  doc/bug-maint-info.txt
-# $gBadEmailPrefix";
-############################################################################
-$gBadEmailPrefix = '' unless defined $gBadEmailPrefix;
-$gTextInstructions = "$gBadEmailPrefix";
-
-
-############################################################################
-# Here is a blurb for any mirrors of the web site.  Here's a sample:
-#
-#$gHTMLCopies = "<p>Copies of the logs are available on the World Wide Web at<BR>
-# <A HREF=\"http://mirror1.domain\"><CODE>http://mirror1.domain</CODE></A><BR>
-# <A HREF=\"http://mirror2.domain\"><CODE>http://mirror2.domain</CODE></A>";
-############################################################################
-$gHTMLCopies = "";
-
-
-############################################################################
-# notice other links you want to note, like your list archives or project
-# home page.
-#
-#$gHTMLOtherPages = "Other Links of note:<BR>
-#      <A HREF=\"http://www.debian.org/\">The Debian Project</A><BR>
-#      <A HREF=\"http://another.domain\">Description of URL</A>";
-############################################################################
-$gHTMLOtherPages = "";
-
-
-############################################################################
-# list of other links you want to note, like your list archives or project
-# home page.  Some pages already have links in a list, this adds them to 
-# the end of the list.
-#
-#$gHTMLOtherPageList = "<LI><A HREF=\"http://www.debian.org/\">
-#      The Debian Project</A>
-#      <LI><A HREF=\"http://another.domain\">Description of URL</A>";
-############################################################################
-$gHTMLOtherPageList = "";
-
-
-############################################################################
-# gives explanation of bad maintainer situation and instructions on how to
-# correct.
-############################################################################
-$gBadMaintHtml = "";
-
-
-############################################################################
-# give directions here for how to find the proper title for Package:
-# pseudo header line.
-############################################################################
-$gHTMLFindPackage = "";
-
-
-############################################################################
-# If you have pseudo packages, place a blurb here.  For example:
-# $gHTMLPseudoDesc = "<p>There are some pseudo-packages available for putting in
-# the <CODE>Package</CODE> line when reporting a $gBug in something other than an
-# actual $gProject software package.  There is 
-# <A HREF="db/ix/pseudopackages.html"> a list of these</A> on the $gBugs WWW 
-# pages.";
-############################################################################
-$gHTMLPseudoDesc = "";
-
-
-############################################################################
-# List any extra information you would like included in bug reports. For
-# example:
-# $gXtraBugInfo = "<li>What kernel version you're using (type
-# <code>uname -a</code>), your shared C library (type <code>ls -l
-# /lib/libc.so.6</code> or <code>dpkg -s libc6 | grep ^Version</code>), and
-# any other details about your Debian system, if it seems appropriate.
-# For example, if you had a problem with a Perl script, you would want to
-# provide the version of the `perl' binary (type <code>perl -v</code> or
-# <code>dpkg -s perl-5.005 | grep ^Version:</code>).";
-############################################################################
-$gXtraBugInfo = "";
-
-
-############################################################################
-# List any extra information you would like about reporting bugs
-############################################################################
-$gXtraReportingInfo = "";
-
-
-############################################################################
-# Process used by system to create Maintainers index file
-############################################################################
-$gCreateMaintainers = "";
-
-
-###########################################################################
-# You shouldn't have to modify anything below here unless it's for personal
-# preference.  Be very careful and don't touch unless you *know* what
-# you're doing.  Much of the stuff has hardcoded duplicates elsewhere.
-
-
-############################################################################
-# Description of the severities
-############################################################################
-$gHTMLSeverityDesc = "<DT><CODE>critical</CODE>
-       <DD>makes unrelated software on the system (or the whole system) break,
-       or causes serious data loss, or introduces a security hole on systems 
-       where you install the package.
-
-       <DT><CODE>grave</CODE>
-       <DD>makes the package in question unusable or mostly so, or causes data
-       loss, or introduces a security hole allowing access to the accounts of
-       users who use the package.
-
-       <DT><CODE>normal</CODE>
-       <DD>the default value, for normal $gBugs.
-
-       <DT><CODE>wishlist</CODE>
-       <DD>for any feature request, and also for any $gBugs that are very 
-       difficult to fix due to major design considerations.";
-
-############################################################################
-# Description of the tags
-############################################################################
-$gHTMLTagDesc = "
-<dt><code>patch</code>
-  <dd>A patch or some other easy procedure for fixing the $gBug is included in
-  the $gBug logs. If there\'s a patch, but it doesn\'t resolve the $gBug
-  adequately or causes some other problems, this tag should not be used.
-
-<dt><code>wontfix</code>
-  <dd>This $gBug won\'t be fixed. Possibly because this is a choice between two
-  arbitrary ways of doing things and the maintainer and submitter prefer
-  different ways of doing things, possibly because changing the behaviour
-  will cause other, worse, problems for others, or possibly for other
-  reasons.
-
-<dt><code>moreinfo</code>
-  <dd>This $gBug can\'t be addressed until more information is provided by the
-  submitter. The $gBug will be closed if the submitter doesn\'t provide more
-  information in a reasonable (few months) timeframe. This is for $gBugs like
-  \"It doesn\'t work\". What doesn\'t work?
-
-<dt><code>unreproducible</code>
-  <dd>This $gBug can\'t be reproduced on the maintainer\'s system.  Assistance
-  from third parties is needed in diagnosing the cause of the problem.
-
-<dt><code>fixed</code>
-  <dd>This $gBug is fixed or worked around, but there\'s still an issue that
-  needs to be resolved.
-
-<dt><code>stable</code>
-  <dd>This $gBug affects the stable distribution in particular.  This is only
-  intended to be used for ease in identifying release critical $gBugs that
-  affect the stable distribution.  It\'ll be replaced eventually with
-  something a little more flexible, probably.
-";
-
-############################################################################
-# shows up at the start of (most) html pages.
-############################################################################
-$gHTMLStart = "<BODY>";
-
-############################################################################
-# shows up at the end of (most) html pages.
-############################################################################
-$gHTMLTail = "
- <ADDRESS>$gMaintainer &lt;<A HREF=\"mailto:$gMaintainerEmail\">$gMaintainerEmail</A>&gt;.
- Last modified:
- <!--timestamp-->
- SUBSTITUTE_DTIME
- <!--timestamp-->
-         
- <P>
- <A HREF=\"http://$gWebDomain/\">Debian $gBug tracking system</A><BR>
- Copyright (C) 1999 Darren O. Benham,
- 1997,2003 nCipher Corporation Ltd,
- 1994-97 Ian Jackson.
- </ADDRESS>
-";
-
-############################################################################
-# Message on when reports are purged.
-############################################################################
-$gHTMLExpireNote = "(Closed $gBugs are archived $gRemoveAge days after the last related message is received.)";
-
-############################################################################
-# Makeup of the stamp page
-############################################################################
-$gHTMLStamp = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBugs - timestamp page</TITLE>
-       <LINK REV=\"made\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>Is this $gBug log or mirror up to date?</H1>
-
-       Unlike all the other $gBug pages, this small timestamp page is updated every
-       time the update check job runs.  If the timestamp here is recent it\'s
-       likely that the mirror in which you\'re reading it is up to date.
-       <P>
-       The last
-       <!--updateupdate-->update<!--/updateupdate-->
-       was at 
-       <STRONG><!--updatetime-->SUBSTITUTE_DTIME<!--/updatetime--></STRONG>;
-       The logs are usually checked every hour and updated if necessary.
-       <P>
-       For the $gBug index or for other information about $gProject and the $gBug 
-       system, see the <A HREF=\"../../\">$gBug system main contents page</A>.
-
-       <HR>
-       <ADDRESS>
-       <A HREF=\"mailto:$gMaintainerEmail\">$gMaintainerEmail</A>,
-       through the <A HREF=\"../../\">$gProject $gBug database</a>
-       </ADDRESS>
-       <!--version 1.0-4.3-->";
-
-############################################################################
-# Makeup of the indices pages
-############################################################################
-$gFullIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBugs - full index</TITLE>
-       <LINK REV=\"make\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug report logs - index</H1>
-
-       This index gives access to $gBugs sent to <CODE>submit\@$gEmailDomain</CODE>
-       but not yet marked as done, and to $gBugs marked as done but not yet purged
-       from the database (this happens $gRemoveAge days after the last message relating to
-       the report).
-       <P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-
-       ";
-
-$gJunkIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBug reports - Junk</TITLE>
-       <LINK REV=\"made\" HREF=\"$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug reports - Junk</H1>
-
-       This is the index page for logs of messages not associated with a specific
-       $gBug report.
-       <P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-
-       ";
-
-$gMaintIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBug reports by maintainer</TITLE>
-       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug reports by maintainer</H1>
-
-       This page lists the package maintainers against whose packages there are
-       outstanding, forwarded or recently-closed $gBug reports.  A maintainer who
-       has several versions of their email address in the <CODE>Maintainer</CODE>
-       package control file field may appear several times.<P>
-       If the maintainers information here is not accurate, please see 
-       <A HREF=\"../../Developer.html#maintincorrect\">the developers\'
-       instructions</A> to find how this can happen and what to do about it. <P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-
-       ";
-
-$gPackageIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBug reports by package</TITLE>
-       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug reports by package</H1>
-
-       This page lists the package against which there are outstanding, forwarded or
-       recently-closed $gBug reports.  A multi-binary package may appear several
-       times, once for each binary package name and once for the source package
-       name (if it is different).<P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-
-       ";
-
-$gSummaryIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBug report logs - summary index</TITLE>
-       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug report logs - summary index</H1>
-
-       This summary index briefly lists $gBugs sent to <CODE>submit\@$gEmailDomain
-       </CODE> but not yet marked as done, or as forwarded to an upstream author.  
-       Here they are sorted by reference number (and therefore by submission date, 
-       too).<P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-       <P>The * column lists the first letter of the severity of the $gBug.
-
-
-       ";
-
-$gPackageLog = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBug report logs - index by package</TITLE>
-       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug report logs - index by package</H1>
-
-       This summary index briefly lists $gBugs sent to <CODE>submit\@$gEmailDomain
-       </CODE> but not yet marked as done, or as forwarded to an upstream author.  
-       Here they are sorted by package name.<P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-
-       ";
-
-$gPseudoIndex = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
-       <HTML><HEAD><TITLE>$gProject $gBug report pseudo-packages</TITLE>
-       <LINK REF=\"made\" HREF=\"mailto:$gMaintainerEmail\">
-       </HEAD>$gHTMLStart<H1>$gProject $gBug report pseudo-packages</H1>
-
-       This page lists the pseudo-packages available for use in the
-       <CODE>Package:</CODE> line in $gBug reports.<P>
-
-       See the <A HREF=\"../../Reporting.html\">instructions for reporting a
-       $gBug</A> for details of how to specify a <CODE>Package:</CODE> line.<P>
-       For other kinds of indices or for other information about $gProject and
-       the $gBug system, see <A HREF=\"../../\">$gBug system top-level contents WWW
-       page</A>.
-
-
-       ";
index f83eabb9a84237e2e9fb7903ebc4ef1a919773e5..ccf1ee7924e8abdfd0450bbe802b61431caf0855 100644 (file)
@@ -228,6 +228,7 @@ while (my ($command,$control_command) = splice(@control_commands,0,2)) {
                              Subject => "Munging a bug with $command",
                             ],
                  body => <<EOF) or fail 'message to control@bugs.something failed';
+debug 10
 $control_command->{command} 1$control_command->{value}
 thanks
 EOF
index dedd4458e0b342fed92ba16f97472dd0e7285e4f..78fbdc74b4cd3742c30dd088ea1555eeea9492cb 100644 (file)
@@ -74,7 +74,8 @@ my $mech = Test::WWW::Mechanize->new();
 
 $mech->get_ok('http://localhost:'.$port.'/?bug=1',
              'Page received ok');
-ok($mech->content() =~ qr/\<title\>\#1\s+\-\s+Submitting a bug/i,
+ok($mech->content() =~ qr/\<title\>\#1.+Submitting a bug/i,
    'Title of bug is submitting a bug');
 
 # Other tests for bugs in the page should be added here eventually
+
index 3154a0835aa11fcfefca31bef29b2541eaefdd2e..2a04c60e07da530176262ced80b81d4150a16690 100644 (file)
@@ -6,13 +6,6 @@ use Test::More tests => 4;
 use warnings;
 use strict;
 
-# Here, we're going to shoot messages through a set of things that can
-# happen.
-
-# First, we're going to send mesages to receive.
-# To do so, we'll first send a message to submit,
-# then send messages to the newly created bugnumber.
-
 use IO::File;
 use File::Temp qw(tempdir);
 use Cwd qw(getcwd);
@@ -32,7 +25,7 @@ if ($@) {
      BAIL_OUT($@);
 }
 
-# Output some debugging information if there's an error
+# Output some debugging information if we're debugging
 END{
      if ($ENV{DEBUG}) {
          foreach my $key (keys %config) {
@@ -55,7 +48,7 @@ This is a silly bug
 EOF
 
 
-# test bugreport.cgi
+# test the soap server
 
 my $port = 11343;
 
@@ -69,8 +62,10 @@ our $child_pid = undef;
 
 END{
      if (defined $child_pid) {
+         my $temp_exit = $?;
          kill(15,$child_pid);
          waitpid(-1,0);
+         $? = $temp_exit;
      }
 }
 
index 5d04848bf255f71bfe9d3627694ae733280dd2bb..580c41d5ebaa06c199e52f766d0fae39c064987f 100644 (file)
@@ -151,16 +151,16 @@ sub send_message{
      my $output='';
      local $SIG{PIPE} = 'IGNORE';
      local $SIG{CHLD} = sub {};
-     my $pid = open3($wfd,$rfd,$rfd,'scripts/receive.in')
-         or die "Unable to start receive.in: $!";
+     my $pid = open3($wfd,$rfd,$rfd,'scripts/receive')
+         or die "Unable to start receive: $!";
      print {$wfd} create_mime_message($param{headers},
-                                        $param{body}) or die "Unable to to print to receive.in";
-     close($wfd) or die "Unable to close receive.in";
+                                        $param{body}) or die "Unable to to print to receive";
+     close($wfd) or die "Unable to close receive";
      my $err = $? >> 8;
      my $childpid = waitpid($pid,0);
      if ($childpid != -1) {
          $err = $? >> 8;
-         print STDERR "receive.in pid: $pid doesn't match childpid: $childpid\n" if $childpid != $pid;
+         print STDERR "receive pid: $pid doesn't match childpid: $childpid\n" if $childpid != $pid;
      }
      if ($err != 0 ) {
          my $rfh =  IO::Handle->new_from_fd($rfd,'r') or die "Unable to create filehandle: $!";
@@ -171,11 +171,11 @@ sub send_message{
               print STDERR "Reading from STDOUT/STDERR would have blocked.";
          }
          print STDERR $output,qq(\n);
-         die "receive.in failed with exit status $err";
+         die "receive failed with exit status $err";
      }
      # now we should run processall to see if the message gets processed
      if ($param{run_processall}) {
-         system('scripts/processall.in') == 0 or die "processall.in failed";
+         system('scripts/processall') == 0 or die "processall failed";
      }
 }
 
@@ -190,8 +190,10 @@ sub send_message{
      END {
          if (defined $child_pid) {
               # stop the child
+              my $temp_exit = $?;
               kill(15,$child_pid);
               waitpid(-1,0);
+              $? = $temp_exit;
          }
      }
 
diff --git a/templates/en_US/cgi/bugreport.tmpl b/templates/en_US/cgi/bugreport.tmpl
new file mode 100644 (file)
index 0000000..57cf044
--- /dev/null
@@ -0,0 +1,48 @@
+{include(q(html/pre_title))}#{$bug_num} - {html_escape($status{subject})} - {html_escape($config{project})} {html_escape($config{bug})} report logs{include(q(html/post_title.tmpl))}
+<script type="text/javascript">
+<!--
+function toggle_infmessages()
+\{
+        allDivs=document.getElementsByTagName("div");
+        for (var i = 0 ; i < allDivs.length ; i++ )
+        \{
+                if (allDivs[i].className == "infmessage")
+                \{
+                        allDivs[i].style.display=(allDivs[i].style.display == 'none' | allDivs[i].style.display == '') ? 'block' : 'none';
+                \}
+        \}
+\}
+-->
+</script>
+</head>
+<body>
+<h1>{html_escape($config{project})} {html_escape($config{bug})} report logs - 
+<a href="mailto:{$bug_num}@{html_escape($config{email_domain})}">#{$bug_num}</a><br/>
+{html_escape($status{subject})}</h1>
+<div class="versiongraph">{$version_graph}</div>
+{include(q(cgi/bugreport_pkginfo))}
+{include(q(cgi/bugreport_buginfo))}
+{ my $output = '';
+  if (looks_like_number($msg)) {
+     $output .= sprintf qq(<p><a href="%s">Full log</a></p>),html_escape(bug_links(bug=>$ref,links_only=>1));
+  }
+  else {
+     $output .=  qq(<p><a href="mailto:$bug_num\@$config{email_domain}">Reply</a> ).
+         qq(or <a href="mailto:$bug_num-subscribe\@$config{email_domain}">subscribe</a> ).
+              qq(to this bug.</p>\n);
+     $output .=  qq(<p><a href="javascript:toggle_infmessages();">Toggle useless messages</a></p>);
+     $output .= sprintf qq(<div class="msgreceived"><p>View this report as an <a href="%s">mbox folder</a>, ).
+         qq(<a href="%s">status mbox</a>, <a href="%s">maintainer mbox</a></p></div>\n),
+              html_escape(bug_links(bug=>$bug_num, links_only=>1,options=>{mbox=>'yes'})),
+                   html_escape(bug_links(bug=>$bug_num, links_only=>1,options=>{mbox=>'yes',mboxstatus=>'yes'})),
+                        html_escape(bug_links(bug=>$bug_num, links_only=>1,options=>{mbox=>'yes',mboxmaint=>'yes'}));
+  }
+  $output;
+}
+{$log}
+<hr>
+<p class="msgreceived">Send a report that <a href="{$config{cgi_domain}}/bugspam.cgi">this bug log contains spam</a>.</p>
+<hr>
+{include(q(html/html_tail))}
+</body>
+</html>
diff --git a/templates/en_US/cgi/bugreport_buginfo.tmpl b/templates/en_US/cgi/bugreport_buginfo.tmpl
new file mode 100644 (file)
index 0000000..6bd16e0
--- /dev/null
@@ -0,0 +1,62 @@
+<div class="buginfo">
+  <p>Reported by: {package_links(submitter=>$status{originator})}</p>
+  <p>Date: {$status{date_text}}</p>
+{ my $output = ''; 
+  if (defined $status{owner} and length $status{owner}) {
+     $output = q(<p>Owned by: ).package_links(owner=>$status{owner}).q(</p>);
+  }
+  $output;
+}
+<p>Severity: {my $output = $status{severity};
+              if (isstrongseverity($status{severity})) {
+                   $output = q(<em class="severity">).$status{severity}.q(</em>);
+              }
+              $output;
+             }</p>
+<p>{@{$status{tags_array}}?q(Tags: ).html_escape(join(q(, ),@{$status{tags_array}})):''}</p>
+{my $output = '';
+ if (@{$status{mergedwith_array}}) {
+    $output .= q(<p>Merged with ).join(qq(,\n),bug_links(bug=>$status{mergedwith_array})).qq(</p>\n);
+ }
+ $output;
+}
+{my $output = '';
+ if (@{$status{found_versions}}) {
+    $output .= q(<p>Found in );
+    $output .= (@{$status{found_versions}} == 1) ? 'version ' : 'versions ';
+    $output .= join(qq(, ),map {html_escape($_);} @{$status{found_versions}}).qq(</p>\n);
+ }
+ if (@{$status{fixed_versions}}) {
+    $output .= q(<p>Fixed in );
+    $output .= (@{$status{fixed_versions}} == 1) ? 'version ' : 'versions ';
+    $output .= join(qq(, ),map {html_escape($_);} @{$status{fixed_versions}}).qq(</p>\n);
+ }
+ $output;
+}
+{ my $output = '';
+  if (length($status{done})) {
+     $output .= q(<p><strong>Done:</strong> ).html_escape($status{done}).q(</p>)
+  }
+  $output;
+}
+{ my $output = '';
+  if (@{$status{blockedby_array}}) {
+     $output .= q(<p>Fix blocked by ).
+        join(q(, ),
+         map {bug_links(bug=>$_->{bug_num}).q(: ).html_escape($_->{subject})}
+         @{$status{blockedby_array}}).q(</p>)
+  }
+  if (@{$status{blocks_array}}) {
+     $output .= q(<p>Blocking fix for ).
+        join(q(, ),
+         map {bug_links(bug=>$_->{bug_num}).q(: ).html_escape($_->{subject})}
+         @{$status{blocks_array}}).q(</p>)
+  }
+  $output;
+}
+{ my $output = '';
+  if (exists $status{archived} and $status{archived}) {
+     $output .= q(<p>Bug is archived. No further changes may be made.<p>)
+  }
+  $output
+}</div>
diff --git a/templates/en_US/cgi/bugreport_pkginfo.tmpl b/templates/en_US/cgi/bugreport_pkginfo.tmpl
new file mode 100644 (file)
index 0000000..22806f7
--- /dev/null
@@ -0,0 +1,16 @@
+<div class="pkginfo">
+  <p>{if (keys %package > 1) { q(Packages)} else {q(Package)}}:
+     {join(q(, ),package_links(package => [map {$_->{package}} values %package],
+                               class => q(submitter),
+                              )
+          )};
+{my $output ='';
+ for my $package (values %package) {
+     $output .= q(Maintainer for ).package_links(package=>$package->{package}).qq( is ).
+                package_links(maintainer => $package->{maintainer}).qq(; );
+     $output .= q(Source for ).package_links(package=>$package->{package}).qq( is ).
+                package_links(source => $package->{source}).qq(. );
+ }
+ $output;
+}</p>
+</div>
diff --git a/templates/en_US/cgi/no_such_bug.tmpl b/templates/en_US/cgi/no_such_bug.tmpl
new file mode 100644 (file)
index 0000000..107f9f2
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head><title>#{$bug_num} - {$config{project}} {$config{bug}} report logs</title></head>
+<body>
+<h1>{$config{project}} {$config{bug}} report logs - #{$bug_num}</h1>
+<p>There is no record of {$config{bug}} #{$bug_num}.
+Try the <a href="http://{$config{web_domain}}/">search page</a> instead.</p>
+{include('html/tail')}
+</body></html>
diff --git a/templates/en_US/cgi/pkgreport_javascript.tmpl b/templates/en_US/cgi/pkgreport_javascript.tmpl
new file mode 100644 (file)
index 0000000..f801df9
--- /dev/null
@@ -0,0 +1,128 @@
+<script type="text/javascript">
+<!--
+toggle_extra_status_visible();
+function pagemain() \{
+       toggle(1);
+//     toggle(2);
+       enable(1);
+\}
+
+var visible_extra_status = 0;
+
+function toggle_extra_status_visible() \{
+  all_divs = document.getElementsByTagName("div");
+  for (var i = 0; i < all_divs.length; i++) \{
+      if (all_divs[i].className == "shortbugstatusextra") \{
+         if (all_divs[i].style.position == 'absolute' ) \{
+           all_divs[i].style.position = "static";
+           all_divs[i].style.display = "block";
+           all_divs[i].style.zIndex = 0;
+           all_divs[i].style.border = 0;
+           var subspans = all_divs[i].getElementsByTagName("span");
+           for (var j = 0; j < subspans.length; j++) \{
+               subspans[j].style.display = "inline";
+           \}
+         \}
+        else \{
+           all_divs[i].style.position = "absolute";
+           all_divs[i].style.display = "none"
+           all_divs[i].style.zIndex = 1;
+           all_divs[i].style.border = "#000 1px solid";
+           var subspans = all_divs[i].getElementsByTagName("span");
+           for (var j = 0; j < subspans.length; j++) \{
+               subspans[j].style.display = "block";
+           \}
+         \}
+      \}
+  \}
+\}
+
+function extra_status_visible(id) \{
+  if (visible_extra_status) \{
+     var t = document.getElementById("extra_status_"+visible_extra_status);
+     t.style.display = "none";
+     if (visible_extra_status == id) \{
+       visible_extra_status = 0;
+        return;
+     \}
+     visible_extra_status = 0;
+  \}
+  var e = document.getElementById("extra_status_"+id);
+  if (e) \{
+     e.style.display = "block";
+     visible_extra_status = id;
+  \}
+\}
+
+function setCookie(name, value, expires, path, domain, secure) \{
+  var curCookie = name + "=" + escape(value) +
+      ((expires) ? "; expires=" + expires.toGMTString() : "") +
+      ((path) ? "; path=" + path : "") +
+      ((domain) ? "; domain=" + domain : "") +
+      ((secure) ? "; secure" : "");
+  document.cookie = curCookie;
+\}
+
+function save_cat_cookies() \{
+  var cat = document.categories.categorisation.value;
+  var exp = new Date();
+  exp.setTime(exp.getTime() + 10 * 365 * 24 * 60 * 60 * 1000);
+  var oldexp = new Date();
+  oldexp.setTime(oldexp.getTime() - 1 * 365 * 24 * 60 * 60 * 1000);
+  var lev;
+  var done = 0;
+
+  var u = document.getElementById("users");
+  if (u != null) \{ u = u.value; \}
+  if (u == "") \{ u = null; \}
+  if (u != null) \{
+      setCookie("cat" + cat + "_users", u, exp, "/");
+  \} else \{
+      setCookie("cat" + cat + "_users", "", oldexp, "/");
+  \}
+
+  var bits = new Array("nam", "pri", "ttl", "ord");
+  for (var i = 0; i < 4; i++) \{
+      for (var j = 0; j < bits.length; j++) \{
+          var e = document.getElementById(bits[j] + i);
+         if (e) e = e.value;
+         if (e == null) \{ e = ""; \}
+         if (j == 0 && e == "") \{ done = 1; \}
+         if (done || e == "") \{
+              setCookie("cat" + cat + "_" + bits[j] + i, "", oldexp, "/");
+         \} else \{
+              setCookie("cat" + cat + "_" + bits[j] + i, e, exp, "/");
+         \}
+      \}
+  \}
+\}
+
+function toggle(i) \{
+        var a = document.getElementById("a_" + i);
+        if (a) \{
+             if (a.style.display == "none") \{
+                     a.style.display = "";
+             \} else \{
+                     a.style.display = "none";
+             \}
+        \}
+\}
+
+function enable(x) \{
+    for (var i = 1; ; i++) \{
+        var a = document.getElementById("b_" + x + "_" + i);
+        if (a == null) break;
+        var ischecked = a.checked;
+        for (var j = 1; ; j++) \{
+            var b = document.getElementById("b_" + x + "_"+ i + "_" + j);
+            if (b == null) break;
+            if (ischecked) \{
+                b.disabled = false;
+            \} else \{
+                b.disabled = true;
+            \}
+        \}
+    \}
+\}
+-->
+</script>
diff --git a/templates/en_US/cgi/pkgreport_options.tmpl b/templates/en_US/cgi/pkgreport_options.tmpl
new file mode 100644 (file)
index 0000000..7fa264a
--- /dev/null
@@ -0,0 +1,83 @@
+<form method="GET">
+<input type="hidden" name="_fo_combine_key_fo_searchkey_value_fo_searchvalue" value="1">
+<input type="hidden" name="form_options" value="1">
+<table class="forms">
+<tr><td><h2>Select bugs</h2>
+</td>
+<td>
+{ my $output = '';
+our $value_index = 0;
+our $search = '';
+our $search_value = '';
+for my $key (@search_key_order){
+   if (exists $param{$key}){
+        for my $value (make_list($param{$key})){
+           $search = $key;
+          $search_value = $value;
+          $output .= include('cgi/pkgreport_options_search_key');
+          $output .= '<br>';
+          $value_index++;
+        }
+   }
+ }
+ $search = '';
+ $search_value = '';
+ $output;
+}
+{include('cgi/pkgreport_options_search_key')}
+</td>
+<td>
+<p>The same search fields are ORed, different fields are ANDed.</p>
+<p>Valid severities are {$config{show_severities}}</p>
+<p>Valid tags are {join(', ',@{$config{tags}})}</p>
+</td>
+</tr>
+<tr><td><h2>Include Bugs</h2></td>
+<td>{our $incexc = 'include';
+include('cgi/pkgreport_options_include_exclude');
+}</td>
+<td></td>
+</tr>
+<tr><td><h2>Exclude Bugs</h2></td>
+<td>
+{our $incexc = 'exclude';
+include('cgi/pkgreport_options_include_exclude');
+}
+</td>
+<td></td>
+</tr>
+<tr><td><h2>Categorize using</h2></td>
+<td></td>
+</tr>
+<tr><td><h2>Order by</h2></td>
+<td><select name="ordering">{ my $output = '';
+  my @orderings = qw(normal oldview raw age);
+  for my $order (@orderings) {
+    $output .= '<option value="'.$order.'"'.(($order eq $param{ordering})?' selected':'').
+     ">$order</option>\n";
+  }
+$output;
+}</td>
+<td></td>
+</tr>
+<tr><td><h2>Misc options</h2></td>
+<td>
+<input type="checkbox" name="repeatmerged" {exists $param{repeatmerged} and $param{repeatmerged}?' checked':''}> Repeat Merged<br>
+<input type="checkbox" name="bug-rev" {exists $param{"bug-rev"} and $param{"bug-rev"}?' checked':''}> Reverse Bugs<br>
+<input type="checkbox" name="pend-rev" {exists $param{"pend-rev"} and $param{"pend-rev"}?' checked':''}> Reverse Pending<br>
+<input type="checkbox" name="sev-rev" {exists $param{"sev-rev"} and $param{"sev-rev"}?' checked':''}> Reverse Severity<br>
+<select name="archive">
+{output_select_options([0 => 'Unarchived',
+                        1 => 'Archived', 
+                        both => 'Archived and Unarchived',
+                       ],$param{archive}||0)
+}</select><br>
+<a href="javascript:toggle_extra_status_visible()">Toggle all extra information</a>
+</td>
+</tr>
+<tr><td><h2>Submit</h2></td><td colspan=2>
+<input type="submit" name="submit" value="Submit">
+</td></tr>
+</table>
+
+
diff --git a/templates/en_US/cgi/pkgreport_options_include_exclude.tmpl b/templates/en_US/cgi/pkgreport_options_include_exclude.tmpl
new file mode 100644 (file)
index 0000000..c0f8acd
--- /dev/null
@@ -0,0 +1,16 @@
+<input type="hidden" name="_fo_concatenate_into_{$incexc}_fo_{$incexc}key_fo_{$incexc}value" value="1">
+{ my $output = '';
+  our $value_index = 0;
+  our $key1 = '';
+  our $key2 = '';
+  for my $field (make_list($param{$incexc})) {
+    ($key1,$key2) = $field =~ m/^([^:]+)\:(.+)/;
+    next unless defined $key2;
+    $output .= include('cgi/pkgreport_options_include_exclude_key');
+  }
+  $key1 = '';
+  $key2 = '';
+  $output .= include('cgi/pkgreport_options_include_exclude_key');
+  $output;
+}
+
diff --git a/templates/en_US/cgi/pkgreport_options_include_exclude_key.tmpl b/templates/en_US/cgi/pkgreport_options_include_exclude_key.tmpl
new file mode 100644 (file)
index 0000000..da67c30
--- /dev/null
@@ -0,0 +1,14 @@
+<nobr><select name="_fo_{$incexc}key">
+{output_select_options([subject => 'with subject containing',
+                               tags => 'tagged',
+                       severity => 'with severity',
+                        pending  => 'with pending state',
+                        originator => 'with submitter containing',
+                       forwarded  => 'with forwarded containing',
+                       owner      => 'with owner containing',
+                       package    => 'with package',
+                       ],$key1||'')}
+</select>
+<input type="text" name="_fo_{$incexc}value" value ="{$key2||''}">
+<!-- {$value_index} -->
+</nobr>
diff --git a/templates/en_US/cgi/pkgreport_options_search_key.tmpl b/templates/en_US/cgi/pkgreport_options_search_key.tmpl
new file mode 100644 (file)
index 0000000..1c2ecd9
--- /dev/null
@@ -0,0 +1,6 @@
+<nobr><select name="_fo_searchkey">
+{output_select_options(\@search_key_order,$search||'')}
+</select>
+<input type="text" name="_fo_searchvalue" value ="{$search_value||''}">
+<!-- {$value_index} -->
+</nobr>
diff --git a/templates/en_US/cgi/quit.tmpl b/templates/en_US/cgi/quit.tmpl
new file mode 100644 (file)
index 0000000..2a89d8e
--- /dev/null
@@ -0,0 +1,6 @@
+<HTML>
+<HEAD><TITLE>Error</TITLE></HEAD>
+<BODY>
+An error occurred.
+Error was: {$msg}
+</BODY></HTML>
diff --git a/templates/en_US/cgi/short_bug_status.tmpl b/templates/en_US/cgi/short_bug_status.tmpl
new file mode 100644 (file)
index 0000000..94392d6
--- /dev/null
@@ -0,0 +1,138 @@
+<div class="shortbugstatus">
+  <a href="{html_escape(bug_links(bug=>$status{bug_num},links_only=>1))}"{length($status{done})?' style="text-decoration:line-through"':''}>#{html_escape($status{bug_num})}</a>
+  [<font face="fixed"><a href="javascript:extra_status_visible({html_escape($status{bug_num})})">{
+  my $output = qq(<span title="$status{severity}">);
+  my $temp = $status{severity};
+  $temp = substr $temp,0,1;
+  if (isstrongseverity($status{severity})){
+     $temp = q(<em class="severity">).uc($temp).q(</em);
+  }
+  $output .= $temp.qq(</span>);
+  $output;
+  }|{
+  my $output = '';
+  for my $tag (@{$status{tags_array}}) {
+     next unless exists $config{tags_single_letter}{$tag};
+     $output .= q(<span title=").$tag.q(">).$config{tags_single_letter}{$tag}.q(</span>);
+  }
+  $output;
+  }|{
+  my $output = '';
+  if (@{$status{mergedwith_array}}) {
+     $output .= qq(<span title="merged">=</span>);
+  }
+  if (@{$status{fixed_versions}}) {
+     $output .= qq(<span title="fixed versions">☺</span>);
+  }
+  if (@{$status{blockedby_array}}) {
+     $output .= qq(<span title="blocked by">┫</span>);
+  }
+  if (@{$status{blocks_array}}) {
+     $output .= qq(<span title="blocks">┣</span>);
+  }
+  if (length($status{forwarded})) {
+     $output .= qq(<span title="forwarded">↝</span>);
+  }
+  if ($status{archived}) {
+     $output .= qq(<span title="archived">♲</span>);
+  }
+  $output;
+  }</a></font>]
+  [{package_links(package=>$status{package},options=>\%options,class=>"submitter")}]
+  <a href="{html_escape(bug_links(bug=>$status{bug_num},links_only=>1))}">{html_escape($status{subject})}</a>
+  <div id="extra_status_{html_escape($status{bug_num})}" class="shortbugstatusextra">
+  <span>Reported by: {package_links(submitter=>$status{originator})};</span>
+  <span>Date: {$status{date_text}};</span>
+{ my $output = ''; 
+  if (defined $status{owner} and length $status{owner}) {
+     $output = q(<span>Owned by: ).package_links(owner=>$status{owner}).q(;</span>);
+  }
+  $output;
+}
+<span>Severity: {my $output = $status{severity};
+              if (isstrongseverity($status{severity})) {
+                   $output = q(<em class="severity">).$status{severity}.q(</em>);
+              }
+              $output;
+             };</span>
+<span>{@{$status{tags_array}}?q(Tags: ).html_escape(join(q(, ),@{$status{tags_array}})).';':''}</span>
+{my $output = '';
+ if (@{$status{mergedwith_array}}) {
+    $output .= q(<span>Merged with ).join(qq(,\n),bug_links(bug=>$status{mergedwith_array})).qq(;</span>\n);
+ }
+ $output;
+}
+{my $output = '';
+ if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
+    $output .= '<a href="'.
+         version_url(package => $status{package},
+                     found   => $status{found_versions},
+                     fixed   => $status{fixed_versions},
+                    ).'") ';
+ }
+ if (@{$status{found_versions}}) {
+    $output .= q(<span>Found in );
+    $output .= (@{$status{found_versions}} == 1) ? 'version ' : 'versions ';
+    $output .= join(qq(, ),map {html_escape($_);} @{$status{found_versions}}).qq(;</span>\n);
+ }
+ if (@{$status{fixed_versions}}) {
+    $output .= q(<span>Fixed in );
+    $output .= (@{$status{fixed_versions}} == 1) ? 'version ' : 'versions ';
+    $output .= join(qq(, ),map {html_escape($_);} @{$status{fixed_versions}}).qq(;</span>\n);
+ }
+ if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
+    $output .= qq(</a>);
+ }
+ $output;
+}
+{ my $output = '';
+  if (length($status{done})) {
+     $output .= q(<span><strong>Done:</strong> ).html_escape($status{done}).q(;</span> )
+  }
+  $output;
+}
+{ my $output = '';
+  if (@{$status{blockedby_array}}) {
+     $output .= q(<span>Fix blocked by ).
+        join(q(, ),
+         map {bug_links(bug=>$_->{bug_num}).q(: ).html_escape($_->{subject})}
+         @{$status{blockedby_array}}).q(;</span> )
+  }
+  if (@{$status{blocks_array}}) {
+     $output .= q(<span>Blocking fix for ).
+        join(q(, ),
+         map {bug_links(bug=>$_->{bug_num}).q(: ).html_escape($_->{subject})}
+         @{$status{blocks_array}}).q(;</span> )
+  }
+  $output;
+}{ my $output = '';
+ my ($days_last,$eng_last) = secs_to_english(time - $status{log_modified});
+ my ($days,$eng) = secs_to_english(time - $status{date});
+
+ if ($days >= 7) {
+   my $font = "";
+   my $efont = "";
+   $font = "em" if ($days > 30);
+   $font = "strong" if ($days > 60);
+   $efont = "</$font>" if ($font);
+   $font = "<$font>" if ($font);
+
+   $output .= "<span>${font}Filed $eng ago$efont;</span>\n";
+ }
+ if ($days_last > 7) {
+   my $font = "";
+   my $efont = "";
+   $font = "em" if ($days_last > 30);
+   $font = "strong" if ($days_last > 60);
+   $efont = "</$font>" if ($font);
+   $font = "<$font>" if ($font);
+
+   $output .= "<span>${font}Modified $eng_last ago$efont;</span>\n";
+ }
+ $output;
+ }{ my $output = '';
+  if (exists $status{archived} and $status{archived}) {
+     $output .= q(<span>Bug is archived. No further changes may be made.<span> )
+  }
+  $output}</div>
+</div>
diff --git a/templates/en_US/html/html_tail.tmpl b/templates/en_US/html/html_tail.tmpl
new file mode 100644 (file)
index 0000000..d7eb979
--- /dev/null
@@ -0,0 +1,11 @@
+<ADDRESS>{$config{maintainer}} &lt;<A HREF="mailto:{$config{maintainer_email}}">{$config{maintainer_email}}</A>&gt;.
+Last modified:
+<!--timestamp-->
+{$last_modified||strftime('%c',gmtime)}
+<!--timestamp-->
+<P>
+<A HREF="http://{$config{web_domain}}/">{$config{project}} {$config{bug}} tracking system</A><BR>
+Copyright (C) 1999 Darren O. Benham,
+1997,2003 nCipher Corporation Ltd,
+1994-97 Ian Jackson.
+</ADDRESS>
diff --git a/templates/en_US/html/post_title.tmpl b/templates/en_US/html/post_title.tmpl
new file mode 100644 (file)
index 0000000..169d161
--- /dev/null
@@ -0,0 +1,3 @@
+</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+<link rel="stylesheet" href="{$config{web_host_bug_dir}}/css/bugs.css" type="text/css">
diff --git a/templates/en_US/html/pre_title.tmpl b/templates/en_US/html/pre_title.tmpl
new file mode 100644 (file)
index 0000000..2f7ab77
--- /dev/null
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html><head>
+<title>
\ No newline at end of file