1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19

20
/**
21
 * Commandline objects help handling command lines specifying processes to
22
 * execute.
23
 *
24
 * The class can be used to define a command line as nested elements or as a
25
 * helper to define a command line by an application.
26
 * <p>
27
 * <code>
28
 * &lt;someelement&gt;<br>
29
 * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
30
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
31
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
32
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
33
 * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
34
 * &lt;/someelement&gt;<br>
35
 * </code>
36
 * The element <code>someelement</code> must provide a method
37
 * <code>createAcommandline</code> which returns an instance of this class.
38
 *
39
 * @author  thomas.haas@softwired-inc.com
40
 * @author  <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
41
 * @package phing.types
42
 */
43
class Commandline implements Countable
44
{
45
    /**
46
     * @var CommandlineArgument[]
47
     */
48
    public $arguments = []; // public so "inner" class can access
49

50
    /**
51
     * Full path (if not on %PATH% env var) to executable program.
52
     *
53
     * @var string
54
     */
55
    public $executable; // public so "inner" class can access
56

57
    public const DISCLAIMER = "The ' characters around the executable and arguments are not part of the command.";
58
    private $escape = false;
59

60
    /**
61
     * @param null $to_process
62
     * @throws BuildException
63
     */
64 1
    public function __construct($to_process = null)
65
    {
66 1
        if ($to_process !== null) {
67 1
            $tmp = static::translateCommandline($to_process);
68 1
            if ($tmp) {
69 1
                $this->setExecutable(array_shift($tmp)); // removes first el
70 1
                foreach ($tmp as $arg) { // iterate through remaining elements
71 1
                    $this->createArgument()->setValue($arg);
72
                }
73
            }
74
        }
75
    }
76

77
    /**
78
     * Creates an argument object and adds it to our list of args.
79
     *
80
     * <p>Each commandline object has at most one instance of the
81
     * argument class.</p>
82
     *
83
     * @param  boolean $insertAtStart if true, the argument is inserted at the
84
     *                                beginning of the list of args, otherwise
85
     *                                it is appended.
86
     * @return CommandlineArgument
87
     */
88 1
    public function createArgument($insertAtStart = false)
89
    {
90 1
        $argument = new CommandlineArgument($this);
91 1
        if ($insertAtStart) {
92 1
            array_unshift($this->arguments, $argument);
93
        } else {
94 1
            $this->arguments[] = $argument;
95
        }
96

97 1
        return $argument;
98
    }
99

100
    /**
101
     * Sets the executable to run.
102
     *
103
     * @param string $executable
104
     * @param bool $translateFileSeparator
105
     */
106 1
    public function setExecutable($executable, $translateFileSeparator = true): void
107
    {
108 1
        if ($executable === null || $executable === '') {
109 1
            return;
110
        }
111 1
        $this->executable = $translateFileSeparator
112 1
            ? str_replace(['/', '\\'], FileUtils::getSeparator(), $executable)
113 0
            : $executable;
114
    }
115

116
    /**
117
     * @return string
118
     */
119 1
    public function getExecutable()
120
    {
121 1
        return $this->executable;
122
    }
123

124
    /**
125
     * @param array $arguments
126
     */
127 1
    public function addArguments(array $arguments)
128
    {
129 1
        foreach ($arguments as $arg) {
130 1
            $this->createArgument()->setValue($arg);
131
        }
132
    }
133

134
    /**
135
     * Returns the executable and all defined arguments.
136
     *
137
     * @return array
138
     */
139 1
    public function getCommandline(): array
140
    {
141 1
        $args = $this->getArguments();
142 1
        if ($this->executable !== null && $this->executable !== '') {
143 1
            array_unshift($args, $this->executable);
144
        }
145

146 1
        return $args;
147
    }
148

149
    /**
150
     * Returns all arguments defined by <code>addLine</code>,
151
     * <code>addValue</code> or the argument object.
152
     */
153 1
    public function getArguments(): array
154
    {
155 1
        $result = [];
156 1
        foreach ($this->arguments as $arg) {
157 1
            $parts = $arg->getParts();
158 1
            if ($parts !== null) {
159 1
                foreach ($parts as $part) {
160 1
                    $result[] = $part;
161
                }
162
            }
163
        }
164

165 1
        return $result;
166
    }
167

168 1
    public function setEscape($flag)
169
    {
170 1
        $this->escape = $flag;
171
    }
172

173
    /**
174
     * @return string
175
     */
176 1
    public function __toString()
177
    {
178
        try {
179 1
            $cmd = $this->toString($this->getCommandline());
180 0
        } catch (BuildException $be) {
181 0
            $cmd = '';
182
        }
183

184 1
        return $cmd;
185
    }
186

187
    /**
188
     * Put quotes around the given String if necessary.
189
     *
190
     * <p>If the argument doesn't include spaces or quotes, return it
191
     * as is. If it contains double quotes, use single quotes - else
192
     * surround the argument by double quotes.</p>
193
     *
194
     * @param $argument
195
     *
196
     * @return string
197
     *
198
     * @throws BuildException if the argument contains both, single
199
     *                           and double quotes.
200
     */
201 1
    public static function quoteArgument($argument, $escape = false)
202
    {
203 1
        if ($escape) {
204 1
            return escapeshellarg($argument);
205
        }
206

207 1
        if (strpos($argument, '"') !== false) {
208 0
            if (strpos($argument, "'") !== false) {
209 0
                throw new BuildException("Can't handle single and double quotes in same argument");
210
            }
211

212 0
            return '\'' . $argument . '\'';
213
        }
214

215
        if (
216 1
            strpos($argument, "'") !== false
217 1
            || strpos($argument, ' ') !== false
218
            // WIN9x uses a bat file for executing commands
219 1
            || (OsCondition::isFamily('win32')
220 0
            && strpos($argument, ';') !== false)
221
        ) {
222 1
            return '"' . $argument . '"';
223
        }
224

225 1
        return $argument;
226
    }
227

228
    /**
229
     * Quotes the parts of the given array in way that makes them
230
     * usable as command line arguments.
231
     *
232
     * @param $lines
233
     *
234
     * @return string
235
     *
236
     * @throws BuildException
237
     */
238 1
    private function toString($lines = null): string
239
    {
240
        // empty path return empty string
241 1
        if ($lines === null || count($lines) === 0) {
242 0
            return '';
243
        }
244

245 1
        return implode(
246 1
            ' ',
247 1
            array_map(
248
                function ($arg) {
249 1
                    return self::quoteArgument($arg, $this->escape);
250 1
                },
251 1
                $lines
252
            )
253
        );
254
    }
255

256
    /**
257
     * Crack a command line.
258
     *
259
     * @param string $toProcess the command line to process.
260
     *
261
     * @return string[] the command line broken into strings.
262
     *                  An empty or null toProcess parameter results in a zero sized array.
263
     *
264
     * @throws \BuildException
265
     */
266 1
    public static function translateCommandline(string $toProcess = null): array
267
    {
268 1
        if ($toProcess === null || $toProcess === '') {
269 0
            return [];
270
        }
271

272
        // parse with a simple finite state machine
273

274 1
        $normal = 0;
275 1
        $inQuote = 1;
276 1
        $inDoubleQuote = 2;
277

278 1
        $state = $normal;
279 1
        $args = [];
280 1
        $current = "";
281 1
        $lastTokenHasBeenQuoted = false;
282

283 1
        $tokens = preg_split('/(["\' ])/', $toProcess, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
284 1
        while (($nextTok = array_shift($tokens)) !== null) {
285
            switch ($state) {
286 1
                case $inQuote:
287 1
                    if ("'" === $nextTok) {
288 1
                        $lastTokenHasBeenQuoted = true;
289 1
                        $state = $normal;
290
                    } else {
291 1
                        $current .= $nextTok;
292
                    }
293 1
                    break;
294 1
                case $inDoubleQuote:
295 1
                    if ("\"" === $nextTok) {
296 1
                        $lastTokenHasBeenQuoted = true;
297 1
                        $state = $normal;
298
                    } else {
299 1
                        $current .= $nextTok;
300
                    }
301 1
                    break;
302
                default:
303 1
                    if ("'" === $nextTok) {
304 1
                        $state = $inQuote;
305 1
                    } elseif ("\"" === $nextTok) {
306 1
                        $state = $inDoubleQuote;
307 1
                    } elseif (" " === $nextTok) {
308 1
                        if ($lastTokenHasBeenQuoted || $current !== '') {
309 1
                            $args[] = $current;
310 1
                            $current = "";
311
                        }
312
                    } else {
313 1
                        $current .= $nextTok;
314
                    }
315 1
                    $lastTokenHasBeenQuoted = false;
316 1
                    break;
317
            }
318
        }
319

320 1
        if ($lastTokenHasBeenQuoted || $current !== '') {
321 1
            $args[] = $current;
322
        }
323

324 1
        if ($state === $inQuote || $state === $inDoubleQuote) {
325 1
            throw new BuildException('unbalanced quotes in ' . $toProcess);
326
        }
327

328 1
        return $args;
329
    }
330

331
    /**
332
     * @return int Number of components in current commandline.
333
     */
334 0
    public function count(): int
335
    {
336 0
        return count($this->getCommandline());
337
    }
338

339
    /**
340
     * @throws \BuildException
341
     */
342 0
    public function __clone()
343
    {
344 0
        $c = new self();
345 0
        $c->addArguments($this->getArguments());
346
    }
347

348
    /**
349
     * Return a marker.
350
     *
351
     * <p>This marker can be used to locate a position on the
352
     * commandline - to insert something for example - when all
353
     * parameters have been set.</p>
354
     *
355
     * @return CommandlineMarker
356
     */
357 1
    public function createMarker()
358
    {
359 1
        return new CommandlineMarker($this, count($this->arguments));
360
    }
361

362
    /**
363
     * Returns a String that describes the command and arguments
364
     * suitable for verbose output before a call to
365
     * <code>Runtime.exec(String[])</code>.
366
     *
367
     * <p>This method assumes that the first entry in the array is the
368
     * executable to run.</p>
369
     *
370
     * @param  array|Commandline $args CommandlineArgument[] to use
371
     * @return string
372
     */
373 1
    public function describeCommand($args = null)
374
    {
375 1
        if ($args === null) {
376 1
            $args = $this->getCommandline();
377 0
        } elseif ($args instanceof self) {
378 0
            $args = $args->getCommandline();
379
        }
380

381 1
        if (!$args) {
382 0
            return '';
383
        }
384

385 1
        $buf = "Executing '";
386 1
        $buf .= $args[0];
387 1
        $buf .= "'";
388 1
        if (count($args) > 0) {
389 1
            $buf .= ' with ';
390 1
            $buf .= $this->describeArguments($args, 1);
391
        } else {
392 0
            $buf .= self::DISCLAIMER;
393
        }
394

395 1
        return $buf;
396
    }
397

398
    /**
399
     * Returns a String that describes the arguments suitable for
400
     * verbose output before a call to
401
     * <code>Runtime.exec(String[])</code>
402
     *
403
     * @param  array $args arguments to use (default is to use current class args)
404
     * @param  int $offset ignore entries before this index
405
     * @return string
406
     */
407 1
    public function describeArguments(array $args = null, $offset = 0)
408
    {
409 1
        if ($args === null) {
410 0
            $args = $this->getArguments();
411
        }
412

413 1
        if ($args === null || count($args) <= $offset) {
414 0
            return '';
415
        }
416

417 1
        $buf = "argument";
418 1
        if (count($args) > $offset) {
419 1
            $buf .= "s";
420
        }
421 1
        $buf .= ":" . PHP_EOL;
422 1
        for ($i = $offset, $alen = count($args); $i < $alen; $i++) {
423 1
            $buf .= "'" . $args[$i] . "'" . PHP_EOL;
424
        }
425 1
        $buf .= self::DISCLAIMER;
426

427 1
        return $buf;
428
    }
429
}

Read our documentation on viewing source code .

Loading