]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1201113: Support to run Bugzilla as a PSGI application
authorFrédéric Buclin <LpSolit@gmail.com>
Wed, 23 Dec 2015 19:46:43 +0000 (20:46 +0100)
committerFrédéric Buclin <LpSolit@gmail.com>
Wed, 23 Dec 2015 19:46:43 +0000 (20:46 +0100)
r=dylan

18 files changed:
.htaccess
Bugzilla.pm
Bugzilla/CGI.pm
Bugzilla/Config.pm
Bugzilla/Error.pm
Bugzilla/Install/Filesystem.pm
Bugzilla/Install/Requirements.pm
Bugzilla/Install/Util.pm
Bugzilla/Template.pm
app.psgi [new file with mode: 0644]
editclassifications.cgi
editgroups.cgi
editkeywords.cgi
shutdown.cgi [new file with mode: 0755]
t/002goodperl.t
t/Support/Files.pm
template/en/default/setup/strings.txt.pl
testagent.cgi

index 973b396d465a9d7ebfdefd3c38dc7f4742229b4f..f9eeb541cbcebe50cd4fb7d4ff5d9f024aefab9e 100644 (file)
--- a/.htaccess
+++ b/.htaccess
@@ -1,5 +1,5 @@
 # Don't allow people to retrieve non-cgi executable files or our private data
-<FilesMatch (\.pm|\.pl|\.tmpl|localconfig.*|cpanfile)$>
+<FilesMatch (\.pm|\.pl|\.psgi|\.tmpl|localconfig.*|cpanfile)$>
   <IfModule mod_version.c>
     <IfVersion < 2.4>
       Deny from all
index c6d7ae39bce94de2a181d14ed962e1894c3067bd..16075b2d128ca0b1ace498bef44c061143392df5 100644 (file)
@@ -13,8 +13,11 @@ use warnings;
 
 # We want any compile errors to get to the browser, if possible.
 BEGIN {
-    # This makes sure we're in a CGI.
-    if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
+    # This makes sure we're in a CGI. mod_perl doesn't support Carp
+    # and Plack reports errors elsewhere.
+    # We cannot call i_am_persistent() from here as its module is
+    # not loaded yet.
+    if ($ENV{SERVER_SOFTWARE} && !($ENV{MOD_PERL} || $ENV{BZ_PLACK})) {
         require CGI::Carp;
         CGI::Carp->import('fatalsToBrowser');
     }
@@ -32,7 +35,7 @@ use Bugzilla::Field;
 use Bugzilla::Flag;
 use Bugzilla::Install::Localconfig qw(read_localconfig);
 use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES have_vers);
-use Bugzilla::Install::Util qw(init_console include_languages);
+use Bugzilla::Install::Util qw(init_console include_languages i_am_persistent);
 use Bugzilla::Memcached;
 use Bugzilla::Template;
 use Bugzilla::Token;
@@ -149,43 +152,46 @@ sub init_page {
         {
             exit;
         }
+        # Plack requires to exit differently.
+        return -1 if $ENV{BZ_PLACK};
+        _shutdown();
+    }
+}
 
