diff options
-rw-r--r-- | Bugzilla/Quantum.pm | 199 | ||||
-rw-r--r-- | Bugzilla/Quantum/CGI.pm | 245 | ||||
-rw-r--r-- | Bugzilla/Quantum/Home.pm | 18 | ||||
-rw-r--r-- | Bugzilla/Quantum/Plugin/BlockIP.pm | 38 | ||||
-rw-r--r-- | Bugzilla/Quantum/Plugin/Glue.pm | 234 | ||||
-rw-r--r-- | Bugzilla/Quantum/Plugin/Helpers.pm | 82 | ||||
-rw-r--r-- | Bugzilla/Quantum/Plugin/Hostage.pm | 117 | ||||
-rw-r--r-- | Bugzilla/Quantum/SES.pm | 367 | ||||
-rw-r--r-- | Bugzilla/Quantum/Static.pm | 16 | ||||
-rw-r--r-- | Bugzilla/Quantum/Stdout.pm | 50 |
10 files changed, 687 insertions, 679 deletions
diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm index 014663bd0..4fddb8da9 100644 --- a/Bugzilla/Quantum.pm +++ b/Bugzilla/Quantum.pm @@ -34,110 +34,113 @@ use Bugzilla::WebService::Server::REST; 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') unless $ENV{BUGZILLA_DISABLE_HOSTAGE}; - $self->plugin('Bugzilla::Quantum::Plugin::BlockIP'); - $self->plugin('Bugzilla::Quantum::Plugin::Helpers'); - - # hypnotoad is weird and doesn't look for MOJO_LISTEN itself. - $self->config( - hypnotoad => { - proxy => $ENV{MOJO_REVERSE_PROXY} // 1, - heartbeat_interval => $ENV{MOJO_HEARTBEAT_INTERVAL} // 10, - heartbeat_timeout => $ENV{MOJO_HEARTBEAT_TIMEOUT} // 120, - inactivity_timeout => $ENV{MOJO_INACTIVITY_TIMEOUT} // 120, - workers => $ENV{MOJO_WORKERS} // 15, - clients => $ENV{MOJO_CLIENTS} // 10, - spare => $ENV{MOJO_SPARE} // 5, - listen => [ $ENV{MOJO_LISTEN} // 'http://*:3000' ], - }, + my ($self) = @_; + + DEBUG('Starting up'); + $self->plugin('Bugzilla::Quantum::Plugin::Glue'); + $self->plugin('Bugzilla::Quantum::Plugin::Hostage') + unless $ENV{BUGZILLA_DISABLE_HOSTAGE}; + $self->plugin('Bugzilla::Quantum::Plugin::BlockIP'); + $self->plugin('Bugzilla::Quantum::Plugin::Helpers'); + + # hypnotoad is weird and doesn't look for MOJO_LISTEN itself. + $self->config( + hypnotoad => { + proxy => $ENV{MOJO_REVERSE_PROXY} // 1, + heartbeat_interval => $ENV{MOJO_HEARTBEAT_INTERVAL} // 10, + heartbeat_timeout => $ENV{MOJO_HEARTBEAT_TIMEOUT} // 120, + inactivity_timeout => $ENV{MOJO_INACTIVITY_TIMEOUT} // 120, + workers => $ENV{MOJO_WORKERS} // 15, + clients => $ENV{MOJO_CLIENTS} // 10, + spare => $ENV{MOJO_SPARE} // 5, + listen => [$ENV{MOJO_LISTEN} // 'http://*:3000'], + }, + ); + + # 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(); + eval { Bugzilla->dbh->ping }; + }); + + 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'); + } ); + } + Bugzilla::WebService::Server::REST->preload; - # 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(); - eval { Bugzilla->dbh->ping }; - } - ); - - 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'); - } - ); - } - Bugzilla::WebService::Server::REST->preload; - - $self->setup_routes; + $self->setup_routes; - Bugzilla::Hook::process( 'app_startup', { app => $self } ); + Bugzilla::Hook::process('app_startup', {app => $self}); } sub setup_routes { - my ($self) = @_; - - my $r = $self->routes; - Bugzilla::Quantum::CGI->load_all($r); - Bugzilla::Quantum::CGI->load_one( 'bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi' ); - - $r->get('/home')->to('Home#index'); - $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->get('/testagent.cgi' => sub { - my $c = shift; - $c->render(text => "OK Mojolicious"); - }); - - $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('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi'); - $r->any('/latest/*PATH_INFO')->to('CGI#bzapi_cgi'); - $r->any('/bzapi/*PATH_INFO')->to('CGI#bzapi_cgi'); - - $r->static_file('/__lbheartbeat__'); - $r->static_file('/__version__' => { file => 'version.json', content_type => 'application/json' }); - $r->static_file('/version.json', { content_type => 'application/json' }); - - $r->page('/review', 'splinter.html'); - $r->page('/user_profile', 'user_profile.html'); - $r->page('/userprofile', 'user_profile.html'); - $r->page('/request_defer', 'request_defer.html'); - - $r->get('/__heartbeat__')->to('CGI#heartbeat_cgi'); - $r->get('/robots.txt')->to('CGI#robots_cgi'); - $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'); + my ($self) = @_; + + my $r = $self->routes; + Bugzilla::Quantum::CGI->load_all($r); + Bugzilla::Quantum::CGI->load_one('bzapi_cgi', + 'extensions/BzAPI/bin/rest.cgi'); + + $r->get('/home')->to('Home#index'); + $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->get( + '/testagent.cgi' => sub { + my $c = shift; + $c->render(text => "OK Mojolicious"); + } + ); + + $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('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi'); + $r->any('/latest/*PATH_INFO')->to('CGI#bzapi_cgi'); + $r->any('/bzapi/*PATH_INFO')->to('CGI#bzapi_cgi'); + + $r->static_file('/__lbheartbeat__'); + $r->static_file('/__version__' => + {file => 'version.json', content_type => 'application/json'}); + $r->static_file('/version.json', {content_type => 'application/json'}); + + $r->page('/review', 'splinter.html'); + $r->page('/user_profile', 'user_profile.html'); + $r->page('/userprofile', 'user_profile.html'); + $r->page('/request_defer', 'request_defer.html'); + + $r->get('/__heartbeat__')->to('CGI#heartbeat_cgi'); + $r->get('/robots.txt')->to('CGI#robots_cgi'); + $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'); } 1; diff --git a/Bugzilla/Quantum/CGI.pm b/Bugzilla/Quantum/CGI.pm index 317c189cc..beb849687 100644 --- a/Bugzilla/Quantum/CGI.pm +++ b/Bugzilla/Quantum/CGI.pm @@ -25,146 +25,151 @@ our $C; my %SEEN; sub load_all { - my ( $class, $r ) = @_; + 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"); - } + 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) - - # the finally block calls cleanup. - $c->stash->{cleanup_guard}->dismiss; - Bugzilla->usage_mode(USAGE_MODE_BROWSER); - try { - Bugzilla->init_page(); - $inner->(); - } - catch { - die $_ unless _is_exit($_); - } - finally { - my $error = shift; - untie *STDOUT; - $c->finish if !$error || _is_exit($error); - Bugzilla->cleanup; - CGI::initialize_globals(); - }; + 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) + + # the finally block calls cleanup. + $c->stash->{cleanup_guard}->dismiss; + Bugzilla->usage_mode(USAGE_MODE_BROWSER); + try { + Bugzilla->init_page(); + $inner->(); + } + catch { + die $_ unless _is_exit($_); + } + finally { + my $error = shift; + untie *STDOUT; + $c->finish if !$error || _is_exit($error); + Bugzilla->cleanup; + CGI::initialize_globals(); }; + }; - no strict 'refs'; ## no critic (strict) - *{$name} = subname( $name, $wrapper ); - return 1; + 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 ($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 $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__, - ); + } + 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 ); + 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; + my ($name) = @_; + $name =~ s/\./_/s; + $name =~ s/\W+/_/gs; + return $name; } sub _is_exit { - my ($error) = @_; - return ref $error eq 'ARRAY' && $error->[0] eq "EXIT\n"; + my ($error) = @_; + return ref $error eq 'ARRAY' && $error->[0] eq "EXIT\n"; } 1; diff --git a/Bugzilla/Quantum/Home.pm b/Bugzilla/Quantum/Home.pm index b3f1ec1d1..48d5e47bd 100644 --- a/Bugzilla/Quantum/Home.pm +++ b/Bugzilla/Quantum/Home.pm @@ -13,14 +13,16 @@ use Try::Tiny; use Bugzilla::Constants; sub index { - my ($c) = @_; - $c->bugzilla->login(LOGIN_REQUIRED) or return; - try { - ThrowUserError('invalid_username', { login => 'batman' }) if $c->param('error'); - $c->render(handler => 'bugzilla', template => 'index'); - } catch { - $c->bugzilla->error_page($_); - }; + my ($c) = @_; + $c->bugzilla->login(LOGIN_REQUIRED) or return; + try { + ThrowUserError('invalid_username', {login => 'batman'}) + if $c->param('error'); + $c->render(handler => 'bugzilla', template => 'index'); + } + catch { + $c->bugzilla->error_page($_); + }; } 1; diff --git a/Bugzilla/Quantum/Plugin/BlockIP.pm b/Bugzilla/Quantum/Plugin/BlockIP.pm index 058ecbf64..974eebff9 100644 --- a/Bugzilla/Quantum/Plugin/BlockIP.pm +++ b/Bugzilla/Quantum/Plugin/BlockIP.pm @@ -9,35 +9,35 @@ use constant BLOCK_TIMEOUT => 60 * 60; my $MEMCACHED = Bugzilla::Memcached->new()->{memcached}; sub register { - my ( $self, $app, $conf ) = @_; + my ($self, $app, $conf) = @_; - $app->hook( before_routes => \&_before_routes ); - $app->helper( block_ip => \&_block_ip ); - $app->helper( unblock_ip => \&_unblock_ip ); + $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; + 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; + 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; - } + 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 index 8f4144589..f04b9c025 100644 --- a/Bugzilla/Quantum/Plugin/Glue.pm +++ b/Bugzilla/Quantum/Plugin/Glue.pm @@ -20,141 +20,145 @@ use Scalar::Util qw(blessed); use Scope::Guard; 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"; - } + 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"; + } + } + } + + $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); + $c->stash->{cleanup_guard} = Scope::Guard->new(\&Bugzilla::cleanup); + Bugzilla->usage_mode(USAGE_MODE_MOJO); } + ); - $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 ); - $c->stash->{cleanup_guard} = Scope::Guard->new( \&Bugzilla::cleanup ); - Bugzilla->usage_mode(USAGE_MODE_MOJO); - } - ); + $app->secrets([Bugzilla->localconfig->{side_wide_secret}]); - $app->secrets( [ Bugzilla->localconfig->{side_wide_secret} ] ); + $app->renderer->add_handler( + 'bugzilla' => sub { + my ($renderer, $c, $output, $options) = @_; - $app->renderer->add_handler( - 'bugzilla' => sub { - my ( $renderer, $c, $output, $options ) = @_; + my %params; - my %params; - # Helpers - foreach my $method (grep { m/^\w+\z/ } keys %{$renderer->helpers}) { - my $sub = $renderer->helpers->{$method}; - $params{$method} = sub { $c->$sub(@_) }; - } - # Stash values - $params{$_} = $c->stash->{$_} for grep { m/^\w+\z/ } keys %{$c->stash}; + # Helpers + foreach my $method (grep {m/^\w+\z/} keys %{$renderer->helpers}) { + my $sub = $renderer->helpers->{$method}; + $params{$method} = sub { $c->$sub(@_) }; + } - $params{self} = $params{c} = $c; + # Stash values + $params{$_} = $c->stash->{$_} for grep {m/^\w+\z/} keys %{$c->stash}; - my $name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format}; - my $template = Bugzilla->template; - $template->process( $name, \%params, $output ) - or die $template->error; - } - ); - $app->helper( - 'bugzilla.login_redirect_if_required' => sub { - my ( $c, $type ) = @_; - - if ( $type == LOGIN_REQUIRED ) { - $c->redirect_to('/login'); - return undef; - } - else { - return Bugzilla->user; - } - } - ); - $app->helper( - 'bugzilla.login' => sub { - my ( $c, $type ) = @_; - $type //= LOGIN_NORMAL; + $params{self} = $params{c} = $c; - return Bugzilla->user if Bugzilla->user->id; + my $name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format}; + my $template = Bugzilla->template; + $template->process($name, \%params, $output) or die $template->error; + } + ); + $app->helper( + 'bugzilla.login_redirect_if_required' => sub { + my ($c, $type) = @_; + + if ($type == LOGIN_REQUIRED) { + $c->redirect_to('/login'); + return undef; + } + else { + return Bugzilla->user; + } + } + ); + $app->helper( + 'bugzilla.login' => sub { + my ($c, $type) = @_; + $type //= LOGIN_NORMAL; - $type = LOGIN_REQUIRED if $c->param('GoAheadAndLogIn') || Bugzilla->params->{requirelogin}; + return Bugzilla->user if Bugzilla->user->id; - # Allow templates to know that we're in a page that always requires - # login. - if ( $type == LOGIN_REQUIRED ) { - Bugzilla->request_cache->{page_requires_login} = 1; - } + $type = LOGIN_REQUIRED + if $c->param('GoAheadAndLogIn') || Bugzilla->params->{requirelogin}; - my $login_cookie = $c->cookie("Bugzilla_logincookie"); - my $user_id = $c->cookie("Bugzilla_login"); - my $ip_addr = $c->tx->remote_address; + # Allow templates to know that we're in a page that always requires + # login. + if ($type == LOGIN_REQUIRED) { + Bugzilla->request_cache->{page_requires_login} = 1; + } - return $c->bugzilla->login_redirect_if_required($type) unless ( $login_cookie && $user_id ); + my $login_cookie = $c->cookie("Bugzilla_logincookie"); + my $user_id = $c->cookie("Bugzilla_login"); + my $ip_addr = $c->tx->remote_address; - my $db_cookie = Bugzilla->dbh->selectrow_array( - q{ + return $c->bugzilla->login_redirect_if_required($type) + unless ($login_cookie && $user_id); + + my $db_cookie = Bugzilla->dbh->selectrow_array( + q{ SELECT cookie FROM logincookies WHERE cookie = ? AND userid = ? AND (restrict_ipaddr = 0 OR ipaddr = ?) - }, - undef, - ( $login_cookie, $user_id, $ip_addr ) - ); - - if ( defined $db_cookie && secure_compare( $login_cookie, $db_cookie ) ) { - my $user = Bugzilla::User->check( { id => $user_id, cache => 1 } ); - - # If we logged in successfully, then update the lastused - # time on the login cookie - with_writable_database { - Bugzilla->dbh->do( q{ UPDATE logincookies SET lastused = NOW() WHERE cookie = ? }, - undef, $login_cookie ); - }; - Bugzilla->set_user($user); - return $user; - } - else { - return $c->bugzilla->login_redirect_if_required($type); - } - } - ); - $app->helper( - 'bugzilla.error_page' => sub { - my ( $c, $error ) = @_; - if ( blessed $error && $error->isa('Bugzilla::Error::Base') ) { - $c->render( - handler => 'bugzilla', - template => $error->template, - error => $error->message, - %{ $error->vars } - ); - } - else { - $c->reply->exception($error); - } - } - ); + }, undef, ($login_cookie, $user_id, $ip_addr) + ); + + if (defined $db_cookie && secure_compare($login_cookie, $db_cookie)) { + my $user = Bugzilla::User->check({id => $user_id, cache => 1}); + + # If we logged in successfully, then update the lastused + # time on the login cookie + with_writable_database { + Bugzilla->dbh->do( + q{ UPDATE logincookies SET lastused = NOW() WHERE cookie = ? }, + undef, $login_cookie); + }; + Bugzilla->set_user($user); + return $user; + } + else { + return $c->bugzilla->login_redirect_if_required($type); + } + } + ); + $app->helper( + 'bugzilla.error_page' => sub { + my ($c, $error) = @_; + if (blessed $error && $error->isa('Bugzilla::Error::Base')) { + $c->render( + handler => 'bugzilla', + template => $error->template, + error => $error->message, + %{$error->vars} + ); + } + else { + $c->reply->exception($error); + } + } + ); - $app->log( MojoX::Log::Log4perl::Tiny->new( logger => Log::Log4perl->get_logger( ref $app ) ) ); + $app->log(MojoX::Log::Log4perl::Tiny->new( + logger => Log::Log4perl->get_logger(ref $app) + )); } 1; diff --git a/Bugzilla/Quantum/Plugin/Helpers.pm b/Bugzilla/Quantum/Plugin/Helpers.pm index 0aedca338..72dd96cf9 100644 --- a/Bugzilla/Quantum/Plugin/Helpers.pm +++ b/Bugzilla/Quantum/Plugin/Helpers.pm @@ -12,53 +12,55 @@ use Bugzilla::Logging; use Carp; sub register { - my ( $self, $app, $conf ) = @_; + my ($self, $app, $conf) = @_; - $app->helper( - basic_auth => sub { - my ( $c, $realm, $auth_user, $auth_pass ) = @_; - my $req = $c->req; - my ( $user, $password ) = $req->url->to_abs->userinfo =~ /^([^:]+):(.*)/; + $app->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 ($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; - } + 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; - } - ); - $app->routes->add_shortcut( - static_file => sub { - my ($r, $path, $option) = @_; - my $file = $option->{file}; - my $content_type = $option->{content_type} // 'text/plain'; - unless ($file) { - $file = $path; - $file =~ s!^/!!; - } + return 1; + } + ); + $app->routes->add_shortcut( + static_file => sub { + my ($r, $path, $option) = @_; + my $file = $option->{file}; + my $content_type = $option->{content_type} // 'text/plain'; + unless ($file) { + $file = $path; + $file =~ s!^/!!; + } - return $r->get($path => sub { - my ($c) = @_; - $c->res->headers->content_type($content_type); - $c->reply->file( $c->app->home->child($file) ); - }) + return $r->get( + $path => sub { + my ($c) = @_; + $c->res->headers->content_type($content_type); + $c->reply->file($c->app->home->child($file)); } - ); - $app->routes->add_shortcut( - page => sub { - my ($r, $path, $id) = @_; + ); + } + ); + $app->routes->add_shortcut( + page => sub { + my ($r, $path, $id) = @_; - return $r->any($path)->to('CGI#page_cgi' => { id => $id }); - } - ); + return $r->any($path)->to('CGI#page_cgi' => {id => $id}); + } + ); } 1; diff --git a/Bugzilla/Quantum/Plugin/Hostage.pm b/Bugzilla/Quantum/Plugin/Hostage.pm index cbde7b5ee..63fad2be2 100644 --- a/Bugzilla/Quantum/Plugin/Hostage.pm +++ b/Bugzilla/Quantum/Plugin/Hostage.pm @@ -4,83 +4,82 @@ use Mojo::Base 'Mojolicious::Plugin'; use Bugzilla::Logging; sub _attachment_root { - my ($base) = @_; - return undef unless $base; - return $base =~ m{^https?://(?:bug)?\%bugid\%\.([a-zA-Z\.-]+)} - ? $1 - : undef; + my ($base) = @_; + return undef unless $base; + return $base =~ m{^https?://(?:bug)?\%bugid\%\.([a-zA-Z\.-]+)} ? $1 : undef; } sub _attachment_host_regex { - my ($base) = @_; - return undef unless $base; - my $val = $base; - $val =~ s{^https?://}{}s; - $val =~ s{/$}{}s; - my $regex = quotemeta $val; - $regex =~ s/\\\%bugid\\\%/\\d+/g; - return qr/^$regex$/s; + my ($base) = @_; + return undef unless $base; + my $val = $base; + $val =~ s{^https?://}{}s; + $val =~ s{/$}{}s; + my $regex = quotemeta $val; + $regex =~ s/\\\%bugid\\\%/\\d+/g; + return qr/^$regex$/s; } sub register { - my ( $self, $app, $conf ) = @_; + my ($self, $app, $conf) = @_; - $app->hook( before_routes => \&_before_routes ); + $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; - state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/; - state $attachment_base = Bugzilla->localconfig->{attachment_base}; - state $attachment_root = _attachment_root($attachment_base); - state $attachment_host_regex = _attachment_host_regex($attachment_base); + my ($c) = @_; + state $urlbase = Bugzilla->localconfig->{urlbase}; + state $urlbase_uri = URI->new($urlbase); + state $urlbase_host = $urlbase_uri->host; + state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/; + state $attachment_base = Bugzilla->localconfig->{attachment_base}; + state $attachment_root = _attachment_root($attachment_base); + state $attachment_host_regex = _attachment_host_regex($attachment_base); - my $stash = $c->stash; - my $req = $c->req; - my $url = $req->url->to_abs; + my $stash = $c->stash; + my $req = $c->req; + my $url = $req->url->to_abs; - return if $stash->{'mojo.static'}; + return if $stash->{'mojo.static'}; - my $hostname = $url->host; - return if $hostname eq $urlbase_host; + my $hostname = $url->host; + return if $hostname eq $urlbase_host; - my $path = $url->path; - return if $path eq '/__lbheartbeat__'; + my $path = $url->path; + return if $path eq '/__lbheartbeat__'; - if ( $attachment_base && $hostname eq $attachment_root ) { - DEBUG("redirecting to $urlbase because $hostname is $attachment_root"); - $c->redirect_to($urlbase); - return; - } - elsif ( $attachment_base && $hostname =~ $attachment_host_regex ) { - if ( $path =~ m{^/attachment\.cgi}s ) { - return; - } - else { - my $new_uri = $url->clone; - $new_uri->scheme( $urlbase_uri->scheme ); - $new_uri->host($urlbase_host); - DEBUG("redirecting to $new_uri because $hostname matches attachment regex"); - $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 ); - DEBUG("redirecting to $new_uri because $hostname includes bug id"); - $c->redirect_to($new_uri); - return; + if ($attachment_base && $hostname eq $attachment_root) { + DEBUG("redirecting to $urlbase because $hostname is $attachment_root"); + $c->redirect_to($urlbase); + return; + } + elsif ($attachment_base && $hostname =~ $attachment_host_regex) { + if ($path =~ m{^/attachment\.cgi}s) { + return; } else { - DEBUG("redirecting to $urlbase because $hostname doesn't make sense"); - $c->redirect_to($urlbase); - return; + my $new_uri = $url->clone; + $new_uri->scheme($urlbase_uri->scheme); + $new_uri->host($urlbase_host); + DEBUG( + "redirecting to $new_uri because $hostname matches attachment regex"); + $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); + DEBUG("redirecting to $new_uri because $hostname includes bug id"); + $c->redirect_to($new_uri); + return; + } + else { + DEBUG("redirecting to $urlbase because $hostname doesn't make sense"); + $c->redirect_to($urlbase); + return; + } } 1; diff --git a/Bugzilla/Quantum/SES.pm b/Bugzilla/Quantum/SES.pm index 03916075d..750da4e77 100644 --- a/Bugzilla/Quantum/SES.pm +++ b/Bugzilla/Quantum/SES.pm @@ -1,4 +1,5 @@ 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/. @@ -22,233 +23,231 @@ use Types::Standard qw( :all ); use Type::Utils; use Type::Params qw( compile ); -my $Invocant = class_type { class => __PACKAGE__ }; +my $Invocant = class_type {class => __PACKAGE__}; sub main { - my ($self) = @_; - try { - $self->_main; - } - catch { - FATAL("Error in SES Handler: ", $_); - $self->_respond( 400 => 'Bad Request' ); - }; + 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); + 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'); } + } - 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' ); - } + 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' ); + 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, +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; + 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 $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, - ], + 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"); - } + 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' ); + $self->_respond(200 => 'OK'); } -my $ComplainedRecipients = ArrayRef[Dict[ emailAddress => Str, slurpy Any ]]; -my $ComplaintNotification = Dict[ - complaint => Dict [ - complainedRecipients => $ComplainedRecipients, - complaintFeedbackType => Str, - slurpy Any, - ], +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); - } + 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' ); + $self->_respond(200 => 'OK'); } sub _respond { - my ( $self, $code, $message ) = @_; - $self->render(text => "$message\n", status => $code); + 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; + 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; + 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 index 4543d1b84..6ac803e96 100644 --- a/Bugzilla/Quantum/Static.pm +++ b/Bugzilla/Quantum/Static.pm @@ -16,15 +16,15 @@ my $LEGACY_RE = qr{ }xs; sub file { - my ( $self, $rel ) = @_; + 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); - } + 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 index 9cf19992c..10be0b664 100644 --- a/Bugzilla/Quantum/Stdout.pm +++ b/Bugzilla/Quantum/Stdout.pm @@ -13,48 +13,42 @@ use Bugzilla::Logging; use Encode; use English qw(-no_match_vars); -has 'controller' => ( - is => 'ro', - required => 1, -); +has 'controller' => (is => 'ro', required => 1,); -has '_encoding' => ( - is => 'rw', - default => '', -); +has '_encoding' => (is => 'rw', default => '',); sub TIEHANDLE { ## no critic (unpack) - my $class = shift; + my $class = shift; - return $class->new(@_); + return $class->new(@_); } sub PRINTF { ## no critic (unpack) - my $self = shift; - $self->PRINT( sprintf @_ ); + 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 // '' ) ); + 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'); - } + my ($self, $mode) = @_; + if ($mode) { + if ($mode eq ':bytes' or $mode eq ':raw') { + $self->_encoding(''); + } + elsif ($mode eq ':utf8') { + $self->_encoding('utf8'); } + } } 1; |