]> git.donarmstrong.com Git - debbugs.git/commitdiff
Merge branch 'don/processencoding' of git+ssh://git.donarmstrong.com/srv/git/debbugs...
authorDon Armstrong <don@donarmstrong.com>
Sun, 8 Jul 2012 03:04:29 +0000 (20:04 -0700)
committerDon Armstrong <don@donarmstrong.com>
Sun, 8 Jul 2012 03:04:29 +0000 (20:04 -0700)
13 files changed:
Debbugs/CGI/Bugreport.pm
Debbugs/Common.pm
Debbugs/Control.pm
Debbugs/Log.pm
Debbugs/MIME.pm
Debbugs/SOAP.pm
Debbugs/Status.pm
Debbugs/Text.pm
cgi/bugreport.cgi
cgi/pkgreport.cgi
t/01_mime.t
t/02_common.t [new file with mode: 0644]
t/05_mail.t

index 02e6fba3a84431f773ab6077349bd549f64d29cc..0b19bab636029b1d0324fe6fffc7b242ff6c8587 100644 (file)
@@ -37,6 +37,7 @@ use Debbugs::CGI qw(:url :html :util);
 use Debbugs::Common qw(globify_scalar english_join);
 use Debbugs::Config qw(:config);
 use POSIX qw(strftime);
