View | Details | Raw Unified | Return to bug 228182
Collapse All | Expand All

(-)b/Bugzilla/CGI.pm (+64 lines)
Lines 288-293 sub close_standby_message { Link Here
288
    }
288
    }
289
}
289
}
290
290
291
our $ALLOW_UNSAFE_RESPONSE = 0;
292
# responding to text/plain or text/html is safe
293
# responding to any request with a referer header is safe
294
# some things need to have unsafe responses (attachment.cgi)
295
# everything else should get a 403.
296
sub _prevent_unsafe_response {
297
    my ($self, $headers) = @_;
298
    my $safe_content_type_re = qr{
299
        ^ (*COMMIT) # COMMIT makes the regex faster
300
                    # by preventing back-tracking. see also perldoc pelre.
301
        # application/x-javascript, xml, atom+xml, rdf+xml, xml-dtd, and json
302
        (?: application/ (?: x(?: -javascript | ml (?: -dtd )? )
303
                           | (?: atom | rdf) \+ xml
304
                           | json )
305
        # text/csv, text/calendar, text/plain, and text/html
306
          | text/ (?: c (?: alendar | sv )
307
                    | plain
308
                    | html )
309
        # used for HTTP push responses
310
          | multipart/x-mixed-replace)
311
    }sx;
312
    my $safe_referer_re = do {
313
        # Note that urlbase must end with a /.
314
        # It almost certainly does, but let's be extra careful.
315
        my $urlbase = correct_urlbase();
316
        $urlbase =~ s{/$}{};
317
        qr{
318
            # Begins with literal urlbase
319
            ^ (*COMMIT)
320
            \Q$urlbase\E
321
            # followed by a slash or end of string
322
            (?: /
323
              | $ )
324
        }sx
325
    };
326
327
    return if $ALLOW_UNSAFE_RESPONSE;
328
329
    if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
330
        # Safe content types are ones that arn't images.
331
        # For now let's assume plain text and html are not valid images.
332
        my $content_type         = $headers->{'-type'} // $headers->{'-content_type'} // 'text/html';
333
        my $is_safe_content_type = $content_type =~ $safe_content_type_re;
334
335
        # Safe referers are ones that begin with the urlbase.
336
        my $referer         = $self->referer;
337
        my $is_safe_referer = $referer && $referer =~ $safe_referer_re;
338
339
        if (!$is_safe_referer && !$is_safe_content_type) {
340
            print $self->SUPER::header(-type => 'text/html',  -status => '403 Forbidden');
341
            if ($content_type ne 'text/html') {
342
                print "Untrusted Referer Header\n";
343
                if ($ENV{MOD_PERL}) {
344
                    my $r = $self->r;
345
                    $r->rflush;
346
                    $r->status(200);
347
                }
348
            }
349
            exit;
350
        }
351
    }
