X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=Debbugs%2FCGI%2FBugreport.pm;h=b3f4ec6defafd3cb7287029ba9859abbc8d72743;hb=507e7fb021c286bcb59d961f9f25c89410d451a2;hp=28bc63f771efaf97759e620055c38affc1ae8e77;hpb=c768b2faebc3e56f8e519e6ea3661c79d954defe;p=debbugs.git diff --git a/Debbugs/CGI/Bugreport.pm b/Debbugs/CGI/Bugreport.pm index 28bc63f..b3f4ec6 100644 --- a/Debbugs/CGI/Bugreport.pm +++ b/Debbugs/CGI/Bugreport.pm @@ -31,6 +31,16 @@ use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT); use base qw(Exporter); use IO::Scalar; +use Params::Validate qw(validate_with :types); +use Digest::MD5 qw(md5_hex); +use Debbugs::Mail qw(get_addresses); +use Debbugs::MIME qw(decode_rfc1522 create_mime_message); +use Debbugs::CGI qw(:url :html :util); +use Debbugs::Common qw(globify_scalar english_join); +use Debbugs::UTF8; +use Debbugs::Config qw(:config); +use POSIX qw(strftime); +use Encode qw(decode_utf8 encode_utf8); BEGIN{ ($VERSION) = q$Revision: 494 $ =~ /^Revision:\s+([^\s+])/; @@ -38,7 +48,7 @@ BEGIN{ @EXPORT = (); %EXPORT_TAGS = (); - @EXPORT_OK = (qw(display_entities handle_record handle_email_message)); + @EXPORT_OK = (qw(display_entity handle_record handle_email_message)); Exporter::export_ok_tags(keys %EXPORT_TAGS); $EXPORT_TAGS{all} = [@EXPORT_OK]; } @@ -74,44 +84,46 @@ BEGIN{ =cut sub display_entity { - my %param = valid_with(params => \@_, - spec => {entity => {type => OBJECT, - }, - bug_num => {type => SCALAR, - regex => qr/^\d+$/, - }, - outer => {type => BOOLEAN, - default => 1, - }, - msg_num => {type => SCALAR, - }, - attachments => {type => ARRAYREF, - default => [], - }, - output => {type => SCALARREF|HANDLE, - default => \*STDOUT, - }, - terse => {type => BOOLEAN, - default => 0, - }, - msg => {type => SCALAR, - optional => 1, - }, - attachment => {type => SCALAR, - optional => 1, - }, - } - ); - + my %param = validate_with(params => \@_, + spec => {entity => {type => OBJECT, + }, + bug_num => {type => SCALAR, + regex => qr/^\d+$/, + }, + outer => {type => BOOLEAN, + default => 1, + }, + msg_num => {type => SCALAR, + }, + attachments => {type => ARRAYREF, + default => [], + }, + output => {type => SCALARREF|HANDLE, + default => \*STDOUT, + }, + terse => {type => BOOLEAN, + default => 0, + }, + msg => {type => SCALAR, + optional => 1, + }, + att => {type => SCALAR, + optional => 1, + }, + trim_headers => {type => BOOLEAN, + default => 1, + }, + avatars => {type => BOOLEAN, + default => 1, + }, + } + ); + + my $output = globify_scalar($param{output}); my $entity = $param{entity}; my $ref = $param{bug_num}; - my $top = $param{outer} + my $top = $param{outer}; my $xmessage = $param{msg_num}; - if (defined ref($options) and - ref($param{output}) eq 'SCALAR' and - not UNIVERSAL::isa($param{output},'GLOB')) { - $param{output} = IO::Scalar->new($param{output}); - } my $attachments = $param{attachments}; my $head = $entity->head; @@ -122,56 +134,79 @@ sub display_entity { $filename = '' unless defined $filename; $filename = decode_rfc1522($filename); - if ($top and not $param{terse}) { + if ($param{outer} and + not $param{terse} and + not exists $param{att}) { my $header = $entity->head; - print {$param{output}} "
\n";
-	 if ($trim_headers) {
+	 print {$output} "
\n"; + if ($param{trim_headers}) { my @headers; foreach (qw(From To Cc Subject Date)) { my $head_field = $head->get($_); next unless defined $head_field and $head_field ne ''; - push @headers, qq($_: ) . html_escape(decode_rfc1522($head_field)); + chomp $head_field; + if ($_ eq 'From' and $param{avatars}) { + my $libravatar_url = __libravatar_url(decode_rfc1522($head_field)); + if (defined $libravatar_url and length $libravatar_url) { + push @headers,q(\n); + } + } + push @headers, qq(

$_: ) . html_escape(decode_rfc1522($head_field))."

\n"; } - print {$param{output}} join(qq(), @headers); + print {$output} join(qq(), @headers); } else { - print {$param{output}} html_escape(decode_rfc1522($entity->head->stringify)); + print {$output} "
".html_escape(decode_rfc1522($entity->head->stringify))."
\n"; } - print {$param{output}} "
\n"; + print {$output} "\n"; } - unless (($top and $type =~ m[^text(?:/plain)?(?:;|$)]) or - ($type =~ m[^multipart/])) { - push @$attachments, $entity; - my @dlargs = ($ref, msg=>$xmessage, att=>$#$attachments); - push @dlargs, (filename=>$filename) if $filename ne ''; - my $printname = $filename; - $printname = 'Message part ' . ($#$attachments + 1) if $filename eq ''; - print {$param{output}} '
[$printname } .
-		  "($type, $disposition)]
\n"; - - if (exists $param{msg} and exists $param{att} and - $att == $#$attachments) { + if (not (($param{outer} and $type =~ m{^text(?:/plain)?(?:;|$)}) + or $type =~ m{^multipart/} + )) { + push @$attachments, $param{entity}; + # output this attachment + if (exists $param{att} and + $param{att} == $#$attachments) { my $head = $entity->head; chomp(my $type = $entity->effective_type); my $body = $entity->stringify_body; - print {$param{output}} "Content-Type: $type"; + # 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($output); + binmode($output,':raw'); + print {$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 {$output} qq(; charset="$charset") if defined $charset; + print {$output} "\n"; if ($filename ne '') { my $qf = $filename; $qf =~ s/"/\\"/g; $qf =~ s[.*/][]; - print {$param{output}} qq{Content-Disposition: inline; filename="$qf"\n}; + print {$output} qq{Content-Disposition: inline; filename="$qf"\n}; } - print {$param{output}} "\n"; + print {$output} "\n"; my $decoder = MIME::Decoder->new($head->mime_encoding); - $decoder->decode(new IO::Scalar(\$body), \*STDOUT); - exit(0); + $decoder->decode(IO::Scalar->new(\$body), $output); + if (grep {/utf8/} @layers) { + binmode($output,':utf8'); + } + return; + } + elsif (not exists $param{att}) { + my @dlargs = (msg=>$xmessage, att=>$#$attachments); + push @dlargs, (filename=>$filename) if $filename ne ''; + my $printname = $filename; + $printname = 'Message part ' . ($#$attachments + 1) if $filename eq ''; + print {$output} '
[$printname } .
+				  "($type, $disposition)]
\n"; } } - return if not $top and $disposition eq 'attachment' and not defined($att); + return if not $param{outer} and $disposition eq 'attachment' and not exists $param{att}; return unless ($type =~ m[^text/?] and $type !~ m[^text/(?:html|enriched)(?:;|$)]) or $type =~ m[^application/pgp(?:;|$)] or @@ -184,37 +219,43 @@ sub display_entity { bug_num => $ref, outer => 0, msg_num => $xmessage, - output => $param{output}, + output => $output, attachments => $attachments, terse => $param{terse}, exists $param{msg}?(msg=>$param{msg}):(), - exists $param{attachment}?(attachment=>$param{attachment}):(), + exists $param{att}?(att=>$param{att}):(), + exists $param{avatars}?(avatars=>$param{avatars}):(), ); - print {$param{output}} "\n"; + # print {$output} "\n"; } } elsif ($entity->parts) { # We must be dealing with a nested message. - print {$param{output}} "
\n"; + if (not exists $param{att}) { + print {$output} "
\n"; + } my @parts = $entity->parts; foreach my $part (@parts) { display_entity(entity => $part, bug_num => $ref, outer => 1, msg_num => $xmessage, - ouput => $param{output}, + output => $output, attachments => $attachments, terse => $param{terse}, exists $param{msg}?(msg=>$param{msg}):(), - exists $param{attachment}?(attachment=>$param{attachment}):(), + exists $param{att}?(att=>$param{att}):(), + exists $param{avatars}?(avatars=>$param{avatars}):(), ); - print {$param{output}} "\n"; + # print {$output} "\n"; } - print {$param{output}} "
\n"; + if (not exists $param{att}) { + print {$output} "
\n"; + } } elsif (not $param{terse}) { 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) { @@ -223,19 +264,27 @@ sub display_entity { # flowed e-mails cause they don't really matter. } # Add links to URLs - # We don't html escape here because we escape above - $body =~ s{((ftp|http|https)://[\S~-]+?/?)((\>\;)?[)]?[']?[:.\,]?(\s|$))} - {$1$3}go; + # We don't html escape here because we escape above; + # wierd terminators are because of that + $body =~ s{((?:ftp|http|https|svn|ftps|rsync)://[\S~-]+?/?) # Url + ((?:\>\;)?[)]?(?:'|\&\#39\;)?[:.\,]?(?:\s|$)) # terminators + }{$1$2}gox; # Add links to bug closures $body =~ s[(closes:\s*(?:bug)?\#?\s?\d+(?:,?\s*(?:bug)?\#?\s?\d+)*)] [my $temp = $1; $temp =~ s{(\d+)} - {qq($1) - }ge; + {bug_links(bug=>$1)}ge; $temp;]gxie; - print {$param{output}} qq(
$body
\n); + if (defined $config{cve_tracker} and + length $config{cve_tracker} + ) { + # Add links to CVE vulnerabilities (closes #568464) + $body =~ s{(^|\s)(CVE-\d{4}-\d{4,})(\s|[,.-\[\]]|$)} + {$1$2$3}gxm; + } + if (not exists $param{att}) { + print {$output} qq(
$body
\n); + } } } @@ -244,7 +293,7 @@ sub display_entity { handle_email_message($record->{text}, ref => $bug_number, - msg_number => $msg_number, + msg_num => $msg_number, ); Returns a decoded e-mail message and displays entities/attachments as @@ -254,10 +303,11 @@ appropriate. =cut sub handle_email_message{ - my ($email,%options) = @_; + my ($email,%param) = @_; - my $output = ''; - my $parser = new MIME::Parser; + my $output; + my $output_fh = globify_scalar(\$output); + my $parser = MIME::Parser->new(); # Because we are using memory, not tempfiles, there's no need to # clean up here like in Debbugs::MIME $parser->tmp_to_core(1); @@ -265,17 +315,18 @@ sub handle_email_message{ my $entity = $parser->parse_data( $email); my @attachments = (); display_entity(entity => $entity, - bug_num => $options{ref}, + bug_num => $param{ref}, outer => 1, - msg_number => $options{msg_number}, - ouput => $output, + msg_num => $param{msg_num}, + output => $output_fh, attachments => \@attachments, - terse => $params{terse}, + terse => $param{terse}, exists $param{msg}?(msg=>$param{msg}):(), - exists $param{att}?(attachment=>$param{att}):(), + exists $param{att}?(att=>$param{att}):(), + exists $param{trim_headers}?(trim_headers=>$param{trim_headers}):(), + exists $param{avatars}?(avatars=>$param{avatars}):(), ); - return $output; - + return decode_utf8($output); } =head2 handle_record @@ -289,33 +340,53 @@ should be output to the browser. =cut sub handle_record{ - my ($record,$bug_number,$msg_number,$seen_msg_ids) = @_; + my ($record,$bug_number,$msg_number,$seen_msg_ids,%param) = @_; - 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} =~ //; - my $class = $record->{text} =~ /^(?:Acknowledgement|Reply|Information|Report|Notification)/ ? 'infmessage':'msgreceived'; - $output .= decode_rfc1522($record->{text}); + # $record->{text} is not in perl's internal encoding; convert it + my $text = decode_rfc1522(decode_utf8($record->{text})); + my ($time) = $text =~ //; + my $class = $text =~ /^(?:Acknowledgement|Reply|Information|Report|Notification)/m ? 'infmessage':'msgreceived'; + $output .= $text; # Link to forwarded http:// urls in the midst of the report # (even though these links already exist at the top) - $output =~ s,((?:ftp|http|https)://[\S~-]+?/?)([\)\'\:\.\,]?(?:\s|\.<|$)),$1$2,go; + $output =~ s,((?:ftp|http|https)://[\S~-]+?/?)((?:[\)\'\:\.\,]|\&\#39;)?(?:\s|\.<|$)),$1$2,go; # Add links to the cloned bugs - $output =~ s{(Bug )(\d+)( cloned as bugs? )(\d+)(?:\-(\d+)|)}{$1.bug_links($2).$3.bug_links($4,$5)}eo; + $output =~ s{(Bug )(\d+)( cloned as bugs? )(\d+)(?:\-(\d+)|)}{$1.bug_links(bug=>$2).$3.bug_links(bug=>(defined $5)?[$4..$5]:$4)}eo; # Add links to merged bugs - $output =~ s{(?<=Merged )([\d\s]+)(?=\.)}{join(' ',map {bug_links($_)} (split /\s+/, $1))}eo; + $output =~ s{(?<=Merged )([\d\s]+)(?=\.)}{join(' ',map {bug_links(bug=>$_)} (split /\s+/, $1))}eo; # Add links to blocked bugs $output =~ s{(?<=Blocking bugs)(?:( of )(\d+))?( (?:added|set to|removed):\s+)([\d\s\,]+)} - {(defined $2?$1.bug_links($2):'').$3. - join(' ',map {bug_links($_)} (split /\,?\s+/, $4))}eo; + {(defined $2?$1.bug_links(bug=>$2):'').$3. + english_join([map {bug_links(bug=>$_)} (split /\,?\s+/, $4)])}eo; + $output =~ s{((?:[Aa]dded|[Rr]emoved)\ blocking\ bug(?:\(s\))?)(?:(\ of\ )(\d+))?(:?\s+) + (\d+(?:,\s+\d+)*(?:\,?\s+and\s+\d+)?)} + {$1.(defined $3?$2.bug_links(bug=>$3):'').$4. + english_join([map {bug_links(bug=>$_)} (split /\,?\s+(?:and\s+)?/, $5)])}xeo; + $output =~ s{([Aa]dded|[Rr]emoved)( indication that bug )(\d+)( blocks )([\d\s\,]+)} + {$1.$2.(bug_links(bug=>$3)).$4. + english_join([map {bug_links(bug=>$_)} (split /\,?\s+(?:and\s+)?/, $5)])}eo; # Add links to reassigned packages $output =~ s{(Bug reassigned from package \`)([^']+?)((?:'|\&\#39;) to \`)([^']+?)((?:'|\&\#39;))} - {$1.q($2).$3.q($4).$5}eo; + {$1.q($2).$3.q($4).$5}eo; if (defined $time) { $output .= ' ('.strftime('%a, %d %b %Y %T GMT',gmtime($time)).') '; } - $output .= 'Full text and rfc822 format available.'; + $output .= 'Full text and rfc822 format available.'; $output = qq(

\n\n) . $output . "
\n"; } @@ -328,11 +399,12 @@ sub handle_record{ $$seen_msg_ids{$msg_id} = 1; } $output .= qq(

\n); - $output .= 'View this message in rfc822 format

'; + $output .= 'View this message in rfc822 format

'; $output .= handle_email_message($record->{text}, - ref => $bug_number, - msg_number => $msg_number, - ); + ref => $bug_number, + msg_num => $msg_number, + %param, + ); } elsif (/autocheck/) { # Do nothing @@ -349,12 +421,18 @@ sub handle_record{ my ($received,$hostname) = $record->{text} =~ m/Received: \(at (\S+)\) by (\S+)\;/; $output .= qq|

Message #$msg_number received at |. html_escape("$received\@$hostname") . - q| (full text'. - q|, mbox)'.":

\n"; + q| (full text'. + q|, mbox)'.":

\n"; $output .= handle_email_message($record->{text}, - ref => $bug_number, - msg_number => $msg_number, - ); + ref => $bug_number, + msg_num => $msg_number, + %param, + ); } else { die "Unknown record type $_"; @@ -363,6 +441,15 @@ sub handle_record{ } +sub __libravatar_url { + my ($email) = @_; + if (not defined $config{libravatar_uri} or not length $config{libravatar_uri}) { + return undef; + } + ($email) = get_addresses($email); + return $config{libravatar_uri}.md5_hex(lc($email)).($config{libravatar_uri_options}//''); +} + 1;