1185 lines
28 KiB
Perl
Executable file
1185 lines
28 KiB
Perl
Executable file
#!/usr/bin/perl -Tw
|
|
#
|
|
# A "More Useful" GNATS query-pr Interface
|
|
#
|
|
# Copyright (C) 2006, Shaun Amott <shaun@FreeBSD.org>
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
# SUCH DAMAGE.
|
|
#
|
|
# $FreeBSD: www/en/cgi/query-pr.cgi,v 1.64 2008/01/26 15:53:55 shaun Exp $
|
|
#
|
|
|
|
#
|
|
# Note: this is a script to run on a webserver. If you want to do tests
|
|
# on the command-line, use the QUERY_STRING environment variable to
|
|
# pass parameters to the script:
|
|
# $ QUERY_STRING=pr=bin/106049 ./query-pr.cgi
|
|
#
|
|
|
|
use strict;
|
|
|
|
use MIME::Base64; # ports/converters/p5-MIME-Base64
|
|
use MIME::QuotedPrint; #
|
|
use Convert::UU qw(uudecode uuencode); # ports/converters/p5-Convert-UU
|
|
|
|
require './cgi-style.pl';
|
|
require './query-pr-lib.pl';
|
|
|
|
use constant HTTP_HEADER => "Content-type: text/html; charset=UTF-8\r\n\r\n";
|
|
use constant HTTP_HEADER_PATCH => "Content-type: text/plain; charset=UTF-8\r\nContent-Disposition: inline; filename=\"%s\"\r\n\r\n";
|
|
|
|
use constant SECT_HEADER => 1;
|
|
use constant SECT_SFIELDS => 2;
|
|
use constant SECT_MFIELDS => 3;
|
|
|
|
use constant ENCODING_BASE64 => 1;
|
|
use constant ENCODING_QP => 2;
|
|
|
|
use constant PATCH_ANY => 0x0001;
|
|
use constant PATCH_DIFF => 0x0002;
|
|
use constant PATCH_UUENC => 0x0004;
|
|
use constant PATCH_UUENC_BIN => 0x0008;
|
|
use constant PATCH_SHAR => 0x0010;
|
|
use constant PATCH_BASE64 => 0x0020;
|
|
|
|
my @fields_single = (
|
|
"Number", "Category", "Synopsis", "Confidential",
|
|
"Severity", "Priority", "Responsible", "State",
|
|
"Quarter", "Keywords", "Date-Required", "Class",
|
|
"Submitter-Id", "Arrival-Date", "Closed-Date", "Last-Modified",
|
|
"Originator", "Release",
|
|
);
|
|
|
|
my @fields_multiple = (
|
|
"Organization", "Environment", "Description", "How-To-Repeat",
|
|
"Fix", "Release-Note", "Audit-Trail", "Unformatted",
|
|
);
|
|
|
|
my $fields_skip = "Confidential|Quarter|Keywords|Date-Required|Submitter-Id";
|
|
|
|
my $valid_category = '[a-z0-9][A-Za-z0-9-_]{1,25}';
|
|
my $valid_pr = '\d{1,8}';
|
|
|
|
my $binary_filetypes = '(?:\.gz|\.bz2|\.zip|\.tar)$';
|
|
|
|
my %fmt;
|
|
|
|
my $f = "";
|
|
my $PR = -1;
|
|
my $getpatch = -1;
|
|
my $mimepatch = "";
|
|
my $inpatch = 0;
|
|
my $patchendhint = 0;
|
|
my $category;
|
|
my @query;
|
|
my (%header, %sfields, %mfields);
|
|
|
|
my $iscgi = defined $ENV{'SCRIPT_NAME'};
|
|
|
|
my $fromwebform = 0;
|
|
|
|
$ENV{'PATH'} = "/bin:/usr/bin:/usr/sbin:/sbin:/usr/local/bin";
|
|
|
|
$ENV{'QUERY_STRING'} ||= "";
|
|
$ENV{'SCRIPT_NAME'} ||= $0;
|
|
|
|
# Junk from cgi-style.pl
|
|
$main::hsty_base ||= "";
|
|
$main::t_style ||= "";
|
|
|
|
my $scriptname = htmlclean($ENV{'SCRIPT_NAME'});
|
|
my $querystring = htmlclean($ENV{'QUERY_STRING'});
|
|
|
|
# Do not change $self_url_base, unless you understand what it is for!
|
|
# In particular: it is used as a delimiter between comments in the
|
|
# Audit-Trail.
|
|
my $self_url_base = "http://www.FreeBSD.org/cgi/query-pr.cgi?pr=";
|
|
my $cvsweb_url = "http://www.FreeBSD.org/cgi/cvsweb.cgi/";
|
|
my $stylesheet = "$main::hsty_base/layout/css/query-pr.css";
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Format strings
|
|
#-----------------------------------------------------------------------
|
|
|
|
$fmt{'header_thead'} = <<EOF;
|
|
<table class="headtable">
|
|
EOF
|
|
|
|
$fmt{'header_tfoot'} = <<EOF;
|
|
</table><br />
|
|
EOF
|
|
|
|
$fmt{'header_trow'} = <<EOF;
|
|
<tr><td class="key">%%(1):</td><td class="val">%%(2)</td></tr>
|
|
EOF
|
|
|
|
$fmt{'sfields_thead'} = <<EOF;
|
|
<table class="headtable">
|
|
EOF
|
|
|
|
$fmt{'sfields_trow'} = <<EOF;
|
|
<tr><td class="key">%%(1):</td><td class="val">%%(2)</td></tr>
|
|
EOF
|
|
|
|
$fmt{'sfields_tfoot'} = <<EOF;
|
|
</table><br />
|
|
EOF
|
|
|
|
$fmt{'mfields_header'} = <<EOF;
|
|
<table class="headtable"><tr><td class="blkhead">%%(1):</td></tr></table>
|
|
<div class="mfield">
|
|
EOF
|
|
$fmt{'mfields_header'} =~ s/\n+$//;
|
|
|
|
$fmt{'mfields_footer'} = <<EOF;
|
|
</div>
|
|
EOF
|
|
|
|
$fmt{'patchblock_thead'} = <<EOF;
|
|
<table class="patchblock" cellspacing="0" cellpadding="3">
|
|
<tr class="info"><td>
|
|
<b>Download <a href="${scriptname}?prp=%%(pr)-%%(1)-%%(3)&n=/%%(2)">%%(2)</a></b>
|
|
</td></tr>
|
|
<tr><td class="content"><pre>
|
|
EOF
|
|
$fmt{'patchblock_thead'} =~ s/\n+$//;
|
|
|
|
$fmt{'patchblock_tfoot'} = <<EOF;
|
|
</pre></td></tr>
|
|
</table><br />
|
|
EOF
|
|
$fmt{'patchblock_tfoot'} =~ s/\n+$//;
|
|
$fmt{'patchblock_tfoot'} =~ s/^\n+//;
|
|
|
|
$fmt{'auditblock_thead'} = <<EOF;
|
|
<table class="auditblock" cellspacing="0" cellpadding="3">
|
|
<tr class="info"><td colspan="2"><b>%%(1) Changed</b></td></tr>
|
|
EOF
|
|
|
|
$fmt{'auditblock_tfoot'} = <<EOF;
|
|
</table>
|
|
<br />
|
|
EOF
|
|
|
|
$fmt{'auditblock_trow'} = <<EOF;
|
|
<tr><td class="key" valign="top">%%(1):</td><td valign="top">%%(2)</td></tr>
|
|
EOF
|
|
|
|
$fmt{'responseblock_thead'} = <<EOF;
|
|
<table class="replyblock" cellspacing="0" cellpadding="3">
|
|
<tr><td class="info" colspan="2"><b>Reply via E-mail</b></td></tr>
|
|
EOF
|
|
|
|
$fmt{'responseblock_tfoot'} = <<EOF;
|
|
</table><br />
|
|
EOF
|
|
|
|
$fmt{'responseblock_textfoot'} = <<EOF;
|
|
</td></tr>
|
|
EOF
|
|
|
|
$fmt{'responseblock_texthead'} = <<EOF;
|
|
<tr><td colspan="2">
|
|
EOF
|
|
|
|
$fmt{'responseblock_trow'} = <<EOF;
|
|
<tr><td class="key"><b>%%(1):</b></td><td class="val">%%(2)</td></tr>
|
|
EOF
|
|
|
|
$fmt{'unexpectedtext_thead'} = <<EOF;
|
|
|
|
EOF
|
|
|
|
$fmt{'unexpectedtext_tfoot'} = <<EOF;
|
|
<br />
|
|
EOF
|
|
|
|
$fmt{'html_footerlinks'} = <<EOF;
|
|
<div>
|
|
<a href="%%(maillink)">Submit Followup</a>
|
|
| <a href="${scriptname}?pr=%%(pr)&f=raw">Raw PR</a>
|
|
| <a href="query-pr-summary.cgi?query">Find another PR</a>
|
|
</div>
|
|
EOF
|
|
|
|
$fmt{'trylatermsg'} = <<EOF;
|
|
<p>
|
|
Please <a href="${scriptname}?${querystring}">try again</a> later.
|
|
</p>
|
|
EOF
|
|
|
|
$fmt{'mime_boundary'} = <<EOF;
|
|
<hr class="mimeboundary" />
|
|
EOF
|
|
|
|
$fmt{'quote_level_0'} = '<span class="quote0">> ';
|
|
$fmt{'quote_level_1'} = '<span class="quote1">> ';
|
|
$fmt{'quote_end'} = '</span>';
|
|
|
|
$fmt{'empty'} = ' ';
|
|
$fmt{'break'} = "<br />\n";
|
|
|
|
# From cgi-style.pl
|
|
$main::t_style = "<link href=\"${stylesheet}\" rel=\"stylesheet\" type=\"text/css\" />";
|
|
$main::t_style .= qq{\n<link rel="search" type="application/opensearchdescription+xml" href="http://www.freebsd.org/search/opensearch/query-pr.xml" title="FreeBSD Bugs" />\n};
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Begin Code
|
|
#-----------------------------------------------------------------------
|
|
|
|
if ($ENV{'QUERY_STRING'}) {
|
|
foreach (split(/&/, $ENV{'QUERY_STRING'})) {
|
|
my ($key, $val) = map { s/%([0-9a-f]{2})/chr hex $1/egi; $_ }
|
|
split /=/;
|
|
$f = lc $val if ($key eq "f");
|
|
$PR = lc $val if ($key eq "pr" or $key eq "q");
|
|
$PR = lc $key if ($key =~ /^(?:$valid_category\/)?$valid_pr$/i);
|
|
$category = lc $val if ($key eq "cat");
|
|
$getpatch = lc $val if ($key eq "getpatch");
|
|
|
|
if ($key eq "prp") {
|
|
if ( lc ($val) =~ /^(\d+)-(\d+)-(\w+)$/ ) {
|
|
$PR = $1;
|
|
$getpatch = $2;
|
|
$mimepatch = $3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unless (!$iscgi) {
|
|
if ($getpatch > 0 or $f eq "raw") {
|
|
if ($mimepatch eq "diff") {
|
|
printf HTTP_HEADER_PATCH, "patch-$getpatch.diff";
|
|
} elsif ($mimepatch eq "shar") {
|
|
printf HTTP_HEADER_PATCH, "shar-$getpatch.sh";
|
|
} elsif ($mimepatch eq "uu") {
|
|
printf HTTP_HEADER_PATCH, "patch-$getpatch.uu";
|
|
} elsif ($mimepatch eq "txt") {
|
|
printf HTTP_HEADER_PATCH, "txt-$getpatch.txt";
|
|
} else {
|
|
printf HTTP_HEADER_PATCH, "unknown-$getpatch.txt";
|
|
}
|
|
}
|
|
}
|
|
|
|
($category, $PR) = ($1, $2)
|
|
if ($PR =~ /^($valid_category)\/($valid_pr)$/);
|
|
|
|
$category = undef
|
|
if ($category && $category !~ /^$valid_category$/);
|
|
|
|
if ($PR !~ /^$valid_pr$/ || $PR < 0) {
|
|
print html_header("Query PR Database");
|
|
displayform();
|
|
print html_footer();
|
|
exit;
|
|
}
|
|
|
|
# Just in case
|
|
$PR = int $PR;
|
|
$PR = quotemeta $PR;
|
|
|
|
if ($category) {
|
|
$category = quotemeta $category;
|
|
@query = split /\n/, qx(query-pr.web --full --category=${category} ${PR} 2>&1);
|
|
} else {
|
|
@query = split /\n/, qx(query-pr.web --full ${PR} 2>&1);
|
|
}
|
|
|
|
if (!@query or ($query[0] and $query[0] =~ /^query-pr(:?\.(:?real|web))?: /)) {
|
|
print html_header("No PRs Matched Query");
|
|
displayform();
|
|
print html_footer();
|
|
exit;
|
|
} elsif ($query[0] =~ /^lockf: /) {
|
|
print html_header("PR Database Busy");
|
|
sprint('trylatermsg');
|
|
print html_footer();
|
|
exit;
|
|
}
|
|
|
|
if ($f eq "raw") {
|
|
print "$_\n" foreach (@query);
|
|
exit;
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Process Results from query-pr
|
|
#-----------------------------------------------------------------------
|
|
|
|
my $section = SECT_HEADER;
|
|
my $mfield = $fields_multiple[0];
|
|
|
|
foreach my $line (@query)
|
|
{
|
|
my ($k, $v);
|
|
|
|
if ($section == SECT_HEADER) {
|
|
$section++ if ($line =~ /^\s*$/);
|
|
|
|
next if ($line !~ /^([A-Z][A-Za-z0-9-_.]+): (.*)$/);
|
|
|
|
($k, $v) = ($1, $2);
|
|
|
|
$k = lc $k;
|
|
$header{$k} = $v;
|
|
|
|
next;
|
|
}
|
|
|
|
if ($section == SECT_SFIELDS) {
|
|
my $i = -1;
|
|
my $f = 0;
|
|
|
|
next if ($line !~ /^>([A-Z][A-Za-z-]+):\s*(.*)$/);
|
|
|
|
($k, $v) = ($1, $2);
|
|
|
|
foreach (@fields_single) {
|
|
if ($k eq $_) {
|
|
$f = 1;
|
|
last;
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
if (!$f or $i == $#fields_single) {
|
|
$section++;
|
|
next;
|
|
}
|
|
|
|
$sfields{$k} = $v;
|
|
|
|
next;
|
|
}
|
|
|
|
if ($section == SECT_MFIELDS) {
|
|
my $f = 0;
|
|
|
|
if ($line =~ /^>([A-Z][A-Za-z-]+):\s*(.*)$/) {
|
|
foreach (@fields_multiple) {
|
|
$f = 1 if $1 eq $_;
|
|
next;
|
|
}
|
|
|
|
if ($f) {
|
|
$mfield = $1;
|
|
} else {
|
|
push @{$mfields{$mfield}}, $2;
|
|
}
|
|
|
|
next;
|
|
}
|
|
|
|
push @{$mfields{$mfield}}, $line;
|
|
next;
|
|
}
|
|
}
|
|
|
|
$getpatch = 0 if ($getpatch < 0);
|
|
|
|
$fromwebform =
|
|
$header{'x-send-pr-version'} =~ /^www-/;
|
|
|
|
if ($getpatch > 0) {
|
|
extractpatch();
|
|
exit;
|
|
}
|
|
|
|
|
|
# Construct footer now we have enough information
|
|
|
|
buildfooter();
|
|
|
|
|
|
print html_header(htmlclean("$sfields{'Category'}/$sfields{'Number'}: "
|
|
. $sfields{'Synopsis'}));
|
|
|
|
sprint('header_thead');
|
|
|
|
sprint('header_trow', 'From', htmlclean($header{'from'}));
|
|
sprint('header_trow', 'Date', htmlclean($header{'date'}));
|
|
sprint('header_trow', 'Subject', htmlclean($header{'subject'}));
|
|
sprint('header_trow', 'Send-pr version',
|
|
htmlclean($header{'x-send-pr-version'}));
|
|
|
|
sprint('header_tfoot');
|
|
|
|
|
|
# Single-Line fields
|
|
|
|
sprint('sfields_thead');
|
|
|
|
foreach (@fields_single)
|
|
{
|
|
my ($k, $v);
|
|
|
|
$k = htmlclean($_);
|
|
$v = htmlclean($sfields{$_}) || "";
|
|
|
|
$v =~ s/^(\S*).*$/<a href="mailto:$1\@FreeBSD.org">$1\@FreeBSD.org<\/a>/
|
|
if ($_ eq "Responsible");
|
|
|
|
$v = "never"
|
|
if ($_ eq "Last-Modified" and $v =~ /^\s*$/);
|
|
|
|
next if ($_ =~ /$fields_skip/i);
|
|
|
|
sprint('sfields_trow', $k, $v);
|
|
}
|
|
|
|
sprint('sfields_tfoot');
|
|
|
|
|
|
# Multiple-Line fields
|
|
|
|
foreach my $field (@fields_multiple)
|
|
{
|
|
my $cfound = 0;
|
|
|
|
sprint('mfields_header', $field);
|
|
|
|
if ($field eq "Audit-Trail") {
|
|
my %block;
|
|
my $cliphack;
|
|
my $blockwhy;
|
|
my ($inblock, $inresponse, $mbreak) = (0, 0, 0);
|
|
my $url = "${self_url_base}${PR}";
|
|
|
|
my $outp = "";
|
|
my %mime_headers;
|
|
my $mime_boundary;
|
|
my $mime_endheader;
|
|
my $encoding = 0;
|
|
|
|
# Hack for older PRs with no usable delimiter
|
|
push @{$mfields{'Audit-Trail'}}, $url;
|
|
|
|
$url = quotemeta $url;
|
|
|
|
foreach (@{$mfields{$field}})
|
|
{
|
|
# If we're sure we have a genuine Reply via E-mail block,
|
|
# allow for a border case, where there is a space rather
|
|
# than an empty line between the header and body.
|
|
$_ = "" if ($cliphack && /^ {1,2}$/);
|
|
$cliphack = 0;
|
|
|
|
if ($inblock == 1 && (/^${url}\s*$/i || /^([A-Za-z_]+-Changed-From-To: .*)$/ || /^(From: )/)) {
|
|
my $onnextline = ($1 ? 1 : 0);
|
|
if ($blockwhy) {
|
|
$blockwhy =~ s/<br \/>$//;
|
|
$blockwhy = htmlparse($blockwhy);
|
|
}
|
|
|
|
sprint('auditblock_trow', "Why", $blockwhy || "");
|
|
|
|
undef %block;
|
|
undef $blockwhy;
|
|
$inblock = 0;
|
|
$mbreak = 0;
|
|
|
|
if ($inresponse) {
|
|
if ($inpatch) {
|
|
$inpatch = 0;
|
|
sprint('patchblock_tfoot');
|
|
sprint('break');
|
|
}
|
|
sprint('responseblock_textfoot') if ($inresponse > 1);
|
|
sprint('responseblock_tfoot');
|
|
$inresponse = 0;
|
|
}
|
|
|
|
sprint('auditblock_tfoot');
|
|
next unless ($onnextline);
|
|
}
|
|
|
|
if (/^([A-Za-z_]+)-Changed-([A-Za-z_-]+?): (.*)$/) {
|
|
my $w = $1;
|
|
my $k = $2;
|
|
|
|
if ($inresponse) {
|
|
if ($inpatch) {
|
|
$inpatch = 0;
|
|
sprint('patchblock_tfoot');
|
|
sprint('break');
|
|
}
|
|
sprint('responseblock_textfoot') if ($inresponse > 1);
|
|
sprint('responseblock_tfoot');
|
|
$inresponse = 0;
|
|
}
|
|
|
|
if ($inblock == 0) {
|
|
$block{'changed'} = $w;
|
|
sprint('auditblock_thead', htmlclean($w));
|
|
$inblock = 1;
|
|
}
|
|
|
|
$block{$k} = $3;
|
|
|
|
if ($k ne "Why") {
|
|
sprint('auditblock_trow', htmlclean($k), htmlclean($block{$k}));
|
|
next;
|
|
}
|
|
|
|
next;
|
|
} elsif (/^(From|To|Cc|Subject|Date): (.*)$/) {
|
|
my ($k, $v);
|
|
|
|
$k = htmlclean($1);
|
|
$v = htmlclean($2);
|
|
|
|
if ($inresponse > 1) {
|
|
if ($inpatch) {
|
|
$inpatch = 0;
|
|
sprint('patchblock_tfoot');
|
|
sprint('break');
|
|
}
|
|
$mime_boundary = undef;
|
|
$mime_endheader = 0;
|
|
$encoding = 0;
|
|
sprint('responseblock_textfoot');
|
|
sprint('responseblock_tfoot');
|
|
}
|
|
|
|
if (!$inresponse || $inresponse > 1) {
|
|
sprint('responseblock_thead');
|
|
}
|
|
|
|
if ($k eq "From" or $k eq "Date") {
|
|
sprint('responseblock_trow', $k, $v);
|
|
}
|
|
|
|
$inresponse = 1;
|
|
$cliphack = 1;
|
|
next;
|
|
} elsif (/^\s/ and $inresponse == 1 and !$mbreak) {
|
|
$cliphack = 1;
|
|
next;
|
|
} elsif (/^ (.*)$/) {
|
|
next if ($inresponse and !$mbreak);
|
|
|
|
if ($inresponse == 1) {
|
|
sprint('responseblock_texthead');
|
|
$inresponse++;
|
|
}
|
|
|
|
# XXX - use trailing cfound
|
|
|
|
if ($inresponse) {
|
|
my $txt = $1;
|
|
|
|
if ($txt !~ /^-+$/ && $txt !~ /(?:cut|snip)/i && $txt =~ /^--(\S+)$/) {
|
|
$mime_boundary = $1 if (!defined $mime_boundary && !$inpatch);
|
|
|
|
if ($1 =~ /^${mime_boundary}(--)?$/) {
|
|
$mime_boundary = undef if (defined $1);
|
|
if ($encoding == ENCODING_BASE64 and $outp ne "") {
|
|
my $patchname;
|
|
my $dp = $mime_headers{'disposition'};
|
|
if ($dp and $dp =~ /.*\bfilename=["']?([A-Za-z0-9\-\.:_]{6,36})["']?.*/) {
|
|
$patchname = $1;
|
|
} else {
|
|
$patchname = "attachment.dat";
|
|
}
|
|
if ($patchname =~ /$binary_filetypes/) {
|
|
$outp = "(Binary attachment not viewable.)\n";
|
|
} else {
|
|
$outp = decode_base64($outp);
|
|
}
|
|
|
|
$outp = "--- $patchname begins here ---\n"
|
|
. $outp
|
|
. "\n--- $patchname ends here ---\n";
|
|
parsepatches($_) foreach (split /\n/, $outp);
|
|
$outp = "";
|
|
}
|
|
|
|
sprint('mime_boundary');
|
|
$mime_endheader = 0;
|
|
$encoding = 0;
|
|
next;
|
|
}
|
|
}
|
|
|
|
if (defined $mime_boundary && !$mime_endheader && !$inpatch) {
|
|
if ($txt =~ /^Content-([A-Za-z-]{2,}):\s*(.*)\s*$/i) {
|
|
$mime_headers{lc $1} = $2;
|
|
next;
|
|
} elsif ($txt =~ /^\s*(?:file)?name=["']?.*?["']?\s*$/i) {
|
|
$mime_headers{'disposition'} ||= "";
|
|
if ($mime_headers{'disposition'} !~ /(?:file)?name=/) {
|
|
$mime_headers{'disposition'} .= "; $txt";
|
|
}
|
|
next;
|
|
} else {
|
|
$mime_endheader = 1;
|
|
if ($mime_headers{'transfer-encoding'}) {
|
|
my $enc = $mime_headers{'transfer-encoding'};
|
|
if ($enc =~ /^\s*["']?base64["']?\s*$/i) {
|
|
$encoding = ENCODING_BASE64;
|
|
} elsif ($enc =~ /^\s*["']?quoted-printable["']?\s*$/i) {
|
|
$encoding = ENCODING_QP;
|
|
} else {
|
|
$encoding = 0;
|
|
}
|
|
} else {
|
|
$encoding = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($encoding == ENCODING_BASE64) {
|
|
$outp .= $txt;
|
|
next;
|
|
} elsif ($encoding == ENCODING_QP) {
|
|
# XXX: lines ending in = should be joined
|
|
$txt =~ s/=$//;
|
|
$txt = decode_qp($txt);
|
|
}
|
|
|
|
if ($txt =~ /^\s*((?:>\s*)+)/) {
|
|
my $level = $1;
|
|
|
|
$txt =~ s/^((?:>\s*)*={47})(=+\s*)$/$1 $2/;
|
|
|
|
if ($level =~ s/.*?>.*?/./g) {
|
|
my $i = 0;
|
|
my @levels = split(/\s*>\s*/, $txt,
|
|
length $level);
|
|
my $last = pop @levels;
|
|
foreach (@levels) {
|
|
sprint('quote_level_'.(++$i % 2));
|
|
$_ = htmlclean($_);
|
|
$_ = htmlparse($_);
|
|
print;
|
|
}
|
|
print htmlclean($last);
|
|
sprint('quote_end') while ($i--);
|
|
sprint('break');
|
|
}
|
|
} else {
|
|
$patchendhint = 1 if ($txt eq '-- ');
|
|
|
|
if ($inpatch or $txt) {
|
|
parsepatches($txt) || ($inpatch || sprint('break'));
|
|
} else {
|
|
sprint('break');
|
|
}
|
|
}
|
|
}
|
|
} elsif (/^$/ and $inresponse and !$mbreak) {
|
|
# XXX: >line 1 ignored (but not needed)
|
|
$mbreak = 1;
|
|
next;
|
|
} elsif (/^$/) {
|
|
$mbreak = 0;
|
|
next;
|
|
} elsif (!$inblock and $_ !~ /^${url}\s*$/i) {
|
|
if ($inresponse > 1) {
|
|
if ($inpatch) {
|
|
$inpatch = 0;
|
|
sprint('patchblock_tfoot');
|
|
sprint('break');
|
|
}
|
|
sprint('responseblock_textfoot');
|
|
sprint('responseblock_tfoot');
|
|
}
|
|
|
|
sprint('unexpectedtext_thead');
|
|
print htmlclean($_);
|
|
sprint('unexpectedtext_tfoot');
|
|
|
|
$inresponse = 0;
|
|
next;
|
|
}
|
|
|
|
$cfound = ($_ ? 1 : 0) if (!$cfound);
|
|
next if (!$cfound);
|
|
|
|
if (!$_) {
|
|
$cfound++;
|
|
next;
|
|
} else {
|
|
print "\n" while (--$cfound);
|
|
$cfound = 1;
|
|
}
|
|
|
|
$_ =~ s/^((?:>\s*)*={47})(=+\s*)$/$1 $2/;
|
|
|
|
$_ = htmlclean($_);
|
|
$blockwhy .= "$_<br />\n" if defined($block{'Why'});
|
|
}
|
|
|
|
if ($inresponse) {
|
|
if ($inpatch) {
|
|
$inpatch = 0;
|
|
sprint('patchblock_tfoot');
|
|
sprint('break');
|
|
}
|
|
sprint('responseblock_textfoot') if ($inresponse > 1);
|
|
sprint('responseblock_tfoot');
|
|
$inresponse = 0;
|
|
}
|
|
} elsif ($field eq "Fix") {
|
|
foreach (@{$mfields{$field}})
|
|
{
|
|
s/\s+$//;
|
|
|
|
$cfound = ($_ ? 1 : 0) if (!$cfound);
|
|
next if (!$cfound);
|
|
|
|
if (!$_) {
|
|
$cfound++;
|
|
next;
|
|
} else {
|
|
sprint('break') while (--$cfound > 1);
|
|
$cfound = 1;
|
|
}
|
|
|
|
parsepatches($_) || ($inpatch || sprint('break'));
|
|
}
|
|
|
|
if ($inpatch) {
|
|
$inpatch = 0;
|
|
sprint('patchblock_tfoot');
|
|
sprint('break');
|
|
}
|
|
} else {
|
|
foreach (@{$mfields{$field}})
|
|
{
|
|
s/\s+$//;
|
|
|
|
$cfound = ($_ ? 1 : 0) if (!$cfound);
|
|
next if (!$cfound);
|
|
|
|
if (!$_) {
|
|
$cfound++;
|
|
next;
|
|
} else {
|
|
sprint('break') while (--$cfound);
|
|
$cfound = 1;
|
|
}
|
|
|
|
$_ = htmlclean($_);
|
|
$_ = htmlparse($_);
|
|
|
|
print;
|
|
sprint('break');
|
|
}
|
|
sprint('empty') if ($cfound <= 1);
|
|
}
|
|
|
|
sprint('mfields_footer');
|
|
}
|
|
|
|
sprint('html_footerlinks');
|
|
print html_footer();
|
|
|
|
# DoS protection -- apparently.
|
|
select undef, undef, undef, 0.35
|
|
unless (!$iscgi);
|
|
|
|
exit;
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Func: extractpatch()
|
|
# Desc: Isolate the requested patch, and print unformatted to STDOUT.
|
|
#-----------------------------------------------------------------------
|
|
|
|
sub extractpatch
|
|
{
|
|
foreach (@{$mfields{'Fix'}}) {
|
|
return if (parsepatches($_) == -1);
|
|
}
|
|
|
|
foreach (@{$mfields{'Audit-Trail'}}) {
|
|
if (s/^ //) {
|
|
return if (parsepatches($_) == -1);
|
|
} else {
|
|
$inpatch = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Func: sprint()
|
|
# Desc: Merge provided list of strings into the desired message and
|
|
# print the result to STDOUT.
|
|
#-----------------------------------------------------------------------
|
|
|
|
sub sprint
|
|
{
|
|
my $k = shift;
|
|
my $msg = $fmt{$k};
|
|
|
|
if (!$msg) {
|
|
warn "Message format \"$k\" not found";
|
|
return;
|
|
}
|
|
|
|
my $i = 1;
|
|
|
|
foreach (@_) {
|
|
$msg =~ s/%%()\(${i}\)/$_/g;
|
|
$i++;
|
|
}
|
|
|
|
$msg =~ s/%%\([0-9]+\)//g;
|
|
|
|
print $msg;
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Func: htmlclean()
|
|
# Desc: Remove HTML entities from message and return the result.
|
|
#-----------------------------------------------------------------------
|
|
|
|
sub htmlclean
|
|
{
|
|
my $v = shift;
|
|
return "" if (!$v);
|
|
|
|
$v =~ s/&/&/g;
|
|
$v =~ s/</</g;
|
|
$v =~ s/>/>/g;
|
|
return $v;
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Func: htmlparse()
|
|
# Desc: Perform any fancy formatting on the message (e.g. HTML-ify
|
|
# URLs) and return the result.
|
|
#-----------------------------------------------------------------------
|
|
|
|
sub htmlparse
|
|
{
|
|
my $v = shift;
|
|
return "" if (!$v);
|
|
|
|
my $iv = 'A-Za-z0-9\-_\/#@\$=\\\\';
|
|
$v =~ s/(?<![$iv])($valid_category)\/($valid_pr)(?![$iv])/<a href="${scriptname}?pr=$2&cat=$1">$1\/$2<\/a>/g;
|
|
|
|
$v =~ s/((?:https?|ftps?):\/\/[^\s\/]+\/[][\w=.,\'\(\)\~\?\!\&\/\%\$\{\}:;@#+-]*)/<a href="$1">$1<\/a>/g;
|
|
$v =~ s/^RCS file: (\/home\/[A-Za-z0-9]+\/(.*?)),v$/RCS file: <a href="$cvsweb_url$2">$1<\/a>,v/;
|
|
return $v;
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Func: buildfooter()
|
|
# Desc: Build the page footer links section.
|
|
#-----------------------------------------------------------------------
|
|
|
|
sub buildfooter
|
|
{
|
|
my ($newstr, $synopsis, $mail, $replyto, $pr, $cat);
|
|
$pr = htmlclean($sfields{'Number'});
|
|
$cat = htmlclean($sfields{'Category'});
|
|
$synopsis = htmlclean($sfields{'Synopsis'});
|
|
|
|
$mail = $header{'from'};
|
|
if ($mail) {
|
|
$mail =~ s/^\s*(.*)\s*$/$1/;
|
|
$mail =~ s/.*<(.*)>.*/$1/;
|
|
$mail =~ s/\s*\(.*\)\s*//;
|
|
}
|
|
|
|
$replyto = $header{'reply-to'};
|
|
if ($replyto) {
|
|
$replyto =~ s/^\s*(.*)\s*$/$1/;
|
|
$replyto =~ s/.*<(.*)>.*/$1/;
|
|
$replyto =~ s/\s*\(.*\)\s*//;
|
|
}
|
|
|
|
$mail = $replyto if ($replyto);
|
|
$mail .= '@FreeBSD.org' unless ($mail =~ /@/);
|
|
|
|
$synopsis =~ s/[^a-zA-Z+.@-]/"%" . sprintf("%02X", unpack("C", $&))/eg;
|
|
$mail =~ s/[^a-zA-Z+.@-]/"%" . sprintf("%02X", unpack("C", $&))/eg;
|
|
|
|
$newstr = "mailto:bug-followup\@FreeBSD.org,${mail}?subject=Re:%20${cat}/${pr}:%20${synopsis}";
|
|
|
|
$fmt{'html_footerlinks'} =~ s/%%\(maillink\)/${newstr}/g;
|
|
|
|
# Do some other replacements while here
|
|
|
|
$fmt{$_} =~ s/%%\(pr\)/${pr}/g
|
|
foreach (keys %fmt);
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Func: parsepatches()
|
|
# Desc: Parse lines which might contain patches, adding HTML formatting
|
|
# if requested.
|
|
#-----------------------------------------------------------------------
|
|
|
|
{ # Local static variables
|
|
my ($outp, $patchnum, $cfound, $lastcol, $lastrev, $context, $mime_boundary);
|
|
|
|
sub parsepatches
|
|
{
|
|
$_ = shift;
|
|
|
|
$outp ||= "";
|
|
$patchnum ||= 0;
|
|
$cfound ||= 0;
|
|
$context ||= 0;
|
|
|
|
my $plus_s = '<span class="patch_plusline">';
|
|
my $minus_s = '<span class="patch_minusline">';
|
|
my $context_s = '<span class="patch_contextline">';
|
|
my $revinfo_s = '<span class="patch_revinfo">';
|
|
my $at_s = '<span class="patch_hunkinfo">';
|
|
my $all_e = '</span>';
|
|
|
|
my $maxcontext = 3; # XXX: This ought to be dynamic
|
|
|
|
if (!$getpatch) {
|
|
$cfound = ($_ ? 1 : 0) if (!$cfound);
|
|
return 0 if (!$cfound);
|
|
|
|
if (!$_) {
|
|
$cfound++;
|
|
return 0;
|
|
} else {
|
|
sprint('break') while (--$cfound > 1);
|
|
$cfound = 1;
|
|
}
|
|
}
|
|
|
|
if (/^--(\S+)$/ && $getpatch && !$inpatch) {
|
|
if ($getpatch == $patchnum+1) {
|
|
$mime_boundary = $1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (/^Content-([A-Za-z-]{2,}):\s*(.*)\s*$/i && $getpatch) {
|
|
if (!$inpatch) {
|
|
my $k = lc $1;
|
|
my $v = lc $2;
|
|
if ($getpatch == $patchnum+1 and defined $mime_boundary) {
|
|
if ($k eq "transfer-encoding" && $v =~ /\bbase64\b/) {
|
|
$patchnum++;
|
|
$inpatch |= PATCH_BASE64;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (defined $mime_boundary && /^--${mime_boundary}(?:--)?$/ && $getpatch && ($inpatch & PATCH_BASE64)) {
|
|
$inpatch = 0;
|
|
$mime_boundary = undef;
|
|
if ($outp ne "") {
|
|
print decode_base64($outp);
|
|
$outp = "";
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (($inpatch & PATCH_BASE64) && $getpatch) {
|
|
$outp .= $_;
|
|
return 1;
|
|
}
|
|
|
|
if (/^Patch attached with submission follows:$/ && $fromwebform && !$inpatch) {
|
|
$patchnum++;
|
|
$inpatch |= PATCH_ANY;
|
|
return 1 if ($getpatch and $patchnum != $getpatch);
|
|
$lastcol = undef;
|
|
$lastrev = undef;
|
|
|
|
sprint('patchblock_thead', $patchnum, 'patch.txt', "txt")
|
|
unless ($getpatch);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (/^---{1,8}\s?([A-Za-z0-9-_.,:%]+) (begins|starts) here/i && !$inpatch) {
|
|
$patchnum++;
|
|
$inpatch |= PATCH_ANY;
|
|
return 1 if ($getpatch and $patchnum != $getpatch);
|
|
$lastcol = undef;
|
|
$lastrev = undef;
|
|
|
|
sprint('patchblock_thead', $patchnum, htmlclean($1), "txt")
|
|
unless ($getpatch);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (/^((?:(?:---|\*\*\*) (?:\S+)\s*(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) .*)|(diff -.*? .*? .*)|(Index: \S+)|(\*{3} \d+,\d+ \*{4}))$/ && !$inpatch) {
|
|
$patchnum++;
|
|
$inpatch |= PATCH_DIFF;
|
|
return 1 if ($getpatch and $patchnum != $getpatch);
|
|
$lastcol = undef;
|
|
$lastrev = undef;
|
|
|
|
sprint('patchblock_thead', $patchnum, "patch-$patchnum.diff", "diff")
|
|
unless ($getpatch);
|
|
}
|
|
|
|
if (/^# This is a shell archive\. Save it in a file, remove anything before/ && !$inpatch) {
|
|
$patchnum++;
|
|
$inpatch |= PATCH_SHAR;
|
|
return 1 if ($getpatch and $patchnum != $getpatch);
|
|
$lastcol = undef;
|
|
$lastrev = undef;
|
|
|
|
sprint('patchblock_thead', $patchnum, "shar-$patchnum.sh", "shar")
|
|
unless ($getpatch);
|
|
}
|
|
|
|
if (/^---{1,8}\s?[A-Za-z0-9-_.,:%]+ ends here/i && ($inpatch & PATCH_ANY)) {
|
|
#$inpatch ^= PATCH_ANY;
|
|
$inpatch = 0;
|
|
$context = 0;
|
|
sprint('patchblock_tfoot')
|
|
unless ($getpatch);
|
|
|
|
return (($patchnum == $getpatch) ? -1 : $inpatch)
|
|
if ($getpatch);
|
|
|
|
return $inpatch;
|
|
}
|
|
|
|
if (/^exit$/ && ($inpatch & PATCH_SHAR)) {
|
|
$inpatch ^= PATCH_SHAR;
|
|
|
|
print;
|
|
sprint('patchblock_tfoot') unless ($getpatch);
|
|
return 1;
|
|
}
|
|
|
|
if (/^begin \d\d\d (.*)/ && !($inpatch & PATCH_UUENC)) {
|
|
if (!$inpatch) {
|
|
$patchnum++;
|
|
return 1 if ($getpatch and $patchnum != $getpatch);
|
|
}
|
|
sprint('patchblock_thead', $patchnum, "patch-$patchnum.uu", "uu")
|
|
unless ($getpatch or $inpatch);
|
|
|
|
$inpatch |= PATCH_UUENC;
|
|
$inpatch |= PATCH_UUENC_BIN if ($1 =~ /$binary_filetypes/);
|
|
}
|
|
|
|
if ($inpatch) {
|
|
if ($inpatch & PATCH_UUENC) {
|
|
if (!$getpatch or $patchnum == $getpatch) {
|
|
$outp .= "$_\n";
|
|
if (/^end$/) {
|
|
$outp = uudecode($outp)
|
|
unless (!$getpatch and $inpatch & PATCH_UUENC_BIN);
|
|
$outp = htmlclean($outp) unless ($getpatch);
|
|
print $outp;
|
|
$inpatch ^= PATCH_UUENC; $outp = "";
|
|
$inpatch ^= PATCH_UUENC_BIN;
|
|
|
|
# No outer container?
|
|
sprint('patchblock_tfoot') if (!$inpatch and !$getpatch);
|
|
return -1;
|
|
}
|
|
}
|
|
} else {
|
|
if (!$getpatch) {
|
|
if (!($inpatch & PATCH_ANY)) {
|
|
if (/^ / or $_ eq "") {
|
|
$context++;
|
|
} else {
|
|
if ($context == $maxcontext and $patchendhint) {
|
|
$context++;
|
|
} else {
|
|
$context = 0;
|
|
}
|
|
}
|
|
|
|
if ($context > $maxcontext and $patchendhint) {
|
|
$context = 0;
|
|
# Disabled for now, since it doesn't
|
|
# work quite right.
|
|
# $inpatch = 0;
|
|
# sprint('patchblock_tfoot');
|
|
# print;
|
|
# return 0;
|
|
}
|
|
}
|
|
|
|
$_=~ s/
|
|
$//;
|
|
|
|
$_ = htmlclean($_);
|
|
$_ = htmlparse($_);
|
|
|
|
while (s/\t/" " x (8 - ((length($`)-1) % 8))/e) {};
|
|
|
|
# Obfustication coutesy of cdiff
|
|
|
|
s/^(\+.*)$/${plus_s}$1${all_e}/o;
|
|
s/^(-.*)$/${minus_s}$1${all_e}/o
|
|
if !s/^(--- \d+,\d+ ----.*)$/${revinfo_s}$1${all_e}/o;
|
|
s/^(\*\*\* \d+,\d+ *\*\*\*.*)$/${revinfo_s}$1${all_e}/o;
|
|
s/^(\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*)$/${revinfo_s}$1${all_e}/o;
|
|
s/^(!.*)$/${context_s}$1${all_e}/o;
|
|
s/^(@@.*$)/${at_s}$1${all_e}/o;
|
|
# if (/^1.(\d+)(\s+\(\w+\s+\d{2}-\w{3}-\d{2}\):\s)(.*)/) {
|
|
# $lastcol = $lastcol || 0;
|
|
# $lastcol++ if defined($lastrev) && $lastrev != $1;
|
|
# $lastrev = $1;
|
|
# $lastcol %= 6;
|
|
# $_ = "\033[3" . ($lastcol + 1) . "m1.$1$2\033[0m$3\n";
|
|
# }
|
|
}
|
|
|
|
if (!$getpatch or $patchnum == $getpatch) {
|
|
print;
|
|
print "\n";
|
|
}
|
|
}
|
|
} else {
|
|
if (!$getpatch) {
|
|
$_ = htmlclean($_);
|
|
$_ = htmlparse($_);
|
|
print;
|
|
}
|
|
}
|
|
|
|
return $inpatch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
# ex: ts=4 sw=4
|