352
}
353
291
# Override header so we can add the cookies in
354
# Override header so we can add the cookies in
292
sub header {
355
sub header {
293
    my $self = shift;
356
    my $self = shift;
Lines 302-307 sub header { Link Here
302
    else {
365
    else {
303
        %headers = @_;
366
        %headers = @_;
304
    }
367
    }
368
    $self->_prevent_unsafe_response(\%headers);
305
369
306
    if ($self->{'_content_disp'}) {
370
    if ($self->{'_content_disp'}) {
307
        $headers{'-content_disposition'} = $self->{'_content_disp'};
371
        $headers{'-content_disposition'} = $self->{'_content_disp'};
(-)b/Bugzilla/Config.pm (-5 / +4 lines)
Lines 16-25 use autodie qw(:default); Link Here
16
16
17
use Bugzilla::Constants;
17
use Bugzilla::Constants;
18
use Bugzilla::Hook;
18
use Bugzilla::Hook;
19
use Bugzilla::Util qw(trick_taint);
19
use Bugzilla::Util qw(trick_taint read_text write_text);
20
20
21
use JSON::XS;
21
use JSON::XS;
22
use File::Slurp;
23
use File::Temp;
22
use File::Temp;
24
use File::Basename;
23
use File::Basename;
25
24
Lines 284-290 sub write_params { Link Here
284
    my $param_file = bz_locations()->{'datadir'} . '/params.json';
283
    my $param_file = bz_locations()->{'datadir'} . '/params.json';
285
284
286
    my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
285
    my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
287
    write_file($param_file, { binmode => ':utf8', atomic => 1 }, \$json_data);
286
    write_text($param_file, $json_data);
288
287
289
    # It's not common to edit parameters and loading
288
    # It's not common to edit parameters and loading
290
    # Bugzilla::Install::Filesystem is slow.
289
    # Bugzilla::Install::Filesystem is slow.
Lines 301-308 sub read_param_file { Link Here
301
    my $file = bz_locations()->{'datadir'} . '/params.json';
300
    my $file = bz_locations()->{'datadir'} . '/params.json';
302
301
303
    if (-e $file) {
302
    if (-e $file) {
304
        my $data;
303
        my $data = read_text($file);
305
        read_file($file, binmode => ':utf8', buf_ref => \$data);
304
        trick_taint($data);
306
305
307
        # If params.json has been manually edited and e.g. some quotes are
306
        # If params.json has been manually edited and e.g. some quotes are
308
        # missing, we don't want JSON::XS to leak the content of the file
307
        # missing, we don't want JSON::XS to leak the content of the file
(-)b/Bugzilla/Constants.pm (-1 / +1 lines)
Lines 200-206 use Memoize; Link Here
200
# CONSTANTS
200
# CONSTANTS
201
#
201
#
202
# Bugzilla version
202
# Bugzilla version
203
use constant BUGZILLA_VERSION => "5.0.3";
203
use constant BUGZILLA_VERSION => "5.0.4";
204
204
205
# A base link to the current REST Documentation. We place it here
205
# A base link to the current REST Documentation. We place it here
206
# as it will need to be updated to whatever the current release is.
206
# as it will need to be updated to whatever the current release is.
(-)b/Bugzilla/DB/Sqlite.pm (+1 lines)
Lines 219-224 sub sql_date_format { Link Here
219
    my ($self, $date, $format) = @_;
219
    my ($self, $date, $format) = @_;
220
    $format = "%Y.%m.%d %H:%M:%S" if !$format;
220
    $format = "%Y.%m.%d %H:%M:%S" if !$format;
221
    $format =~ s/\%i/\%M/g;
221
    $format =~ s/\%i/\%M/g;
222
    $format =~ s/\%s/\%S/g;
222
    return "STRFTIME(" . $self->quote($format) . ", $date)";
223
    return "STRFTIME(" . $self->quote($format) . ", $date)";
223
}
224
}
224
225
(-)b/Bugzilla/Install/Filesystem.pm (-11 / +9 lines)
Lines 31-37 use File::Path; Link Here
31
use File::Basename;
31
use File::Basename;
32
use File::Copy qw(move);
32
use File::Copy qw(move);
33
use File::Spec;
33
use File::Spec;
34
use File::Slurp;
35
use IO::File;
34
use IO::File;
36
use POSIX ();
35
use POSIX ();
37
36
Lines 536-542 sub update_filesystem { Link Here
536
535
537
    # Remove old assets htaccess file to force recreation with correct values.
536
    # Remove old assets htaccess file to force recreation with correct values.
538
    if (-e "$assetsdir/.htaccess") {
537
    if (-e "$assetsdir/.htaccess") {
539
        if (read_file("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
538
        if (read_text("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
540
            unlink("$assetsdir/.htaccess");
539
            unlink("$assetsdir/.htaccess");
541
        }
540
        }
542
    }
541
    }
Lines 782-803 sub _update_old_charts { Link Here
782
# to product IDs.
781
# to product IDs.
783
sub _update_old_mining_filenames {
782
sub _update_old_mining_filenames {
784
    my ($miningdir) = @_;
783
    my ($miningdir) = @_;
784
    my $dbh = Bugzilla->dbh;
785
    my @conversion_errors;
785
    my @conversion_errors;
786
786
787
    require Bugzilla::Product;
788
789
    # We use a dummy product instance with ID 0, representing all products
787
    # We use a dummy product instance with ID 0, representing all products
790
    my $product_all = {id => 0, name => '-All-'};
788
    my $product_all = {id => 0, name => '-All-'};
791
    bless($product_all, 'Bugzilla::Product');
792
789
793
    print "Updating old charting data file names...";
790
    print "Updating old charting data file names...";
794
    my @products = Bugzilla::Product->get_all();
791
    my @products = @{ $dbh->selectall_arrayref('SELECT id, name FROM products
792
                                                ORDER BY name', {Slice=>{}}) };
795
    push(@products, $product_all);
793
    push(@products, $product_all);
796
    foreach my $product (@products) {
794
    foreach my $product (@products) {
797
        if (-e File::Spec->catfile($miningdir, $product->id)) {
795
        if (-e File::Spec->catfile($miningdir, $product->{id})) {
798
            push(@conversion_errors,
796
            push(@conversion_errors,
799
                 { product => $product,
797
                 { product => $product,
800
                   message => 'A file named "' . $product->id .
798
                   message => 'A file named "' . $product->{id} .
801
                              '" already exists.' });
799
                              '" already exists.' });
802
        }
800
        }
803
    }
801
    }
Lines 805-812 sub _update_old_mining_filenames { Link Here
805
    if (! @conversion_errors) {
803
    if (! @conversion_errors) {
806
        # Renaming mining files should work now without a hitch.
804
        # Renaming mining files should work now without a hitch.
807
        foreach my $product (@products) {
805
        foreach my $product (@products) {
808
            if (! rename(File::Spec->catfile($miningdir, $product->name),
806
            if (! rename(File::Spec->catfile($miningdir, $product->{name}),
809
                         File::Spec->catfile($miningdir, $product->id))) {
807
                         File::Spec->catfile($miningdir, $product->{id}))) {
810
                push(@conversion_errors,
808
                push(@conversion_errors,
811
                     { product => $product,
809
                     { product => $product,
812
                       message => $! });
810
                       message => $! });
Lines 822-828 sub _update_old_mining_filenames { Link Here
822
        print " FAILED:\n";
820
        print " FAILED:\n";
823
        foreach my $error (@conversion_errors) {
821
        foreach my $error (@conversion_errors) {
824
            printf "Cannot rename charting data file for product %d (%s): %s\n",
822
            printf "Cannot rename charting data file for product %d (%s): %s\n",
825
                   $error->{product}->id, $error->{product}->name,
823
                   $error->{product}->{id}, $error->{product}->{name},
826
                   $error->{message};
824
                   $error->{message};
827
        }
825
        }
828
        print "You need to empty the \"$miningdir\" directory, then run\n",
826
        print "You need to empty the \"$miningdir\" directory, then run\n",
(-)b/Bugzilla/Install/Requirements.pm (-5 lines)
Lines 155-165 sub REQUIRED_MODULES { Link Here
155
        module  => 'Math::Random::ISAAC',
155
        module  => 'Math::Random::ISAAC',
156
        version => '1.0.1',
156
        version => '1.0.1',
157
    },
157
    },
158
    {
159
        package => 'File-Slurp',
160
        module  => 'File::Slurp',
161
        version => '9999.13',
162
    },
163
    {
158
    {
164
        package => 'JSON-XS',
159
        package => 'JSON-XS',
165
        module  => 'JSON::XS',
160
        module  => 'JSON::XS',
(-)b/Bugzilla/JobQueue.pm (-3 / +3 lines)
Lines 14-21 use warnings; Link Here
14
use Bugzilla::Constants;
14
use Bugzilla::Constants;
15
use Bugzilla::Error;
15
use Bugzilla::Error;
16
use Bugzilla::Install::Util qw(install_string);
16
use Bugzilla::Install::Util qw(install_string);
17
use Bugzilla::Util qw(read_text);
17
use File::Basename;
18
use File::Basename;
18
use File::Slurp;
19
use base qw(TheSchwartz);
19
use base qw(TheSchwartz);
20
use fields qw(_worker_pidfile);
20
use fields qw(_worker_pidfile);
21
21
Lines 124-130 sub subprocess_worker { Link Here
124
            # And poll the PID to detect when the working has finished.
124
            # And poll the PID to detect when the working has finished.
125
            # We do this instead of system() to allow for the INT signal to
125
            # We do this instead of system() to allow for the INT signal to
126
            # interrup us and trigger kill_worker().
126
            # interrup us and trigger kill_worker().
127
            my $pid = read_file($self->{_worker_pidfile}, err_mode => 'quiet');
127
            my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet');
128
            if ($pid) {
128
            if ($pid) {
129
                sleep(3) while(kill(0, $pid));
129
                sleep(3) while(kill(0, $pid));
130
            }
130
            }
Lines 139-145 sub subprocess_worker { Link Here
139
sub kill_worker {
139
sub kill_worker {
140
    my $self = Bugzilla->job_queue();
140
    my $self = Bugzilla->job_queue();
141
    if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
141
    if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
142
        my $worker_pid = read_file($self->{_worker_pidfile});
142
        my $worker_pid = read_text($self->{_worker_pidfile});
143
        if ($worker_pid && kill(0, $worker_pid)) {
143
        if ($worker_pid && kill(0, $worker_pid)) {
144
            $self->debug("Stopping worker process");
144
            $self->debug("Stopping worker process");
145
            system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";
145
            system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";
(-)b/Bugzilla/Migrate.pm (-1 / +1 lines)
Lines 403-409 sub parse_date { Link Here
403
    }
403
    }
404
    my $tz;
404
    my $tz;
405
    if ($time[6]) {
405
    if ($time[6]) {
406
        $tz = Bugzilla->local_timezone->offset_as_string($time[6]);
406
        $tz = DateTime::TimeZone->offset_as_string($time[6]);
407
    }
407
    }
408
    else {
408
    else {
409
        $tz = $self->config('timezone');
409
        $tz = $self->config('timezone');
(-)b/Bugzilla/Template.pm (-9 / +8 lines)
Lines 32-38 use Digest::MD5 qw(md5_hex); Link Here
32
use File::Basename qw(basename dirname);
32
use File::Basename qw(basename dirname);
33
use File::Find;
33
use File::Find;
34
use File::Path qw(rmtree mkpath);
34
use File::Path qw(rmtree mkpath);
35
use File::Slurp;
36
use File::Spec;
35
use File::Spec;
37
use IO::Dir;
36
use IO::Dir;
38
use List::MoreUtils qw(firstidx);
37
use List::MoreUtils qw(firstidx);
Lines 502-508 sub _concatenate_css { Link Here
502
        next unless -e "$cgi_path/$files{$source}";
501
        next unless -e "$cgi_path/$files{$source}";
503
        my $file = $skins_path . '/' . md5_hex($source) . '.css';
502
        my $file = $skins_path . '/' . md5_hex($source) . '.css';
504
        if (!-e $file) {
503
        if (!-e $file) {
505
            my $content = read_file("$cgi_path/$files{$source}");
504
            my $content = read_text("$cgi_path/$files{$source}");
506
505
507
            # minify
506
            # minify
508
            $content =~ s{/\*.*?\*/}{}sg;   # comments
507
            $content =~ s{/\*.*?\*/}{}sg;   # comments
Lines 512-518 sub _concatenate_css { Link Here
512
            # rewrite urls
511
            # rewrite urls
513
            $content =~ s{url\(([^\)]+)\)}{_css_url_rewrite($source, $1)}eig;
512
            $content =~ s{url\(([^\)]+)\)}{_css_url_rewrite($source, $1)}eig;
514
513
515
            write_file($file, "/* $files{$source} */\n" . $content . "\n");
514
            write_text($file, "/* $files{$source} */\n" . $content . "\n");
516
        }
515
        }
517
        push @minified, $file;
516
        push @minified, $file;
518
    }
517
    }
Lines 522-530 sub _concatenate_css { Link Here
522
    if (!-e $file) {
521
    if (!-e $file) {
523
        my $content = '';
522
        my $content = '';
524
        foreach my $source (@minified) {
523
        foreach my $source (@minified) {
525
            $content .= read_file($source);
524
            $content .= read_text($source);
526
        }
525
        }
527
        write_file($file, $content);
526
        write_text($file, $content);
528
    }
527
    }
529
528
530
    $file =~ s/^\Q$cgi_path\E\///o;
529
    $file =~ s/^\Q$cgi_path\E\///o;
Lines 563-569 sub _concatenate_js { Link Here
563
        next unless -e "$cgi_path/$files{$source}";
562
        next unless -e "$cgi_path/$files{$source}";
564
        my $file = $skins_path . '/' . md5_hex($source) . '.js';
563
        my $file = $skins_path . '/' . md5_hex($source) . '.js';
565
        if (!-e $file) {
564
        if (!-e $file) {
566
            my $content = read_file("$cgi_path/$files{$source}");
565
            my $content = read_text("$cgi_path/$files{$source}");
567
566
568
            # minimal minification
567
            # minimal minification
569
            $content =~ s#/\*.*?\*/##sg;    # block comments
568
            $content =~ s#/\*.*?\*/##sg;    # block comments
Lines 572-578 sub _concatenate_js { Link Here
572
            $content =~ s#\n{2,}#\n#g;      # blank lines
571
            $content =~ s#\n{2,}#\n#g;      # blank lines
573
            $content =~ s#(^\s+|\s+$)##g;   # whitespace at the start/end of file
572
            $content =~ s#(^\s+|\s+$)##g;   # whitespace at the start/end of file
574
573
575
            write_file($file, ";/* $files{$source} */\n" . $content . "\n");
574
            write_text($file, ";/* $files{$source} */\n" . $content . "\n");
576
        }
575
        }
577
        push @minified, $file;
576
        push @minified, $file;
578
    }
577
    }
Lines 582-590 sub _concatenate_js { Link Here
582
    if (!-e $file) {
581
    if (!-e $file) {
583
        my $content = '';
582
        my $content = '';
584
        foreach my $source (@minified) {
583
        foreach my $source (@minified) {
585
            $content .= read_file($source);
584
            $content .= read_text($source);
586
        }
585
        }
587
        write_file($file, $content);
586
        write_text($file, $content);
588
    }
587
    }
589
588
590
    $file =~ s/^\Q$cgi_path\E\///o;
589
    $file =~ s/^\Q$cgi_path\E\///o;
(-)b/Bugzilla/Util.pm (-2 / +27 lines)
Lines 24-30 use parent qw(Exporter); Link Here
24
                             validate_email_syntax check_email_syntax clean_text
24
                             validate_email_syntax check_email_syntax clean_text
25
                             get_text template_var display_value disable_utf8
25
                             get_text template_var display_value disable_utf8
26
                             detect_encoding email_filter
26
                             detect_encoding email_filter
27
                             join_activity_entries);
27
                             join_activity_entries read_text write_text);
28
28
29
use Bugzilla::Constants;
29
use Bugzilla::Constants;
30
use Bugzilla::RNG qw(irand);
30
use Bugzilla::RNG qw(irand);
Lines 39-44 use Scalar::Util qw(tainted blessed); Link Here
39
use Text::Wrap;
39
use Text::Wrap;
40
use Encode qw(encode decode resolve_alias);
40
use Encode qw(encode decode resolve_alias);
41
use Encode::Guess;
41
use Encode::Guess;
42
use File::Basename qw(dirname);
43
use File::Temp qw(tempfile);
42
44
43
sub trick_taint {
45
sub trick_taint {
44
    require Carp;
46
    require Carp;
Lines 106-111 sub html_quote { Link Here
106
    return $var;
108
    return $var;
107
}
109
}
108
110
111
sub read_text {
112
    my ($filename) = @_;
113
    open my $fh, '<:encoding(utf-8)', $filename;
114
    local $/ = undef;
115
    my $content = <$fh>;
116
    close $fh;
117
    return $content;
118
}
119
120
sub write_text {
121
    my ($filename, $content) = @_;
122
    my ($tmp_fh, $tmp_filename) = tempfile('.tmp.XXXXXXXXXX',
123
        DIR    => dirname($filename),
124
        UNLINK => 0,
125
    );
126
    binmode $tmp_fh, ':encoding(utf-8)';
127
    print $tmp_fh $content;
128
    close $tmp_fh;
129
    # File::Temp tries for secure files, but File::Slurp used the umask.
130
    chmod(0666 & ~umask, $tmp_filename);
131
    rename $tmp_filename, $filename;
132
}
133
109
sub html_light_quote {
134
sub html_light_quote {
110
    my ($text) = @_;
135
    my ($text) = @_;
111
    # admin/table.html.tmpl calls |FILTER html_light| many times.
136
    # admin/table.html.tmpl calls |FILTER html_light| many times.
Lines 588-594 sub datetime_from { Link Here
588
        second => defined($time[0]) ? int($time[0]) : undef,
613
        second => defined($time[0]) ? int($time[0]) : undef,
589
        # If a timezone was specified, use it. Otherwise, use the
614
        # If a timezone was specified, use it. Otherwise, use the
590
        # local timezone.
615
        # local timezone.
591
        time_zone => Bugzilla->local_timezone->offset_as_string($time[6]) 
616
        time_zone => DateTime::TimeZone->offset_as_string($time[6])
592
                     || Bugzilla->local_timezone,
617
                     || Bugzilla->local_timezone,
593
    );
618
    );
594
619
(-)b/attachment.cgi (+1 lines)
Lines 35-40 use Encode::MIME::Header; # Required to alter Encode::Encoding{'MIME-Q'}. Link Here
35
local our $cgi = Bugzilla->cgi;
35
local our $cgi = Bugzilla->cgi;
36
local our $template = Bugzilla->template;
36
local our $template = Bugzilla->template;
37
local our $vars = {};
37
local our $vars = {};
38
local $Bugzilla::CGI::ALLOW_UNSAFE_RESPONSE = 1;
38
39
39
# All calls to this script should contain an "action" variable whose
40
# All calls to this script should contain an "action" variable whose
40
# value determines what the user wants to do.  The code below checks
41
# value determines what the user wants to do.  The code below checks
(-)b/contrib/jb2bz.py (-56 / +66 lines)
Lines 17-24 This code requires a recent version of Andy Dustman's MySQLdb interface, Link Here
17
Share and enjoy.
17
Share and enjoy.
18
"""
18
"""
19
19
20
import rfc822, mimetools, multifile, mimetypes, email.utils
20
import email, mimetypes, email.utils
21
import sys, re, glob, StringIO, os, stat, time
21
import sys, re, glob, os, stat, time
22
import MySQLdb, getopt
22
import MySQLdb, getopt
23
23
24
# mimetypes doesn't include everything we might encounter, yet.
24
# mimetypes doesn't include everything we might encounter, yet.
Lines 89-98 def process_notes_file(current, fname): Link Here
89
def process_reply_file(current, fname):
89
def process_reply_file(current, fname):
90
    new_note = {}
90
    new_note = {}
91
    reply = open(fname, "r")
91
    reply = open(fname, "r")
92
    msg = rfc822.Message(reply)
92
    msg = email.message_from_file(reply)
93
    new_note['text'] = "%s\n%s" % (msg['From'], msg.fp.read())
93
94
    new_note['timestamp'] = email.utils.parsedate_tz(msg['Date'])
94
    # Add any attachments that may have been in a followup or reply
95
    current["notes"].append(new_note)
95
    msgtype = msg.get_content_maintype()
96
    if msgtype == "multipart":
97
        for part in msg.walk():
98
            new_note = {}
99
            if part.get_filename() is None:
100
                if part.get_content_type() == "text/plain":
101
                    new_note['timestamp'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date'])))
102
                    new_note['text'] = "%s\n%s" % (msg['From'], part.get_payload())
103
                    current["notes"].append(new_note)
104
            else:
105
                maybe_add_attachment(part, current)
106
    else:
107
        new_note['text'] = "%s\n%s" % (msg['From'], msg.get_payload())
108
        new_note['timestamp'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date'])))
109
        current["notes"].append(new_note)
96
110
97
def add_notes(current):
111
def add_notes(current):
98
    """Add any notes that have been recorded for the current bug."""
112
    """Add any notes that have been recorded for the current bug."""
Lines 104-154 def add_notes(current): Link Here
104
    for f in glob.glob("%d.followup.*" % current['number']):
118
    for f in glob.glob("%d.followup.*" % current['number']):
105
        process_reply_file(current, f)
119
        process_reply_file(current, f)
106
120
107
def maybe_add_attachment(current, file, submsg):
121
def maybe_add_attachment(submsg, current):
108
    """Adds the attachment to the current record"""
122
    """Adds the attachment to the current record"""
109
    cd = submsg["Content-Disposition"]
123
    attachment_filename = submsg.get_filename()
110
    m = re.search(r'filename="([^"]+)"', cd)
124
    if attachment_filename is None:
111
    if m == None:
112
        return
125
        return
113
    attachment_filename = m.group(1)
126
114
    if (submsg.gettype() == 'application/octet-stream'):
127
    if (submsg.get_content_type() == 'application/octet-stream'):
115
        # try get a more specific content-type for this attachment
128
        # try get a more specific content-type for this attachment
116
        type, encoding = mimetypes.guess_type(m.group(1))
129
        mtype, encoding = mimetypes.guess_type(attachment_filename)
117
        if type == None:
130
        if mtype == None:
118
            type = submsg.gettype()
131
            mtype = submsg.get_content_type()
119
    else:
132
    else:
120
        type = submsg.gettype()
133
        mtype = submsg.get_content_type()
121
134
122
    try:
135
    if mtype == 'application/x-pkcs7-signature':
123
        data = StringIO.StringIO()
136
        return
124
        mimetools.decode(file, data, submsg.getencoding())
137
125
    except:
138
    if mtype == 'application/pkcs7-signature':
139
         return
140
141
    if mtype == 'application/pgp-signature':
126
        return
142
        return
127
143
128
    current['attachments'].append( ( attachment_filename, type, data.getvalue() ) )
144
    if mtype == 'message/rfc822':
145
        return
129
146
130
def process_mime_body(current, file, submsg):
131
    data = StringIO.StringIO()
132
    try:
147
    try:
133
        mimetools.decode(file, data, submsg.getencoding())
148
        data = submsg.get_payload(decode=True)
134
        current['description'] = data.getvalue()
135
    except:
149
    except:
136
        return
150
        return
137
151
152
    current['attachments'].append( ( attachment_filename, mtype, data ) )
153
138
def process_text_plain(msg, current):
154
def process_text_plain(msg, current):
139
    current['description'] = msg.fp.read()
155
    current['description'] = msg.get_payload()
140
156
141
def process_multi_part(file, msg, current):
157
def process_multi_part(msg, current):
142
    mf = multifile.MultiFile(file)
158
    for part in msg.walk():
143
    mf.push(msg.getparam("boundary"))
159
        if part.get_filename() is None:
144
    while mf.next():
160
            process_text_plain(part, current)
145
        submsg = mimetools.Message(file)
146
        if submsg.has_key("Content-Disposition"):
147
            maybe_add_attachment(current, mf, submsg)
148
        else:
161
        else:
149
            # This is the message body itself (always?), so process
162
            maybe_add_attachment(part, current)
150
            # accordingly
151
            process_mime_body(current, mf, submsg)
152
163
153
def process_jitterbug(filename):
164
def process_jitterbug(filename):
154
    current = {}
165
    current = {}
Lines 158-196 def process_jitterbug(filename): Link Here
158
    current['description'] = ''
169
    current['description'] = ''
159
    current['date-reported'] = ()
170
    current['date-reported'] = ()
160
    current['short-description'] = ''
171
    current['short-description'] = ''
161
    
162
    print "Processing: %d" % current['number']
163
172
164
    file = open(filename, "r")
173
    print "Processing: %d" % current['number']
165
    create_date = os.fstat(file.fileno())
166
    msg = mimetools.Message(file)
167
174
168
    msgtype = msg.gettype()
175
    mfile = open(filename, "r")
176
    create_date = os.fstat(mfile.fileno())
177
    msg = email.message_from_file(mfile)
169
178
170
    add_notes(current)
179
    current['date-reported'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date'])))
171
    current['date-reported'] = email.utils.parsedate_tz(msg['Date'])
172
    if current['date-reported'] is None:
180
    if current['date-reported'] is None:
173
       current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME])
181
       current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME])
174
182
175
    if current['date-reported'][0] < 1900:
183
    if current['date-reported'][0] < 1900:
176
       current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME])
184
       current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME])
177
185
178
    if msg.getparam('Subject') is not None: 
186
    if msg.has_key('Subject') is not False:
179
        current['short-description'] = msg['Subject']
187
        current['short-description'] = msg['Subject']
180
    else:
188
    else:
181
        current['short-description'] = "Unknown"
189
        current['short-description'] = "Unknown"
182
190
183
    if msgtype[:5] == 'text/':
191
    msgtype = msg.get_content_maintype()
192
    if msgtype == 'text':
184
        process_text_plain(msg, current)
193
        process_text_plain(msg, current)
185
    elif msgtype[:5] == 'text':
194
    elif msgtype == "multipart":
186
        process_text_plain(msg, current)
195
        process_multi_part(msg, current)
187
    elif msgtype[:10] == "multipart/":
188
        process_multi_part(file, msg, current)
189
    else:
196
    else:
190
        # Huh? This should never happen.
197
        # Huh? This should never happen.
191
        print "Unknown content-type: %s" % msgtype
198
        print "Unknown content-type: %s" % msgtype
192
        sys.exit(1)
199
        sys.exit(1)
193
200
201
    add_notes(current)
202
194
    # At this point we have processed the message: we have all of the notes and
203
    # At this point we have processed the message: we have all of the notes and
195
    # attachments stored, so it's time to add things to the database.
204
    # attachments stored, so it's time to add things to the database.
196
    # The schema for JitterBug 2.14 can be found at:
205
    # The schema for JitterBug 2.14 can be found at:
Lines 219-224 def process_jitterbug(filename): Link Here
219
    try:
228
    try:
220
        cursor.execute( "INSERT INTO bugs SET " \
229
        cursor.execute( "INSERT INTO bugs SET " \
221
                        "bug_id=%s," \
230
                        "bug_id=%s," \
231
                        "priority='---'," \
222
                        "bug_severity='normal',"  \
232
                        "bug_severity='normal',"  \
223
                        "bug_status=%s," \
233
                        "bug_status=%s," \
224
                        "creation_ts=%s,"  \
234
                        "creation_ts=%s,"  \
Lines 242-248 def process_jitterbug(filename): Link Here
242
                          version,
252
                          version,
243
                          component,
253
                          component,
244
                          resolution] )
254
                          resolution] )
245
    
255
246
        # This is the initial long description associated with the bug report
256
        # This is the initial long description associated with the bug report
247
        cursor.execute( "INSERT INTO longdescs SET " \
257
        cursor.execute( "INSERT INTO longdescs SET " \
248
                        "bug_id=%s," \
258
                        "bug_id=%s," \
Lines 253-259 def process_jitterbug(filename): Link Here
253
                          reporter,
263
                          reporter,
254
                          time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
264
                          time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
255
                          current['description'] ] )
265
                          current['description'] ] )
256
    
266
257
        # Add whatever notes are associated with this defect
267
        # Add whatever notes are associated with this defect
258
        for n in current['notes']:
268
        for n in current['notes']:
259
                            cursor.execute( "INSERT INTO longdescs SET " \
269
                            cursor.execute( "INSERT INTO longdescs SET " \
Lines 265-279 def process_jitterbug(filename): Link Here
265
                             reporter,
275
                             reporter,
266
                             time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]),
276
                             time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]),
267
                             n['text']])
277
                             n['text']])
268
    
278
269
        # add attachments associated with this defect
279
        # add attachments associated with this defect
270
        for a in current['attachments']:
280
        for a in current['attachments']:
271
            cursor.execute( "INSERT INTO attachments SET " \
281
            cursor.execute( "INSERT INTO attachments SET " \
272
                            "bug_id=%s, creation_ts=%s, description='', mimetype=%s," \
282
                            "bug_id=%s, creation_ts=%s, description=%s, mimetype=%s," \
273
                            "filename=%s, submitter_id=%s",
283
                            "filename=%s, submitter_id=%s",
274
                            [ current['number'],
284
                            [ current['number'],
275
                              time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
285
                              time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
276
                              a[1], a[0], reporter ])
286
                              a[0], a[1], a[0], reporter ])
277
            cursor.execute( "INSERT INTO attach_data SET " \
287
            cursor.execute( "INSERT INTO attach_data SET " \
278
                            "id=LAST_INSERT_ID(), thedata=%s",
288
                            "id=LAST_INSERT_ID(), thedata=%s",
279
                            [ a[2] ])
289
                            [ a[2] ])
(-)b/editflagtypes.cgi (-1 / +1 lines)
Lines 453-459 sub get_products_and_components { Link Here
453
453
454
            # Let's sort the list by classifications.
454
            # Let's sort the list by classifications.
455
            @products = ();
455
            @products = ();
456
            push(@products, @{$class{$_->id}}) foreach Bugzilla::Classification->get_all;
456
            push(@products, @{$class{$_->id} || []}) foreach Bugzilla::Classification->get_all;
457
        }
457
        }
458
    }
458
    }
459
459
(-)b/taskgraph.json (-17 / +19 lines)
Lines 17-24 Link Here
17
        "provisionerId": "aws-provisioner-v1",
17
        "provisionerId": "aws-provisioner-v1",
18
        "workerType": "b2gtest",
18
        "workerType": "b2gtest",
19
        "payload": {
19
        "payload": {
20
          "image": "dklawren/docker-bugzilla",
20
          "image": "bugzilla/bugzilla-ci",
21
          "command": ["/runtests.sh"],
21
          "command": ["runtests.sh"],
22
          "env": {
22
          "env": {
23
            "TEST_SUITE": "sanity"
23
            "TEST_SUITE": "sanity"
24
          },
24
          },
Lines 54-61 Link Here
54
        "provisionerId": "aws-provisioner-v1",
54
        "provisionerId": "aws-provisioner-v1",
55
        "workerType": "b2gtest",
55
        "workerType": "b2gtest",
56
        "payload": {
56
        "payload": {
57
          "image": "dklawren/docker-bugzilla",
57
          "image": "bugzilla/bugzilla-ci",
58
          "command": ["/runtests.sh"],
58
          "command": ["runtests.sh"],
59
          "env": {
59
          "env": {
60
            "TEST_SUITE": "docs"
60
            "TEST_SUITE": "docs"
61
          },
61
          },
Lines 91-98 Link Here
91
        "provisionerId": "aws-provisioner-v1",
91
        "provisionerId": "aws-provisioner-v1",
92
        "workerType": "b2gtest",
92
        "workerType": "b2gtest",
93
        "payload": {
93
        "payload": {
94
          "image": "dklawren/docker-bugzilla",
94
          "image": "bugzilla/bugzilla-ci",
95
          "command": ["/runtests.sh"],
95
          "command": ["runtests.sh"],
96
          "env": {
96
          "env": {
97
            "TEST_SUITE": "webservices"
97
            "TEST_SUITE": "webservices"
98
          },
98
          },
Lines 133-147 Link Here
133
        "provisionerId": "aws-provisioner-v1",
133
        "provisionerId": "aws-provisioner-v1",
134
        "workerType": "b2gtest",
134
        "workerType": "b2gtest",
135
        "payload": {
135
        "payload": {
136
          "image": "dklawren/docker-bugzilla",
136
          "image": "bugzilla/bugzilla-ci",
137
          "command": ["/runtests.sh"],
137
          "command": ["runtests.sh"],
138
          "env": {
138
          "env": {
139
            "TEST_SUITE": "selenium"
139
            "TEST_SUITE": "selenium"
140
          },
140
          },
141
          "artifacts": {
141
          "artifacts": {
142
            "public/runtests_log": {
142
            "public/runtests_log": {
143
              "type": "file",
143
              "type": "file",
144
              "path": "/runtests.log",
144
              "path": "/tmp/runtests.log",
145
              "expires": "2018-02-17T17:33:38.806Z"
145
              "expires": "2018-02-17T17:33:38.806Z"
146
            },
146
            },
147
            "public/httpd_error_log": {
147
            "public/httpd_error_log": {
Lines 151-157 Link Here
151
            },
151
            },
152
            "public/selenium_log": {
152
            "public/selenium_log": {
153
              "type": "file",
153
              "type": "file",
154
              "path": "/selenium.log",
154
              "path": "/tmp/selenium.log",
155
              "expires": "2018-02-17T17:33:38.806Z"
155
              "expires": "2018-02-17T17:33:38.806Z"
156
            }
156
            }
157
          }
157
          }
Lines 180-194 Link Here
180
        "provisionerId": "aws-provisioner-v1",
180
        "provisionerId": "aws-provisioner-v1",
181
        "workerType": "b2gtest",
181
        "workerType": "b2gtest",
182
        "payload": {
182
        "payload": {
183
          "image": "dklawren/docker-bugzilla:pgsql",
183
          "image": "bugzilla/bugzilla-ci",
184
          "command": ["/runtests.sh"],
184
          "command": ["runtests.sh"],
185
          "env": {
185
          "env": {
186
            "BUGS_DB_DRIVER": "pg",
186
            "TEST_SUITE": "webservices"
187
            "TEST_SUITE": "webservices"
187
          },
188
          },
188
          "artifacts": {
189
          "artifacts": {
189
            "public/runtests_log": {
190
            "public/runtests_log": {
190
              "type": "file",
191
              "type": "file",
191
              "path": "/runtests.log",
192
              "path": "/tmp/runtests.log",
192
              "expires": "2018-02-17T17:33:38.806Z"
193
              "expires": "2018-02-17T17:33:38.806Z"
193
            },
194
            },
194
            "public/httpd_error_log": {
195
            "public/httpd_error_log": {
Lines 222-236 Link Here
222
        "provisionerId": "aws-provisioner-v1",
223
        "provisionerId": "aws-provisioner-v1",
223
        "workerType": "b2gtest",
224
        "workerType": "b2gtest",
224
        "payload": {
225
        "payload": {
225
          "image": "dklawren/docker-bugzilla:pgsql",
226
          "image": "bugzilla/bugzilla-ci",
226
          "command": ["/runtests.sh"],
227
          "command": ["runtests.sh"],
227
          "env": {
228
          "env": {
229
            "BUGS_DB_DRIVER": "pg",
228
            "TEST_SUITE": "selenium"
230
            "TEST_SUITE": "selenium"
229
          },
231
          },
230
          "artifacts": {
232
          "artifacts": {
231
            "public/runtests_log": {
233
            "public/runtests_log": {
232
              "type": "file",
234
              "type": "file",
233
              "path": "/runtests.log",
235
              "path": "/tmp/runtests.log",
234
              "expires": "2018-02-17T17:33:38.806Z"
236
              "expires": "2018-02-17T17:33:38.806Z"
235
            },
237
            },
236
            "public/httpd_error_log": {
238
            "public/httpd_error_log": {
Lines 240-246 Link Here
240
            },
242
            },
241
            "public/selenium_log": {
243
            "public/selenium_log": {
242
              "type": "file",
244
              "type": "file",
243
              "path": "/selenium.log",
245
              "path": "/tmp/selenium.log",
244
              "expires": "2018-02-17T17:33:38.806Z"
246
              "expires": "2018-02-17T17:33:38.806Z"
245
            }
247
            }
246
          }
248
          }
(-)b/template/en/default/pages/release-notes.html.tmpl (-2 / +24 lines)
Lines 43-48 Link Here
43
43
44
<h2 id="point">Updates in this 5.0.x Release</h2>
44
<h2 id="point">Updates in this 5.0.x Release</h2>
45
45
46
<h3>5.0.4</h3>
47
48
<p>This release fixes one security issue. See the
49
  <a href="https://www.bugzilla.org/security/4.4.12/">Security Advisory</a>
50
  for details.</p>
51
52
<p>This release also contains the following [% terms.bug %] fixes:</p>
53
54
<ul>
55
  <li><kbd>checksetup.pl</kbd> would fail to update Chart storage during pre-3.6 to 5.0 upgrade.
56
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1273846">[% terms.Bug %] 1273846</a>)</li>
57
  <li><kbd>editflagtypes.cgi</kbd> would crash when classifications are enabled and
58
    the user did not have global <kbd>editcomponents</kbd> privileges.
59
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1310728">[% terms.Bug %] 1310728</a>)</li>
60
  <li>The <kbd>File::Slurp</kbd> would trigger warnings on perl 5.24.
61
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301887">[% terms.Bug %] 1301887</a>)</li>
62
  <li>All the time entries in the 'when' column had the correct date but the time
63
    was fixed to 00:00 when using Sqlite.
64
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1303702">[% terms.Bug %] 1303702</a>)</li>
65
</ul>
66
46
<h3>5.0.3</h3>
67
<h3>5.0.3</h3>
47
68
48
<p>This release fixes one security issue. See the
69
<p>This release fixes one security issue. See the
Lines 69-75 Link Here
69
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1259881">[% terms.Bug %] 1259881</a>)</li>
90
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1259881">[% terms.Bug %] 1259881</a>)</li>
70
  <li>An extension which allows user-controlled data to be used as a link in
91
  <li>An extension which allows user-controlled data to be used as a link in
71
    tabs could trigger XSS if the data is not correctly sanitized.
92
    tabs could trigger XSS if the data is not correctly sanitized.
72
    [%+ terms. Bugzilla %] no longer relies on the extension to do the sanity
93
    [%+ terms.Bugzilla %] no longer relies on the extension to do the sanity
73
    check. A vanilla installation is not affected as no tab is user-controlled.
94
    check. A vanilla installation is not affected as no tab is user-controlled.
74
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1250114">[% terms.Bug %] 1250114</a>)</li>
95
    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1250114">[% terms.Bug %] 1250114</a>)</li>
75
  <li>Extensions can now easily override the favicon used for the
96
  <li>Extensions can now easily override the favicon used for the
Lines 174-180 Link Here
174
<h3 id="req_modules">Required Perl Modules</h3>
195
<h3 id="req_modules">Required Perl Modules</h3>
175
196
176
[% INCLUDE req_table reqs = REQUIRED_MODULES
197
[% INCLUDE req_table reqs = REQUIRED_MODULES
177
                     new = ['File-Slurp','JSON-XS', 'Email-Sender']
198
                     new = ['JSON-XS', 'Email-Sender']
178
                     updated = ['DateTime', 'DateTime-TimeZone',
199
                     updated = ['DateTime', 'DateTime-TimeZone',
179
                                'Template-Toolkit', 'URI'] %]
200
                                'Template-Toolkit', 'URI'] %]
180
201
Lines 205-210 Link Here
205
  you.</p>
226
  you.</p>
206
227
207
228
229
<a name="v50_feat"></a>
208
<h2 id="feat">New Features and Improvements</h2>
230
<h2 id="feat">New Features and Improvements</h2>
209
231
210
<ul>
232
<ul>

Return to bug 228182