RSS

(root)/bugzilla/3.4 : 5441 (compared to revision 6722) : Bugzilla/Util.pm

To get this branch, use:
bzr branch /bugzilla/3.4

« back to all changes in this revision

Viewing changes to Bugzilla/Util.pm

lpsolit%gmail.com
2008-01-27 21:14:14
Revision ID: cvs-1:lpsolitgmail.com-20080128051414-2yv396bj7ybtinpg
Bug 325487: Admin pages should require less "clicks" (remove useless confirmation pages) - Patch by Frédéric Buclin <LpSolit@gmail.com> r=justdave r=mkanat a=LpSolit

Show diffs side-by-side

added added

removed removed

31
31
use strict;
32
32
 
33
33
use base qw(Exporter);
34
 
@Bugzilla::Util::EXPORT = qw(trick_taint detaint_natural
 
34
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
35
35
                             detaint_signed
36
36
                             html_quote url_quote xml_quote
37
37
                             css_class_quote html_light_quote url_decode
38
38
                             i_am_cgi get_netaddr correct_urlbase
39
 
                             lsearch ssl_require_redirect use_attachbase
40
 
                             diff_arrays
 
39
                             lsearch
 
40
                             diff_arrays diff_strings
41
41
                             trim wrap_hard wrap_comment find_wrap_point
42
42
                             format_time format_time_decimal validate_date
43
43
                             validate_time
44
44
                             file_mod_time is_7bit_clean
45
45
                             bz_crypt generate_random_password
46
46
                             validate_email_syntax clean_text
47
 
                             get_text disable_utf8);
 
47
                             get_text);
48
48
 
49
49
use Bugzilla::Constants;
50
50
 
51
51
use Date::Parse;
52
52
use Date::Format;
53
 
use DateTime;
54
 
use DateTime::TimeZone;
55
 
use Digest;
56
 
use Email::Address;
57
 
use Scalar::Util qw(tainted);
58
53
use Text::Wrap;
59
54
 
 
55
# This is from the perlsec page, slightly modified to remove a warning
 
56
# From that page:
 
57
#      This function makes use of the fact that the presence of
 
58
#      tainted data anywhere within an expression renders the
 
59
#      entire expression tainted.
 
60
# Don't ask me how it works...
 
61
sub is_tainted {
 
62
    return not eval { my $foo = join('',@_), kill 0; 1; };
 
63
}
 
64
 
60
65
sub trick_taint {
61
66
    require Carp;
62
67
    Carp::confess("Undef to trick_taint") unless defined $_[0];
95
100
 
96
101
    # List of allowed HTML elements having no attributes.
97
102
    my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
98
 
                   dfn samp kbd big small sub sup tt dd dt dl ul li ol
99
 
                   fieldset legend);
 
103
                   dfn samp kbd big small sub sup tt dd dt dl ul li ol);
100
104
 
101
105
    # Are HTML::Scrubber and HTML::Parser installed?
102
106
    eval { require HTML::Scrubber;
171
175
    }
172
176
}
173
177
 
174
 
sub email_filter {
175
 
    my ($toencode) = @_;
176
 
    if (!Bugzilla->user->id) {
177
 
        my @emails = Email::Address->parse($toencode);
178
 
        if (scalar @emails) {
179
 
            my @hosts = map { quotemeta($_->host) } @emails;
180
 
            my $hosts_re = join('|', @hosts);
181
 
            $toencode =~ s/\@(?:$hosts_re)//g;
182
 
            return $toencode;
183
 
        }
184
 
    }
185
 
    return $toencode;
186
 
}
187
 
 
188
178
# This originally came from CGI.pm, by Lincoln D. Stein
189
179
sub url_quote {
190
180
    my ($toencode) = (@_);
208
198
    $var =~ s/>/\&gt;/g;
209
199
    $var =~ s/\"/\&quot;/g;
210
200
    $var =~ s/\'/\&apos;/g;
211
 
    
212
 
    # the following nukes characters disallowed by the XML 1.0
213
 
    # spec, Production 2.2. 1.0 declares that only the following 
214
 
    # are valid:
215
 
    # (#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF])
216
 
    $var =~ s/([\x{0001}-\x{0008}]|
217
 
               [\x{000B}-\x{000C}]|
218
 
               [\x{000E}-\x{001F}]|
219
 
               [\x{D800}-\x{DFFF}]|
220
 
               [\x{FFFE}-\x{FFFF}])//gx;
221
201
    return $var;
222
202
}
223
203
 
