This repository has been archived by the owner on Aug 17, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Sftp.php
527 lines (447 loc) · 16.5 KB
/
Sftp.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
<?php
/**
* Copyright 2009-2010 Zikula Foundation - Zikula Application Framework
*
* This work is contributed to the Zikula Foundation under one or more
* Contributor Agreements and licensed to You under the following license:
*
* @license GNU/LGPLv3 (or at your option, any later version).
* @package FileSystem
*
* Please see the NOTICE file distributed with this source code for further
* information regarding copyright and licensing.
*/
namespace Zikula\Component\FileSystem;
/**
* Sftp is the standard driver for SFTP connections.
*/
class Sftp extends AbstractDriver
{
/**
* Resource.
*
* @var object
*/
private $resource;
/**
* SSH Resource.
*
* @var object
*/
private $sshResource;
/**
* Current working directory.
*
* @var string
*/
private $dir;
/**
* Shell type to use when creating a ssh shell.
*
* @var string
*/
private $terminal = 'xterm';
/**
* Standard function for creating a SFTP connection and logging in.
*
* This must be called before any of the other functions in the
* Interface. However the construct itself calles this function
* upon completion, which alleviates the need to ever call this function
* manually.
*
* @return boolean True on connect false on failure
*/
public function connect()
{
$this->errorHandler->start();
$methods = array();
if ($this->configuration->getAuthType() !== 'pass') {
$methods['hostkey'] = $this->configuration->getAuthType();
}
if (($this->sshResource = $this->driver->connect($this->configuration->getHost(), $this->configuration->getPort(), $methods)) !== false) {
//connected
if ($this->configuration->getAuthType() !== 'pass') {
$auth = $this->driver->authPubkey(
$this->sshResource, $this->configuration->getUser(), $this->configuration->getPubKey(), $this->configuration->getPrivKey(), $this->configuration->getPassphrase());
} else {
$auth = $this->driver->authPassword($this->sshResource, $this->configuration->getUser(), $this->configuration->getPass());
}
if ($auth !== false) {
//logged in
if (($this->resource = $this->driver->sftpStart($this->sshResource)) !== false) {
//started sftp
if (($this->dir = $this->driver->realpath($this->resource, $this->configuration->getDir())) !== false) {
//changed dir
$this->errorHandler->stop();
return true;
}
//could not enter dir
}
//could not start sftp
}
//Could not log in
}
//Could not connect to host/port
$this->errorHandler->stop();
return false;
}
/**
* Put a local file up to a remote server.
*
* This method should be used with caution because it undermines the purpose of the
* FileSystem classes by the fact that it gets the local file without using the
* local driver.
*
* @param string $local The pathname to the local file.
* @param string $remote The pathname to the desired remote file.
*
* @return boolean True on success false on failure.
*/
public function put($local, $remote)
{
$this->errorHandler->start();
if ($this->driver->scpSend($this->resource, $local, $remote)) {
$this->errorHandler->stop();
return true;
}
$this->errorHandler->stop();
return false;
}
/**
* Similar to put but does not get the file localy.
*
* This should be used instead of put in most cases.
*
* @param string $stream The resource to put remotely, probably the resource returned from a fget.
* @param string $remote The pathname to the desired remote pathname.
*
* @return boolean|integer Number of bytes written on success, false on failure.
*/
public function fput($stream, $remote)
{
if ($remote == '' || substr($remote, 0, 1) !== '/') {
$remote = $this->dir . '/' . $remote;
}
$this->errorHandler->start();
if (($bytes = $this->driver->putContents($this->resource, $remote, $stream)) !== false) {
fclose($stream);
$this->errorHandler->stop();
return $bytes;
}
$this->errorHandler->stop();
return false;
}
/**
* Write the contents of a string to the remote.
*
* @param string $contents The contents to put remotely.
* @param string $remote The pathname to the desired remote pathname.
*
* @return boolean|integer Number of bytes written on success, false on failure.
*/
public function putContents($contents, $remote)
{
$stream = fopen('data://text/plain,' . $contents, 'r');
return $this->fput($stream, $remote);
}
/**
* Get the contents of a file from the remote.
*
* @param string $remote The pathname to the desired remote file.
*
* @return string|boolean The string containing file contents on success false on fail.
*/
public function getContents($remote)
{
return stream_get_contents($this->fget($remote));
}
/**
* Get a remote file and save it localy, opposite of put function.
*
* This method should be used with caution because it undermines the purpose of the
* FileSystem classes by the fact that it saves the file localy without using the
* local driver.
*
* @param string $local The pathname to the desired local file.
* @param string $remote The pathname to the remote file to access.
*
* @return boolean True on success false on failure.
*/
public function get($local, $remote)
{
$this->errorHandler->start();
if ($this->driver->scpRecv($this->resource, $remote, $local)) {
$this->errorHandler->stop();
return true;
}
$this->errorHandler->stop();
return false;
}
/**
* Similar to get but does not save file locally.
*
* This should usually be used instead of get in most cases.
*
* @param string $remote The path to the remote file.
*
* @return resource|bool The resource on success false on fail.
*/
public function fget($remote)
{
if ($remote == '' || substr($remote, 0, 1) !== '/') {
$remote = $this->dir . '/' . $remote;
}
$this->errorHandler->start();
if (($handle = $this->driver->sftpFopen($this->resource, $remote, 'r+')) !== false) {
rewind($handle);
$this->errorHandler->stop();
return $handle;
}
$this->errorHandler->stop();
return false;
}
/**
* Change the permissions of a file.
*
* @param integer $perm The permission to assign to the file, unix style (example: 777 for full permission).
* @param string $file The pathname to the remote file to chmod.
*
* @return boolean|integer The new permission or false if failed.
*/
public function chmod($perm, $file)
{
$this->errorHandler->start();
if ($file == '' || substr($file, 0, 1) !== '/') {
$file = $this->dir . '/' . $file;
}
//make sure that $perm is numeric, this also stops injection
if (!is_numeric($perm)) {
$this->errorHandler->register('permission "' . $perm . '" must be numeric.');
return false;
}
$perm = intval($perm);
if (($file = $this->driver->realpath($this->resource, $file)) === false) {
$this->errorHandler->stop(); //source file not found.
return false;
}
if (($shell = $this->driver->sshShell($this->sshResource, $this->terminal)) == false) {
return false; //could not get shell.
}
if ($this->driver->sshShellWrite($shell, "chmod $perm $file;echo :::$?:::" . PHP_EOL) === false) {
return false; //couldnt write to shell
}
usleep(350000);
if (($resp = $this->driver->sshShellRead($shell, 4096)) === false) {
return false; //could not read from shell
}
fclose($shell); //the shell closes even if we dont put this, thats why next line is needed
$this->connect(); //TODO we need a way to make sure that the connection is alive
$matches = array();
preg_match("/:::\d:::/", $resp, $matches);
if (sizeof($matches) > 0) {
switch (intval(str_replace(':', '', $matches[0]))) {
case 1:
$this->errorHandler->register('Chmod returned with Code 1: failure.', 0);
$this->errorHandler->stop();
return false;
case 0:
$this->errorHandler->stop();
return $perm;
default:
$this->errorHandler->stop();
return false;
}
}
//size of matches less then 1, there is no readable response
$this->errorHandler->stop();
$this->errorHandler->register('Did not get acknowledgment from host, chmod may or may not have succeeded.', 0);
return false;
}
/**
* Get the entire contents of a directory.
*
* @param string $dir The directory to get the contents of, blank for current directory, start with / for absolute path.
*
* @return array|boolean An array of the contents of $dir or false if fail.
*/
public function ls($dir = '')
{
if ($dir == '' || substr($dir, 0, 1) !== '/') {
$dir = $this->dir . '/' . $dir;
}
if ($this->driver->sftpIsDir($this->resource, $dir)) {
$handle = $this->driver->sftpOpenDir($this->resource, $dir);
$files = array();
while (false !== ($file = $this->driver->sftpReadDir($handle))) {
if (substr("$file", 0, 1) != '.') {
$files[] = $file;
}
}
//finished searching the directory
return $files;
}
//if IsDir fails that means its either not a directory or doesnt exist
if (!$this->driver->sftpFileExists($this->resource, $dir)) {
$this->errorHandler->register("$dir does not exist.", 0);
return false;
}
$this->errorHandler->register("$dir is not a directory", 0);
return false;
}
/**
* Change the current working directory on the remote machine.
*
* @param string $dir The directory on the remote machine to enter, start with '/' for absolute path.
*
* @return boolean True on success false on failure.
*/
public function cd($dir = '')
{
if ($dir == '' || substr($dir, 0, 1) !== '/') {
$dir = $this->dir . '/' . $dir;
}
$this->errorHandler->start();
if (($dir = $this->driver->realpath($this->resource, $dir)) !== false) {
$this->dir = $dir;
$this->errorHandler->stop();
return true;
}
$this->errorHandler->stop();
return false;
}
/**
* Move a remote file to a new location on the remote server.
*
* This can also be used to rename files.
*
* @param string $sourcepath The path to the original source file.
* @param string $destpath The path to where you want to move the source file.
*
* @return boolean True on success false on failure.
*/
public function mv($sourcepath, $destpath)
{
$this->errorHandler->start();
if ($sourcepath == '' || substr($sourcepath, 0, 1) !== '/') {
$sourcepath = $this->dir . '/' . $sourcepath;
}
if ($destpath == '' || substr($destpath, 0, 1) !== '/') {
$destpath = $this->dir . '/' . $destpath;
}
if (($sourcepath = $this->driver->realpath($this->resource, $sourcepath)) !== false) {
if (($this->driver->sftpRename($this->resource, $sourcepath, $destpath)) !== false) {
$this->errorHandler->stop(); //renamed file
return true;
}//could not rename file
}//Could not get reapath of sourcefile, it does not exist
$this->errorHandler->stop();
return false;
}
/**
* Copy a file on the remote server to a new location on the remote.
*
* Same as mv method but leaves the original file.
*
* @param string $sourcepath The path to the original source file.
* @param string $destpath The path to where you want to copy the source file.
*
* @return boolean True on success false on failure.
*/
public function cp($sourcepath, $destpath)
{
$this->errorHandler->start();
if ($sourcepath == '' || substr($sourcepath, 0, 1) !== '/') {
$sourcepath = $this->dir . '/' . $sourcepath;
}
if ($destpath == '' || substr($destpath, 0, 1) !== '/') {
$destpath = $this->dir . '/' . $destpath;
}
if (($sourcepath = $this->driver->realpath($this->resource, $sourcepath)) === false) {
$this->errorHandler->stop(); //source file not found.
return false;
}
if (($shell = $this->driver->sshShell($this->sshResource, $this->terminal)) == false) {
return false; //could not get shell.
}
if ($this->driver->sshShellWrite($shell, "cp $sourcepath $destpath;echo :::$?:::" . PHP_EOL) === false) {
return false; //couldnt write to shell
}
usleep(350000);
if (($resp = $this->driver->sshShellRead($shell, 4096)) === false) {
return false; //could not read from shell
}
fclose($shell); //the shell closes even if we dont put this, thats why next line is needed
$this->connect(); //TODO we need a way to make sure that the connection is alive
$matches = array();
preg_match("/:::\d:::/", $resp, $matches);
if (sizeof($matches) > 0) {
switch (str_replace(':', '', $matches[0])) {
case 1:
$this->errorHandler->register('cp returned with Code 1: failure.', 0);
$this->errorHandler->stop();
return false;
case 0:
$this->errorHandler->stop();
return true;
default:
$this->errorHandler->stop();
return false;
}
} //size of matches less then 1, there is no readable response
$this->errorHandler->stop();
$this->errorHandler->register('Did not get acknowledgment from host, cp may or may not have succeeded.', 0);
return false;
}
/**
* Remove a file from the remote file system.
*
* @param string $sourcepath The path to the remote file to remove.
*
* @return boolean
*/
public function rm($sourcepath)
{
if ($sourcepath == '' || substr($sourcepath, 0, 1) !== '/') {
$sourcepath = $this->dir . '/' . $sourcepath;
}
$this->errorHandler->start();
//check the file actauly exists.
if (($sourcepath = $this->driver->realpath($this->resource, $sourcepath)) !== false) {
//file exists
if ($this->driver->sftpDelete($this->resource, $sourcepath)) {
//file deleted
$this->errorHandler->stop();
return true;
} //file not deleted
} //file does not exist.
$this->errorHandler->stop();
$this->errorHandler->register("Could not delete: $sourcepath", 0);
return false;
}
/**
* Check if a file is writable.
*
* @param string $sourcepath The path to the file to check if is writable.
*
* @return boolean True if is writable False if not.
*/
public function isWritable($sourcepath)
{
$this->errorHandler->start();
if ($this->driver->is_writable($sourcepath)) {
$this->errorHandler->stop();
return true;
}
$this->errorHandler->stop();
return false;
}
/**
* Determine if driver is available for use.
*
* @return boolean True if available, false if not.
*/
public static function isAvailable()
{
return extension_loaded('ssh2');
}
}