Completed
Push — master ( 2eca99...95d9ac )
by Greg
02:35
created

Escape::shellArg()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 4
nc 3
nop 2
1
<?php
2
namespace Consolidation\SiteProcess\Util;
3
4
use Consolidation\SiteAlias\AliasRecord;
5
use Symfony\Component\Process\Process;
6
use Consolidation\Config\Util\Interpolator;
7
use Symfony\Component\Console\Output\OutputInterface;
8
use Consolidation\SiteProcess\Util\ShellOperatorInterface;
9
10
/**
11
 * Escape will shell-escape commandline arguments for different platforms.
12
 */
13
class Escape
14
{
15
    /**
16
     * argsForSite escapes each argument in an array for the given site.
17
     */
18
    public static function argsForSite(AliasRecord $siteAlias, $args)
19
    {
20
        return array_map(
21
            function ($arg) use ($siteAlias) {
22
                return Escape::forSite($siteAlias, $arg);
23
            },
24
            $args
25
        );
26
    }
27
28
    /**
29
     * forSite escapes the provided argument for the specified alias record.
30
     */
31
    public static function forSite(AliasRecord $siteAlias, $arg)
32
    {
33
        return static::shellArg($arg, $siteAlias->os());
34
    }
35
36
    /**
37
     * shellArg escapes the provided argument for the specified OS
38
     *
39
     * @param string|ShellOperatorInterface $arg The argument to escape
40
     * @param string|null $os The OS to escape for. Optional; defaults to LINUX
41
     *
42
     * @return string The escaped string
43
     */
44
    public static function shellArg($arg, $os = null)
45
    {
46
        // Short-circuit escaping for simple params (keep stuff readable);
47
        // also skip escaping for shell operators (e.g. &&), which must not
48
        // be escaped.
49
        if (($arg instanceof ShellOperatorInterface) || preg_match('|^[a-zA-Z0-9@=.:/_-]*$|', $arg)) {
50
            return (string) $arg;
51
        }
52
53
        if (static::isWindows($os)) {
54
            return static::windowsArg($arg);
55
        }
56
        return static::linuxArg($arg);
57
    }
58
59
    /**
60
     * isWindows determines whether the provided OS is Windows.
61
     */
62
    public static function isWindows($os)
63
    {
64
        return strtoupper(substr($os, 0, 3)) === 'WIN';
65
    }
66
67
    /**
68
     * linuxArg is the Linux version of escapeshellarg().
69
     *
70
     * This is intended to work the same way that escapeshellarg() does on
71
     * Linux.  If we need to escape a string that will be used remotely on
72
     * a Linux system, then we need our own implementation of escapeshellarg,
73
     * because the Windows version behaves differently.
74
     *
75
     * Note that we behave somewhat differently than the built-in escapeshellarg()
76
     * with respect to whitespace replacement in order
77
     *
78
     * @param string $arg The argument to escape
79
     *
80
     * @return string The escaped string
81
     */
82
    public static function linuxArg($arg)
83
    {
84
        // For single quotes existing in the string, we will "exit"
85
        // single-quote mode, add a \' and then "re-enter"
86
        // single-quote mode.  The result of this is that
87
        // 'quote' becomes '\''quote'\''
88
        $arg = preg_replace('/\'/', '\'\\\'\'', $arg);
89
90
        // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace.
91
        // Note that this replacement makes Drush's escapeshellarg work differently
92
        // than the built-in escapeshellarg in PHP on Linux, as these characters
93
        // usually are NOT replaced. However, this was done deliberately to be more
94
        // conservative when running _drush_escapeshellarg_linux on Windows
95
        // (this can happen when generating a command to run on a remote Linux server.)
96
        //
97
        // TODO: Perhaps we should only do this if the local system is Windows?
98
        // n.b. that would be a little more complicated to test.
99
        $arg = str_replace(["\t", "\n", "\r", "\0", "\x0B"], ' ', $arg);
100
101
        // Add surrounding quotes.
102
        $arg = "'" . $arg . "'";
103
104
        return $arg;
105
    }
106
107
    /**
108
     * windowsArg is the Windows version of escapeshellarg().
109
     *
110
     * @param string $arg The argument to escape
111
     *
112
     * @return string The escaped string
113
     */
114
    public static function windowsArg($arg)
115
    {
116
        // Double up existing backslashes
117
        $arg = preg_replace('/\\\/', '\\\\\\\\', $arg);
118
119
        // Double up double quotes
120
        $arg = preg_replace('/"/', '""', $arg);
121
122
        // Double up percents.
123
        // $arg = preg_replace('/%/', '%%', $arg);
124
125
        // Replacing whitespace for good measure (see comment above).
126
        $arg = str_replace(["\t", "\n", "\r", "\0", "\x0B"], ' ', $arg);
127
128
        // Add surrounding quotes.
129
        $arg = '"' . $arg . '"';
130
131
        return $arg;
132
    }
133
}
134