]> git.mxchange.org Git - friendica.git/blob - library/phpsec/Net/SFTP.php
Merge pull request #482 from pixelroot/master
[friendica.git] / library / phpsec / Net / SFTP.php
1 <?php\r
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */\r
3 \r
4 /**\r
5  * Pure-PHP implementation of SFTP.\r
6  *\r
7  * PHP versions 4 and 5\r
8  *\r
9  * Currently only supports SFTPv3, which, according to wikipedia.org, "is the most widely used version,\r
10  * implemented by the popular OpenSSH SFTP server".  If you want SFTPv4/5/6 support, provide me with access\r
11  * to an SFTPv4/5/6 server.\r
12  *\r
13  * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.\r
14  *\r
15  * Here's a short example of how to use this library:\r
16  * <code>\r
17  * <?php\r
18  *    include('Net/SFTP.php');\r
19  *\r
20  *    $sftp = new Net_SFTP('www.domain.tld');\r
21  *    if (!$sftp->login('username', 'password')) {\r
22  *        exit('Login Failed');\r
23  *    }\r
24  *\r
25  *    echo $sftp->pwd() . "\r\n";\r
26  *    $sftp->put('filename.ext', 'hello, world!');\r
27  *    print_r($sftp->nlist());\r
28  * ?>\r
29  * </code>\r
30  *\r
31  * LICENSE: This library is free software; you can redistribute it and/or\r
32  * modify it under the terms of the GNU Lesser General Public\r
33  * License as published by the Free Software Foundation; either\r
34  * version 2.1 of the License, or (at your option) any later version.\r
35  *\r
36  * This library is distributed in the hope that it will be useful,\r
37  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
38  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
39  * Lesser General Public License for more details.\r
40  *\r
41  * You should have received a copy of the GNU Lesser General Public\r
42  * License along with this library; if not, write to the Free Software\r
43  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,\r
44  * MA  02111-1307  USA\r
45  *\r
46  * @category   Net\r
47  * @package    Net_SFTP\r
48  * @author     Jim Wigginton <terrafrost@php.net>\r
49  * @copyright  MMIX Jim Wigginton\r
50  * @license    http://www.gnu.org/licenses/lgpl.txt\r
51  * @version    $Id: SFTP.php,v 1.21 2010/04/09 02:31:34 terrafrost Exp $\r
52  * @link       http://phpseclib.sourceforge.net\r
53  */\r
54 \r
55 /**\r
56  * Include Net_SSH2\r
57  */\r
58 require_once('Net/SSH2.php');\r
59 \r
60 /**#@+\r
61  * @access public\r
62  * @see Net_SFTP::getLog()\r
63  */\r
64 /**\r
65  * Returns the message numbers\r
66  */\r
67 define('NET_SFTP_LOG_SIMPLE',  NET_SSH2_LOG_SIMPLE);\r
68 /**\r
69  * Returns the message content\r
70  */\r
71 define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);\r
72 /**#@-*/\r
73 \r
74 /**\r
75  * SFTP channel constant\r
76  *\r
77  * Net_SSH2::exec() uses 0 and Net_SSH2::interactiveRead() / Net_SSH2::interactiveWrite() use 1.\r
78  *\r
79  * @see Net_SSH2::_send_channel_packet()\r
80  * @see Net_SSH2::_get_channel_packet()\r
81  * @access private\r
82  */\r
83 define('NET_SFTP_CHANNEL', 2);\r
84 \r
85 /**#@+\r
86  * @access public\r
87  * @see Net_SFTP::put()\r
88  */\r
89 /**\r
90  * Reads data from a local file.\r
91  */\r
92 define('NET_SFTP_LOCAL_FILE', 1);\r
93 /**\r
94  * Reads data from a string.\r
95  */\r
96 define('NET_SFTP_STRING',  2);\r
97 /**#@-*/\r
98 \r
99 /**\r
100  * Pure-PHP implementations of SFTP.\r
101  *\r
102  * @author  Jim Wigginton <terrafrost@php.net>\r
103  * @version 0.1.0\r
104  * @access  public\r
105  * @package Net_SFTP\r
106  */\r
107 class Net_SFTP extends Net_SSH2 {\r
108     /**\r
109      * Packet Types\r
110      *\r
111      * @see Net_SFTP::Net_SFTP()\r
112      * @var Array\r
113      * @access private\r
114      */\r
115     var $packet_types = array();\r
116 \r
117     /**\r
118      * Status Codes\r
119      *\r
120      * @see Net_SFTP::Net_SFTP()\r
121      * @var Array\r
122      * @access private\r
123      */\r
124     var $status_codes = array();\r
125 \r
126     /**\r
127      * The Request ID\r
128      *\r
129      * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support\r
130      * concurrent actions, so it's somewhat academic, here.\r
131      *\r
132      * @var Integer\r
133      * @see Net_SFTP::_send_sftp_packet()\r
134      * @access private\r
135      */\r
136     var $request_id = false;\r
137 \r
138     /**\r
139      * The Packet Type\r
140      *\r
141      * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support\r
142      * concurrent actions, so it's somewhat academic, here.\r
143      *\r
144      * @var Integer\r
145      * @see Net_SFTP::_get_sftp_packet()\r
146      * @access private\r
147      */\r
148     var $packet_type = -1;\r
149 \r
150     /**\r
151      * Packet Buffer\r
152      *\r
153      * @var String\r
154      * @see Net_SFTP::_get_sftp_packet()\r
155      * @access private\r
156      */\r
157     var $packet_buffer = '';\r
158 \r
159     /**\r
160      * Extensions supported by the server\r
161      *\r
162      * @var Array\r
163      * @see Net_SFTP::_initChannel()\r
164      * @access private\r
165      */\r
166     var $extensions = array();\r
167 \r
168     /**\r
169      * Server SFTP version\r
170      *\r
171      * @var Integer\r
172      * @see Net_SFTP::_initChannel()\r
173      * @access private\r
174      */\r
175     var $version;\r
176 \r
177     /**\r
178      * Current working directory\r
179      *\r
180      * @var String\r
181      * @see Net_SFTP::_realpath()\r
182      * @see Net_SFTP::chdir()\r
183      * @access private\r
184      */\r
185     var $pwd = false;\r
186 \r
187     /**\r
188      * Packet Type Log\r
189      *\r
190      * @see Net_SFTP::getLog()\r
191      * @var Array\r
192      * @access private\r
193      */\r
194     var $packet_type_log = array();\r
195 \r
196     /**\r
197      * Packet Log\r
198      *\r
199      * @see Net_SFTP::getLog()\r
200      * @var Array\r
201      * @access private\r
202      */\r
203     var $packet_log = array();\r
204 \r
205     /**\r
206      * Error information\r
207      *\r
208      * @see Net_SFTP::getSFTPErrors()\r
209      * @see Net_SFTP::getLastSFTPError()\r
210      * @var String\r
211      * @access private\r
212      */\r
213     var $errors = array();\r
214 \r
215     /**\r
216      * Default Constructor.\r
217      *\r
218      * Connects to an SFTP server\r
219      *\r
220      * @param String $host\r
221      * @param optional Integer $port\r
222      * @param optional Integer $timeout\r
223      * @return Net_SFTP\r
224      * @access public\r
225      */\r
226     function Net_SFTP($host, $port = 22, $timeout = 10)\r
227     {\r
228         parent::Net_SSH2($host, $port, $timeout);\r
229         $this->packet_types = array(\r
230             1  => 'NET_SFTP_INIT',\r
231             2  => 'NET_SFTP_VERSION',\r
232             /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:\r
233                    SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1\r
234                pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */\r
235             3  => 'NET_SFTP_OPEN',\r
236             4  => 'NET_SFTP_CLOSE',\r
237             5  => 'NET_SFTP_READ',\r
238             6  => 'NET_SFTP_WRITE',\r
239             8  => 'NET_SFTP_FSTAT',\r
240             9  => 'NET_SFTP_SETSTAT',\r
241             11 => 'NET_SFTP_OPENDIR',\r
242             12 => 'NET_SFTP_READDIR',\r
243             13 => 'NET_SFTP_REMOVE',\r
244             14 => 'NET_SFTP_MKDIR',\r
245             15 => 'NET_SFTP_RMDIR',\r
246             16 => 'NET_SFTP_REALPATH',\r
247             17 => 'NET_SFTP_STAT',\r
248             /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:\r
249                    SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3\r
250                pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */\r
251             18 => 'NET_SFTP_RENAME',\r
252 \r
253             101=> 'NET_SFTP_STATUS',\r
254             102=> 'NET_SFTP_HANDLE',\r
255             /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:\r
256                    SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4\r
257                pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */\r
258             103=> 'NET_SFTP_DATA',\r
259             104=> 'NET_SFTP_NAME',\r
260             105=> 'NET_SFTP_ATTRS',\r
261 \r
262             200=> 'NET_SFTP_EXTENDED'\r
263         );\r
264         $this->status_codes = array(\r
265             0 => 'NET_SFTP_STATUS_OK',\r
266             1 => 'NET_SFTP_STATUS_EOF',\r
267             2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',\r
268             3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',\r
269             4 => 'NET_SFTP_STATUS_FAILURE',\r
270             5 => 'NET_SFTP_STATUS_BAD_MESSAGE',\r
271             6 => 'NET_SFTP_STATUS_NO_CONNECTION',\r
272             7 => 'NET_SFTP_STATUS_CONNECTION_LOST',\r
273             8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED'\r
274         );\r
275         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1\r
276         // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why\r
277         $this->attributes = array(\r
278             0x00000001 => 'NET_SFTP_ATTR_SIZE',\r
279             0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+\r
280             0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',\r
281             0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',\r
282                     -1 => 'NET_SFTP_ATTR_EXTENDED' // unpack('N', "\xFF\xFF\xFF\xFF") == array(1 => int(-1))\r
283         );\r
284         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3\r
285         // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name\r
286         // the array for that $this->open5_flags and similarily alter the constant names.\r
287         $this->open_flags = array(\r
288             0x00000001 => 'NET_SFTP_OPEN_READ',\r
289             0x00000002 => 'NET_SFTP_OPEN_WRITE',\r
290             0x00000008 => 'NET_SFTP_OPEN_CREATE',\r
291             0x00000010 => 'NET_SFTP_OPEN_TRUNCATE'\r
292         );\r
293         $this->_define_array(\r
294             $this->packet_types,\r
295             $this->status_codes,\r
296             $this->attributes,\r
297             $this->open_flags\r
298         );\r
299     }\r
300 \r
301     /**\r
302      * Login\r
303      *\r
304      * @param String $username\r
305      * @param optional String $password\r
306      * @return Boolean\r
307      * @access public\r
308      */\r
309     function login($username, $password = '')\r
310     {\r
311         if (!parent::login($username, $password)) {\r
312             return false;\r
313         }\r
314 \r
315         $this->window_size_client_to_server[NET_SFTP_CHANNEL] = $this->window_size;\r
316 \r
317         $packet = pack('CNa*N3',\r
318             NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);\r
319 \r
320         if (!$this->_send_binary_packet($packet)) {\r
321             return false;\r
322         }\r
323 \r
324         $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;\r
325 \r
326         $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);\r
327         if ($response === false) {\r
328             return false;\r
329         }\r
330 \r
331         $packet = pack('CNNa*CNa*',\r
332             NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');\r
333         if (!$this->_send_binary_packet($packet)) {\r
334             return false;\r
335         }\r
336 \r
337         $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;\r
338 \r
339         $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);\r
340         if ($response === false) {\r
341             return false;\r
342         }\r
343 \r
344         $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;\r
345 \r
346         if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {\r
347             return false;\r
348         }\r
349 \r
350         $response = $this->_get_sftp_packet();\r
351         if ($this->packet_type != NET_SFTP_VERSION) {\r
352             user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE);\r
353             return false;\r
354         }\r
355 \r
356         extract(unpack('Nversion', $this->_string_shift($response, 4)));\r
357         $this->version = $version;\r
358         while (!empty($response)) {\r
359             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
360             $key = $this->_string_shift($response, $length);\r
361             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
362             $value = $this->_string_shift($response, $length);\r
363             $this->extensions[$key] = $value;\r
364         }\r
365 \r
366         /*\r
367          SFTPv4+ defines a 'newline' extension.  SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',\r
368          however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's\r
369          not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for\r
370          one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that\r
371          'newline@vandyke.com' would.\r
372         */\r
373         /*\r
374         if (isset($this->extensions['newline@vandyke.com'])) {\r
375             $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];\r
376             unset($this->extensions['newline@vandyke.com']);\r
377         }\r
378         */\r
379 \r
380         $this->request_id = 1;\r
381 \r
382         /*\r
383          A Note on SFTPv4/5/6 support:\r
384          <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:\r
385 \r
386          "If the client wishes to interoperate with servers that support noncontiguous version\r
387           numbers it SHOULD send '3'"\r
388 \r
389          Given that the server only sends its version number after the client has already done so, the above\r
390          seems to be suggesting that v3 should be the default version.  This makes sense given that v3 is the\r
391          most popular.\r
392 \r
393          <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;\r
394 \r
395          "If the server did not send the "versions" extension, or the version-from-list was not included, the\r
396           server MAY send a status response describing the failure, but MUST then close the channel without\r
397           processing any further requests."\r
398 \r
399          So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and\r
400          a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4?  If it only implements\r
401          v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed\r
402          in draft-ietf-secsh-filexfer-13 would be quite impossible.  As such, what Net_SFTP would do is close the\r
403          channel and reopen it with a new and updated SSH_FXP_INIT packet.\r
404         */\r
405         if ($this->version != 3) {\r
406             return false;\r
407         }\r
408 \r
409         $this->pwd = $this->_realpath('.');\r
410 \r
411         return true;\r
412     }\r
413 \r
414     /**\r
415      * Returns the current directory name\r
416      *\r
417      * @return Mixed\r
418      * @access public\r
419      */\r
420     function pwd()\r
421     {\r
422         return $this->pwd;\r
423     }\r
424 \r
425     /**\r
426      * Canonicalize the Server-Side Path Name\r
427      *\r
428      * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it.  Returns\r
429      * the absolute (canonicalized) path.  If $mode is set to NET_SFTP_CONFIRM_DIR (as opposed to NET_SFTP_CONFIRM_NONE,\r
430      * which is what it is set to by default), false is returned if $dir is not a valid directory.\r
431      *\r
432      * @see Net_SFTP::chdir()\r
433      * @param String $dir\r
434      * @param optional Integer $mode\r
435      * @return Mixed\r
436      * @access private\r
437      */\r
438     function _realpath($dir)\r
439     {\r
440         /*\r
441         "This protocol represents file names as strings.  File names are\r
442          assumed to use the slash ('/') character as a directory separator.\r
443 \r
444          File names starting with a slash are "absolute", and are relative to\r
445          the root of the file system.  Names starting with any other character\r
446          are relative to the user's default directory (home directory).  Note\r
447          that identifying the user is assumed to take place outside of this\r
448          protocol."\r
449 \r
450          -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6\r
451         */\r
452         $file = '';\r
453         if ($this->pwd !== false) {\r
454             // if the SFTP server returned the canonicalized path even for non-existant files this wouldn't be necessary\r
455             // on OpenSSH it isn't necessary but on other SFTP servers it is.  that and since the specs say nothing on\r
456             // the subject, we'll go ahead and work around it with the following.\r
457             if ($dir[strlen($dir) - 1] != '/') {\r
458                 $file = basename($dir);\r
459                 $dir = dirname($dir);\r
460             }\r
461 \r
462             if ($dir == '.' || $dir == $this->pwd) {\r
463                 return $this->pwd . $file;\r
464             }\r
465 \r
466             if ($dir[0] != '/') {\r
467                 $dir = $this->pwd . '/' . $dir;\r
468             }\r
469             // on the surface it seems like maybe resolving a path beginning with / is unnecessary, but such paths\r
470             // can contain .'s and ..'s just like any other.  we could parse those out as appropriate or we can let\r
471             // the server do it.  we'll do the latter.\r
472         }\r
473 \r
474         /*\r
475          that SSH_FXP_REALPATH returns SSH_FXP_NAME does not necessarily mean that anything actually exists at the\r
476          specified path.  generally speaking, no attributes are returned with this particular SSH_FXP_NAME packet\r
477          regardless of whether or not a file actually exists.  and in SFTPv3, the longname field and the filename\r
478          field match for this particular SSH_FXP_NAME packet.  for other SSH_FXP_NAME packets, this will likely\r
479          not be the case, but for this one, it is.\r
480         */\r
481         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9\r
482         if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($dir), $dir))) {\r
483             return false;\r
484         }\r
485 \r
486         $response = $this->_get_sftp_packet();\r
487         switch ($this->packet_type) {\r
488             case NET_SFTP_NAME:\r
489                 // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following\r
490                 // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks\r
491                 // at is the first part and that part is defined the same in SFTP versions 3 through 6.\r
492                 $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway\r
493                 extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
494                 $realpath = $this->_string_shift($response, $length);\r
495                 break;\r
496             case NET_SFTP_STATUS:\r
497                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
498                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
499                 return false;\r
500             default:\r
501                 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);\r
502                 return false;\r
503         }\r
504 \r
505         // if $this->pwd isn't set than the only thing $realpath could be is for '.', which is pretty much guaranteed to\r
506         // be a bonafide directory\r
507         return $realpath . '/' . $file;\r
508     }\r
509 \r
510     /**\r
511      * Changes the current directory\r
512      *\r
513      * @param String $dir\r
514      * @return Boolean\r
515      * @access public\r
516      */\r
517     function chdir($dir)\r
518     {\r
519         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
520             return false;\r
521         }\r
522 \r
523         if ($dir[strlen($dir) - 1] != '/') {\r
524             $dir.= '/';\r
525         }\r
526         $dir = $this->_realpath($dir);\r
527 \r
528         // confirm that $dir is, in fact, a valid directory\r
529         if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {\r
530             return false;\r
531         }\r
532 \r
533         // see Net_SFTP::nlist() for a more thorough explanation of the following\r
534         $response = $this->_get_sftp_packet();\r
535         switch ($this->packet_type) {\r
536             case NET_SFTP_HANDLE:\r
537                 $handle = substr($response, 4);\r
538                 break;\r
539             case NET_SFTP_STATUS:\r
540                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
541                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
542                 return false;\r
543             default:\r
544                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);\r
545                 return false;\r
546         }\r
547 \r
548         if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {\r
549             return false;\r
550         }\r
551 \r
552         $response = $this->_get_sftp_packet();\r
553         if ($this->packet_type != NET_SFTP_STATUS) {\r
554             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
555             return false;\r
556         }\r
557 \r
558         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
559         if ($status != NET_SFTP_STATUS_OK) {\r
560             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
561             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
562             return false;\r
563         }\r
564 \r
565         $this->pwd = $dir;\r
566         return true;\r
567     }\r
568 \r
569     /**\r
570      * Returns a list of files in the given directory\r
571      *\r
572      * @param optional String $dir\r
573      * @return Mixed\r
574      * @access public\r
575      */\r
576     function nlist($dir = '.')\r
577     {\r
578         return $this->_list($dir, false);\r
579     }\r
580 \r
581     /**\r
582      * Returns a list of files in the given directory\r
583      *\r
584      * @param optional String $dir\r
585      * @return Mixed\r
586      * @access public\r
587      */\r
588     function rawlist($dir = '.')\r
589     {\r
590         return $this->_list($dir, true);\r
591     }\r
592 \r
593     function _list($dir, $raw = true)\r
594     {\r
595         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
596             return false;\r
597         }\r
598 \r
599         $dir = $this->_realpath($dir);\r
600         if ($dir === false) {\r
601             return false;\r
602         }\r
603 \r
604         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2\r
605         if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {\r
606             return false;\r
607         }\r
608 \r
609         $response = $this->_get_sftp_packet();\r
610         switch ($this->packet_type) {\r
611             case NET_SFTP_HANDLE:\r
612                 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2\r
613                 // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that\r
614                 // represent the length of the string and leave it at that\r
615                 $handle = substr($response, 4);\r
616                 break;\r
617             case NET_SFTP_STATUS:\r
618                 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED\r
619                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
620                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
621                 return false;\r
622             default:\r
623                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);\r
624                 return false;\r
625         }\r
626 \r
627         $contents = array();\r
628         while (true) {\r
629             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2\r
630             // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many\r
631             // SSH_MSG_CHANNEL_DATA messages is not known to me.\r
632             if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {\r
633                 return false;\r
634             }\r
635 \r
636             $response = $this->_get_sftp_packet();\r
637             switch ($this->packet_type) {\r
638                 case NET_SFTP_NAME:\r
639                     extract(unpack('Ncount', $this->_string_shift($response, 4)));\r
640                     for ($i = 0; $i < $count; $i++) {\r
641                         extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
642                         $shortname = $this->_string_shift($response, $length);\r
643                         extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
644                         $this->_string_shift($response, $length); // SFTPv4+ drop this field - the "longname" field\r
645                         $attributes = $this->_parseAttributes($response); // we also don't care about the attributes\r
646                         if (!$raw) {\r
647                             $contents[] = $shortname;\r
648                         } else {\r
649                             $contents[$shortname] = $attributes;\r
650                         }\r
651                         // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the\r
652                         // final SSH_FXP_STATUS packet should tell us that, already.\r
653                     }\r
654                     break;\r
655                 case NET_SFTP_STATUS:\r
656                     extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
657                     if ($status != NET_SFTP_STATUS_EOF) {\r
658                         extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
659                         $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
660                         return false;\r
661                     }\r
662                     break 2;\r
663                 default:\r
664                     user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);\r
665                     return false;\r
666             }\r
667         }\r
668 \r
669         if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {\r
670             return false;\r
671         }\r
672 \r
673         // "The client MUST release all resources associated with the handle regardless of the status."\r
674         //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3\r
675         $response = $this->_get_sftp_packet();\r
676         if ($this->packet_type != NET_SFTP_STATUS) {\r
677             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
678             return false;\r
679         }\r
680 \r
681         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
682         if ($status != NET_SFTP_STATUS_OK) {\r
683             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
684             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
685             return false;\r
686         }\r
687 \r
688         return $contents;\r
689     }\r
690 \r
691     /**\r
692      * Returns the file size, in bytes, or false, on failure\r
693      *\r
694      * Files larger than 4GB will show up as being exactly 4GB.\r
695      *\r
696      * @param optional String $dir\r
697      * @return Mixed\r
698      * @access public\r
699      */\r
700     function size($filename)\r
701     {\r
702         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
703             return false;\r
704         }\r
705 \r
706         $filename = $this->_realpath($filename);\r
707         if ($filename === false) {\r
708             return false;\r
709         }\r
710 \r
711         // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:\r
712         $packet = pack('Na*', strlen($filename), $filename);\r
713         if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {\r
714             return false;\r
715         }\r
716 \r
717         $response = $this->_get_sftp_packet();\r
718         switch ($this->packet_type) {\r
719             case NET_SFTP_ATTRS:\r
720                 $attrs = $this->_parseAttributes($response);\r
721                 return $attrs['size'];\r
722             case NET_SFTP_STATUS:\r
723                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
724                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
725                 return false;\r
726         }\r
727 \r
728         user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);\r
729         return false;\r
730     }\r
731 \r
732     /**\r
733      * Set permissions on a file.\r
734      *\r
735      * Returns the new file permissions on success or FALSE on error.\r
736      *\r
737      * @param Integer $mode\r
738      * @param String $filename\r
739      * @return Mixed\r
740      * @access public\r
741      */\r
742     function chmod($mode, $filename)\r
743     {\r
744         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
745             return false;\r
746         }\r
747 \r
748         $filename = $this->_realpath($filename);\r
749         if ($filename === false) {\r
750             return false;\r
751         }\r
752 \r
753         // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to\r
754         // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.\r
755         $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);\r
756         if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {\r
757             return false;\r
758         }\r
759 \r
760         /*\r
761          "Because some systems must use separate system calls to set various attributes, it is possible that a failure \r
762           response will be returned, but yet some of the attributes may be have been successfully modified.  If possible,\r
763           servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."\r
764 \r
765           -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6\r
766         */\r
767         $response = $this->_get_sftp_packet();\r
768         if ($this->packet_type != NET_SFTP_STATUS) {\r
769             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
770             return false;\r
771         }\r
772 \r
773         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
774         if ($status != NET_SFTP_STATUS_EOF) {\r
775             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
776             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
777         }\r
778 \r
779         // rather than return what the permissions *should* be, we'll return what they actually are.  this will also\r
780         // tell us if the file actually exists.\r
781         // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:\r
782         $packet = pack('Na*', strlen($filename), $filename);\r
783         if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {\r
784             return false;\r
785         }\r
786 \r
787         $response = $this->_get_sftp_packet();\r
788         switch ($this->packet_type) {\r
789             case NET_SFTP_ATTRS:\r
790                 $attrs = $this->_parseAttributes($response);\r
791                 return $attrs['permissions'];\r
792             case NET_SFTP_STATUS:\r
793                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
794                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
795                 return false;\r
796         }\r
797 \r
798         user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);\r
799         return false;\r
800     }\r
801 \r
802     /**\r
803      * Creates a directory.\r
804      *\r
805      * @param String $dir\r
806      * @return Boolean\r
807      * @access public\r
808      */\r
809     function mkdir($dir)\r
810     {\r
811         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
812             return false;\r
813         }\r
814 \r
815         $dir = $this->_realpath(rtrim($dir, '/'));\r
816         if ($dir === false) {\r
817             return false;\r
818         }\r
819 \r
820         // by not providing any permissions, hopefully the server will use the logged in users umask - their \r
821         // default permissions.\r
822         if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*N', strlen($dir), $dir, 0))) {\r
823             return false;\r
824         }\r
825 \r
826         $response = $this->_get_sftp_packet();\r
827         if ($this->packet_type != NET_SFTP_STATUS) {\r
828             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
829             return false;\r
830         }\r
831 \r
832         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
833         if ($status != NET_SFTP_STATUS_OK) {\r
834             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
835             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
836             return false;\r
837         }\r
838 \r
839         return true;\r
840     }\r
841 \r
842     /**\r
843      * Removes a directory.\r
844      *\r
845      * @param String $dir\r
846      * @return Boolean\r
847      * @access public\r
848      */\r
849     function rmdir($dir)\r
850     {\r
851         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
852             return false;\r
853         }\r
854 \r
855         $dir = $this->_realpath($dir);\r
856         if ($dir === false) {\r
857             return false;\r
858         }\r
859 \r
860         if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {\r
861             return false;\r
862         }\r
863 \r
864         $response = $this->_get_sftp_packet();\r
865         if ($this->packet_type != NET_SFTP_STATUS) {\r
866             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
867             return false;\r
868         }\r
869 \r
870         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
871         if ($status != NET_SFTP_STATUS_OK) {\r
872             // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?\r
873             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
874             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
875             return false;\r
876         }\r
877 \r
878         return true;\r
879     }\r
880 \r
881     /**\r
882      * Uploads a file to the SFTP server.\r
883      *\r
884      * By default, Net_SFTP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.\r
885      * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes\r
886      * long, containing 'filename.ext' as its contents.\r
887      *\r
888      * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior.  With NET_SFTP_LOCAL_FILE, $remote_file will \r
889      * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how\r
890      * large $remote_file will be, as well.\r
891      *\r
892      * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take\r
893      * care of that, yourself.\r
894      *\r
895      * @param String $remote_file\r
896      * @param String $data\r
897      * @param optional Integer $flags\r
898      * @return Boolean\r
899      * @access public\r
900      * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().\r
901      */\r
902     function put($remote_file, $data, $mode = NET_SFTP_STRING)\r
903     {\r
904         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
905             return false;\r
906         }\r
907 \r
908         $remote_file = $this->_realpath($remote_file);\r
909         if ($remote_file === false) {\r
910             return false;\r
911         }\r
912 \r
913         $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_TRUNCATE, 0);\r
914         if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {\r
915             return false;\r
916         }\r
917 \r
918         $response = $this->_get_sftp_packet();\r
919         switch ($this->packet_type) {\r
920             case NET_SFTP_HANDLE:\r
921                 $handle = substr($response, 4);\r
922                 break;\r
923             case NET_SFTP_STATUS:\r
924                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
925                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
926                 return false;\r
927             default:\r
928                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);\r
929                 return false;\r
930         }\r
931 \r
932         $initialize = true;\r
933 \r
934         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3\r
935         if ($mode == NET_SFTP_LOCAL_FILE) {\r
936             if (!is_file($data)) {\r
937                 user_error("$data is not a valid file", E_USER_NOTICE);\r
938                 return false;\r
939             }\r
940             $fp = fopen($data, 'rb');\r
941             if (!$fp) {\r
942                 return false;\r
943             }\r
944             $sent = 0;\r
945             $size = filesize($data);\r
946         } else {\r
947             $sent = 0;\r
948             $size = strlen($data);\r
949         }\r
950 \r
951         $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;\r
952 \r
953         $sftp_packet_size = 34000; // PuTTY uses 4096\r
954         $i = 0;\r
955         while ($sent < $size) {\r
956             $temp = $mode == NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);\r
957             $packet = pack('Na*N3a*', strlen($handle), $handle, 0, $sent, strlen($temp), $temp);\r
958             if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {\r
959                 fclose($fp);\r
960                 return false;\r
961             }\r
962             $sent+= strlen($temp);\r
963 \r
964             $i++;\r
965 \r
966             if ($i == 50) {\r
967                 if (!$this->_read_put_responses($i)) {\r
968                     $i = 0;\r
969                     break;\r
970                 }\r
971                 $i = 0;\r
972             }\r
973         }\r
974 \r
975         $this->_read_put_responses($i);\r
976 \r
977         if ($mode == NET_SFTP_LOCAL_FILE) {\r
978             fclose($fp);\r
979         }\r
980 \r
981         if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {\r
982             return false;\r
983         }\r
984 \r
985         $response = $this->_get_sftp_packet();\r
986         if ($this->packet_type != NET_SFTP_STATUS) {\r
987             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
988             return false;\r
989         }\r
990 \r
991         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
992         if ($status != NET_SFTP_STATUS_OK) {\r
993             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
994             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
995             return false;\r
996         }\r
997 \r
998         return true;\r
999     }\r
1000 \r
1001     /**\r
1002      * Reads multiple successive SSH_FXP_WRITE responses\r
1003      *\r
1004      * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i\r
1005      * SSH_FXP_WRITEs, in succession, and then reading $i responses.\r
1006      *\r
1007      * @param Integer $i\r
1008      * @return Boolean\r
1009      * @access private\r
1010      */\r
1011     function _read_put_responses($i)\r
1012     {\r
1013         while ($i--) {\r
1014             $response = $this->_get_sftp_packet();\r
1015             if ($this->packet_type != NET_SFTP_STATUS) {\r
1016                 user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
1017                 return false;\r
1018             }\r
1019 \r
1020             extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
1021             if ($status != NET_SFTP_STATUS_OK) {\r
1022                 extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1023                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1024                 break;\r
1025             }\r
1026         }\r
1027 \r
1028         return $i < 0;\r
1029     }\r
1030 \r
1031     /**\r
1032      * Downloads a file from the SFTP server.\r
1033      *\r
1034      * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if\r
1035      * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the\r
1036      * operation\r
1037      *\r
1038      * @param String $remote_file\r
1039      * @param optional String $local_file\r
1040      * @return Mixed\r
1041      * @access public\r
1042      */\r
1043     function get($remote_file, $local_file = false)\r
1044     {\r
1045         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
1046             return false;\r
1047         }\r
1048 \r
1049         $remote_file = $this->_realpath($remote_file);\r
1050         if ($remote_file === false) {\r
1051             return false;\r
1052         }\r
1053 \r
1054         $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);\r
1055         if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {\r
1056             return false;\r
1057         }\r
1058 \r
1059         $response = $this->_get_sftp_packet();\r
1060         switch ($this->packet_type) {\r
1061             case NET_SFTP_HANDLE:\r
1062                 $handle = substr($response, 4);\r
1063                 break;\r
1064             case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED\r
1065                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
1066                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1067                 return false;\r
1068             default:\r
1069                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);\r
1070                 return false;\r
1071         }\r
1072 \r
1073         $packet = pack('Na*', strlen($handle), $handle);\r
1074         if (!$this->_send_sftp_packet(NET_SFTP_FSTAT, $packet)) {\r
1075             return false;\r
1076         }\r
1077 \r
1078         $response = $this->_get_sftp_packet();\r
1079         switch ($this->packet_type) {\r
1080             case NET_SFTP_ATTRS:\r
1081                 $attrs = $this->_parseAttributes($response);\r
1082                 break;\r
1083             case NET_SFTP_STATUS:\r
1084                 extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
1085                 $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1086                 return false;\r
1087             default:\r
1088                 user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);\r
1089                 return false;\r
1090         }\r
1091 \r
1092         if ($local_file !== false) {\r
1093             $fp = fopen($local_file, 'wb');\r
1094             if (!$fp) {\r
1095                 return false;\r
1096             }\r
1097         } else {\r
1098             $content = '';\r
1099         }\r
1100 \r
1101         $read = 0;\r
1102         while ($read < $attrs['size']) {\r
1103             $packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 1 << 20);\r
1104             if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {\r
1105                 return false;\r
1106             }\r
1107 \r
1108             $response = $this->_get_sftp_packet();\r
1109             switch ($this->packet_type) {\r
1110                 case NET_SFTP_DATA:\r
1111                     $temp = substr($response, 4);\r
1112                     $read+= strlen($temp);\r
1113                     if ($local_file === false) {\r
1114                         $content.= $temp;\r
1115                     } else {\r
1116                         fputs($fp, $temp);\r
1117                     }\r
1118                     break;\r
1119                 case NET_SFTP_STATUS:\r
1120                     extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));\r
1121                     $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1122                     break 2;\r
1123                 default:\r
1124                     user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE);\r
1125                     return false;\r
1126             }\r
1127         }\r
1128 \r
1129         if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {\r
1130             return false;\r
1131         }\r
1132 \r
1133         $response = $this->_get_sftp_packet();\r
1134         if ($this->packet_type != NET_SFTP_STATUS) {\r
1135             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
1136             return false;\r
1137         }\r
1138 \r
1139         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
1140         if ($status != NET_SFTP_STATUS_OK) {\r
1141             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1142             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1143             return false;\r
1144         }\r
1145 \r
1146         if (isset($content)) {\r
1147             return $content;\r
1148         }\r
1149 \r
1150         fclose($fp);\r
1151         return true;\r
1152     }\r
1153 \r
1154     /**\r
1155      * Deletes a file on the SFTP server.\r
1156      *\r
1157      * @param String $path\r
1158      * @return Boolean\r
1159      * @access public\r
1160      */\r
1161     function delete($path)\r
1162     {\r
1163         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
1164             return false;\r
1165         }\r
1166 \r
1167         $remote_file = $this->_realpath($path);\r
1168         if ($path === false) {\r
1169             return false;\r
1170         }\r
1171 \r
1172         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3\r
1173         if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {\r
1174             return false;\r
1175         }\r
1176 \r
1177         $response = $this->_get_sftp_packet();\r
1178         if ($this->packet_type != NET_SFTP_STATUS) {\r
1179             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
1180             return false;\r
1181         }\r
1182 \r
1183         // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED\r
1184         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
1185         if ($status != NET_SFTP_STATUS_OK) {\r
1186             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1187             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1188             return false;\r
1189         }\r
1190 \r
1191         return true;\r
1192     }\r
1193 \r
1194     /**\r
1195      * Renames a file or a directory on the SFTP server\r
1196      *\r
1197      * @param String $oldname\r
1198      * @param String $newname\r
1199      * @return Boolean\r
1200      * @access public\r
1201      */\r
1202     function rename($oldname, $newname)\r
1203     {\r
1204         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
1205             return false;\r
1206         }\r
1207 \r
1208         $oldname = $this->_realpath($oldname);\r
1209         $newname = $this->_realpath($newname);\r
1210         if ($oldname === false || $newname === false) {\r
1211             return false;\r
1212         }\r
1213 \r
1214         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3\r
1215         $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);\r
1216         if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {\r
1217             return false;\r
1218         }\r
1219 \r
1220         $response = $this->_get_sftp_packet();\r
1221         if ($this->packet_type != NET_SFTP_STATUS) {\r
1222             user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);\r
1223             return false;\r
1224         }\r
1225 \r
1226         // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED\r
1227         extract(unpack('Nstatus', $this->_string_shift($response, 4)));\r
1228         if ($status != NET_SFTP_STATUS_OK) {\r
1229             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1230             $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);\r
1231             return false;\r
1232         }\r
1233 \r
1234         return true;\r
1235     }\r
1236 \r
1237     /**\r
1238      * Parse Attributes\r
1239      *\r
1240      * See '7.  File Attributes' of draft-ietf-secsh-filexfer-13 for more info.\r
1241      *\r
1242      * @param String $response\r
1243      * @return Array\r
1244      * @access private\r
1245      */\r
1246     function _parseAttributes(&$response)\r
1247     {\r
1248         $attr = array();\r
1249         extract(unpack('Nflags', $this->_string_shift($response, 4)));\r
1250         // SFTPv4+ have a type field (a byte) that follows the above flag field\r
1251         foreach ($this->attributes as $key => $value) {\r
1252             switch ($flags & $key) {\r
1253                 case NET_SFTP_ATTR_SIZE: // 0x00000001\r
1254                     // size is represented by a 64-bit integer, so we perhaps ought to be doing the following:\r
1255                     // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);\r
1256                     // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB\r
1257                     // (0xFFFFFFFF bytes), anyway.  as such, we'll just represent all file sizes that are bigger than\r
1258                     // 4GB as being 4GB.\r
1259                     extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));\r
1260                     if ($upper) {\r
1261                         $attr['size'] = 0xFFFFFFFF;\r
1262                     } else {\r
1263                         $attr['size'] = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;\r
1264                     }\r
1265                     break;\r
1266                 case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)\r
1267                     $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));\r
1268                     break;\r
1269                 case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004\r
1270                     $attr+= unpack('Npermissions', $this->_string_shift($response, 4));\r
1271                     break;\r
1272                 case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008\r
1273                     $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));\r
1274                     break;\r
1275                 case NET_SFTP_ATTR_EXTENDED: // 0x80000000\r
1276                     extract(unpack('Ncount', $this->_string_shift($response, 4)));\r
1277                     for ($i = 0; $i < $count; $i++) {\r
1278                         extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1279                         $key = $this->_string_shift($response, $length);\r
1280                         extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1281                         $attr[$key] = $this->_string_shift($response, $length);                        \r
1282                     }\r
1283             }\r
1284         }\r
1285         return $attr;\r
1286     }\r
1287 \r
1288     /**\r
1289      * Sends SFTP Packets\r
1290      *\r
1291      * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.\r
1292      *\r
1293      * @param Integer $type\r
1294      * @param String $data\r
1295      * @see Net_SFTP::_get_sftp_packet()\r
1296      * @see Net_SSH2::_send_channel_packet()\r
1297      * @return Boolean\r
1298      * @access private\r
1299      */\r
1300     function _send_sftp_packet($type, $data)\r
1301     {\r
1302         $packet = $this->request_id !== false ?\r
1303             pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :\r
1304             pack('NCa*',  strlen($data) + 1, $type, $data);\r
1305 \r
1306         $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838\r
1307         $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);\r
1308         $stop = strtok(microtime(), ' ') + strtok('');\r
1309 \r
1310         if (defined('NET_SFTP_LOGGING')) {\r
1311             $this->packet_type_log[] = '-> ' . $this->packet_types[$type] . \r
1312                                        ' (' . round($stop - $start, 4) . 's)';\r
1313             if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {\r
1314                 $this->packet_log[] = $data;\r
1315             }\r
1316         }\r
1317 \r
1318         return $result;\r
1319     }\r
1320 \r
1321     /**\r
1322      * Receives SFTP Packets\r
1323      *\r
1324      * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.\r
1325      *\r
1326      * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.\r
1327      * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA\r
1328      * messages containing one SFTP packet.\r
1329      *\r
1330      * @see Net_SFTP::_send_sftp_packet()\r
1331      * @return String\r
1332      * @access private\r
1333      */\r
1334     function _get_sftp_packet()\r
1335     {\r
1336         $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838\r
1337 \r
1338         // SFTP packet length\r
1339         while (strlen($this->packet_buffer) < 4) {\r
1340             $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);\r
1341             if (is_bool($temp)) {\r
1342                 $this->packet_type = false;\r
1343                 $this->packet_buffer = '';\r
1344                 return false;\r
1345             }\r
1346             $this->packet_buffer.= $temp;\r
1347         }\r
1348         extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));\r
1349         $tempLength = $length;\r
1350         $tempLength-= strlen($this->packet_buffer);\r
1351 \r
1352         // SFTP packet type and data payload\r
1353         while ($tempLength > 0) {\r
1354             $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);\r
1355             if (is_bool($temp)) {\r
1356                 $this->packet_type = false;\r
1357                 $this->packet_buffer = '';\r
1358                 return false;\r
1359             }\r
1360             $this->packet_buffer.= $temp;\r
1361             $tempLength-= strlen($temp);\r
1362         }\r
1363 \r
1364         $stop = strtok(microtime(), ' ') + strtok('');\r
1365 \r
1366         $this->packet_type = ord($this->_string_shift($this->packet_buffer));\r
1367 \r
1368         if ($this->request_id !== false) {\r
1369             $this->_string_shift($this->packet_buffer, 4); // remove the request id\r
1370             $length-= 5; // account for the request id and the packet type\r
1371         } else {\r
1372             $length-= 1; // account for the packet type\r
1373         }\r
1374 \r
1375         $packet = $this->_string_shift($this->packet_buffer, $length);\r
1376 \r
1377         if (defined('NET_SFTP_LOGGING')) {\r
1378             $this->packet_type_log[] = '<- ' . $this->packet_types[$this->packet_type] . \r
1379                                        ' (' . round($stop - $start, 4) . 's)';\r
1380             if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {\r
1381                 $this->packet_log[] = $packet;\r
1382             }\r
1383         }\r
1384 \r
1385         return $packet;\r
1386     }\r
1387 \r
1388     /**\r
1389      * Returns a log of the packets that have been sent and received.\r
1390      *\r
1391      * 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')\r
1392      *\r
1393      * @access public\r
1394      * @return String or Array\r
1395      */\r
1396     function getSFTPLog()\r
1397     {\r
1398         if (!defined('NET_SFTP_LOGGING')) {\r
1399             return false;\r
1400         }\r
1401 \r
1402         switch (NET_SFTP_LOGGING) {\r
1403             case NET_SFTP_LOG_COMPLEX:\r
1404                 return $this->_format_log($this->packet_log, $this->packet_type_log);\r
1405                 break;\r
1406             //case NET_SFTP_LOG_SIMPLE:\r
1407             default:\r
1408                 return $this->packet_type_log;\r
1409         }\r
1410     }\r
1411 \r
1412     /**\r
1413      * Returns all errors\r
1414      *\r
1415      * @return String\r
1416      * @access public\r
1417      */\r
1418     function getSFTPErrors()\r
1419     {\r
1420         return $this->sftp_errors;\r
1421     }\r
1422 \r
1423     /**\r
1424      * Returns the last error\r
1425      *\r
1426      * @return String\r
1427      * @access public\r
1428      */\r
1429     function getLastSFTPError()\r
1430     {\r
1431         return $this->sftp_errors[count($this->sftp_errors) - 1];\r
1432     }\r
1433 \r
1434     /**\r
1435      * Get supported SFTP versions\r
1436      *\r
1437      * @return Array\r
1438      * @access public\r
1439      */\r
1440     function getSupportedVersions()\r
1441     {\r
1442         $temp = array('version' => $this->version);\r
1443         if (isset($this->extensions['versions'])) {\r
1444             $temp['extensions'] = $this->extensions['versions'];\r
1445         }\r
1446         return $temp;\r
1447     }\r
1448 \r
1449     /**\r
1450      * Disconnect\r
1451      *\r
1452      * @param Integer $reason\r
1453      * @return Boolean\r
1454      * @access private\r
1455      */\r
1456     function _disconnect($reason)\r
1457     {\r
1458         $this->pwd = false;\r
1459         parent::_disconnect($reason);\r
1460     }\r
1461 }