summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Attachment/PatchReader.pm15
-rw-r--r--Bugzilla/CGI.pm36
-rw-r--r--Bugzilla/Config/General.pm14
-rw-r--r--Bugzilla/Constants.pm4
-rw-r--r--Bugzilla/DaemonControl.pm26
-rw-r--r--Bugzilla/Elastic/Role/HasClient.pm2
-rw-r--r--Bugzilla/Error.pm4
-rw-r--r--Bugzilla/Group.pm29
-rw-r--r--Bugzilla/Install.pm40
-rw-r--r--Bugzilla/Install/DB.pm11
-rw-r--r--Bugzilla/Install/Filesystem.pm143
-rw-r--r--Bugzilla/Memcached.pm2
-rw-r--r--Bugzilla/ModPerl.pm118
-rw-r--r--Bugzilla/ModPerl/BasicAuth.pm65
-rw-r--r--Bugzilla/ModPerl/BlockIP.pm65
-rw-r--r--Bugzilla/ModPerl/StartupFix.pm51
-rw-r--r--Bugzilla/PSGI.pm42
-rw-r--r--Bugzilla/Quantum.pm119
-rw-r--r--Bugzilla/Quantum/CGI.pm161
-rw-r--r--Bugzilla/Quantum/Plugin/BasicAuth.pm40
-rw-r--r--Bugzilla/Quantum/Plugin/BlockIP.pm43
-rw-r--r--Bugzilla/Quantum/Plugin/Glue.pm101
-rw-r--r--Bugzilla/Quantum/Plugin/Hostage.pm (renamed from Bugzilla/ModPerl/Hostage.pm)53
-rw-r--r--Bugzilla/Quantum/SES.pm254
-rw-r--r--Bugzilla/Quantum/Static.pm30
-rw-r--r--Bugzilla/Quantum/Stdout.pm60
-rw-r--r--Bugzilla/Search.pm26
-rw-r--r--Bugzilla/Template.pm3
-rw-r--r--Bugzilla/Util.pm3
-rw-r--r--Bugzilla/WebService/Server/XMLRPC.pm6
-rw-r--r--Bugzilla/WebService/Util.pm8
31 files changed, 1028 insertions, 546 deletions
diff --git a/Bugzilla/Attachment/PatchReader.pm b/Bugzilla/Attachment/PatchReader.pm
index 2cfbf2c6b..8025f5b82 100644
--- a/Bugzilla/Attachment/PatchReader.pm
+++ b/Bugzilla/Attachment/PatchReader.pm
@@ -116,18 +116,9 @@ sub process_interdiff {
$ENV{'PATH'} = $lc->{diffpath};
my ($pid, $interdiff_stdout, $interdiff_stderr);
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- require Apache2::SubProcess;
- my $request = Apache2::RequestUtil->request;
- (undef, $interdiff_stdout, $interdiff_stderr) = $request->spawn_proc_prog(
- $lc->{interdiffbin}, [$old_filename, $new_filename]
- );
- } else {
- $interdiff_stderr = gensym;
- my $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr,
- $lc->{interdiffbin}, $old_filename, $new_filename);
- }
+ $interdiff_stderr = gensym;
+ $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr,
+ $lc->{interdiffbin}, $old_filename, $new_filename);
binmode $interdiff_stdout;
# Check for errors
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index 6236b015a..2cbb02e3e 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -123,7 +123,7 @@ sub new {
# Under mod_perl, CGI's global variables get reset on each request,
# so we need to set them up again every time.
- $class->_init_bz_cgi_globals() if $ENV{MOD_PERL};
+ $class->_init_bz_cgi_globals();
my $self = $class->SUPER::new(@args);
@@ -141,6 +141,7 @@ sub new {
# apache collapses // to / in $ENV{PATH_INFO} but not in $self->path_info.
# url() requires the full path in ENV in order to generate the correct url.
$ENV{PATH_INFO} = $path;
+ DEBUG("redirecting because we see PATH_INFO and don't like it");
print $self->redirect($self->url(-path => 0, -query => 1));
exit;
}
@@ -151,6 +152,7 @@ sub new {
# Redirect to urlbase if we are not viewing an attachment.
if ($self->url_is_attachment_base and $script ne 'attachment.cgi') {
+ DEBUG("Redirecting to urlbase because the url is in the attachment base and not attachment.cgi");
$self->redirect_to_urlbase();
}
@@ -481,11 +483,6 @@ sub _prevent_unsafe_response {
print $self->SUPER::header(-type => 'text/html', -status => '403 Forbidden');
if ($content_type ne 'text/html') {
print "Untrusted Referer Header\n";
- if ($ENV{MOD_PERL}) {
- my $r = $self->r;
- $r->rflush;
- $r->status(200);
- }
}
exit;
}
@@ -603,8 +600,25 @@ sub header {
$headers{'-link'} .= ', <https://www.google-analytics.com>; rel="preconnect"; crossorigin';
}
}
-
- return $self->SUPER::header(%headers) || "";
+ my $headers = $self->SUPER::header(%headers) || '';
+ if ($self->server_software eq 'Bugzilla::Quantum::CGI') {
+ my $c = $Bugzilla::Quantum::CGI::C;
+ $c->res->headers->parse($headers);
+ my $status = $c->res->headers->status;
+ if ($status && $status =~ /^([0-9]+)/) {
+ $c->res->code($1);
+ }
+ elsif ($c->res->headers->location) {
+ $c->res->code(302);
+ }
+ else {
+ $c->res->code(200);
+ }
+ return '';
+ }
+ else {
+ LOGDIE("Bugzilla::CGI->header() should only be called from inside Bugzilla::Quantum::CGI!");
+ }
}
sub param {
@@ -721,6 +735,7 @@ sub redirect {
return $self->SUPER::redirect(@_);
}
+use Bugzilla::Logging;
# This helps implement Bugzilla::Search::Recent, and also shortens search
# URLs that get POSTed to buglist.cgi.
sub redirect_search_url {
@@ -769,6 +784,7 @@ sub redirect_search_url {
# are only redirected if they're under the CGI_URI_LIMIT though.
my $self_url = $self->self_url();
if ($self->request_method() ne 'POST' or length($self_url) < CGI_URI_LIMIT) {
+ DEBUG("Redirecting search url");
print $self->redirect(-url => $self_url);
exit;
}
@@ -790,10 +806,8 @@ sub redirect_to_https {
# XML-RPC clients (SOAP::Lite at least) require a 301 to redirect properly
# and do not work with 302. Our redirect really is permanent anyhow, so
# it doesn't hurt to make it a 301.
+ DEBUG("Redirecting to https");
print $self->redirect(-location => $url, -status => 301);
-
- # When using XML-RPC with mod_perl, we need the headers sent immediately.
- $self->r->rflush if $ENV{MOD_PERL};
exit;
}
diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm
index 15688dfd3..fa7cf2d08 100644
--- a/Bugzilla/Config/General.pm
+++ b/Bugzilla/Config/General.pm
@@ -25,6 +25,14 @@ use constant get_param_list => (
},
{
+ name => 'nobody_user',
+ type => 't',
+ no_reset => '1',
+ default => 'nobody@mozilla.org',
+ checker => \&check_email
+ },
+
+ {
name => 'docs_urlbase',
type => 't',
default => 'docs/%lang%/html/',
@@ -40,12 +48,6 @@ use constant get_param_list => (
},
{
- name => 'shutdownhtml',
- type => 'l',
- default => ''
- },
-
- {
name => 'announcehtml',
type => 'l',
default => ''
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 185a30390..34e4a4cfe 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -194,6 +194,8 @@ use Memoize;
EMAIL_LIMIT_EXCEPTION
JOB_QUEUE_VIEW_MAX_JOBS
+
+ BZ_PERSISTENT
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -696,6 +698,8 @@ sub _bz_locations {
};
}
+use constant BZ_PERSISTENT => $main::BUGZILLA_PERSISTENT;
+
# This makes us not re-compute all the bz_locations data every time it's
# called.
BEGIN { memoize('_bz_locations') };
diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm
index 6ff883af0..5cb32973f 100644
--- a/Bugzilla/DaemonControl.pm
+++ b/Bugzilla/DaemonControl.pm
@@ -23,7 +23,8 @@ use IO::Async::Protocol::LineStream;
use IO::Async::Signal;
use IO::Socket;
use LWP::Simple qw(get);
-use POSIX qw(setsid WEXITSTATUS);
+use JSON::MaybeXS qw(encode_json);
+use POSIX qw(WEXITSTATUS);
use base qw(Exporter);
@@ -43,8 +44,14 @@ our %EXPORT_TAGS = (
my $BUGZILLA_DIR = bz_locations->{cgi_path};
my $JOBQUEUE_BIN = catfile( $BUGZILLA_DIR, 'jobqueue.pl' );
my $CEREAL_BIN = catfile( $BUGZILLA_DIR, 'scripts', 'cereal.pl' );
-my $HTTPD_BIN = '/usr/sbin/httpd';
-my $HTTPD_CONFIG = catfile( bz_locations->{confdir}, 'httpd.conf' );
+my $BUGZILLA_BIN = catfile( $BUGZILLA_DIR, 'bugzilla.pl' );
+my $HYPNOTOAD_BIN = catfile( $BUGZILLA_DIR, 'local', 'bin', 'hypnotoad' );
+my @PERL5LIB = ( $BUGZILLA_DIR, catdir($BUGZILLA_DIR, 'lib'), catdir($BUGZILLA_DIR, 'local', 'lib', 'perl5') );
+
+my %HTTP_BACKENDS = (
+ hypnotoad => [ $HYPNOTOAD_BIN, $BUGZILLA_BIN, '-f' ],
+ simple => [ $BUGZILLA_BIN, 'daemon' ],
+);
sub catch_signal {
my ($name, @done) = @_;
@@ -98,13 +105,12 @@ sub run_httpd {
my $exit_f = $loop->new_future;
my $httpd = IO::Async::Process->new(
code => sub {
-
- # we have to setsid() to make a new process group
- # or else apache will kill its parent.
- setsid();
- my @command = ( $HTTPD_BIN, '-DFOREGROUND', '-f' => $HTTPD_CONFIG, @args );
- exec @command
- or die "failed to exec $command[0] $!";
+ $ENV{BUGZILLA_HTTPD_ARGS} = encode_json(\@args);
+ $ENV{PERL5LIB} = join(':', @PERL5LIB);
+ my $backend = $ENV{HTTP_BACKEND} // 'hypnotoad';
+ my $command = $HTTP_BACKENDS{ $backend };
+ exec @$command
+ or die "failed to exec $command->[0] $!";
},
on_finish => on_finish($exit_f),
on_exception => on_exception( 'httpd', $exit_f ),
diff --git a/Bugzilla/Elastic/Role/HasClient.pm b/Bugzilla/Elastic/Role/HasClient.pm
index 8e2687880..a971392e0 100644
--- a/Bugzilla/Elastic/Role/HasClient.pm
+++ b/Bugzilla/Elastic/Role/HasClient.pm
@@ -8,7 +8,6 @@ package Bugzilla::Elastic::Role::HasClient;
use 5.10.1;
use Moo::Role;
-use Search::Elasticsearch;
has 'client' => (is => 'lazy');
@@ -16,6 +15,7 @@ has 'client' => (is => 'lazy');
sub _build_client {
my ($self) = @_;
+ require Search::Elasticsearch;
return Search::Elasticsearch->new(
nodes => [ split(/\s+/, Bugzilla->params->{elasticsearch_nodes}) ],
cxn_pool => 'Sniff',
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
index 9fcd16386..f932294b0 100644
--- a/Bugzilla/Error.pm
+++ b/Bugzilla/Error.pm
@@ -31,7 +31,7 @@ use Scalar::Util qw(blessed);
sub _in_eval {
my $in_eval = 0;
for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
- last if $sub =~ /^ModPerl/;
+ last if $sub =~ /^Bugzilla::Quantum::CGI::try/;
$in_eval = 1 if $sub =~ /^\(eval\)/;
}
return $in_eval;
@@ -196,7 +196,7 @@ sub ThrowTemplateError {
# mod_perl overrides exit to call die with this string
# we never want to display this to the user
- exit if $template_err =~ /\bModPerl::Util::exit\b/;
+ die $template_err if ref($template_err) eq 'ARRAY' && $template_err->[0] eq "EXIT\n";
state $logger = Log::Log4perl->get_logger('Bugzilla.Error.Template');
$logger->error($template_err);
diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm
index c941482f0..7f684ea15 100644
--- a/Bugzilla/Group.pm
+++ b/Bugzilla/Group.pm
@@ -26,17 +26,24 @@ use Scalar::Util qw(blessed);
use constant IS_CONFIG => 1;
-use constant DB_COLUMNS => qw(
- groups.id
- groups.name
- groups.description
- groups.isbuggroup
- groups.userregexp
- groups.isactive
- groups.icon_url
- groups.owner_user_id
- groups.idle_member_removal
-);
+sub DB_COLUMNS {
+ my $class = shift;
+ my @columns = qw(
+ id
+ name
+ description
+ isbuggroup
+ userregexp
+ isactive
+ icon_url
+ owner_user_id
+ idle_member_removal
+ );
+ my $dbh = Bugzilla->dbh;
+ my $table = $class->DB_TABLE;
+
+ return map { "$table.$_" } grep { $dbh->bz_column_info($table, $_) } @columns;
+}
use constant DB_TABLE => 'groups';
diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm
index 8bce9b5e7..c9935f34d 100644
--- a/Bugzilla/Install.pm
+++ b/Bugzilla/Install.pm
@@ -31,21 +31,31 @@ use Bugzilla::Util qw(get_text);
use Bugzilla::Version;
use constant STATUS_WORKFLOW => (
- [undef, 'UNCONFIRMED'],
- [undef, 'CONFIRMED'],
- [undef, 'IN_PROGRESS'],
- ['UNCONFIRMED', 'CONFIRMED'],
- ['UNCONFIRMED', 'IN_PROGRESS'],
- ['UNCONFIRMED', 'RESOLVED'],
- ['CONFIRMED', 'IN_PROGRESS'],
- ['CONFIRMED', 'RESOLVED'],
- ['IN_PROGRESS', 'CONFIRMED'],
- ['IN_PROGRESS', 'RESOLVED'],
- ['RESOLVED', 'UNCONFIRMED'],
- ['RESOLVED', 'CONFIRMED'],
- ['RESOLVED', 'VERIFIED'],
- ['VERIFIED', 'UNCONFIRMED'],
- ['VERIFIED', 'CONFIRMED'],
+ [ undef, 'UNCONFIRMED' ],
+ [ undef, 'NEW' ],
+ [ undef, 'ASSIGNED' ],
+ [ 'UNCONFIRMED', 'NEW' ],
+ [ 'UNCONFIRMED', 'ASSIGNED' ],
+ [ 'UNCONFIRMED', 'RESOLVED' ],
+ [ 'NEW', 'UNCONFIRMED' ],
+ [ 'NEW', 'ASSIGNED' ],
+ [ 'NEW', 'RESOLVED' ],
+ [ 'ASSIGNED', 'UNCONFIRMED' ],
+ [ 'ASSIGNED', 'NEW' ],
+ [ 'ASSIGNED', 'RESOLVED' ],
+ [ 'REOPENED', 'UNCONFIRMED' ],
+ [ 'REOPENED', 'NEW' ],
+ [ 'REOPENED', 'ASSIGNED' ],
+ [ 'REOPENED', 'RESOLVED' ],
+ [ 'RESOLVED', 'UNCONFIRMED' ],
+ [ 'RESOLVED', 'REOPENED' ],
+ [ 'RESOLVED', 'VERIFIED' ],
+ [ 'VERIFIED', 'UNCONFIRMED' ],
+ [ 'VERIFIED', 'REOPENED' ],
+ [ 'VERIFIED', 'RESOLVED' ],
+ [ 'CLOSED', 'UNCONFIRMED' ],
+ [ 'CLOSED', 'REOPENED' ],
+ [ 'CLOSED', 'RESOLVED' ],
);
sub SETTINGS {
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 2e5ae5ff2..8b3d4b8cc 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -3908,7 +3908,16 @@ sub _migrate_group_owners {
my $dbh = Bugzilla->dbh;
return if $dbh->bz_column_info('groups', 'owner_user_id');
$dbh->bz_add_column('groups', 'owner_user_id', {TYPE => 'INT3'});
- my $nobody = Bugzilla::User->check('nobody@mozilla.org');
+ my $nobody = Bugzilla::User->new({ name => Bugzilla->params->{'nobody_user'}, cache => 1 });
+ unless ($nobody) {
+ $nobody = Bugzilla::User->create(
+ {
+ login_name => Bugzilla->params->{'nobody_user'},
+ realname => 'Nobody (ok to assign bugs to)',
+ cryptpassword => '*',
+ }
+ );
+ }
$dbh->do('UPDATE groups SET owner_user_id = ?', undef, $nobody->id);
}
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index 003be22e4..cb1b1ad15 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -41,56 +41,11 @@ use English qw(-no_match_vars $OSNAME);
use base qw(Exporter);
our @EXPORT = qw(
update_filesystem
- create_htaccess
fix_all_file_permissions
fix_dir_permissions
fix_file_permissions
);
-use constant HT_DEFAULT_DENY => <<'EOT';
-# nothing in this directory is retrievable unless overridden by an .htaccess
-# in a subdirectory
-deny from all
-EOT
-
-use constant HT_GRAPHS_DIR => <<'EOT';
-# Allow access to .png and .gif files.
-<FilesMatch (\.gif|\.png)$>
- Allow from all
-</FilesMatch>
-
-# And no directory listings, either.
-Deny from all
-EOT
-
-use constant HT_WEBDOT_DIR => <<'EOT';
-# Restrict access to .dot files to the public webdot server at research.att.com
-# if research.att.com ever changes their IP, or if you use a different
-# webdot server, you'll need to edit this
-<FilesMatch \.dot$>
- Allow from 192.20.225.0/24
- Deny from all
-</FilesMatch>
-
-# Allow access to .png files created by a local copy of 'dot'
-<FilesMatch \.png\$>
- Allow from all
-</FilesMatch>
-
-# And no directory listings, either.
-Deny from all
-EOT
-
-use constant HT_ASSETS_DIR => <<'EOT';
-# Allow access to .css and js files
-<FilesMatch \.(css|js)$>
- Allow from all
-</FilesMatch>
-
-# And no directory listings, either.
-Deny from all
-EOT
-
use constant INDEX_HTML => <<'EOT';
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
@@ -112,11 +67,6 @@ use constant HTTPD_ENV => qw(
NYTPROF_DIR
);
-sub HTTPD_ENV_CONF {
- my @env = (ENV_KEYS, HTTPD_ENV);
- return join( "\n", map { "PerlPassEnv " . $_ } @env ) . "\n";
-}
-
###############
# Permissions #
###############
@@ -214,6 +164,7 @@ sub FILESYSTEM {
# users to be able to cron them or otherwise run
# them as a secure user, like the webserver owner.
'*.cgi' => { perms => WS_EXECUTE },
+ '*.psgi' => { perms => CGI_READ },
'whineatnews.pl' => { perms => WS_EXECUTE },
'collectstats.pl' => { perms => WS_EXECUTE },
'importxml.pl' => { perms => WS_EXECUTE },
@@ -230,13 +181,13 @@ sub FILESYSTEM {
'jobqueue-worker.pl' => { perms => OWNER_EXECUTE },
'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE },
+ 'bugzilla.pl' => { perms => OWNER_EXECUTE },
'Bugzilla.pm' => { perms => CGI_READ },
"$localconfig*" => { perms => CGI_READ },
'META.*' => { perms => CGI_READ },
'MYMETA.*' => { perms => CGI_READ },
'bugzilla.dtd' => { perms => WS_SERVE },
'mod_perl.pl' => { perms => WS_SERVE },
- '.htaccess' => { perms => WS_SERVE },
'cvs-update.log' => { perms => WS_SERVE },
'scripts/sendunsentbugmail.pl' => { perms => WS_EXECUTE },
'docs/bugzilla.ent' => { perms => OWNER_WRITE },
@@ -345,7 +296,7 @@ sub FILESYSTEM {
'contrib' => { files => OWNER_EXECUTE,
dirs => DIR_OWNER_WRITE, },
'scripts' => { files => OWNER_EXECUTE,
- dirs => DIR_OWNER_WRITE, },
+ dirs => DIR_WS_SERVE, },
);
# --- FILES TO CREATE --- #
@@ -427,65 +378,26 @@ sub FILESYSTEM {
"skins/yui3.css" => { perms => CGI_READ,
overwrite => 1,
contents => $yui3_all_css },
- "$confdir/env.conf" => { perms => CGI_READ,
- overwrite => 1,
- contents => \&HTTPD_ENV_CONF },
);
+ # Create static error pages.
+ $create_dirs{"errors"} = DIR_CGI_READ;
+
# Because checksetup controls the creation of index.html separately
# from all other files, it gets its very own hash.
my %index_html = (
'index.html' => { perms => WS_SERVE, contents => INDEX_HTML }
);
- # Because checksetup controls the .htaccess creation separately
- # by a localconfig variable, these go in a separate variable from
- # %create_files.
- #
- # Note that these get WS_SERVE as their permission
- # because they're *read* by the webserver, even though they're not
- # actually, themselves, served.
- my %htaccess = (
- "$attachdir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$libdir/Bugzilla/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$extlib/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$templatedir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 'contrib/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 'scripts/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 't/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 'xt/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- '.circleci/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$confdir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$datadir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$graphsdir/.htaccess" => { perms => WS_SERVE,
- contents => HT_GRAPHS_DIR },
- "$webdotdir/.htaccess" => { perms => WS_SERVE,
- contents => HT_WEBDOT_DIR },
- "$assetsdir/.htaccess" => { perms => WS_SERVE,
- contents => HT_ASSETS_DIR },
- );
-
Bugzilla::Hook::process('install_filesystem', {
files => \%files,
create_dirs => \%create_dirs,
non_recurse_dirs => \%non_recurse_dirs,
recurse_dirs => \%recurse_dirs,
create_files => \%create_files,
- htaccess => \%htaccess,
});
- my %all_files = (%create_files, %htaccess, %index_html, %files);
+ my %all_files = (%create_files, %index_html, %files);
my %all_dirs = (%create_dirs, %non_recurse_dirs);
return {
@@ -494,7 +406,6 @@ sub FILESYSTEM {
all_dirs => \%all_dirs,
create_files => \%create_files,
- htaccess => \%htaccess,
index_html => \%index_html,
all_files => \%all_files,
};
@@ -542,13 +453,6 @@ sub update_filesystem {
_rename_file($oldparamsfile, "$datadir/$oldparamsfile");
}
- # Remove old assets htaccess file to force recreation with correct values.
- if (-e "$assetsdir/.htaccess") {
- if (read_file("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
- unlink("$assetsdir/.htaccess");
- }
- }
-
_create_files(%files);
if ($params->{index_html}) {
_create_files(%{$fs->{index_html}});
@@ -564,7 +468,7 @@ sub update_filesystem {
# Delete old files that no longer need to exist
# 2001-04-29 jake@bugzilla.org - Remove oldemailtech
- # http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
+ # http://bugzilla.mozilla.org/show_bug.cgi?id=71552
if (-d 'shadow') {
print "Removing shadow directory...\n";
rmtree("shadow");
@@ -653,27 +557,6 @@ sub _convert_single_file_skins {
}
}
-sub create_htaccess {
- _create_files(%{FILESYSTEM()->{htaccess}});
-
- # Repair old .htaccess files
-
- my $webdot_dir = bz_locations()->{'webdotdir'};
- # The public webdot IP address changed.
- my $webdot = new IO::File("$webdot_dir/.htaccess", 'r')
- || die "$webdot_dir/.htaccess: $!";
- my $webdot_data;
- { local $/; $webdot_data = <$webdot>; }
- $webdot->close;
- if ($webdot_data =~ /192\.20\.225\.10/) {
- print "Repairing $webdot_dir/.htaccess...\n";
- $webdot_data =~ s/192\.20\.225\.10/192.20.225.0\/24/g;
- $webdot = new IO::File("$webdot_dir/.htaccess", 'w') || die $!;
- print $webdot $webdot_data;
- $webdot->close;
- }
-}
-
sub _rename_file {
my ($from, $to) = @_;
print install_string('file_rename', { from => $from, to => $to }), "\n";
@@ -984,16 +867,6 @@ Params: C<index_html> - Whether or not we should create
Returns: nothing
-=item C<create_htaccess()>
-
-Description: Creates all of the .htaccess files for Apache,
- in the various Bugzilla directories. Also updates
- the .htaccess files if they need updating.
-
-Params: none
-
-Returns: nothing
-
=item C<fix_all_file_permissions($output)>
Description: Sets all the file permissions on all of Bugzilla's files
diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm
index 40755aa29..6bbef080a 100644
--- a/Bugzilla/Memcached.pm
+++ b/Bugzilla/Memcached.pm
@@ -25,6 +25,8 @@ use Sys::Syslog qw(:DEFAULT);
use constant MAX_KEY_LENGTH => 250;
use constant RATE_LIMIT_PREFIX => "rate:";
+*new = \&_new;
+
sub _new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
diff --git a/Bugzilla/ModPerl.pm b/Bugzilla/ModPerl.pm
deleted file mode 100644
index 19cd1128f..000000000
--- a/Bugzilla/ModPerl.pm
+++ /dev/null
@@ -1,118 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-package Bugzilla::ModPerl;
-
-use 5.10.1;
-use strict;
-use warnings;
-
-use File::Find ();
-use Cwd ();
-use Carp ();
-
-# We don't need (or want) to use Bugzilla's template subclass.
-# it is easier to reason with the code without all the extra things Bugzilla::Template adds
-# (and there might be side-effects, since this code is loaded very early in the httpd startup)
-use Template ();
-
-use Bugzilla::ModPerl::BlockIP;
-use Bugzilla::ModPerl::Hostage;
-
-sub apache_config {
- my ($class, $cgi_path) = @_;
-
- Carp::croak "\$cgi_path is required" unless $cgi_path;
-
- my %htaccess;
- $cgi_path = Cwd::realpath($cgi_path);
- my $wanted = sub {
- package File::Find;
- our ($name, $dir);
-
- if ($name =~ m#/\.htaccess$#) {
- open my $fh, '<', $name or die "cannot open $name $!";
- my $contents = do {
- local $/ = undef;
- <$fh>;
- };
- close $fh;
- $htaccess{$dir} = { file => $name, contents => $contents, dir => $dir };
- }
- };
-
- File::Find::find( { wanted => $wanted, no_chdir => 1 }, $cgi_path );
- my $template = Template->new;
- my $conf;
- my %vars = (
- root_htaccess => delete $htaccess{$cgi_path},
- htaccess_files => [ map { $htaccess{$_} } sort { length $a <=> length $b } keys %htaccess ],
- cgi_path => $cgi_path,
- );
- $template->process(\*DATA, \%vars, \$conf);
- my $apache_version = Apache2::ServerUtil::get_server_version();
- if ($apache_version =~ m!Apache/(\d+)\.(\d+)\.(\d+)!) {
- my ($major, $minor, $patch) = ($1, $2, $3);
- if ($major > 2 || $major == 2 && $minor >= 4) {
- $conf =~ s{^\s+deny\s+from\s+all.*$}{Require all denied}gmi;
- $conf =~ s{^\s+allow\s+from\s+all.*$}{Require all granted}gmi;
- $conf =~ s{^\s+allow\s+from\s+(\S+).*$}{Require host $1}gmi;
- }
- }
-
- return $conf;
-}
-
-1;
-
-__DATA__
-# Make sure each httpd child receives a different random seed (bug 476622).
-# Bugzilla::RNG has one srand that needs to be called for
-# every process, and Perl has another. (Various Perl modules still use
-# the built-in rand(), even though we never use it in Bugzilla itself,
-# so we need to srand() both of them.)
-PerlChildInitHandler "sub { Bugzilla::RNG::srand(); srand(); eval { Bugzilla->dbh->ping } }"
-PerlInitHandler Bugzilla::ModPerl::Hostage
-PerlAccessHandler Bugzilla::ModPerl::BlockIP
-
-# It is important to specify ErrorDocuments outside of all directories.
-# These used to be in .htaccess, but then things like "AllowEncodedSlashes no"
-# mean that urls containing %2f are unstyled.
-ErrorDocument 401 /errors/401.html
-ErrorDocument 403 /errors/403.html
-ErrorDocument 404 /errors/404.html
-ErrorDocument 500 /errors/500.html
-
-<Directory "[% cgi_path %]">
- AddHandler perl-script .cgi
- # No need to PerlModule these because they're already defined in mod_perl.pl
- PerlResponseHandler Bugzilla::ModPerl::ResponseHandler
- PerlCleanupHandler Bugzilla::ModPerl::CleanupHandler Apache2::SizeLimit
- PerlOptions +ParseHeaders
- Options +ExecCGI +FollowSymLinks
- DirectoryIndex index.cgi index.html
- AllowOverride none
- # from [% root_htaccess.file %]
- [% root_htaccess.contents FILTER indent %]
-</Directory>
-
-# AWS SES endpoint for handling mail bounces/complaints
-<Location "/ses">
- PerlSetEnv AUTH_VAR_NAME ses_username
- PerlSetEnv AUTH_VAR_PASS ses_password
- PerlAuthenHandler Bugzilla::ModPerl::BasicAuth
- AuthName SES
- AuthType Basic
- require valid-user
-</Location>
-
-# directory rules for all the other places we have .htaccess files
-[% FOREACH htaccess IN htaccess_files %]
-# from [% htaccess.file %]
-<Directory "[% htaccess.dir %]">
- [% htaccess.contents FILTER indent %]
-</Directory>
-[% END %]
diff --git a/Bugzilla/ModPerl/BasicAuth.pm b/Bugzilla/ModPerl/BasicAuth.pm
deleted file mode 100644
index 7248a19f3..000000000
--- a/Bugzilla/ModPerl/BasicAuth.pm
+++ /dev/null
@@ -1,65 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-package Bugzilla::ModPerl::BasicAuth;
-use 5.10.1;
-use strict;
-use warnings;
-
-# Protects a mod_perl <Location> with Basic HTTP authentication.
-#
-# Example use:
-#
-# <Location "/ses">
-# PerlAuthenHandler Bugzilla::ModPerl::BasicAuth
-# PerlSetEnv AUTH_VAR_NAME ses_username
-# PerlSetEnv AUTH_VAR_PASS ses_password
-# AuthName SES
-# AuthType Basic
-# require valid-user
-# </Location>
-#
-# AUTH_VAR_NAME and AUTH_VAR_PASS are the names of variables defined in
-# `localconfig` which hold the authentication credentials.
-
-use Apache2::Const -compile => qw(OK HTTP_UNAUTHORIZED); ## no critic (Freenode::ModPerl)
-use Bugzilla::Logging;
-use Bugzilla ();
-
-sub handler {
- my $r = shift;
- my ($status, $password) = $r->get_basic_auth_pw;
- if ($status != Apache2::Const::OK) {
- WARN("Got non-OK status: $status when trying to get password");
- return $status
- }
-
- my $auth_var_name = $ENV{AUTH_VAR_NAME};
- my $auth_var_pass = $ENV{AUTH_VAR_PASS};
- unless ($auth_var_name && $auth_var_pass) {
- ERROR('AUTH_VAR_NAME and AUTH_VAR_PASS environmental vars not set');
- $r->note_basic_auth_failure;
- return Apache2::Const::HTTP_UNAUTHORIZED;
- }
-
- my $auth_user = Bugzilla->localconfig->{$auth_var_name};
- my $auth_pass = Bugzilla->localconfig->{$auth_var_pass};
- unless ($auth_user && $auth_pass) {
- ERROR("$auth_var_name and $auth_var_pass not configured");
- $r->note_basic_auth_failure;
- return Apache2::Const::HTTP_UNAUTHORIZED;
- }
-
- unless ($r->user eq $auth_user && $password eq $auth_pass) {
- $r->note_basic_auth_failure;
- WARN('username and password do not match');
- return Apache2::Const::HTTP_UNAUTHORIZED;
- }
-
- return Apache2::Const::OK;
-}
-
-1;
diff --git a/Bugzilla/ModPerl/BlockIP.pm b/Bugzilla/ModPerl/BlockIP.pm
deleted file mode 100644
index 4e9a4be5c..000000000
--- a/Bugzilla/ModPerl/BlockIP.pm
+++ /dev/null
@@ -1,65 +0,0 @@
-package Bugzilla::ModPerl::BlockIP;
-use 5.10.1;
-use strict;
-use warnings;
-
-use Apache2::RequestRec ();
-use Apache2::Connection ();
-
-use Apache2::Const -compile => qw(OK);
-use Cache::Memcached::Fast;
-
-use constant BLOCK_TIMEOUT => 60*60;
-
-my $MEMCACHED = Bugzilla::Memcached->_new()->{memcached};
-my $STATIC_URI = qr{
- ^/
- (?: extensions/[^/]+/web
- | robots\.txt
- | __heartbeat__
- | __lbheartbeat__
- | __version__
- | images
- | skins
- | js
- | errors
- )
-}xms;
-
-sub block_ip {
- my ($class, $ip) = @_;
- $MEMCACHED->set("block_ip:$ip" => 1, BLOCK_TIMEOUT) if $MEMCACHED;
-}
-
-sub unblock_ip {
- my ($class, $ip) = @_;
- $MEMCACHED->delete("block_ip:$ip") if $MEMCACHED;
-}
-
-sub handler {
- my $r = shift;
- return Apache2::Const::OK if $r->uri =~ $STATIC_URI;
-
- my $ip = $r->headers_in->{'X-Forwarded-For'};
- if ($ip) {
- $ip = (split(/\s*,\s*/ms, $ip))[-1];
- }
- else {
- $ip = $r->connection->remote_ip;
- }
-
- if ($MEMCACHED && $MEMCACHED->get("block_ip:$ip")) {
- __PACKAGE__->block_ip($ip);
- $r->status_line("429 Too Many Requests");
- # 500 is used here because apache 2.2 doesn't understand 429.
- # the above line and the return value together mean we produce 429.
- # Any other variation doesn't work.
- $r->custom_response(500, "Too Many Requests");
- return 429;
- }
- else {
- return Apache2::Const::OK;
- }
-}
-
-1;
diff --git a/Bugzilla/ModPerl/StartupFix.pm b/Bugzilla/ModPerl/StartupFix.pm
deleted file mode 100644
index bcc467e9f..000000000
--- a/Bugzilla/ModPerl/StartupFix.pm
+++ /dev/null
@@ -1,51 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-package Bugzilla::ModPerl::StartupFix;
-use 5.10.1;
-use strict;
-use warnings;
-
-use Filter::Util::Call;
-use Apache2::ServerUtil ();
-
-# This module is a source filter that removes every subsequent line
-# if this is the first time apache has started,
-# as reported by Apache2::ServerUtil::restart_count(), which is 1
-# on the first start.
-
-my $FIRST_STARTUP = <<'CODE';
-warn "Bugzilla::ModPerl::StartupFix: Skipping first startup using source filter\n";
-1;
-CODE
-
-sub import {
- my ($class) = @_;
- my ($ref) = {};
- filter_add( bless $ref, $class );
-}
-
-# this will be called for each line.
-# For the first line replaced, we insert $FIRST_STARTUP.
-# Every subsequent line is replaced with an empty string.
-sub filter {
- my ($self) = @_;
- my ($status);
- if ($status = filter_read() > 0) {
- if (Apache2::ServerUtil::restart_count() < 2) {
- if (!$self->{did_it}) {
- $self->{did_it} = 1;
- $_ = $FIRST_STARTUP;
- }
- else {
- $_ = "";
- }
- }
- }
- return $status;
-}
-
-1; \ No newline at end of file
diff --git a/Bugzilla/PSGI.pm b/Bugzilla/PSGI.pm
new file mode 100644
index 000000000..46352b319
--- /dev/null
+++ b/Bugzilla/PSGI.pm
@@ -0,0 +1,42 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::PSGI;
+use 5.10.1;
+use strict;
+use warnings;
+
+use base qw(Exporter);
+
+use Bugzilla::Logging;
+our @EXPORT_OK = qw(compile_cgi);
+
+sub compile_cgi {
+ my ($script) = @_;
+ require CGI::Compile;
+ require CGI::Emulate::PSGI;
+
+ my $cgi = CGI::Compile->compile($script);
+ my $app = CGI::Emulate::PSGI->handler(
+ sub {
+ Bugzilla::init_page();
+ $cgi->();
+ }
+ );
+ return sub {
+ my $env = shift;
+ if ($env->{'psgix.cleanup'}) {
+ push @{ $env->{'psgix.cleanup.handler'} }, \&Bugzilla::_cleanup;
+ }
+ my $res = $app->($env);
+ Bugzilla::_cleanup() if not $env->{'psgix.cleanup'};
+ return $res;
+ };
+}
+
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm
new file mode 100644
index 000000000..34d6fc45d
--- /dev/null
+++ b/Bugzilla/Quantum.pm
@@ -0,0 +1,119 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Quantum;
+use Mojo::Base 'Mojolicious';
+
+# Needed for its exit() overload, must happen early in execution.
+use CGI::Compile;
+
+use Bugzilla ();
+use Bugzilla::BugMail ();
+use Bugzilla::CGI ();
+use Bugzilla::Constants qw(bz_locations);
+use Bugzilla::Extension ();
+use Bugzilla::Install::Requirements ();
+use Bugzilla::Logging;
+use Bugzilla::Quantum::CGI;
+use Bugzilla::Quantum::SES;
+use Bugzilla::Quantum::Static;
+use Mojo::Loader qw( find_modules );
+use Module::Runtime qw( require_module );
+use Bugzilla::Util ();
+use Cwd qw(realpath);
+use MojoX::Log::Log4perl::Tiny;
+
+has 'static' => sub { Bugzilla::Quantum::Static->new };
+
+sub startup {
+ my ($self) = @_;
+
+ DEBUG('Starting up');
+ $self->plugin('Bugzilla::Quantum::Plugin::Glue');
+ $self->plugin('Bugzilla::Quantum::Plugin::Hostage');
+ $self->plugin('Bugzilla::Quantum::Plugin::BlockIP');
+ $self->plugin('Bugzilla::Quantum::Plugin::BasicAuth');
+
+ Bugzilla::Extension->load_all();
+ if ( $self->mode ne 'development' ) {
+ Bugzilla->preload_features();
+ DEBUG('preloading templates');
+ Bugzilla->preload_templates();
+ DEBUG('done preloading templates');
+ require_module($_) for find_modules('Bugzilla::User::Setting');
+
+ $self->hook(
+ after_static => sub {
+ my ($c) = @_;
+ $c->res->headers->cache_control('public, max-age=31536000');
+ }
+ );
+ }
+
+ my $r = $self->routes;
+ Bugzilla::Quantum::CGI->load_all($r);
+ Bugzilla::Quantum::CGI->load_one( 'bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi' );
+
+ Bugzilla::WebService::Server::REST->preload;
+
+ $r->any('/')->to('CGI#index_cgi');
+ $r->any('/bug/<id:num>')->to('CGI#show_bug_cgi');
+ $r->any('/<id:num>')->to('CGI#show_bug_cgi');
+
+ $r->any('/rest')->to('CGI#rest_cgi');
+ $r->any('/rest.cgi/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' } );
+ $r->any('/rest/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' } );
+ $r->any('/bzapi')->to('CGI#bzapi_cgi');
+ $r->any('/bzapi/*PATH_INFO')->to('CGI#bzapi_cgi');
+ $r->any('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi');
+
+ $r->get(
+ '/__lbheartbeat__' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('__lbheartbeat__') );
+ },
+ );
+
+ $r->get(
+ '/__version__' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('version.json') );
+ },
+ );
+
+ $r->get(
+ '/version.json' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('version.json') );
+ },
+ );
+
+ $r->get('/__heartbeat__')->to('CGI#heartbeat_cgi');
+ $r->get('/robots.txt')->to('CGI#robots_cgi');
+
+ $r->any('/review')->to( 'CGI#page_cgi' => { 'id' => 'splinter.html' } );
+ $r->any('/user_profile')->to( 'CGI#page_cgi' => { 'id' => 'user_profile.html' } );
+ $r->any('/userprofile')->to( 'CGI#page_cgi' => { 'id' => 'user_profile.html' } );
+ $r->any('/request_defer')->to( 'CGI#page_cgi' => { 'id' => 'request_defer.html' } );
+ $r->any('/login')->to( 'CGI#index_cgi' => { 'GoAheadAndLogIn' => '1' } );
+
+ $r->any( '/:new_bug' => [ new_bug => qr{new[-_]bug} ] )->to('CGI#new_bug_cgi');
+
+ my $ses_auth = $r->under(
+ '/ses' => sub {
+ my ($c) = @_;
+ my $lc = Bugzilla->localconfig;
+
+ return $c->basic_auth( 'SES', $lc->{ses_username}, $lc->{ses_password} );
+ }
+ );
+ $ses_auth->any('/index.cgi')->to('SES#main');
+
+ Bugzilla::Hook::process( 'app_startup', { app => $self } );
+}
+
+1;
diff --git a/Bugzilla/Quantum/CGI.pm b/Bugzilla/Quantum/CGI.pm
new file mode 100644
index 000000000..7548c0809
--- /dev/null
+++ b/Bugzilla/Quantum/CGI.pm
@@ -0,0 +1,161 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Quantum::CGI;
+use Mojo::Base 'Mojolicious::Controller';
+
+use CGI::Compile;
+use Try::Tiny;
+use Taint::Util qw(untaint);
+use Sys::Hostname;
+use Sub::Quote 2.005000;
+use Sub::Name;
+use Socket qw(AF_INET inet_aton);
+use File::Spec::Functions qw(catfile);
+use File::Slurper qw(read_text);
+use English qw(-no_match_vars);
+use Bugzilla::Quantum::Stdout;
+use Bugzilla::Constants qw(bz_locations);
+
+our $C;
+my %SEEN;
+
+sub load_all {
+ my ( $class, $r ) = @_;
+
+ foreach my $file ( glob '*.cgi' ) {
+ my $name = _file_to_method($file);
+ $class->load_one( $name, $file );
+ $r->any("/$file")->to("CGI#$name");
+ }
+}
+
+sub load_one {
+ my ( $class, $name, $file ) = @_;
+ my $package = __PACKAGE__ . "::$name", my $inner_name = "_$name";
+ my $content = read_text( catfile( bz_locations->{cgi_path}, $file ) );
+ $content = "package $package; $content";
+ untaint($content);
+ my %options = (
+ package => $package,
+ file => $file,
+ line => 1,
+ no_defer => 1,
+ );
+ die "Tried to load $file more than once" if $SEEN{$file}++;
+ my $inner = quote_sub $inner_name, $content, {}, \%options;
+ my $wrapper = sub {
+ my ($c) = @_;
+ my $stdin = $c->_STDIN;
+ local $C = $c;
+ local %ENV = $c->_ENV($file);
+ local $CGI::Compile::USE_REAL_EXIT = 0;
+ local $PROGRAM_NAME = $file;
+ local *STDIN; ## no critic (local)
+ open STDIN, '<', $stdin->path or die "STDIN @{[$stdin->path]}: $!" if -s $stdin->path;
+ tie *STDOUT, 'Bugzilla::Quantum::Stdout', controller => $c; ## no critic (tie)
+ try {
+ Bugzilla->init_page();
+ $inner->();
+ }
+ catch {
+ die $_ unless ref $_ eq 'ARRAY' && $_->[0] eq "EXIT\n";
+ }
+ finally {
+ my $error = shift;
+ untie *STDOUT;
+ $c->finish unless $error;
+ Bugzilla->cleanup;
+ CGI::initialize_globals();
+ };
+ };
+
+ no strict 'refs'; ## no critic (strict)
+ *{$name} = subname( $name, $wrapper );
+ return 1;
+}
+
+
+sub _ENV {
+ my ( $c, $script_name ) = @_;
+ my $tx = $c->tx;
+ my $req = $tx->req;
+ my $headers = $req->headers;
+ my $content_length = $req->content->is_multipart ? $req->body_size : $headers->content_length;
+ my %env_headers = ( HTTP_COOKIE => '', HTTP_REFERER => '' );
+
+ for my $name ( @{ $headers->names } ) {
+ my $key = uc "http_$name";
+ $key =~ s/\W/_/g;
+ $env_headers{$key} = $headers->header($name);
+ }
+
+ my $remote_user;
+ if ( my $userinfo = $req->url->to_abs->userinfo ) {
+ $remote_user = $userinfo =~ /([^:]+)/ ? $1 : '';
+ }
+ elsif ( my $authenticate = $headers->authorization ) {
+ $remote_user = $authenticate =~ /Basic\s+(.*)/ ? b64_decode $1 : '';
+ $remote_user = $remote_user =~ /([^:]+)/ ? $1 : '';
+ }
+ my $path_info = $c->stash->{'mojo.captures'}{'PATH_INFO'};
+ my %captures = %{ $c->stash->{'mojo.captures'} // {} };
+ foreach my $key ( keys %captures ) {
+ if ( $key eq 'controller' || $key eq 'action' || $key eq 'PATH_INFO' || $key =~ /^REWRITE_/ ) {
+ delete $captures{$key};
+ }
+ }
+ my $cgi_query = Mojo::Parameters->new(%captures);
+ $cgi_query->append( $req->url->query );
+ my $prefix = $c->stash->{bmo_prefix} ? '/bmo/' : '/';
+
+ return (
+ %ENV,
+ CONTENT_LENGTH => $content_length || 0,
+ CONTENT_TYPE => $headers->content_type || '',
+ GATEWAY_INTERFACE => 'CGI/1.1',
+ HTTPS => $req->is_secure ? 'on' : 'off',
+ %env_headers,
+ QUERY_STRING => $cgi_query->to_string,
+ PATH_INFO => $path_info ? "/$path_info" : '',
+ REMOTE_ADDR => $tx->original_remote_address,
+ REMOTE_HOST => $tx->original_remote_address,
+ REMOTE_PORT => $tx->remote_port,
+ REMOTE_USER => $remote_user || '',
+ REQUEST_METHOD => $req->method,
+ SCRIPT_NAME => "$prefix$script_name",
+ SERVER_NAME => hostname,
+ SERVER_PORT => $tx->local_port,
+ SERVER_PROTOCOL => $req->is_secure ? 'HTTPS' : 'HTTP', # TODO: Version is missing
+ SERVER_SOFTWARE => __PACKAGE__,
+ );
+}
+
+sub _STDIN {
+ my $c = shift;
+ my $stdin;
+
+ if ( $c->req->content->is_multipart ) {
+ $stdin = Mojo::Asset::File->new;
+ $stdin->add_chunk( $c->req->build_body );
+ }
+ else {
+ $stdin = $c->req->content->asset;
+ }
+
+ return $stdin if $stdin->isa('Mojo::Asset::File');
+ return Mojo::Asset::File->new->add_chunk( $stdin->slurp );
+}
+
+sub _file_to_method {
+ my ($name) = @_;
+ $name =~ s/\./_/s;
+ $name =~ s/\W+/_/gs;
+ return $name;
+}
+
+1;
diff --git a/Bugzilla/Quantum/Plugin/BasicAuth.pm b/Bugzilla/Quantum/Plugin/BasicAuth.pm
new file mode 100644
index 000000000..e17273404
--- /dev/null
+++ b/Bugzilla/Quantum/Plugin/BasicAuth.pm
@@ -0,0 +1,40 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+package Bugzilla::Quantum::Plugin::BasicAuth;
+use 5.10.1;
+use Mojo::Base qw(Mojolicious::Plugin);
+
+use Bugzilla::Logging;
+use Carp;
+
+sub register {
+ my ( $self, $app, $conf ) = @_;
+
+ $app->renderer->add_helper(
+ basic_auth => sub {
+ my ( $c, $realm, $auth_user, $auth_pass ) = @_;
+ my $req = $c->req;
+ my ( $user, $password ) = $req->url->to_abs->userinfo =~ /^([^:]+):(.*)/;
+
+ unless ( $realm && $auth_user && $auth_pass ) {
+ croak 'basic_auth() called with missing parameters.';
+ }
+
+ unless ( $user eq $auth_user && $password eq $auth_pass ) {
+ WARN('username and password do not match');
+ $c->res->headers->www_authenticate("Basic realm=\"$realm\"");
+ $c->res->code(401);
+ $c->rendered;
+ return 0;
+ }
+
+ return 1;
+ }
+ );
+}
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Quantum/Plugin/BlockIP.pm b/Bugzilla/Quantum/Plugin/BlockIP.pm
new file mode 100644
index 000000000..058ecbf64
--- /dev/null
+++ b/Bugzilla/Quantum/Plugin/BlockIP.pm
@@ -0,0 +1,43 @@
+package Bugzilla::Quantum::Plugin::BlockIP;
+use 5.10.1;
+use Mojo::Base 'Mojolicious::Plugin';
+
+use Bugzilla::Memcached;
+
+use constant BLOCK_TIMEOUT => 60 * 60;
+
+my $MEMCACHED = Bugzilla::Memcached->new()->{memcached};
+
+sub register {
+ my ( $self, $app, $conf ) = @_;
+
+ $app->hook( before_routes => \&_before_routes );
+ $app->helper( block_ip => \&_block_ip );
+ $app->helper( unblock_ip => \&_unblock_ip );
+}
+
+sub _block_ip {
+ my ( $class, $ip ) = @_;
+ $MEMCACHED->set( "block_ip:$ip" => 1, BLOCK_TIMEOUT ) if $MEMCACHED;
+}
+
+sub _unblock_ip {
+ my ( $class, $ip ) = @_;
+ $MEMCACHED->delete("block_ip:$ip") if $MEMCACHED;
+}
+
+sub _before_routes {
+ my ($c) = @_;
+ return if $c->stash->{'mojo.static'};
+
+ my $ip = $c->tx->remote_address;
+ if ( $MEMCACHED && $MEMCACHED->get("block_ip:$ip") ) {
+ $c->block_ip($ip);
+ $c->res->code(429);
+ $c->res->message('Too Many Requests');
+ $c->res->body('Too Many Requests');
+ $c->finish;
+ }
+}
+
+1;
diff --git a/Bugzilla/Quantum/Plugin/Glue.pm b/Bugzilla/Quantum/Plugin/Glue.pm
new file mode 100644
index 000000000..ded4daf15
--- /dev/null
+++ b/Bugzilla/Quantum/Plugin/Glue.pm
@@ -0,0 +1,101 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Quantum::Plugin::Glue;
+use 5.10.1;
+use Mojo::Base 'Mojolicious::Plugin';
+
+use Try::Tiny;
+use Bugzilla::Constants;
+use Bugzilla::Logging;
+use Bugzilla::RNG ();
+use JSON::MaybeXS qw(decode_json);
+
+sub register {
+ my ( $self, $app, $conf ) = @_;
+
+ my %D;
+ if ( $ENV{BUGZILLA_HTTPD_ARGS} ) {
+ my $args = decode_json( $ENV{BUGZILLA_HTTPD_ARGS} );
+ foreach my $arg (@$args) {
+ if ( $arg =~ /^-D(\w+)$/ ) {
+ $D{$1} = 1;
+ }
+ else {
+ die "Unknown httpd arg: $arg";
+ }
+ }
+ }
+
+ # hypnotoad is weird and doesn't look for MOJO_LISTEN itself.
+ $app->config(
+ hypnotoad => {
+ proxy => 1,
+ listen => [ $ENV{MOJO_LISTEN} ],
+ },
+ );
+
+ # Make sure each httpd child receives a different random seed (bug 476622).
+ # Bugzilla::RNG has one srand that needs to be called for
+ # every process, and Perl has another. (Various Perl modules still use
+ # the built-in rand(), even though we never use it in Bugzilla itself,
+ # so we need to srand() both of them.)
+ # Also, ping the dbh to force a reconnection.
+ Mojo::IOLoop->next_tick(
+ sub {
+ Bugzilla::RNG::srand();
+ srand();
+ try { Bugzilla->dbh->ping };
+ }
+ );
+
+ $app->hook(
+ before_dispatch => sub {
+ my ($c) = @_;
+ if ( $D{HTTPD_IN_SUBDIR} ) {
+ my $path = $c->req->url->path;
+ if ( $path =~ s{^/bmo}{}s ) {
+ $c->stash->{bmo_prefix} = 1;
+ $c->req->url->path($path);
+ }
+ }
+ Log::Log4perl::MDC->put( request_id => $c->req->request_id );
+ }
+ );
+
+
+ $app->secrets( [ Bugzilla->localconfig->{side_wide_secret} ] );
+
+ $app->renderer->add_handler(
+ 'bugzilla' => sub {
+ my ( $renderer, $c, $output, $options ) = @_;
+ my $vars = delete $c->stash->{vars};
+
+ # Helpers
+ my %helper;
+ foreach my $method ( grep {m/^\w+\z/} keys %{ $renderer->helpers } ) {
+ my $sub = $renderer->helpers->{$method};
+ $helper{$method} = sub { $c->$sub(@_) };
+ }
+ $vars->{helper} = \%helper;
+
+ # The controller
+ $vars->{c} = $c;
+ my $name = $options->{template};
+ unless ( $name =~ /\./ ) {
+ $name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format};
+ }
+ my $template = Bugzilla->template;
+ $template->process( $name, $vars, $output )
+ or die $template->error;
+ }
+ );
+
+ $app->log( MojoX::Log::Log4perl::Tiny->new( logger => Log::Log4perl->get_logger( ref $app ) ) );
+}
+
+1;
diff --git a/Bugzilla/ModPerl/Hostage.pm b/Bugzilla/Quantum/Plugin/Hostage.pm
index a3bdfac58..418b09a0c 100644
--- a/Bugzilla/ModPerl/Hostage.pm
+++ b/Bugzilla/Quantum/Plugin/Hostage.pm
@@ -1,9 +1,6 @@
-package Bugzilla::ModPerl::Hostage;
+package Bugzilla::Quantum::Plugin::Hostage;
use 5.10.1;
-use strict;
-use warnings;
-
-use Apache2::Const qw(:common); ## no critic (Freenode::ModPerl)
+use Mojo::Base 'Mojolicious::Plugin';
sub _attachment_root {
my ($base) = @_;
@@ -24,8 +21,14 @@ sub _attachment_host_regex {
return qr/^$regex$/s;
}
-sub handler {
- my $r = shift;
+sub register {
+ my ( $self, $app, $conf ) = @_;
+
+ $app->hook(before_routes => \&_before_routes);
+}
+
+sub _before_routes {
+ my ( $c ) = @_;
state $urlbase = Bugzilla->localconfig->{urlbase};
state $urlbase_uri = URI->new($urlbase);
state $urlbase_host = $urlbase_uri->host;
@@ -34,38 +37,44 @@ sub handler {
state $attachment_root = _attachment_root($attachment_base);
state $attachment_host_regex = _attachment_host_regex($attachment_base);
- my $hostname = $r->hostname;
- return OK if $hostname eq $urlbase_host;
+ my $stash = $c->stash;
+ my $req = $c->req;
+ my $url = $req->url->to_abs;
+
+ return if $stash->{'mojo.static'};
+
+ my $hostname = $url->host;
+ return if $hostname eq $urlbase_host;
- my $path = $r->uri;
- return OK if $path eq '/__lbheartbeat__';
+ my $path = $url->path;
+ return if $path eq '/__lbheartbeat__';
if ($attachment_base && $hostname eq $attachment_root) {
- $r->headers_out->set(Location => $urlbase);
- return REDIRECT;
+ $c->redirect_to($urlbase);
+ return;
}
elsif ($attachment_base && $hostname =~ $attachment_host_regex) {
if ($path =~ m{^/attachment\.cgi}s) {
- return OK;
+ return;
} else {
- my $new_uri = URI->new($r->unparsed_uri);
+ my $new_uri = $url->clone;
$new_uri->scheme($urlbase_uri->scheme);
$new_uri->host($urlbase_host);
- $r->headers_out->set(Location => $new_uri);
- return REDIRECT;
+ $c->redirect_to($new_uri);
+ return;
}
}
elsif (my ($id) = $hostname =~ $urlbase_host_regex) {
my $new_uri = $urlbase_uri->clone;
$new_uri->path('/show_bug.cgi');
$new_uri->query_form(id => $id);
- $r->headers_out->set(Location => $new_uri);
- return REDIRECT;
+ $c->redirect_to($new_uri);
+ return;
}
else {
- $r->headers_out->set(Location => $urlbase);
- return REDIRECT;
+ $c->redirect_to($urlbase);
+ return;
}
}
-1; \ No newline at end of file
+1;
diff --git a/Bugzilla/Quantum/SES.pm b/Bugzilla/Quantum/SES.pm
new file mode 100644
index 000000000..03916075d
--- /dev/null
+++ b/Bugzilla/Quantum/SES.pm
@@ -0,0 +1,254 @@
+package Bugzilla::Quantum::SES;
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use 5.10.1;
+use Mojo::Base qw( Mojolicious::Controller );
+
+use Bugzilla::Constants qw(ERROR_MODE_DIE);
+use Bugzilla::Logging;
+use Bugzilla::Mailer qw(MessageToMTA);
+use Bugzilla::User ();
+use Bugzilla::Util qw(html_quote remote_ip);
+use JSON::MaybeXS qw(decode_json);
+use LWP::UserAgent ();
+use Try::Tiny qw(catch try);
+
+use Types::Standard qw( :all );
+use Type::Utils;
+use Type::Params qw( compile );
+
+my $Invocant = class_type { class => __PACKAGE__ };
+
+sub main {
+ my ($self) = @_;
+ try {
+ $self->_main;
+ }
+ catch {
+ FATAL("Error in SES Handler: ", $_);
+ $self->_respond( 400 => 'Bad Request' );
+ };
+}
+
+sub _main {
+ my ($self) = @_;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ my $message = $self->_decode_json_wrapper( $self->req->body ) // return;
+ my $message_type = $self->req->headers->header('X-Amz-SNS-Message-Type') // '(missing)';
+
+ if ( $message_type eq 'SubscriptionConfirmation' ) {
+ $self->_confirm_subscription($message);
+ }
+
+ elsif ( $message_type eq 'Notification' ) {
+ my $notification = $self->_decode_json_wrapper( $message->{Message} ) // return;
+ unless (
+ # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html
+ $self->_handle_notification( $notification, 'eventType' )
+
+ # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
+ || $self->_handle_notification( $notification, 'notificationType' )
+ )
+ {
+ WARN('Failed to find notification type');
+ $self->_respond( 400 => 'Bad Request' );
+ }
+ }
+
+ else {
+ WARN("Unsupported message-type: $message_type");
+ $self->_respond( 200 => 'OK' );
+ }
+}
+
+sub _confirm_subscription {
+ state $check = compile($Invocant, Dict[SubscribeURL => Str, slurpy Any]);
+ my ($self, $message) = $check->(@_);
+
+ my $subscribe_url = $message->{SubscribeURL};
+ if ( !$subscribe_url ) {
+ WARN('Bad SubscriptionConfirmation request: missing SubscribeURL');
+ $self->_respond( 400 => 'Bad Request' );
+ return;
+ }
+
+ my $ua = ua();
+ my $res = $ua->get( $message->{SubscribeURL} );
+ if ( !$res->is_success ) {
+ WARN( 'Bad response from SubscribeURL: ' . $res->status_line );
+ $self->_respond( 400 => 'Bad Request' );
+ return;
+ }
+
+ $self->_respond( 200 => 'OK' );
+}
+
+my $NotificationType = Enum [qw( Bounce Complaint )];
+my $TypeField = Enum [qw(eventType notificationType)];
+my $Notification = Dict [
+ eventType => Optional [$NotificationType],
+ notificationType => Optional [$NotificationType],
+ slurpy Any,
+];
+
+sub _handle_notification {
+ state $check = compile($Invocant, $Notification, $TypeField );
+ my ( $self, $notification, $type_field ) = $check->(@_);
+
+ if ( !exists $notification->{$type_field} ) {
+ return 0;
+ }
+ my $type = $notification->{$type_field};
+
+ if ( $type eq 'Bounce' ) {
+ $self->_process_bounce($notification);
+ }
+ elsif ( $type eq 'Complaint' ) {
+ $self->_process_complaint($notification);
+ }
+ else {
+ WARN("Unsupported notification-type: $type");
+ $self->_respond( 200 => 'OK' );
+ }
+ return 1;
+}
+
+my $BouncedRecipients = ArrayRef[
+ Dict[
+ emailAddress => Str,
+ action => Str,
+ diagnosticCode => Str,
+ slurpy Any,
+ ],
+];
+my $BounceNotification = Dict [
+ bounce => Dict [
+ bouncedRecipients => $BouncedRecipients,
+ reportingMTA => Str,
+ bounceSubType => Str,
+ bounceType => Str,
+ slurpy Any,
+ ],
+ slurpy Any,
+];
+
+sub _process_bounce {
+ state $check = compile($Invocant, $BounceNotification);
+ my ($self, $notification) = $check->(@_);
+
+ # disable each account that is bouncing
+ foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) {
+ my $address = $recipient->{emailAddress};
+ my $reason = sprintf '(%s) %s', $recipient->{action} // 'error', $recipient->{diagnosticCode} // 'unknown';
+
+ my $user = Bugzilla::User->new( { name => $address, cache => 1 } );
+ if ($user) {
+
+ # never auto-disable admin accounts
+ if ( $user->in_group('admin') ) {
+ Bugzilla->audit("ignoring bounce for admin <$address>: $reason");
+ }
+
+ else {
+ my $template = Bugzilla->template_inner();
+ my $vars = {
+ mta => $notification->{bounce}->{reportingMTA} // 'unknown',
+ reason => $reason,
+ };
+ my $disable_text;
+ $template->process( 'admin/users/bounce-disabled.txt.tmpl', $vars, \$disable_text )
+ || die $template->error();
+
+ $user->set_disabledtext($disable_text);
+ $user->set_disable_mail(1);
+ $user->update();
+ Bugzilla->audit( "bounce for <$address> disabled userid-" . $user->id . ": $reason" );
+ }
+ }
+
+ else {
+ Bugzilla->audit("bounce for <$address> has no user: $reason");
+ }
+ }
+
+ $self->_respond( 200 => 'OK' );
+}
+
+my $ComplainedRecipients = ArrayRef[Dict[ emailAddress => Str, slurpy Any ]];
+my $ComplaintNotification = Dict[
+ complaint => Dict [
+ complainedRecipients => $ComplainedRecipients,
+ complaintFeedbackType => Str,
+ slurpy Any,
+ ],
+ slurpy Any,
+];
+
+sub _process_complaint {
+ state $check = compile($Invocant, $ComplaintNotification);
+ my ($self, $notification) = $check->(@_);
+ my $template = Bugzilla->template_inner();
+ my $json = JSON::MaybeXS->new(
+ pretty => 1,
+ utf8 => 1,
+ canonical => 1,
+ );
+
+ foreach my $recipient ( @{ $notification->{complaint}->{complainedRecipients} } ) {
+ my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown';
+ my $address = $recipient->{emailAddress};
+ Bugzilla->audit("complaint for <$address> for '$reason'");
+ my $vars = {
+ email => $address,
+ user => Bugzilla::User->new( { name => $address, cache => 1 } ),
+ reason => $reason,
+ notification => $json->encode($notification),
+ };
+ my $message;
+ $template->process( 'email/ses-complaint.txt.tmpl', $vars, \$message )
+ || die $template->error();
+ MessageToMTA($message);
+ }
+
+ $self->_respond( 200 => 'OK' );
+}
+
+sub _respond {
+ my ( $self, $code, $message ) = @_;
+ $self->render(text => "$message\n", status => $code);
+}
+
+sub _decode_json_wrapper {
+ state $check = compile($Invocant, Str);
+ my ($self, $json) = $check->(@_);
+ my $result;
+ my $ok = try {
+ $result = decode_json($json);
+ }
+ catch {
+ WARN( 'Malformed JSON from ' . $self->tx->remote_address );
+ $self->_respond( 400 => 'Bad Request' );
+ return undef;
+ };
+ return $ok ? $result : undef;
+}
+
+sub ua {
+ my $ua = LWP::UserAgent->new();
+ $ua->timeout(10);
+ $ua->protocols_allowed( [ 'http', 'https' ] );
+ if ( my $proxy_url = Bugzilla->params->{'proxy_url'} ) {
+ $ua->proxy( [ 'http', 'https' ], $proxy_url );
+ }
+ else {
+ $ua->env_proxy;
+ }
+ return $ua;
+}
+
+1;
diff --git a/Bugzilla/Quantum/Static.pm b/Bugzilla/Quantum/Static.pm
new file mode 100644
index 000000000..c01f062a4
--- /dev/null
+++ b/Bugzilla/Quantum/Static.pm
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Quantum::Static;
+use Mojo::Base 'Mojolicious::Static';
+use Bugzilla::Constants qw(bz_locations);
+
+my $LEGACY_RE = qr{
+ ^ (?:static/v[0-9]+\.[0-9]+/) ?
+ ( (?:extensions/[^/]+/web|(?:image|graph|skin|j)s)/.+)
+ $
+}xs;
+
+sub file {
+ my ( $self, $rel ) = @_;
+
+ if ( my ($legacy_rel) = $rel =~ $LEGACY_RE ) {
+ local $self->{paths} = [ bz_locations->{cgi_path} ];
+ return $self->SUPER::file($legacy_rel);
+ }
+ else {
+ return $self->SUPER::file($rel);
+ }
+}
+
+1;
diff --git a/Bugzilla/Quantum/Stdout.pm b/Bugzilla/Quantum/Stdout.pm
new file mode 100644
index 000000000..9cf19992c
--- /dev/null
+++ b/Bugzilla/Quantum/Stdout.pm
@@ -0,0 +1,60 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Quantum::Stdout;
+use 5.10.1;
+use Moo;
+
+use Bugzilla::Logging;
+use Encode;
+use English qw(-no_match_vars);
+
+has 'controller' => (
+ is => 'ro',
+ required => 1,
+);
+
+has '_encoding' => (
+ is => 'rw',
+ default => '',
+);
+
+sub TIEHANDLE { ## no critic (unpack)
+ my $class = shift;
+
+ return $class->new(@_);
+}
+
+sub PRINTF { ## no critic (unpack)
+ my $self = shift;
+ $self->PRINT( sprintf @_ );
+}
+
+sub PRINT { ## no critic (unpack)
+ my $self = shift;
+ my $c = $self->controller;
+ my $bytes = join '', @_;
+ return unless $bytes;
+ if ( $self->_encoding ) {
+ $bytes = encode( $self->_encoding, $bytes );
+ }
+ $c->write($bytes . ( $OUTPUT_RECORD_SEPARATOR // '' ) );
+}
+
+sub BINMODE {
+ my ( $self, $mode ) = @_;
+ if ($mode) {
+ if ( $mode eq ':bytes' or $mode eq ':raw' ) {
+ $self->_encoding('');
+ }
+ elsif ( $mode eq ':utf8' ) {
+ $self->_encoding('utf8');
+ }
+ }
+}
+
+1;
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index f419955dc..e15c60f7f 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -802,18 +802,20 @@ sub data {
# BMO - to avoid massive amounts of joins, if we're selecting a lot of
# tracking flags, replace them with placeholders. the values will be
# retrieved later and injected into the result.
- my %tf_map = map { $_ => 1 } Bugzilla::Extension::TrackingFlags::Flag->get_all_names();
- my @tf_selected = grep { exists $tf_map{$_} } @orig_fields;
- # mysql has a limit of 61 joins, and we want to avoid massive amounts of joins
- # 30 ensures we won't hit the limit, nor generate too many joins
- if (scalar @tf_selected > 30) {
- foreach my $column (@tf_selected) {
- $self->COLUMNS->{$column}->{name} = "'---'";
+ if (Bugzilla->has_extension('TrackingFlags')) {
+ my %tf_map = map { $_ => 1 } Bugzilla::Extension::TrackingFlags::Flag->get_all_names();
+ my @tf_selected = grep { exists $tf_map{$_} } @orig_fields;
+ # mysql has a limit of 61 joins, and we want to avoid massive amounts of joins
+ # 30 ensures we won't hit the limit, nor generate too many joins
+ if (scalar @tf_selected > 30) {
+ foreach my $column (@tf_selected) {
+ $self->COLUMNS->{$column}->{name} = "'---'";
+ }
+ $self->{tracking_flags} = \@tf_selected;
+ }
+ else {
+ $self->{tracking_flags} = [];
}
- $self->{tracking_flags} = \@tf_selected;
- }
- else {
- $self->{tracking_flags} = [];
}
my $start_time = [gettimeofday()];
@@ -863,7 +865,7 @@ sub data {
$self->{data} = [map { $data{$_} } @$bug_ids];
# BMO - get tracking flags values, and insert into result
- if (@{ $self->{tracking_flags} }) {
+ if (Bugzilla->has_extension('TrackingFlags') && @{ $self->{tracking_flags} }) {
# read values
my $values;
$sql = "
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 299734d64..cdeb54a50 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -12,6 +12,7 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Logging;
use Bugzilla::Template::PreloadProvider;
use Bugzilla::Bug;
use Bugzilla::Constants;
@@ -994,6 +995,8 @@ sub create {
'feature_enabled' => sub { return Bugzilla->feature(@_); },
+ 'has_extension' => sub { return Bugzilla->has_extension(@_); },
+
# field_descs can be somewhat slow to generate, so we generate
# it only once per-language no matter how many times
# $template->process() is called.
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index a8477a62d..aa524b263 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -29,7 +29,7 @@ use base qw(Exporter);
get_text template_var disable_utf8
enable_utf8 detect_encoding email_filter
round extract_nicks);
-
+use Bugzilla::Logging;
use Bugzilla::Constants;
use Bugzilla::RNG qw(irand);
@@ -317,6 +317,7 @@ sub do_ssl_redirect_if_required {
# If we're already running under SSL, never redirect.
return if $ENV{HTTPS} && $ENV{HTTPS} eq 'on';
+ DEBUG("Redirect to HTTPS because \$ENV{HTTPS}=$ENV{HTTPS}");
Bugzilla->cgi->redirect_to_https();
}
diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm
index 6bb73af01..5ad50e91c 100644
--- a/Bugzilla/WebService/Server/XMLRPC.pm
+++ b/Bugzilla/WebService/Server/XMLRPC.pm
@@ -14,11 +14,7 @@ use warnings;
use Bugzilla::Logging;
use XMLRPC::Transport::HTTP;
use Bugzilla::WebService::Server;
-if ($ENV{MOD_PERL}) {
- our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
-} else {
- our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
-}
+our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
use Bugzilla::WebService::Constants;
use Bugzilla::Error;
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
index 29ff05448..d462c884a 100644
--- a/Bugzilla/WebService/Util.pm
+++ b/Bugzilla/WebService/Util.pm
@@ -23,7 +23,7 @@ use base qw(Exporter);
# We have to "require", not "use" this, because otherwise it tries to
# use features of Test::More during import().
-require Test::Taint;
+require Test::Taint if ${^TAINT};
our @EXPORT_OK = qw(
extract_flags
@@ -193,8 +193,10 @@ sub taint_data {
# Though this is a private function, it hasn't changed since 2004 and
# should be safe to use, and prevents us from having to write it ourselves
# or require another module to do it.
- Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
- Test::Taint::taint_deeply(\@params);
+ if (${^TAINT}) {
+ Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
+ Test::Taint::taint_deeply(\@params);
+ }
}
sub _delete_bad_keys {