SFTP.php 87 KB

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