use strict;
use warnings FATAL => 'all';
use base 'Exporter';
use Carp;
our $VERSION = '0.003001';
$VERSION = eval $VERSION;
our @EXPORT_OK = qw(
quote_native
quote_cmd
quote_system_list
quote_system_string
quote_system
quote_system_cmd
quote_literal
cmd_escape
unquote_native
cmd_unescape
);
our %EXPORT_TAGS = (all => [@EXPORT_OK]);
sub quote_native {
return join q{ }, quote_system_list(@_);
}
sub quote_cmd {
return cmd_escape(quote_native(@_));
}
sub quote_system_list {
# have to force quoting, or perl might try to use cmd anyway
return map { quote_literal($_, 1) } @_;
}
sub quote_system_string {
my $args = quote_native(@_);
if (_has_shell_metachars($args)) {
$args = cmd_escape($args);
}
return $args;
}
sub quote_system {
if (@_ > 1) {
return quote_system_list(@_);
}
else {
return quote_system_string(@_);
}
}
sub quote_system_cmd {
# force cmd, even when running through system
my $args = quote_native(@_);
if (! _has_shell_metachars($args)) {
# IT BURNS LOOK AWAY
return '%PATH:~0,0%' . cmd_escape($args);
}
return cmd_escape($args);
}
sub cmd_escape {
my $string = shift;
if ($string =~ /[\r\n\0]/) {
croak "can't quote newlines to pass through cmd.exe";
}
$string =~ s/([()%!^"<>&|])/^$1/g;
return $string;
}
sub quote_literal {
my ($text, $force) = @_;
# basic argument quoting. uses backslashes and quotes to escape
# everything.
if (!$force && $text ne '' && $text !~ /[ \t\n\x0b"]/) {
# no quoting needed
}
else {
$text =~ s{(\\*)(?="|\z)}{$1$1}g;
$text =~ s{"}{\\"}g;
$text = qq{"$text"};
}
return $text;
}
# derived from rules in code in win32.c
sub _has_shell_metachars {
my $string = shift;
return 1
if $string =~ /%/;
$string =~ s/(['"]).*?(\1|\z)//sg;
return $string =~ /[<>|]/;
}
sub unquote_native {
local ($_) = @_;
my @argv;
my $length = length
or return @argv;
m/\G\s*/gc;
ARGS: until ( pos == $length ) {
my $quote_mode;
my $arg = '';
CHARS: until ( pos == $length ) {
if ( m/\G((?:\\\\)+)(?=\\?(")?)/gc ) {
if (defined $2) {
$arg .= '\\' x (length($1) / 2);
}
else {
$arg .= $1;
}
}
elsif ( m/\G\\"/gc ) {
$arg .= '"';
}
elsif ( m/\G"/gc ) {
if ( $quote_mode && m/\G"/gc ) {
$arg .= '"';
}
$quote_mode = !$quote_mode;
}
elsif ( !$quote_mode && m/\G\s+/gc ) {
last;
}
elsif ( m/\G(.)/sgc ) {
$arg .= $1;
}
}
push @argv, $arg;
}
return @argv;
}
sub cmd_unescape {
my ($string) = @_;
no warnings 'uninitialized';
$string =~ s/\^(.?)|([^^"]+)|("[^"]*(?:"|\z))/$1$2$3/gs;
return $string;
}
1;
__END__
=head1 NAME
Win32::ShellQuote - Quote argument lists for Win32
=head1 SYNOPSIS
use Win32::ShellQuote qw(:all);
system quote_system('program.exe', '--switch', 'argument with spaces or other special characters');
=head1 DESCRIPTION
Quotes argument lists to be used in Win32 in several different
situations.
Windows passes its arguments as a single string instead of an array
as other platforms do. In almost all cases, the standard Win32
function is used to parse this string. F<cmd.exe> has different
rules for handling quoting, so extra work has to be done if it is
involved. It isn't possible to consistantly create a single string
that will be handled the same by F<cmd.exe> and the stardard parsing
rules.
Perl will try to detect if you need the shell by detecting shell
metacharacters. The routine that checks that uses different quoting
rules from both F<cmd.exe> and the native Win32 parsing. Extra
work must therefore be done to protect against this autodetection.
=head1 SUBROUTINES
=head2 quote_native
Quotes as a string to pass directly to a program using native methods
like L<Win32::Spawn()|Win32>. This is the safest option to use if
possible.
=head2 quote_cmd
Quotes as a string to be run through F<cmd.exe>, such as in a batch file.
=head2 quote_system_list
Quotes as a list to be passed to L<system|perlfunc/system> or
L<exec|perlfunc/exec>. This is equally as safe as L</quote_native>,
but you must ensure you have more than one item being quoted for
the list to be usable with system.
=head2 quote_system_string
Like L</quote_system_list>, but returns a single string. Some
argument lists cannot be properly quoted using this function.
=head2 quote_system
Switches between L</quote_system_list> and L</quote_system_string>
based on the number of items quoted.
=head2 quote_system_cmd
Quotes as a single string that will always be run with F<cmd.exe>.
=head2 quote_literal
Quotes a single parameter in native form.
=head2 cmd_escape
Escapes a string to be passed untouched by F<cmd.exe>.
=head1 CAVEATS
=over
=item *
Newlines (\n or \r) and null (\0) can't be properly quoted when
running through F<cmd.exe>.
=item *
This module re-implements some under-specified part of the perl
internals to accurately perform its work.
=back
=head1 AUTHOR
haarg - Graham Knop (cpan:HAARG) <haarg@haarg.org>
=head1 CONTRIBUTORS
=over 8
=item * Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2012 the L</AUTHOR> and L</CONTRIBUTORS>
as listed above.
This is free software; you can redistribute it and/or modify it
under the same terms as the Perl 5 programming language system
itself.
=cut