-        # For security reasons, log out users when Bugzilla is down.
-        # Bugzilla->login() is required to catch the logincookie, if any.
-        my $user;
-        eval { $user = Bugzilla->login(LOGIN_OPTIONAL); };
-        if ($@) {
-            # The DB is not accessible. Use the default user object.
-            $user = Bugzilla->user;
-            $user->{settings} = {};
-        }
-        my $userid = $user->id;
-        Bugzilla->logout();
-
-        my $template = Bugzilla->template;
-        my $vars = {};
-        $vars->{'message'} = 'shutdown';
-        $vars->{'userid'} = $userid;
-        # Generate and return a message about the downtime, appropriately
-        # for if we're a command-line script or a CGI script.
-        my $extension;
-        if (i_am_cgi() && (!Bugzilla->cgi->param('ctype')
-                           || Bugzilla->cgi->param('ctype') eq 'html')) {
+sub _shutdown {
+    # For security reasons, log out users when Bugzilla is down.
+    # Bugzilla->login() is required to catch the logincookie, if any.
+    my $user = eval { Bugzilla->login(LOGIN_OPTIONAL); };
+    if ($@) {
+        # The DB is not accessible. Use the default user object.
+        $user = Bugzilla->user;
+        $user->{settings} = {};
+    }
+    my $userid = $user->id;
+    Bugzilla->logout();
+
+    # Generate and return a message about the downtime, appropriately
+    # for if we're a command-line script or a CGI script.
+    my $cgi = Bugzilla->cgi;
+    my $extension = 'txt';
+
+    if (i_am_cgi()) {
+        # Set the HTTP status to 503 when Bugzilla is down to avoid pages
+        # being indexed by search engines.
+        print $cgi->header(-status => 503,
+                           -retry_after => SHUTDOWNHTML_RETRY_AFTER);
+
+        if (!$cgi->param('ctype') || $cgi->param('ctype') eq 'html') {
             $extension = 'html';
         }
-        else {
-            $extension = 'txt';
-        }
-        if (i_am_cgi()) {
-            # Set the HTTP status to 503 when Bugzilla is down to avoid pages
-            # being indexed by search engines.
-            print Bugzilla->cgi->header(-status => 503, 
-                -retry_after => SHUTDOWNHTML_RETRY_AFTER);
-        }
-        $template->process("global/message.$extension.tmpl", $vars)
-            || ThrowTemplateError($template->error);
-        exit;
     }
+
+    my $template = Bugzilla->template;
+    my $vars = { message => 'shutdown', userid => $userid };
+
+    $template->process("global/message.$extension.tmpl", $vars)
+      or ThrowTemplateError($template->error);
+    exit;
 }
 
 #####################################################################
@@ -714,11 +720,13 @@ sub _cleanup {
 }
 
 sub END {
-    # Bugzilla.pm cannot compile in mod_perl.pl if this runs.
-    _cleanup() unless $ENV{MOD_PERL};
+    # This is managed in mod_perl.pl and app.psgi when running
+    # in a persistent environment.
+    _cleanup() unless i_am_persistent();
 }
 
-init_page() if !$ENV{MOD_PERL};
+# Also managed in mod_perl.pl and app.psgi.
+init_page() unless i_am_persistent();
 
 1;
 
index fcca0ec6a899ef7211acc2d1aa12514afd5f1858..25ee0acbec7089d4743042b98973afdd57fc0e4f 100644 (file)
@@ -66,7 +66,7 @@ sub new {
             # else we will be redirected outside Bugzilla.
             my $script_name = $self->script_name;
             $path_info =~ s/^\Q$script_name\E//;
-            if ($path_info) {
+            if ($script_name && $path_info) {
                 print $self->redirect($self->url(-path => 0, -query => 1));
             }
         }
@@ -283,7 +283,7 @@ sub close_standby_message {
         print $self->multipart_end();
         print $self->multipart_start(-type => $contenttype);
     }
-    else {
+    elsif (!$self->{_header_done}) {
         print $self->header($contenttype);
     }
 }
@@ -356,6 +356,7 @@ sub header {
     Bugzilla::Hook::process('cgi_headers',
         { cgi => $self, headers => \%headers }
     );
+    $self->{_header_done} = 1;
 
     return $self->SUPER::header(%headers) || "";
 }
index 5dfe2e37d9a79ad747a6c6d3b13664f2b11adaec..d47577212c6a5fc02fee0e22d63509197dad4161 100644 (file)
@@ -16,6 +16,7 @@ use autodie qw(:default);
 
 use Bugzilla::Constants;
 use Bugzilla::Hook;
+use Bugzilla::Install::Util qw(i_am_persistent);
 use Bugzilla::Util qw(trick_taint);
 
 use JSON::XS;
@@ -319,15 +320,17 @@ sub read_param_file {
         }
     }
     elsif ($ENV{'SERVER_SOFTWARE'}) {
-       # We're in a CGI, but the params file doesn't exist. We can't
-       # Template Toolkit, or even install_string, since checksetup
-       # might not have thrown an error. Bugzilla::CGI->new
-       # hasn't even been called yet, so we manually use CGI::Carp here
-       # so that the user sees the error.
-       require CGI::Carp;
-       CGI::Carp->import('fatalsToBrowser');
-       die "The $file file does not exist."
-           . ' You probably need to run checksetup.pl.',
+        # We're in a CGI, but the params file doesn't exist. We can't
+        # Template Toolkit, or even install_string, since checksetup
+        # might not have thrown an error. Bugzilla::CGI->new
+        # hasn't even been called yet, so we manually use CGI::Carp here
+        # so that the user sees the error.
+        unless (i_am_persistent()) {
+            require CGI::Carp;
+            CGI::Carp->import('fatalsToBrowser');
+        }
+        die "The $file file does not exist."
+            . ' You probably need to run checksetup.pl.',
     }
     return \%params;
 }