238
218
    return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
239
219
}
240
220
 
241
 
sub ssl_require_redirect {
242
 
    my $method = shift;
243
 
 
244
 
    # If currently not in a protected SSL 
245
 
    # connection, determine if a redirection is 
246
 
    # needed based on value in Bugzilla->params->{ssl}.
247
 
    # If we are already in a protected connection or
248
 
    # sslbase is not set then no action is required.
249
 
    if (uc($ENV{'HTTPS'}) ne 'ON' 
250
 
        && $ENV{'SERVER_PORT'} != 443 
251
 
        && Bugzilla->params->{'sslbase'} ne '')
252
 
    {
253
 
        # System is configured to never require SSL 
254
 
        # so no redirection is needed.
255
 
        return 0 
256
 
            if Bugzilla->params->{'ssl'} eq 'never';
257
 
            
258
 
        # System is configured to always require a SSL
259
 
        # connection so we need to redirect.
260
 
        return 1
261
 
            if Bugzilla->params->{'ssl'} eq 'always';
262
 
 
263
 
        # System is configured such that if we are inside
264
 
        # of an authenticated session, then we need to make
265
 
        # sure that all of the connections are over SSL. Non
266
 
        # authenticated sessions SSL is not mandatory.
267
 
        # For XMLRPC requests, if the method is User.login
268
 
        # then we always want the connection to be over SSL
269
 
        # if the system is configured for authenticated
270
 
        # sessions since the user's username and password
271
 
        # will be passed before the user is logged in.
272
 
        return 1 
273
 
            if Bugzilla->params->{'ssl'} eq 'authenticated sessions'
274
 
                && (Bugzilla->user->id 
275
 
                    || (defined $method && $method eq 'User.login'));
276
 
    }
277
 
 
278
 
    return 0;
279
 
}
280
 
 
281
221
sub correct_urlbase {
282
222
    my $ssl = Bugzilla->params->{'ssl'};
283
223
    return Bugzilla->params->{'urlbase'} if $ssl eq 'never';
294
234
    return Bugzilla->params->{'urlbase'};
295
235
}
296
236
 
297
 
sub use_attachbase {
298
 
    my $attachbase = Bugzilla->params->{'attachment_base'};
299
 
    return ($attachbase ne ''
300
 
            && $attachbase ne Bugzilla->params->{'urlbase'}
301
 
            && $attachbase ne Bugzilla->params->{'sslbase'}) ? 1 : 0;
302
 
}
303
 
 
304
237
sub lsearch {
305
238
    my ($list,$item) = (@_);
306
239
    my $count = 0;
345
278
    return $str;
346
279
}
347
280
 
 
281
sub diff_strings {
 
282
    my ($oldstr, $newstr) = @_;
 
283
 
 
284
    # Split the old and new strings into arrays containing their values.
 
285
    $oldstr =~ s/[\s,]+/ /g;
 
286
    $newstr =~ s/[\s,]+/ /g;
 
287
    my @old = split(" ", $oldstr);
 
288
    my @new = split(" ", $newstr);
 
289
 
 
290
    my ($rem, $add) = diff_arrays(\@old, \@new);
 
291
 
 
292
    my $removed = join (", ", @$rem);
 
293
    my $added = join (", ", @$add);
 
294
 
 
295
    return ($removed, $added);
 
296
}
 
297
 
348
298
sub wrap_comment {
349
299
    my ($comment, $cols) = @_;
350
300
    my $wrappedcomment = "";
362
312
        $wrappedcomment .= ($line . "\n");
363
313
      }
364
314
      else {
365
 
        # Due to a segfault in Text::Tabs::expand() when processing tabs with
366
 
        # Unicode (see http://rt.perl.org/rt3/Public/Bug/Display.html?id=52104),
367
 
        # we have to remove tabs before processing the comment. This restriction
368
 
        # can go away when we require Perl 5.8.9 or newer.
369
 
        $line =~ s/\t/    /g;
370
315
        $wrappedcomment .= (wrap('', '', $line) . "\n");
371
316
      }
