sftp.js 85 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967
  1. // TODO: support EXTENDED request packets
  2. var TransformStream = require('stream').Transform;
  3. var ReadableStream = require('stream').Readable;
  4. var WritableStream = require('stream').Writable;
  5. var constants = require('fs').constants || process.binding('constants');
  6. var util = require('util');
  7. var inherits = util.inherits;
  8. var isDate = util.isDate;
  9. var listenerCount = require('events').EventEmitter.listenerCount;
  10. var fs = require('fs');
  11. var readString = require('./utils').readString;
  12. var readInt = require('./utils').readInt;
  13. var ATTR = {
  14. SIZE: 0x00000001,
  15. UIDGID: 0x00000002,
  16. PERMISSIONS: 0x00000004,
  17. ACMODTIME: 0x00000008,
  18. EXTENDED: 0x80000000
  19. };
  20. var STATUS_CODE = {
  21. OK: 0,
  22. EOF: 1,
  23. NO_SUCH_FILE: 2,
  24. PERMISSION_DENIED: 3,
  25. FAILURE: 4,
  26. BAD_MESSAGE: 5,
  27. NO_CONNECTION: 6,
  28. CONNECTION_LOST: 7,
  29. OP_UNSUPPORTED: 8
  30. };
  31. Object.keys(STATUS_CODE).forEach(function(key) {
  32. STATUS_CODE[STATUS_CODE[key]] = key;
  33. });
  34. var STATUS_CODE_STR = {
  35. 0: 'No error',
  36. 1: 'End of file',
  37. 2: 'No such file or directory',
  38. 3: 'Permission denied',
  39. 4: 'Failure',
  40. 5: 'Bad message',
  41. 6: 'No connection',
  42. 7: 'Connection lost',
  43. 8: 'Operation unsupported'
  44. };
  45. SFTPStream.STATUS_CODE = STATUS_CODE;
  46. var REQUEST = {
  47. INIT: 1,
  48. OPEN: 3,
  49. CLOSE: 4,
  50. READ: 5,
  51. WRITE: 6,
  52. LSTAT: 7,
  53. FSTAT: 8,
  54. SETSTAT: 9,
  55. FSETSTAT: 10,
  56. OPENDIR: 11,
  57. READDIR: 12,
  58. REMOVE: 13,
  59. MKDIR: 14,
  60. RMDIR: 15,
  61. REALPATH: 16,
  62. STAT: 17,
  63. RENAME: 18,
  64. READLINK: 19,
  65. SYMLINK: 20,
  66. EXTENDED: 200
  67. };
  68. Object.keys(REQUEST).forEach(function(key) {
  69. REQUEST[REQUEST[key]] = key;
  70. });
  71. var RESPONSE = {
  72. VERSION: 2,
  73. STATUS: 101,
  74. HANDLE: 102,
  75. DATA: 103,
  76. NAME: 104,
  77. ATTRS: 105,
  78. EXTENDED: 201
  79. };
  80. Object.keys(RESPONSE).forEach(function(key) {
  81. RESPONSE[RESPONSE[key]] = key;
  82. });
  83. var OPEN_MODE = {
  84. READ: 0x00000001,
  85. WRITE: 0x00000002,
  86. APPEND: 0x00000004,
  87. CREAT: 0x00000008,
  88. TRUNC: 0x00000010,
  89. EXCL: 0x00000020
  90. };
  91. SFTPStream.OPEN_MODE = OPEN_MODE;
  92. var MAX_PKT_LEN = 34000;
  93. var MAX_REQID = Math.pow(2, 32) - 1;
  94. var CLIENT_VERSION_BUFFER = new Buffer([0, 0, 0, 5 /* length */,
  95. REQUEST.INIT,
  96. 0, 0, 0, 3 /* version */]);
  97. var SERVER_VERSION_BUFFER = new Buffer([0, 0, 0, 5 /* length */,
  98. RESPONSE.VERSION,
  99. 0, 0, 0, 3 /* version */]);
  100. /*
  101. http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02:
  102. The maximum size of a packet is in practice determined by the client
  103. (the maximum size of read or write requests that it sends, plus a few
  104. bytes of packet overhead). All servers SHOULD support packets of at
  105. least 34000 bytes (where the packet size refers to the full length,
  106. including the header above). This should allow for reads and writes
  107. of at most 32768 bytes.
  108. OpenSSH caps this to 256kb instead of the ~34kb as mentioned in the sftpv3
  109. spec.
  110. */
  111. var RE_OPENSSH = /^SSH-2.0-(?:OpenSSH|dropbear)/;
  112. var OPENSSH_MAX_DATA_LEN = (256 * 1024) - (2 * 1024)/*account for header data*/;
  113. function DEBUG_NOOP(msg) {}
  114. function SFTPStream(cfg, remoteIdentRaw) {
  115. if (typeof cfg === 'string' && !remoteIdentRaw) {
  116. remoteIdentRaw = cfg;
  117. cfg = undefined;
  118. }
  119. if (typeof cfg !== 'object' || !cfg)
  120. cfg = {};
  121. TransformStream.call(this, {
  122. highWaterMark: (typeof cfg.highWaterMark === 'number'
  123. ? cfg.highWaterMark
  124. : 32 * 1024)
  125. });
  126. this.debug = (typeof cfg.debug === 'function' ? cfg.debug : DEBUG_NOOP);
  127. this.server = (cfg.server ? true : false);
  128. this._isOpenSSH = (remoteIdentRaw && RE_OPENSSH.test(remoteIdentRaw));
  129. this._needContinue = false;
  130. this._state = {
  131. // common
  132. status: 'packet_header',
  133. writeReqid: -1,
  134. pktLeft: undefined,
  135. pktHdrBuf: new Buffer(9), // room for pktLen + pktType + req id
  136. pktBuf: undefined,
  137. pktType: undefined,
  138. version: undefined,
  139. extensions: {},
  140. // client
  141. maxDataLen: (this._isOpenSSH ? OPENSSH_MAX_DATA_LEN : 32768),
  142. requests: {}
  143. };
  144. var self = this;
  145. this.on('end', function() {
  146. self.readable = false;
  147. }).on('finish', onFinish)
  148. .on('prefinish', onFinish);
  149. function onFinish() {
  150. self.writable = false;
  151. self._cleanup(false);
  152. }
  153. if (!this.server)
  154. this.push(CLIENT_VERSION_BUFFER);
  155. }
  156. inherits(SFTPStream, TransformStream);
  157. SFTPStream.prototype.__read = TransformStream.prototype._read;
  158. SFTPStream.prototype._read = function(n) {
  159. if (this._needContinue) {
  160. this._needContinue = false;
  161. this.emit('continue');
  162. }
  163. return this.__read(n);
  164. };
  165. SFTPStream.prototype.__push = TransformStream.prototype.push;
  166. SFTPStream.prototype.push = function(chunk, encoding) {
  167. if (!this.readable)
  168. return false;
  169. if (chunk === null)
  170. this.readable = false;
  171. var ret = this.__push(chunk, encoding);
  172. this._needContinue = (ret === false);
  173. return ret;
  174. };
  175. SFTPStream.prototype._cleanup = function(callback) {
  176. var state = this._state;
  177. state.pktBuf = undefined; // give GC something to do
  178. var requests = state.requests;
  179. var keys = Object.keys(requests);
  180. var len = keys.length;
  181. if (len) {
  182. if (this.readable) {
  183. var err = new Error('SFTP session ended early');
  184. for (var i = 0, cb; i < len; ++i)
  185. (cb = requests[keys[i]].cb) && cb(err);
  186. }
  187. state.requests = {};
  188. }
  189. if (this.readable)
  190. this.push(null);
  191. if (!this._readableState.endEmitted && !this._readableState.flowing) {
  192. // Ugh!
  193. this.resume();
  194. }
  195. if (callback !== false) {
  196. this.debug('DEBUG[SFTP]: Parser: Malformed packet');
  197. callback && callback(new Error('Malformed packet'));
  198. }
  199. };
  200. SFTPStream.prototype._transform = function(chunk, encoding, callback) {
  201. var state = this._state;
  202. var server = this.server;
  203. var status = state.status;
  204. var pktType = state.pktType;
  205. var pktBuf = state.pktBuf;
  206. var pktLeft = state.pktLeft;
  207. var version = state.version;
  208. var pktHdrBuf = state.pktHdrBuf;
  209. var requests = state.requests;
  210. var debug = this.debug;
  211. var chunkLen = chunk.length;
  212. var chunkPos = 0;
  213. var buffer;
  214. var chunkLeft;
  215. var id;
  216. while (true) {
  217. if (status === 'discard') {
  218. chunkLeft = (chunkLen - chunkPos);
  219. if (pktLeft <= chunkLeft) {
  220. chunkPos += pktLeft;
  221. pktLeft = 0;
  222. status = 'packet_header';
  223. buffer = pktBuf = undefined;
  224. } else {
  225. pktLeft -= chunkLeft;
  226. break;
  227. }
  228. } else if (pktBuf !== undefined) {
  229. chunkLeft = (chunkLen - chunkPos);
  230. if (pktLeft <= chunkLeft) {
  231. chunk.copy(pktBuf,
  232. pktBuf.length - pktLeft,
  233. chunkPos,
  234. chunkPos + pktLeft);
  235. chunkPos += pktLeft;
  236. pktLeft = 0;
  237. buffer = pktBuf;
  238. pktBuf = undefined;
  239. continue;
  240. } else {
  241. chunk.copy(pktBuf, pktBuf.length - pktLeft, chunkPos);
  242. pktLeft -= chunkLeft;
  243. break;
  244. }
  245. } else if (status === 'packet_header') {
  246. if (!buffer) {
  247. pktLeft = 5;
  248. pktBuf = pktHdrBuf;
  249. } else {
  250. // here we read the right-most 5 bytes from buffer (pktHdrBuf)
  251. pktLeft = buffer.readUInt32BE(4, true) - 1; // account for type byte
  252. pktType = buffer[8];
  253. if (server) {
  254. if (version === undefined && pktType !== REQUEST.INIT) {
  255. debug('DEBUG[SFTP]: Parser: Unexpected packet before init');
  256. this._cleanup(false);
  257. return callback(new Error('Unexpected packet before init'));
  258. } else if (version !== undefined && pktType === REQUEST.INIT) {
  259. debug('DEBUG[SFTP]: Parser: Unexpected duplicate init');
  260. status = 'bad_pkt';
  261. } else if (pktLeft > MAX_PKT_LEN) {
  262. var msg = 'Packet length ('
  263. + pktLeft
  264. + ') exceeds max length ('
  265. + MAX_PKT_LEN
  266. + ')';
  267. debug('DEBUG[SFTP]: Parser: ' + msg);
  268. this._cleanup(false);
  269. return callback(new Error(msg));
  270. } else if (pktType === REQUEST.EXTENDED) {
  271. status = 'bad_pkt';
  272. } else if (REQUEST[pktType] === undefined) {
  273. debug('DEBUG[SFTP]: Parser: Unsupported packet type: ' + pktType);
  274. status = 'discard';
  275. }
  276. } else if (version === undefined && pktType !== RESPONSE.VERSION) {
  277. debug('DEBUG[SFTP]: Parser: Unexpected packet before version');
  278. this._cleanup(false);
  279. return callback(new Error('Unexpected packet before version'));
  280. } else if (version !== undefined && pktType === RESPONSE.VERSION) {
  281. debug('DEBUG[SFTP]: Parser: Unexpected duplicate version');
  282. status = 'bad_pkt';
  283. } else if (RESPONSE[pktType] === undefined) {
  284. status = 'discard';
  285. }
  286. if (status === 'bad_pkt') {
  287. // copy original packet info
  288. pktHdrBuf.writeUInt32BE(pktLeft, 0, true);
  289. pktHdrBuf[4] = pktType;
  290. pktLeft = 4;
  291. pktBuf = pktHdrBuf;
  292. } else {
  293. pktBuf = new Buffer(pktLeft);
  294. status = 'payload';
  295. }
  296. }
  297. } else if (status === 'payload') {
  298. if (pktType === RESPONSE.VERSION || pktType === REQUEST.INIT) {
  299. /*
  300. uint32 version
  301. <extension data>
  302. */
  303. version = state.version = readInt(buffer, 0, this, callback);
  304. if (version === false)
  305. return;
  306. if (version < 3) {
  307. this._cleanup(false);
  308. return callback(new Error('Incompatible SFTP version: ' + version));
  309. } else if (server)
  310. this.push(SERVER_VERSION_BUFFER);
  311. var buflen = buffer.length;
  312. var extname;
  313. var extdata;
  314. buffer._pos = 4;
  315. while (buffer._pos < buflen) {
  316. extname = readString(buffer, buffer._pos, 'ascii', this, callback);
  317. if (extname === false)
  318. return;
  319. extdata = readString(buffer, buffer._pos, 'ascii', this, callback);
  320. if (extdata === false)
  321. return;
  322. if (state.extensions[extname])
  323. state.extensions[extname].push(extdata);
  324. else
  325. state.extensions[extname] = [ extdata ];
  326. }
  327. this.emit('ready');
  328. } else {
  329. /*
  330. All other packets (client and server) begin with a (client) request
  331. id:
  332. uint32 id
  333. */
  334. id = readInt(buffer, 0, this, callback);
  335. if (id === false)
  336. return;
  337. var filename;
  338. var attrs;
  339. var handle;
  340. var data;
  341. if (!server) {
  342. var req = requests[id];
  343. var cb = req && req.cb;
  344. debug('DEBUG[SFTP]: Parser: Response: ' + RESPONSE[pktType]);
  345. if (req && cb) {
  346. if (pktType === RESPONSE.STATUS) {
  347. /*
  348. uint32 error/status code
  349. string error message (ISO-10646 UTF-8)
  350. string language tag
  351. */
  352. var code = readInt(buffer, 4, this, callback);
  353. if (code === false)
  354. return;
  355. if (code === STATUS_CODE.OK) {
  356. cb();
  357. } else {
  358. // We borrow OpenSSH behavior here, specifically we make the
  359. // message and language fields optional, despite the
  360. // specification requiring them (even if they are empty). This
  361. // helps to avoid problems with buggy implementations that do
  362. // not fully conform to the SFTP(v3) specification.
  363. var msg;
  364. var lang = '';
  365. if (buffer.length >= 12) {
  366. msg = readString(buffer, 8, 'utf8', this, callback);
  367. if (msg === false)
  368. return;
  369. if ((buffer._pos + 4) < buffer.length) {
  370. lang = readString(buffer,
  371. buffer._pos,
  372. 'ascii',
  373. this,
  374. callback);
  375. if (lang === false)
  376. return;
  377. }
  378. }
  379. var err = new Error(msg
  380. || STATUS_CODE_STR[code]
  381. || 'Unknown status');
  382. err.code = code;
  383. err.lang = lang;
  384. cb(err);
  385. }
  386. } else if (pktType === RESPONSE.HANDLE) {
  387. /*
  388. string handle
  389. */
  390. handle = readString(buffer, 4, this, callback);
  391. if (handle === false)
  392. return;
  393. cb(undefined, handle);
  394. } else if (pktType === RESPONSE.DATA) {
  395. /*
  396. string data
  397. */
  398. if (req.buffer) {
  399. // we have already pre-allocated space to store the data
  400. var dataLen = readInt(buffer, 4, this, callback);
  401. if (dataLen === false)
  402. return;
  403. var reqBufLen = req.buffer.length;
  404. if (dataLen > reqBufLen) {
  405. // truncate response data to fit expected size
  406. buffer.writeUInt32BE(reqBufLen, 4, true);
  407. }
  408. data = readString(buffer, 4, req.buffer, this, callback);
  409. if (data === false)
  410. return;
  411. cb(undefined, data, dataLen);
  412. } else {
  413. data = readString(buffer, 4, this, callback);
  414. if (data === false)
  415. return;
  416. cb(undefined, data);
  417. }
  418. } else if (pktType === RESPONSE.NAME) {
  419. /*
  420. uint32 count
  421. repeats count times:
  422. string filename
  423. string longname
  424. ATTRS attrs
  425. */
  426. var namesLen = readInt(buffer, 4, this, callback);
  427. if (namesLen === false)
  428. return;
  429. var names = [],
  430. longname;
  431. buffer._pos = 8;
  432. for (var i = 0; i < namesLen; ++i) {
  433. // we are going to assume UTF-8 for filenames despite the SFTPv3
  434. // spec not specifying an encoding because the specs for newer
  435. // versions of the protocol all explicitly specify UTF-8 for
  436. // filenames
  437. filename = readString(buffer,
  438. buffer._pos,
  439. 'utf8',
  440. this,
  441. callback);
  442. if (filename === false)
  443. return;
  444. // `longname` only exists in SFTPv3 and since it typically will
  445. // contain the filename, we assume it is also UTF-8
  446. longname = readString(buffer,
  447. buffer._pos,
  448. 'utf8',
  449. this,
  450. callback);
  451. if (longname === false)
  452. return;
  453. attrs = readAttrs(buffer, buffer._pos, this, callback);
  454. if (attrs === false)
  455. return;
  456. names.push({
  457. filename: filename,
  458. longname: longname,
  459. attrs: attrs
  460. });
  461. }
  462. cb(undefined, names);
  463. } else if (pktType === RESPONSE.ATTRS) {
  464. /*
  465. ATTRS attrs
  466. */
  467. attrs = readAttrs(buffer, 4, this, callback);
  468. if (attrs === false)
  469. return;
  470. cb(undefined, attrs);
  471. } else if (pktType === RESPONSE.EXTENDED) {
  472. if (req.extended) {
  473. switch (req.extended) {
  474. case 'statvfs@openssh.com':
  475. case 'fstatvfs@openssh.com':
  476. /*
  477. uint64 f_bsize // file system block size
  478. uint64 f_frsize // fundamental fs block size
  479. uint64 f_blocks // number of blocks (unit f_frsize)
  480. uint64 f_bfree // free blocks in file system
  481. uint64 f_bavail // free blocks for non-root
  482. uint64 f_files // total file inodes
  483. uint64 f_ffree // free file inodes
  484. uint64 f_favail // free file inodes for to non-root
  485. uint64 f_fsid // file system id
  486. uint64 f_flag // bit mask of f_flag values
  487. uint64 f_namemax // maximum filename length
  488. */
  489. var stats = {
  490. f_bsize: undefined,
  491. f_frsize: undefined,
  492. f_blocks: undefined,
  493. f_bfree: undefined,
  494. f_bavail: undefined,
  495. f_files: undefined,
  496. f_ffree: undefined,
  497. f_favail: undefined,
  498. f_sid: undefined,
  499. f_flag: undefined,
  500. f_namemax: undefined
  501. };
  502. stats.f_bsize = readUInt64BE(buffer, 4, this, callback);
  503. if (stats.f_bsize === false)
  504. return;
  505. stats.f_frsize = readUInt64BE(buffer, 12, this, callback);
  506. if (stats.f_frsize === false)
  507. return;
  508. stats.f_blocks = readUInt64BE(buffer, 20, this, callback);
  509. if (stats.f_blocks === false)
  510. return;
  511. stats.f_bfree = readUInt64BE(buffer, 28, this, callback);
  512. if (stats.f_bfree === false)
  513. return;
  514. stats.f_bavail = readUInt64BE(buffer, 36, this, callback);
  515. if (stats.f_bavail === false)
  516. return;
  517. stats.f_files = readUInt64BE(buffer, 44, this, callback);
  518. if (stats.f_files === false)
  519. return;
  520. stats.f_ffree = readUInt64BE(buffer, 52, this, callback);
  521. if (stats.f_ffree === false)
  522. return;
  523. stats.f_favail = readUInt64BE(buffer, 60, this, callback);
  524. if (stats.f_favail === false)
  525. return;
  526. stats.f_sid = readUInt64BE(buffer, 68, this, callback);
  527. if (stats.f_sid === false)
  528. return;
  529. stats.f_flag = readUInt64BE(buffer, 76, this, callback);
  530. if (stats.f_flag === false)
  531. return;
  532. stats.f_namemax = readUInt64BE(buffer, 84, this, callback);
  533. if (stats.f_namemax === false)
  534. return;
  535. cb(undefined, stats);
  536. break;
  537. }
  538. }
  539. // XXX: at least provide the raw buffer data to the callback in
  540. // case of unexpected extended response?
  541. cb();
  542. }
  543. }
  544. if (req)
  545. delete requests[id];
  546. } else {
  547. // server
  548. var evName = REQUEST[pktType];
  549. var offset;
  550. var path;
  551. debug('DEBUG[SFTP]: Parser: Request: ' + evName);
  552. if (listenerCount(this, evName)) {
  553. if (pktType === REQUEST.OPEN) {
  554. /*
  555. string filename
  556. uint32 pflags
  557. ATTRS attrs
  558. */
  559. filename = readString(buffer, 4, 'utf8', this, callback);
  560. if (filename === false)
  561. return;
  562. var pflags = readInt(buffer, buffer._pos, this, callback);
  563. if (pflags === false)
  564. return;
  565. attrs = readAttrs(buffer, buffer._pos + 4, this, callback);
  566. if (attrs === false)
  567. return;
  568. this.emit(evName, id, filename, pflags, attrs);
  569. } else if (pktType === REQUEST.CLOSE
  570. || pktType === REQUEST.FSTAT
  571. || pktType === REQUEST.READDIR) {
  572. /*
  573. string handle
  574. */
  575. handle = readString(buffer, 4, this, callback);
  576. if (handle === false)
  577. return;
  578. this.emit(evName, id, handle);
  579. } else if (pktType === REQUEST.READ) {
  580. /*
  581. string handle
  582. uint64 offset
  583. uint32 len
  584. */
  585. handle = readString(buffer, 4, this, callback);
  586. if (handle === false)
  587. return;
  588. offset = readUInt64BE(buffer, buffer._pos, this, callback);
  589. if (offset === false)
  590. return;
  591. var len = readInt(buffer, buffer._pos, this, callback);
  592. if (len === false)
  593. return;
  594. this.emit(evName, id, handle, offset, len);
  595. } else if (pktType === REQUEST.WRITE) {
  596. /*
  597. string handle
  598. uint64 offset
  599. string data
  600. */
  601. handle = readString(buffer, 4, this, callback);
  602. if (handle === false)
  603. return;
  604. offset = readUInt64BE(buffer, buffer._pos, this, callback);
  605. if (offset === false)
  606. return;
  607. data = readString(buffer, buffer._pos, this, callback);
  608. if (data === false)
  609. return;
  610. this.emit(evName, id, handle, offset, data);
  611. } else if (pktType === REQUEST.LSTAT
  612. || pktType === REQUEST.STAT
  613. || pktType === REQUEST.OPENDIR
  614. || pktType === REQUEST.REMOVE
  615. || pktType === REQUEST.RMDIR
  616. || pktType === REQUEST.REALPATH
  617. || pktType === REQUEST.READLINK) {
  618. /*
  619. string path
  620. */
  621. path = readString(buffer, 4, 'utf8', this, callback);
  622. if (path === false)
  623. return;
  624. this.emit(evName, id, path);
  625. } else if (pktType === REQUEST.SETSTAT
  626. || pktType === REQUEST.MKDIR) {
  627. /*
  628. string path
  629. ATTRS attrs
  630. */
  631. path = readString(buffer, 4, 'utf8', this, callback);
  632. if (path === false)
  633. return;
  634. attrs = readAttrs(buffer, buffer._pos, this, callback);
  635. if (attrs === false)
  636. return;
  637. this.emit(evName, id, path, attrs);
  638. } else if (pktType === REQUEST.FSETSTAT) {
  639. /*
  640. string handle
  641. ATTRS attrs
  642. */
  643. handle = readString(buffer, 4, this, callback);
  644. if (handle === false)
  645. return;
  646. attrs = readAttrs(buffer, buffer._pos, this, callback);
  647. if (attrs === false)
  648. return;
  649. this.emit(evName, id, handle, attrs);
  650. } else if (pktType === REQUEST.RENAME
  651. || pktType === REQUEST.SYMLINK) {
  652. /*
  653. RENAME:
  654. string oldpath
  655. string newpath
  656. SYMLINK:
  657. string linkpath
  658. string targetpath
  659. */
  660. var str1;
  661. var str2;
  662. str1 = readString(buffer, 4, 'utf8', this, callback);
  663. if (str1 === false)
  664. return;
  665. str2 = readString(buffer, buffer._pos, 'utf8', this, callback);
  666. if (str2 === false)
  667. return;
  668. if (pktType === REQUEST.SYMLINK && this._isOpenSSH) {
  669. // OpenSSH has linkpath and targetpath positions switched
  670. this.emit(evName, id, str2, str1);
  671. } else
  672. this.emit(evName, id, str1, str2);
  673. }
  674. } else {
  675. // automatically reject request if no handler for request type
  676. this.status(id, STATUS_CODE.OP_UNSUPPORTED);
  677. }
  678. }
  679. }
  680. // prepare for next packet
  681. status = 'packet_header';
  682. buffer = pktBuf = undefined;
  683. } else if (status === 'bad_pkt') {
  684. if (server && buffer[4] !== REQUEST.INIT) {
  685. var errCode = (buffer[4] === REQUEST.EXTENDED
  686. ? STATUS_CODE.OP_UNSUPPORTED
  687. : STATUS_CODE.FAILURE);
  688. // no request id for init/version packets, so we have no way to send a
  689. // status response, so we just close up shop ...
  690. if (buffer[4] === REQUEST.INIT || buffer[4] === RESPONSE.VERSION)
  691. return this._cleanup(callback);
  692. id = readInt(buffer, 5, this, callback);
  693. if (id === false)
  694. return;
  695. this.status(id, errCode);
  696. }
  697. // by this point we have already read the type byte and the id bytes, so
  698. // we subtract those from the number of bytes to skip
  699. pktLeft = buffer.readUInt32BE(0, true) - 5;
  700. status = 'discard';
  701. }
  702. if (chunkPos >= chunkLen)
  703. break;
  704. }
  705. state.status = status;
  706. state.pktType = pktType;
  707. state.pktBuf = pktBuf;
  708. state.pktLeft = pktLeft;
  709. state.version = version;
  710. callback();
  711. };
  712. // client
  713. SFTPStream.prototype.createReadStream = function(path, options) {
  714. if (this.server)
  715. throw new Error('Client-only method called in server mode');
  716. return new ReadStream(this, path, options);
  717. };
  718. SFTPStream.prototype.createWriteStream = function(path, options) {
  719. if (this.server)
  720. throw new Error('Client-only method called in server mode');
  721. return new WriteStream(this, path, options);
  722. };
  723. SFTPStream.prototype.open = function(path, flags_, attrs, cb) {
  724. if (this.server)
  725. throw new Error('Client-only method called in server mode');
  726. var state = this._state;
  727. if (typeof attrs === 'function') {
  728. cb = attrs;
  729. attrs = undefined;
  730. }
  731. var flags = stringToFlags(flags_);
  732. if (flags === null)
  733. throw new Error('Unknown flags string: ' + flags_);
  734. var attrFlags = 0;
  735. var attrBytes = 0;
  736. if (typeof attrs === 'string' || typeof attrs === 'number') {
  737. attrs = { mode: attrs };
  738. }
  739. if (typeof attrs === 'object') {
  740. attrs = attrsToBytes(attrs);
  741. attrFlags = attrs.flags;
  742. attrBytes = attrs.nbytes;
  743. attrs = attrs.bytes;
  744. }
  745. /*
  746. uint32 id
  747. string filename
  748. uint32 pflags
  749. ATTRS attrs
  750. */
  751. var pathlen = Buffer.byteLength(path);
  752. var p = 9;
  753. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen + 4 + 4 + attrBytes);
  754. buf.writeUInt32BE(buf.length - 4, 0, true);
  755. buf[4] = REQUEST.OPEN;
  756. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  757. buf.writeUInt32BE(reqid, 5, true);
  758. buf.writeUInt32BE(pathlen, p, true);
  759. buf.write(path, p += 4, pathlen, 'utf8');
  760. buf.writeUInt32BE(flags, p += pathlen, true);
  761. buf.writeUInt32BE(attrFlags, p += 4, true);
  762. if (attrs && attrFlags) {
  763. p += 4;
  764. for (var i = 0, len = attrs.length; i < len; ++i)
  765. for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
  766. buf[p++] = attrs[i][j];
  767. }
  768. state.requests[reqid] = { cb: cb };
  769. this.debug('DEBUG[SFTP]: Outgoing: Writing OPEN');
  770. return this.push(buf);
  771. };
  772. SFTPStream.prototype.close = function(handle, cb) {
  773. if (this.server)
  774. throw new Error('Client-only method called in server mode');
  775. else if (!Buffer.isBuffer(handle))
  776. throw new Error('handle is not a Buffer');
  777. var state = this._state;
  778. /*
  779. uint32 id
  780. string handle
  781. */
  782. var handlelen = handle.length;
  783. var p = 9;
  784. var buf = new Buffer(4 + 1 + 4 + 4 + handlelen);
  785. buf.writeUInt32BE(buf.length - 4, 0, true);
  786. buf[4] = REQUEST.CLOSE;
  787. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  788. buf.writeUInt32BE(reqid, 5, true);
  789. buf.writeUInt32BE(handlelen, p, true);
  790. handle.copy(buf, p += 4);
  791. state.requests[reqid] = { cb: cb };
  792. this.debug('DEBUG[SFTP]: Outgoing: Writing CLOSE');
  793. return this.push(buf);
  794. };
  795. SFTPStream.prototype.readData = function(handle, buf, off, len, position, cb) {
  796. if (this.server)
  797. throw new Error('Client-only method called in server mode');
  798. else if (!Buffer.isBuffer(handle))
  799. throw new Error('handle is not a Buffer');
  800. else if (!Buffer.isBuffer(buf))
  801. throw new Error('buffer is not a Buffer');
  802. else if (off >= buf.length)
  803. throw new Error('offset is out of bounds');
  804. else if (off + len > buf.length)
  805. throw new Error('length extends beyond buffer');
  806. else if (position === null)
  807. throw new Error('null position currently unsupported');
  808. var state = this._state;
  809. /*
  810. uint32 id
  811. string handle
  812. uint64 offset
  813. uint32 len
  814. */
  815. var handlelen = handle.length;
  816. var p = 9;
  817. var pos = position;
  818. var out = new Buffer(4 + 1 + 4 + 4 + handlelen + 8 + 4);
  819. out.writeUInt32BE(out.length - 4, 0, true);
  820. out[4] = REQUEST.READ;
  821. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  822. out.writeUInt32BE(reqid, 5, true);
  823. out.writeUInt32BE(handlelen, p, true);
  824. handle.copy(out, p += 4);
  825. p += handlelen;
  826. for (var i = 7; i >= 0; --i) {
  827. out[p + i] = pos & 0xFF;
  828. pos /= 256;
  829. }
  830. out.writeUInt32BE(len, p += 8, true);
  831. state.requests[reqid] = {
  832. cb: function(err, data, nb) {
  833. if (err && err.code !== STATUS_CODE.EOF)
  834. return cb(err);
  835. cb(undefined, nb || 0, data, position);
  836. },
  837. buffer: buf.slice(off, off + len)
  838. };
  839. this.debug('DEBUG[SFTP]: Outgoing: Writing READ');
  840. return this.push(out);
  841. };
  842. SFTPStream.prototype.writeData = function(handle, buf, off, len, position, cb) {
  843. if (this.server)
  844. throw new Error('Client-only method called in server mode');
  845. else if (!Buffer.isBuffer(handle))
  846. throw new Error('handle is not a Buffer');
  847. else if (!Buffer.isBuffer(buf))
  848. throw new Error('buffer is not a Buffer');
  849. else if (off > buf.length)
  850. throw new Error('offset is out of bounds');
  851. else if (off + len > buf.length)
  852. throw new Error('length extends beyond buffer');
  853. else if (position === null)
  854. throw new Error('null position currently unsupported');
  855. var self = this;
  856. var state = this._state;
  857. if (!len) {
  858. cb && process.nextTick(function() { cb(undefined, 0); });
  859. return;
  860. }
  861. var overflow = (len > state.maxDataLen
  862. ? len - state.maxDataLen
  863. : 0);
  864. var origPosition = position;
  865. if (overflow)
  866. len = state.maxDataLen;
  867. /*
  868. uint32 id
  869. string handle
  870. uint64 offset
  871. string data
  872. */
  873. var handlelen = handle.length;
  874. var p = 9;
  875. var out = new Buffer(4 + 1 + 4 + 4 + handlelen + 8 + 4 + len);
  876. out.writeUInt32BE(out.length - 4, 0, true);
  877. out[4] = REQUEST.WRITE;
  878. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  879. out.writeUInt32BE(reqid, 5, true);
  880. out.writeUInt32BE(handlelen, p, true);
  881. handle.copy(out, p += 4);
  882. p += handlelen;
  883. for (var i = 7; i >= 0; --i) {
  884. out[p + i] = position & 0xFF;
  885. position /= 256;
  886. }
  887. out.writeUInt32BE(len, p += 8, true);
  888. buf.copy(out, p += 4, off, off + len);
  889. state.requests[reqid] = {
  890. cb: function(err) {
  891. if (err)
  892. cb && cb(err);
  893. else if (overflow) {
  894. self.writeData(handle,
  895. buf,
  896. off + len,
  897. overflow,
  898. origPosition + len,
  899. cb);
  900. } else
  901. cb && cb(undefined, off + len);
  902. }
  903. };
  904. this.debug('DEBUG[SFTP]: Outgoing: Writing WRITE');
  905. return this.push(out);
  906. };
  907. function tryCreateBuffer(size) {
  908. try {
  909. return new Buffer(size);
  910. } catch (ex) {
  911. return ex;
  912. }
  913. }
  914. function fastXfer(src, dst, srcPath, dstPath, opts, cb) {
  915. var concurrency = 64;
  916. var chunkSize = 32768;
  917. //var preserve = false;
  918. var onstep;
  919. var mode;
  920. if (typeof opts === 'function') {
  921. cb = opts;
  922. } else if (typeof opts === 'object') {
  923. if (typeof opts.concurrency === 'number'
  924. && opts.concurrency > 0
  925. && !isNaN(opts.concurrency))
  926. concurrency = opts.concurrency;
  927. if (typeof opts.chunkSize === 'number'
  928. && opts.chunkSize > 0
  929. && !isNaN(opts.chunkSize))
  930. chunkSize = opts.chunkSize;
  931. if (typeof opts.step === 'function')
  932. onstep = opts.step;
  933. //preserve = (opts.preserve ? true : false);
  934. if (typeof opts.mode === 'string' || typeof opts.mode === 'number')
  935. mode = modeNum(opts.mode);
  936. }
  937. // internal state variables
  938. var fsize;
  939. var chunk;
  940. var psrc = 0;
  941. var pdst = 0;
  942. var reads = 0;
  943. var total = 0;
  944. var hadError = false;
  945. var srcHandle;
  946. var dstHandle;
  947. var readbuf;
  948. var bufsize = chunkSize * concurrency;
  949. function onerror(err) {
  950. if (hadError)
  951. return;
  952. hadError = true;
  953. var left = 0;
  954. var cbfinal;
  955. if (srcHandle || dstHandle) {
  956. cbfinal = function() {
  957. if (--left === 0)
  958. cb(err);
  959. };
  960. if (srcHandle && (src === fs || src.writable))
  961. ++left;
  962. if (dstHandle && (dst === fs || dst.writable))
  963. ++left;
  964. if (srcHandle && (src === fs || src.writable))
  965. src.close(srcHandle, cbfinal);
  966. if (dstHandle && (dst === fs || dst.writable))
  967. dst.close(dstHandle, cbfinal);
  968. } else
  969. cb(err);
  970. }
  971. src.open(srcPath, 'r', function(err, sourceHandle) {
  972. if (err)
  973. return onerror(err);
  974. srcHandle = sourceHandle;
  975. src.fstat(srcHandle, function tryStat(err, attrs) {
  976. if (err) {
  977. if (src !== fs) {
  978. // Try stat() for sftp servers that may not support fstat() for
  979. // whatever reason
  980. src.stat(srcPath, function(err_, attrs_) {
  981. if (err_)
  982. return onerror(err);
  983. tryStat(null, attrs_);
  984. });
  985. return;
  986. }
  987. return onerror(err);
  988. }
  989. fsize = attrs.size;
  990. dst.open(dstPath, 'w', function(err, destHandle) {
  991. if (err)
  992. return onerror(err);
  993. dstHandle = destHandle;
  994. if (fsize <= 0)
  995. return onerror();
  996. // Use less memory where possible
  997. while (bufsize > fsize) {
  998. if (concurrency === 1) {
  999. bufsize = fsize;
  1000. break;
  1001. }
  1002. bufsize -= chunkSize;
  1003. --concurrency;
  1004. }
  1005. readbuf = tryCreateBuffer(bufsize);
  1006. if (readbuf instanceof Error)
  1007. return onerror(readbuf);
  1008. if (mode !== undefined) {
  1009. dst.fchmod(dstHandle, mode, function tryAgain(err) {
  1010. if (err) {
  1011. // Try chmod() for sftp servers that may not support fchmod() for
  1012. // whatever reason
  1013. dst.chmod(dstPath, mode, function(err_) {
  1014. tryAgain();
  1015. });
  1016. return;
  1017. }
  1018. read();
  1019. });
  1020. } else {
  1021. read();
  1022. }
  1023. function onread(err, nb, data, dstpos, datapos) {
  1024. if (err)
  1025. return onerror(err);
  1026. if (src === fs)
  1027. dst.writeData(dstHandle, data, datapos || 0, nb, dstpos, writeCb);
  1028. else
  1029. dst.write(dstHandle, data, datapos || 0, nb, dstpos, writeCb);
  1030. function writeCb(err) {
  1031. if (err)
  1032. return onerror(err);
  1033. total += nb;
  1034. onstep && onstep(total, nb, fsize);
  1035. if (--reads === 0) {
  1036. if (total === fsize) {
  1037. dst.close(dstHandle, function(err) {
  1038. dstHandle = undefined;
  1039. if (err)
  1040. return onerror(err);
  1041. src.close(srcHandle, function(err) {
  1042. srcHandle = undefined;
  1043. if (err)
  1044. return onerror(err);
  1045. cb();
  1046. });
  1047. });
  1048. } else
  1049. read();
  1050. }
  1051. }
  1052. }
  1053. function makeCb(psrc, pdst) {
  1054. return function(err, nb, data) {
  1055. onread(err, nb, data, pdst, psrc);
  1056. };
  1057. }
  1058. function read() {
  1059. while (pdst < fsize && reads < concurrency) {
  1060. chunk = (pdst + chunkSize > fsize ? fsize - pdst : chunkSize);
  1061. if (src === fs) {
  1062. src.read(srcHandle,
  1063. readbuf,
  1064. psrc,
  1065. chunk,
  1066. pdst,
  1067. makeCb(psrc, pdst));
  1068. } else
  1069. src.readData(srcHandle, readbuf, psrc, chunk, pdst, onread);
  1070. psrc += chunk;
  1071. pdst += chunk;
  1072. ++reads;
  1073. }
  1074. psrc = 0;
  1075. }
  1076. });
  1077. });
  1078. });
  1079. }
  1080. SFTPStream.prototype.fastGet = function(remotePath, localPath, opts, cb) {
  1081. if (this.server)
  1082. throw new Error('Client-only method called in server mode');
  1083. fastXfer(this, fs, remotePath, localPath, opts, cb);
  1084. };
  1085. SFTPStream.prototype.fastPut = function(localPath, remotePath, opts, cb) {
  1086. if (this.server)
  1087. throw new Error('Client-only method called in server mode');
  1088. fastXfer(fs, this, localPath, remotePath, opts, cb);
  1089. };
  1090. SFTPStream.prototype.readFile = function(path, options, callback_) {
  1091. if (this.server)
  1092. throw new Error('Client-only method called in server mode');
  1093. var callback;
  1094. if (typeof callback_ === 'function') {
  1095. callback = callback_;
  1096. } else if (typeof options === 'function') {
  1097. callback = options;
  1098. options = undefined;
  1099. }
  1100. var self = this;
  1101. if (typeof options === 'string')
  1102. options = { encoding: options, flag: 'r' };
  1103. else if (!options)
  1104. options = { encoding: null, flag: 'r' };
  1105. else if (typeof options !== 'object')
  1106. throw new TypeError('Bad arguments');
  1107. var encoding = options.encoding;
  1108. if (encoding && !Buffer.isEncoding(encoding))
  1109. throw new Error('Unknown encoding: ' + encoding);
  1110. // first, stat the file, so we know the size.
  1111. var size;
  1112. var buffer; // single buffer with file data
  1113. var buffers; // list for when size is unknown
  1114. var pos = 0;
  1115. var handle;
  1116. // SFTPv3 does not support using -1 for read position, so we have to track
  1117. // read position manually
  1118. var bytesRead = 0;
  1119. var flag = options.flag || 'r';
  1120. this.open(path, flag, 438 /*=0666*/, function(er, handle_) {
  1121. if (er)
  1122. return callback && callback(er);
  1123. handle = handle_;
  1124. self.fstat(handle, function tryStat(er, st) {
  1125. if (er) {
  1126. // Try stat() for sftp servers that may not support fstat() for
  1127. // whatever reason
  1128. self.stat(path, function(er_, st_) {
  1129. if (er_) {
  1130. return self.close(handle, function() {
  1131. callback && callback(er);
  1132. });
  1133. }
  1134. tryStat(null, st_);
  1135. });
  1136. return;
  1137. }
  1138. size = st.size || 0;
  1139. if (size === 0) {
  1140. // the kernel lies about many files.
  1141. // Go ahead and try to read some bytes.
  1142. buffers = [];
  1143. return read();
  1144. }
  1145. buffer = new Buffer(size);
  1146. read();
  1147. });
  1148. });
  1149. function read() {
  1150. if (size === 0) {
  1151. buffer = new Buffer(8192);
  1152. self.readData(handle, buffer, 0, 8192, bytesRead, afterRead);
  1153. } else
  1154. self.readData(handle, buffer, pos, size - pos, bytesRead, afterRead);
  1155. }
  1156. function afterRead(er, nbytes) {
  1157. if (er) {
  1158. return self.close(handle, function() {
  1159. return callback && callback(er);
  1160. });
  1161. }
  1162. if (nbytes === 0)
  1163. return close();
  1164. bytesRead += nbytes;
  1165. pos += nbytes;
  1166. if (size !== 0) {
  1167. if (pos === size)
  1168. close();
  1169. else
  1170. read();
  1171. } else {
  1172. // unknown size, just read until we don't get bytes.
  1173. buffers.push(buffer.slice(0, nbytes));
  1174. read();
  1175. }
  1176. }
  1177. function close() {
  1178. self.close(handle, function(er) {
  1179. if (size === 0) {
  1180. // collected the data into the buffers list.
  1181. buffer = Buffer.concat(buffers, pos);
  1182. } else if (pos < size)
  1183. buffer = buffer.slice(0, pos);
  1184. if (encoding)
  1185. buffer = buffer.toString(encoding);
  1186. return callback && callback(er, buffer);
  1187. });
  1188. }
  1189. };
  1190. function writeAll(self, handle, buffer, offset, length, position, callback_) {
  1191. var callback = (typeof callback_ === 'function' ? callback_ : undefined);
  1192. self.writeData(handle,
  1193. buffer,
  1194. offset,
  1195. length,
  1196. position,
  1197. function(writeErr, written) {
  1198. if (writeErr) {
  1199. return self.close(handle, function() {
  1200. callback && callback(writeErr);
  1201. });
  1202. }
  1203. if (written === length)
  1204. self.close(handle, callback);
  1205. else {
  1206. offset += written;
  1207. length -= written;
  1208. position += written;
  1209. writeAll(self, handle, buffer, offset, length, position, callback);
  1210. }
  1211. });
  1212. }
  1213. SFTPStream.prototype.writeFile = function(path, data, options, callback_) {
  1214. if (this.server)
  1215. throw new Error('Client-only method called in server mode');
  1216. var callback;
  1217. if (typeof callback_ === 'function') {
  1218. callback = callback_;
  1219. } else if (typeof options === 'function') {
  1220. callback = options;
  1221. options = undefined;
  1222. }
  1223. var self = this;
  1224. if (typeof options === 'string')
  1225. options = { encoding: options, mode: 438, flag: 'w' };
  1226. else if (!options)
  1227. options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'w' };
  1228. else if (typeof options !== 'object')
  1229. throw new TypeError('Bad arguments');
  1230. if (options.encoding && !Buffer.isEncoding(options.encoding))
  1231. throw new Error('Unknown encoding: ' + options.encoding);
  1232. var flag = options.flag || 'w';
  1233. this.open(path, flag, options.mode, function(openErr, handle) {
  1234. if (openErr)
  1235. callback && callback(openErr);
  1236. else {
  1237. var buffer = (Buffer.isBuffer(data)
  1238. ? data
  1239. : new Buffer('' + data, options.encoding || 'utf8'));
  1240. var position = (/a/.test(flag) ? null : 0);
  1241. // SFTPv3 does not support the notion of 'current position'
  1242. // (null position), so we just attempt to append to the end of the file
  1243. // instead
  1244. if (position === null) {
  1245. self.fstat(handle, function tryStat(er, st) {
  1246. if (er) {
  1247. // Try stat() for sftp servers that may not support fstat() for
  1248. // whatever reason
  1249. self.stat(path, function(er_, st_) {
  1250. if (er_) {
  1251. return self.close(handle, function() {
  1252. callback && callback(er);
  1253. });
  1254. }
  1255. tryStat(null, st_);
  1256. });
  1257. return;
  1258. }
  1259. writeAll(self, handle, buffer, 0, buffer.length, st.size, callback);
  1260. });
  1261. return;
  1262. }
  1263. writeAll(self, handle, buffer, 0, buffer.length, position, callback);
  1264. }
  1265. });
  1266. };
  1267. SFTPStream.prototype.appendFile = function(path, data, options, callback_) {
  1268. if (this.server)
  1269. throw new Error('Client-only method called in server mode');
  1270. var callback;
  1271. if (typeof callback_ === 'function') {
  1272. callback = callback_;
  1273. } else if (typeof options === 'function') {
  1274. callback = options;
  1275. options = undefined;
  1276. }
  1277. if (typeof options === 'string')
  1278. options = { encoding: options, mode: 438, flag: 'a' };
  1279. else if (!options)
  1280. options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'a' };
  1281. else if (typeof options !== 'object')
  1282. throw new TypeError('Bad arguments');
  1283. if (!options.flag)
  1284. options = util._extend({ flag: 'a' }, options);
  1285. this.writeFile(path, data, options, callback);
  1286. };
  1287. SFTPStream.prototype.exists = function(path, cb) {
  1288. if (this.server)
  1289. throw new Error('Client-only method called in server mode');
  1290. this.stat(path, function(err) {
  1291. cb && cb(err ? false : true);
  1292. });
  1293. };
  1294. SFTPStream.prototype.unlink = function(filename, cb) {
  1295. if (this.server)
  1296. throw new Error('Client-only method called in server mode');
  1297. var state = this._state;
  1298. /*
  1299. uint32 id
  1300. string filename
  1301. */
  1302. var fnamelen = Buffer.byteLength(filename);
  1303. var p = 9;
  1304. var buf = new Buffer(4 + 1 + 4 + 4 + fnamelen);
  1305. buf.writeUInt32BE(buf.length - 4, 0, true);
  1306. buf[4] = REQUEST.REMOVE;
  1307. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1308. buf.writeUInt32BE(reqid, 5, true);
  1309. buf.writeUInt32BE(fnamelen, p, true);
  1310. buf.write(filename, p += 4, fnamelen, 'utf8');
  1311. state.requests[reqid] = { cb: cb };
  1312. this.debug('DEBUG[SFTP]: Outgoing: Writing REMOVE');
  1313. return this.push(buf);
  1314. };
  1315. SFTPStream.prototype.rename = function(oldPath, newPath, cb) {
  1316. if (this.server)
  1317. throw new Error('Client-only method called in server mode');
  1318. var state = this._state;
  1319. /*
  1320. uint32 id
  1321. string oldpath
  1322. string newpath
  1323. */
  1324. var oldlen = Buffer.byteLength(oldPath);
  1325. var newlen = Buffer.byteLength(newPath);
  1326. var p = 9;
  1327. var buf = new Buffer(4 + 1 + 4 + 4 + oldlen + 4 + newlen);
  1328. buf.writeUInt32BE(buf.length - 4, 0, true);
  1329. buf[4] = REQUEST.RENAME;
  1330. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1331. buf.writeUInt32BE(reqid, 5, true);
  1332. buf.writeUInt32BE(oldlen, p, true);
  1333. buf.write(oldPath, p += 4, oldlen, 'utf8');
  1334. buf.writeUInt32BE(newlen, p += oldlen, true);
  1335. buf.write(newPath, p += 4, newlen, 'utf8');
  1336. state.requests[reqid] = { cb: cb };
  1337. this.debug('DEBUG[SFTP]: Outgoing: Writing RENAME');
  1338. return this.push(buf);
  1339. };
  1340. SFTPStream.prototype.mkdir = function(path, attrs, cb) {
  1341. if (this.server)
  1342. throw new Error('Client-only method called in server mode');
  1343. var flags = 0;
  1344. var attrBytes = 0;
  1345. var state = this._state;
  1346. if (typeof attrs === 'function') {
  1347. cb = attrs;
  1348. attrs = undefined;
  1349. }
  1350. if (typeof attrs === 'object') {
  1351. attrs = attrsToBytes(attrs);
  1352. flags = attrs.flags;
  1353. attrBytes = attrs.nbytes;
  1354. attrs = attrs.bytes;
  1355. }
  1356. /*
  1357. uint32 id
  1358. string path
  1359. ATTRS attrs
  1360. */
  1361. var pathlen = Buffer.byteLength(path);
  1362. var p = 9;
  1363. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen + 4 + attrBytes);
  1364. buf.writeUInt32BE(buf.length - 4, 0, true);
  1365. buf[4] = REQUEST.MKDIR;
  1366. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1367. buf.writeUInt32BE(reqid, 5, true);
  1368. buf.writeUInt32BE(pathlen, p, true);
  1369. buf.write(path, p += 4, pathlen, 'utf8');
  1370. buf.writeUInt32BE(flags, p += pathlen);
  1371. if (flags) {
  1372. p += 4;
  1373. for (var i = 0, len = attrs.length; i < len; ++i)
  1374. for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
  1375. buf[p++] = attrs[i][j];
  1376. }
  1377. state.requests[reqid] = { cb: cb };
  1378. this.debug('DEBUG[SFTP]: Outgoing: Writing MKDIR');
  1379. return this.push(buf);
  1380. };
  1381. SFTPStream.prototype.rmdir = function(path, cb) {
  1382. if (this.server)
  1383. throw new Error('Client-only method called in server mode');
  1384. var state = this._state;
  1385. /*
  1386. uint32 id
  1387. string path
  1388. */
  1389. var pathlen = Buffer.byteLength(path);
  1390. var p = 9;
  1391. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
  1392. buf.writeUInt32BE(buf.length - 4, 0, true);
  1393. buf[4] = REQUEST.RMDIR;
  1394. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1395. buf.writeUInt32BE(reqid, 5, true);
  1396. buf.writeUInt32BE(pathlen, p, true);
  1397. buf.write(path, p += 4, pathlen, 'utf8');
  1398. state.requests[reqid] = { cb: cb };
  1399. this.debug('DEBUG[SFTP]: Outgoing: Writing RMDIR');
  1400. return this.push(buf);
  1401. };
  1402. SFTPStream.prototype.readdir = function(where, opts, cb) {
  1403. if (this.server)
  1404. throw new Error('Client-only method called in server mode');
  1405. var state = this._state;
  1406. var doFilter;
  1407. if (typeof opts === 'function') {
  1408. cb = opts;
  1409. opts = {};
  1410. }
  1411. if (typeof opts !== 'object')
  1412. opts = {};
  1413. doFilter = (opts && opts.full ? false : true);
  1414. if (!Buffer.isBuffer(where) && typeof where !== 'string')
  1415. throw new Error('missing directory handle or path');
  1416. if (typeof where === 'string') {
  1417. var self = this;
  1418. var entries = [];
  1419. var e = 0;
  1420. return this.opendir(where, function reread(err, handle) {
  1421. if (err)
  1422. return cb(err);
  1423. self.readdir(handle, opts, function(err, list) {
  1424. var eof = (err && err.code === STATUS_CODE.EOF);
  1425. if (err && !eof) {
  1426. return self.close(handle, function() {
  1427. cb(err);
  1428. });
  1429. } else if (eof) {
  1430. return self.close(handle, function(err) {
  1431. if (err)
  1432. return cb(err);
  1433. cb(undefined, entries);
  1434. });
  1435. }
  1436. for (var i = 0, len = list.length; i < len; ++i, ++e)
  1437. entries[e] = list[i];
  1438. reread(undefined, handle);
  1439. });
  1440. });
  1441. }
  1442. /*
  1443. uint32 id
  1444. string handle
  1445. */
  1446. var handlelen = where.length;
  1447. var p = 9;
  1448. var buf = new Buffer(4 + 1 + 4 + 4 + handlelen);
  1449. buf.writeUInt32BE(buf.length - 4, 0, true);
  1450. buf[4] = REQUEST.READDIR;
  1451. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1452. buf.writeUInt32BE(reqid, 5, true);
  1453. buf.writeUInt32BE(handlelen, p, true);
  1454. where.copy(buf, p += 4);
  1455. state.requests[reqid] = {
  1456. cb: (doFilter
  1457. ? function(err, list) {
  1458. if (err)
  1459. return cb(err);
  1460. for (var i = list.length - 1; i >= 0; --i) {
  1461. if (list[i].filename === '.' || list[i].filename === '..')
  1462. list.splice(i, 1);
  1463. }
  1464. cb(undefined, list);
  1465. }
  1466. : cb)
  1467. };
  1468. this.debug('DEBUG[SFTP]: Outgoing: Writing READDIR');
  1469. return this.push(buf);
  1470. };
  1471. SFTPStream.prototype.fstat = function(handle, cb) {
  1472. if (this.server)
  1473. throw new Error('Client-only method called in server mode');
  1474. else if (!Buffer.isBuffer(handle))
  1475. throw new Error('handle is not a Buffer');
  1476. var state = this._state;
  1477. /*
  1478. uint32 id
  1479. string handle
  1480. */
  1481. var handlelen = handle.length;
  1482. var p = 9;
  1483. var buf = new Buffer(4 + 1 + 4 + 4 + handlelen);
  1484. buf.writeUInt32BE(buf.length - 4, 0, true);
  1485. buf[4] = REQUEST.FSTAT;
  1486. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1487. buf.writeUInt32BE(reqid, 5, true);
  1488. buf.writeUInt32BE(handlelen, p, true);
  1489. handle.copy(buf, p += 4);
  1490. state.requests[reqid] = { cb: cb };
  1491. this.debug('DEBUG[SFTP]: Outgoing: Writing FSTAT');
  1492. return this.push(buf);
  1493. };
  1494. SFTPStream.prototype.stat = function(path, cb) {
  1495. if (this.server)
  1496. throw new Error('Client-only method called in server mode');
  1497. var state = this._state;
  1498. /*
  1499. uint32 id
  1500. string path
  1501. */
  1502. var pathlen = Buffer.byteLength(path);
  1503. var p = 9;
  1504. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
  1505. buf.writeUInt32BE(buf.length - 4, 0, true);
  1506. buf[4] = REQUEST.STAT;
  1507. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1508. buf.writeUInt32BE(reqid, 5, true);
  1509. buf.writeUInt32BE(pathlen, p, true);
  1510. buf.write(path, p += 4, pathlen, 'utf8');
  1511. state.requests[reqid] = { cb: cb };
  1512. this.debug('DEBUG[SFTP]: Outgoing: Writing STAT');
  1513. return this.push(buf);
  1514. };
  1515. SFTPStream.prototype.lstat = function(path, cb) {
  1516. if (this.server)
  1517. throw new Error('Client-only method called in server mode');
  1518. var state = this._state;
  1519. /*
  1520. uint32 id
  1521. string path
  1522. */
  1523. var pathlen = Buffer.byteLength(path);
  1524. var p = 9;
  1525. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
  1526. buf.writeUInt32BE(buf.length - 4, 0, true);
  1527. buf[4] = REQUEST.LSTAT;
  1528. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1529. buf.writeUInt32BE(reqid, 5, true);
  1530. buf.writeUInt32BE(pathlen, p, true);
  1531. buf.write(path, p += 4, pathlen, 'utf8');
  1532. state.requests[reqid] = { cb: cb };
  1533. this.debug('DEBUG[SFTP]: Outgoing: Writing LSTAT');
  1534. return this.push(buf);
  1535. };
  1536. SFTPStream.prototype.opendir = function(path, cb) {
  1537. if (this.server)
  1538. throw new Error('Client-only method called in server mode');
  1539. var state = this._state;
  1540. /*
  1541. uint32 id
  1542. string path
  1543. */
  1544. var pathlen = Buffer.byteLength(path);
  1545. var p = 9;
  1546. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
  1547. buf.writeUInt32BE(buf.length - 4, 0, true);
  1548. buf[4] = REQUEST.OPENDIR;
  1549. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1550. buf.writeUInt32BE(reqid, 5, true);
  1551. buf.writeUInt32BE(pathlen, p, true);
  1552. buf.write(path, p += 4, pathlen, 'utf8');
  1553. state.requests[reqid] = { cb: cb };
  1554. this.debug('DEBUG[SFTP]: Outgoing: Writing OPENDIR');
  1555. return this.push(buf);
  1556. };
  1557. SFTPStream.prototype.setstat = function(path, attrs, cb) {
  1558. if (this.server)
  1559. throw new Error('Client-only method called in server mode');
  1560. var flags = 0;
  1561. var attrBytes = 0;
  1562. var state = this._state;
  1563. if (typeof attrs === 'object') {
  1564. attrs = attrsToBytes(attrs);
  1565. flags = attrs.flags;
  1566. attrBytes = attrs.nbytes;
  1567. attrs = attrs.bytes;
  1568. } else if (typeof attrs === 'function')
  1569. cb = attrs;
  1570. /*
  1571. uint32 id
  1572. string path
  1573. ATTRS attrs
  1574. */
  1575. var pathlen = Buffer.byteLength(path);
  1576. var p = 9;
  1577. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen + 4 + attrBytes);
  1578. buf.writeUInt32BE(buf.length - 4, 0, true);
  1579. buf[4] = REQUEST.SETSTAT;
  1580. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1581. buf.writeUInt32BE(reqid, 5, true);
  1582. buf.writeUInt32BE(pathlen, p, true);
  1583. buf.write(path, p += 4, pathlen, 'utf8');
  1584. buf.writeUInt32BE(flags, p += pathlen);
  1585. if (flags) {
  1586. p += 4;
  1587. for (var i = 0, len = attrs.length; i < len; ++i)
  1588. for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
  1589. buf[p++] = attrs[i][j];
  1590. }
  1591. state.requests[reqid] = { cb: cb };
  1592. this.debug('DEBUG[SFTP]: Outgoing: Writing SETSTAT');
  1593. return this.push(buf);
  1594. };
  1595. SFTPStream.prototype.fsetstat = function(handle, attrs, cb) {
  1596. if (this.server)
  1597. throw new Error('Client-only method called in server mode');
  1598. else if (!Buffer.isBuffer(handle))
  1599. throw new Error('handle is not a Buffer');
  1600. var flags = 0;
  1601. var attrBytes = 0;
  1602. var state = this._state;
  1603. if (typeof attrs === 'object') {
  1604. attrs = attrsToBytes(attrs);
  1605. flags = attrs.flags;
  1606. attrBytes = attrs.nbytes;
  1607. attrs = attrs.bytes;
  1608. } else if (typeof attrs === 'function')
  1609. cb = attrs;
  1610. /*
  1611. uint32 id
  1612. string handle
  1613. ATTRS attrs
  1614. */
  1615. var handlelen = handle.length;
  1616. var p = 9;
  1617. var buf = new Buffer(4 + 1 + 4 + 4 + handlelen + 4 + attrBytes);
  1618. buf.writeUInt32BE(buf.length - 4, 0, true);
  1619. buf[4] = REQUEST.FSETSTAT;
  1620. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1621. buf.writeUInt32BE(reqid, 5, true);
  1622. buf.writeUInt32BE(handlelen, p, true);
  1623. handle.copy(buf, p += 4);
  1624. buf.writeUInt32BE(flags, p += handlelen);
  1625. if (flags) {
  1626. p += 4;
  1627. for (var i = 0, len = attrs.length; i < len; ++i)
  1628. for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
  1629. buf[p++] = attrs[i][j];
  1630. }
  1631. state.requests[reqid] = { cb: cb };
  1632. this.debug('DEBUG[SFTP]: Outgoing: Writing FSETSTAT');
  1633. return this.push(buf);
  1634. };
  1635. SFTPStream.prototype.futimes = function(handle, atime, mtime, cb) {
  1636. return this.fsetstat(handle, {
  1637. atime: toUnixTimestamp(atime),
  1638. mtime: toUnixTimestamp(mtime)
  1639. }, cb);
  1640. };
  1641. SFTPStream.prototype.utimes = function(path, atime, mtime, cb) {
  1642. return this.setstat(path, {
  1643. atime: toUnixTimestamp(atime),
  1644. mtime: toUnixTimestamp(mtime)
  1645. }, cb);
  1646. };
  1647. SFTPStream.prototype.fchown = function(handle, uid, gid, cb) {
  1648. return this.fsetstat(handle, {
  1649. uid: uid,
  1650. gid: gid
  1651. }, cb);
  1652. };
  1653. SFTPStream.prototype.chown = function(path, uid, gid, cb) {
  1654. return this.setstat(path, {
  1655. uid: uid,
  1656. gid: gid
  1657. }, cb);
  1658. };
  1659. SFTPStream.prototype.fchmod = function(handle, mode, cb) {
  1660. return this.fsetstat(handle, {
  1661. mode: mode
  1662. }, cb);
  1663. };
  1664. SFTPStream.prototype.chmod = function(path, mode, cb) {
  1665. return this.setstat(path, {
  1666. mode: mode
  1667. }, cb);
  1668. };
  1669. SFTPStream.prototype.readlink = function(path, cb) {
  1670. if (this.server)
  1671. throw new Error('Client-only method called in server mode');
  1672. var state = this._state;
  1673. /*
  1674. uint32 id
  1675. string path
  1676. */
  1677. var pathlen = Buffer.byteLength(path);
  1678. var p = 9;
  1679. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
  1680. buf.writeUInt32BE(buf.length - 4, 0, true);
  1681. buf[4] = REQUEST.READLINK;
  1682. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1683. buf.writeUInt32BE(reqid, 5, true);
  1684. buf.writeUInt32BE(pathlen, p, true);
  1685. buf.write(path, p += 4, pathlen, 'utf8');
  1686. state.requests[reqid] = {
  1687. cb: function(err, names) {
  1688. if (err)
  1689. return cb(err);
  1690. else if (!names || !names.length)
  1691. return cb(new Error('Response missing link info'));
  1692. cb(undefined, names[0].filename);
  1693. }
  1694. };
  1695. this.debug('DEBUG[SFTP]: Outgoing: Writing READLINK');
  1696. return this.push(buf);
  1697. };
  1698. SFTPStream.prototype.symlink = function(targetPath, linkPath, cb) {
  1699. if (this.server)
  1700. throw new Error('Client-only method called in server mode');
  1701. var state = this._state;
  1702. /*
  1703. uint32 id
  1704. string linkpath
  1705. string targetpath
  1706. */
  1707. var linklen = Buffer.byteLength(linkPath);
  1708. var targetlen = Buffer.byteLength(targetPath);
  1709. var p = 9;
  1710. var buf = new Buffer(4 + 1 + 4 + 4 + linklen + 4 + targetlen);
  1711. buf.writeUInt32BE(buf.length - 4, 0, true);
  1712. buf[4] = REQUEST.SYMLINK;
  1713. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1714. buf.writeUInt32BE(reqid, 5, true);
  1715. if (this._isOpenSSH) {
  1716. // OpenSSH has linkpath and targetpath positions switched
  1717. buf.writeUInt32BE(targetlen, p, true);
  1718. buf.write(targetPath, p += 4, targetlen, 'utf8');
  1719. buf.writeUInt32BE(linklen, p += targetlen, true);
  1720. buf.write(linkPath, p += 4, linklen, 'utf8');
  1721. } else {
  1722. buf.writeUInt32BE(linklen, p, true);
  1723. buf.write(linkPath, p += 4, linklen, 'utf8');
  1724. buf.writeUInt32BE(targetlen, p += linklen, true);
  1725. buf.write(targetPath, p += 4, targetlen, 'utf8');
  1726. }
  1727. state.requests[reqid] = { cb: cb };
  1728. this.debug('DEBUG[SFTP]: Outgoing: Writing SYMLINK');
  1729. return this.push(buf);
  1730. };
  1731. SFTPStream.prototype.realpath = function(path, cb) {
  1732. if (this.server)
  1733. throw new Error('Client-only method called in server mode');
  1734. var state = this._state;
  1735. /*
  1736. uint32 id
  1737. string path
  1738. */
  1739. var pathlen = Buffer.byteLength(path);
  1740. var p = 9;
  1741. var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
  1742. buf.writeUInt32BE(buf.length - 4, 0, true);
  1743. buf[4] = REQUEST.REALPATH;
  1744. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1745. buf.writeUInt32BE(reqid, 5, true);
  1746. buf.writeUInt32BE(pathlen, p, true);
  1747. buf.write(path, p += 4, pathlen, 'utf8');
  1748. state.requests[reqid] = {
  1749. cb: function(err, names) {
  1750. if (err)
  1751. return cb(err);
  1752. else if (!names || !names.length)
  1753. return cb(new Error('Response missing path info'));
  1754. cb(undefined, names[0].filename);
  1755. }
  1756. };
  1757. this.debug('DEBUG[SFTP]: Outgoing: Writing REALPATH');
  1758. return this.push(buf);
  1759. };
  1760. // extended requests
  1761. SFTPStream.prototype.ext_openssh_rename = function(oldPath, newPath, cb) {
  1762. var state = this._state;
  1763. if (this.server)
  1764. throw new Error('Client-only method called in server mode');
  1765. else if (!state.extensions['posix-rename@openssh.com']
  1766. || state.extensions['posix-rename@openssh.com'].indexOf('1') === -1)
  1767. throw new Error('Server does not support this extended request');
  1768. /*
  1769. uint32 id
  1770. string "posix-rename@openssh.com"
  1771. string oldpath
  1772. string newpath
  1773. */
  1774. var oldlen = Buffer.byteLength(oldPath);
  1775. var newlen = Buffer.byteLength(newPath);
  1776. var p = 9;
  1777. var buf = new Buffer(4 + 1 + 4 + 4 + 24 + 4 + oldlen + 4 + newlen);
  1778. buf.writeUInt32BE(buf.length - 4, 0, true);
  1779. buf[4] = REQUEST.EXTENDED;
  1780. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1781. buf.writeUInt32BE(reqid, 5, true);
  1782. buf.writeUInt32BE(24, p, true);
  1783. buf.write('posix-rename@openssh.com', p += 4, 24, 'ascii');
  1784. buf.writeUInt32BE(oldlen, p += 24, true);
  1785. buf.write(oldPath, p += 4, oldlen, 'utf8');
  1786. buf.writeUInt32BE(newlen, p += oldlen, true);
  1787. buf.write(newPath, p += 4, newlen, 'utf8');
  1788. state.requests[reqid] = { cb: cb };
  1789. this.debug('DEBUG[SFTP]: Outgoing: Writing posix-rename@openssh.com');
  1790. return this.push(buf);
  1791. };
  1792. SFTPStream.prototype.ext_openssh_statvfs = function(path, cb) {
  1793. var state = this._state;
  1794. if (this.server)
  1795. throw new Error('Client-only method called in server mode');
  1796. else if (!state.extensions['statvfs@openssh.com']
  1797. || state.extensions['statvfs@openssh.com'].indexOf('2') === -1)
  1798. throw new Error('Server does not support this extended request');
  1799. /*
  1800. uint32 id
  1801. string "statvfs@openssh.com"
  1802. string path
  1803. */
  1804. var pathlen = Buffer.byteLength(path);
  1805. var p = 9;
  1806. var buf = new Buffer(4 + 1 + 4 + 4 + 19 + 4 + pathlen);
  1807. buf.writeUInt32BE(buf.length - 4, 0, true);
  1808. buf[4] = REQUEST.EXTENDED;
  1809. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1810. buf.writeUInt32BE(reqid, 5, true);
  1811. buf.writeUInt32BE(19, p, true);
  1812. buf.write('statvfs@openssh.com', p += 4, 19, 'ascii');
  1813. buf.writeUInt32BE(pathlen, p += 19, true);
  1814. buf.write(path, p += 4, pathlen, 'utf8');
  1815. state.requests[reqid] = {
  1816. extended: 'statvfs@openssh.com',
  1817. cb: cb
  1818. };
  1819. this.debug('DEBUG[SFTP]: Outgoing: Writing statvfs@openssh.com');
  1820. return this.push(buf);
  1821. };
  1822. SFTPStream.prototype.ext_openssh_fstatvfs = function(handle, cb) {
  1823. var state = this._state;
  1824. if (this.server)
  1825. throw new Error('Client-only method called in server mode');
  1826. else if (!state.extensions['fstatvfs@openssh.com']
  1827. || state.extensions['fstatvfs@openssh.com'].indexOf('2') === -1)
  1828. throw new Error('Server does not support this extended request');
  1829. else if (!Buffer.isBuffer(handle))
  1830. throw new Error('handle is not a Buffer');
  1831. /*
  1832. uint32 id
  1833. string "fstatvfs@openssh.com"
  1834. string handle
  1835. */
  1836. var handlelen = handle.length;
  1837. var p = 9;
  1838. var buf = new Buffer(4 + 1 + 4 + 4 + 20 + 4 + handlelen);
  1839. buf.writeUInt32BE(buf.length - 4, 0, true);
  1840. buf[4] = REQUEST.EXTENDED;
  1841. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1842. buf.writeUInt32BE(reqid, 5, true);
  1843. buf.writeUInt32BE(20, p, true);
  1844. buf.write('fstatvfs@openssh.com', p += 4, 20, 'ascii');
  1845. buf.writeUInt32BE(handlelen, p += 20, true);
  1846. buf.write(handle, p += 4, handlelen, 'utf8');
  1847. state.requests[reqid] = {
  1848. extended: 'fstatvfs@openssh.com',
  1849. cb: cb
  1850. };
  1851. this.debug('DEBUG[SFTP]: Outgoing: Writing fstatvfs@openssh.com');
  1852. return this.push(buf);
  1853. };
  1854. SFTPStream.prototype.ext_openssh_hardlink = function(oldPath, newPath, cb) {
  1855. var state = this._state;
  1856. if (this.server)
  1857. throw new Error('Client-only method called in server mode');
  1858. else if (!state.extensions['hardlink@openssh.com']
  1859. || state.extensions['hardlink@openssh.com'].indexOf('1') === -1)
  1860. throw new Error('Server does not support this extended request');
  1861. /*
  1862. uint32 id
  1863. string "hardlink@openssh.com"
  1864. string oldpath
  1865. string newpath
  1866. */
  1867. var oldlen = Buffer.byteLength(oldPath);
  1868. var newlen = Buffer.byteLength(newPath);
  1869. var p = 9;
  1870. var buf = new Buffer(4 + 1 + 4 + 4 + 20 + 4 + oldlen + 4 + newlen);
  1871. buf.writeUInt32BE(buf.length - 4, 0, true);
  1872. buf[4] = REQUEST.EXTENDED;
  1873. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1874. buf.writeUInt32BE(reqid, 5, true);
  1875. buf.writeUInt32BE(20, p, true);
  1876. buf.write('hardlink@openssh.com', p += 4, 20, 'ascii');
  1877. buf.writeUInt32BE(oldlen, p += 20, true);
  1878. buf.write(oldPath, p += 4, oldlen, 'utf8');
  1879. buf.writeUInt32BE(newlen, p += oldlen, true);
  1880. buf.write(newPath, p += 4, newlen, 'utf8');
  1881. state.requests[reqid] = { cb: cb };
  1882. this.debug('DEBUG[SFTP]: Outgoing: Writing hardlink@openssh.com');
  1883. return this.push(buf);
  1884. };
  1885. SFTPStream.prototype.ext_openssh_fsync = function(handle, cb) {
  1886. var state = this._state;
  1887. if (this.server)
  1888. throw new Error('Client-only method called in server mode');
  1889. else if (!state.extensions['fsync@openssh.com']
  1890. || state.extensions['fsync@openssh.com'].indexOf('1') === -1)
  1891. throw new Error('Server does not support this extended request');
  1892. else if (!Buffer.isBuffer(handle))
  1893. throw new Error('handle is not a Buffer');
  1894. /*
  1895. uint32 id
  1896. string "fsync@openssh.com"
  1897. string handle
  1898. */
  1899. var handlelen = handle.length;
  1900. var p = 9;
  1901. var buf = new Buffer(4 + 1 + 4 + 4 + 17 + 4 + handlelen);
  1902. buf.writeUInt32BE(buf.length - 4, 0, true);
  1903. buf[4] = REQUEST.EXTENDED;
  1904. var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
  1905. buf.writeUInt32BE(reqid, 5, true);
  1906. buf.writeUInt32BE(17, p, true);
  1907. buf.write('fsync@openssh.com', p += 4, 17, 'ascii');
  1908. buf.writeUInt32BE(handlelen, p += 17, true);
  1909. buf.write(handle, p += 4, handlelen, 'utf8');
  1910. state.requests[reqid] = { cb: cb };
  1911. this.debug('DEBUG[SFTP]: Outgoing: Writing fsync@openssh.com');
  1912. return this.push(buf);
  1913. };
  1914. // server
  1915. SFTPStream.prototype.status = function(id, code, message, lang) {
  1916. if (!this.server)
  1917. throw new Error('Server-only method called in client mode');
  1918. if (!STATUS_CODE[code] || typeof code !== 'number')
  1919. throw new Error('Bad status code: ' + code);
  1920. message || (message = '');
  1921. lang || (lang = '');
  1922. var msgLen = Buffer.byteLength(message);
  1923. var langLen = Buffer.byteLength(lang);
  1924. var buf = new Buffer(4 + 1 + 4 + 4 + 4 + msgLen + 4 + langLen);
  1925. buf.writeUInt32BE(buf.length - 4, 0, true);
  1926. buf[4] = RESPONSE.STATUS;
  1927. buf.writeUInt32BE(id, 5, true);
  1928. buf.writeUInt32BE(code, 9, true);
  1929. buf.writeUInt32BE(msgLen, 13, true);
  1930. if (msgLen)
  1931. buf.write(message, 17, msgLen, 'utf8');
  1932. buf.writeUInt32BE(langLen, 17 + msgLen, true);
  1933. if (langLen)
  1934. buf.write(lang, 17 + msgLen + 4, langLen, 'ascii');
  1935. this.debug('DEBUG[SFTP]: Outgoing: Writing STATUS');
  1936. return this.push(buf);
  1937. };
  1938. SFTPStream.prototype.handle = function(id, handle) {
  1939. if (!this.server)
  1940. throw new Error('Server-only method called in client mode');
  1941. if (!Buffer.isBuffer(handle))
  1942. throw new Error('handle is not a Buffer');
  1943. var handleLen = handle.length;
  1944. if (handleLen > 256)
  1945. throw new Error('handle too large (> 256 bytes)');
  1946. var buf = new Buffer(4 + 1 + 4 + 4 + handleLen);
  1947. buf.writeUInt32BE(buf.length - 4, 0, true);
  1948. buf[4] = RESPONSE.HANDLE;
  1949. buf.writeUInt32BE(id, 5, true);
  1950. buf.writeUInt32BE(handleLen, 9, true);
  1951. if (handleLen)
  1952. handle.copy(buf, 13);
  1953. this.debug('DEBUG[SFTP]: Outgoing: Writing HANDLE');
  1954. return this.push(buf);
  1955. };
  1956. SFTPStream.prototype.data = function(id, data, encoding) {
  1957. if (!this.server)
  1958. throw new Error('Server-only method called in client mode');
  1959. var isBuffer = Buffer.isBuffer(data);
  1960. if (!isBuffer && typeof data !== 'string')
  1961. throw new Error('data is not a Buffer or string');
  1962. if (!isBuffer)
  1963. encoding || (encoding = 'utf8');
  1964. var dataLen = (isBuffer ? data.length : Buffer.byteLength(data, encoding));
  1965. var buf = new Buffer(4 + 1 + 4 + 4 + dataLen);
  1966. buf.writeUInt32BE(buf.length - 4, 0, true);
  1967. buf[4] = RESPONSE.DATA;
  1968. buf.writeUInt32BE(id, 5, true);
  1969. buf.writeUInt32BE(dataLen, 9, true);
  1970. if (dataLen) {
  1971. if (isBuffer)
  1972. data.copy(buf, 13);
  1973. else
  1974. buf.write(data, 13, dataLen, encoding);
  1975. }
  1976. this.debug('DEBUG[SFTP]: Outgoing: Writing DATA');
  1977. return this.push(buf);
  1978. };
  1979. SFTPStream.prototype.name = function(id, names) {
  1980. if (!this.server)
  1981. throw new Error('Server-only method called in client mode');
  1982. if (!Array.isArray(names) && typeof names === 'object')
  1983. names = [ names ];
  1984. else if (!Array.isArray(names))
  1985. throw new Error('names is not an object or array');
  1986. var count = names.length;
  1987. var namesLen = 0;
  1988. var nameAttrs;
  1989. var attrs = [];
  1990. var name;
  1991. var filename;
  1992. var longname;
  1993. var attr;
  1994. var len;
  1995. var len2;
  1996. var buf;
  1997. var p;
  1998. var i;
  1999. var j;
  2000. var k;
  2001. for (i = 0; i < count; ++i) {
  2002. name = names[i];
  2003. filename = (!name || !name.filename || typeof name.filename !== 'string'
  2004. ? ''
  2005. : name.filename);
  2006. namesLen += 4 + Buffer.byteLength(filename);
  2007. longname = (!name || !name.longname || typeof name.longname !== 'string'
  2008. ? ''
  2009. : name.longname);
  2010. namesLen += 4 + Buffer.byteLength(longname);
  2011. if (typeof name.attrs === 'object') {
  2012. nameAttrs = attrsToBytes(name.attrs);
  2013. namesLen += 4 + nameAttrs.nbytes;
  2014. attrs.push(nameAttrs);
  2015. } else {
  2016. namesLen += 4;
  2017. attrs.push(null);
  2018. }
  2019. }
  2020. buf = new Buffer(4 + 1 + 4 + 4 + namesLen);
  2021. buf.writeUInt32BE(buf.length - 4, 0, true);
  2022. buf[4] = RESPONSE.NAME;
  2023. buf.writeUInt32BE(id, 5, true);
  2024. buf.writeUInt32BE(count, 9, true);
  2025. p = 13;
  2026. for (i = 0; i < count; ++i) {
  2027. name = names[i];
  2028. filename = (!name || !name.filename || typeof name.filename !== 'string'
  2029. ? ''
  2030. : name.filename);
  2031. len = Buffer.byteLength(filename);
  2032. buf.writeUInt32BE(len, p, true);
  2033. p += 4;
  2034. if (len) {
  2035. buf.write(filename, p, len, 'utf8');
  2036. p += len;
  2037. }
  2038. longname = (!name || !name.longname || typeof name.longname !== 'string'
  2039. ? ''
  2040. : name.longname);
  2041. len = Buffer.byteLength(longname);
  2042. buf.writeUInt32BE(len, p, true);
  2043. p += 4;
  2044. if (len) {
  2045. buf.write(longname, p, len, 'utf8');
  2046. p += len;
  2047. }
  2048. attr = attrs[i];
  2049. if (attr) {
  2050. buf.writeUInt32BE(attr.flags, p, true);
  2051. p += 4;
  2052. if (attr.flags && attr.bytes) {
  2053. var bytes = attr.bytes;
  2054. for (j = 0, len = bytes.length; j < len; ++j)
  2055. for (k = 0, len2 = bytes[j].length; k < len2; ++k)
  2056. buf[p++] = bytes[j][k];
  2057. }
  2058. } else {
  2059. buf.writeUInt32BE(0, p, true);
  2060. p += 4;
  2061. }
  2062. }
  2063. this.debug('DEBUG[SFTP]: Outgoing: Writing NAME');
  2064. return this.push(buf);
  2065. };
  2066. SFTPStream.prototype.attrs = function(id, attrs) {
  2067. if (!this.server)
  2068. throw new Error('Server-only method called in client mode');
  2069. if (typeof attrs !== 'object')
  2070. throw new Error('attrs is not an object');
  2071. var info = attrsToBytes(attrs);
  2072. var buf = new Buffer(4 + 1 + 4 + 4 + info.nbytes);
  2073. var p = 13;
  2074. buf.writeUInt32BE(buf.length - 4, 0, true);
  2075. buf[4] = RESPONSE.ATTRS;
  2076. buf.writeUInt32BE(id, 5, true);
  2077. buf.writeUInt32BE(info.flags, 9, true);
  2078. if (info.flags && info.bytes) {
  2079. var bytes = info.bytes;
  2080. for (var j = 0, len = bytes.length; j < len; ++j)
  2081. for (var k = 0, len2 = bytes[j].length; k < len2; ++k)
  2082. buf[p++] = bytes[j][k];
  2083. }
  2084. this.debug('DEBUG[SFTP]: Outgoing: Writing ATTRS');
  2085. return this.push(buf);
  2086. };
  2087. function readAttrs(buf, p, stream, callback) {
  2088. /*
  2089. uint32 flags
  2090. uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
  2091. uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID
  2092. uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID
  2093. uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
  2094. uint32 atime present only if flag SSH_FILEXFER_ACMODTIME
  2095. uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME
  2096. uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
  2097. string extended_type
  2098. string extended_data
  2099. ... more extended data (extended_type - extended_data pairs),
  2100. so that number of pairs equals extended_count
  2101. */
  2102. var flags = buf.readUInt32BE(p, true);
  2103. var attrs = new Stats();
  2104. p += 4;
  2105. if (flags & ATTR.SIZE) {
  2106. var size = readUInt64BE(buf, p, stream, callback);
  2107. if (size === false)
  2108. return false;
  2109. attrs.size = size;
  2110. p += 8;
  2111. }
  2112. if (flags & ATTR.UIDGID) {
  2113. var uid;
  2114. var gid;
  2115. uid = readInt(buf, p, this, callback);
  2116. if (uid === false)
  2117. return false;
  2118. attrs.uid = uid;
  2119. p += 4;
  2120. gid = readInt(buf, p, this, callback);
  2121. if (gid === false)
  2122. return false;
  2123. attrs.gid = gid;
  2124. p += 4;
  2125. }
  2126. if (flags & ATTR.PERMISSIONS) {
  2127. var mode = readInt(buf, p, this, callback);
  2128. if (mode === false)
  2129. return false;
  2130. attrs.mode = mode;
  2131. // backwards compatibility
  2132. attrs.permissions = mode;
  2133. p += 4;
  2134. }
  2135. if (flags & ATTR.ACMODTIME) {
  2136. var atime;
  2137. var mtime;
  2138. atime = readInt(buf, p, this, callback);
  2139. if (atime === false)
  2140. return false;
  2141. attrs.atime = atime;
  2142. p += 4;
  2143. mtime = readInt(buf, p, this, callback);
  2144. if (mtime === false)
  2145. return false;
  2146. attrs.mtime = mtime;
  2147. p += 4;
  2148. }
  2149. if (flags & ATTR.EXTENDED) {
  2150. // TODO: read/parse extended data
  2151. var extcount = readInt(buf, p, this, callback);
  2152. if (extcount === false)
  2153. return false;
  2154. p += 4;
  2155. for (var i = 0, len; i < extcount; ++i) {
  2156. len = readInt(buf, p, this, callback);
  2157. if (len === false)
  2158. return false;
  2159. p += 4 + len;
  2160. }
  2161. }
  2162. buf._pos = p;
  2163. return attrs;
  2164. }
  2165. function readUInt64BE(buffer, p, stream, callback) {
  2166. if ((buffer.length - p) < 8) {
  2167. stream && stream._cleanup(callback);
  2168. return false;
  2169. }
  2170. var val = 0;
  2171. for (var len = p + 8; p < len; ++p) {
  2172. val *= 256;
  2173. val += buffer[p];
  2174. }
  2175. buffer._pos = p;
  2176. return val;
  2177. }
  2178. function attrsToBytes(attrs) {
  2179. var flags = 0;
  2180. var attrBytes = 0;
  2181. var ret = [];
  2182. var i = 0;
  2183. if (typeof attrs.size === 'number') {
  2184. flags |= ATTR.SIZE;
  2185. attrBytes += 8;
  2186. var sizeBytes = new Array(8);
  2187. var val = attrs.size;
  2188. for (i = 7; i >= 0; --i) {
  2189. sizeBytes[i] = val & 0xFF;
  2190. val /= 256;
  2191. }
  2192. ret.push(sizeBytes);
  2193. }
  2194. if (typeof attrs.uid === 'number' && typeof attrs.gid === 'number') {
  2195. flags |= ATTR.UIDGID;
  2196. attrBytes += 8;
  2197. ret.push([(attrs.uid >> 24) & 0xFF, (attrs.uid >> 16) & 0xFF,
  2198. (attrs.uid >> 8) & 0xFF, attrs.uid & 0xFF]);
  2199. ret.push([(attrs.gid >> 24) & 0xFF, (attrs.gid >> 16) & 0xFF,
  2200. (attrs.gid >> 8) & 0xFF, attrs.gid & 0xFF]);
  2201. }
  2202. if (typeof attrs.permissions === 'number'
  2203. || typeof attrs.permissions === 'string'
  2204. || typeof attrs.mode === 'number'
  2205. || typeof attrs.mode === 'string') {
  2206. var mode = modeNum(attrs.mode || attrs.permissions);
  2207. flags |= ATTR.PERMISSIONS;
  2208. attrBytes += 4;
  2209. ret.push([(mode >> 24) & 0xFF,
  2210. (mode >> 16) & 0xFF,
  2211. (mode >> 8) & 0xFF,
  2212. mode & 0xFF]);
  2213. }
  2214. if ((typeof attrs.atime === 'number' || isDate(attrs.atime))
  2215. && (typeof attrs.mtime === 'number' || isDate(attrs.mtime))) {
  2216. var atime = toUnixTimestamp(attrs.atime);
  2217. var mtime = toUnixTimestamp(attrs.mtime);
  2218. flags |= ATTR.ACMODTIME;
  2219. attrBytes += 8;
  2220. ret.push([(atime >> 24) & 0xFF, (atime >> 16) & 0xFF,
  2221. (atime >> 8) & 0xFF, atime & 0xFF]);
  2222. ret.push([(mtime >> 24) & 0xFF, (mtime >> 16) & 0xFF,
  2223. (mtime >> 8) & 0xFF, mtime & 0xFF]);
  2224. }
  2225. // TODO: extended attributes
  2226. return { flags: flags, nbytes: attrBytes, bytes: ret };
  2227. }
  2228. function toUnixTimestamp(time) {
  2229. if (typeof time === 'number' && !isNaN(time))
  2230. return time;
  2231. else if (isDate(time))
  2232. return parseInt(time.getTime() / 1000, 10);
  2233. throw new Error('Cannot parse time: ' + time);
  2234. }
  2235. function modeNum(mode) {
  2236. if (typeof mode === 'number' && !isNaN(mode))
  2237. return mode;
  2238. else if (typeof mode === 'string')
  2239. return modeNum(parseInt(mode, 8));
  2240. throw new Error('Cannot parse mode: ' + mode);
  2241. }
  2242. var stringFlagMap = {
  2243. 'r': OPEN_MODE.READ,
  2244. 'r+': OPEN_MODE.READ | OPEN_MODE.WRITE,
  2245. 'w': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE,
  2246. 'wx': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
  2247. 'xw': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
  2248. 'w+': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE,
  2249. 'wx+': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
  2250. | OPEN_MODE.EXCL,
  2251. 'xw+': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
  2252. | OPEN_MODE.EXCL,
  2253. 'a': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.WRITE,
  2254. 'ax': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
  2255. 'xa': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
  2256. 'a+': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE,
  2257. 'ax+': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
  2258. | OPEN_MODE.EXCL,
  2259. 'xa+': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
  2260. | OPEN_MODE.EXCL
  2261. };
  2262. var stringFlagMapKeys = Object.keys(stringFlagMap);
  2263. function stringToFlags(str) {
  2264. var flags = stringFlagMap[str];
  2265. if (flags !== undefined)
  2266. return flags;
  2267. return null;
  2268. }
  2269. SFTPStream.stringToFlags = stringToFlags;
  2270. function flagsToString(flags) {
  2271. for (var i = 0; i < stringFlagMapKeys.length; ++i) {
  2272. var key = stringFlagMapKeys[i];
  2273. if (stringFlagMap[key] === flags)
  2274. return key;
  2275. }
  2276. return null;
  2277. }
  2278. SFTPStream.flagsToString = flagsToString;
  2279. function Stats(initial) {
  2280. this.mode = (initial && initial.mode);
  2281. this.permissions = this.mode; // backwards compatiblity
  2282. this.uid = (initial && initial.uid);
  2283. this.gid = (initial && initial.gid);
  2284. this.size = (initial && initial.size);
  2285. this.atime = (initial && initial.atime);
  2286. this.mtime = (initial && initial.mtime);
  2287. }
  2288. Stats.prototype._checkModeProperty = function(property) {
  2289. return ((this.mode & constants.S_IFMT) === property);
  2290. };
  2291. Stats.prototype.isDirectory = function() {
  2292. return this._checkModeProperty(constants.S_IFDIR);
  2293. };
  2294. Stats.prototype.isFile = function() {
  2295. return this._checkModeProperty(constants.S_IFREG);
  2296. };
  2297. Stats.prototype.isBlockDevice = function() {
  2298. return this._checkModeProperty(constants.S_IFBLK);
  2299. };
  2300. Stats.prototype.isCharacterDevice = function() {
  2301. return this._checkModeProperty(constants.S_IFCHR);
  2302. };
  2303. Stats.prototype.isSymbolicLink = function() {
  2304. return this._checkModeProperty(constants.S_IFLNK);
  2305. };
  2306. Stats.prototype.isFIFO = function() {
  2307. return this._checkModeProperty(constants.S_IFIFO);
  2308. };
  2309. Stats.prototype.isSocket = function() {
  2310. return this._checkModeProperty(constants.S_IFSOCK);
  2311. };
  2312. SFTPStream.Stats = Stats;
  2313. // ReadStream-related
  2314. var kMinPoolSpace = 128;
  2315. var pool;
  2316. function allocNewPool(poolSize) {
  2317. pool = new Buffer(poolSize);
  2318. pool.used = 0;
  2319. }
  2320. function ReadStream(sftp, path, options) {
  2321. if (!(this instanceof ReadStream))
  2322. return new ReadStream(sftp, path, options);
  2323. var self = this;
  2324. if (options === undefined)
  2325. options = {};
  2326. else if (typeof options === 'string')
  2327. options = { encoding: options };
  2328. else if (options === null || typeof options !== 'object')
  2329. throw new TypeError('"options" argument must be a string or an object');
  2330. else
  2331. options = Object.create(options);
  2332. // a little bit bigger buffer and water marks by default
  2333. if (options.highWaterMark === undefined)
  2334. options.highWaterMark = 64 * 1024;
  2335. ReadableStream.call(this, options);
  2336. this.path = path;
  2337. this.handle = options.handle === undefined ? null : options.handle;
  2338. this.flags = options.flags === undefined ? 'r' : options.flags;
  2339. this.mode = options.mode === undefined ? 438/*0666*/ : options.mode;
  2340. this.start = options.start === undefined ? undefined : options.start;
  2341. this.end = options.end === undefined ? undefined : options.end;
  2342. this.autoClose = options.autoClose === undefined ? true : options.autoClose;
  2343. this.pos = 0;
  2344. this.sftp = sftp;
  2345. if (this.start !== undefined) {
  2346. if (typeof this.start !== 'number')
  2347. throw new TypeError('start must be a Number');
  2348. if (this.end === undefined)
  2349. this.end = Infinity;
  2350. else if (typeof this.end !== 'number')
  2351. throw new TypeError('end must be a Number');
  2352. if (this.start > this.end)
  2353. throw new Error('start must be <= end');
  2354. else if (this.start < 0)
  2355. throw new Error('start must be >= zero');
  2356. this.pos = this.start;
  2357. }
  2358. this.on('end', function() {
  2359. if (self.autoClose) {
  2360. self.destroy();
  2361. }
  2362. });
  2363. if (!Buffer.isBuffer(this.handle))
  2364. this.open();
  2365. }
  2366. inherits(ReadStream, ReadableStream);
  2367. ReadStream.prototype.open = function() {
  2368. var self = this;
  2369. this.sftp.open(this.path, this.flags, this.mode, function(er, handle) {
  2370. if (er) {
  2371. self.emit('error', er);
  2372. this.destroyed = this.closed = true;
  2373. self.emit('close');
  2374. return;
  2375. }
  2376. self.handle = handle;
  2377. self.emit('open', handle);
  2378. // start the flow of data.
  2379. self.read();
  2380. });
  2381. };
  2382. ReadStream.prototype._read = function(n) {
  2383. if (!Buffer.isBuffer(this.handle)) {
  2384. return this.once('open', function() {
  2385. this._read(n);
  2386. });
  2387. }
  2388. if (this.destroyed)
  2389. return;
  2390. if (!pool || pool.length - pool.used < kMinPoolSpace) {
  2391. // discard the old pool.
  2392. pool = null;
  2393. allocNewPool(this._readableState.highWaterMark);
  2394. }
  2395. // Grab another reference to the pool in the case that while we're
  2396. // in the thread pool another read() finishes up the pool, and
  2397. // allocates a new one.
  2398. var thisPool = pool;
  2399. var toRead = Math.min(pool.length - pool.used, n);
  2400. var start = pool.used;
  2401. if (this.end !== undefined)
  2402. toRead = Math.min(this.end - this.pos + 1, toRead);
  2403. // already read everything we were supposed to read!
  2404. // treat as EOF.
  2405. if (toRead <= 0)
  2406. return this.push(null);
  2407. // the actual read.
  2408. var self = this;
  2409. this.sftp.readData(this.handle, pool, pool.used, toRead, this.pos, onread);
  2410. // move the pool positions, and internal position for reading.
  2411. this.pos += toRead;
  2412. pool.used += toRead;
  2413. function onread(er, bytesRead) {
  2414. if (er) {
  2415. if (self.autoClose)
  2416. self.destroy();
  2417. self.emit('error', er);
  2418. } else {
  2419. var b = null;
  2420. if (bytesRead > 0)
  2421. b = thisPool.slice(start, start + bytesRead);
  2422. self.push(b);
  2423. }
  2424. }
  2425. };
  2426. ReadStream.prototype.destroy = function() {
  2427. if (this.destroyed)
  2428. return;
  2429. this.destroyed = true;
  2430. if (Buffer.isBuffer(this.handle))
  2431. this.close();
  2432. };
  2433. ReadStream.prototype.close = function(cb) {
  2434. var self = this;
  2435. if (cb)
  2436. this.once('close', cb);
  2437. if (this.closed || !Buffer.isBuffer(this.handle)) {
  2438. if (!Buffer.isBuffer(this.handle)) {
  2439. this.once('open', close);
  2440. return;
  2441. }
  2442. return process.nextTick(this.emit.bind(this, 'close'));
  2443. }
  2444. this.closed = true;
  2445. close();
  2446. function close(handle) {
  2447. self.sftp.close(handle || self.handle, function(er) {
  2448. if (er)
  2449. self.emit('error', er);
  2450. else
  2451. self.emit('close');
  2452. });
  2453. self.handle = null;
  2454. }
  2455. };
  2456. function WriteStream(sftp, path, options) {
  2457. if (!(this instanceof WriteStream))
  2458. return new WriteStream(sftp, path, options);
  2459. if (options === undefined)
  2460. options = {};
  2461. else if (typeof options === 'string')
  2462. options = { encoding: options };
  2463. else if (options === null || typeof options !== 'object')
  2464. throw new TypeError('"options" argument must be a string or an object');
  2465. else
  2466. options = Object.create(options);
  2467. WritableStream.call(this, options);
  2468. this.path = path;
  2469. this.handle = options.handle === undefined ? null : options.handle;
  2470. this.flags = options.flags === undefined ? 'w' : options.flags;
  2471. this.mode = options.mode === undefined ? 438/*0666*/ : options.mode;
  2472. this.start = options.start === undefined ? undefined : options.start;
  2473. this.autoClose = options.autoClose === undefined ? true : options.autoClose;
  2474. this.pos = 0;
  2475. this.bytesWritten = 0;
  2476. this.sftp = sftp;
  2477. if (this.start !== undefined) {
  2478. if (typeof this.start !== 'number')
  2479. throw new TypeError('start must be a Number');
  2480. if (this.start < 0)
  2481. throw new Error('start must be >= zero');
  2482. this.pos = this.start;
  2483. }
  2484. if (options.encoding)
  2485. this.setDefaultEncoding(options.encoding);
  2486. if (!Buffer.isBuffer(this.handle))
  2487. this.open();
  2488. // dispose on finish.
  2489. this.once('finish', function onclose() {
  2490. if (this.autoClose)
  2491. this.close();
  2492. });
  2493. }
  2494. inherits(WriteStream, WritableStream);
  2495. WriteStream.prototype.open = function() {
  2496. var self = this;
  2497. this.sftp.open(this.path, this.flags, this.mode, function(er, handle) {
  2498. if (er) {
  2499. self.emit('error', er);
  2500. if (self.autoClose) {
  2501. self.destroyed = self.closed = true;
  2502. self.emit('close');
  2503. }
  2504. return;
  2505. }
  2506. self.handle = handle;
  2507. self.sftp.fchmod(handle, self.mode, function tryAgain(err) {
  2508. if (err) {
  2509. // Try chmod() for sftp servers that may not support fchmod() for
  2510. // whatever reason
  2511. self.sftp.chmod(self.path, self.mode, function(err_) {
  2512. tryAgain();
  2513. });
  2514. return;
  2515. }
  2516. // SFTPv3 requires absolute offsets, no matter the open flag used
  2517. if (self.flags[0] === 'a') {
  2518. self.sftp.fstat(handle, function tryStat(err, st) {
  2519. if (err) {
  2520. // Try stat() for sftp servers that may not support fstat() for
  2521. // whatever reason
  2522. self.sftp.stat(self.path, function(err_, st_) {
  2523. if (err_) {
  2524. self.destroy();
  2525. self.emit('error', err);
  2526. return;
  2527. }
  2528. tryStat(null, st_);
  2529. });
  2530. return;
  2531. }
  2532. self.pos = st.size;
  2533. self.emit('open', handle);
  2534. });
  2535. return;
  2536. }
  2537. self.emit('open', handle);
  2538. });
  2539. });
  2540. };
  2541. WriteStream.prototype._write = function(data, encoding, cb) {
  2542. if (!Buffer.isBuffer(data))
  2543. return this.emit('error', new Error('Invalid data'));
  2544. if (!Buffer.isBuffer(this.handle)) {
  2545. return this.once('open', function() {
  2546. this._write(data, encoding, cb);
  2547. });
  2548. }
  2549. var self = this;
  2550. this.sftp.writeData(this.handle,
  2551. data,
  2552. 0,
  2553. data.length,
  2554. this.pos,
  2555. function(er, bytes) {
  2556. if (er) {
  2557. if (self.autoClose)
  2558. self.destroy();
  2559. return cb(er);
  2560. }
  2561. self.bytesWritten += bytes;
  2562. cb();
  2563. });
  2564. this.pos += data.length;
  2565. };
  2566. WriteStream.prototype._writev = function(data, cb) {
  2567. if (!Buffer.isBuffer(this.handle)) {
  2568. return this.once('open', function() {
  2569. this._writev(data, cb);
  2570. });
  2571. }
  2572. var sftp = this.sftp;
  2573. var handle = this.handle;
  2574. var writesLeft = data.length;
  2575. var self = this;
  2576. for (var i = 0; i < data.length; ++i) {
  2577. var chunk = data[i].chunk;
  2578. sftp.writeData(handle, chunk, 0, chunk.length, this.pos, onwrite);
  2579. this.pos += chunk.length;
  2580. }
  2581. function onwrite(er, bytes) {
  2582. if (er) {
  2583. self.destroy();
  2584. return cb(er);
  2585. }
  2586. self.bytesWritten += bytes;
  2587. if (--writesLeft === 0)
  2588. cb();
  2589. }
  2590. };
  2591. WriteStream.prototype.destroy = ReadStream.prototype.destroy;
  2592. WriteStream.prototype.close = ReadStream.prototype.close;
  2593. // There is no shutdown() for files.
  2594. WriteStream.prototype.destroySoon = WriteStream.prototype.end;
  2595. module.exports = SFTPStream;