SFTP.php 100 KB

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