aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrédéric Buclin <LpSolit@netscape.net>2018-02-18 18:25:30 +0100
committerFrédéric Buclin <LpSolit@netscape.net>2018-02-18 18:25:30 +0100
commitc52e3c6e8ad547ec035309994144fd22e2925267 (patch)
treed762218e6f71e302121d6112ad14deb5552201f8
parentd966c278e6c653a90343c739ac5a48f4127cf7e0 (diff)
parent37784703eba80cb61d1734a11e09b62fa0eaeae9 (diff)
downloadbugs-c52e3c6e8ad547ec035309994144fd22e2925267.tar
bugs-c52e3c6e8ad547ec035309994144fd22e2925267.tar.gz
bugs-c52e3c6e8ad547ec035309994144fd22e2925267.tar.bz2
bugs-c52e3c6e8ad547ec035309994144fd22e2925267.tar.xz
bugs-c52e3c6e8ad547ec035309994144fd22e2925267.zip
Sync with upstream release 5.0.4
-rw-r--r--Bugzilla/CGI.pm64
-rw-r--r--Bugzilla/Config.pm9
-rw-r--r--Bugzilla/Constants.pm2
-rw-r--r--Bugzilla/Install/Filesystem.pm3
-rw-r--r--Bugzilla/Install/Requirements.pm5
-rw-r--r--Bugzilla/JobQueue.pm6
-rw-r--r--Bugzilla/Template.pm17
-rw-r--r--Bugzilla/Util.pm27
-rwxr-xr-xattachment.cgi1
-rwxr-xr-xcontrib/jb2bz.py122
-rw-r--r--docs/en/rst/installing/linux.rst2
-rw-r--r--docs/en/rst/installing/windows.rst1
-rw-r--r--template/en/default/pages/release-notes.html.tmpl26
13 files changed, 199 insertions, 86 deletions
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index 44c089a20..9b1ff9235 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -288,6 +288,69 @@ sub close_standby_message {
}
}
+our $ALLOW_UNSAFE_RESPONSE = 0;
+# responding to text/plain or text/html is safe
+# responding to any request with a referer header is safe
+# some things need to have unsafe responses (attachment.cgi)
+# everything else should get a 403.
+sub _prevent_unsafe_response {
+ my ($self, $headers) = @_;
+ my $safe_content_type_re = qr{
+ ^ (*COMMIT) # COMMIT makes the regex faster
+ # by preventing back-tracking. see also perldoc pelre.
+ # application/x-javascript, xml, atom+xml, rdf+xml, xml-dtd, and json
+ (?: application/ (?: x(?: -javascript | ml (?: -dtd )? )
+ | (?: atom | rdf) \+ xml
+ | json )
+ # text/csv, text/calendar, text/plain, and text/html
+ | text/ (?: c (?: alendar | sv )
+ | plain
+ | html )
+ # used for HTTP push responses
+ | multipart/x-mixed-replace)
+ }sx;
+ my $safe_referer_re = do {
+ # Note that urlbase must end with a /.
+ # It almost certainly does, but let's be extra careful.
+ my $urlbase = correct_urlbase();
+ $urlbase =~ s{/$}{};
+ qr{
+ # Begins with literal urlbase
+ ^ (*COMMIT)
+ \Q$urlbase\E
+ # followed by a slash or end of string
+ (?: /
+ | $ )
+ }sx
+ };
+
+ return if $ALLOW_UNSAFE_RESPONSE;
+
+ if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+ # Safe content types are ones that arn't images.
+ # For now let's assume plain text and html are not valid images.
+ my $content_type = $headers->{'-type'} // $headers->{'-content_type'} // 'text/html';
+ my $is_safe_content_type = $content_type =~ $safe_content_type_re;
+
+ # Safe referers are ones that begin with the urlbase.
+ my $referer = $self->referer;
+ my $is_safe_referer = $referer && $referer =~ $safe_referer_re;
+
+ if (!$is_safe_referer && !$is_safe_content_type) {
+ print $self->SUPER::header(-type => 'text/html', -status => '403 Forbidden');
+ if ($content_type ne 'text/html') {
+ print "Untrusted Referer Header\n";
+ if ($ENV{MOD_PERL}) {
+ my $r = $self->r;
+ $r->rflush;
+ $r->status(200);
+ }
+ }
+ exit;
+ }
+ }
+}
+
# Override header so we can add the cookies in
sub header {
my $self = shift;
@@ -302,6 +365,7 @@ sub header {
else {
%headers = @_;
}
+ $self->_prevent_unsafe_response(\%headers);
if ($self->{'_content_disp'}) {
$headers{'-content_disposition'} = $self->{'_content_disp'};
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
index 1c02d9dda..458616701 100644
--- a/Bugzilla/Config.pm
+++ b/Bugzilla/Config.pm
@@ -16,10 +16,9 @@ use autodie qw(:default);
use Bugzilla::Constants;
use Bugzilla::Hook;
-use Bugzilla::Util qw(trick_taint);
+use Bugzilla::Util qw(trick_taint read_text write_text);
use JSON::XS;
-use File::Slurp;
use File::Temp;
use File::Basename;
@@ -284,7 +283,7 @@ sub write_params {
my $param_file = bz_locations()->{'datadir'} . '/params.json';
my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
- write_file($param_file, { binmode => ':utf8', atomic => 1 }, \$json_data);
+ write_text($param_file, $json_data);
# It's not common to edit parameters and loading
# Bugzilla::Install::Filesystem is slow.
@@ -301,8 +300,8 @@ sub read_param_file {
my $file = bz_locations()->{'datadir'} . '/params.json';
if (-e $file) {
- my $data;
- read_file($file, binmode => ':utf8', buf_ref => \$data);
+ my $data = read_text($file);
+ trick_taint($data);
# If params.json has been manually edited and e.g. some quotes are
# missing, we don't want JSON::XS to leak the content of the file
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 479212686..edaa8baa5 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -200,7 +200,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
-use constant BUGZILLA_VERSION => "5.0.3+";
+use constant BUGZILLA_VERSION => "5.0.4+";
# A base link to the current REST Documentation. We place it here
# as it will need to be updated to whatever the current release is.
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index 43512e79c..d30ae18dc 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -31,7 +31,6 @@ use File::Path;
use File::Basename;
use File::Copy qw(move);
use File::Spec;
-use File::Slurp;
use IO::File;
use POSIX ();
@@ -536,7 +535,7 @@ sub update_filesystem {
# Remove old assets htaccess file to force recreation with correct values.
if (-e "$assetsdir/.htaccess") {
- if (read_file("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
+ if (read_text("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
unlink("$assetsdir/.htaccess");
}
}
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index a688a0ffa..61496d843 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -156,11 +156,6 @@ sub REQUIRED_MODULES {
version => '1.0.1',
},
{
- package => 'File-Slurp',
- module => 'File::Slurp',
- version => '9999.13',
- },
- {
package => 'JSON-XS',
module => 'JSON::XS',
# 2.0 is the first version that will work with JSON::RPC.
diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm
index d5ceda8e9..6ff85d84f 100644
--- a/Bugzilla/JobQueue.pm
+++ b/Bugzilla/JobQueue.pm
@@ -14,8 +14,8 @@ use warnings;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Install::Util qw(install_string);
+use Bugzilla::Util qw(read_text);
use File::Basename;
-use File::Slurp;
use base qw(TheSchwartz);
use fields qw(_worker_pidfile);
@@ -124,7 +124,7 @@ sub subprocess_worker {
# And poll the PID to detect when the working has finished.
# We do this instead of system() to allow for the INT signal to
# interrup us and trigger kill_worker().
- my $pid = read_file($self->{_worker_pidfile}, err_mode => 'quiet');
+ my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet');
if ($pid) {
sleep(3) while(kill(0, $pid));
}
@@ -139,7 +139,7 @@ sub subprocess_worker {
sub kill_worker {
my $self = Bugzilla->job_queue();
if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
- my $worker_pid = read_file($self->{_worker_pidfile});
+ my $worker_pid = read_text($self->{_worker_pidfile});
if ($worker_pid && kill(0, $worker_pid)) {
$self->debug("Stopping worker process");
system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 95882b65f..75ab17298 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -32,7 +32,6 @@ use Digest::MD5 qw(md5_hex);
use File::Basename qw(basename dirname);
use File::Find;
use File::Path qw(rmtree mkpath);
-use File::Slurp;
use File::Spec;
use IO::Dir;
use List::MoreUtils qw(firstidx);
@@ -502,7 +501,7 @@ sub _concatenate_css {
next unless -e "$cgi_path/$files{$source}";
my $file = $skins_path . '/' . md5_hex($source) . '.css';
if (!-e $file) {
- my $content = read_file("$cgi_path/$files{$source}");
+ my $content = read_text("$cgi_path/$files{$source}");
# minify
$content =~ s{/\*.*?\*/}{}sg; # comments
@@ -512,7 +511,7 @@ sub _concatenate_css {
# rewrite urls
$content =~ s{url\(([^\)]+)\)}{_css_url_rewrite($source, $1)}eig;
- write_file($file, "/* $files{$source} */\n" . $content . "\n");
+ write_text($file, "/* $files{$source} */\n" . $content . "\n");
}
push @minified, $file;
}
@@ -522,9 +521,9 @@ sub _concatenate_css {
if (!-e $file) {
my $content = '';
foreach my $source (@minified) {
- $content .= read_file($source);
+ $content .= read_text($source);
}
- write_file($file, $content);
+ write_text($file, $content);
}
$file =~ s/^\Q$cgi_path\E\///o;
@@ -563,7 +562,7 @@ sub _concatenate_js {
next unless -e "$cgi_path/$files{$source}";
my $file = $skins_path . '/' . md5_hex($source) . '.js';
if (!-e $file) {
- my $content = read_file("$cgi_path/$files{$source}");
+ my $content = read_text("$cgi_path/$files{$source}");
# minimal minification
$content =~ s#/\*.*?\*/##sg; # block comments
@@ -572,7 +571,7 @@ sub _concatenate_js {
$content =~ s#\n{2,}#\n#g; # blank lines
$content =~ s#(^\s+|\s+$)##g; # whitespace at the start/end of file
- write_file($file, ";/* $files{$source} */\n" . $content . "\n");
+ write_text($file, ";/* $files{$source} */\n" . $content . "\n");
}
push @minified, $file;
}
@@ -582,9 +581,9 @@ sub _concatenate_js {
if (!-e $file) {
my $content = '';
foreach my $source (@minified) {
- $content .= read_file($source);
+ $content .= read_text($source);
}
- write_file($file, $content);
+ write_text($file, $content);
}
$file =~ s/^\Q$cgi_path\E\///o;
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index 9d388699d..9976b12d6 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -24,7 +24,7 @@ use parent qw(Exporter);
validate_email_syntax check_email_syntax clean_text
get_text template_var display_value disable_utf8
detect_encoding email_filter
- join_activity_entries);
+ join_activity_entries read_text write_text);
use Bugzilla::Constants;
use Bugzilla::RNG qw(irand);
@@ -40,6 +40,8 @@ use Scalar::Util qw(tainted blessed);
use Text::Wrap;
use Encode qw(encode decode resolve_alias);
use Encode::Guess;
+use File::Basename qw(dirname);
+use File::Temp qw(tempfile);
sub trick_taint {
require Carp;
@@ -107,6 +109,29 @@ sub html_quote {
return $var;
}
+sub read_text {
+ my ($filename) = @_;
+ open my $fh, '<:encoding(utf-8)', $filename;
+ local $/ = undef;
+ my $content = <$fh>;
+ close $fh;
+ return $content;
+}
+
+sub write_text {
+ my ($filename, $content) = @_;
+ my ($tmp_fh, $tmp_filename) = tempfile('.tmp.XXXXXXXXXX',
+ DIR => dirname($filename),
+ UNLINK => 0,
+ );
+ binmode $tmp_fh, ':encoding(utf-8)';
+ print $tmp_fh $content;
+ close $tmp_fh;
+ # File::Temp tries for secure files, but File::Slurp used the umask.
+ chmod(0666 & ~umask, $tmp_filename);
+ rename $tmp_filename, $filename;
+}
+
sub html_light_quote {
my ($text) = @_;
# admin/table.html.tmpl calls |FILTER html_light| many times.
diff --git a/attachment.cgi b/attachment.cgi
index e4761a805..57cc8548f 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -35,6 +35,7 @@ use Encode::MIME::Header; # Required to alter Encode::Encoding{'MIME-Q'}.
local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
local our $vars = {};
+local $Bugzilla::CGI::ALLOW_UNSAFE_RESPONSE = 1;
# All calls to this script should contain an "action" variable whose
# value determines what the user wants to do. The code below checks
diff --git a/contrib/jb2bz.py b/contrib/jb2bz.py
index 85f95423a..caaa0c5e2 100755
--- a/contrib/jb2bz.py
+++ b/contrib/jb2bz.py
@@ -17,8 +17,8 @@ This code requires a recent version of Andy Dustman's MySQLdb interface,
Share and enjoy.
"""
-import rfc822, mimetools, multifile, mimetypes, email.utils
-import sys, re, glob, StringIO, os, stat, time
+import email, mimetypes, email.utils
+import sys, re, glob, os, stat, time
import MySQLdb, getopt
# mimetypes doesn't include everything we might encounter, yet.
@@ -89,10 +89,24 @@ def process_notes_file(current, fname):
def process_reply_file(current, fname):
new_note = {}
reply = open(fname, "r")
- msg = rfc822.Message(reply)
- new_note['text'] = "%s\n%s" % (msg['From'], msg.fp.read())
- new_note['timestamp'] = email.utils.parsedate_tz(msg['Date'])
- current["notes"].append(new_note)
+ msg = email.message_from_file(reply)
+
+ # Add any attachments that may have been in a followup or reply
+ msgtype = msg.get_content_maintype()
+ if msgtype == "multipart":
+ for part in msg.walk():
+ new_note = {}
+ if part.get_filename() is None:
+ if part.get_content_type() == "text/plain":
+ new_note['timestamp'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date'])))
+ new_note['text'] = "%s\n%s" % (msg['From'], part.get_payload())
+ current["notes"].append(new_note)
+ else:
+ maybe_add_attachment(part, current)
+ else:
+ new_note['text'] = "%s\n%s" % (msg['From'], msg.get_payload())
+ new_note['timestamp'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date'])))
+ current["notes"].append(new_note)
def add_notes(current):
"""Add any notes that have been recorded for the current bug."""
@@ -104,51 +118,48 @@ def add_notes(current):
for f in glob.glob("%d.followup.*" % current['number']):
process_reply_file(current, f)
-def maybe_add_attachment(current, file, submsg):
+def maybe_add_attachment(submsg, current):
"""Adds the attachment to the current record"""
- cd = submsg["Content-Disposition"]
- m = re.search(r'filename="([^"]+)"', cd)
- if m == None:
+ attachment_filename = submsg.get_filename()
+ if attachment_filename is None:
return
- attachment_filename = m.group(1)
- if (submsg.gettype() == 'application/octet-stream'):
+
+ if (submsg.get_content_type() == 'application/octet-stream'):
# try get a more specific content-type for this attachment
- type, encoding = mimetypes.guess_type(m.group(1))
- if type == None:
- type = submsg.gettype()
+ mtype, encoding = mimetypes.guess_type(attachment_filename)
+ if mtype == None:
+ mtype = submsg.get_content_type()
else:
- type = submsg.gettype()
+ mtype = submsg.get_content_type()
- try:
- data = StringIO.StringIO()
- mimetools.decode(file, data, submsg.getencoding())
- except:
+ if mtype == 'application/x-pkcs7-signature':
+ return
+
+ if mtype == 'application/pkcs7-signature':
+ return
+
+ if mtype == 'application/pgp-signature':
return
- current['attachments'].append( ( attachment_filename, type, data.getvalue() ) )
+ if mtype == 'message/rfc822':
+ return
-def process_mime_body(current, file, submsg):
- data = StringIO.StringIO()
try:
- mimetools.decode(file, data, submsg.getencoding())
- current['description'] = data.getvalue()
+ data = submsg.get_payload(decode=True)
except:
return
+ current['attachments'].append( ( attachment_filename, mtype, data ) )
+
def process_text_plain(msg, current):
- current['description'] = msg.fp.read()
-
-def process_multi_part(file, msg, current):
- mf = multifile.MultiFile(file)
- mf.push(msg.getparam("boundary"))
- while mf.next():
- submsg = mimetools.Message(file)
- if submsg.has_key("Content-Disposition"):
- maybe_add_attachment(current, mf, submsg)
+ current['description'] = msg.get_payload()
+
+def process_multi_part(msg, current):
+ for part in msg.walk():
+ if part.get_filename() is None:
+ process_text_plain(part, current)
else:
- # This is the message body itself (always?), so process
- # accordingly
- process_mime_body(current, mf, submsg)
+ maybe_add_attachment(part, current)
def process_jitterbug(filename):
current = {}
@@ -158,39 +169,37 @@ def process_jitterbug(filename):
current['description'] = ''
current['date-reported'] = ()
current['short-description'] = ''
-
- print "Processing: %d" % current['number']
- file = open(filename, "r")
- create_date = os.fstat(file.fileno())
- msg = mimetools.Message(file)
+ print "Processing: %d" % current['number']
- msgtype = msg.gettype()
+ mfile = open(filename, "r")
+ create_date = os.fstat(mfile.fileno())
+ msg = email.message_from_file(mfile)
- add_notes(current)
- current['date-reported'] = email.utils.parsedate_tz(msg['Date'])
+ current['date-reported'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date'])))
if current['date-reported'] is None:
current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME])
if current['date-reported'][0] < 1900:
current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME])
- if msg.getparam('Subject') is not None:
+ if msg.has_key('Subject') is not False:
current['short-description'] = msg['Subject']
else:
current['short-description'] = "Unknown"
- if msgtype[:5] == 'text/':
+ msgtype = msg.get_content_maintype()
+ if msgtype == 'text':
process_text_plain(msg, current)
- elif msgtype[:5] == 'text':
- process_text_plain(msg, current)
- elif msgtype[:10] == "multipart/":
- process_multi_part(file, msg, current)
+ elif msgtype == "multipart":
+ process_multi_part(msg, current)
else:
# Huh? This should never happen.
print "Unknown content-type: %s" % msgtype
sys.exit(1)
+ add_notes(current)
+
# At this point we have processed the message: we have all of the notes and
# attachments stored, so it's time to add things to the database.
# The schema for JitterBug 2.14 can be found at:
@@ -219,6 +228,7 @@ def process_jitterbug(filename):
try:
cursor.execute( "INSERT INTO bugs SET " \
"bug_id=%s," \
+ "priority='---'," \
"bug_severity='normal'," \
"bug_status=%s," \
"creation_ts=%s," \
@@ -242,7 +252,7 @@ def process_jitterbug(filename):
version,
component,
resolution] )
-
+
# This is the initial long description associated with the bug report
cursor.execute( "INSERT INTO longdescs SET " \
"bug_id=%s," \
@@ -253,7 +263,7 @@ def process_jitterbug(filename):
reporter,
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
current['description'] ] )
-
+
# Add whatever notes are associated with this defect
for n in current['notes']:
cursor.execute( "INSERT INTO longdescs SET " \
@@ -265,15 +275,15 @@ def process_jitterbug(filename):
reporter,
time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]),
n['text']])
-
+
# add attachments associated with this defect
for a in current['attachments']:
cursor.execute( "INSERT INTO attachments SET " \
- "bug_id=%s, creation_ts=%s, description='', mimetype=%s," \
+ "bug_id=%s, creation_ts=%s, description=%s, mimetype=%s," \
"filename=%s, submitter_id=%s",
[ current['number'],
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
- a[1], a[0], reporter ])
+ a[0], a[1], a[0], reporter ])
cursor.execute( "INSERT INTO attach_data SET " \
"id=LAST_INSERT_ID(), thedata=%s",
[ a[2] ])
diff --git a/docs/en/rst/installing/linux.rst b/docs/en/rst/installing/linux.rst
index f40c5e8f0..e2e05c310 100644
--- a/docs/en/rst/installing/linux.rst
+++ b/docs/en/rst/installing/linux.rst
@@ -49,7 +49,7 @@ graphviz patchutils gcc 'perl(Apache2::SizeLimit)' 'perl(Authen::Radius)'
'perl(Daemon::Generic)' 'perl(Date::Format)' 'perl(DateTime)'
'perl(DateTime::TimeZone)' 'perl(DBI)' 'perl(Digest::SHA)' 'perl(Email::MIME)'
'perl(Email::Reply)' 'perl(Email::Sender)' 'perl(Encode)' 'perl(Encode::Detect)'
-'perl(File::MimeInfo::Magic)' 'perl(File::Slurp)' 'perl(GD)' 'perl(GD::Graph)'
+'perl(File::MimeInfo::Magic)' 'perl(GD)' 'perl(GD::Graph)'
'perl(GD::Text)' 'perl(HTML::FormatText::WithLinks)' 'perl(HTML::Parser)'
'perl(HTML::Scrubber)' 'perl(IO::Scalar)' 'perl(JSON::RPC)' 'perl(JSON::XS)'
'perl(List::MoreUtils)' 'perl(LWP::UserAgent)' 'perl(Math::Random::ISAAC)'
diff --git a/docs/en/rst/installing/windows.rst b/docs/en/rst/installing/windows.rst
index e2137a9fc..d756077b0 100644
--- a/docs/en/rst/installing/windows.rst
+++ b/docs/en/rst/installing/windows.rst
@@ -85,7 +85,6 @@ Install the following mandatory modules with:
* URI
* List-MoreUtils
* Math-Random-ISAAC
-* File-Slurp
* JSON-XS
* Win32
* Win32-API
diff --git a/template/en/default/pages/release-notes.html.tmpl b/template/en/default/pages/release-notes.html.tmpl
index 358298bc8..b89e3a61d 100644
--- a/template/en/default/pages/release-notes.html.tmpl
+++ b/template/en/default/pages/release-notes.html.tmpl
@@ -43,6 +43,27 @@
<h2 id="point">Updates in this 5.0.x Release</h2>
+<h3>5.0.4</h3>
+
+<p>This release fixes one security issue. See the
+ <a href="https://www.bugzilla.org/security/4.4.12/">Security Advisory</a>
+ for details.</p>
+
+<p>This release also contains the following [% terms.bug %] fixes:</p>
+
+<ul>
+ <li><kbd>checksetup.pl</kbd> would fail to update Chart storage during pre-3.6 to 5.0 upgrade.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1273846">[% terms.Bug %] 1273846</a>)</li>
+ <li><kbd>editflagtypes.cgi</kbd> would crash when classifications are enabled and
+ the user did not have global <kbd>editcomponents</kbd> privileges.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1310728">[% terms.Bug %] 1310728</a>)</li>
+ <li>The <kbd>File::Slurp</kbd> would trigger warnings on perl 5.24.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301887">[% terms.Bug %] 1301887</a>)</li>
+ <li>All the time entries in the 'when' column had the correct date but the time
+ was fixed to 00:00 when using Sqlite.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1303702">[% terms.Bug %] 1303702</a>)</li>
+</ul>
+
<h3>5.0.3</h3>
<p>This release fixes one security issue. See the
@@ -69,7 +90,7 @@
(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1259881">[% terms.Bug %] 1259881</a>)</li>
<li>An extension which allows user-controlled data to be used as a link in
tabs could trigger XSS if the data is not correctly sanitized.
- [%+ terms. Bugzilla %] no longer relies on the extension to do the sanity
+ [%+ terms.Bugzilla %] no longer relies on the extension to do the sanity
check. A vanilla installation is not affected as no tab is user-controlled.
(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1250114">[% terms.Bug %] 1250114</a>)</li>
<li>Extensions can now easily override the favicon used for the
@@ -174,7 +195,7 @@
<h3 id="req_modules">Required Perl Modules</h3>
[% INCLUDE req_table reqs = REQUIRED_MODULES
- new = ['File-Slurp','JSON-XS', 'Email-Sender']
+ new = ['JSON-XS', 'Email-Sender']
updated = ['DateTime', 'DateTime-TimeZone',
'Template-Toolkit', 'URI'] %]
@@ -205,6 +226,7 @@
you.</p>
+<a name="v50_feat"></a>
<h2 id="feat">New Features and Improvements</h2>
<ul>