SFTP.php 93 KB

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