index e730022db8a924e76eff7a19b5488df73537850a..ee40ccf8bd44ac6fc95d94375c9f216360e91145 100644 (file)
@@ -28,8 +28,9 @@ use Date::Format;
 sub _in_eval {
     my $in_eval = 0;
     for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
-        last if $sub =~ /^ModPerl/;
-        $in_eval = 1 if $sub =~ /^\(eval\)/;
+        last if $sub =~ /^(?:ModPerl|Plack|CGI::Compile)/;
+        # An eval followed by CGI::Compile is not a "real" eval.
+        $in_eval = 1 if $sub =~ /^\(eval\)/ && (caller($stack + 1))[3] !~ /^CGI::Compile/;
     }
     return $in_eval;
 }
index 5f5677460902790452bc736ffe852b95c6d04a72..e17285b2fa0468e7efbc4e79e369101503215089 100644 (file)
@@ -167,6 +167,7 @@ sub FILESYSTEM {
         'install-module.pl' => { perms => OWNER_EXECUTE },
         'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE },
 
+        'app.psgi'      => { perms => CGI_READ },
         'Bugzilla.pm'   => { perms => CGI_READ },
         "$localconfig*" => { perms => CGI_READ },
         'bugzilla.dtd'  => { perms => WS_SERVE },
index 9a03968d7395f2b2218e3717ea119d9b6c914a69..a48778c1b6eac784bf1ffca9e3649ee364f58f3f 100644 (file)
@@ -312,6 +312,26 @@ sub OPTIONAL_MODULES {
         version => 0,
         feature => ['jsonrpc'],
     },
+    {
+        package => 'Plack',
+        module  => 'Plack',
+        # 1.0031 contains a security fix which would affect us.
+        # It also fixes warnings thrown in Perl 5.20 and newer.
+        version => 1.0031,
+        feature => ['psgi'],
+    },
+    {
+        package => 'CGI-Compile',
+        module  => 'CGI::Compile',
+        version => 0,
+        feature => ['psgi'],
+    },
+    {
+        package => 'CGI-Emulate-PSGI',
+        module  => 'CGI::Emulate::PSGI',
+        version => 0,
+        feature => ['psgi'],
+    },
     {
         package => 'Test-Taint',
         module  => 'Test::Taint',
@@ -474,6 +494,7 @@ use constant FEATURE_FILES => (
                       'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'],
     rest          => ['Bugzilla/WebService/Server/REST.pm', 'rest.cgi',
                       'Bugzilla/WebService/Server/REST/Resources/*.pm'],
+    psgi          => ['app.psgi'],
     moving        => ['importxml.pl'],
     auth_ldap     => ['Bugzilla/Auth/Verify/LDAP.pm'],
     auth_radius   => ['Bugzilla/Auth/Verify/RADIUS.pm'],
index c05037061f9eb38bda4bbf327214a0959b4c38ac..82752b961aad269c298573c008a448996b0936d7 100644 (file)
@@ -34,6 +34,7 @@ our @EXPORT_OK = qw(
     extension_requirement_packages
     extension_template_directory
     extension_web_directory
+    i_am_persistent
     indicate_progress
     install_string
     include_languages
@@ -83,6 +84,10 @@ sub get_version_and_os {
              os_ver   => $os_details[3] };
 }
 
+sub i_am_persistent {
+    return ($ENV{MOD_PERL} || $ENV{BZ_PLACK}) ? 1 : 0;
+}
+
 sub _extension_paths {
     my $dir = bz_locations()->{'extensionsdir'};
     my @extension_items = glob("$dir/*");
@@ -711,6 +716,11 @@ binary, if the binary is in the C<PATH>.
 Returns a hash containing information about what version of Bugzilla we're
 running, what perl version we're using, and what OS we're running on.
 
+=item C<i_am_persistent>
+
+Returns true if Bugzilla is running in a persistent environment, such as
+mod_perl or PSGI. Returns false if running in mod_cgi mode.
+
 =item C<get_console_locale>
 
 Returns the language to use based on the LC_CTYPE value returned by the OS.
index 04abe82001f107cc6ff4de4133aaf1c9787c429d..9398ca4b51609018b22c730b6dc25f5df31676e3 100644 (file)
@@ -17,7 +17,7 @@ use Bugzilla::WebService::Constants;
 use Bugzilla::Hook;
 use Bugzilla::Install::Requirements;
 use Bugzilla::Install::Util qw(install_string template_include_path 
-                               include_languages);
+                               include_languages i_am_persistent);
 use Bugzilla::Classification;
 use Bugzilla::Keyword;
 use Bugzilla::Util;
@@ -740,7 +740,7 @@ sub create {
         # if a packager has modified bz_locations() to contain absolute
         # paths.
         ABSOLUTE => 1,
-        RELATIVE => $ENV{MOD_PERL} ? 0 : 1,
+        RELATIVE => i_am_persistent() ? 0 : 1,
 
         COMPILE_DIR => bz_locations()->{'template_cache'},
 
diff --git a/app.psgi b/app.psgi
new file mode 100644 (file)
index 0000000..c04359f
--- /dev/null
+++ b/app.psgi
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+# 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 strict;
+use warnings;
+
+use File::Basename;
+use lib dirname(__FILE__);
+use Bugzilla::Constants ();
+use lib Bugzilla::Constants::bz_locations()->{ext_libpath};
+
+use Plack;
+use Plack::Builder;
+use Plack::App::URLMap;
+use Plack::App::WrapCGI;
+use Plack::Response;
+
+use constant STATIC => qw(
+    data/assets
+    data/webdot
+    docs
+    extensions/[^/]+/web
+    graphs
+    images
+    js
+    skins
+);
+
+builder {
+    my $static_paths = join('|', STATIC);
+    enable 'Static',
+        path => qr{^/($static_paths)/},
+        root => Bugzilla::Constants::bz_locations->{cgi_path};
+
+    $ENV{BZ_PLACK} = 'Plack/' . Plack->VERSION;
+
+    my $map = Plack::App::URLMap->new;
+
+    my @cgis = glob('*.cgi');
+    my $shutdown_app = Plack::App::WrapCGI->new(script => 'shutdown.cgi')->to_app;
+
+    foreach my $cgi_script (@cgis) {
+        my $app = eval { Plack::App::WrapCGI->new(script => $cgi_script)->to_app };
+        # Some CGI scripts won't compile if not all optional Perl modules are
+        # installed. That's expected.
+        if ($@) {
+            warn "Cannot compile $cgi_script. Skipping!\n";
+            next;
+        }
+
+        my $wrapper = sub {
+            my $ret = Bugzilla::init_page();
+            my $res = ($ret eq '-1' && $cgi_script ne 'editparams.cgi') ? $shutdown_app->(@_) : $app->(@_);
+            Bugzilla::_cleanup();
+            return $res;
+        };
+
+        my $base_name = basename($cgi_script);
+        $map->map('/' => $wrapper) if $cgi_script eq 'index.cgi';
+        $map->map('/rest' => $wrapper) if $cgi_script eq 'rest.cgi';
+        $map->map("/$base_name" => $wrapper);
+    }
+    my $app = $map->to_app;
+};
index ea4b139da528376a3e33474fe0c364076f766db8..f839cfa03ea3254eed040675c8b3fe928e99cf3e 100755 (executable)
@@ -38,7 +38,6 @@ sub LoadTemplate {
 
     $action =~ /(\w+)/;
     $action = $1;
-    print $cgi->header();
     $template->process("admin/classifications/$action.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
index 35989b954b0384d3caa025aa37929c9360d4ea1e..f2c915556a26191f245a8a65fd606972e1f3be24 100755 (executable)
@@ -135,8 +135,7 @@ sub get_current_and_available {
 unless ($action) {
     my @groups = Bugzilla::Group->get_all;
     $vars->{'groups'} = \@groups;
-    
-    print $cgi->header();
+
     $template->process("admin/groups/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
@@ -155,12 +154,10 @@ if ($action eq 'changeform') {
 
     get_current_and_available($group, $vars);
     $vars->{'group'} = $group;
-    $vars->{'token'}       = issue_session_token('edit_group');
+    $vars->{'token'} = issue_session_token('edit_group');
 
-    print $cgi->header();
     $template->process("admin/groups/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
@@ -172,10 +169,9 @@ if ($action eq 'changeform') {
 
 if ($action eq 'add') {
     $vars->{'token'} = issue_session_token('add_group');
-    print $cgi->header();
+
     $template->process("admin/groups/create.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-    
     exit;
 }
 
@@ -204,7 +200,6 @@ if ($action eq 'new') {
     get_current_and_available($group, $vars);
     $vars->{'token'} = issue_session_token('edit_group');
 
-    print $cgi->header();
     $template->process("admin/groups/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
@@ -228,10 +223,8 @@ if ($action eq 'del') {
     $vars->{'group'} = $group;
     $vars->{'token'} = issue_session_token('delete_group');
 
-    print $cgi->header();
     $template->process("admin/groups/delete.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-    
     exit;
 }
 
@@ -255,7 +248,6 @@ if ($action eq 'delete') {
     $vars->{'message'} = 'group_deleted';
     $vars->{'groups'} = [Bugzilla::Group->get_all];
 
-    print $cgi->header();
     $template->process("admin/groups/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
@@ -277,7 +269,6 @@ if ($action eq 'postchanges') {
     $vars->{'changes'} = $changes;
     $vars->{'token'} = issue_session_token('edit_group');
 
-    print $cgi->header();
     $template->process("admin/groups/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
@@ -288,6 +279,7 @@ if ($action eq 'confirm_remove') {
     $vars->{'group'} = $group;
     $vars->{'regexp'} = CheckGroupRegexp($cgi->param('regexp'));
     $vars->{'token'} = issue_session_token('remove_group_members');
+
     $template->process('admin/groups/confirm-remove.html.tmpl', $vars)
         || ThrowTemplateError($template->error());
     exit;
@@ -326,10 +318,8 @@ if ($action eq 'remove_regexp') {
     $vars->{'group'} = $group->name;
     $vars->{'groups'} = [Bugzilla::Group->get_all];
 
-    print $cgi->header();
     $template->process("admin/groups/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
index ab079e5405845bc20988a5c5820d6d94352c6520..da7513f6f2a3d4544b108bbf3b835af8c3ddf12b 100755 (executable)
@@ -24,10 +24,6 @@ my $dbh = Bugzilla->dbh;
 my $template = Bugzilla->template;
 my $vars = {};
 
-#
-# Preliminary checks:
-#
-
 my $user = Bugzilla->login(LOGIN_REQUIRED);
 
 print $cgi->header();
@@ -47,22 +43,16 @@ $vars->{'action'} = $action;
 if ($action eq "") {
     $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
 
-    print $cgi->header();
     $template->process("admin/keywords/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
-    
 
 if ($action eq 'add') {
     $vars->{'token'} = issue_session_token('add_keyword');
 
-    print $cgi->header();
-
     $template->process("admin/keywords/create.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
@@ -79,8 +69,6 @@ if ($action eq 'new') {
 
     delete_token($token);
 
-    print $cgi->header();
-
     $vars->{'message'} = 'keyword_created';
     $vars->{'name'} = $keyword->name;
     $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
@@ -90,7 +78,6 @@ if ($action eq 'new') {
     exit;
 }
 
-
 #
 # action='edit' -> present the edit keywords from
 #
@@ -104,13 +91,11 @@ if ($action eq 'edit') {
     $vars->{'keyword'} = $keyword;
     $vars->{'token'} = issue_session_token('edit_keyword');
 
-    print $cgi->header();
     $template->process("admin/keywords/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
 }
 
-
 #
 # action='update' -> update the keyword
 #
@@ -129,8 +114,6 @@ if ($action eq 'update') {
 
     delete_token($token);
 
-    print $cgi->header();
-
     $vars->{'message'} = 'keyword_updated';
     $vars->{'keyword'} = $keyword;
     $vars->{'changes'} = $changes;
@@ -148,7 +131,6 @@ if ($action eq 'del') {
     $vars->{'keyword'} = $keyword;
     $vars->{'token'} = issue_session_token('delete_keyword');
 
-    print $cgi->header();
     $template->process("admin/keywords/confirm-delete.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
@@ -163,8 +145,6 @@ if ($action eq 'delete') {
 
     delete_token($token);
 
-    print $cgi->header();
-
     $vars->{'message'} = 'keyword_deleted';
     $vars->{'keyword'} = $keyword;
     $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
diff --git a/shutdown.cgi b/shutdown.cgi
new file mode 100755 (executable)
index 0000000..7b33ec7
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/perl -T
+# 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 strict;
+use warnings;
+
+use lib qw(. lib);
+
+use Bugzilla;
+
+Bugzilla::_shutdown();
index d1858361f7bfbbc5a5f9bcf5d48e243e69cb4089..cfc9fb9e9a80f326c835eaa9c80814867f9d5a4d 100644 (file)
@@ -40,7 +40,7 @@ foreach my $file (@testitems) {
         ok(1,"$file does not have a shebang");
     } else {
         my $flags;
-        if (!defined $ext || $ext eq "pl") {
+        if (!defined $ext || $ext eq 'pl' || $ext eq 'psgi') {
             # standalone programs aren't taint checked yet
             if (grep { $file eq $_ } @require_taint) {
                 $flags = 'T';
index f3fae58fc6859765d2608c5edd20b83eff5f0dd1..f1c88e858a8ab21c340a84f87012973354ab76c1 100644 (file)
@@ -34,7 +34,7 @@ sub isTestingFile {
     my ($file) = @_;
     my $exclude;
 
-    if ($file =~ /\.cgi$|\.pl$|\.pm$/) {
+    if ($file =~ /\.psgi$|\.cgi$|\.pl$|\.pm$/) {
         return 1;
     }
     my $additional;
index d1bb873cad7d1e8cb2094ffbecfe18828be9881c..4409d9ff3cc772d49f01e87553cca48a5e4057fd 100644 (file)
@@ -103,6 +103,7 @@ END
     feature_mod_perl          => 'mod_perl',
     feature_moving            => 'Move Bugs Between Installations',
     feature_patch_viewer      => 'Patch Viewer',
+    feature_psgi              => 'PSGI Support',
     feature_rest              => 'REST Interface',
     feature_smtp_auth         => 'SMTP Authentication',
     feature_smtp_ssl          => 'SSL Support for SMTP',
index d9d5afd1a06d7e91381feebeb60a6f65eec03770..dfb1ff228c7e347f087af8e8951118285681ff9f 100755 (executable)
@@ -15,5 +15,6 @@ use strict;
 use warnings;
 
 say "content-type:text/plain\n";
-say "OK " . ($::ENV{MOD_PERL} || "mod_cgi");
-exit;
+
+print 'OK ';
+say $ENV{BZ_PLACK} || $ENV{MOD_PERL} || 'mod_cgi';