Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

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