+use Encode qw(decode_utf8);
 
 BEGIN{
      ($VERSION) = q$Revision: 494 $ =~ /^Revision:\s+([^\s+])/;
@@ -156,10 +157,14 @@ sub display_entity {
            my $head = $entity->head;
            chomp(my $type = $entity->effective_type);
            my $body = $entity->stringify_body;
+           # this attachment has its own content type, so we must not
+           # try to convert it to UTF-8 or do anything funky.
+           my @layers = PerlIO::get_layers($param{output});
+           binmode($param{output},':raw');
            print {$param{output}} "Content-Type: $type";
            my ($charset) = $head->get('Content-Type:') =~ m/charset\s*=\s*\"?([\w-]+)\"?/i;
            print {$param{output}} qq(; charset="$charset") if defined $charset;
-           print {$param{output}}"\n";
+           print {$param{output}} "\n";
            if ($filename ne '') {
                my $qf = $filename;
                $qf =~ s/"/\\"/g;
@@ -169,6 +174,9 @@ sub display_entity {
            print {$param{output}} "\n";
            my $decoder = MIME::Decoder->new($head->mime_encoding);
            $decoder->decode(IO::Scalar->new(\$body), $param{output});
+           if (grep {/utf8/} @layers) {
+               binmode($param{output},':utf8');
+           }
            return;
        }
        elsif (not exists $param{att}) {
@@ -232,7 +240,7 @@ sub display_entity {
         my $content_type = $entity->head->get('Content-Type:') || "text/html";
         my ($charset) = $content_type =~ m/charset\s*=\s*\"?([\w-]+)\"?/i;
         my $body = $entity->bodyhandle->as_string;
-        $body = convert_to_utf8($body,$charset) if defined $charset;
+        $body = convert_to_utf8($body,$charset//'utf8');
         $body = html_escape($body);
         # Attempt to deal with format=flowed
         if ($content_type =~ m/format\s*=\s*\"?flowed\"?/i) {
@@ -282,7 +290,9 @@ appropriate.
 sub handle_email_message{
      my ($email,%param) = @_;
 
-     my $output = '';
+     # output needs to have the is_utf8 flag on to avoid double
+     # encoding
+     my $output = decode_utf8('');
      my $parser = MIME::Parser->new();
      # Because we are using memory, not tempfiles, there's no need to
      # clean up here like in Debbugs::MIME
@@ -318,7 +328,9 @@ should be output to the browser.
 sub handle_record{
      my ($record,$bug_number,$msg_number,$seen_msg_ids) = @_;
 
-     my $output = '';
+     # output needs to have the is_utf8 flag on to avoid double
+     # encoding
+     my $output = decode_utf8('');
      local $_ = $record->{type};
      if (/html/) {
          my ($time) = $record->{text} =~ /<!--\s+time:(\d+)\s+-->/;
index 880989843d1cf32f7e52cd1bae317cada4cee633..f3c8218ea66cf1cc8e63e6ac77e22fd31d5a2f47 100644 (file)
@@ -50,12 +50,13 @@ BEGIN{
                                qw(cleanup_eval_fail),
                                qw(hash_slice),
                               ],
+                    utf8   => [qw(encode_utf8_structure)],
                     date   => [qw(secs_to_english)],
                     quit   => [qw(quit)],
                     lock   => [qw(filelock unfilelock lockpid)],
                    );
      @EXPORT_OK = ();
-     Exporter::export_ok_tags(qw(lock quit date util misc));
+     Exporter::export_ok_tags(keys %EXPORT_TAGS);
      $EXPORT_TAGS{all} = [@EXPORT_OK];
 }
 
@@ -70,6 +71,8 @@ use IO::Scalar;
 use Debbugs::MIME qw(decode_rfc1522);
 use Mail::Address;
 use Cwd qw(cwd);
+use Encode qw(encode_utf8 is_utf8);
+use Storable qw(dclone);
 
 use Params::Validate qw(validate_with :types);
 
@@ -429,6 +432,7 @@ sub __add_to_hash {
     $type //= 'address';
     my $fh = IO::File->new($fn,'r') or
        die "Unable to open $fn for reading: $!";
+    binmode($fh,':encoding(UTF-8)');
     while (<$fh>) {
        chomp;
        next unless m/^(\S+)\s+(\S.*\S)\s*$/;
@@ -823,7 +827,8 @@ sub globify_scalar {
          if (defined ref($scalar)) {
               if (ref($scalar) eq 'SCALAR' and
                   not UNIVERSAL::isa($scalar,'GLOB')) {
-                   return IO::Scalar->new($scalar);
+                   open $handle, '>:scalar:utf8', $scalar;
+                   return $handle;
               }
               else {
                    return $scalar;
@@ -836,7 +841,7 @@ sub globify_scalar {
               carp "Given a non-scalar reference, non-glob to globify_scalar; returning /dev/null handle";
          }
      }
-     return IO::File->new('/dev/null','w');
+     return IO::File->new('/dev/null','>:utf8');
 }
 
 =head2 cleanup_eval_fail()
@@ -894,6 +899,56 @@ sub hash_slice(\%@) {
     return map {exists $hashref->{$_}?($_,$hashref->{$_}):()} @keys;
 }
 
+
+=head1 UTF-8
+
+These functions are exported with the :utf8 tag
+
+=head2 encode_utf8_structure
+
+     %newdata = encode_utf8_structure(%newdata);
+
+Takes a complex data structure and encodes any strings with is_utf8
+set into their constituent octets.
+
+=cut
+
+our $depth = 0;
+sub encode_utf8_structure {
+    ++$depth;
+    my @ret;
+    for my $_ (@_) {
+       if (ref($_) eq 'HASH') {
+           push @ret, {encode_utf8_structure(%{$depth == 1 ? dclone($_):$_})};
+       }
+       elsif (ref($_) eq 'ARRAY') {
+           push @ret, [encode_utf8_structure(@{$depth == 1 ? dclone($_):$_})];
+       }
+       elsif (ref($_)) {
+           # we don't know how to handle non hash or non arrays
+           push @ret,$_;
+       }
+       else {
+           push @ret,__encode_utf8($_);
+       }
+    }
+    --$depth;
+    return @ret;
+}
+
+sub __encode_utf8 {
+    my @ret;
+    for my $r (@_) {
+       if (not ref($r) and is_utf8($r)) {
+           $r = encode_utf8($r);
+       }
+       push @ret,$r;
+    }
+    return @ret;
+}
+
+
+
 1;
 
 __END__
index ed69c84bd4147a3824a4709713937d315ccf8a1c..631504a8d5420a24db292d736732201a3ad9113b 100644 (file)
@@ -996,7 +996,7 @@ sub set_done {
                                                             headers =>
                                                             [To => $data->{submitter},
                                                              Subject => "$config{ubug}#$data->{bug_num} ".
-                                                             "closed by $param{requester} ($param{request_subject})",
+                                                             "closed by $param{requester} ".(defined $param{request_subject}?"($param{request_subject})":""),
                                                             ],
                                                            )
                                            ],
index 20e13503441df9521913a4ed7308e8c0a21ea08e..96748b7ee401813e6a096bf825a33122caca9daf 100644 (file)
@@ -201,6 +201,7 @@ sub new
              die "Unable to open bug log $bug_log for reading: $!";
     }
 
+    binmode($self->{logfh},':utf8');
     $self->{state} = 'kill-init';
     $self->{linenum} = 0;
     return $self;
index e94733ee016db8de4213700a7581bb3b6a00e285..05534e3e054c47d2cb281f683aaacf1a0ac7a52e 100644 (file)
@@ -229,9 +229,7 @@ sub convert_to_utf8 {
      return $data if $charset eq 'raw' or is_utf8($data,1);
      my $result;
      eval {
-         # this encode/decode madness is to make sure that the data
-         # really is valid utf8 and that the is_utf8 flag is off.
-         $result = encode("utf8",decode($charset,$data))
+        $result = decode($charset,$data);
      };
      if ($@) {
          warn "Unable to decode charset; '$charset' and '$data': $@";
@@ -286,6 +284,9 @@ sub encode_rfc1522 {
 
      # handle being passed undef properly
      return undef if not defined $rawstr;
+     if (is_utf8($rawstr)) {
+        $rawstr= encode_utf8($rawstr);
+     }
      # We process words in reverse so we can preserve spacing between
      # encoded words. This regex splits on word|nonword boundaries and
      # nonword|nonword boundaries. We also consider parenthesis and "
index db066bfd870d4c67902b3f51ad5a2b349556b072..5cb08bb0f11eff7de6ab49dea880731a13dcb8b3 100644 (file)
@@ -24,6 +24,7 @@ None known.
 use warnings;
 use strict;
 use vars qw($DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
+use Debbugs::SOAP::Server;
 use base qw(Exporter SOAP::Server::Parameters);
 
 BEGIN{
@@ -38,12 +39,12 @@ BEGIN{
 
 }
 
-
 use IO::File;
 use Debbugs::Status qw(get_bug_status);
-use Debbugs::Common qw(make_list getbuglocation getbugcomponent);
+use Debbugs::Common qw(make_list getbuglocation getbugcomponent :utf8);
 use Debbugs::Packages;
-use Storable qw(nstore retrieve);
+
+use Storable qw(nstore retrieve dclone);
 use Scalar::Util qw(looks_like_number);
 
 
@@ -75,7 +76,7 @@ sub get_usertag {
               delete $ut{$tag} unless exists $tags{$tag};
          }
      }
-     return \%ut;
+     return encode_utf8_structure(\%ut);
 }
 
 
@@ -142,7 +143,7 @@ sub get_status {
          }
      }
 #     __prepare_response($self);
-     return \%status;
+     return encode_utf8_structure(\%status);
 }
 
 =head2 get_bugs
@@ -172,7 +173,7 @@ sub get_bugs{
      my %params = __collapse_params(@params);
      my @bugs;
      @bugs = Debbugs::Bugs::get_bugs(%params);
-     return \@bugs;
+     return encode_utf8_structure(\@bugs);
 }
 
 =head2 newest_bugs
@@ -188,7 +189,7 @@ sub newest_bugs{
      my $VERSION = __populate_version(pop);
      my ($self,$num) = @_;
      my $newest_bug = Debbugs::Bugs::newest_bug();
-     return [($newest_bug - $num + 1) .. $newest_bug];
+     return encode_utf8_structure([($newest_bug - $num + 1) .. $newest_bug]);
 
 }
 
@@ -249,7 +250,7 @@ sub get_bug_log{
                          msg_num => $current_msg,
                         };
      }
-     return \@messages;
+     return encode_utf8_structure(\@messages);
 }
 
 =head2 binary_to_source
@@ -276,13 +277,13 @@ sub binary_to_source{
      my ($self,@params) = @_;
 
      if ($VERSION <= 1) {
-        return [Debbugs::Packages::binary_to_source(binary => $params[0],
+        return encode_utf8_structure([Debbugs::Packages::binary_to_source(binary => $params[0],
                                                     (@params > 1)?(version => $params[1]):(),
                                                     (@params > 2)?(arch    => $params[2]):(),
-                                                   )];
+                                                   )]);
      }
      else {
-        return [Debbugs::Packages::binary_to_source(@params)];
+        return encode_utf8_structure([Debbugs::Packages::binary_to_source(@params)]);
      }
 }
 
@@ -304,7 +305,7 @@ sub source_to_binary {
      my $VERSION = __populate_version(pop);
      my ($self,@params) = @_;
 
-     return [Debbugs::Packages::sourcetobinary(@params)];
+     return encode_utf8_structure([Debbugs::Packages::sourcetobinary(@params)]);
 }
 
 =head2 get_versions
@@ -349,7 +350,7 @@ sub get_versions{
      my $VERSION = __populate_version(pop);
      my ($self,@params) = @_;
 
-     return scalar Debbugs::Packages::get_versions(@params);
+     return encode_utf8_structure(scalar Debbugs::Packages::get_versions(@params));
 }
 
 =head1 VERSION COMPATIBILITY
index d44d0bdea277da4add795748ab9c200cabafc3b1..cf6918aa86136325b46444df25a7bd946421ff13 100644 (file)
@@ -37,7 +37,7 @@ use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
 use base qw(Exporter);
 
 use Params::Validate qw(validate_with :types);
-use Debbugs::Common qw(:util :lock :quit :misc);
+use Debbugs::Common qw(:util :lock :quit :misc :utf8);
 use Debbugs::Config qw(:config);
 use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522);
 use Debbugs::Packages qw(makesourceversions make_source_versions getversions get_versions binary_to_source);
@@ -45,7 +45,7 @@ use Debbugs::Versions;
 use Debbugs::Versions::Dpkg;
 use POSIX qw(ceil);
 use File::Copy qw(copy);
-use Encode qw(decode encode);
+use Encode qw(decode encode is_utf8);
 
 use Storable qw(dclone);
 use List::Util qw(min max);
@@ -217,6 +217,7 @@ sub read_bug{
        }
        return undef;
     }
+    binmode($status_fh,':encoding(UTF-8)');
 
     my %data;
     my @lines;
@@ -240,9 +241,6 @@ sub read_bug{
 
     my %namemap = reverse %fields;
     for my $line (@lines) {
-       eval {
-           $line = decode("utf8",$line,Encode::FB_CROAK);
-       };
         if ($line =~ /(\S+?): (.*)/) {
             my ($name, $value) = (lc $1, $2);
            # this is a bit of a hack; we should never, ever have \r
@@ -253,9 +251,13 @@ sub read_bug{
         }
     }
     for my $field (keys %fields) {
-        $data{$field} = '' unless exists $data{$field};
+       $data{$field} = '' unless exists $data{$field};
+    }
+    if ($version < 3) {
+       for my $field (@rfc1522_fields) {
+           $data{$field} = decode_rfc1522($data{$field});
+       }
     }
-
     $data{severity} = $config{default_severity} if $data{severity} eq '';
     for my $field (qw(found_versions fixed_versions found_date fixed_date)) {
         $data{$field} = [split ' ', $data{$field}];
@@ -268,11 +270,6 @@ sub read_bug{
               @{$data{"${field}_date"}});
     }
 
-    if ($version < 3) {
-       for my $field (@rfc1522_fields) {
-           $data{$field} = decode_rfc1522($data{$field});
-       }
-    }
     my $status_modified = (stat($status))[9];
     # Add log last modified time
     $data{log_modified} = (stat($log))[9];
@@ -596,7 +593,7 @@ version.
 
 sub makestatus {
     my ($data,$version) = @_;
-    $version = 2 unless defined $version;
+    $version = 3 unless defined $version;
 
     my $contents = '';
 
@@ -609,6 +606,8 @@ sub makestatus {
     }
     %newdata = %{join_status_fields(\%newdata)};
 
+    %newdata = encode_utf8_structure(%newdata);
+
     if ($version < 3) {
         for my $field (@rfc1522_fields) {
             $newdata{$field} = encode_rfc1522($newdata{$field});
@@ -645,9 +644,6 @@ sub makestatus {
             }
         }
     }
-    eval {
-       $contents = encode("utf8",$contents,Encode::FB_CROAK);
-    };
     return $contents;
 }
 
@@ -667,15 +663,23 @@ sub writebug {
     my ($ref, $data, $location, $minversion, $disablebughook) = @_;
     my $change;
 
-    my %outputs = (1 => 'status', 2 => 'summary');
+    my %outputs = (1 => 'status', 3 => 'summary');
     for my $version (keys %outputs) {
         next if defined $minversion and $version < $minversion;
         my $status = getbugcomponent($ref, $outputs{$version}, $location);
         die "can't find location for $ref" unless defined $status;
-        open(S,"> $status.new") || die "opening $status.new: $!";
-        print(S makestatus($data, $version)) ||
+       my $sfh;
+       if ($version >= 3) {
+           open $sfh,">","$status.new"  or
+               die "opening $status.new: $!";
+       }
+       else {
+           open $sfh,">","$status.new"  or
+               die "opening $status.new: $!";
+       }
+        print {$sfh} makestatus($data, $version) or
             die "writing $status.new: $!";
-        close(S) || die "closing $status.new: $!";
+        close($sfh) or die "closing $status.new: $!";
         if (-e $status) {
             $change = 'change';
         } else {
index f5ede9d699f9791b086a49eb7f06c69445d52b70..353392013c90e621780d1febc76290e40954d075 100644 (file)
@@ -141,6 +141,7 @@ sub fill_in_template{
         ref(\$param{template}) eq 'GLOB') {
          $tt_type = 'FILE_HANDLE';
          $tt_source = $param{template};
+         binmode($tt_source,":encoding(UTF-8)");
      }
      elsif (ref($param{template}) eq 'SCALAR') {
          $tt_type = 'STRING';
@@ -193,17 +194,23 @@ sub fill_in_template{
      my $tt;
      if ($tt_type eq 'FILE' and
         defined $tt_templates{$tt_source} and
+        ($tt_templates{$tt_source}{mtime} + 60) < time and
         (stat $tt_source)[9] <= $tt_templates{$tt_source}{mtime}
        ) {
          $tt = $tt_templates{$tt_source}{template};
      }
      else {
+        my $passed_source = $tt_source;
+        my $passed_type = $tt_type;
          if ($tt_type eq 'FILE') {
               $tt_templates{$tt_source}{mtime} =
                    (stat $tt_source)[9];
+              $passed_source = IO::File->new($tt_source,'r');
+              binmode($passed_source,":encoding(UTF-8)");
+              $passed_type = 'FILEHANDLE';
          }
-         $tt = Text::Template->new(TYPE => $tt_type,
-                                   SOURCE => $tt_source,
+         $tt = Text::Template->new(TYPE => $passed_type,
+                                   SOURCE => $passed_source,
                                    UNTAINT => 1,
                                   );
          if ($tt_type eq 'FILE') {
index 8ad688f6923b501ea836557574b2a63f881c738a..de4ec76371633cfe8d9760a14a7f6e2142df6250 100755 (executable)
@@ -8,6 +8,9 @@ BEGIN{
     delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
 }
 
+# STDOUT should be using the utf8 io layer
+binmode(STDOUT,':utf8');
+
 use POSIX qw(strftime);
 use MIME::Parser;
 use MIME::Decoder;
@@ -168,6 +171,7 @@ if (defined($msg) and ($msg-1) <= $#records) {
 }
 my @log;
 if ( $mbox ) {
+     binmode(STDOUT,":raw");
      my $date = strftime "%a %b %d %T %Y", localtime;
      if (@records > 1) {
         print $q->header(-type => "text/plain",
@@ -242,6 +246,7 @@ END
 
 else {
      if (defined $att and defined $msg and @records) {
+        binmode(STDOUT,":raw");
          $msg_num++;
          print handle_email_message($records[0]->{text},
                                     ref => $ref,
index 4f4ea800af986fcd38f0c58ba04dd8e121a41a95..d29c3abc7ff56943a7a0a2196be16e5560bda857 100755 (executable)
@@ -18,6 +18,9 @@ BEGIN{
     delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
 }
 
+# STDOUT should be in utf8 mode
+binmode(STDOUT,':utf8');
+
 use POSIX qw(strftime nice);
 
 use Debbugs::Config qw(:globals :text :config);
index 10bf167ae7410888384d26dc078bdca73a3714b5..5599b304ca25a791c441152497335ba84792f5fb 100644 (file)
@@ -27,7 +27,7 @@ END
 
 # 1: test decode
 ok(Debbugs::MIME::decode_rfc1522(q(=?iso-8859-1?Q?D=F6n_Armstr=F3ng?= <don@donarmstrong.com>)) eq
-  encode_utf8(q(Dön Armstróng <don@donarmstrong.com>)),"decode_rfc1522 decodes and converts to UTF8 properly");
+  q(Dön Armstróng <don@donarmstrong.com>),"decode_rfc1522 decodes and converts to UTF8 properly");
 
 
 # 2: test encode
diff --git a/t/02_common.t b/t/02_common.t
new file mode 100644 (file)
index 0000000..d770d3f
--- /dev/null
@@ -0,0 +1,14 @@
+# -*- mode: cperl;-*-
+
+use Test::More tests => 2;
+use Encode qw(decode_utf8);
+
+use_ok('Debbugs::Common');
+is_deeply(Debbugs::Common::encode_utf8_structure(
+          {a => decode_utf8('föö'),
+          b => [map {decode_utf8($_)} qw(blëh bl♥h)],
+         }),
+         {a => 'föö',
+          b => [qw(blëh bl♥h)],
+         },
+        );
index e92bde14ae2cd72f8776cc1c27a9313e36869be3..3d21049c579ef9e3c994ce261fc701e674ac7fac 100644 (file)
@@ -11,6 +11,7 @@ use utf8;
 use UNIVERSAL;
 
 use Debbugs::MIME qw(decode_rfc1522);
+use Encode qw(encode_utf8);
 
 use_ok('Debbugs::Mail');
 
@@ -24,7 +25,7 @@ blah blah blah
 END
 
 # 1: test decode
-ok(decode_rfc1522(Debbugs::Mail::encode_headers($test_str)) eq $test_str);
+ok(decode_rfc1522(Debbugs::Mail::encode_headers($test_str)) eq encode_utf8($test_str));
 
 # XXX Figure out a good way to test the send message bit of
 # Debbugs::Mail