372
317
    }
406
351
}
407
352
 
408
353
sub format_time {
409
 
    my ($date, $format, $timezone) = @_;
 
354
    my ($date, $format) = @_;
410
355
 
411
 
    # If $format is not set, try to guess the correct date format.
412
 
    if (!$format) {
 
356
    # If $format is undefined, try to guess the correct date format.    
 
357
    my $show_timezone;
 
358
    if (!defined($format)) {
413
359
        if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
414
360
            my $sec = $7;
415
361
            if (defined $sec) {
416
 
                $format = "%Y-%m-%d %T %Z";
 
362
                $format = "%Y-%m-%d %T";
417
363
            } else {
418
 
                $format = "%Y-%m-%d %R %Z";
 
364
                $format = "%Y-%m-%d %R";
419
365
            }
420
366
        } else {
421
 
            # Default date format. See DateTime for other formats available.
422
 
            $format = "%Y-%m-%d %R %Z";
 
367
            # Default date format. See Date::Format for other formats available.
 
368
            $format = "%Y-%m-%d %R";
423
369
        }
424
 
    }
425
 
 
426
 
    # strptime($date) returns an empty array if $date has an invalid date format.
427
 
    my @time = strptime($date);
428
 
 
429
 
    unless (scalar @time) {
430
 
        # If an unknown timezone is passed (such as MSK, for Moskow), strptime() is
431
 
        # unable to parse the date. We try again, but we first remove the timezone.
432
 
        $date =~ s/\s+\S+$//;
433
 
        @time = strptime($date);
434
 
    }
435
 
 
436
 
    if (scalar @time) {
437
 
        # Fix a bug in strptime() where seconds can be undefined in some cases.
438
 
        $time[0] ||= 0;
439
 
 
440
 
        # strptime() counts years from 1900, and months from 0 (January).
441
 
        # We have to fix both values.
442
 
        my $dt = DateTime->new({year   => 1900 + $time[5],
443
 
                                month  => ++$time[4],
444
 
                                day    => $time[3],
445
 
                                hour   => $time[2],
446
 
                                minute => $time[1],
447
 
                                # DateTime doesn't like fractional seconds.
448
 
                                second => int($time[0]),
449
 
                                # If importing, use the specified timezone, otherwise 
450
 
                                # use the timezone specified by the server.
451
 
                                time_zone => Bugzilla->local_timezone->offset_as_string($time[6]) 
452
 
                                          || Bugzilla->local_timezone});
453
 
 
454
 
        # Now display the date using the given timezone,
455
 
        # or the user's timezone if none is given.
456
 
        $dt->set_time_zone($timezone || Bugzilla->user->timezone);
457
 
        $date = $dt->strftime($format);
 
370
        # By default, we want the timezone to be displayed.
 
371
        $show_timezone = 1;
 
372
    }
 
373
    else {
 
374
        # Search for %Z or %z, meaning we want the timezone to be displayed.
 
375
        # Till bug 182238 gets fixed, we assume Bugzilla->params->{'timezone'}
 
376
        # is used.
 
377
        $show_timezone = ($format =~ s/\s?%Z$//i);
 
378
    }
 
379
 
 
380
    # str2time($date) is undefined if $date has an invalid date format.
 
381
    my $time = str2time($date);
 
382
 
 
383
    if (defined $time) {
 
384
        $date = time2str($format, $time);
 
385
        $date .= " " . Bugzilla->params->{'timezone'} if $show_timezone;
458
386
    }
459
387
    else {
460
388
        # Don't let invalid (time) strings to be passed to templates!
484
412
}
485
413
 
486
414
sub bz_crypt {
487
 
    my ($password, $salt) = @_;
488
 
 
489
 
    my $algorithm;
490
 
    if (!defined $salt) {
491
 
        # If you don't use a salt, then people can create tables of
492
 
        # hashes that map to particular passwords, and then break your
493
 
        # hashing very easily if they have a large-enough table of common
494
 
        # (or even uncommon) passwords. So we generate a unique salt for
495
 
        # each password in the database, and then just prepend it to
496
 
        # the hash.
497
 
        $salt = generate_random_password(PASSWORD_SALT_LENGTH);
498
 
        $algorithm = PASSWORD_DIGEST_ALGORITHM;
499
 
    }
500
 
 
501
 
    # We append the algorithm used to the string. This is good because then
502
 
    # we can change the algorithm being used, in the future, without 
503
 
    # disrupting the validation of existing passwords. Also, this tells
504
 
    # us if a password is using the old "crypt" method of hashing passwords,
505
 
    # because the algorithm will be missing from the string.
506
 
    if ($salt =~ /{([^}]+)}$/) {
507
 
        $algorithm = $1;
508
 
    }
509
 
 
510
 
    my $crypted_password;
511
 
    if (!$algorithm) {
512
 
        # Wide characters cause crypt to die
513
 
        if (Bugzilla->params->{'utf8'}) {
514
 
            utf8::encode($password) if utf8::is_utf8($password);
515
 
        }
516
 
    
517
 
        # Crypt the password.
518
 
        $crypted_password = crypt($password, $salt);
519
 
 
520
 
        # HACK: Perl has bug where returned crypted password is considered
521
 
        # tainted. See http://rt.perl.org/rt3/Public/Bug/Display.html?id=59998
522
 
        unless(tainted($password) || tainted($salt)) {
523
 
            trick_taint($crypted_password);
524
 
        } 
525
 
    }
526
 
    else {
527
 
        my $hasher = Digest->new($algorithm);
528
 
        # We only want to use the first characters of the salt, no
529
 
        # matter how long of a salt we may have been passed.
530
 
        $salt = substr($salt, 0, PASSWORD_SALT_LENGTH);
531
 
        $hasher->add($password, $salt);
532
 
        $crypted_password = $salt . $hasher->b64digest . "{$algorithm}";
533
 
    }
 
415
    my ($password) = @_;
 
416
 
 
417
    # The list of characters that can appear in a salt.  Salts and hashes
 
418
    # are both encoded as a sequence of characters from a set containing
 
419
    # 64 characters, each one of which represents 6 bits of the salt/hash.
 
420
    # The encoding is similar to BASE64, the difference being that the
 
421
    # BASE64 plus sign (+) is replaced with a forward slash (/).
 
422
    my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
 
423
 
 
424
    # Generate the salt.  We use an 8 character (48 bit) salt for maximum
 
425
    # security on systems whose crypt uses MD5.  Systems with older
 
426
    # versions of crypt will just use the first two characters of the salt.
 
427
    my $salt = '';
 
428
    for ( my $i=0 ; $i < 8 ; ++$i ) {
 
429
        $salt .= $saltchars[rand(64)];
 
430
    }
 
431
 
 
432
    # Crypt the password.
 
433
    my $cryptedpassword = crypt($password, $salt);
534
434
 
535
435
    # Return the crypted password.
536
 
    return $crypted_password;
 
436
    return $cryptedpassword;
537
437
}
538
438
 
539
439
sub generate_random_password {
600
500
    $vars ||= {};
601
501
    $vars->{'message'} = $name;
602
502
    my $message;
603
 
    if (!$template->process('global/message.txt.tmpl', $vars, \$message)) {
604
 
        require Bugzilla::Error;
605
 
        Bugzilla::Error::ThrowTemplateError($template->error());
606
 
    }
 
503
    $template->process('global/message.txt.tmpl', $vars, \$message)
 
504
        || ThrowTemplateError($template->error());
607
505
    # Remove the indenting that exists in messages.html.tmpl.
608
506
    $message =~ s/^    //gm;
609
507
    return $message;
631
529
    return join(".", unpack("CCCC", pack("N", $addr)));
632
530
}
633
531
 
634
 
sub disable_utf8 {
635
 
    if (Bugzilla->params->{'utf8'}) {
636
 
        binmode STDOUT, ':bytes'; # Turn off UTF8 encoding.
637
 
    }
638
 
}
639
 
 
640
532
1;
641
533
 
642
534
__END__
650
542
  use Bugzilla::Util;
651
543
 
652
544
  # Functions for dealing with variable tainting
 
545
  $rv = is_tainted($var);
653
546
  trick_taint($var);
654
547
  detaint_natural($var);
655
548
  detaint_signed($var);
658
551
  html_quote($var);
659
552
  url_quote($var);
660
553
  xml_quote($var);
661
 
  email_filter($var);
662
554
 
663
555
  # Functions for decoding
664
556
  $rv = url_decode($var);
676
568
 
677
569
  # Functions for manipulating strings
678
570
  $val = trim(" abc ");
 
571
  ($removed, $added) = diff_strings($old, $new);
679
572
  $wrapped = wrap_comment($comment);
680
573
 
681
574
  # Functions for formatting time
713
606
 
714
607
=over 4
715
608
 
 
609
=item C<is_tainted>
 
610
 
 
611
Determines whether a particular variable is tainted
 
612
 
716
613
=item C<trick_taint($val)>
717
614
 
718
615
Tricks perl into untainting a particular variable.
775
672
 
776
673
Converts the %xx encoding from the given URL back to its original form.
777
674
 
778
 
=item C<email_filter>
779
 
 
780
 
Removes the hostname from email addresses in the string, if the user
781
 
currently viewing Bugzilla is logged out. If the user is logged-in,
782
 
this filter just returns the input string.
783
 
 
784
675
=back
785
676
 
786
677
=head2 Environment and Location
807
698
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
808
699
current setting for the C<ssl> parameter.
809
700
 
810
 
=item C<use_attachbase()>
811
 
 
812
 
Returns true if an alternate host is used to display attachments; false
813
 
otherwise.
814
 
 
815
701
=back
816
702
 
817
703
=head2 Searching
856
742
Removes any leading or trailing whitespace from a string. This routine does not
857
743
modify the existing string.
858
744
 
 
745
=item C<diff_strings($oldstr, $newstr)>
 
746
 
 
747
Takes two strings containing a list of comma- or space-separated items
 
748
and returns what items were removed from or added to the new one, 
 
749
compared to the old one. Returns a list, where the first entry is a scalar
 
750
containing removed items, and the second entry is a scalar containing added
 
751
items.
 
752
 
859
753
=item C<wrap_hard($string, $size)>
860
754
 
861
755
Wraps a string, so that a line is I<never> longer than C<$size>.
882
776
Returns true is the string contains only 7-bit characters (ASCII 32 through 126,
883
777
ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).
884
778
 
885
 
=item C<disable_utf8()>
886
 
 
887
 
Disable utf8 on STDOUT (and display raw data instead).
888
 
 
889
779
=item C<clean_text($str)>
890
780
Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
891
781
Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
926
816
 
927
817
=item C<format_time($time)>
928
818
 
929
 
Takes a time and converts it to the desired format and timezone.
930
 
If no format is given, the routine guesses the correct one and returns
931
 
an empty array if it cannot. If no timezone is given, the user's timezone
932
 
is used, as defined in his preferences.
 
819
Takes a time, converts it to the desired format and appends the timezone
 
820
as defined in editparams.cgi, if desired. This routine will be expanded
 
821
in the future to adjust for user preferences regarding what timezone to
 
822
display times in.
933
823
 
934
824
This routine is mainly called from templates to filter dates, see
935
 
"FILTER time" in L<Bugzilla::Template>.
 
825
"FILTER time" in Templates.pm. In this case, $format is undefined and
 
826
the routine has to "guess" the date format that was passed to $dbh->sql_date_format().
 
827
 
936
828
 
937
829
=item C<format_time_decimal($time)>
938
830
 
957
849
 
958
850
=over 4
959
851
 
960
 
=item C<bz_crypt($password, $salt)>
961
 
 
962
 
Takes a string and returns a hashed (encrypted) value for it, using a
963
 
random salt. An optional salt string may also be passed in.
964
 
 
965
 
Please always use this function instead of the built-in perl C<crypt>
966
 
function, when checking or setting a password. Bugzilla does not use
967
 
C<crypt>.
 
852
=item C<bz_crypt($password)>
 
853
 
 
854
Takes a string and returns a C<crypt>ed value for it, using a random salt.
 
855
 
 
856
Please always use this function instead of the built-in perl "crypt"
 
857
when initially encrypting a password.
968
858
 
969
859
=begin undocumented
970
860
 

Loggerhead 1.18.1 is a web-based interface for Bazaar branches