ScpTask::main()   C
last analyzed

Complexity

Conditions 16
Paths 33

Size

Total Lines 69
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 69
ccs 0
cts 41
cp 0
rs 5.5666
c 0
b 0
f 0
cc 16
nc 33
nop 0
crap 272

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\Ext\Ssh;
22
23
use Phing\Exception\BuildException;
24
use Phing\Task;
25
use Phing\Task\System\Element\LogLevelAware;
26
use Phing\Type\Element\FileSetAware;
27
28
/**
29
 * Copy files to and from a remote host using scp.
30
 *
31
 * @author  Michiel Rook <[email protected]>
32
 * @author  Johan Van den Brande <[email protected]>
33
 * @package phing.tasks.ext
34
 */
35
class ScpTask extends Task
36
{
37
    use FileSetAware;
38
    use LogLevelAware;
39
40
    protected $file = "";
41
    protected $todir = "";
42
    protected $mode = null;
43
44
    protected $host = "";
45
    protected $port = 22;
46
    protected $methods = null;
47
    protected $username = "";
48
    protected $password = "";
49
    protected $autocreate = true;
50
    protected $fetch = false;
51
    protected $localEndpoint = "";
52
    protected $remoteEndpoint = "";
53
54
    protected $pubkeyfile = '';
55
    protected $privkeyfile = '';
56
    protected $privkeyfilepassphrase = '';
57
58
    protected $connection = null;
59
    protected $sftp = null;
60
61
    protected $counter = 0;
62
63
    /**
64
     * If number of success of "sftp" is grater than declared number
65
     * decide to skip "scp" operation.
66
     *
67
     * @var int
68
     */
69
    protected $heuristicDecision = 5;
70
71
    /**
72
     * Indicate number of failures in sending files via "scp" over "sftp"
73
     *
74
     * - If number is negative - scp & sftp failed
75
     * - If number is positive - scp failed & sftp succeed
76
     * - If number is 0 - scp succeed
77
     *
78
     * @var integer
79
     */
80
    protected $heuristicScpSftp = 0;
81
82
    /**
83
     * Sets the remote host
84
     *
85
     * @param $h
86
     */
87
    public function setHost($h)
88
    {
89
        $this->host = $h;
90
    }
91
92
    /**
93
     * Returns the remote host
94
     */
95
    public function getHost()
96
    {
97
        return $this->host;
98
    }
99
100
    /**
101
     * Sets the remote host port
102
     *
103
     * @param $p
104
     */
105
    public function setPort($p)
106
    {
107
        $this->port = $p;
108
    }
109
110
    /**
111
     * Returns the remote host port
112
     */
113
    public function getPort()
114
    {
115
        return $this->port;
116
    }
117
118
    /**
119
     * Sets the mode value
120
     *
121
     * @param $value
122
     */
123
    public function setMode($value)
124
    {
125
        $this->mode = $value;
126
    }
127
128
    /**
129
     * Returns the mode value
130
     */
131
    public function getMode()
132
    {
133
        return $this->mode;
134
    }
135
136
    /**
137
     * Sets the username of the user to scp
138
     *
139
     * @param $username
140
     */
141
    public function setUsername($username)
142
    {
143
        $this->username = $username;
144
    }
145
146
    /**
147
     * Returns the username
148
     */
149
    public function getUsername()
150
    {
151
        return $this->username;
152
    }
153
154
    /**
155
     * Sets the password of the user to scp
156
     *
157
     * @param $password
158
     */
159
    public function setPassword($password)
160
    {
161
        $this->password = $password;
162
    }
163
164
    /**
165
     * Returns the password
166
     */
167
    public function getPassword()
168
    {
169
        return $this->password;
170
    }
171
172
    /**
173
     * Sets the public key file of the user to scp
174
     *
175
     * @param $pubkeyfile
176
     */
177
    public function setPubkeyfile($pubkeyfile)
178
    {
179
        $this->pubkeyfile = $pubkeyfile;
180
    }
181
182
    /**
183
     * Returns the pubkeyfile
184
     */
185
    public function getPubkeyfile()
186
    {
187
        return $this->pubkeyfile;
188
    }
189
190
    /**
191
     * Sets the private key file of the user to scp
192
     *
193
     * @param $privkeyfile
194
     */
195
    public function setPrivkeyfile($privkeyfile)
196
    {
197
        $this->privkeyfile = $privkeyfile;
198
    }
199
200
    /**
201
     * Returns the private keyfile
202
     */
203
    public function getPrivkeyfile()
204
    {
205
        return $this->privkeyfile;
206
    }
207
208
    /**
209
     * Sets the private key file passphrase of the user to scp
210
     *
211
     * @param $privkeyfilepassphrase
212
     */
213
    public function setPrivkeyfilepassphrase($privkeyfilepassphrase)
214
    {
215
        $this->privkeyfilepassphrase = $privkeyfilepassphrase;
216
    }
217
218
    /**
219
     * Returns the private keyfile passphrase
220
     *
221
     * @param  $privkeyfilepassphrase
222
     * @return string
223
     */
224
    public function getPrivkeyfilepassphrase($privkeyfilepassphrase)
0 ignored issues
show
Unused Code introduced by
The parameter $privkeyfilepassphrase is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

224
    public function getPrivkeyfilepassphrase(/** @scrutinizer ignore-unused */ $privkeyfilepassphrase)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
225
    {
226
        return $this->privkeyfilepassphrase;
227
    }
228
229
    /**
230
     * Sets whether to autocreate remote directories
231
     *
232
     * @param bool $autocreate
233
     */
234
    public function setAutocreate(bool $autocreate)
235
    {
236
        $this->autocreate = $autocreate;
237
    }
238
239
    /**
240
     * Returns whether to autocreate remote directories
241
     */
242
    public function getAutocreate()
243
    {
244
        return $this->autocreate;
245
    }
246
247
    /**
248
     * Set destination directory
249
     *
250
     * @param $todir
251
     */
252
    public function setTodir($todir)
253
    {
254
        $this->todir = $todir;
255
    }
256
257
    /**
258
     * Returns the destination directory
259
     */
260
    public function getTodir()
261
    {
262
        return $this->todir;
263
    }
264
265
    /**
266
     * Sets local filename
267
     *
268
     * @param $file
269
     */
270
    public function setFile($file)
271
    {
272
        $this->file = $file;
273
    }
274
275
    /**
276
     * Returns local filename
277
     */
278
    public function getFile()
279
    {
280
        return $this->file;
281
    }
282
283
    /**
284
     * Sets whether to send (default) or fetch files
285
     *
286
     * @param bool $fetch
287
     */
288
    public function setFetch(bool $fetch)
289
    {
290
        $this->fetch = $fetch;
291
    }
292
293
    /**
294
     * Returns whether to send (default) or fetch files
295
     */
296
    public function getFetch()
297
    {
298
        return $this->fetch;
299
    }
300
301
    /**
302
     * Declare number of successful operations above which "sftp" will be chosen over "scp".
303
     *
304
     * @param int $heuristicDecision Number
305
     */
306
    public function setHeuristicDecision($heuristicDecision)
307
    {
308
        $this->heuristicDecision = (int) $heuristicDecision;
309
    }
310
311
    /**
312
     * Get declared number of successful operations above which "sftp" will be chosen over "scp".
313
     *
314
     * @return int
315
     */
316
    public function getHeuristicDecision()
317
    {
318
        return $this->heuristicDecision;
319
    }
320
321
    /**
322
     * Creates an Ssh2MethodParam object. Handles the <sshconfig /> nested tag
323
     *
324
     * @return Ssh2MethodParam
325
     */
326
    public function createSshconfig()
327
    {
328
        $this->methods = new Ssh2MethodParam();
329
330
        return $this->methods;
331
    }
332
333
    public function init()
334
    {
335
    }
336
337
    public function main()
338
    {
339
        $p = $this->getProject();
340
341
        if (!function_exists('ssh2_connect')) {
342
            throw new BuildException("To use ScpTask, you need to install the PHP SSH2 extension.");
343
        }
344
345
        if ($this->file === "" && empty($this->filesets)) {
346
            throw new BuildException("Missing either a nested fileset or attribute 'file'");
347
        }
348
349
        if ($this->host === "" || $this->username == "") {
350
            throw new BuildException("Attribute 'host' and 'username' must be set");
351
        }
352
353
        $methods = !empty($this->methods) ? $this->methods->toArray($p) : [];
354
        $this->connection = ssh2_connect($this->host, $this->port, $methods);
355
        if (!$this->connection) {
356
            throw new BuildException("Could not establish connection to " . $this->host . ":" . $this->port . "!");
357
        }
358
359
        $could_auth = null;
360
        if ($this->pubkeyfile) {
361
            $could_auth = ssh2_auth_pubkey_file(
362
                $this->connection,
363
                $this->username,
364
                $this->pubkeyfile,
365
                $this->privkeyfile,
366
                $this->privkeyfilepassphrase
367
            );
368
        } else {
369
            $could_auth = ssh2_auth_password($this->connection, $this->username, $this->password);
370
        }
371
        if (!$could_auth) {
372
            throw new BuildException("Could not authenticate connection!");
373
        }
374
375
        // prepare sftp resource
376
        if ($this->autocreate) {
377
            $this->sftp = ssh2_sftp($this->connection);
378
        }
379
380
        if ($this->file != "") {
381
            $this->copyFile($this->file, basename($this->file));
382
        } else {
383
            if ($this->fetch) {
384
                throw new BuildException("Unable to use filesets to retrieve files from remote server");
385
            }
386
387
            foreach ($this->filesets as $fs) {
388
                $ds = $fs->getDirectoryScanner($this->project);
389
                $files = $ds->getIncludedFiles();
390
                $dir = $fs->getDir($this->project)->getPath();
391
                foreach ($files as $file) {
392
                    $path = $dir . DIRECTORY_SEPARATOR . $file;
393
394
                    // Translate any Windows paths
395
                    $this->copyFile($path, strtr($file, '\\', '/'));
396
                }
397
            }
398
        }
399
400
        $this->log(
401
            "Copied " . $this->counter . " file(s) " . ($this->fetch ? "from" : "to") . " '" . $this->host . "'"
402
        );
403
404
        // explicitly close ssh connection
405
        @ssh2_exec($this->connection, 'exit');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ssh2_exec(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

405
        /** @scrutinizer ignore-unhandled */ @ssh2_exec($this->connection, 'exit');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
406
    }
407
408
    /**
409
     * @param $local
410
     * @param $remote
411
     * @throws BuildException
412
     */
413
    protected function copyFile($local, $remote)
414
    {
415
        $path = rtrim($this->todir, "/") . "/";
416
417
        if ($this->fetch) {
418
            $localEndpoint = $path . $remote;
419
            $remoteEndpoint = $local;
420
421
            $this->log('Will fetch ' . $remoteEndpoint . ' to ' . $localEndpoint, $this->logLevel);
422
423
            $ret = @ssh2_scp_recv($this->connection, $remoteEndpoint, $localEndpoint);
424
425
            if ($ret === false) {
426
                throw new BuildException("Could not fetch remote file '" . $remoteEndpoint . "'");
427
            }
428
        } else {
429
            $localEndpoint = $local;
430
            $remoteEndpoint = $path . $remote;
431
432
            if ($this->autocreate) {
433
                ssh2_sftp_mkdir(
434
                    $this->sftp,
435
                    dirname($remoteEndpoint),
436
                    ($this->mode ?? 0777),
437
                    true
438
                );
439
            }
440
441
            $this->log('Will copy ' . $localEndpoint . ' to ' . $remoteEndpoint, $this->logLevel);
442
443
            $ret = false;
444
            // If more than "$this->heuristicDecision" successfully send files by "ssh2.sftp" over "ssh2_scp_send"
445
            // then ship this step (task finish ~40% faster)
446
            if ($this->heuristicScpSftp < $this->heuristicDecision) {
447
                if (null !== $this->mode) {
448
                    $ret = @ssh2_scp_send($this->connection, $localEndpoint, $remoteEndpoint, $this->mode);
449
                } else {
450
                    $ret = @ssh2_scp_send($this->connection, $localEndpoint, $remoteEndpoint);
451
                }
452
            }
453
454
            // sometimes remote server allow only create files via sftp (eg. phpcloud.com)
455
            if (false === $ret && $this->sftp) {
456
                // mark failure of "scp"
457
                --$this->heuristicScpSftp;
458
459
                // try create file via ssh2.sftp://file wrapper
460
                $fh = @fopen("ssh2.sftp://$this->sftp/$remoteEndpoint", 'wb');
461
                if (is_resource($fh)) {
462
                    $ret = fwrite($fh, file_get_contents($localEndpoint));
463
                    fclose($fh);
464
465
                    // mark success of "sftp"
466
                    $this->heuristicScpSftp += 2;
467
                }
468
            }
469
470
            if ($ret === false) {
471
                throw new BuildException("Could not create remote file '" . $remoteEndpoint . "'");
472
            }
473
        }
474
475
        $this->counter++;
476
    }
477
}
478