You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by da...@apache.org on 2014/02/20 02:50:34 UTC
svn commit: r1570045 - /subversion/trunk/tools/dist/backport.pl
Author: danielsh
Date: Thu Feb 20 01:50:33 2014
New Revision: 1570045
URL: http://svn.apache.org/r1570045
Log:
backport.pl: Add some shell-escaping.
* tools/dist/backport.pl
(shell_escape, shell_safe_path_or_url, my_tempfile): New helpers.
(merge, handle_entry): Escape $entry{branch} and @conflicts.
(merge, edit_string, vote): Use my_tempfile().
(nominate_main): Outsource validation to new helper (which vote() uses too).
(Carp): New use().
Modified:
subversion/trunk/tools/dist/backport.pl
Modified: subversion/trunk/tools/dist/backport.pl
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dist/backport.pl?rev=1570045&r1=1570044&r2=1570045&view=diff
==============================================================================
--- subversion/trunk/tools/dist/backport.pl (original)
+++ subversion/trunk/tools/dist/backport.pl Thu Feb 20 01:50:33 2014
@@ -20,6 +20,7 @@ use feature qw/switch say/;
# specific language governing permissions and limitations
# under the License.
+use Carp qw/croak confess carp cluck/;
use Digest ();
use Term::ReadKey qw/ReadMode ReadKey/;
use File::Basename qw/basename dirname/;
@@ -219,28 +220,57 @@ sub prompt {
: ($answer =~ /^y/i) ? 1 : 0;
}
+# Bourne-escape a string.
+# Example:
+# >>> shell_escape(q[foo'bar]) eq q['foo'\''bar']
+# True
+sub shell_escape {
+ map {
+ local $_ = $_; # the LHS $_ is mutable; the RHS $_ may not be.
+ s/\x27/'\\\x27'/g;
+ "'$_'"
+ } @_
+}
+
+sub shell_safe_path_or_url($) {
+ local $_ = shift;
+ return m{^[A-Za-z0-9._:+/-]+$} and !/^-|^[+]/;
+}
+
+# Shell-safety-validating wrapper for File::Temp::tempfile
+sub my_tempfile {
+ my ($fh, $fn) = tempfile();
+ croak "Tempfile name '$fn' not shell-safe; aborting"
+ unless shell_safe_path_or_url $fn;
+ return ($fh, $fn);
+}
+
sub merge {
my %entry = @_;
- my ($logmsg_fh, $logmsg_filename) = tempfile();
- my ($mergeargs);
+ my ($logmsg_fh, $logmsg_filename) = my_tempfile();
+ my (@mergeargs);
+
+ my $shell_escaped_branch = shell_escape($entry{branch})
+ if defined($entry{branch});
if ($entry{branch}) {
if ($SVNvsn >= 1_008_000) {
- $mergeargs = "$BRANCHES/$entry{branch}";
+ @mergeargs = shell_escape "$BRANCHES/$entry{branch}";
say $logmsg_fh "Merge $entry{header}:";
} else {
- $mergeargs = "--reintegrate $BRANCHES/$entry{branch}";
+ @mergeargs = shell_escape qw/--reintegrate/, "$BRANCHES/$entry{branch}";
say $logmsg_fh "Reintegrate $entry{header}:";
}
say $logmsg_fh "";
} elsif (@{$entry{revisions}}) {
- $mergeargs = join " ",
+ @mergeargs = shell_escape(
($entry{accept} ? "--accept=$entry{accept}" : ()),
(map { "-c$_" } @{$entry{revisions}}),
'--',
- '^/subversion/trunk';
+ '^/subversion/trunk',
+ );
say $logmsg_fh
"Merge $entry{header} from trunk",
$entry{accept} ? ", with --accept=$entry{accept}" : "",
@@ -260,7 +290,7 @@ if $sh[$DEBUG]; then
set -x
fi
$SVNq up
-$SVNq merge $mergeargs
+$SVNq merge @mergeargs
if [ "`$SVN status -q | wc -l`" -eq 1 ]; then
if [ -n "`$SVN diff | perl -lne 'print if s/^(Added|Deleted|Modified): //' | grep -vx svn:mergeinfo`" ]; then
# This check detects STATUS entries that name non-^/subversion/ revnums.
@@ -293,10 +323,10 @@ reinteg_rev=\`$SVN info $STATUS | sed -n
if $sh[$MAY_COMMIT]; then
# Sleep to avoid out-of-order commit notifications
if $sh[$YES]; then sleep 15; fi
- $SVNq rm $BRANCHES/$entry{branch} -m "Remove the '$entry{branch}' branch, $reintegrated_word in r\$reinteg_rev."
+ $SVNq rm $BRANCHES/$shell_escaped_branch -m "Remove the '"$shell_escaped_branch"' branch, $reintegrated_word in r\$reinteg_rev."
if $sh[$YES]; then sleep 1; fi
elif ! $sh[$YES]; then
- echo "Would remove $reintegrated_word '$entry{branch}' branch"
+ echo "Would remove $reintegrated_word '"$shell_escaped_branch"' branch"
fi
EOF
@@ -457,7 +487,7 @@ sub edit_string {
my $name = shift;
my %args = @_;
my $trailing_eol = $args{trailing_eol};
- my ($fh, $fn) = tempfile;
+ my ($fh, $fn) = my_tempfile();
print $fh $string;
$fh->flush or die $!;
system("$EDITOR -- $fn") == 0
@@ -569,12 +599,9 @@ sub vote {
system "$SVN diff -- $STATUS";
printf "[[[\n%s%s]]]\n", $logmsg, ("\n" x ($logmsg !~ /\n\z/));
if (prompt "Commit these votes? ") {
- my ($logmsg_fh, $logmsg_filename) = tempfile();
+ my ($logmsg_fh, $logmsg_filename) = my_tempfile();
print $logmsg_fh $logmsg;
close $logmsg_fh;
- warn("Tempfile name '$logmsg_filename' not shell-safe; "
- ."refraining from commit.\n") and return
- unless $logmsg_filename =~ /^([A-Z0-9._-]|\x2f)+$/i;
system("$SVN commit -F $logmsg_filename -- $STATUS") == 0
or warn("Committing the votes failed($?): $!") and return;
unlink $logmsg_filename;
@@ -745,7 +772,7 @@ sub handle_entry {
say STDERR "Conflicts merging $entry{header}!";
say STDERR "";
say STDERR $output;
- system "$SVN diff -- @conflicts";
+ system "$SVN diff -- " . join ' ', shell_escape @conflicts;
} elsif (!@conflicts and $entry{depends}) {
# Not a warning since svn-role may commit the dependency without
# also committing the dependent in the same pass.
@@ -812,7 +839,7 @@ sub handle_entry {
when (/^l/i) {
if ($entry{branch}) {
system "$SVN log --stop-on-copy -v -g -r 0:HEAD -- "
- ."$BRANCHES/$entry{branch} "
+ .shell_escape("$BRANCHES/$entry{branch}")." "
."| $PAGER";
} elsif (@{$entry{revisions}}) {
system "$SVN log ".(join ' ', map { "-r$_" } @{$entry{revisions}})
@@ -969,7 +996,7 @@ sub nominate_main {
my ($URL) = `$SVN info` =~ /^URL: (.*)$/m;
die "Can't retrieve URL of cwd" unless $URL;
- die if $URL !~ m%^[A-Za-z0-9:_.+/][A-Za-z0-9:_.+/-]*$%;
+ die unless shell_safe_path_or_url $URL;
system "$SVN info -- $URL-r$revnums[0] 2>/dev/null";
my $branch = ($? == 0) ? basename("$URL-r$revnums[0]") : undef;