| 103 |
- |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
/**
|
|
|
4 |
* Pure-PHP implementation of SFTP.
|
|
|
5 |
*
|
|
|
6 |
* PHP version 5
|
|
|
7 |
*
|
|
|
8 |
* Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
|
|
|
9 |
* implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
|
|
|
10 |
* to an SFTPv4/5/6 server.
|
|
|
11 |
*
|
|
|
12 |
* The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
|
|
|
13 |
*
|
|
|
14 |
* Here's a short example of how to use this library:
|
|
|
15 |
* <code>
|
|
|
16 |
* <?php
|
|
|
17 |
* include 'vendor/autoload.php';
|
|
|
18 |
*
|
|
|
19 |
* $sftp = new \phpseclib\Net\SFTP('www.domain.tld');
|
|
|
20 |
* if (!$sftp->login('username', 'password')) {
|
|
|
21 |
* exit('Login Failed');
|
|
|
22 |
* }
|
|
|
23 |
*
|
|
|
24 |
* echo $sftp->pwd() . "\r\n";
|
|
|
25 |
* $sftp->put('filename.ext', 'hello, world!');
|
|
|
26 |
* print_r($sftp->nlist());
|
|
|
27 |
* ?>
|
|
|
28 |
* </code>
|
|
|
29 |
*
|
|
|
30 |
* @category Net
|
|
|
31 |
* @package SFTP
|
|
|
32 |
* @author Jim Wigginton <terrafrost@php.net>
|
|
|
33 |
* @copyright 2009 Jim Wigginton
|
|
|
34 |
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
|
|
35 |
* @link http://phpseclib.sourceforge.net
|
|
|
36 |
*/
|
|
|
37 |
|
|
|
38 |
namespace phpseclib\Net;
|
|
|
39 |
|
|
|
40 |
/**
|
|
|
41 |
* Pure-PHP implementations of SFTP.
|
|
|
42 |
*
|
|
|
43 |
* @package SFTP
|
|
|
44 |
* @author Jim Wigginton <terrafrost@php.net>
|
|
|
45 |
* @access public
|
|
|
46 |
*/
|
|
|
47 |
class SFTP extends SSH2
|
|
|
48 |
{
|
|
|
49 |
/**
|
|
|
50 |
* SFTP channel constant
|
|
|
51 |
*
|
|
|
52 |
* \phpseclib\Net\SSH2::exec() uses 0 and \phpseclib\Net\SSH2::read() / \phpseclib\Net\SSH2::write() use 1.
|
|
|
53 |
*
|
|
|
54 |
* @see \phpseclib\Net\SSH2::_send_channel_packet()
|
|
|
55 |
* @see \phpseclib\Net\SSH2::_get_channel_packet()
|
|
|
56 |
* @access private
|
|
|
57 |
*/
|
|
|
58 |
const CHANNEL = 0x100;
|
|
|
59 |
|
|
|
60 |
/**#@+
|
|
|
61 |
* @access public
|
|
|
62 |
* @see \phpseclib\Net\SFTP::put()
|
|
|
63 |
*/
|
|
|
64 |
/**
|
|
|
65 |
* Reads data from a local file.
|
|
|
66 |
*/
|
|
|
67 |
const SOURCE_LOCAL_FILE = 1;
|
|
|
68 |
/**
|
|
|
69 |
* Reads data from a string.
|
|
|
70 |
*/
|
|
|
71 |
// this value isn't really used anymore but i'm keeping it reserved for historical reasons
|
|
|
72 |
const SOURCE_STRING = 2;
|
|
|
73 |
/**
|
|
|
74 |
* Reads data from callback:
|
|
|
75 |
* function callback($length) returns string to proceed, null for EOF
|
|
|
76 |
*/
|
|
|
77 |
const SOURCE_CALLBACK = 16;
|
|
|
78 |
/**
|
|
|
79 |
* Resumes an upload
|
|
|
80 |
*/
|
|
|
81 |
const RESUME = 4;
|
|
|
82 |
/**
|
|
|
83 |
* Append a local file to an already existing remote file
|
|
|
84 |
*/
|
|
|
85 |
const RESUME_START = 8;
|
|
|
86 |
/**#@-*/
|
|
|
87 |
|
|
|
88 |
/**
|
|
|
89 |
* Packet Types
|
|
|
90 |
*
|
|
|
91 |
* @see self::__construct()
|
|
|
92 |
* @var array
|
|
|
93 |
* @access private
|
|
|
94 |
*/
|
|
|
95 |
var $packet_types = array();
|
|
|
96 |
|
|
|
97 |
/**
|
|
|
98 |
* Status Codes
|
|
|
99 |
*
|
|
|
100 |
* @see self::__construct()
|
|
|
101 |
* @var array
|
|
|
102 |
* @access private
|
|
|
103 |
*/
|
|
|
104 |
var $status_codes = array();
|
|
|
105 |
|
|
|
106 |
/**
|
|
|
107 |
* The Request ID
|
|
|
108 |
*
|
|
|
109 |
* The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
|
|
|
110 |
* concurrent actions, so it's somewhat academic, here.
|
|
|
111 |
*
|
|
|
112 |
* @var int
|
|
|
113 |
* @see self::_send_sftp_packet()
|
|
|
114 |
* @access private
|
|
|
115 |
*/
|
|
|
116 |
var $request_id = false;
|
|
|
117 |
|
|
|
118 |
/**
|
|
|
119 |
* The Packet Type
|
|
|
120 |
*
|
|
|
121 |
* The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
|
|
|
122 |
* concurrent actions, so it's somewhat academic, here.
|
|
|
123 |
*
|
|
|
124 |
* @var int
|
|
|
125 |
* @see self::_get_sftp_packet()
|
|
|
126 |
* @access private
|
|
|
127 |
*/
|
|
|
128 |
var $packet_type = -1;
|
|
|
129 |
|
|
|
130 |
/**
|
|
|
131 |
* Packet Buffer
|
|
|
132 |
*
|
|
|
133 |
* @var string
|
|
|
134 |
* @see self::_get_sftp_packet()
|
|
|
135 |
* @access private
|
|
|
136 |
*/
|
|
|
137 |
var $packet_buffer = '';
|
|
|
138 |
|
|
|
139 |
/**
|
|
|
140 |
* Extensions supported by the server
|
|
|
141 |
*
|
|
|
142 |
* @var array
|
|
|
143 |
* @see self::_initChannel()
|
|
|
144 |
* @access private
|
|
|
145 |
*/
|
|
|
146 |
var $extensions = array();
|
|
|
147 |
|
|
|
148 |
/**
|
|
|
149 |
* Server SFTP version
|
|
|
150 |
*
|
|
|
151 |
* @var int
|
|
|
152 |
* @see self::_initChannel()
|
|
|
153 |
* @access private
|
|
|
154 |
*/
|
|
|
155 |
var $version;
|
|
|
156 |
|
|
|
157 |
/**
|
|
|
158 |
* Current working directory
|
|
|
159 |
*
|
|
|
160 |
* @var string
|
|
|
161 |
* @see self::_realpath()
|
|
|
162 |
* @see self::chdir()
|
|
|
163 |
* @access private
|
|
|
164 |
*/
|
|
|
165 |
var $pwd = false;
|
|
|
166 |
|
|
|
167 |
/**
|
|
|
168 |
* Packet Type Log
|
|
|
169 |
*
|
|
|
170 |
* @see self::getLog()
|
|
|
171 |
* @var array
|
|
|
172 |
* @access private
|
|
|
173 |
*/
|
|
|
174 |
var $packet_type_log = array();
|
|
|
175 |
|
|
|
176 |
/**
|
|
|
177 |
* Packet Log
|
|
|
178 |
*
|
|
|
179 |
* @see self::getLog()
|
|
|
180 |
* @var array
|
|
|
181 |
* @access private
|
|
|
182 |
*/
|
|
|
183 |
var $packet_log = array();
|
|
|
184 |
|
|
|
185 |
/**
|
|
|
186 |
* Error information
|
|
|
187 |
*
|
|
|
188 |
* @see self::getSFTPErrors()
|
|
|
189 |
* @see self::getLastSFTPError()
|
|
|
190 |
* @var string
|
|
|
191 |
* @access private
|
|
|
192 |
*/
|
|
|
193 |
var $sftp_errors = array();
|
|
|
194 |
|
|
|
195 |
/**
|
|
|
196 |
* Stat Cache
|
|
|
197 |
*
|
|
|
198 |
* Rather than always having to open a directory and close it immediately there after to see if a file is a directory
|
|
|
199 |
* we'll cache the results.
|
|
|
200 |
*
|
|
|
201 |
* @see self::_update_stat_cache()
|
|
|
202 |
* @see self::_remove_from_stat_cache()
|
|
|
203 |
* @see self::_query_stat_cache()
|
|
|
204 |
* @var array
|
|
|
205 |
* @access private
|
|
|
206 |
*/
|
|
|
207 |
var $stat_cache = array();
|
|
|
208 |
|
|
|
209 |
/**
|
|
|
210 |
* Max SFTP Packet Size
|
|
|
211 |
*
|
|
|
212 |
* @see self::__construct()
|
|
|
213 |
* @see self::get()
|
|
|
214 |
* @var array
|
|
|
215 |
* @access private
|
|
|
216 |
*/
|
|
|
217 |
var $max_sftp_packet;
|
|
|
218 |
|
|
|
219 |
/**
|
|
|
220 |
* Stat Cache Flag
|
|
|
221 |
*
|
|
|
222 |
* @see self::disableStatCache()
|
|
|
223 |
* @see self::enableStatCache()
|
|
|
224 |
* @var bool
|
|
|
225 |
* @access private
|
|
|
226 |
*/
|
|
|
227 |
var $use_stat_cache = true;
|
|
|
228 |
|
|
|
229 |
/**
|
|
|
230 |
* Sort Options
|
|
|
231 |
*
|
|
|
232 |
* @see self::_comparator()
|
|
|
233 |
* @see self::setListOrder()
|
|
|
234 |
* @var array
|
|
|
235 |
* @access private
|
|
|
236 |
*/
|
|
|
237 |
var $sortOptions = array();
|
|
|
238 |
|
|
|
239 |
/**
|
|
|
240 |
* Default Constructor.
|
|
|
241 |
*
|
|
|
242 |
* Connects to an SFTP server
|
|
|
243 |
*
|
|
|
244 |
* @param string $host
|
|
|
245 |
* @param int $port
|
|
|
246 |
* @param int $timeout
|
|
|
247 |
* @return \phpseclib\Net\SFTP
|
|
|
248 |
* @access public
|
|
|
249 |
*/
|
|
|
250 |
function __construct($host, $port = 22, $timeout = 10)
|
|
|
251 |
{
|
|
|
252 |
parent::__construct($host, $port, $timeout);
|
|
|
253 |
|
|
|
254 |
$this->max_sftp_packet = 1 << 15;
|
|
|
255 |
|
|
|
256 |
$this->packet_types = array(
|
|
|
257 |
1 => 'NET_SFTP_INIT',
|
|
|
258 |
2 => 'NET_SFTP_VERSION',
|
|
|
259 |
/* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
|
|
|
260 |
SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
|
|
|
261 |
pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
|
|
|
262 |
3 => 'NET_SFTP_OPEN',
|
|
|
263 |
4 => 'NET_SFTP_CLOSE',
|
|
|
264 |
5 => 'NET_SFTP_READ',
|
|
|
265 |
6 => 'NET_SFTP_WRITE',
|
|
|
266 |
7 => 'NET_SFTP_LSTAT',
|
|
|
267 |
9 => 'NET_SFTP_SETSTAT',
|
|
|
268 |
11 => 'NET_SFTP_OPENDIR',
|
|
|
269 |
12 => 'NET_SFTP_READDIR',
|
|
|
270 |
13 => 'NET_SFTP_REMOVE',
|
|
|
271 |
14 => 'NET_SFTP_MKDIR',
|
|
|
272 |
15 => 'NET_SFTP_RMDIR',
|
|
|
273 |
16 => 'NET_SFTP_REALPATH',
|
|
|
274 |
17 => 'NET_SFTP_STAT',
|
|
|
275 |
/* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
|
|
|
276 |
SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
|
|
277 |
pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
|
|
|
278 |
18 => 'NET_SFTP_RENAME',
|
|
|
279 |
19 => 'NET_SFTP_READLINK',
|
|
|
280 |
20 => 'NET_SFTP_SYMLINK',
|
|
|
281 |
|
|
|
282 |
101=> 'NET_SFTP_STATUS',
|
|
|
283 |
102=> 'NET_SFTP_HANDLE',
|
|
|
284 |
/* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
|
|
|
285 |
SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
|
|
|
286 |
pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
|
|
|
287 |
103=> 'NET_SFTP_DATA',
|
|
|
288 |
104=> 'NET_SFTP_NAME',
|
|
|
289 |
105=> 'NET_SFTP_ATTRS',
|
|
|
290 |
|
|
|
291 |
200=> 'NET_SFTP_EXTENDED'
|
|
|
292 |
);
|
|
|
293 |
$this->status_codes = array(
|
|
|
294 |
|
|
|
295 |
1 => 'NET_SFTP_STATUS_EOF',
|
|
|
296 |
2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
|
|
|
297 |
3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
|
|
|
298 |
4 => 'NET_SFTP_STATUS_FAILURE',
|
|
|
299 |
5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
|
|
|
300 |
6 => 'NET_SFTP_STATUS_NO_CONNECTION',
|
|
|
301 |
7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
|
|
|
302 |
8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
|
|
|
303 |
9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
|
|
|
304 |
10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
|
|
|
305 |
11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
|
|
|
306 |
12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
|
|
|
307 |
13 => 'NET_SFTP_STATUS_NO_MEDIA',
|
|
|
308 |
14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
|
|
|
309 |
15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
|
|
|
310 |
16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
|
|
|
311 |
17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
|
|
|
312 |
18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
|
|
|
313 |
19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
|
|
|
314 |
20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
|
|
|
315 |
21 => 'NET_SFTP_STATUS_LINK_LOOP',
|
|
|
316 |
22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
|
|
|
317 |
23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
|
|
|
318 |
24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
|
|
|
319 |
25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
|
|
|
320 |
26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
|
|
|
321 |
27 => 'NET_SFTP_STATUS_DELETE_PENDING',
|
|
|
322 |
28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
|
|
|
323 |
29 => 'NET_SFTP_STATUS_OWNER_INVALID',
|
|
|
324 |
30 => 'NET_SFTP_STATUS_GROUP_INVALID',
|
|
|
325 |
31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
|
|
|
326 |
);
|
|
|
327 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
|
|
|
328 |
// the order, in this case, matters quite a lot - see \phpseclib\Net\SFTP::_parseAttributes() to understand why
|
|
|
329 |
$this->attributes = array(
|
|
|
330 |
0x00000001 => 'NET_SFTP_ATTR_SIZE',
|
|
|
331 |
0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
|
|
|
332 |
0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
|
|
|
333 |
0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
|
|
|
334 |
// 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
|
|
|
335 |
// yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
|
|
|
336 |
// two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
|
|
|
337 |
// that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
|
|
|
338 |
-1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
|
|
|
339 |
);
|
|
|
340 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
|
|
|
341 |
// the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
|
|
|
342 |
// the array for that $this->open5_flags and similarly alter the constant names.
|
|
|
343 |
$this->open_flags = array(
|
|
|
344 |
0x00000001 => 'NET_SFTP_OPEN_READ',
|
|
|
345 |
0x00000002 => 'NET_SFTP_OPEN_WRITE',
|
|
|
346 |
0x00000004 => 'NET_SFTP_OPEN_APPEND',
|
|
|
347 |
0x00000008 => 'NET_SFTP_OPEN_CREATE',
|
|
|
348 |
0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
|
|
|
349 |
0x00000020 => 'NET_SFTP_OPEN_EXCL'
|
|
|
350 |
);
|
|
|
351 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
|
|
|
352 |
// see \phpseclib\Net\SFTP::_parseLongname() for an explanation
|
|
|
353 |
$this->file_types = array(
|
|
|
354 |
1 => 'NET_SFTP_TYPE_REGULAR',
|
|
|
355 |
2 => 'NET_SFTP_TYPE_DIRECTORY',
|
|
|
356 |
3 => 'NET_SFTP_TYPE_SYMLINK',
|
|
|
357 |
4 => 'NET_SFTP_TYPE_SPECIAL',
|
|
|
358 |
5 => 'NET_SFTP_TYPE_UNKNOWN',
|
|
|
359 |
// the followin types were first defined for use in SFTPv5+
|
|
|
360 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
|
|
|
361 |
6 => 'NET_SFTP_TYPE_SOCKET',
|
|
|
362 |
7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
|
|
|
363 |
8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
|
|
|
364 |
9 => 'NET_SFTP_TYPE_FIFO'
|
|
|
365 |
);
|
|
|
366 |
$this->_define_array(
|
|
|
367 |
$this->packet_types,
|
|
|
368 |
$this->status_codes,
|
|
|
369 |
$this->attributes,
|
|
|
370 |
$this->open_flags,
|
|
|
371 |
$this->file_types
|
|
|
372 |
);
|
|
|
373 |
|
|
|
374 |
if (!defined('NET_SFTP_QUEUE_SIZE')) {
|
|
|
375 |
define('NET_SFTP_QUEUE_SIZE', 50);
|
|
|
376 |
}
|
|
|
377 |
}
|
|
|
378 |
|
|
|
379 |
/**
|
|
|
380 |
* Login
|
|
|
381 |
*
|
|
|
382 |
* @param string $username
|
|
|
383 |
* @param string $password
|
|
|
384 |
* @return bool
|
|
|
385 |
* @access public
|
|
|
386 |
*/
|
|
|
387 |
function login($username)
|
|
|
388 |
{
|
|
|
389 |
$args = func_get_args();
|
|
|
390 |
if (!call_user_func_array(array(&$this, '_login'), $args)) {
|
|
|
391 |
return false;
|
|
|
392 |
}
|
|
|
393 |
|
|
|
394 |
$this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
|
|
|
395 |
|
|
|
396 |
$packet = pack(
|
|
|
397 |
'CNa*N3',
|
|
|
398 |
NET_SSH2_MSG_CHANNEL_OPEN,
|
|
|
399 |
strlen('session'),
|
|
|
400 |
'session',
|
|
|
401 |
self::CHANNEL,
|
|
|
402 |
$this->window_size,
|
|
|
403 |
0x4000
|
|
|
404 |
);
|
|
|
405 |
|
|
|
406 |
if (!$this->_send_binary_packet($packet)) {
|
|
|
407 |
return false;
|
|
|
408 |
}
|
|
|
409 |
|
|
|
410 |
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
|
|
|
411 |
|
|
|
412 |
$response = $this->_get_channel_packet(self::CHANNEL);
|
|
|
413 |
if ($response === false) {
|
|
|
414 |
return false;
|
|
|
415 |
}
|
|
|
416 |
|
|
|
417 |
$packet = pack(
|
|
|
418 |
'CNNa*CNa*',
|
|
|
419 |
NET_SSH2_MSG_CHANNEL_REQUEST,
|
|
|
420 |
$this->server_channels[self::CHANNEL],
|
|
|
421 |
strlen('subsystem'),
|
|
|
422 |
'subsystem',
|
|
|
423 |
1,
|
|
|
424 |
strlen('sftp'),
|
|
|
425 |
'sftp'
|
|
|
426 |
);
|
|
|
427 |
if (!$this->_send_binary_packet($packet)) {
|
|
|
428 |
return false;
|
|
|
429 |
}
|
|
|
430 |
|
|
|
431 |
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
|
|
|
432 |
|
|
|
433 |
$response = $this->_get_channel_packet(self::CHANNEL);
|
|
|
434 |
if ($response === false) {
|
|
|
435 |
// from PuTTY's psftp.exe
|
|
|
436 |
$command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
|
|
|
437 |
"test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
|
|
|
438 |
"exec sftp-server";
|
|
|
439 |
// we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
|
|
|
440 |
// is redundant
|
|
|
441 |
$packet = pack(
|
|
|
442 |
'CNNa*CNa*',
|
|
|
443 |
NET_SSH2_MSG_CHANNEL_REQUEST,
|
|
|
444 |
$this->server_channels[self::CHANNEL],
|
|
|
445 |
strlen('exec'),
|
|
|
446 |
'exec',
|
|
|
447 |
1,
|
|
|
448 |
strlen($command),
|
|
|
449 |
$command
|
|
|
450 |
);
|
|
|
451 |
if (!$this->_send_binary_packet($packet)) {
|
|
|
452 |
return false;
|
|
|
453 |
}
|
|
|
454 |
|
|
|
455 |
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
|
|
|
456 |
|
|
|
457 |
$response = $this->_get_channel_packet(self::CHANNEL);
|
|
|
458 |
if ($response === false) {
|
|
|
459 |
return false;
|
|
|
460 |
}
|
|
|
461 |
}
|
|
|
462 |
|
|
|
463 |
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
|
|
|
464 |
|
|
|
465 |
if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
|
|
|
466 |
return false;
|
|
|
467 |
}
|
|
|
468 |
|
|
|
469 |
$response = $this->_get_sftp_packet();
|
|
|
470 |
if ($this->packet_type != NET_SFTP_VERSION) {
|
|
|
471 |
user_error('Expected SSH_FXP_VERSION');
|
|
|
472 |
return false;
|
|
|
473 |
}
|
|
|
474 |
|
|
|
475 |
extract(unpack('Nversion', $this->_string_shift($response, 4)));
|
|
|
476 |
$this->version = $version;
|
|
|
477 |
while (!empty($response)) {
|
|
|
478 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
479 |
$key = $this->_string_shift($response, $length);
|
|
|
480 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
481 |
$value = $this->_string_shift($response, $length);
|
|
|
482 |
$this->extensions[$key] = $value;
|
|
|
483 |
}
|
|
|
484 |
|
|
|
485 |
/*
|
|
|
486 |
SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
|
|
|
487 |
however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
|
|
|
488 |
not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
|
|
|
489 |
one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
|
|
|
490 |
'newline@vandyke.com' would.
|
|
|
491 |
*/
|
|
|
492 |
/*
|
|
|
493 |
if (isset($this->extensions['newline@vandyke.com'])) {
|
|
|
494 |
$this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
|
|
|
495 |
unset($this->extensions['newline@vandyke.com']);
|
|
|
496 |
}
|
|
|
497 |
*/
|
|
|
498 |
|
|
|
499 |
$this->request_id = 1;
|
|
|
500 |
|
|
|
501 |
/*
|
|
|
502 |
A Note on SFTPv4/5/6 support:
|
|
|
503 |
<http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
|
|
|
504 |
|
|
|
505 |
"If the client wishes to interoperate with servers that support noncontiguous version
|
|
|
506 |
numbers it SHOULD send '3'"
|
|
|
507 |
|
|
|
508 |
Given that the server only sends its version number after the client has already done so, the above
|
|
|
509 |
seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
|
|
|
510 |
most popular.
|
|
|
511 |
|
|
|
512 |
<http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
|
|
|
513 |
|
|
|
514 |
"If the server did not send the "versions" extension, or the version-from-list was not included, the
|
|
|
515 |
server MAY send a status response describing the failure, but MUST then close the channel without
|
|
|
516 |
processing any further requests."
|
|
|
517 |
|
|
|
518 |
So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
|
|
|
519 |
a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
|
|
|
520 |
v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
|
|
|
521 |
in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib\Net\SFTP would do is close the
|
|
|
522 |
channel and reopen it with a new and updated SSH_FXP_INIT packet.
|
|
|
523 |
*/
|
|
|
524 |
switch ($this->version) {
|
|
|
525 |
case 2:
|
|
|
526 |
case 3:
|
|
|
527 |
break;
|
|
|
528 |
default:
|
|
|
529 |
return false;
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
$this->pwd = $this->_realpath('.');
|
|
|
533 |
|
|
|
534 |
$this->_update_stat_cache($this->pwd, array());
|
|
|
535 |
|
|
|
536 |
return true;
|
|
|
537 |
}
|
|
|
538 |
|
|
|
539 |
/**
|
|
|
540 |
* Disable the stat cache
|
|
|
541 |
*
|
|
|
542 |
* @access public
|
|
|
543 |
*/
|
|
|
544 |
function disableStatCache()
|
|
|
545 |
{
|
|
|
546 |
$this->use_stat_cache = false;
|
|
|
547 |
}
|
|
|
548 |
|
|
|
549 |
/**
|
|
|
550 |
* Enable the stat cache
|
|
|
551 |
*
|
|
|
552 |
* @access public
|
|
|
553 |
*/
|
|
|
554 |
function enableStatCache()
|
|
|
555 |
{
|
|
|
556 |
$this->use_stat_cache = true;
|
|
|
557 |
}
|
|
|
558 |
|
|
|
559 |
/**
|
|
|
560 |
* Clear the stat cache
|
|
|
561 |
*
|
|
|
562 |
* @access public
|
|
|
563 |
*/
|
|
|
564 |
function clearStatCache()
|
|
|
565 |
{
|
|
|
566 |
$this->stat_cache = array();
|
|
|
567 |
}
|
|
|
568 |
|
|
|
569 |
/**
|
|
|
570 |
* Returns the current directory name
|
|
|
571 |
*
|
|
|
572 |
* @return mixed
|
|
|
573 |
* @access public
|
|
|
574 |
*/
|
|
|
575 |
function pwd()
|
|
|
576 |
{
|
|
|
577 |
return $this->pwd;
|
|
|
578 |
}
|
|
|
579 |
|
|
|
580 |
/**
|
|
|
581 |
* Logs errors
|
|
|
582 |
*
|
|
|
583 |
* @param string $response
|
|
|
584 |
* @param int $status
|
|
|
585 |
* @access public
|
|
|
586 |
*/
|
|
|
587 |
function _logError($response, $status = -1)
|
|
|
588 |
{
|
|
|
589 |
if ($status == -1) {
|
|
|
590 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
591 |
}
|
|
|
592 |
|
|
|
593 |
$error = $this->status_codes[$status];
|
|
|
594 |
|
|
|
595 |
if ($this->version > 2) {
|
|
|
596 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
597 |
$this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
|
|
|
598 |
} else {
|
|
|
599 |
$this->sftp_errors[] = $error;
|
|
|
600 |
}
|
|
|
601 |
}
|
|
|
602 |
|
|
|
603 |
/**
|
|
|
604 |
* Returns canonicalized absolute pathname
|
|
|
605 |
*
|
|
|
606 |
* realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
|
|
|
607 |
* path and returns the canonicalized absolute pathname.
|
|
|
608 |
*
|
|
|
609 |
* @param string $path
|
|
|
610 |
* @return mixed
|
|
|
611 |
* @access public
|
|
|
612 |
*/
|
|
|
613 |
function realpath($path)
|
|
|
614 |
{
|
|
|
615 |
return $this->_realpath($path);
|
|
|
616 |
}
|
|
|
617 |
|
|
|
618 |
/**
|
|
|
619 |
* Canonicalize the Server-Side Path Name
|
|
|
620 |
*
|
|
|
621 |
* SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
|
|
|
622 |
* the absolute (canonicalized) path.
|
|
|
623 |
*
|
|
|
624 |
* @see self::chdir()
|
|
|
625 |
* @param string $path
|
|
|
626 |
* @return mixed
|
|
|
627 |
* @access private
|
|
|
628 |
*/
|
|
|
629 |
function _realpath($path)
|
|
|
630 |
{
|
|
|
631 |
if ($this->pwd === false) {
|
|
|
632 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
|
|
|
633 |
if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
|
|
|
634 |
return false;
|
|
|
635 |
}
|
|
|
636 |
|
|
|
637 |
$response = $this->_get_sftp_packet();
|
|
|
638 |
switch ($this->packet_type) {
|
|
|
639 |
case NET_SFTP_NAME:
|
|
|
640 |
// although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
|
|
|
641 |
// should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
|
|
|
642 |
// at is the first part and that part is defined the same in SFTP versions 3 through 6.
|
|
|
643 |
$this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
|
|
|
644 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
645 |
return $this->_string_shift($response, $length);
|
|
|
646 |
case NET_SFTP_STATUS:
|
|
|
647 |
$this->_logError($response);
|
|
|
648 |
return false;
|
|
|
649 |
default:
|
|
|
650 |
user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
|
|
|
651 |
return false;
|
|
|
652 |
}
|
|
|
653 |
}
|
|
|
654 |
|
|
|
655 |
if ($path[0] != '/') {
|
|
|
656 |
$path = $this->pwd . '/' . $path;
|
|
|
657 |
}
|
|
|
658 |
|
|
|
659 |
$path = explode('/', $path);
|
|
|
660 |
$new = array();
|
|
|
661 |
foreach ($path as $dir) {
|
|
|
662 |
if (!strlen($dir)) {
|
|
|
663 |
continue;
|
|
|
664 |
}
|
|
|
665 |
switch ($dir) {
|
|
|
666 |
case '..':
|
|
|
667 |
array_pop($new);
|
|
|
668 |
case '.':
|
|
|
669 |
break;
|
|
|
670 |
default:
|
|
|
671 |
$new[] = $dir;
|
|
|
672 |
}
|
|
|
673 |
}
|
|
|
674 |
|
|
|
675 |
return '/' . implode('/', $new);
|
|
|
676 |
}
|
|
|
677 |
|
|
|
678 |
/**
|
|
|
679 |
* Changes the current directory
|
|
|
680 |
*
|
|
|
681 |
* @param string $dir
|
|
|
682 |
* @return bool
|
|
|
683 |
* @access public
|
|
|
684 |
*/
|
|
|
685 |
function chdir($dir)
|
|
|
686 |
{
|
|
|
687 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
688 |
return false;
|
|
|
689 |
}
|
|
|
690 |
|
|
|
691 |
// assume current dir if $dir is empty
|
|
|
692 |
if ($dir === '') {
|
|
|
693 |
$dir = './';
|
|
|
694 |
// suffix a slash if needed
|
|
|
695 |
} elseif ($dir[strlen($dir) - 1] != '/') {
|
|
|
696 |
$dir.= '/';
|
|
|
697 |
}
|
|
|
698 |
|
|
|
699 |
$dir = $this->_realpath($dir);
|
|
|
700 |
|
|
|
701 |
// confirm that $dir is, in fact, a valid directory
|
|
|
702 |
if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
|
|
|
703 |
$this->pwd = $dir;
|
|
|
704 |
return true;
|
|
|
705 |
}
|
|
|
706 |
|
|
|
707 |
// we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
|
|
|
708 |
// the currently logged in user has the appropriate permissions or not. maybe you could see if
|
|
|
709 |
// the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
|
|
|
710 |
// way to get those with SFTP
|
|
|
711 |
|
|
|
712 |
if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
|
|
|
713 |
return false;
|
|
|
714 |
}
|
|
|
715 |
|
|
|
716 |
// see \phpseclib\Net\SFTP::nlist() for a more thorough explanation of the following
|
|
|
717 |
$response = $this->_get_sftp_packet();
|
|
|
718 |
switch ($this->packet_type) {
|
|
|
719 |
case NET_SFTP_HANDLE:
|
|
|
720 |
$handle = substr($response, 4);
|
|
|
721 |
break;
|
|
|
722 |
case NET_SFTP_STATUS:
|
|
|
723 |
$this->_logError($response);
|
|
|
724 |
return false;
|
|
|
725 |
default:
|
|
|
726 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
727 |
return false;
|
|
|
728 |
}
|
|
|
729 |
|
|
|
730 |
if (!$this->_close_handle($handle)) {
|
|
|
731 |
return false;
|
|
|
732 |
}
|
|
|
733 |
|
|
|
734 |
$this->_update_stat_cache($dir, array());
|
|
|
735 |
|
|
|
736 |
$this->pwd = $dir;
|
|
|
737 |
return true;
|
|
|
738 |
}
|
|
|
739 |
|
|
|
740 |
/**
|
|
|
741 |
* Returns a list of files in the given directory
|
|
|
742 |
*
|
|
|
743 |
* @param string $dir
|
|
|
744 |
* @param bool $recursive
|
|
|
745 |
* @return mixed
|
|
|
746 |
* @access public
|
|
|
747 |
*/
|
|
|
748 |
function nlist($dir = '.', $recursive = false)
|
|
|
749 |
{
|
|
|
750 |
return $this->_nlist_helper($dir, $recursive, '');
|
|
|
751 |
}
|
|
|
752 |
|
|
|
753 |
/**
|
|
|
754 |
* Helper method for nlist
|
|
|
755 |
*
|
|
|
756 |
* @param string $dir
|
|
|
757 |
* @param bool $recursive
|
|
|
758 |
* @param string $relativeDir
|
|
|
759 |
* @return mixed
|
|
|
760 |
* @access private
|
|
|
761 |
*/
|
|
|
762 |
function _nlist_helper($dir, $recursive, $relativeDir)
|
|
|
763 |
{
|
|
|
764 |
$files = $this->_list($dir, false);
|
|
|
765 |
|
|
|
766 |
if (!$recursive || $files === false) {
|
|
|
767 |
return $files;
|
|
|
768 |
}
|
|
|
769 |
|
|
|
770 |
$result = array();
|
|
|
771 |
foreach ($files as $value) {
|
|
|
772 |
if ($value == '.' || $value == '..') {
|
|
|
773 |
if ($relativeDir == '') {
|
|
|
774 |
$result[] = $value;
|
|
|
775 |
}
|
|
|
776 |
continue;
|
|
|
777 |
}
|
|
|
778 |
if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
|
|
|
779 |
$temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
|
|
|
780 |
$result = array_merge($result, $temp);
|
|
|
781 |
} else {
|
|
|
782 |
$result[] = $relativeDir . $value;
|
|
|
783 |
}
|
|
|
784 |
}
|
|
|
785 |
|
|
|
786 |
return $result;
|
|
|
787 |
}
|
|
|
788 |
|
|
|
789 |
/**
|
|
|
790 |
* Returns a detailed list of files in the given directory
|
|
|
791 |
*
|
|
|
792 |
* @param string $dir
|
|
|
793 |
* @param bool $recursive
|
|
|
794 |
* @return mixed
|
|
|
795 |
* @access public
|
|
|
796 |
*/
|
|
|
797 |
function rawlist($dir = '.', $recursive = false)
|
|
|
798 |
{
|
|
|
799 |
$files = $this->_list($dir, true);
|
|
|
800 |
if (!$recursive || $files === false) {
|
|
|
801 |
return $files;
|
|
|
802 |
}
|
|
|
803 |
|
|
|
804 |
static $depth = 0;
|
|
|
805 |
|
|
|
806 |
foreach ($files as $key => $value) {
|
|
|
807 |
if ($depth != 0 && $key == '..') {
|
|
|
808 |
unset($files[$key]);
|
|
|
809 |
continue;
|
|
|
810 |
}
|
|
|
811 |
if ($key != '.' && $key != '..' && is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)))) {
|
|
|
812 |
$depth++;
|
|
|
813 |
$files[$key] = $this->rawlist($dir . '/' . $key, true);
|
|
|
814 |
$depth--;
|
|
|
815 |
} else {
|
|
|
816 |
$files[$key] = (object) $value;
|
|
|
817 |
}
|
|
|
818 |
}
|
|
|
819 |
|
|
|
820 |
return $files;
|
|
|
821 |
}
|
|
|
822 |
|
|
|
823 |
/**
|
|
|
824 |
* Reads a list, be it detailed or not, of files in the given directory
|
|
|
825 |
*
|
|
|
826 |
* @param string $dir
|
|
|
827 |
* @param bool $raw
|
|
|
828 |
* @return mixed
|
|
|
829 |
* @access private
|
|
|
830 |
*/
|
|
|
831 |
function _list($dir, $raw = true)
|
|
|
832 |
{
|
|
|
833 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
834 |
return false;
|
|
|
835 |
}
|
|
|
836 |
|
|
|
837 |
$dir = $this->_realpath($dir . '/');
|
|
|
838 |
if ($dir === false) {
|
|
|
839 |
return false;
|
|
|
840 |
}
|
|
|
841 |
|
|
|
842 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
|
|
|
843 |
if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
|
|
|
844 |
return false;
|
|
|
845 |
}
|
|
|
846 |
|
|
|
847 |
$response = $this->_get_sftp_packet();
|
|
|
848 |
switch ($this->packet_type) {
|
|
|
849 |
case NET_SFTP_HANDLE:
|
|
|
850 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
|
|
|
851 |
// since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
|
|
|
852 |
// represent the length of the string and leave it at that
|
|
|
853 |
$handle = substr($response, 4);
|
|
|
854 |
break;
|
|
|
855 |
case NET_SFTP_STATUS:
|
|
|
856 |
// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
|
|
|
857 |
$this->_logError($response);
|
|
|
858 |
return false;
|
|
|
859 |
default:
|
|
|
860 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
861 |
return false;
|
|
|
862 |
}
|
|
|
863 |
|
|
|
864 |
$this->_update_stat_cache($dir, array());
|
|
|
865 |
|
|
|
866 |
$contents = array();
|
|
|
867 |
while (true) {
|
|
|
868 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
|
|
|
869 |
// why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
|
|
|
870 |
// SSH_MSG_CHANNEL_DATA messages is not known to me.
|
|
|
871 |
if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
|
|
|
872 |
return false;
|
|
|
873 |
}
|
|
|
874 |
|
|
|
875 |
$response = $this->_get_sftp_packet();
|
|
|
876 |
switch ($this->packet_type) {
|
|
|
877 |
case NET_SFTP_NAME:
|
|
|
878 |
extract(unpack('Ncount', $this->_string_shift($response, 4)));
|
|
|
879 |
for ($i = 0; $i < $count; $i++) {
|
|
|
880 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
881 |
$shortname = $this->_string_shift($response, $length);
|
|
|
882 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
883 |
$longname = $this->_string_shift($response, $length);
|
|
|
884 |
$attributes = $this->_parseAttributes($response);
|
|
|
885 |
if (!isset($attributes['type'])) {
|
|
|
886 |
$fileType = $this->_parseLongname($longname);
|
|
|
887 |
if ($fileType) {
|
|
|
888 |
$attributes['type'] = $fileType;
|
|
|
889 |
}
|
|
|
890 |
}
|
|
|
891 |
$contents[$shortname] = $attributes + array('filename' => $shortname);
|
|
|
892 |
|
|
|
893 |
if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
|
|
|
894 |
$this->_update_stat_cache($dir . '/' . $shortname, array());
|
|
|
895 |
} else {
|
|
|
896 |
if ($shortname == '..') {
|
|
|
897 |
$temp = $this->_realpath($dir . '/..') . '/.';
|
|
|
898 |
} else {
|
|
|
899 |
$temp = $dir . '/' . $shortname;
|
|
|
900 |
}
|
|
|
901 |
$this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
|
|
|
902 |
}
|
|
|
903 |
// SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
|
|
|
904 |
// final SSH_FXP_STATUS packet should tell us that, already.
|
|
|
905 |
}
|
|
|
906 |
break;
|
|
|
907 |
case NET_SFTP_STATUS:
|
|
|
908 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
909 |
if ($status != NET_SFTP_STATUS_EOF) {
|
|
|
910 |
$this->_logError($response, $status);
|
|
|
911 |
return false;
|
|
|
912 |
}
|
|
|
913 |
break 2;
|
|
|
914 |
default:
|
|
|
915 |
user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
|
|
|
916 |
return false;
|
|
|
917 |
}
|
|
|
918 |
}
|
|
|
919 |
|
|
|
920 |
if (!$this->_close_handle($handle)) {
|
|
|
921 |
return false;
|
|
|
922 |
}
|
|
|
923 |
|
|
|
924 |
if (count($this->sortOptions)) {
|
|
|
925 |
uasort($contents, array(&$this, '_comparator'));
|
|
|
926 |
}
|
|
|
927 |
|
|
|
928 |
return $raw ? $contents : array_keys($contents);
|
|
|
929 |
}
|
|
|
930 |
|
|
|
931 |
/**
|
|
|
932 |
* Compares two rawlist entries using parameters set by setListOrder()
|
|
|
933 |
*
|
|
|
934 |
* Intended for use with uasort()
|
|
|
935 |
*
|
|
|
936 |
* @param array $a
|
|
|
937 |
* @param array $b
|
|
|
938 |
* @return int
|
|
|
939 |
* @access private
|
|
|
940 |
*/
|
|
|
941 |
function _comparator($a, $b)
|
|
|
942 |
{
|
|
|
943 |
switch (true) {
|
|
|
944 |
case $a['filename'] === '.' || $b['filename'] === '.':
|
|
|
945 |
if ($a['filename'] === $b['filename']) {
|
|
|
946 |
return 0;
|
|
|
947 |
}
|
|
|
948 |
return $a['filename'] === '.' ? -1 : 1;
|
|
|
949 |
case $a['filename'] === '..' || $b['filename'] === '..':
|
|
|
950 |
if ($a['filename'] === $b['filename']) {
|
|
|
951 |
return 0;
|
|
|
952 |
}
|
|
|
953 |
return $a['filename'] === '..' ? -1 : 1;
|
|
|
954 |
case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
|
|
|
955 |
if (!isset($b['type'])) {
|
|
|
956 |
return 1;
|
|
|
957 |
}
|
|
|
958 |
if ($b['type'] !== $a['type']) {
|
|
|
959 |
return -1;
|
|
|
960 |
}
|
|
|
961 |
break;
|
|
|
962 |
case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
|
|
|
963 |
return 1;
|
|
|
964 |
}
|
|
|
965 |
foreach ($this->sortOptions as $sort => $order) {
|
|
|
966 |
if (!isset($a[$sort]) || !isset($b[$sort])) {
|
|
|
967 |
if (isset($a[$sort])) {
|
|
|
968 |
return -1;
|
|
|
969 |
}
|
|
|
970 |
if (isset($b[$sort])) {
|
|
|
971 |
return 1;
|
|
|
972 |
}
|
|
|
973 |
return 0;
|
|
|
974 |
}
|
|
|
975 |
switch ($sort) {
|
|
|
976 |
case 'filename':
|
|
|
977 |
$result = strcasecmp($a['filename'], $b['filename']);
|
|
|
978 |
if ($result) {
|
|
|
979 |
return $order === SORT_DESC ? -$result : $result;
|
|
|
980 |
}
|
|
|
981 |
break;
|
|
|
982 |
case 'permissions':
|
|
|
983 |
case 'mode':
|
|
|
984 |
$a[$sort]&= 07777;
|
|
|
985 |
$b[$sort]&= 07777;
|
|
|
986 |
default:
|
|
|
987 |
if ($a[$sort] === $b[$sort]) {
|
|
|
988 |
break;
|
|
|
989 |
}
|
|
|
990 |
return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
|
|
|
991 |
}
|
|
|
992 |
}
|
|
|
993 |
}
|
|
|
994 |
|
|
|
995 |
/**
|
|
|
996 |
* Defines how nlist() and rawlist() will be sorted - if at all.
|
|
|
997 |
*
|
|
|
998 |
* If sorting is enabled directories and files will be sorted independently with
|
|
|
999 |
* directories appearing before files in the resultant array that is returned.
|
|
|
1000 |
*
|
|
|
1001 |
* Any parameter returned by stat is a valid sort parameter for this function.
|
|
|
1002 |
* Filename comparisons are case insensitive.
|
|
|
1003 |
*
|
|
|
1004 |
* Examples:
|
|
|
1005 |
*
|
|
|
1006 |
* $sftp->setListOrder('filename', SORT_ASC);
|
|
|
1007 |
* $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
|
|
|
1008 |
* $sftp->setListOrder(true);
|
|
|
1009 |
* Separates directories from files but doesn't do any sorting beyond that
|
|
|
1010 |
* $sftp->setListOrder();
|
|
|
1011 |
* Don't do any sort of sorting
|
|
|
1012 |
*
|
|
|
1013 |
* @access public
|
|
|
1014 |
*/
|
|
|
1015 |
function setListOrder()
|
|
|
1016 |
{
|
|
|
1017 |
$this->sortOptions = array();
|
|
|
1018 |
$args = func_get_args();
|
|
|
1019 |
if (empty($args)) {
|
|
|
1020 |
return;
|
|
|
1021 |
}
|
|
|
1022 |
$len = count($args) & 0x7FFFFFFE;
|
|
|
1023 |
for ($i = 0; $i < $len; $i+=2) {
|
|
|
1024 |
$this->sortOptions[$args[$i]] = $args[$i + 1];
|
|
|
1025 |
}
|
|
|
1026 |
if (!count($this->sortOptions)) {
|
|
|
1027 |
$this->sortOptions = array('bogus' => true);
|
|
|
1028 |
}
|
|
|
1029 |
}
|
|
|
1030 |
|
|
|
1031 |
/**
|
|
|
1032 |
* Returns the file size, in bytes, or false, on failure
|
|
|
1033 |
*
|
|
|
1034 |
* Files larger than 4GB will show up as being exactly 4GB.
|
|
|
1035 |
*
|
|
|
1036 |
* @param string $filename
|
|
|
1037 |
* @return mixed
|
|
|
1038 |
* @access public
|
|
|
1039 |
*/
|
|
|
1040 |
function size($filename)
|
|
|
1041 |
{
|
|
|
1042 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1043 |
return false;
|
|
|
1044 |
}
|
|
|
1045 |
|
|
|
1046 |
$result = $this->stat($filename);
|
|
|
1047 |
if ($result === false) {
|
|
|
1048 |
return false;
|
|
|
1049 |
}
|
|
|
1050 |
return isset($result['size']) ? $result['size'] : -1;
|
|
|
1051 |
}
|
|
|
1052 |
|
|
|
1053 |
/**
|
|
|
1054 |
* Save files / directories to cache
|
|
|
1055 |
*
|
|
|
1056 |
* @param string $path
|
|
|
1057 |
* @param mixed $value
|
|
|
1058 |
* @access private
|
|
|
1059 |
*/
|
|
|
1060 |
function _update_stat_cache($path, $value)
|
|
|
1061 |
{
|
|
|
1062 |
if ($this->use_stat_cache === false) {
|
|
|
1063 |
return;
|
|
|
1064 |
}
|
|
|
1065 |
|
|
|
1066 |
// preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
|
|
|
1067 |
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
|
|
|
1068 |
|
|
|
1069 |
$temp = &$this->stat_cache;
|
|
|
1070 |
$max = count($dirs) - 1;
|
|
|
1071 |
foreach ($dirs as $i => $dir) {
|
|
|
1072 |
// if $temp is an object that means one of two things.
|
|
|
1073 |
// 1. a file was deleted and changed to a directory behind phpseclib's back
|
|
|
1074 |
// 2. it's a symlink. when lstat is done it's unclear what it's a symlink to
|
|
|
1075 |
if (is_object($temp)) {
|
|
|
1076 |
$temp = array();
|
|
|
1077 |
}
|
|
|
1078 |
if (!isset($temp[$dir])) {
|
|
|
1079 |
$temp[$dir] = array();
|
|
|
1080 |
}
|
|
|
1081 |
if ($i === $max) {
|
|
|
1082 |
if (is_object($temp[$dir])) {
|
|
|
1083 |
if (!isset($value->stat) && isset($temp[$dir]->stat)) {
|
|
|
1084 |
$value->stat = $temp[$dir]->stat;
|
|
|
1085 |
}
|
|
|
1086 |
if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
|
|
|
1087 |
$value->lstat = $temp[$dir]->lstat;
|
|
|
1088 |
}
|
|
|
1089 |
}
|
|
|
1090 |
$temp[$dir] = $value;
|
|
|
1091 |
break;
|
|
|
1092 |
}
|
|
|
1093 |
$temp = &$temp[$dir];
|
|
|
1094 |
}
|
|
|
1095 |
}
|
|
|
1096 |
|
|
|
1097 |
/**
|
|
|
1098 |
* Remove files / directories from cache
|
|
|
1099 |
*
|
|
|
1100 |
* @param string $path
|
|
|
1101 |
* @return bool
|
|
|
1102 |
* @access private
|
|
|
1103 |
*/
|
|
|
1104 |
function _remove_from_stat_cache($path)
|
|
|
1105 |
{
|
|
|
1106 |
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
|
|
|
1107 |
|
|
|
1108 |
$temp = &$this->stat_cache;
|
|
|
1109 |
$max = count($dirs) - 1;
|
|
|
1110 |
foreach ($dirs as $i => $dir) {
|
|
|
1111 |
if ($i === $max) {
|
|
|
1112 |
unset($temp[$dir]);
|
|
|
1113 |
return true;
|
|
|
1114 |
}
|
|
|
1115 |
if (!isset($temp[$dir])) {
|
|
|
1116 |
return false;
|
|
|
1117 |
}
|
|
|
1118 |
$temp = &$temp[$dir];
|
|
|
1119 |
}
|
|
|
1120 |
}
|
|
|
1121 |
|
|
|
1122 |
/**
|
|
|
1123 |
* Checks cache for path
|
|
|
1124 |
*
|
|
|
1125 |
* Mainly used by file_exists
|
|
|
1126 |
*
|
|
|
1127 |
* @param string $dir
|
|
|
1128 |
* @return mixed
|
|
|
1129 |
* @access private
|
|
|
1130 |
*/
|
|
|
1131 |
function _query_stat_cache($path)
|
|
|
1132 |
{
|
|
|
1133 |
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
|
|
|
1134 |
|
|
|
1135 |
$temp = &$this->stat_cache;
|
|
|
1136 |
foreach ($dirs as $dir) {
|
|
|
1137 |
if (!isset($temp[$dir])) {
|
|
|
1138 |
return null;
|
|
|
1139 |
}
|
|
|
1140 |
$temp = &$temp[$dir];
|
|
|
1141 |
}
|
|
|
1142 |
return $temp;
|
|
|
1143 |
}
|
|
|
1144 |
|
|
|
1145 |
/**
|
|
|
1146 |
* Returns general information about a file.
|
|
|
1147 |
*
|
|
|
1148 |
* Returns an array on success and false otherwise.
|
|
|
1149 |
*
|
|
|
1150 |
* @param string $filename
|
|
|
1151 |
* @return mixed
|
|
|
1152 |
* @access public
|
|
|
1153 |
*/
|
|
|
1154 |
function stat($filename)
|
|
|
1155 |
{
|
|
|
1156 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1157 |
return false;
|
|
|
1158 |
}
|
|
|
1159 |
|
|
|
1160 |
$filename = $this->_realpath($filename);
|
|
|
1161 |
if ($filename === false) {
|
|
|
1162 |
return false;
|
|
|
1163 |
}
|
|
|
1164 |
|
|
|
1165 |
if ($this->use_stat_cache) {
|
|
|
1166 |
$result = $this->_query_stat_cache($filename);
|
|
|
1167 |
if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
|
|
|
1168 |
return $result['.']->stat;
|
|
|
1169 |
}
|
|
|
1170 |
if (is_object($result) && isset($result->stat)) {
|
|
|
1171 |
return $result->stat;
|
|
|
1172 |
}
|
|
|
1173 |
}
|
|
|
1174 |
|
|
|
1175 |
$stat = $this->_stat($filename, NET_SFTP_STAT);
|
|
|
1176 |
if ($stat === false) {
|
|
|
1177 |
$this->_remove_from_stat_cache($filename);
|
|
|
1178 |
return false;
|
|
|
1179 |
}
|
|
|
1180 |
if (isset($stat['type'])) {
|
|
|
1181 |
if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
|
|
|
1182 |
$filename.= '/.';
|
|
|
1183 |
}
|
|
|
1184 |
$this->_update_stat_cache($filename, (object) array('stat' => $stat));
|
|
|
1185 |
return $stat;
|
|
|
1186 |
}
|
|
|
1187 |
|
|
|
1188 |
$pwd = $this->pwd;
|
|
|
1189 |
$stat['type'] = $this->chdir($filename) ?
|
|
|
1190 |
NET_SFTP_TYPE_DIRECTORY :
|
|
|
1191 |
NET_SFTP_TYPE_REGULAR;
|
|
|
1192 |
$this->pwd = $pwd;
|
|
|
1193 |
|
|
|
1194 |
if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
|
|
|
1195 |
$filename.= '/.';
|
|
|
1196 |
}
|
|
|
1197 |
$this->_update_stat_cache($filename, (object) array('stat' => $stat));
|
|
|
1198 |
|
|
|
1199 |
return $stat;
|
|
|
1200 |
}
|
|
|
1201 |
|
|
|
1202 |
/**
|
|
|
1203 |
* Returns general information about a file or symbolic link.
|
|
|
1204 |
*
|
|
|
1205 |
* Returns an array on success and false otherwise.
|
|
|
1206 |
*
|
|
|
1207 |
* @param string $filename
|
|
|
1208 |
* @return mixed
|
|
|
1209 |
* @access public
|
|
|
1210 |
*/
|
|
|
1211 |
function lstat($filename)
|
|
|
1212 |
{
|
|
|
1213 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1214 |
return false;
|
|
|
1215 |
}
|
|
|
1216 |
|
|
|
1217 |
$filename = $this->_realpath($filename);
|
|
|
1218 |
if ($filename === false) {
|
|
|
1219 |
return false;
|
|
|
1220 |
}
|
|
|
1221 |
|
|
|
1222 |
if ($this->use_stat_cache) {
|
|
|
1223 |
$result = $this->_query_stat_cache($filename);
|
|
|
1224 |
if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
|
|
|
1225 |
return $result['.']->lstat;
|
|
|
1226 |
}
|
|
|
1227 |
if (is_object($result) && isset($result->lstat)) {
|
|
|
1228 |
return $result->lstat;
|
|
|
1229 |
}
|
|
|
1230 |
}
|
|
|
1231 |
|
|
|
1232 |
$lstat = $this->_stat($filename, NET_SFTP_LSTAT);
|
|
|
1233 |
if ($lstat === false) {
|
|
|
1234 |
$this->_remove_from_stat_cache($filename);
|
|
|
1235 |
return false;
|
|
|
1236 |
}
|
|
|
1237 |
if (isset($lstat['type'])) {
|
|
|
1238 |
if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
|
|
|
1239 |
$filename.= '/.';
|
|
|
1240 |
}
|
|
|
1241 |
$this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
|
|
|
1242 |
return $lstat;
|
|
|
1243 |
}
|
|
|
1244 |
|
|
|
1245 |
$stat = $this->_stat($filename, NET_SFTP_STAT);
|
|
|
1246 |
|
|
|
1247 |
if ($lstat != $stat) {
|
|
|
1248 |
$lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
|
|
|
1249 |
$this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
|
|
|
1250 |
return $stat;
|
|
|
1251 |
}
|
|
|
1252 |
|
|
|
1253 |
$pwd = $this->pwd;
|
|
|
1254 |
$lstat['type'] = $this->chdir($filename) ?
|
|
|
1255 |
NET_SFTP_TYPE_DIRECTORY :
|
|
|
1256 |
NET_SFTP_TYPE_REGULAR;
|
|
|
1257 |
$this->pwd = $pwd;
|
|
|
1258 |
|
|
|
1259 |
if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
|
|
|
1260 |
$filename.= '/.';
|
|
|
1261 |
}
|
|
|
1262 |
$this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
|
|
|
1263 |
|
|
|
1264 |
return $lstat;
|
|
|
1265 |
}
|
|
|
1266 |
|
|
|
1267 |
/**
|
|
|
1268 |
* Returns general information about a file or symbolic link
|
|
|
1269 |
*
|
|
|
1270 |
* Determines information without calling \phpseclib\Net\SFTP::_realpath().
|
|
|
1271 |
* The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
|
|
|
1272 |
*
|
|
|
1273 |
* @param string $filename
|
|
|
1274 |
* @param int $type
|
|
|
1275 |
* @return mixed
|
|
|
1276 |
* @access private
|
|
|
1277 |
*/
|
|
|
1278 |
function _stat($filename, $type)
|
|
|
1279 |
{
|
|
|
1280 |
// SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
|
|
|
1281 |
$packet = pack('Na*', strlen($filename), $filename);
|
|
|
1282 |
if (!$this->_send_sftp_packet($type, $packet)) {
|
|
|
1283 |
return false;
|
|
|
1284 |
}
|
|
|
1285 |
|
|
|
1286 |
$response = $this->_get_sftp_packet();
|
|
|
1287 |
switch ($this->packet_type) {
|
|
|
1288 |
case NET_SFTP_ATTRS:
|
|
|
1289 |
return $this->_parseAttributes($response);
|
|
|
1290 |
case NET_SFTP_STATUS:
|
|
|
1291 |
$this->_logError($response);
|
|
|
1292 |
return false;
|
|
|
1293 |
}
|
|
|
1294 |
|
|
|
1295 |
user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
|
|
|
1296 |
return false;
|
|
|
1297 |
}
|
|
|
1298 |
|
|
|
1299 |
/**
|
|
|
1300 |
* Truncates a file to a given length
|
|
|
1301 |
*
|
|
|
1302 |
* @param string $filename
|
|
|
1303 |
* @param int $new_size
|
|
|
1304 |
* @return bool
|
|
|
1305 |
* @access public
|
|
|
1306 |
*/
|
|
|
1307 |
function truncate($filename, $new_size)
|
|
|
1308 |
{
|
|
|
1309 |
$attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
|
|
|
1310 |
|
|
|
1311 |
return $this->_setstat($filename, $attr, false);
|
|
|
1312 |
}
|
|
|
1313 |
|
|
|
1314 |
/**
|
|
|
1315 |
* Sets access and modification time of file.
|
|
|
1316 |
*
|
|
|
1317 |
* If the file does not exist, it will be created.
|
|
|
1318 |
*
|
|
|
1319 |
* @param string $filename
|
|
|
1320 |
* @param int $time
|
|
|
1321 |
* @param int $atime
|
|
|
1322 |
* @return bool
|
|
|
1323 |
* @access public
|
|
|
1324 |
*/
|
|
|
1325 |
function touch($filename, $time = null, $atime = null)
|
|
|
1326 |
{
|
|
|
1327 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1328 |
return false;
|
|
|
1329 |
}
|
|
|
1330 |
|
|
|
1331 |
$filename = $this->_realpath($filename);
|
|
|
1332 |
if ($filename === false) {
|
|
|
1333 |
return false;
|
|
|
1334 |
}
|
|
|
1335 |
|
|
|
1336 |
if (!isset($time)) {
|
|
|
1337 |
$time = time();
|
|
|
1338 |
}
|
|
|
1339 |
if (!isset($atime)) {
|
|
|
1340 |
$atime = $time;
|
|
|
1341 |
}
|
|
|
1342 |
|
|
|
1343 |
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
|
|
|
1344 |
$attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
|
|
|
1345 |
$packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
|
|
|
1346 |
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
|
|
|
1347 |
return false;
|
|
|
1348 |
}
|
|
|
1349 |
|
|
|
1350 |
$response = $this->_get_sftp_packet();
|
|
|
1351 |
switch ($this->packet_type) {
|
|
|
1352 |
case NET_SFTP_HANDLE:
|
|
|
1353 |
return $this->_close_handle(substr($response, 4));
|
|
|
1354 |
case NET_SFTP_STATUS:
|
|
|
1355 |
$this->_logError($response);
|
|
|
1356 |
break;
|
|
|
1357 |
default:
|
|
|
1358 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
1359 |
return false;
|
|
|
1360 |
}
|
|
|
1361 |
|
|
|
1362 |
return $this->_setstat($filename, $attr, false);
|
|
|
1363 |
}
|
|
|
1364 |
|
|
|
1365 |
/**
|
|
|
1366 |
* Changes file or directory owner
|
|
|
1367 |
*
|
|
|
1368 |
* Returns true on success or false on error.
|
|
|
1369 |
*
|
|
|
1370 |
* @param string $filename
|
|
|
1371 |
* @param int $uid
|
|
|
1372 |
* @param bool $recursive
|
|
|
1373 |
* @return bool
|
|
|
1374 |
* @access public
|
|
|
1375 |
*/
|
|
|
1376 |
function chown($filename, $uid, $recursive = false)
|
|
|
1377 |
{
|
|
|
1378 |
// quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
|
|
|
1379 |
// "if the owner or group is specified as -1, then that ID is not changed"
|
|
|
1380 |
$attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
|
|
|
1381 |
|
|
|
1382 |
return $this->_setstat($filename, $attr, $recursive);
|
|
|
1383 |
}
|
|
|
1384 |
|
|
|
1385 |
/**
|
|
|
1386 |
* Changes file or directory group
|
|
|
1387 |
*
|
|
|
1388 |
* Returns true on success or false on error.
|
|
|
1389 |
*
|
|
|
1390 |
* @param string $filename
|
|
|
1391 |
* @param int $gid
|
|
|
1392 |
* @param bool $recursive
|
|
|
1393 |
* @return bool
|
|
|
1394 |
* @access public
|
|
|
1395 |
*/
|
|
|
1396 |
function chgrp($filename, $gid, $recursive = false)
|
|
|
1397 |
{
|
|
|
1398 |
$attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
|
|
|
1399 |
|
|
|
1400 |
return $this->_setstat($filename, $attr, $recursive);
|
|
|
1401 |
}
|
|
|
1402 |
|
|
|
1403 |
/**
|
|
|
1404 |
* Set permissions on a file.
|
|
|
1405 |
*
|
|
|
1406 |
* Returns the new file permissions on success or false on error.
|
|
|
1407 |
* If $recursive is true than this just returns true or false.
|
|
|
1408 |
*
|
|
|
1409 |
* @param int $mode
|
|
|
1410 |
* @param string $filename
|
|
|
1411 |
* @param bool $recursive
|
|
|
1412 |
* @return mixed
|
|
|
1413 |
* @access public
|
|
|
1414 |
*/
|
|
|
1415 |
function chmod($mode, $filename, $recursive = false)
|
|
|
1416 |
{
|
|
|
1417 |
if (is_string($mode) && is_int($filename)) {
|
|
|
1418 |
$temp = $mode;
|
|
|
1419 |
$mode = $filename;
|
|
|
1420 |
$filename = $temp;
|
|
|
1421 |
}
|
|
|
1422 |
|
|
|
1423 |
$attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
|
|
|
1424 |
if (!$this->_setstat($filename, $attr, $recursive)) {
|
|
|
1425 |
return false;
|
|
|
1426 |
}
|
|
|
1427 |
if ($recursive) {
|
|
|
1428 |
return true;
|
|
|
1429 |
}
|
|
|
1430 |
|
|
|
1431 |
$filename = $this->_realPath($filename);
|
|
|
1432 |
// rather than return what the permissions *should* be, we'll return what they actually are. this will also
|
|
|
1433 |
// tell us if the file actually exists.
|
|
|
1434 |
// incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
|
|
|
1435 |
$packet = pack('Na*', strlen($filename), $filename);
|
|
|
1436 |
if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
|
|
|
1437 |
return false;
|
|
|
1438 |
}
|
|
|
1439 |
|
|
|
1440 |
$response = $this->_get_sftp_packet();
|
|
|
1441 |
switch ($this->packet_type) {
|
|
|
1442 |
case NET_SFTP_ATTRS:
|
|
|
1443 |
$attrs = $this->_parseAttributes($response);
|
|
|
1444 |
return $attrs['permissions'];
|
|
|
1445 |
case NET_SFTP_STATUS:
|
|
|
1446 |
$this->_logError($response);
|
|
|
1447 |
return false;
|
|
|
1448 |
}
|
|
|
1449 |
|
|
|
1450 |
user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
|
|
|
1451 |
return false;
|
|
|
1452 |
}
|
|
|
1453 |
|
|
|
1454 |
/**
|
|
|
1455 |
* Sets information about a file
|
|
|
1456 |
*
|
|
|
1457 |
* @param string $filename
|
|
|
1458 |
* @param string $attr
|
|
|
1459 |
* @param bool $recursive
|
|
|
1460 |
* @return bool
|
|
|
1461 |
* @access private
|
|
|
1462 |
*/
|
|
|
1463 |
function _setstat($filename, $attr, $recursive)
|
|
|
1464 |
{
|
|
|
1465 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1466 |
return false;
|
|
|
1467 |
}
|
|
|
1468 |
|
|
|
1469 |
$filename = $this->_realpath($filename);
|
|
|
1470 |
if ($filename === false) {
|
|
|
1471 |
return false;
|
|
|
1472 |
}
|
|
|
1473 |
|
|
|
1474 |
$this->_remove_from_stat_cache($filename);
|
|
|
1475 |
|
|
|
1476 |
if ($recursive) {
|
|
|
1477 |
$i = 0;
|
|
|
1478 |
$result = $this->_setstat_recursive($filename, $attr, $i);
|
|
|
1479 |
$this->_read_put_responses($i);
|
|
|
1480 |
return $result;
|
|
|
1481 |
}
|
|
|
1482 |
|
|
|
1483 |
// SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
|
|
|
1484 |
// SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
|
|
|
1485 |
if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
|
|
|
1486 |
return false;
|
|
|
1487 |
}
|
|
|
1488 |
|
|
|
1489 |
/*
|
|
|
1490 |
"Because some systems must use separate system calls to set various attributes, it is possible that a failure
|
|
|
1491 |
response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
|
|
|
1492 |
servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
|
|
|
1493 |
|
|
|
1494 |
-- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
|
|
|
1495 |
*/
|
|
|
1496 |
$response = $this->_get_sftp_packet();
|
|
|
1497 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
1498 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
1499 |
return false;
|
|
|
1500 |
}
|
|
|
1501 |
|
|
|
1502 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
1503 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
1504 |
$this->_logError($response, $status);
|
|
|
1505 |
return false;
|
|
|
1506 |
}
|
|
|
1507 |
|
|
|
1508 |
return true;
|
|
|
1509 |
}
|
|
|
1510 |
|
|
|
1511 |
/**
|
|
|
1512 |
* Recursively sets information on directories on the SFTP server
|
|
|
1513 |
*
|
|
|
1514 |
* Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
|
|
|
1515 |
*
|
|
|
1516 |
* @param string $path
|
|
|
1517 |
* @param string $attr
|
|
|
1518 |
* @param int $i
|
|
|
1519 |
* @return bool
|
|
|
1520 |
* @access private
|
|
|
1521 |
*/
|
|
|
1522 |
function _setstat_recursive($path, $attr, &$i)
|
|
|
1523 |
{
|
|
|
1524 |
if (!$this->_read_put_responses($i)) {
|
|
|
1525 |
return false;
|
|
|
1526 |
}
|
|
|
1527 |
$i = 0;
|
|
|
1528 |
$entries = $this->_list($path, true);
|
|
|
1529 |
|
|
|
1530 |
if ($entries === false) {
|
|
|
1531 |
return $this->_setstat($path, $attr, false);
|
|
|
1532 |
}
|
|
|
1533 |
|
|
|
1534 |
// normally $entries would have at least . and .. but it might not if the directories
|
|
|
1535 |
// permissions didn't allow reading
|
|
|
1536 |
if (empty($entries)) {
|
|
|
1537 |
return false;
|
|
|
1538 |
}
|
|
|
1539 |
|
|
|
1540 |
unset($entries['.'], $entries['..']);
|
|
|
1541 |
foreach ($entries as $filename => $props) {
|
|
|
1542 |
if (!isset($props['type'])) {
|
|
|
1543 |
return false;
|
|
|
1544 |
}
|
|
|
1545 |
|
|
|
1546 |
$temp = $path . '/' . $filename;
|
|
|
1547 |
if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
|
|
|
1548 |
if (!$this->_setstat_recursive($temp, $attr, $i)) {
|
|
|
1549 |
return false;
|
|
|
1550 |
}
|
|
|
1551 |
} else {
|
|
|
1552 |
if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
|
|
|
1553 |
return false;
|
|
|
1554 |
}
|
|
|
1555 |
|
|
|
1556 |
$i++;
|
|
|
1557 |
|
|
|
1558 |
if ($i >= NET_SFTP_QUEUE_SIZE) {
|
|
|
1559 |
if (!$this->_read_put_responses($i)) {
|
|
|
1560 |
return false;
|
|
|
1561 |
}
|
|
|
1562 |
$i = 0;
|
|
|
1563 |
}
|
|
|
1564 |
}
|
|
|
1565 |
}
|
|
|
1566 |
|
|
|
1567 |
if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
|
|
|
1568 |
return false;
|
|
|
1569 |
}
|
|
|
1570 |
|
|
|
1571 |
$i++;
|
|
|
1572 |
|
|
|
1573 |
if ($i >= NET_SFTP_QUEUE_SIZE) {
|
|
|
1574 |
if (!$this->_read_put_responses($i)) {
|
|
|
1575 |
return false;
|
|
|
1576 |
}
|
|
|
1577 |
$i = 0;
|
|
|
1578 |
}
|
|
|
1579 |
|
|
|
1580 |
return true;
|
|
|
1581 |
}
|
|
|
1582 |
|
|
|
1583 |
/**
|
|
|
1584 |
* Return the target of a symbolic link
|
|
|
1585 |
*
|
|
|
1586 |
* @param string $link
|
|
|
1587 |
* @return mixed
|
|
|
1588 |
* @access public
|
|
|
1589 |
*/
|
|
|
1590 |
function readlink($link)
|
|
|
1591 |
{
|
|
|
1592 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1593 |
return false;
|
|
|
1594 |
}
|
|
|
1595 |
|
|
|
1596 |
$link = $this->_realpath($link);
|
|
|
1597 |
|
|
|
1598 |
if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
|
|
|
1599 |
return false;
|
|
|
1600 |
}
|
|
|
1601 |
|
|
|
1602 |
$response = $this->_get_sftp_packet();
|
|
|
1603 |
switch ($this->packet_type) {
|
|
|
1604 |
case NET_SFTP_NAME:
|
|
|
1605 |
break;
|
|
|
1606 |
case NET_SFTP_STATUS:
|
|
|
1607 |
$this->_logError($response);
|
|
|
1608 |
return false;
|
|
|
1609 |
default:
|
|
|
1610 |
user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
|
|
|
1611 |
return false;
|
|
|
1612 |
}
|
|
|
1613 |
|
|
|
1614 |
extract(unpack('Ncount', $this->_string_shift($response, 4)));
|
|
|
1615 |
// the file isn't a symlink
|
|
|
1616 |
if (!$count) {
|
|
|
1617 |
return false;
|
|
|
1618 |
}
|
|
|
1619 |
|
|
|
1620 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
1621 |
return $this->_string_shift($response, $length);
|
|
|
1622 |
}
|
|
|
1623 |
|
|
|
1624 |
/**
|
|
|
1625 |
* Create a symlink
|
|
|
1626 |
*
|
|
|
1627 |
* symlink() creates a symbolic link to the existing target with the specified name link.
|
|
|
1628 |
*
|
|
|
1629 |
* @param string $target
|
|
|
1630 |
* @param string $link
|
|
|
1631 |
* @return bool
|
|
|
1632 |
* @access public
|
|
|
1633 |
*/
|
|
|
1634 |
function symlink($target, $link)
|
|
|
1635 |
{
|
|
|
1636 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1637 |
return false;
|
|
|
1638 |
}
|
|
|
1639 |
|
|
|
1640 |
//$target = $this->_realpath($target);
|
|
|
1641 |
$link = $this->_realpath($link);
|
|
|
1642 |
|
|
|
1643 |
$packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
|
|
|
1644 |
if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
|
|
|
1645 |
return false;
|
|
|
1646 |
}
|
|
|
1647 |
|
|
|
1648 |
$response = $this->_get_sftp_packet();
|
|
|
1649 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
1650 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
1651 |
return false;
|
|
|
1652 |
}
|
|
|
1653 |
|
|
|
1654 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
1655 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
1656 |
$this->_logError($response, $status);
|
|
|
1657 |
return false;
|
|
|
1658 |
}
|
|
|
1659 |
|
|
|
1660 |
return true;
|
|
|
1661 |
}
|
|
|
1662 |
|
|
|
1663 |
/**
|
|
|
1664 |
* Creates a directory.
|
|
|
1665 |
*
|
|
|
1666 |
* @param string $dir
|
|
|
1667 |
* @return bool
|
|
|
1668 |
* @access public
|
|
|
1669 |
*/
|
|
|
1670 |
function mkdir($dir, $mode = -1, $recursive = false)
|
|
|
1671 |
{
|
|
|
1672 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1673 |
return false;
|
|
|
1674 |
}
|
|
|
1675 |
|
|
|
1676 |
$dir = $this->_realpath($dir);
|
|
|
1677 |
// by not providing any permissions, hopefully the server will use the logged in users umask - their
|
|
|
1678 |
// default permissions.
|
|
|
1679 |
$attr = $mode == -1 ? "\0\0\0\0" : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
|
|
|
1680 |
|
|
|
1681 |
if ($recursive) {
|
|
|
1682 |
$dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
|
|
|
1683 |
if (empty($dirs[0])) {
|
|
|
1684 |
array_shift($dirs);
|
|
|
1685 |
$dirs[0] = '/' . $dirs[0];
|
|
|
1686 |
}
|
|
|
1687 |
for ($i = 0; $i < count($dirs); $i++) {
|
|
|
1688 |
$temp = array_slice($dirs, 0, $i + 1);
|
|
|
1689 |
$temp = implode('/', $temp);
|
|
|
1690 |
$result = $this->_mkdir_helper($temp, $attr);
|
|
|
1691 |
}
|
|
|
1692 |
return $result;
|
|
|
1693 |
}
|
|
|
1694 |
|
|
|
1695 |
return $this->_mkdir_helper($dir, $attr);
|
|
|
1696 |
}
|
|
|
1697 |
|
|
|
1698 |
/**
|
|
|
1699 |
* Helper function for directory creation
|
|
|
1700 |
*
|
|
|
1701 |
* @param string $dir
|
|
|
1702 |
* @return bool
|
|
|
1703 |
* @access private
|
|
|
1704 |
*/
|
|
|
1705 |
function _mkdir_helper($dir, $attr)
|
|
|
1706 |
{
|
|
|
1707 |
if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
|
|
|
1708 |
return false;
|
|
|
1709 |
}
|
|
|
1710 |
|
|
|
1711 |
$response = $this->_get_sftp_packet();
|
|
|
1712 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
1713 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
1714 |
return false;
|
|
|
1715 |
}
|
|
|
1716 |
|
|
|
1717 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
1718 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
1719 |
$this->_logError($response, $status);
|
|
|
1720 |
return false;
|
|
|
1721 |
}
|
|
|
1722 |
|
|
|
1723 |
return true;
|
|
|
1724 |
}
|
|
|
1725 |
|
|
|
1726 |
/**
|
|
|
1727 |
* Removes a directory.
|
|
|
1728 |
*
|
|
|
1729 |
* @param string $dir
|
|
|
1730 |
* @return bool
|
|
|
1731 |
* @access public
|
|
|
1732 |
*/
|
|
|
1733 |
function rmdir($dir)
|
|
|
1734 |
{
|
|
|
1735 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1736 |
return false;
|
|
|
1737 |
}
|
|
|
1738 |
|
|
|
1739 |
$dir = $this->_realpath($dir);
|
|
|
1740 |
if ($dir === false) {
|
|
|
1741 |
return false;
|
|
|
1742 |
}
|
|
|
1743 |
|
|
|
1744 |
if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
|
|
|
1745 |
return false;
|
|
|
1746 |
}
|
|
|
1747 |
|
|
|
1748 |
$response = $this->_get_sftp_packet();
|
|
|
1749 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
1750 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
1751 |
return false;
|
|
|
1752 |
}
|
|
|
1753 |
|
|
|
1754 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
1755 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
1756 |
// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
|
|
|
1757 |
$this->_logError($response, $status);
|
|
|
1758 |
return false;
|
|
|
1759 |
}
|
|
|
1760 |
|
|
|
1761 |
$this->_remove_from_stat_cache($dir);
|
|
|
1762 |
// the following will do a soft delete, which would be useful if you deleted a file
|
|
|
1763 |
// and then tried to do a stat on the deleted file. the above, in contrast, does
|
|
|
1764 |
// a hard delete
|
|
|
1765 |
//$this->_update_stat_cache($dir, false);
|
|
|
1766 |
|
|
|
1767 |
return true;
|
|
|
1768 |
}
|
|
|
1769 |
|
|
|
1770 |
/**
|
|
|
1771 |
* Uploads a file to the SFTP server.
|
|
|
1772 |
*
|
|
|
1773 |
* By default, \phpseclib\Net\SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
|
|
|
1774 |
* So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SFTP::get(), you will get a file, twelve bytes
|
|
|
1775 |
* long, containing 'filename.ext' as its contents.
|
|
|
1776 |
*
|
|
|
1777 |
* Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior. With self::SOURCE_LOCAL_FILE, $remote_file will
|
|
|
1778 |
* contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
|
|
|
1779 |
* large $remote_file will be, as well.
|
|
|
1780 |
*
|
|
|
1781 |
* Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number of bytes to return, and returns a string if there is some data or null if there is no more data
|
|
|
1782 |
*
|
|
|
1783 |
* If $data is a resource then it'll be used as a resource instead.
|
|
|
1784 |
*
|
|
|
1785 |
* Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
|
|
|
1786 |
* care of that, yourself.
|
|
|
1787 |
*
|
|
|
1788 |
* $mode can take an additional two parameters - self::RESUME and self::RESUME_START. These are bitwise AND'd with
|
|
|
1789 |
* $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
|
|
|
1790 |
*
|
|
|
1791 |
* self::SOURCE_LOCAL_FILE | self::RESUME
|
|
|
1792 |
*
|
|
|
1793 |
* If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
|
|
|
1794 |
* self::RESUME with self::RESUME_START.
|
|
|
1795 |
*
|
|
|
1796 |
* If $mode & (self::RESUME | self::RESUME_START) then self::RESUME_START will be assumed.
|
|
|
1797 |
*
|
|
|
1798 |
* $start and $local_start give you more fine grained control over this process and take precident over self::RESUME
|
|
|
1799 |
* when they're non-negative. ie. $start could let you write at the end of a file (like self::RESUME) or in the middle
|
|
|
1800 |
* of one. $local_start could let you start your reading from the end of a file (like self::RESUME_START) or in the
|
|
|
1801 |
* middle of one.
|
|
|
1802 |
*
|
|
|
1803 |
* Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE.
|
|
|
1804 |
*
|
|
|
1805 |
* @param string $remote_file
|
|
|
1806 |
* @param string|resource $data
|
|
|
1807 |
* @param int $mode
|
|
|
1808 |
* @param int $start
|
|
|
1809 |
* @param int $local_start
|
|
|
1810 |
* @param callable|null $progressCallback
|
|
|
1811 |
* @return bool
|
|
|
1812 |
* @access public
|
|
|
1813 |
* @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib\Net\SFTP::setMode().
|
|
|
1814 |
*/
|
|
|
1815 |
function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
|
|
|
1816 |
{
|
|
|
1817 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
1818 |
return false;
|
|
|
1819 |
}
|
|
|
1820 |
|
|
|
1821 |
$remote_file = $this->_realpath($remote_file);
|
|
|
1822 |
if ($remote_file === false) {
|
|
|
1823 |
return false;
|
|
|
1824 |
}
|
|
|
1825 |
|
|
|
1826 |
$this->_remove_from_stat_cache($remote_file);
|
|
|
1827 |
|
|
|
1828 |
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
|
|
|
1829 |
// according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
|
|
|
1830 |
// in practice, it doesn't seem to do that.
|
|
|
1831 |
//$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
|
|
|
1832 |
|
|
|
1833 |
if ($start >= 0) {
|
|
|
1834 |
$offset = $start;
|
|
|
1835 |
} elseif ($mode & self::RESUME) {
|
|
|
1836 |
// if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
|
|
|
1837 |
$size = $this->size($remote_file);
|
|
|
1838 |
$offset = $size !== false ? $size : 0;
|
|
|
1839 |
} else {
|
|
|
1840 |
$offset = 0;
|
|
|
1841 |
$flags|= NET_SFTP_OPEN_TRUNCATE;
|
|
|
1842 |
}
|
|
|
1843 |
|
|
|
1844 |
$packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
|
|
|
1845 |
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
|
|
|
1846 |
return false;
|
|
|
1847 |
}
|
|
|
1848 |
|
|
|
1849 |
$response = $this->_get_sftp_packet();
|
|
|
1850 |
switch ($this->packet_type) {
|
|
|
1851 |
case NET_SFTP_HANDLE:
|
|
|
1852 |
$handle = substr($response, 4);
|
|
|
1853 |
break;
|
|
|
1854 |
case NET_SFTP_STATUS:
|
|
|
1855 |
$this->_logError($response);
|
|
|
1856 |
return false;
|
|
|
1857 |
default:
|
|
|
1858 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
1859 |
return false;
|
|
|
1860 |
}
|
|
|
1861 |
|
|
|
1862 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
|
|
|
1863 |
$dataCallback = false;
|
|
|
1864 |
switch (true) {
|
|
|
1865 |
case $mode & self::SOURCE_CALLBACK:
|
|
|
1866 |
if (!is_callable($data)) {
|
|
|
1867 |
user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
|
|
|
1868 |
}
|
|
|
1869 |
$dataCallback = $data;
|
|
|
1870 |
// do nothing
|
|
|
1871 |
break;
|
|
|
1872 |
case is_resource($data):
|
|
|
1873 |
$mode = $mode & ~self::SOURCE_LOCAL_FILE;
|
|
|
1874 |
$fp = $data;
|
|
|
1875 |
break;
|
|
|
1876 |
case $mode & self::SOURCE_LOCAL_FILE:
|
|
|
1877 |
if (!is_file($data)) {
|
|
|
1878 |
user_error("$data is not a valid file");
|
|
|
1879 |
return false;
|
|
|
1880 |
}
|
|
|
1881 |
$fp = @fopen($data, 'rb');
|
|
|
1882 |
if (!$fp) {
|
|
|
1883 |
return false;
|
|
|
1884 |
}
|
|
|
1885 |
}
|
|
|
1886 |
|
|
|
1887 |
if (isset($fp)) {
|
|
|
1888 |
$stat = fstat($fp);
|
|
|
1889 |
$size = $stat['size'];
|
|
|
1890 |
|
|
|
1891 |
if ($local_start >= 0) {
|
|
|
1892 |
fseek($fp, $local_start);
|
|
|
1893 |
$size-= $local_start;
|
|
|
1894 |
}
|
|
|
1895 |
} elseif ($dataCallback) {
|
|
|
1896 |
$size = 0;
|
|
|
1897 |
} else {
|
|
|
1898 |
$size = strlen($data);
|
|
|
1899 |
}
|
|
|
1900 |
|
|
|
1901 |
$sent = 0;
|
|
|
1902 |
$size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
|
|
|
1903 |
|
|
|
1904 |
$sftp_packet_size = 4096; // PuTTY uses 4096
|
|
|
1905 |
// make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header"
|
|
|
1906 |
$sftp_packet_size-= strlen($handle) + 25;
|
|
|
1907 |
$i = 0;
|
|
|
1908 |
while ($dataCallback || ($size === 0 || $sent < $size)) {
|
|
|
1909 |
if ($dataCallback) {
|
|
|
1910 |
$temp = call_user_func($dataCallback, $sftp_packet_size);
|
|
|
1911 |
if (is_null($temp)) {
|
|
|
1912 |
break;
|
|
|
1913 |
}
|
|
|
1914 |
} else {
|
|
|
1915 |
$temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
|
|
|
1916 |
if ($temp === false || $temp === '') {
|
|
|
1917 |
break;
|
|
|
1918 |
}
|
|
|
1919 |
}
|
|
|
1920 |
|
|
|
1921 |
$subtemp = $offset + $sent;
|
|
|
1922 |
$packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
|
|
|
1923 |
if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
|
|
|
1924 |
if ($mode & self::SOURCE_LOCAL_FILE) {
|
|
|
1925 |
fclose($fp);
|
|
|
1926 |
}
|
|
|
1927 |
return false;
|
|
|
1928 |
}
|
|
|
1929 |
$sent+= strlen($temp);
|
|
|
1930 |
if (is_callable($progressCallback)) {
|
|
|
1931 |
call_user_func($progressCallback, $sent);
|
|
|
1932 |
}
|
|
|
1933 |
|
|
|
1934 |
$i++;
|
|
|
1935 |
|
|
|
1936 |
if ($i == NET_SFTP_QUEUE_SIZE) {
|
|
|
1937 |
if (!$this->_read_put_responses($i)) {
|
|
|
1938 |
$i = 0;
|
|
|
1939 |
break;
|
|
|
1940 |
}
|
|
|
1941 |
$i = 0;
|
|
|
1942 |
}
|
|
|
1943 |
}
|
|
|
1944 |
|
|
|
1945 |
if (!$this->_read_put_responses($i)) {
|
|
|
1946 |
if ($mode & self::SOURCE_LOCAL_FILE) {
|
|
|
1947 |
fclose($fp);
|
|
|
1948 |
}
|
|
|
1949 |
$this->_close_handle($handle);
|
|
|
1950 |
return false;
|
|
|
1951 |
}
|
|
|
1952 |
|
|
|
1953 |
if ($mode & self::SOURCE_LOCAL_FILE) {
|
|
|
1954 |
fclose($fp);
|
|
|
1955 |
}
|
|
|
1956 |
|
|
|
1957 |
return $this->_close_handle($handle);
|
|
|
1958 |
}
|
|
|
1959 |
|
|
|
1960 |
/**
|
|
|
1961 |
* Reads multiple successive SSH_FXP_WRITE responses
|
|
|
1962 |
*
|
|
|
1963 |
* Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
|
|
|
1964 |
* SSH_FXP_WRITEs, in succession, and then reading $i responses.
|
|
|
1965 |
*
|
|
|
1966 |
* @param int $i
|
|
|
1967 |
* @return bool
|
|
|
1968 |
* @access private
|
|
|
1969 |
*/
|
|
|
1970 |
function _read_put_responses($i)
|
|
|
1971 |
{
|
|
|
1972 |
while ($i--) {
|
|
|
1973 |
$response = $this->_get_sftp_packet();
|
|
|
1974 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
1975 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
1976 |
return false;
|
|
|
1977 |
}
|
|
|
1978 |
|
|
|
1979 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
1980 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
1981 |
$this->_logError($response, $status);
|
|
|
1982 |
break;
|
|
|
1983 |
}
|
|
|
1984 |
}
|
|
|
1985 |
|
|
|
1986 |
return $i < 0;
|
|
|
1987 |
}
|
|
|
1988 |
|
|
|
1989 |
/**
|
|
|
1990 |
* Close handle
|
|
|
1991 |
*
|
|
|
1992 |
* @param string $handle
|
|
|
1993 |
* @return bool
|
|
|
1994 |
* @access private
|
|
|
1995 |
*/
|
|
|
1996 |
function _close_handle($handle)
|
|
|
1997 |
{
|
|
|
1998 |
if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
|
|
|
1999 |
return false;
|
|
|
2000 |
}
|
|
|
2001 |
|
|
|
2002 |
// "The client MUST release all resources associated with the handle regardless of the status."
|
|
|
2003 |
// -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
|
|
|
2004 |
$response = $this->_get_sftp_packet();
|
|
|
2005 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
2006 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
2007 |
return false;
|
|
|
2008 |
}
|
|
|
2009 |
|
|
|
2010 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
2011 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
2012 |
$this->_logError($response, $status);
|
|
|
2013 |
return false;
|
|
|
2014 |
}
|
|
|
2015 |
|
|
|
2016 |
return true;
|
|
|
2017 |
}
|
|
|
2018 |
|
|
|
2019 |
/**
|
|
|
2020 |
* Downloads a file from the SFTP server.
|
|
|
2021 |
*
|
|
|
2022 |
* Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
|
|
|
2023 |
* the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
|
|
|
2024 |
* operation.
|
|
|
2025 |
*
|
|
|
2026 |
* $offset and $length can be used to download files in chunks.
|
|
|
2027 |
*
|
|
|
2028 |
* @param string $remote_file
|
|
|
2029 |
* @param string $local_file
|
|
|
2030 |
* @param int $offset
|
|
|
2031 |
* @param int $length
|
|
|
2032 |
* @return mixed
|
|
|
2033 |
* @access public
|
|
|
2034 |
*/
|
|
|
2035 |
function get($remote_file, $local_file = false, $offset = 0, $length = -1)
|
|
|
2036 |
{
|
|
|
2037 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
2038 |
return false;
|
|
|
2039 |
}
|
|
|
2040 |
|
|
|
2041 |
$remote_file = $this->_realpath($remote_file);
|
|
|
2042 |
if ($remote_file === false) {
|
|
|
2043 |
return false;
|
|
|
2044 |
}
|
|
|
2045 |
|
|
|
2046 |
$packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
|
|
|
2047 |
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
|
|
|
2048 |
return false;
|
|
|
2049 |
}
|
|
|
2050 |
|
|
|
2051 |
$response = $this->_get_sftp_packet();
|
|
|
2052 |
switch ($this->packet_type) {
|
|
|
2053 |
case NET_SFTP_HANDLE:
|
|
|
2054 |
$handle = substr($response, 4);
|
|
|
2055 |
break;
|
|
|
2056 |
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
|
|
|
2057 |
$this->_logError($response);
|
|
|
2058 |
return false;
|
|
|
2059 |
default:
|
|
|
2060 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
2061 |
return false;
|
|
|
2062 |
}
|
|
|
2063 |
|
|
|
2064 |
if (is_resource($local_file)) {
|
|
|
2065 |
$fp = $local_file;
|
|
|
2066 |
$stat = fstat($fp);
|
|
|
2067 |
$res_offset = $stat['size'];
|
|
|
2068 |
} else {
|
|
|
2069 |
$res_offset = 0;
|
|
|
2070 |
if ($local_file !== false) {
|
|
|
2071 |
$fp = fopen($local_file, 'wb');
|
|
|
2072 |
if (!$fp) {
|
|
|
2073 |
return false;
|
|
|
2074 |
}
|
|
|
2075 |
} else {
|
|
|
2076 |
$content = '';
|
|
|
2077 |
}
|
|
|
2078 |
}
|
|
|
2079 |
|
|
|
2080 |
$fclose_check = $local_file !== false && !is_resource($local_file);
|
|
|
2081 |
|
|
|
2082 |
$start = $offset;
|
|
|
2083 |
$read = 0;
|
|
|
2084 |
while (true) {
|
|
|
2085 |
$i = 0;
|
|
|
2086 |
|
|
|
2087 |
while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
|
|
|
2088 |
$tempoffset = $start + $read;
|
|
|
2089 |
|
|
|
2090 |
$packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
|
|
|
2091 |
|
|
|
2092 |
$packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
|
|
|
2093 |
if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
|
|
|
2094 |
if ($fclose_check) {
|
|
|
2095 |
fclose($fp);
|
|
|
2096 |
}
|
|
|
2097 |
return false;
|
|
|
2098 |
}
|
|
|
2099 |
$packet = null;
|
|
|
2100 |
$read+= $packet_size;
|
|
|
2101 |
$i++;
|
|
|
2102 |
}
|
|
|
2103 |
|
|
|
2104 |
if (!$i) {
|
|
|
2105 |
break;
|
|
|
2106 |
}
|
|
|
2107 |
|
|
|
2108 |
$clear_responses = false;
|
|
|
2109 |
while ($i > 0) {
|
|
|
2110 |
$i--;
|
|
|
2111 |
|
|
|
2112 |
if ($clear_responses) {
|
|
|
2113 |
$this->_get_sftp_packet();
|
|
|
2114 |
continue;
|
|
|
2115 |
} else {
|
|
|
2116 |
$response = $this->_get_sftp_packet();
|
|
|
2117 |
}
|
|
|
2118 |
|
|
|
2119 |
switch ($this->packet_type) {
|
|
|
2120 |
case NET_SFTP_DATA:
|
|
|
2121 |
$temp = substr($response, 4);
|
|
|
2122 |
$offset+= strlen($temp);
|
|
|
2123 |
if ($local_file === false) {
|
|
|
2124 |
$content.= $temp;
|
|
|
2125 |
} else {
|
|
|
2126 |
fputs($fp, $temp);
|
|
|
2127 |
}
|
|
|
2128 |
$temp = null;
|
|
|
2129 |
break;
|
|
|
2130 |
case NET_SFTP_STATUS:
|
|
|
2131 |
// could, in theory, return false if !strlen($content) but we'll hold off for the time being
|
|
|
2132 |
$this->_logError($response);
|
|
|
2133 |
$clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
|
|
|
2134 |
break;
|
|
|
2135 |
default:
|
|
|
2136 |
if ($fclose_check) {
|
|
|
2137 |
fclose($fp);
|
|
|
2138 |
}
|
|
|
2139 |
user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
|
|
|
2140 |
}
|
|
|
2141 |
$response = null;
|
|
|
2142 |
}
|
|
|
2143 |
|
|
|
2144 |
if ($clear_responses) {
|
|
|
2145 |
break;
|
|
|
2146 |
}
|
|
|
2147 |
}
|
|
|
2148 |
|
|
|
2149 |
if ($length > 0 && $length <= $offset - $start) {
|
|
|
2150 |
if ($local_file === false) {
|
|
|
2151 |
$content = substr($content, 0, $length);
|
|
|
2152 |
} else {
|
|
|
2153 |
ftruncate($fp, $length + $res_offset);
|
|
|
2154 |
}
|
|
|
2155 |
}
|
|
|
2156 |
|
|
|
2157 |
if ($fclose_check) {
|
|
|
2158 |
fclose($fp);
|
|
|
2159 |
}
|
|
|
2160 |
|
|
|
2161 |
if (!$this->_close_handle($handle)) {
|
|
|
2162 |
return false;
|
|
|
2163 |
}
|
|
|
2164 |
|
|
|
2165 |
// if $content isn't set that means a file was written to
|
|
|
2166 |
return isset($content) ? $content : true;
|
|
|
2167 |
}
|
|
|
2168 |
|
|
|
2169 |
/**
|
|
|
2170 |
* Deletes a file on the SFTP server.
|
|
|
2171 |
*
|
|
|
2172 |
* @param string $path
|
|
|
2173 |
* @param bool $recursive
|
|
|
2174 |
* @return bool
|
|
|
2175 |
* @access public
|
|
|
2176 |
*/
|
|
|
2177 |
function delete($path, $recursive = true)
|
|
|
2178 |
{
|
|
|
2179 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
2180 |
return false;
|
|
|
2181 |
}
|
|
|
2182 |
|
|
|
2183 |
$path = $this->_realpath($path);
|
|
|
2184 |
if ($path === false) {
|
|
|
2185 |
return false;
|
|
|
2186 |
}
|
|
|
2187 |
|
|
|
2188 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
|
|
2189 |
if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
|
|
|
2190 |
return false;
|
|
|
2191 |
}
|
|
|
2192 |
|
|
|
2193 |
$response = $this->_get_sftp_packet();
|
|
|
2194 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
2195 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
2196 |
return false;
|
|
|
2197 |
}
|
|
|
2198 |
|
|
|
2199 |
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
|
|
|
2200 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
2201 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
2202 |
$this->_logError($response, $status);
|
|
|
2203 |
if (!$recursive) {
|
|
|
2204 |
return false;
|
|
|
2205 |
}
|
|
|
2206 |
$i = 0;
|
|
|
2207 |
$result = $this->_delete_recursive($path, $i);
|
|
|
2208 |
$this->_read_put_responses($i);
|
|
|
2209 |
return $result;
|
|
|
2210 |
}
|
|
|
2211 |
|
|
|
2212 |
$this->_remove_from_stat_cache($path);
|
|
|
2213 |
|
|
|
2214 |
return true;
|
|
|
2215 |
}
|
|
|
2216 |
|
|
|
2217 |
/**
|
|
|
2218 |
* Recursively deletes directories on the SFTP server
|
|
|
2219 |
*
|
|
|
2220 |
* Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
|
|
|
2221 |
*
|
|
|
2222 |
* @param string $path
|
|
|
2223 |
* @param int $i
|
|
|
2224 |
* @return bool
|
|
|
2225 |
* @access private
|
|
|
2226 |
*/
|
|
|
2227 |
function _delete_recursive($path, &$i)
|
|
|
2228 |
{
|
|
|
2229 |
if (!$this->_read_put_responses($i)) {
|
|
|
2230 |
return false;
|
|
|
2231 |
}
|
|
|
2232 |
$i = 0;
|
|
|
2233 |
$entries = $this->_list($path, true);
|
|
|
2234 |
|
|
|
2235 |
// normally $entries would have at least . and .. but it might not if the directories
|
|
|
2236 |
// permissions didn't allow reading
|
|
|
2237 |
if (empty($entries)) {
|
|
|
2238 |
return false;
|
|
|
2239 |
}
|
|
|
2240 |
|
|
|
2241 |
unset($entries['.'], $entries['..']);
|
|
|
2242 |
foreach ($entries as $filename => $props) {
|
|
|
2243 |
if (!isset($props['type'])) {
|
|
|
2244 |
return false;
|
|
|
2245 |
}
|
|
|
2246 |
|
|
|
2247 |
$temp = $path . '/' . $filename;
|
|
|
2248 |
if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
|
|
|
2249 |
if (!$this->_delete_recursive($temp, $i)) {
|
|
|
2250 |
return false;
|
|
|
2251 |
}
|
|
|
2252 |
} else {
|
|
|
2253 |
if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
|
|
|
2254 |
return false;
|
|
|
2255 |
}
|
|
|
2256 |
$this->_remove_from_stat_cache($temp);
|
|
|
2257 |
|
|
|
2258 |
$i++;
|
|
|
2259 |
|
|
|
2260 |
if ($i >= NET_SFTP_QUEUE_SIZE) {
|
|
|
2261 |
if (!$this->_read_put_responses($i)) {
|
|
|
2262 |
return false;
|
|
|
2263 |
}
|
|
|
2264 |
$i = 0;
|
|
|
2265 |
}
|
|
|
2266 |
}
|
|
|
2267 |
}
|
|
|
2268 |
|
|
|
2269 |
if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
|
|
|
2270 |
return false;
|
|
|
2271 |
}
|
|
|
2272 |
$this->_remove_from_stat_cache($path);
|
|
|
2273 |
|
|
|
2274 |
$i++;
|
|
|
2275 |
|
|
|
2276 |
if ($i >= NET_SFTP_QUEUE_SIZE) {
|
|
|
2277 |
if (!$this->_read_put_responses($i)) {
|
|
|
2278 |
return false;
|
|
|
2279 |
}
|
|
|
2280 |
$i = 0;
|
|
|
2281 |
}
|
|
|
2282 |
|
|
|
2283 |
return true;
|
|
|
2284 |
}
|
|
|
2285 |
|
|
|
2286 |
/**
|
|
|
2287 |
* Checks whether a file or directory exists
|
|
|
2288 |
*
|
|
|
2289 |
* @param string $path
|
|
|
2290 |
* @return bool
|
|
|
2291 |
* @access public
|
|
|
2292 |
*/
|
|
|
2293 |
function file_exists($path)
|
|
|
2294 |
{
|
|
|
2295 |
if ($this->use_stat_cache) {
|
|
|
2296 |
$path = $this->_realpath($path);
|
|
|
2297 |
|
|
|
2298 |
$result = $this->_query_stat_cache($path);
|
|
|
2299 |
|
|
|
2300 |
if (isset($result)) {
|
|
|
2301 |
// return true if $result is an array or if it's an stdClass object
|
|
|
2302 |
return $result !== false;
|
|
|
2303 |
}
|
|
|
2304 |
}
|
|
|
2305 |
|
|
|
2306 |
return $this->stat($path) !== false;
|
|
|
2307 |
}
|
|
|
2308 |
|
|
|
2309 |
/**
|
|
|
2310 |
* Tells whether the filename is a directory
|
|
|
2311 |
*
|
|
|
2312 |
* @param string $path
|
|
|
2313 |
* @return bool
|
|
|
2314 |
* @access public
|
|
|
2315 |
*/
|
|
|
2316 |
function is_dir($path)
|
|
|
2317 |
{
|
|
|
2318 |
$result = $this->_get_stat_cache_prop($path, 'type');
|
|
|
2319 |
if ($result === false) {
|
|
|
2320 |
return false;
|
|
|
2321 |
}
|
|
|
2322 |
return $result === NET_SFTP_TYPE_DIRECTORY;
|
|
|
2323 |
}
|
|
|
2324 |
|
|
|
2325 |
/**
|
|
|
2326 |
* Tells whether the filename is a regular file
|
|
|
2327 |
*
|
|
|
2328 |
* @param string $path
|
|
|
2329 |
* @return bool
|
|
|
2330 |
* @access public
|
|
|
2331 |
*/
|
|
|
2332 |
function is_file($path)
|
|
|
2333 |
{
|
|
|
2334 |
$result = $this->_get_stat_cache_prop($path, 'type');
|
|
|
2335 |
if ($result === false) {
|
|
|
2336 |
return false;
|
|
|
2337 |
}
|
|
|
2338 |
return $result === NET_SFTP_TYPE_REGULAR;
|
|
|
2339 |
}
|
|
|
2340 |
|
|
|
2341 |
/**
|
|
|
2342 |
* Tells whether the filename is a symbolic link
|
|
|
2343 |
*
|
|
|
2344 |
* @param string $path
|
|
|
2345 |
* @return bool
|
|
|
2346 |
* @access public
|
|
|
2347 |
*/
|
|
|
2348 |
function is_link($path)
|
|
|
2349 |
{
|
|
|
2350 |
$result = $this->_get_lstat_cache_prop($path, 'type');
|
|
|
2351 |
if ($result === false) {
|
|
|
2352 |
return false;
|
|
|
2353 |
}
|
|
|
2354 |
return $result === NET_SFTP_TYPE_SYMLINK;
|
|
|
2355 |
}
|
|
|
2356 |
|
|
|
2357 |
/**
|
|
|
2358 |
* Tells whether a file exists and is readable
|
|
|
2359 |
*
|
|
|
2360 |
* @param string $path
|
|
|
2361 |
* @return bool
|
|
|
2362 |
* @access public
|
|
|
2363 |
*/
|
|
|
2364 |
function is_readable($path)
|
|
|
2365 |
{
|
|
|
2366 |
$path = $this->_realpath($path);
|
|
|
2367 |
|
|
|
2368 |
$packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
|
|
|
2369 |
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
|
|
|
2370 |
return false;
|
|
|
2371 |
}
|
|
|
2372 |
|
|
|
2373 |
$response = $this->_get_sftp_packet();
|
|
|
2374 |
switch ($this->packet_type) {
|
|
|
2375 |
case NET_SFTP_HANDLE:
|
|
|
2376 |
return true;
|
|
|
2377 |
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
|
|
|
2378 |
return false;
|
|
|
2379 |
default:
|
|
|
2380 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
2381 |
return false;
|
|
|
2382 |
}
|
|
|
2383 |
}
|
|
|
2384 |
|
|
|
2385 |
/**
|
|
|
2386 |
* Tells whether the filename is writable
|
|
|
2387 |
*
|
|
|
2388 |
* @param string $path
|
|
|
2389 |
* @return bool
|
|
|
2390 |
* @access public
|
|
|
2391 |
*/
|
|
|
2392 |
function is_writable($path)
|
|
|
2393 |
{
|
|
|
2394 |
$path = $this->_realpath($path);
|
|
|
2395 |
|
|
|
2396 |
$packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
|
|
|
2397 |
if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
|
|
|
2398 |
return false;
|
|
|
2399 |
}
|
|
|
2400 |
|
|
|
2401 |
$response = $this->_get_sftp_packet();
|
|
|
2402 |
switch ($this->packet_type) {
|
|
|
2403 |
case NET_SFTP_HANDLE:
|
|
|
2404 |
return true;
|
|
|
2405 |
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
|
|
|
2406 |
return false;
|
|
|
2407 |
default:
|
|
|
2408 |
user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
|
|
|
2409 |
return false;
|
|
|
2410 |
}
|
|
|
2411 |
}
|
|
|
2412 |
|
|
|
2413 |
/**
|
|
|
2414 |
* Tells whether the filename is writeable
|
|
|
2415 |
*
|
|
|
2416 |
* Alias of is_writable
|
|
|
2417 |
*
|
|
|
2418 |
* @param string $path
|
|
|
2419 |
* @return bool
|
|
|
2420 |
* @access public
|
|
|
2421 |
*/
|
|
|
2422 |
function is_writeable($path)
|
|
|
2423 |
{
|
|
|
2424 |
return $this->is_writable($path);
|
|
|
2425 |
}
|
|
|
2426 |
|
|
|
2427 |
/**
|
|
|
2428 |
* Gets last access time of file
|
|
|
2429 |
*
|
|
|
2430 |
* @param string $path
|
|
|
2431 |
* @return mixed
|
|
|
2432 |
* @access public
|
|
|
2433 |
*/
|
|
|
2434 |
function fileatime($path)
|
|
|
2435 |
{
|
|
|
2436 |
return $this->_get_stat_cache_prop($path, 'atime');
|
|
|
2437 |
}
|
|
|
2438 |
|
|
|
2439 |
/**
|
|
|
2440 |
* Gets file modification time
|
|
|
2441 |
*
|
|
|
2442 |
* @param string $path
|
|
|
2443 |
* @return mixed
|
|
|
2444 |
* @access public
|
|
|
2445 |
*/
|
|
|
2446 |
function filemtime($path)
|
|
|
2447 |
{
|
|
|
2448 |
return $this->_get_stat_cache_prop($path, 'mtime');
|
|
|
2449 |
}
|
|
|
2450 |
|
|
|
2451 |
/**
|
|
|
2452 |
* Gets file permissions
|
|
|
2453 |
*
|
|
|
2454 |
* @param string $path
|
|
|
2455 |
* @return mixed
|
|
|
2456 |
* @access public
|
|
|
2457 |
*/
|
|
|
2458 |
function fileperms($path)
|
|
|
2459 |
{
|
|
|
2460 |
return $this->_get_stat_cache_prop($path, 'permissions');
|
|
|
2461 |
}
|
|
|
2462 |
|
|
|
2463 |
/**
|
|
|
2464 |
* Gets file owner
|
|
|
2465 |
*
|
|
|
2466 |
* @param string $path
|
|
|
2467 |
* @return mixed
|
|
|
2468 |
* @access public
|
|
|
2469 |
*/
|
|
|
2470 |
function fileowner($path)
|
|
|
2471 |
{
|
|
|
2472 |
return $this->_get_stat_cache_prop($path, 'uid');
|
|
|
2473 |
}
|
|
|
2474 |
|
|
|
2475 |
/**
|
|
|
2476 |
* Gets file group
|
|
|
2477 |
*
|
|
|
2478 |
* @param string $path
|
|
|
2479 |
* @return mixed
|
|
|
2480 |
* @access public
|
|
|
2481 |
*/
|
|
|
2482 |
function filegroup($path)
|
|
|
2483 |
{
|
|
|
2484 |
return $this->_get_stat_cache_prop($path, 'gid');
|
|
|
2485 |
}
|
|
|
2486 |
|
|
|
2487 |
/**
|
|
|
2488 |
* Gets file size
|
|
|
2489 |
*
|
|
|
2490 |
* @param string $path
|
|
|
2491 |
* @return mixed
|
|
|
2492 |
* @access public
|
|
|
2493 |
*/
|
|
|
2494 |
function filesize($path)
|
|
|
2495 |
{
|
|
|
2496 |
return $this->_get_stat_cache_prop($path, 'size');
|
|
|
2497 |
}
|
|
|
2498 |
|
|
|
2499 |
/**
|
|
|
2500 |
* Gets file type
|
|
|
2501 |
*
|
|
|
2502 |
* @param string $path
|
|
|
2503 |
* @return mixed
|
|
|
2504 |
* @access public
|
|
|
2505 |
*/
|
|
|
2506 |
function filetype($path)
|
|
|
2507 |
{
|
|
|
2508 |
$type = $this->_get_stat_cache_prop($path, 'type');
|
|
|
2509 |
if ($type === false) {
|
|
|
2510 |
return false;
|
|
|
2511 |
}
|
|
|
2512 |
|
|
|
2513 |
switch ($type) {
|
|
|
2514 |
case NET_SFTP_TYPE_BLOCK_DEVICE:
|
|
|
2515 |
return 'block';
|
|
|
2516 |
case NET_SFTP_TYPE_CHAR_DEVICE:
|
|
|
2517 |
return 'char';
|
|
|
2518 |
case NET_SFTP_TYPE_DIRECTORY:
|
|
|
2519 |
return 'dir';
|
|
|
2520 |
case NET_SFTP_TYPE_FIFO:
|
|
|
2521 |
return 'fifo';
|
|
|
2522 |
case NET_SFTP_TYPE_REGULAR:
|
|
|
2523 |
return 'file';
|
|
|
2524 |
case NET_SFTP_TYPE_SYMLINK:
|
|
|
2525 |
return 'link';
|
|
|
2526 |
default:
|
|
|
2527 |
return false;
|
|
|
2528 |
}
|
|
|
2529 |
}
|
|
|
2530 |
|
|
|
2531 |
/**
|
|
|
2532 |
* Return a stat properity
|
|
|
2533 |
*
|
|
|
2534 |
* Uses cache if appropriate.
|
|
|
2535 |
*
|
|
|
2536 |
* @param string $path
|
|
|
2537 |
* @param string $prop
|
|
|
2538 |
* @return mixed
|
|
|
2539 |
* @access private
|
|
|
2540 |
*/
|
|
|
2541 |
function _get_stat_cache_prop($path, $prop)
|
|
|
2542 |
{
|
|
|
2543 |
return $this->_get_xstat_cache_prop($path, $prop, 'stat');
|
|
|
2544 |
}
|
|
|
2545 |
|
|
|
2546 |
/**
|
|
|
2547 |
* Return an lstat properity
|
|
|
2548 |
*
|
|
|
2549 |
* Uses cache if appropriate.
|
|
|
2550 |
*
|
|
|
2551 |
* @param string $path
|
|
|
2552 |
* @param string $prop
|
|
|
2553 |
* @return mixed
|
|
|
2554 |
* @access private
|
|
|
2555 |
*/
|
|
|
2556 |
function _get_lstat_cache_prop($path, $prop)
|
|
|
2557 |
{
|
|
|
2558 |
return $this->_get_xstat_cache_prop($path, $prop, 'lstat');
|
|
|
2559 |
}
|
|
|
2560 |
|
|
|
2561 |
/**
|
|
|
2562 |
* Return a stat or lstat properity
|
|
|
2563 |
*
|
|
|
2564 |
* Uses cache if appropriate.
|
|
|
2565 |
*
|
|
|
2566 |
* @param string $path
|
|
|
2567 |
* @param string $prop
|
|
|
2568 |
* @return mixed
|
|
|
2569 |
* @access private
|
|
|
2570 |
*/
|
|
|
2571 |
function _get_xstat_cache_prop($path, $prop, $type)
|
|
|
2572 |
{
|
|
|
2573 |
if ($this->use_stat_cache) {
|
|
|
2574 |
$path = $this->_realpath($path);
|
|
|
2575 |
|
|
|
2576 |
$result = $this->_query_stat_cache($path);
|
|
|
2577 |
|
|
|
2578 |
if (is_object($result) && isset($result->$type)) {
|
|
|
2579 |
return $result->{$type}[$prop];
|
|
|
2580 |
}
|
|
|
2581 |
}
|
|
|
2582 |
|
|
|
2583 |
$result = $this->$type($path);
|
|
|
2584 |
|
|
|
2585 |
if ($result === false || !isset($result[$prop])) {
|
|
|
2586 |
return false;
|
|
|
2587 |
}
|
|
|
2588 |
|
|
|
2589 |
return $result[$prop];
|
|
|
2590 |
}
|
|
|
2591 |
|
|
|
2592 |
/**
|
|
|
2593 |
* Renames a file or a directory on the SFTP server
|
|
|
2594 |
*
|
|
|
2595 |
* @param string $oldname
|
|
|
2596 |
* @param string $newname
|
|
|
2597 |
* @return bool
|
|
|
2598 |
* @access public
|
|
|
2599 |
*/
|
|
|
2600 |
function rename($oldname, $newname)
|
|
|
2601 |
{
|
|
|
2602 |
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
|
|
|
2603 |
return false;
|
|
|
2604 |
}
|
|
|
2605 |
|
|
|
2606 |
$oldname = $this->_realpath($oldname);
|
|
|
2607 |
$newname = $this->_realpath($newname);
|
|
|
2608 |
if ($oldname === false || $newname === false) {
|
|
|
2609 |
return false;
|
|
|
2610 |
}
|
|
|
2611 |
|
|
|
2612 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
|
|
|
2613 |
$packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
|
|
|
2614 |
if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
|
|
|
2615 |
return false;
|
|
|
2616 |
}
|
|
|
2617 |
|
|
|
2618 |
$response = $this->_get_sftp_packet();
|
|
|
2619 |
if ($this->packet_type != NET_SFTP_STATUS) {
|
|
|
2620 |
user_error('Expected SSH_FXP_STATUS');
|
|
|
2621 |
return false;
|
|
|
2622 |
}
|
|
|
2623 |
|
|
|
2624 |
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
|
|
|
2625 |
extract(unpack('Nstatus', $this->_string_shift($response, 4)));
|
|
|
2626 |
if ($status != NET_SFTP_STATUS_OK) {
|
|
|
2627 |
$this->_logError($response, $status);
|
|
|
2628 |
return false;
|
|
|
2629 |
}
|
|
|
2630 |
|
|
|
2631 |
// don't move the stat cache entry over since this operation could very well change the
|
|
|
2632 |
// atime and mtime attributes
|
|
|
2633 |
//$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
|
|
|
2634 |
$this->_remove_from_stat_cache($oldname);
|
|
|
2635 |
$this->_remove_from_stat_cache($newname);
|
|
|
2636 |
|
|
|
2637 |
return true;
|
|
|
2638 |
}
|
|
|
2639 |
|
|
|
2640 |
/**
|
|
|
2641 |
* Parse Attributes
|
|
|
2642 |
*
|
|
|
2643 |
* See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
|
|
|
2644 |
*
|
|
|
2645 |
* @param string $response
|
|
|
2646 |
* @return array
|
|
|
2647 |
* @access private
|
|
|
2648 |
*/
|
|
|
2649 |
function _parseAttributes(&$response)
|
|
|
2650 |
{
|
|
|
2651 |
$attr = array();
|
|
|
2652 |
extract(unpack('Nflags', $this->_string_shift($response, 4)));
|
|
|
2653 |
// SFTPv4+ have a type field (a byte) that follows the above flag field
|
|
|
2654 |
foreach ($this->attributes as $key => $value) {
|
|
|
2655 |
switch ($flags & $key) {
|
|
|
2656 |
case NET_SFTP_ATTR_SIZE: // 0x00000001
|
|
|
2657 |
// The size attribute is defined as an unsigned 64-bit integer.
|
|
|
2658 |
// The following will use floats on 32-bit platforms, if necessary.
|
|
|
2659 |
// As can be seen in the BigInteger class, floats are generally
|
|
|
2660 |
// IEEE 754 binary64 "double precision" on such platforms and
|
|
|
2661 |
// as such can represent integers of at least 2^50 without loss
|
|
|
2662 |
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
|
|
|
2663 |
$attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
|
|
|
2664 |
break;
|
|
|
2665 |
case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
|
|
|
2666 |
$attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
|
|
|
2667 |
break;
|
|
|
2668 |
case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
|
|
|
2669 |
$attr+= unpack('Npermissions', $this->_string_shift($response, 4));
|
|
|
2670 |
// mode == permissions; permissions was the original array key and is retained for bc purposes.
|
|
|
2671 |
// mode was added because that's the more industry standard terminology
|
|
|
2672 |
$attr+= array('mode' => $attr['permissions']);
|
|
|
2673 |
$fileType = $this->_parseMode($attr['permissions']);
|
|
|
2674 |
if ($fileType !== false) {
|
|
|
2675 |
$attr+= array('type' => $fileType);
|
|
|
2676 |
}
|
|
|
2677 |
break;
|
|
|
2678 |
case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
|
|
|
2679 |
$attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
|
|
|
2680 |
break;
|
|
|
2681 |
case NET_SFTP_ATTR_EXTENDED: // 0x80000000
|
|
|
2682 |
extract(unpack('Ncount', $this->_string_shift($response, 4)));
|
|
|
2683 |
for ($i = 0; $i < $count; $i++) {
|
|
|
2684 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
2685 |
$key = $this->_string_shift($response, $length);
|
|
|
2686 |
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
|
|
2687 |
$attr[$key] = $this->_string_shift($response, $length);
|
|
|
2688 |
}
|
|
|
2689 |
}
|
|
|
2690 |
}
|
|
|
2691 |
return $attr;
|
|
|
2692 |
}
|
|
|
2693 |
|
|
|
2694 |
/**
|
|
|
2695 |
* Attempt to identify the file type
|
|
|
2696 |
*
|
|
|
2697 |
* Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
|
|
|
2698 |
*
|
|
|
2699 |
* @param int $mode
|
|
|
2700 |
* @return int
|
|
|
2701 |
* @access private
|
|
|
2702 |
*/
|
|
|
2703 |
function _parseMode($mode)
|
|
|
2704 |
{
|
|
|
2705 |
// values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
|
|
|
2706 |
// see, also, http://linux.die.net/man/2/stat
|
|
|
2707 |
switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
|
|
|
2708 |
case 0000000: // no file type specified - figure out the file type using alternative means
|
|
|
2709 |
return false;
|
|
|
2710 |
case 0040000:
|
|
|
2711 |
return NET_SFTP_TYPE_DIRECTORY;
|
|
|
2712 |
case 0100000:
|
|
|
2713 |
return NET_SFTP_TYPE_REGULAR;
|
|
|
2714 |
case 0120000:
|
|
|
2715 |
return NET_SFTP_TYPE_SYMLINK;
|
|
|
2716 |
// new types introduced in SFTPv5+
|
|
|
2717 |
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
|
|
|
2718 |
case 0010000: // named pipe (fifo)
|
|
|
2719 |
return NET_SFTP_TYPE_FIFO;
|
|
|
2720 |
case 0020000: // character special
|
|
|
2721 |
return NET_SFTP_TYPE_CHAR_DEVICE;
|
|
|
2722 |
case 0060000: // block special
|
|
|
2723 |
return NET_SFTP_TYPE_BLOCK_DEVICE;
|
|
|
2724 |
case 0140000: // socket
|
|
|
2725 |
return NET_SFTP_TYPE_SOCKET;
|
|
|
2726 |
case 0160000: // whiteout
|
|
|
2727 |
// "SPECIAL should be used for files that are of
|
|
|
2728 |
// a known type which cannot be expressed in the protocol"
|
|
|
2729 |
return NET_SFTP_TYPE_SPECIAL;
|
|
|
2730 |
default:
|
|
|
2731 |
return NET_SFTP_TYPE_UNKNOWN;
|
|
|
2732 |
}
|
|
|
2733 |
}
|
|
|
2734 |
|
|
|
2735 |
/**
|
|
|
2736 |
* Parse Longname
|
|
|
2737 |
*
|
|
|
2738 |
* SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
|
|
|
2739 |
* a file as a directory and see if an error is returned or you could try to parse the
|
|
|
2740 |
* SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
|
|
|
2741 |
* The result is returned using the
|
|
|
2742 |
* {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
|
|
|
2743 |
*
|
|
|
2744 |
* If the longname is in an unrecognized format bool(false) is returned.
|
|
|
2745 |
*
|
|
|
2746 |
* @param string $longname
|
|
|
2747 |
* @return mixed
|
|
|
2748 |
* @access private
|
|
|
2749 |
*/
|
|
|
2750 |
function _parseLongname($longname)
|
|
|
2751 |
{
|
|
|
2752 |
// http://en.wikipedia.org/wiki/Unix_file_types
|
|
|
2753 |
// http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
|
|
|
2754 |
if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
|
|
|
2755 |
switch ($longname[0]) {
|
|
|
2756 |
case '-':
|
|
|
2757 |
return NET_SFTP_TYPE_REGULAR;
|
|
|
2758 |
case 'd':
|
|
|
2759 |
return NET_SFTP_TYPE_DIRECTORY;
|
|
|
2760 |
case 'l':
|
|
|
2761 |
return NET_SFTP_TYPE_SYMLINK;
|
|
|
2762 |
default:
|
|
|
2763 |
return NET_SFTP_TYPE_SPECIAL;
|
|
|
2764 |
}
|
|
|
2765 |
}
|
|
|
2766 |
|
|
|
2767 |
return false;
|
|
|
2768 |
}
|
|
|
2769 |
|
|
|
2770 |
/**
|
|
|
2771 |
* Sends SFTP Packets
|
|
|
2772 |
*
|
|
|
2773 |
* See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
|
|
|
2774 |
*
|
|
|
2775 |
* @param int $type
|
|
|
2776 |
* @param string $data
|
|
|
2777 |
* @see self::_get_sftp_packet()
|
|
|
2778 |
* @see self::_send_channel_packet()
|
|
|
2779 |
* @return bool
|
|
|
2780 |
* @access private
|
|
|
2781 |
*/
|
|
|
2782 |
function _send_sftp_packet($type, $data)
|
|
|
2783 |
{
|
|
|
2784 |
$packet = $this->request_id !== false ?
|
|
|
2785 |
pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
|
|
|
2786 |
pack('NCa*', strlen($data) + 1, $type, $data);
|
|
|
2787 |
|
|
|
2788 |
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
|
|
|
2789 |
$result = $this->_send_channel_packet(self::CHANNEL, $packet);
|
|
|
2790 |
$stop = strtok(microtime(), ' ') + strtok('');
|
|
|
2791 |
|
|
|
2792 |
if (defined('NET_SFTP_LOGGING')) {
|
|
|
2793 |
$packet_type = '-> ' . $this->packet_types[$type] .
|
|
|
2794 |
' (' . round($stop - $start, 4) . 's)';
|
|
|
2795 |
if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
|
|
|
2796 |
echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
|
|
|
2797 |
flush();
|
|
|
2798 |
ob_flush();
|
|
|
2799 |
} else {
|
|
|
2800 |
$this->packet_type_log[] = $packet_type;
|
|
|
2801 |
if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
|
|
|
2802 |
$this->packet_log[] = $data;
|
|
|
2803 |
}
|
|
|
2804 |
}
|
|
|
2805 |
}
|
|
|
2806 |
|
|
|
2807 |
return $result;
|
|
|
2808 |
}
|
|
|
2809 |
|
|
|
2810 |
/**
|
|
|
2811 |
* Receives SFTP Packets
|
|
|
2812 |
*
|
|
|
2813 |
* See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
|
|
|
2814 |
*
|
|
|
2815 |
* Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
|
|
|
2816 |
* There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
|
|
|
2817 |
* messages containing one SFTP packet.
|
|
|
2818 |
*
|
|
|
2819 |
* @see self::_send_sftp_packet()
|
|
|
2820 |
* @return string
|
|
|
2821 |
* @access private
|
|
|
2822 |
*/
|
|
|
2823 |
function _get_sftp_packet()
|
|
|
2824 |
{
|
|
|
2825 |
$this->curTimeout = false;
|
|
|
2826 |
|
|
|
2827 |
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
|
|
|
2828 |
|
|
|
2829 |
// SFTP packet length
|
|
|
2830 |
while (strlen($this->packet_buffer) < 4) {
|
|
|
2831 |
$temp = $this->_get_channel_packet(self::CHANNEL);
|
|
|
2832 |
if (is_bool($temp)) {
|
|
|
2833 |
$this->packet_type = false;
|
|
|
2834 |
$this->packet_buffer = '';
|
|
|
2835 |
return false;
|
|
|
2836 |
}
|
|
|
2837 |
$this->packet_buffer.= $temp;
|
|
|
2838 |
}
|
|
|
2839 |
extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
|
|
|
2840 |
$tempLength = $length;
|
|
|
2841 |
$tempLength-= strlen($this->packet_buffer);
|
|
|
2842 |
|
|
|
2843 |
// SFTP packet type and data payload
|
|
|
2844 |
while ($tempLength > 0) {
|
|
|
2845 |
$temp = $this->_get_channel_packet(self::CHANNEL);
|
|
|
2846 |
if (is_bool($temp)) {
|
|
|
2847 |
$this->packet_type = false;
|
|
|
2848 |
$this->packet_buffer = '';
|
|
|
2849 |
return false;
|
|
|
2850 |
}
|
|
|
2851 |
$this->packet_buffer.= $temp;
|
|
|
2852 |
$tempLength-= strlen($temp);
|
|
|
2853 |
}
|
|
|
2854 |
|
|
|
2855 |
$stop = strtok(microtime(), ' ') + strtok('');
|
|
|
2856 |
|
|
|
2857 |
$this->packet_type = ord($this->_string_shift($this->packet_buffer));
|
|
|
2858 |
|
|
|
2859 |
if ($this->request_id !== false) {
|
|
|
2860 |
$this->_string_shift($this->packet_buffer, 4); // remove the request id
|
|
|
2861 |
$length-= 5; // account for the request id and the packet type
|
|
|
2862 |
} else {
|
|
|
2863 |
$length-= 1; // account for the packet type
|
|
|
2864 |
}
|
|
|
2865 |
|
|
|
2866 |
$packet = $this->_string_shift($this->packet_buffer, $length);
|
|
|
2867 |
|
|
|
2868 |
if (defined('NET_SFTP_LOGGING')) {
|
|
|
2869 |
$packet_type = '<- ' . $this->packet_types[$this->packet_type] .
|
|
|
2870 |
' (' . round($stop - $start, 4) . 's)';
|
|
|
2871 |
if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
|
|
|
2872 |
echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
|
|
|
2873 |
flush();
|
|
|
2874 |
ob_flush();
|
|
|
2875 |
} else {
|
|
|
2876 |
$this->packet_type_log[] = $packet_type;
|
|
|
2877 |
if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
|
|
|
2878 |
$this->packet_log[] = $packet;
|
|
|
2879 |
}
|
|
|
2880 |
}
|
|
|
2881 |
}
|
|
|
2882 |
|
|
|
2883 |
return $packet;
|
|
|
2884 |
}
|
|
|
2885 |
|
|
|
2886 |
/**
|
|
|
2887 |
* Returns a log of the packets that have been sent and received.
|
|
|
2888 |
*
|
|
|
2889 |
* Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
|
|
|
2890 |
*
|
|
|
2891 |
* @access public
|
|
|
2892 |
* @return string or Array
|
|
|
2893 |
*/
|
|
|
2894 |
function getSFTPLog()
|
|
|
2895 |
{
|
|
|
2896 |
if (!defined('NET_SFTP_LOGGING')) {
|
|
|
2897 |
return false;
|
|
|
2898 |
}
|
|
|
2899 |
|
|
|
2900 |
switch (NET_SFTP_LOGGING) {
|
|
|
2901 |
case NET_SFTP_LOG_COMPLEX:
|
|
|
2902 |
return $this->_format_log($this->packet_log, $this->packet_type_log);
|
|
|
2903 |
break;
|
|
|
2904 |
//case NET_SFTP_LOG_SIMPLE:
|
|
|
2905 |
default:
|
|
|
2906 |
return $this->packet_type_log;
|
|
|
2907 |
}
|
|
|
2908 |
}
|
|
|
2909 |
|
|
|
2910 |
/**
|
|
|
2911 |
* Returns all errors
|
|
|
2912 |
*
|
|
|
2913 |
* @return string
|
|
|
2914 |
* @access public
|
|
|
2915 |
*/
|
|
|
2916 |
function getSFTPErrors()
|
|
|
2917 |
{
|
|
|
2918 |
return $this->sftp_errors;
|
|
|
2919 |
}
|
|
|
2920 |
|
|
|
2921 |
/**
|
|
|
2922 |
* Returns the last error
|
|
|
2923 |
*
|
|
|
2924 |
* @return string
|
|
|
2925 |
* @access public
|
|
|
2926 |
*/
|
|
|
2927 |
function getLastSFTPError()
|
|
|
2928 |
{
|
|
|
2929 |
return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
|
|
|
2930 |
}
|
|
|
2931 |
|
|
|
2932 |
/**
|
|
|
2933 |
* Get supported SFTP versions
|
|
|
2934 |
*
|
|
|
2935 |
* @return array
|
|
|
2936 |
* @access public
|
|
|
2937 |
*/
|
|
|
2938 |
function getSupportedVersions()
|
|
|
2939 |
{
|
|
|
2940 |
$temp = array('version' => $this->version);
|
|
|
2941 |
if (isset($this->extensions['versions'])) {
|
|
|
2942 |
$temp['extensions'] = $this->extensions['versions'];
|
|
|
2943 |
}
|
|
|
2944 |
return $temp;
|
|
|
2945 |
}
|
|
|
2946 |
|
|
|
2947 |
/**
|
|
|
2948 |
* Disconnect
|
|
|
2949 |
*
|
|
|
2950 |
* @param int $reason
|
|
|
2951 |
* @return bool
|
|
|
2952 |
* @access private
|
|
|
2953 |
*/
|
|
|
2954 |
function _disconnect($reason)
|
|
|
2955 |
{
|
|
|
2956 |
$this->pwd = false;
|
|
|
2957 |
parent::_disconnect($reason);
|
|
|
2958 |
}
|
|
|
2959 |
}
|