summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2018-08-04 18:24:15 +0200
committerDylan William Hardison <dylan@hardison.net>2018-08-04 18:24:15 +0200
commitf44392e8cdbea85ac308b2472f813ee605ebae4b (patch)
tree6e7adaf99a0e5a43eb1bf5a0d673d86b60f34f99 /Bugzilla
parent5be3a7fd0061aa0bc3059e09079741873b9b833f (diff)
parent4528b21bc922f8b1e0ba8581d230a492aa43c9cf (diff)
downloadbugzilla-f44392e8cdbea85ac308b2472f813ee605ebae4b.tar.gz
bugzilla-f44392e8cdbea85ac308b2472f813ee605ebae4b.tar.xz
Merge branch 'mojo-poc'
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/CGI.pm15
-rw-r--r--Bugzilla/Config/Common.pm14
-rw-r--r--Bugzilla/Config/General.pm11
-rw-r--r--Bugzilla/Constants.pm6
-rw-r--r--Bugzilla/DB.pm8
-rw-r--r--Bugzilla/DB/Mysql.pm116
-rw-r--r--Bugzilla/DB/Schema/Mysql.pm27
-rw-r--r--Bugzilla/DaemonControl.pm6
-rw-r--r--Bugzilla/Install/DB.pm12
-rw-r--r--Bugzilla/Memcached.pm2
-rw-r--r--Bugzilla/PatchReader/Raw.pm1
-rw-r--r--Bugzilla/Quantum.pm191
-rw-r--r--Bugzilla/Quantum/CGI.pm97
-rw-r--r--Bugzilla/Quantum/Plugin/BasicAuth.pm40
-rw-r--r--Bugzilla/Quantum/Plugin/BlockIP.pm24
-rw-r--r--Bugzilla/Quantum/Plugin/Glue.pm41
-rw-r--r--Bugzilla/Quantum/SES.pm89
-rw-r--r--Bugzilla/Quantum/Static.pm4
-rw-r--r--Bugzilla/Quantum/Stdout.pm41
-rw-r--r--Bugzilla/Template.pm12
-rw-r--r--Bugzilla/User.pm39
-rw-r--r--Bugzilla/Util.pm4
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm9
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/User.pm5
-rw-r--r--Bugzilla/WebService/User.pm66
25 files changed, 507 insertions, 373 deletions
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index e50b394fb..9ac01c71e 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -39,11 +39,13 @@ sub DEFAULT_CSP {
script_src => [ 'self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com' ],
frame_src => [ 'none', ],
worker_src => [ 'none', ],
- img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ],
+ img_src => [ 'self', 'https://secure.gravatar.com' ],
style_src => [ 'self', 'unsafe-inline' ],
object_src => [ 'none' ],
connect_src => [
'self',
+ # This is for extensions/GoogleAnalytics using beacon or XHR
+ 'https://www.google-analytics.com',
# This is from extensions/OrangeFactor/web/js/orange_factor.js
'https://treeherder.mozilla.org/api/failurecount/',
],
@@ -70,9 +72,11 @@ sub SHOW_BUG_MODAL_CSP {
my ($bug_id) = @_;
my %policy = (
script_src => ['self', 'nonce', 'unsafe-inline', 'unsafe-eval', 'https://www.google-analytics.com' ],
- img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ],
+ img_src => [ 'self', 'https://secure.gravatar.com' ],
connect_src => [
'self',
+ # This is for extensions/GoogleAnalytics using beacon or XHR
+ 'https://www.google-analytics.com',
# This is from extensions/OrangeFactor/web/js/orange_factor.js
'https://treeherder.mozilla.org/api/failurecount/',
],
@@ -137,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;
}
@@ -147,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();
}
@@ -611,7 +617,7 @@ sub header {
return '';
}
else {
- return $headers;
+ LOGDIE("Bugzilla::CGI->header() should only be called from inside Bugzilla::Quantum::CGI!");
}
}
@@ -729,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 {
@@ -777,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;
}
@@ -798,6 +806,7 @@ 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);
exit;
}
diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm
index fabf7c880..24b636099 100644
--- a/Bugzilla/Config/Common.pm
+++ b/Bugzilla/Config/Common.pm
@@ -83,14 +83,16 @@ sub check_email {
sub check_utf8 {
- my $utf8 = shift;
+ my ($utf8, $entry) = @_;
- # You cannot turn off the UTF-8 parameter if you've already converted
- # your tables to utf-8.
- my $dbh = Bugzilla->dbh;
- if ( $dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8 ) {
- return "You cannot disable UTF-8 support, because your MySQL database" . " is encoded in UTF-8";
+ # You cannot turn off the UTF-8 parameter.
+ if ( !$utf8 ) {
+ return "You cannot disable UTF-8 support.";
}
+ elsif ($entry eq 'utf8mb4' && $utf8 ne 'utf8mb4') {
+ return "You cannot disable UTF8-MB4 support.";
+ }
+
return "";
}
diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm
index 7e1c812c1..fa7cf2d08 100644
--- a/Bugzilla/Config/General.pm
+++ b/Bugzilla/Config/General.pm
@@ -41,18 +41,13 @@ use constant get_param_list => (
{
name => 'utf8',
- type => 'b',
- default => '0',
+ type => 's',
+ choices => [ '1', 'utf8', 'utf8mb4' ],
+ default => 'utf8',
checker => \&check_utf8
},
{
- name => 'shutdownhtml',
- type => 'l',
- default => ''
- },
-
- {
name => 'announcehtml',
type => 'l',
default => ''
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index d71ec25ee..34e4a4cfe 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -647,11 +647,7 @@ sub _bz_locations {
# directory under both mod_cgi and mod_perl. We call dirname twice
# to get the name of the directory above the "Bugzilla/" directory.
#
- # Calling dirname twice like that won't work on VMS or AmigaOS
- # but I doubt anybody runs Bugzilla on those.
- #
- # On mod_cgi this will be a relative path. On mod_perl it will be an
- # absolute path.
+ # Always use an absolute path, based on the location of this file.
my $libpath = realpath(dirname(dirname(__FILE__)));
# We have to detaint $libpath, but we can't use Bugzilla::Util here.
$libpath =~ /(.*)/;
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index ec0f058b9..f07bb7183 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -348,6 +348,14 @@ sub import {
$Exporter::ExportLevel-- if $is_exporter;
}
+sub sql_prefix_match {
+ my ($self, $column, $str) = @_;
+ my $must_escape = $str =~ s/([_%!])/!$1/g;
+ my $escape = $must_escape ? q/ESCAPE '!'/ : '';
+ my $quoted_str = $self->quote("$str%");
+ return "$column LIKE $quoted_str $escape";
+}
+
sub sql_istrcmp {
my ($self, $left, $right, $op) = @_;
$op ||= "=";
diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm
index d0b9724eb..4dd2620d3 100644
--- a/Bugzilla/DB/Mysql.pm
+++ b/Bugzilla/DB/Mysql.pm
@@ -32,8 +32,9 @@ use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::DB::Schema::Mysql;
-use List::Util qw(max);
+use List::Util qw(max any);
use Text::ParseWords;
+use Carp;
# This is how many comments of MAX_COMMENT_LENGTH we expect on a single bug.
# In reality, you could have a LOT more comments than this, because
@@ -52,9 +53,7 @@ sub BUILDARGS {
$dsn .= ";port=$port" if $port;
$dsn .= ";mysql_socket=$sock" if $sock;
- my %attrs = (
- mysql_enable_utf8 => Bugzilla->params->{'utf8'},
- );
+ my %attrs = ( mysql_enable_utf8 => 1 );
return { dsn => $dsn, user => $user, pass => $pass, attrs => \%attrs };
}
@@ -64,7 +63,9 @@ sub on_dbi_connected {
# This makes sure that if the tables are encoded as UTF-8, we
# return their data correctly.
- $dbh->do("SET NAMES utf8") if Bugzilla->params->{'utf8'};
+ my $charset = $class->utf8_charset;
+ my $collate = $class->utf8_collate;
+ $dbh->do("SET NAMES $charset COLLATE $collate");
# Bug 321645 - disable MySQL strict mode, if set
my ($var, $sql_mode) = $dbh->selectrow_array(
@@ -310,6 +311,26 @@ sub bz_setup_database {
die install_string('mysql_innodb_disabled');
}
+ if ($self->utf8_charset eq 'utf8mb4') {
+ my %global = map { @$_ } @{ $self->selectall_arrayref(q(SHOW GLOBAL VARIABLES LIKE 'innodb_%')) };
+ my $utf8mb4_supported
+ = $global{innodb_file_format} eq 'Barracuda'
+ && $global{innodb_file_per_table} eq 'ON'
+ && $global{innodb_large_prefix} eq 'ON';
+
+ die install_string('mysql_innodb_settings') unless $utf8mb4_supported;
+
+ my $tables = $self->selectall_arrayref('SHOW TABLE STATUS');
+ foreach my $table (@$tables) {
+ my ($table, undef, undef, $row_format) = @$table;
+ my $new_row_format = $self->default_row_format($table);
+ next if $new_row_format =~ /compact/i;
+ if (lc($new_row_format) ne lc($row_format)) {
+ print install_string('mysql_row_format_conversion', { table => $table, format => $new_row_format }), "\n";
+ $self->do(sprintf 'ALTER TABLE %s ROW_FORMAT=%s', $table, $new_row_format);
+ }
+ }
+ }
my ($sd_index_deleted, $longdescs_index_deleted);
my @tables = $self->bz_table_list_real();
@@ -345,9 +366,6 @@ sub bz_setup_database {
'SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ? AND ENGINE = ?',
undef, $db_name, 'MyISAM');
- foreach my $should_be_myisam (Bugzilla::DB::Schema::Mysql::MYISAM_TABLES) {
- @$myisam_tables = grep { $_ ne $should_be_myisam } @$myisam_tables;
- }
if (scalar @$myisam_tables) {
print "Bugzilla now uses the InnoDB storage engine in MySQL for",
@@ -520,9 +538,7 @@ sub bz_setup_database {
# This kind of situation happens when people create the database
# themselves, and if we don't do this they will get the big
# scary WARNING statement about conversion to UTF8.
- if ( !$self->bz_db_is_utf8 && !@tables
- && (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
- {
+ unless ( $self->bz_db_is_utf8 ) {
$self->_alter_db_charset_to_utf8();
}
@@ -559,11 +575,13 @@ sub bz_setup_database {
# the table charsets.
#
# TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
+ my $charset = $self->utf8_charset;
+ my $collate = $self->utf8_collate;
my $non_utf8_tables = $self->selectrow_array(
"SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ? AND TABLE_COLLATION IS NOT NULL
- AND TABLE_COLLATION NOT LIKE 'utf8%'
- LIMIT 1", undef, $db_name);
+ AND TABLE_COLLATION != ?
+ LIMIT 1", undef, $db_name, $collate);
if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
print "\n", install_string('mysql_utf8_conversion');
@@ -580,8 +598,7 @@ sub bz_setup_database {
}
}
- print "Converting table storage format to UTF-8. This may take a",
- " while.\n";
+ print "Converting table storage format to $charset (collate $collate). This may take a while.\n";
foreach my $table ($self->bz_table_list_real) {
my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
$info_sth->execute();
@@ -594,11 +611,11 @@ sub bz_setup_database {
# If this particular column isn't stored in utf-8
if ($column->{Collation}
&& $column->{Collation} ne 'NULL'
- && $column->{Collation} !~ /utf8/)
+ && $column->{Collation} ne $collate)
{
my $name = $column->{Field};
- print "$table.$name needs to be converted to UTF-8...\n";
+ print "$table.$name needs to be converted to $charset (collate $collate)...\n";
# These will be automatically re-created at the end
# of checksetup.
@@ -618,7 +635,7 @@ sub bz_setup_database {
my ($binary, $utf8) = ($sql_def, $sql_def);
my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
$binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
- $utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET utf8/;
+ $utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET $charset COLLATE $collate/;
push(@binary_sql, "MODIFY COLUMN $name $binary");
push(@utf8_sql, "MODIFY COLUMN $name $utf8");
}
@@ -639,7 +656,7 @@ sub bz_setup_database {
print "Converting the $table table to UTF-8...\n";
my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
my $utf = "ALTER TABLE $table " . join(', ', @utf8_sql,
- 'DEFAULT CHARACTER SET utf8');
+ "DEFAULT CHARACTER SET $charset COLLATE $collate");
$self->do($bin);
$self->do($utf);
@@ -649,7 +666,7 @@ sub bz_setup_database {
}
}
else {
- $self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
+ $self->do("ALTER TABLE $table DEFAULT CHARACTER SET $charset COLLATE $collate");
}
} # foreach my $table (@tables)
@@ -660,7 +677,7 @@ sub bz_setup_database {
# a mysqldump.) So we have this change outside of the above block,
# so that it just happens silently if no actual *table* conversion
# needs to happen.
- if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
+ unless ($self->bz_db_is_utf8) {
$self->_alter_db_charset_to_utf8();
}
@@ -739,18 +756,69 @@ sub _fix_defaults {
}
}
+sub utf8_charset {
+ return 'utf8' unless Bugzilla->params->{'utf8'};
+ return Bugzilla->params->{'utf8'} eq 'utf8mb4' ? 'utf8mb4' : 'utf8';
+}
+
+sub utf8_collate {
+ my $charset = utf8_charset();
+ if ($charset eq 'utf8') {
+ return 'utf8_general_ci';
+ }
+ elsif ($charset eq 'utf8mb4') {
+ return 'utf8mb4_unicode_520_ci';
+ }
+ else {
+ croak "invalid charset: $charset";
+ }
+}
+
+sub default_row_format {
+ my ($class, $table) = @_;
+ my $charset = utf8_charset();
+ if ($charset eq 'utf8') {
+ return 'Compact';
+ }
+ elsif ($charset eq 'utf8mb4') {
+ my @no_compress = qw(
+ bug_user_last_visit
+ cc
+ email_rates
+ logincookies
+ token_data
+ tokens
+ ts_error
+ ts_exitstatus
+ ts_funcmap
+ ts_job
+ ts_note
+ user_request_log
+ votes
+ );
+ return 'Dynamic' if any { $table eq $_ } @no_compress;
+ return 'Compressed';
+ }
+ else {
+ croak "invalid charset: $charset";
+ }
+}
+
sub _alter_db_charset_to_utf8 {
my $self = shift;
my $db_name = Bugzilla->localconfig->{db_name};
- $self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
+ my $charset = $self->utf8_charset;
+ my $collate = $self->utf8_collate;
+ $self->do("ALTER DATABASE $db_name CHARACTER SET $charset COLLATE $collate");
}
sub bz_db_is_utf8 {
my $self = shift;
- my $db_collation = $self->selectrow_arrayref(
+ my $db_charset = $self->selectrow_arrayref(
"SHOW VARIABLES LIKE 'character_set_database'");
# First column holds the variable name, second column holds the value.
- return $db_collation->[1] =~ /utf8/ ? 1 : 0;
+ my $charset = $self->utf8_charset;
+ return $db_charset->[1] eq $charset ? 1 : 0;
}
diff --git a/Bugzilla/DB/Schema/Mysql.pm b/Bugzilla/DB/Schema/Mysql.pm
index 5893c6a80..79814140a 100644
--- a/Bugzilla/DB/Schema/Mysql.pm
+++ b/Bugzilla/DB/Schema/Mysql.pm
@@ -76,8 +76,6 @@ use constant REVERSE_MAPPING => {
# as in their db-specific version, so no reverse mapping is needed.
};
-use constant MYISAM_TABLES => qw();
-
#------------------------------------------------------------------------------
sub _initialize {
@@ -120,16 +118,18 @@ sub _initialize {
} #eosub--_initialize
#------------------------------------------------------------------------------
sub _get_create_table_ddl {
- # Extend superclass method to specify the MYISAM storage engine.
# Returns a "create table" SQL statement.
-
my($self, $table) = @_;
-
- my $charset = "CHARACTER SET utf8";
- my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
- return($self->SUPER::_get_create_table_ddl($table)
- . " ENGINE = $type $charset");
-
+ my $charset = Bugzilla::DB::Mysql->utf8_charset;
+ my $collate = Bugzilla::DB::Mysql->utf8_collate;
+ my $row_format = Bugzilla::DB::Mysql->default_row_format($table);
+ my @parts = (
+ $self->SUPER::_get_create_table_ddl($table),
+ 'ENGINE = InnoDB',
+ "CHARACTER SET $charset COLLATE $collate",
+ "ROW_FORMAT=$row_format",
+ );
+ return join(' ', @parts);
} #eosub--_get_create_table_ddl
#------------------------------------------------------------------------------
sub _get_create_index_ddl {
@@ -153,10 +153,9 @@ sub get_create_database_sql {
my ($self, $name) = @_;
# We only create as utf8 if we have no params (meaning we're doing
# a new installation) or if the utf8 param is on.
- my $create_utf8 = Bugzilla->params->{'utf8'}
- || !defined Bugzilla->params->{'utf8'};
- my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
- return ("CREATE DATABASE $name $charset");
+ my $charset = Bugzilla::DB::Mysql->utf8_charset;
+ my $collate = Bugzilla::DB::Mysql->utf8_collate;
+ return ("CREATE DATABASE $name CHARACTER SET $charset COLLATE $collate");
}
# MySQL has a simpler ALTER TABLE syntax than ANSI.
diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm
index d0d6af8f7..5cb32973f 100644
--- a/Bugzilla/DaemonControl.pm
+++ b/Bugzilla/DaemonControl.pm
@@ -41,14 +41,14 @@ our %EXPORT_TAGS = (
utils => [qw(catch_signal on_exception on_finish)],
);
-my $BUGZILLA_DIR = realpath(bz_locations->{cgi_path});
+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 $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_COMMAND = (
+my %HTTP_BACKENDS = (
hypnotoad => [ $HYPNOTOAD_BIN, $BUGZILLA_BIN, '-f' ],
simple => [ $BUGZILLA_BIN, 'daemon' ],
);
@@ -108,7 +108,7 @@ sub run_httpd {
$ENV{BUGZILLA_HTTPD_ARGS} = encode_json(\@args);
$ENV{PERL5LIB} = join(':', @PERL5LIB);
my $backend = $ENV{HTTP_BACKEND} // 'hypnotoad';
- my $command = $HTTP_COMMAND{ $backend };
+ my $command = $HTTP_BACKENDS{ $backend };
exec @$command
or die "failed to exec $command->[0] $!";
},
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 86edf8a30..8b3d4b8cc 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -773,6 +773,7 @@ sub update_table_definitions {
$dbh->bz_add_index('profiles', 'profiles_realname_ft_idx',
{TYPE => 'FULLTEXT', FIELDS => ['realname']});
+ _migrate_nicknames();
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
@@ -3920,6 +3921,17 @@ sub _migrate_group_owners {
$dbh->do('UPDATE groups SET owner_user_id = ?', undef, $nobody->id);
}
+sub _migrate_nicknames {
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare('SELECT userid FROM profiles WHERE realname LIKE "%:%" AND is_enabled = 1 AND NOT nickname');
+ $sth->execute();
+ while (my ($user_id) = $sth->fetchrow_array) {
+ my $user = Bugzilla::User->new($user_id);
+ $user->set_name($user->name);
+ $user->update();
+ }
+}
+
sub _migrate_preference_categories {
my $dbh = Bugzilla->dbh;
return if $dbh->bz_column_info('setting', 'category');
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/PatchReader/Raw.pm b/Bugzilla/PatchReader/Raw.pm
index 0a8387a15..bb5a6cefd 100644
--- a/Bugzilla/PatchReader/Raw.pm
+++ b/Bugzilla/PatchReader/Raw.pm
@@ -16,6 +16,7 @@ package Bugzilla::PatchReader::Raw;
use 5.10.1;
use strict;
use warnings;
+no warnings 'utf8';
use Bugzilla::PatchReader::Base;
diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm
index 2519e23ad..135ff94a9 100644
--- a/Bugzilla/Quantum.pm
+++ b/Bugzilla/Quantum.pm
@@ -8,21 +8,21 @@
package Bugzilla::Quantum;
use Mojo::Base 'Mojolicious';
-use CGI::Compile; # Needed for its exit() overload
-use Bugzilla::Logging;
-use Bugzilla::Quantum::Template;
-use Bugzilla::Quantum::CGI;
-use Bugzilla::Quantum::Static;
+# Needed for its exit() overload, must happen early in execution.
+use CGI::Compile;
-use Bugzilla ();
-use Bugzilla::Constants qw(bz_locations);
+use Bugzilla ();
use Bugzilla::BugMail ();
-use Bugzilla::CGI ();
-use Bugzilla::Extension ();
+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 Bugzilla::Util ();
use Cwd qw(realpath);
-
use MojoX::Log::Log4perl::Tiny;
has 'static' => sub { Bugzilla::Quantum::Static->new };
@@ -30,9 +30,11 @@ 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');
if ( $self->mode ne 'development' ) {
$self->hook(
@@ -45,14 +47,15 @@ sub startup {
my $r = $self->routes;
Bugzilla::Quantum::CGI->load_all($r);
- Bugzilla::Quantum::CGI->load_one('bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi');
+ Bugzilla::Quantum::CGI->load_one( 'bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi' );
$r->any('/')->to('CGI#index_cgi');
$r->any('/rest')->to('CGI#rest_cgi');
- $r->any('/rest.cgi/*PATH_INFP')->to('CGI#rest_cgi' => { PATH_INFO => '' });
- $r->any('/rest/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' });
+ $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('/bug/:id')->to('CGI#show_bug_cgi');
$r->any('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi');
+
$r->get(
'/__lbheartbeat__' => sub {
my $c = shift;
@@ -60,140 +63,42 @@ sub startup {
},
);
- $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');
- $r->any('/:REWRITE_itrequest' => [REWRITE_itrequest => qr{form[\.:]itrequest}])->to(
- 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'itrequest' }
- );
- $r->any('/:REWRITE_mozlist' => [REWRITE_mozlist => qr{form[\.:]mozlist}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'mozlist'}
- );
- $r->any('/:REWRITE_poweredby' => [REWRITE_poweredby => qr{form[\.:]poweredby}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'poweredby'}
- );
- $r->any('/:REWRITE_presentation' => [REWRITE_presentation => qr{form[\.:]presentation}])->to(
- 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'presentation'}
- );
- $r->any('/:REWRITE_trademark' => [REWRITE_trademark => qr{form[\.:]trademark}])->to(
- 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'trademark'}
- );
- $r->any('/:REWRITE_recoverykey' => [REWRITE_recoverykey => qr{form[\.:]recoverykey}])->to(
- 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'recoverykey'}
- );
- $r->any('/:REWRITE_legal' => [REWRITE_legal => qr{form[\.:]legal}])->to(
- 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'legal' },
- );
- $r->any('/:REWRITE_recruiting' => [REWRITE_recruiting => qr{form[\.:]recruiting}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'recruiting'}
- );
- $r->any('/:REWRITE_intern' => [REWRITE_intern => qr{form[\.:]intern}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'intern'}
- );
- $r->any('/:REWRITE_mozpr' => [REWRITE_mozpr => qr{form[\.:]mozpr}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla PR', 'format' => 'mozpr' },
- );
- $r->any('/:REWRITE_reps_mentorship' => [REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'mozreps' },
- );
- $r->any('/:REWRITE_reps_budget' => [REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-budget'}
- );
- $r->any('/:REWRITE_reps_swag' => [REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-swag'}
- );
- $r->any('/:REWRITE_reps_it' => [REWRITE_reps_it => qr{form[\.:]reps[\.:]it}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-it'}
- );
- $r->any('/:REWRITE_reps_payment' => [REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment}])->to(
- 'CGI#page_cgi' => {'id' => 'remo-form-payment.html'}
- );
- $r->any('/:REWRITE_csa_discourse' => [REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse'}
- );
- $r->any('/:REWRITE_employee_incident' => [REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'employee-incident'}
- );
- $r->any('/:REWRITE_brownbag' => [REWRITE_brownbag => qr{form[\.:]brownbag}])->to(
- 'CGI#https_air_mozilla_org_requests' => {}
- );
- $r->any('/:REWRITE_finance' => [REWRITE_finance => qr{form[\.:]finance}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Finance','format' => 'finance'}
- );
- $r->any('/:REWRITE_moz_project_review' => [REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org','format' => 'moz-project-review'}
- );
- $r->any('/:REWRITE_docs' => [REWRITE_docs => qr{form[\.:]docs?}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Developer Documentation','format' => 'doc'}
- );
- $r->any('/:REWRITE_mdn' => [REWRITE_mdn => qr{form[\.:]mdn?}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'mdn','product' => 'developer.mozilla.org'}
- );
- $r->any('/:REWRITE_swag_gear' => [REWRITE_swag_gear => qr{form[\.:](swag|gear)}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'swag','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_costume' => [REWRITE_costume => qr{form[\.:]costume}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Marketing','format' => 'costume'}
- );
- $r->any('/:REWRITE_ipp' => [REWRITE_ipp => qr{form[\.:]ipp}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Internet Public Policy','format' => 'ipp'}
- );
- $r->any('/:REWRITE_creative' => [REWRITE_creative => qr{form[\.:]creative}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'creative','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_user_engagement' => [REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'user-engagement','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_dev_engagement_event' => [REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Developer Engagement','format' => 'dev-engagement-event'}
- );
- $r->any('/:REWRITE_mobile_compat' => [REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Tech Evangelism','format' => 'mobile-compat'}
- );
- $r->any('/:REWRITE_web_bounty' => [REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'web-bounty','product' => 'mozilla.org'}
- );
- $r->any('/:REWRITE_automative' => [REWRITE_automative => qr{form[\.:]automative}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Testing','format' => 'automative'}
- );
- $r->any('/:REWRITE_comm_newsletter' => [REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'comm-newsletter','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_screen_share_whitelist' => [REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'screen-share-whitelist','product' => 'Firefox'}
- );
- $r->any('/:REWRITE_data_compliance' => [REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Data Compliance','format' => 'data-compliance'}
- );
- $r->any('/:REWRITE_fsa_budget' => [REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'FSA','format' => 'fsa-budget'}
- );
- $r->any('/:REWRITE_triage_request' => [REWRITE_triage_request => qr{form[\.:]triage[\.\-]request}])->to(
- 'CGI#page_cgi' => {'id' => 'triage_request.html'}
- );
- $r->any('/:REWRITE_crm_CRM' => [REWRITE_crm_CRM => qr{form[\.:](crm|CRM)}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'crm','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_nda' => [REWRITE_nda => qr{form[\.:]nda}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Legal','format' => 'nda'}
- );
- $r->any('/:REWRITE_name_clearance' => [REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'name-clearance','product' => 'Legal'}
+ $r->get(
+ '/__version__' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('version.json') );
+ },
);
- $r->any('/:REWRITE_shield_studies' => [REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Shield','format' => 'shield-studies'}
+
+ $r->get(
+ '/version.json' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('version.json') );
+ },
);
- $r->any('/:REWRITE_client_bounty' => [REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Firefox','format' => 'client-bounty'}
+
+ $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
index 16c733686..0a74f1ee5 100644
--- a/Bugzilla/Quantum/CGI.pm
+++ b/Bugzilla/Quantum/CGI.pm
@@ -9,57 +9,55 @@ package Bugzilla::Quantum::CGI;
use Mojo::Base 'Mojolicious::Controller';
use CGI::Compile;
-use Bugzilla::Constants qw(bz_locations);
-use Bugzilla::Quantum::Stdout;
-use File::Slurper qw(read_text);
-use File::Spec::Functions qw(catfile);
-use Sub::Name;
-use Sub::Quote 2.005000;
use Try::Tiny;
use Taint::Util qw(untaint);
-use Socket qw(AF_INET inet_aton);
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) = @_;
+ my ( $class, $r ) = @_;
- foreach my $file (glob '*.cgi') {
- my $name = _file_to_method($file);
- $class->load_one($name, $file);
+ 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 ) );
+ 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,
+ 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;
- my $stdout = '';
- local $C = $c;
- local %ENV = $c->_ENV($file);
- local *STDIN; ## no critic (local)
+ my $stdin = $c->_STDIN;
+ local $C = $c;
+ local %ENV = $c->_ENV($file);
local $CGI::Compile::USE_REAL_EXIT = 0;
- local $PROGRAM_NAME = $file;
+ 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)
+ tie *STDOUT, 'Bugzilla::Quantum::Stdout', controller => $c; ## no critic (tie)
try {
Bugzilla->init_page();
$inner->();
@@ -70,66 +68,68 @@ sub load_one {
finally {
untie *STDOUT;
$c->finish;
- Bugzilla->_cleanup; ## no critic (private)
+ Bugzilla->cleanup;
CGI::initialize_globals();
};
};
- no strict 'refs'; ## no critic (strict)
- *{$name} = subname($name, $wrapper);
+ 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 ( $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 => '' );
+ my %env_headers = ( HTTP_COOKIE => '', HTTP_REFERER => '' );
for my $name ( @{ $headers->names } ) {
my $key = uc "http_$name";
- $key =~ s!\W!_!g;
+ $key =~ s/\W/_/g;
$env_headers{$key} = $headers->header($name);
}
my $remote_user;
- if ( my $userinfo = $c->req->url->to_abs->userinfo ) {
+ 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->param('PATH_INFO');
+ my $path_info = $c->stash->{'mojo.captures'}{'PATH_INFO'};
my %captures = %{ $c->stash->{'mojo.captures'} // {} };
- foreach my $key (keys %captures) {
- if ($key eq 'action' || $key eq 'PATH_INFO' || $key =~ /^REWRITE_/) {
+ 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);
+ $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 ? 'YES' : 'NO',
+ HTTPS => $req->is_secure ? 'on' : 'off',
%env_headers,
- QUERY_STRING => $cgi_query->to_string,
- PATH_INFO => $path_info ? "/$path_info" : '',
- REMOTE_ADDR => $tx->remote_address,
- REMOTE_HOST => gethostbyaddr( inet_aton( $tx->remote_address || '127.0.0.1' ), AF_INET ) || '',
- REMOTE_PORT => $tx->remote_port,
- REMOTE_USER => $remote_user || '',
+ 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 => "/$script_name",
+ 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_PROTOCOL => $req->is_secure ? 'HTTPS' : 'HTTP', # TODO: Version is missing
SERVER_SOFTWARE => __PACKAGE__,
);
}
@@ -157,5 +157,4 @@ sub _file_to_method {
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
index fbfffad66..058ecbf64 100644
--- a/Bugzilla/Quantum/Plugin/BlockIP.pm
+++ b/Bugzilla/Quantum/Plugin/BlockIP.pm
@@ -4,38 +4,38 @@ use Mojo::Base 'Mojolicious::Plugin';
use Bugzilla::Memcached;
-use constant BLOCK_TIMEOUT => 60*60;
+use constant BLOCK_TIMEOUT => 60 * 60;
-my $MEMCACHED = Bugzilla::Memcached->_new()->{memcached};
+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);
+ $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) = @_;
+ my ( $class, $ip ) = @_;
$MEMCACHED->delete("block_ip:$ip") if $MEMCACHED;
}
sub _before_routes {
- my ( $c ) = @_;
+ my ($c) = @_;
return if $c->stash->{'mojo.static'};
my $ip = $c->tx->remote_address;
- if ($MEMCACHED && $MEMCACHED->get("block_ip:$ip")) {
+ 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->res->message('Too Many Requests');
+ $c->res->body('Too Many Requests');
$c->finish;
}
}
diff --git a/Bugzilla/Quantum/Plugin/Glue.pm b/Bugzilla/Quantum/Plugin/Glue.pm
index 54a360003..ea21429bd 100644
--- a/Bugzilla/Quantum/Plugin/Glue.pm
+++ b/Bugzilla/Quantum/Plugin/Glue.pm
@@ -11,7 +11,6 @@ use Mojo::Base 'Mojolicious::Plugin';
use Try::Tiny;
use Bugzilla::Constants;
-use Bugzilla::Quantum::Template;
use Bugzilla::Logging;
use Bugzilla::RNG ();
use JSON::MaybeXS qw(decode_json);
@@ -20,10 +19,10 @@ sub register {
my ( $self, $app, $conf ) = @_;
my %D;
- if ($ENV{BUGZILLA_HTTPD_ARGS}) {
- my $args = decode_json($ENV{BUGZILLA_HTTPD_ARGS});
+ if ( $ENV{BUGZILLA_HTTPD_ARGS} ) {
+ my $args = decode_json( $ENV{BUGZILLA_HTTPD_ARGS} );
foreach my $arg (@$args) {
- if ($arg =~ /^-D(\w+)$/) {
+ if ( $arg =~ /^-D(\w+)$/ ) {
$D{$1} = 1;
}
else {
@@ -35,6 +34,7 @@ sub register {
# hypnotoad is weird and doesn't look for MOJO_LISTEN itself.
$app->config(
hypnotoad => {
+ proxy => 1,
listen => [ $ENV{MOJO_LISTEN} ],
},
);
@@ -49,30 +49,32 @@ sub register {
sub {
Bugzilla::RNG::srand();
srand();
- eval { Bugzilla->dbh->ping };
+ try { Bugzilla->dbh->ping };
}
);
$app->hook(
before_dispatch => sub {
my ($c) = @_;
- if ($D{HTTPD_IN_SUBDIR}) {
+ if ( $D{HTTPD_IN_SUBDIR} ) {
my $path = $c->req->url->path;
- $path =~ s{^/bmo}{}s;
- $c->req->url->path($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);
+ Log::Log4perl::MDC->put( request_id => $c->req->request_id );
}
);
Bugzilla::Extension->load_all();
- if ($app->mode ne 'development') {
+ if ( $app->mode ne 'development' ) {
Bugzilla->preload_features();
- DEBUG("preloading templates");
+ DEBUG('preloading templates');
Bugzilla->preload_templates();
- DEBUG("done preloading templates");
+ DEBUG('done preloading templates');
}
- $app->secrets([Bugzilla->localconfig->{side_wide_secret}]);
+ $app->secrets( [ Bugzilla->localconfig->{side_wide_secret} ] );
$app->renderer->add_handler(
'bugzilla' => sub {
@@ -90,23 +92,16 @@ sub register {
# The controller
$vars->{c} = $c;
my $name = $options->{template};
- unless ($name =~ /\./) {
+ unless ( $name =~ /\./ ) {
$name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format};
}
my $template = Bugzilla->template;
$template->process( $name, $vars, $output )
- or die $template->error;
+ or die $template->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/SES.pm b/Bugzilla/Quantum/SES.pm
index e36956b1d..47c591fb5 100644
--- a/Bugzilla/Quantum/SES.pm
+++ b/Bugzilla/Quantum/SES.pm
@@ -1,4 +1,4 @@
-#!/usr/bin/perl
+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/.
@@ -7,12 +7,8 @@
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
-use strict;
-use warnings;
+use Mojo::Base qw( Mojolicious::Controller );
-use lib qw(.. ../lib ../local/lib/perl5);
-
-use Bugzilla ();
use Bugzilla::Constants qw(ERROR_MODE_DIE);
use Bugzilla::Logging;
use Bugzilla::Mailer qw(MessageToMTA);
@@ -22,51 +18,44 @@ use JSON::MaybeXS qw(decode_json);
use LWP::UserAgent ();
use Try::Tiny qw(catch try);
-Bugzilla->error_mode(ERROR_MODE_DIE);
-try {
- main();
-}
-catch {
- FATAL("Fatal error: $_");
- respond( 500 => 'Internal Server Error' );
-};
-
sub main {
- my $message = decode_json_wrapper( Bugzilla->cgi->param('POSTDATA') ) // return;
- my $message_type = $ENV{HTTP_X_AMZ_SNS_MESSAGE_TYPE} // '(missing)';
+ 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' ) {
- confirm_subscription($message);
+ $self->_confirm_subscription($message);
}
elsif ( $message_type eq 'Notification' ) {
- my $notification = decode_json_wrapper( $message->{Message} ) // return;
+ my $notification = $self->_decode_json_wrapper( $message->{Message} ) // return;
unless (
# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html
- handle_notification( $notification, 'eventType' )
+ $self->_handle_notification( $notification, 'eventType' )
# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
- || handle_notification( $notification, 'notificationType' )
+ || $self->_handle_notification( $notification, 'notificationType' )
)
{
WARN('Failed to find notification type');
- respond( 400 => 'Bad Request' );
+ $self->_respond( 400 => 'Bad Request' );
}
}
else {
WARN("Unsupported message-type: $message_type");
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
}
-sub confirm_subscription {
- my ($message) = @_;
+sub _confirm_subscription {
+ my ($self, $message) = @_;
my $subscribe_url = $message->{SubscribeURL};
if ( !$subscribe_url ) {
WARN('Bad SubscriptionConfirmation request: missing SubscribeURL');
- respond( 400 => 'Bad Request' );
+ $self->_respond( 400 => 'Bad Request' );
return;
}
@@ -74,15 +63,15 @@ sub confirm_subscription {
my $res = $ua->get( $message->{SubscribeURL} );
if ( !$res->is_success ) {
WARN( 'Bad response from SubscribeURL: ' . $res->status_line );
- respond( 400 => 'Bad Request' );
+ $self->_respond( 400 => 'Bad Request' );
return;
}
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
-sub handle_notification {
- my ( $notification, $type_field ) = @_;
+sub _handle_notification {
+ my ( $self, $notification, $type_field ) = @_;
if ( !exists $notification->{$type_field} ) {
return 0;
@@ -90,20 +79,20 @@ sub handle_notification {
my $type = $notification->{$type_field};
if ( $type eq 'Bounce' ) {
- process_bounce($notification);
+ $self->_process_bounce($notification);
}
elsif ( $type eq 'Complaint' ) {
- process_complaint($notification);
+ $self->_process_complaint($notification);
}
else {
WARN("Unsupported notification-type: $type");
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
return 1;
}
-sub process_bounce {
- my ($notification) = @_;
+sub _process_bounce {
+ my ($self, $notification) = @_;
# disable each account that is bouncing
foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) {
@@ -140,10 +129,11 @@ sub process_bounce {
}
}
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
-sub process_complaint {
+sub _process_complaint {
+ my ($self) = @_;
# email notification to bugzilla admin
my ($notification) = @_;
@@ -170,31 +160,28 @@ sub process_complaint {
MessageToMTA($message);
}
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
-sub respond {
- my ( $code, $message ) = @_;
- print Bugzilla->cgi->header( -status => "$code $message" );
-
- # apache will generate non-200 response pages for us
- say html_quote($message) if $code == 200;
+sub _respond {
+ my ( $self, $code, $message ) = @_;
+ $self->render(text => "$message\n", status => $code);
}
-sub decode_json_wrapper {
- my ($json) = @_;
+sub _decode_json_wrapper {
+ my ($self, $json) = @_;
my $result;
if ( !defined $json ) {
- WARN( 'Missing JSON from ' . remote_ip() );
- respond( 400 => 'Bad Request' );
+ WARN( 'Missing JSON from ' . $self->tx->remote_address );
+ $self->_respond( 400 => 'Bad Request' );
return undef;
}
my $ok = try {
$result = decode_json($json);
}
catch {
- WARN( 'Malformed JSON from ' . remote_ip() );
- respond( 400 => 'Bad Request' );
+ WARN( 'Malformed JSON from ' . $self->tx->remote_address );
+ $self->_respond( 400 => 'Bad Request' );
return undef;
};
return $ok ? $result : undef;
@@ -212,3 +199,5 @@ sub ua {
}
return $ua;
}
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Quantum/Static.pm b/Bugzilla/Quantum/Static.pm
index 2bb54990e..d687873ab 100644
--- a/Bugzilla/Quantum/Static.pm
+++ b/Bugzilla/Quantum/Static.pm
@@ -16,9 +16,9 @@ my $LEGACY_RE = qr{
}xs;
sub file {
- my ($self, $rel) = @_;
+ my ( $self, $rel ) = @_;
- if (my ($legacy_rel) = $rel =~ $LEGACY_RE) {
+ if ( my ($legacy_rel) = $rel =~ $LEGACY_RE ) {
local $self->{paths} = [ bz_locations->{cgi_path} ];
return $self->SUPER::file($legacy_rel);
}
diff --git a/Bugzilla/Quantum/Stdout.pm b/Bugzilla/Quantum/Stdout.pm
index ee470a56a..be7b546ea 100644
--- a/Bugzilla/Quantum/Stdout.pm
+++ b/Bugzilla/Quantum/Stdout.pm
@@ -9,34 +9,51 @@ package Bugzilla::Quantum::Stdout;
use 5.10.1;
use Moo;
+use Bugzilla::Logging;
+use Encode;
+
has 'controller' => (
is => 'ro',
required => 1,
);
-sub TIEHANDLE { ## no critic (unpack)
+has '_encoding' => (
+ is => 'rw',
+ default => '',
+);
+
+sub TIEHANDLE { ## no critic (unpack)
my $class = shift;
return $class->new(@_);
}
-sub PRINTF { ## no critic (unpack)
+sub PRINTF { ## no critic (unpack)
my $self = shift;
- $self->PRINT(sprintf @_);
+ $self->PRINT( sprintf @_ );
}
-sub PRINT { ## no critic (unpack)
- my $self = shift;
-
- foreach my $chunk (@_) {
- my $str = "$chunk";
- utf8::encode($str);
- $self->controller->write($str);
+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.$\);
}
sub BINMODE {
- # no-op
+ my ( $self, $mode ) = @_;
+ if ($mode) {
+ if ( $mode eq ':bytes' or $mode eq ':raw' ) {
+ $self->_encoding('');
+ }
+ elsif ( $mode eq ':utf8' ) {
+ $self->_encoding('utf8');
+ }
+ }
}
-1; \ No newline at end of file
+1;
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index d4a5b15dc..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;
@@ -565,13 +566,8 @@ sub create {
PRE_CHOMP => 1,
TRIM => 1,
- # Bugzilla::Template::Plugin::Hook uses the absolute (in mod_perl)
- # or relative (in mod_cgi) paths of hook files to explicitly compile
- # a specific file. Also, these paths may be absolute at any time
- # if a packager has modified bz_locations() to contain absolute
- # paths.
ABSOLUTE => 1,
- RELATIVE => $ENV{SERVER_SOFTWARE} ? 0 : 1,
+ RELATIVE => 0,
# Only use an on-disk template cache if we're running as the web
# server. This ensures the permissions of the cache remain correct.
@@ -584,7 +580,7 @@ sub create {
# Initialize templates (f.e. by loading plugins like Hook).
PRE_PROCESS => ["global/initialize.none.tmpl"],
- ENCODING => Bugzilla->params->{'utf8'} ? 'UTF-8' : undef,
+ ENCODING => 'UTF-8',
# Functions for processing text within templates in various ways.
# IMPORTANT! When adding a filter here that does not override a
@@ -624,6 +620,7 @@ sub create {
# and newlines/carriage returns escaped for use in JS strings.
js => sub {
my ($var) = @_;
+ no warnings 'utf8';
$var =~ s/([\\\'\"\/])/\\$1/g;
$var =~ s/\n/\\n/g;
$var =~ s/\r/\\r/g;
@@ -639,6 +636,7 @@ sub create {
# for details.
json => sub {
my ($var) = @_;
+ no warnings 'utf8';
$var =~ s/([\\\"\/])/\\$1/g;
$var =~ s/\n/\\n/g;
$var =~ s/\r/\\r/g;
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index dc8f60565..4a58043a0 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -80,7 +80,8 @@ sub DB_COLUMNS {
'profiles.password_change_required',
'profiles.password_change_reason',
'profiles.mfa',
- 'profiles.mfa_required_date'
+ 'profiles.mfa_required_date',
+ 'profiles.nickname'
),
}
@@ -94,6 +95,7 @@ use constant VALIDATORS => {
disabledtext => \&_check_disabledtext,
login_name => \&check_login_name_for_creation,
realname => \&_check_realname,
+ nickname => \&_check_realname,
extern_id => \&_check_extern_id,
is_enabled => \&_check_is_enabled,
password_change_required => \&Bugzilla::Object::check_boolean,
@@ -114,6 +116,7 @@ sub UPDATE_COLUMNS {
password_change_reason
mfa
mfa_required_date
+ nickname
);
push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
return @cols;
@@ -478,10 +481,29 @@ sub set_login {
delete $self->{nick};
}
+sub _generate_nickname {
+ my ($name, $login, $id) = @_;
+ my ($nick) = extract_nicks($name);
+ if (!$nick) {
+ $nick = "";
+ }
+ my ($count) = Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM profiles WHERE nickname = ? AND userid != ?', undef, $nick, $id);
+ if ($count) {
+ $nick = "";
+ }
+ return $nick;
+}
+
sub set_name {
my ($self, $name) = @_;
$self->set('realname', $name);
delete $self->{identity};
+ $self->set('nickname', _generate_nickname($name, $self->login, $self->id));
+}
+
+sub set_nick {
+ my ($self, $nick) = @_;
+ $self->set('nickname', $nick);
}
sub set_password {
@@ -726,12 +748,8 @@ sub nick {
my $self = shift;
return "" unless $self->id;
-
- if (!defined $self->{nick}) {
- $self->{nick} = (split(/@/, $self->login, 2))[0];
- }
-
- return $self->{nick};
+ return $self->{nickname} if $self->{nickname};
+ return $self->{nick} //= (split(/@/, $self->login, 2))[0];
}
sub queries {
@@ -2514,13 +2532,12 @@ sub get_userlist {
}
sub create {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
+ my ($class, $params) = @_;
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
-
- my $user = $class->SUPER::create(@_);
+ $params->{nickname} = _generate_nickname($params->{realname}, $params->{login_name}, 0);
+ my $user = $class->SUPER::create($params);
# Turn on all email for the new user
require Bugzilla::BugMail;
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index a1316c7ef..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);
@@ -105,6 +105,7 @@ my %html_quote = (
# Bug 319331: Handle BiDi disruptions.
sub html_quote {
my $var = shift;
+ no warnings 'utf8';
$var =~ s/([&<>"@])/$html_quote{$1}/g;
state $use_utf8 = Bugzilla->params->{'utf8'};
@@ -316,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/REST/Resources/BugUserLastVisit.pm b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
index a434d4bef..12290e84e 100644
--- a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
@@ -32,6 +32,15 @@ sub _rest_resources {
},
},
},
+ # no bug-id
+ qr{^/bug_user_last_visit$}, {
+ GET => {
+ method => 'get',
+ },
+ POST => {
+ method => 'update',
+ },
+ },
];
}
diff --git a/Bugzilla/WebService/Server/REST/Resources/User.pm b/Bugzilla/WebService/Server/REST/Resources/User.pm
index eb44e9d2d..6185237fb 100644
--- a/Bugzilla/WebService/Server/REST/Resources/User.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/User.pm
@@ -20,6 +20,11 @@ BEGIN {
sub _rest_resources {
my $rest_resources = [
+ qr{^/user/suggest$}, {
+ GET => {
+ method => 'suggest',
+ },
+ },
qr{^/valid_login$}, {
GET => {
method => 'valid_login'
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
index 5f9b54787..c569cf9d8 100644
--- a/Bugzilla/WebService/User.pm
+++ b/Bugzilla/WebService/User.pm
@@ -24,6 +24,7 @@ use Bugzilla::WebService::Util qw(filter filter_wants validate
use Bugzilla::Hook;
use List::Util qw(first);
+use Taint::Util qw(untaint);
# Don't need auth to login
use constant LOGIN_EXEMPT => {
@@ -33,6 +34,7 @@ use constant LOGIN_EXEMPT => {
use constant READ_ONLY => qw(
get
+ suggest
);
use constant PUBLIC_METHODS => qw(
@@ -135,6 +137,70 @@ sub create {
return { id => $self->type('int', $user->id) };
}
+sub suggest {
+ my ($self, $params) = @_;
+
+ Bugzilla->switch_to_shadow_db();
+
+ ThrowCodeError('params_required', { function => 'User.suggest', params => ['match'] })
+ unless defined $params->{match};
+
+ ThrowUserError('user_access_by_match_denied')
+ unless Bugzilla->user->id;
+
+ untaint($params->{match});
+ my $s = $params->{match};
+ trim($s);
+ return { users => [] } if length($s) < 3;
+
+ my $dbh = Bugzilla->dbh;
+ my @select = ('userid AS id', 'realname AS real_name', 'login_name AS name');
+ my $order = 'last_seen_date DESC';
+ my $where;
+ state $have_mysql = $dbh->isa('Bugzilla::DB::Mysql');
+
+ if ($s =~ /^[:@](.+)$/s) {
+ $where = $dbh->sql_prefix_match(nickname => $1);
+ }
+ elsif ($s =~ /@/) {
+ $where = $dbh->sql_prefix_match(login_name => $s);
+ }
+ else {
+ if ($have_mysql && ( $s =~ /[[:space:]]/ || $s =~ /[^[:ascii:]]/ ) ) {
+ my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
+ push @select, "$match AS relevance";
+ $order = 'relevance DESC';
+ $where = $match;
+ }
+ elsif ($have_mysql && $s =~ /^[[:upper:]]/) {
+ my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
+ $where = join ' OR ',
+ $match,
+ $dbh->sql_prefix_match( nickname => $s ),
+ $dbh->sql_prefix_match( login_name => $s );
+ }
+ else {
+ $where = join ' OR ', $dbh->sql_prefix_match( nickname => $s ), $dbh->sql_prefix_match( login_name => $s );
+ }
+ }
+ $where = "($where) AND is_enabled = 1";
+
+ my $sql = 'SELECT ' . join(', ', @select) . " FROM profiles WHERE $where ORDER BY $order LIMIT 25";
+ my $results = $dbh->selectall_arrayref($sql, { Slice => {} });
+
+ my @users = map {
+ {
+ id => $self->type(int => $_->{id}),
+ real_name => $self->type(string => $_->{real_name}),
+ name => $self->type(email => $_->{name}),
+ }
+ } @$results;
+
+ Bugzilla::Hook::process('webservice_user_suggest',
+ { webservice => $self, params => $params, users => \@users });
+
+ return { users => \@users };
+}
# function to return user information by passing either user ids or
# login names or both together: