BATsh
view release on metacpan or search on metacpan
t/0007-extcmd-env.t view on Meta::CPAN
##################################################################
# EE04: no inline perl -e/-ne/-pe may be wrapped in single quotes,
# which cmd.exe does not honor (Windows portability).
sub {
my @hits = _scan_perl_oneliners('SQ');
if (@hits) { for my $h (@hits) { print "# WIN-HAZARD (single-quote): $h\n" } }
_ok(!@hits,
'EE04: no single-quote inline Perl in t/ eg/ lib/ doc/ README'
. (@hits ? ' (' . scalar(@hits) . ' hit(s))' : ''));
},
##################################################################
# 5. Static guard (A): /bin/sh dollar-expansion hazard
##################################################################
# EE05: no double-quoted inline perl -e/-ne/-pe may contain a
# shell-expandable dollar token ($_ , $name , ${...} , $1..$9),
# which /bin/sh would expand using the environment (Unix portability).
# The harmless numeric $$ (PID) is exempt.
sub {
my @hits = _scan_perl_oneliners('DOLLAR');
if (@hits) { for my $h (@hits) { print "# UNIX-HAZARD (dollar): $h\n" } }
_ok(!@hits,
'EE05: no shell-dollar in double-quoted inline Perl (all docs)'
. (@hits ? ' (' . scalar(@hits) . ' hit(s))' : ''));
},
);
######################################################################
# Helpers
######################################################################
# Scan every t/*.t and eg/* file for inline Perl one-liners and return
# the offending lines for the requested hazard class:
# 'SQ' -- the -e/-ne/-pe flag is immediately followed by a single
# quote (cmd.exe-unsafe wrapping).
# 'DOLLAR' -- the code is double-quoted and contains a dollar token
# that /bin/sh would expand ($ + letter/underscore/digit
# or ${ ), excluding the harmless numeric $$.
# Backslash-escaped quotes (\" \') as they appear inside .t string
# literals are normalised first, so the scan sees the effective command.
sub _scan_perl_oneliners {
my ($mode) = @_;
my $root = File::Spec->catdir($FindBin::Bin, File::Spec->updir);
my @hits;
# Directories scanned (relative to the distribution root), plus the
# top-level README. Covers executable code (t/, eg/), the module
# POD samples (lib/), and the 21-language reference docs (doc/), so a
# non-portable inline-Perl one-liner cannot be reintroduced anywhere.
my @relfiles;
for my $sub ('t', 'eg', 'doc', 'lib', 'lib/BATsh') {
my $dir = File::Spec->catdir($root, split(m{/}, $sub));
next unless -d $dir;
local *EE_DIR;
next unless opendir(EE_DIR, $dir);
my @names = sort grep { $_ !~ /\A\./ } readdir(EE_DIR);
closedir(EE_DIR);
for my $name (@names) {
# Only canonical source files; skip backups / editor files
# (e.g. *-OLD, *-OLD2, *.bak, *~) that may sit in the tree.
my $ok = 0;
if ($sub eq 't') { $ok = ($name =~ /\.t\z/) }
elsif ($sub eq 'eg') { $ok = ($name =~ /\.(?:batsh|pl)\z/) }
elsif ($sub eq 'doc') { $ok = ($name =~ /\.txt\z/) }
else { $ok = ($name =~ /\.pm\z/) } # lib, lib/BATsh
next unless $ok;
push @relfiles, "$sub/$name";
}
}
push @relfiles, 'README';
for my $rel (@relfiles) {
# This regression file documents the hazards in prose on purpose,
# so it must not scan itself.
next if $rel =~ /0007-extcmd-env\.t\z/;
my $path = File::Spec->catfile($root, split(m{/}, $rel));
next unless -f $path;
local *EE_FH;
next unless open(EE_FH, $path);
my $lineno = 0;
while (<EE_FH>) {
$lineno++;
my $line = $_;
$line =~ s/[\r\n]+\z//;
# Skip full-line comments (Perl/SH '#', CMD '::' / 'REM') so
# prose that merely mentions perl one-liners is ignored.
next if $line =~ /\A\s*#/;
next if $line =~ /\A\s*::/;
next if $line =~ /\A\s*(?:REM|rem)\b/;
# Normalise escaped quotes from .t string literals.
my $eff = $line;
$eff =~ s/\\"/"/g;
$eff =~ s/\\'/'/g;
my $bad = 0;
if ($mode eq 'SQ') {
# perl <flags ending in e> followed by a single quote
$bad = 1 if $eff =~ /\bperl\b[^|<>;&`]*?-\w*e\b\s*'/;
}
else {
# perl <flags ending in e> "double-quoted-code"
if ($eff =~ /\bperl\b[^|<>;&`]*?-\w*e\b\s*"([^"]*)"/) {
my $code = $1;
$bad = 1 if $code =~ /\$[A-Za-z_0-9{]/;
}
}
push @hits, "$rel:$lineno: $line" if $bad;
}
close(EE_FH);
}
return @hits;
}
sub _capture {
my ($code) = @_;
my $tmpfile = File::Spec->catfile(
File::Spec->tmpdir(), "batsh_cap7_$$\.tmp");
open(_CAP_OLD, '>&STDOUT') or return '';
open(_CAP_FH, ">$tmpfile") or do { open(STDOUT, '>&_CAP_OLD'); return '' };
open(STDOUT, '>&_CAP_FH');
( run in 2.472 seconds using v1.01-cache-2.11-cpan-c966e8aa7e8 )