SFTP.php 96 KB

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